91欧美超碰AV自拍|国产成年人性爱视频免费看|亚洲 日韩 欧美一厂二区入|人人看人人爽人人操aV|丝袜美腿视频一区二区在线看|人人操人人爽人人爱|婷婷五月天超碰|97色色欧美亚州A√|另类A√无码精品一级av|欧美特级日韩特级

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

深度學(xué)習(xí)Pytorch翻車記錄:單卡改多卡踩坑記

深度學(xué)習(xí)自然語言處理 ? 來源:深度學(xué)習(xí)自然語言處理 ? 作者:喲林小平 ? 2021-01-18 17:06 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

先說明一下背景,目前正在魔改以下這篇論文的代碼:

https://github.com/QipengGuo/GraphWriter-DGLgithub.com

由于每次完成實(shí)驗(yàn)需要5個小時(baseline),自己的模型需要更久(2倍),非常不利于調(diào)參和發(fā)現(xiàn)問題,所以開始嘗試使用多卡加速。

torch.nn.DataParallel ==> 簡稱 DP

torch.nn.parallel.DistributedDataParallel ==> 簡稱DDP

一開始采用dp試圖加速,結(jié)果因?yàn)閐gl的實(shí)現(xiàn)(每個batch的點(diǎn)都會打包進(jìn)一個batch,從而不可分割),而torch.nn.DataParallel的實(shí)現(xiàn)是把一個batch切分成更小,再加上他的加速性能也不如ddp,所以我開始嘗試魔改成ddp。

另外,作者在實(shí)現(xiàn)Sampler的時候是繼承了torch.utils.data.Sampler這個類的,目的在于agenda數(shù)據(jù)集的文本長度嚴(yán)重不均衡,如下:

68176276-58b2-11eb-8b86-12bb97331649.jpg

為了讓模型更快train完,把長度相近的文本打包成一個batch(溫馨提醒,torchtext也有相關(guān)的類 bucketiterator[1],大概形式如下:

class BucketSampler(torch.utils.data.Sampler):
    def __init__(self, data_source, batch_size=32):
        self.data_source = data_source
        self.batch_size = batch_size 

    def __iter__(self):
        idxs, lens, batch, middle_batch_size, long_batch_size = basesampler(self.data_source , self.batch_size)
        for idx in idxs:
            batch.append(idx)
            mlen = max([0]+[lens[x] for x in batch])
            #if (mlen<100 and len(batch) == 32) or (mlen>100 and mlen<220 and len(batch) >= 24) or (mlen>220 and len(batch)>=8) or len(batch)==32:
            if (mlen<100 and len(batch) == self.batch_size) or (mlen>100 and mlen<220 and len(batch) >= middle_batch_size) or (mlen>220 and len(batch)>=long_batch_size) or len(batch)==self.batch_size:
                yield batch
                batch = []
        if len(batch) > 0:
            yield batch

    def __len__(self):
        return (len(self.data_source)+self.batch_size-1)//self.batch_size

這是背景。

寫bug第一步:繼承DistributedSampler的漏洞百出

我一開始理想當(dāng)然的把作者的sampler源碼crtl-cv下來,唯獨(dú)只改動了這里:

class DDPBaseBucketSampler(torch.utils.data.distributed.DistributedSampler):

隨后就發(fā)現(xiàn)了幾個問題:

  • dataloader不會發(fā)包;
  • dataloader給每個進(jìn)程發(fā)的是完整的數(shù)據(jù),按武德來說,應(yīng)該是1/n的數(shù)據(jù),n為你設(shè)置的gpu數(shù)量;

然后我就開始看起了源碼[2],很快?。?/p>

 def __iter__(self) -> Iterator[T_co]:
        if self.shuffle:
            # deterministically shuffle based on epoch and seed
            g = torch.Generator()
            g.manual_seed(self.seed + self.epoch)
            indices = torch.randperm(len(self.dataset), generator=g).tolist()  # type: ignore
        else:
            indices = list(range(len(self.dataset)))  # type: ignore

        if not self.drop_last:
            # add extra samples to make it evenly divisible
            padding_size = self.total_size - len(indices)
            if padding_size <= len(indices):
                indices += indices[:padding_size]
            else:
                indices += (indices * math.ceil(padding_size / len(indices)))[:padding_size]
        else:
            # remove tail of data to make it evenly divisible.
            indices = indices[:self.total_size]
        assert len(indices) == self.total_size

        # subsample
        indices = indices[self.rankself.num_replicas] # 這一步保證每個進(jìn)程拿到的數(shù)據(jù)不同
        assert len(indices) == self.num_samples

        return iter(indices)

這里最關(guān)鍵的問題是是什么呢?首先在torch.utils.data.distributed.DistributedSampler里面,數(shù)據(jù)集的變量叫self.dataset而不是data_source;其次和torch.utils.data.Sampler要求你_重寫__iter__函數(shù)不同:

def __iter__(self) -> Iterator[T_co]:
        raise NotImplementedError

DistributedSampler這個父類里有部分實(shí)現(xiàn),如果你沒有考慮到這部分,就自然會出現(xiàn)每個進(jìn)程拿到的數(shù)據(jù)都是all的情況。

于是我重寫了我的DDPBaseBucketSampler類:

def basesampler(lens, indices, batch_size):
    # the magic number comes from the author's code
    t1 = []
    t2 = []
    t3 = []
    for i, l in enumerate(lens):
        if (l<100):
            t1.append(indices[i])
        elif (l>100 and l<220):
            t2.append(indices[i])
        else:
            t3.append(indices[i])
    datas = [t1,t2,t3]
    random.shuffle(datas)
    idxs = sum(datas, [])
    batch = []

    #為了保證不爆卡,我們給不同長度的數(shù)據(jù)上保護(hù)鎖
    middle_batch_size = min(int(batch_size * 0.75) , 32)
    long_batch_size = min(int(batch_size * 0.5) , 24)

    return idxs, batch, middle_batch_size, long_batch_size

class DDPBaseBucketSampler(torch.utils.data.distributed.DistributedSampler):
    '''
    這里要注意和單GPU的sampler類同步
    '''
    def __init__(self, dataset, num_replicas, rank, shuffle=True, batch_size=32):
        super(DDPBaseBucketSampler, self).__init__(dataset, num_replicas, rank, shuffle)
        self.batch_size = batch_size

    def __iter__(self):
        # deterministically shuffle based on epoch
        g = torch.Generator()
        g.manual_seed(self.epoch)
        #print('here is pytorch code and you can delete it in the /home/lzk/anaconda3/lib/python3.7/site-packages/torch/utils/data')
        if self.shuffle:
            indices = torch.randperm(len(self.dataset), generator=g).tolist()
        else:
            indices = list(range(len(self.dataset)))
        # add extra samples to make it evenly divisible
        indices += indices[:(self.total_size - len(indices))]
        assert len(indices) == self.total_size

        indices = indices[self.rankself.num_replicas]
        assert len(indices) == self.num_samples

        # 然后我也要拿到每個數(shù)據(jù)的長度 (每個rank不同)
        lens = torch.Tensor([len(x) for x in self.dataset])

        idxs, batch, middle_batch_size, long_batch_size = basesampler(lens[indices], indices, self.batch_size)
        
        for idx in idxs:
            batch.append(idx)
            mlen = max([0]+[lens[x] for x in batch])
            #if (mlen<100 and len(batch) == 32) or (mlen>100 and mlen<220 and len(batch) >= 24) or (mlen>220 and len(batch)>=8) or len(batch)==32:
            if (mlen<100 and len(batch) == self.batch_size) or (mlen>100 and mlen<220 and len(batch) >= middle_batch_size) or (mlen>220 and len(batch)>=long_batch_size) or len(batch)==self.batch_size:
                yield batch
                batch = []
        # print('應(yīng)該出現(xiàn)2次如果是2個進(jìn)程的話')
        if len(batch) > 0:
            yield batch

    def __len__(self):
        return (len(self.dataset)+self.batch_size-1)//self.batch_size

后面每個進(jìn)程終于可以跑屬于自己的數(shù)據(jù)了(1/n,n=進(jìn)程數(shù)量=GPU數(shù)量,單機(jī))

緊接著問題又來了,我發(fā)現(xiàn)訓(xùn)練過程正常結(jié)束后,主進(jìn)程無法退出mp.spawn()函數(shù)。

寫bug第二步,master進(jìn)程無法正常結(jié)束

number workers ddp pytorch下無法正常結(jié)束。具體表現(xiàn)為,mp.spawn傳遞的函數(shù)參數(shù)可以順利運(yùn)行完,但是master進(jìn)程一直占著卡,不退出。一開始我懷疑是sampler函數(shù)的分發(fā)batch的機(jī)制導(dǎo)致的,什么意思呢?就是由于每個進(jìn)程拿到的數(shù)據(jù)不一樣,各自進(jìn)程執(zhí)行sampler類的時候,由于我規(guī)定了長度接近的文本打包在一起,所以可能master進(jìn)程有一百個iter,slave只有80個,然后我馬上試了一下,很快?。?/p>

68655972-58b2-11eb-8b86-12bb97331649.jpg

▲DDPBucketSampler(torch.utils.data.distributed.DistributedSampler)類迭代函數(shù)__iter__

6897a242-58b2-11eb-8b86-12bb97331649.jpg

▲都能夠正常打印,證明__iter__函數(shù)沒有問題

發(fā)現(xiàn)只有細(xì)微的差別,并且,程序最后都越過了這些print,應(yīng)該不會是batch數(shù)量不一致導(dǎo)致的問題。(順便指的一提的是,sampler在很早的時候就把batch打包好了)

加了摧毀進(jìn)程,也于事無補(bǔ)

if args.is_ddp:
     dist.destroy_process_group()
     print('rank destroy_process_group: ' , rank)

然后只能點(diǎn)擊強(qiáng)制退出

File "train.py", line 322, in 
    main(args.gpu, args)
  File "/home/lzk/anaconda3/lib/python3.7/site-packages/torch/multiprocessing/spawn.py", line 171, in spawn
    while not spawn_context.join():
  File "/home/lzk/anaconda3/lib/python3.7/site-packages/torch/multiprocessing/spawn.py", line 77, in join
    timeout=timeout,
  File "/home/lzk/anaconda3/lib/python3.7/multiprocessing/connection.py", line 920, in wait
    ready = selector.select(timeout)
  File "/home/lzk/anaconda3/lib/python3.7/selectors.py", line 415, in select
    fd_event_list = self._selector.poll(timeout)
TypeError: keyboard_interrupt_handler() takes 1 positional argument but 2 were given
^CError in atexit._run_exitfuncs:
Traceback (most recent call last):
  File "/home/lzk/anaconda3/lib/python3.7/multiprocessing/popen_fork.py", line 28, in poll
    pid, sts = os.waitpid(self.pid, flag)
TypeError: keyboard_interrupt_handler() takes 1 positional argument but 2 were given

代碼參考:基于Python初探Linux下的僵尸進(jìn)程和孤兒進(jìn)程(三)[3]、Multiprocessing in python blocked[4]

很顯然是pytorch master進(jìn)程產(chǎn)生死鎖了,變成了僵尸進(jìn)程。

再探究,發(fā)現(xiàn)當(dāng)我把dataloader的number workers設(shè)為0的時候,程序可以正常結(jié)束。經(jīng)過我的注釋大法后我發(fā)現(xiàn),哪怕我把for _i , batch in enumerate(dataloader)內(nèi)的代碼全部注釋改為pass,程序還是會出現(xiàn)master無法正常結(jié)束的情況。所以問題鎖定在dataloader身上。參考:nero:PyTorch DataLoader初探[5]

另外一種想法是,mp.spawn出現(xiàn)了問題。使用此方式啟動的進(jìn)程,只會執(zhí)行和 target 參數(shù)或者 run() 方法相關(guān)的代碼。Windows 平臺只能使用此方法,事實(shí)上該平臺默認(rèn)使用的也是該啟動方式。相比其他兩種方式,此方式啟動進(jìn)程的效率最低。參考:Python設(shè)置進(jìn)程啟動的3種方式[6]

現(xiàn)在試一下,繞開mp.spawn函數(shù),用shell腳本實(shí)現(xiàn)ddp,能不能不報錯:

python -m torch.distributed.launch --nproc_per_node=2 --nnodes=1 --node_rank=0 --master_addr="192.168.1.201" --master_port=23456 我的文件.py

參數(shù)解釋:

  • nnodes:因?yàn)槭菃螜C(jī)多卡,所以設(shè)為1,顯然node_rank 只能是0了
  • local_rank:進(jìn)程在運(yùn)行的時候,會利用args插入local_rank這個參數(shù)標(biāo)識進(jìn)程序號

一番改動后,發(fā)現(xiàn)問題有所好轉(zhuǎn),最直觀的感受是速度快了非常多!!現(xiàn)在我沒有父進(jìn)程的問題了,但還是在運(yùn)行完所有的程序后,無法正常結(jié)束:

68c16578-58b2-11eb-8b86-12bb97331649.jpg

此時我的代碼運(yùn)行到:

692dbfc0-58b2-11eb-8b86-12bb97331649.jpg

上面的代碼是main函數(shù),2個進(jìn)程(master,salve)都可以越過barrier,其中slave順利結(jié)束,但是master卻遲遲不見蹤影:

6968adba-58b2-11eb-8b86-12bb97331649.jpg

這個時候ctrl+c終止,發(fā)現(xiàn):

69a049e6-58b2-11eb-8b86-12bb97331649.jpg

順著報錯路徑去torch/distributed/launch.py, line 239找代碼:

def main():
    args = parse_args()

    # world size in terms of number of processes
    dist_world_size = args.nproc_per_node * args.nnodes

    # set PyTorch distributed related environmental variables
    current_env = os.environ.copy()
    current_env["MASTER_ADDR"] = args.master_addr
    current_env["MASTER_PORT"] = str(args.master_port)
    current_env["WORLD_SIZE"] = str(dist_world_size)

    processes = []

    if 'OMP_NUM_THREADS' not in os.environ and args.nproc_per_node > 1:
        current_env["OMP_NUM_THREADS"] = str(1)
        print("*****************************************
"
              "Setting OMP_NUM_THREADS environment variable for each process "
              "to be {} in default, to avoid your system being overloaded, "
              "please further tune the variable for optimal performance in "
              "your application as needed. 
"
              "*****************************************".format(current_env["OMP_NUM_THREADS"]))

    for local_rank in range(0, args.nproc_per_node):
        # each process's rank
        dist_rank = args.nproc_per_node * args.node_rank + local_rank
        current_env["RANK"] = str(dist_rank)
        current_env["LOCAL_RANK"] = str(local_rank)

        # spawn the processes
        if args.use_env:
            cmd = [sys.executable, "-u",
                   args.training_script] + args.training_script_args
        else:
            cmd = [sys.executable,
                   "-u",
                   args.training_script,
                   "--local_rank={}".format(local_rank)] + args.training_script_args

        process = subprocess.Popen(cmd, env=current_env)
        processes.append(process)

    for process in processes:
        process.wait() # 等待運(yùn)行結(jié)束
        if process.returncode != 0:
            raise subprocess.CalledProcessError(returncode=process.returncode,
                                                cmd=cmd)

可惡,master和dataloader到底有什么關(guān)系哇。。

這個問題終于在昨天(2020/12/22)被解決了,說來也好笑,左手是graphwriter的ddp實(shí)現(xiàn),無法正常退出,右手是minst的ddp最小例程,可以正常退出,于是我開始了刪減大法。替換了數(shù)據(jù)集,model,然后讓dataloader空轉(zhuǎn),都沒有發(fā)現(xiàn)問題,最后一步步逼近,知道我把自己的代碼這一行注釋掉以后,終于可以正常結(jié)束了:

def main(args):
    ############################################################
    print('local_rank : ' , args.local_rank )
    if args.is_ddp:
        dist.init_process_group(
        backend='nccl',
       init_method='env://',
        world_size=args.world_size,
        rank=args.local_rank
        )
    ############################################################
    # torch.multiprocessing.set_sharing_strategy('file_system')  萬惡之源

    os.environ["CUDA_VISIBLE_DEVICES"] = os.environ["CUDA_VISIBLE_DEVICES"].split(',')[args.local_rank]
    args.device = torch.device(0) 
    ...

為什么我當(dāng)時會加上這句話呢?因?yàn)楫?dāng)時在調(diào)試number worker的時候(當(dāng)時年輕,以為越大越好,所以設(shè)置成了number workers = cpu.count()),發(fā)現(xiàn)系統(tǒng)報錯,說超出了打開文件的最大數(shù)量限制。在torch.multiprocessing的設(shè)定里,共享策略(參考pytorch中文文檔[7])默認(rèn)是File descriptor,此策略將使用文件描述符作為共享內(nèi)存句柄。當(dāng)存儲被移動到共享內(nèi)存中,一個由shm_open獲得的文件描述符被緩存。當(dāng)時,文檔還提到:

如果你的系統(tǒng)對打開的文件描述符數(shù)量有限制,并且無法提高,你應(yīng)該使用file_system策略。

所以我換成了torch.multiprocessing.set_sharing_strategy('file_system'),但是卻忽略文檔里的共享內(nèi)存泄露警告。顯然,或許這不是嚴(yán)重的問題,文檔里提到:

69eb6b92-58b2-11eb-8b86-12bb97331649.jpg

也有可能我所說的master進(jìn)程就是這個torch_shm_manager,因?yàn)閐estory進(jìn)程組始終無法結(jié)束0號進(jìn)程:

6a35e19a-58b2-11eb-8b86-12bb97331649.jpg

這個BUG結(jié)束了,真開心,期待下一個BUG快快到來。

責(zé)任編輯:xj

原文標(biāo)題:Pytorch翻車記錄:單卡改多卡踩坑記!

文章出處:【微信公眾號:深度學(xué)習(xí)自然語言處理】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 機(jī)器學(xué)習(xí)

    關(guān)注

    66

    文章

    8554

    瀏覽量

    136980
  • 深度學(xué)習(xí)
    +關(guān)注

    關(guān)注

    73

    文章

    5599

    瀏覽量

    124406
  • pytorch
    +關(guān)注

    關(guān)注

    2

    文章

    813

    瀏覽量

    14856

原文標(biāo)題:Pytorch翻車記錄:單卡改多卡踩坑記!

文章出處:【微信號:zenRRan,微信公眾號:深度學(xué)習(xí)自然語言處理】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評論

    相關(guān)推薦
    熱點(diǎn)推薦

    PyTorch 中RuntimeError分析

    ? 錯誤原因 這個 RuntimeError 是因?yàn)樵?PyTorch 中,upsample_nearest2d_out_frame(最近鄰2D上采樣)操作尚未對 BFloat16 數(shù)據(jù)類型提供
    發(fā)表于 03-06 06:02

    到高效落地:關(guān)鍵詞搜索淘寶天貓商品列表 API 的實(shí)操心得

    API」 關(guān)鍵詞搜索是電商數(shù)據(jù)業(yè)務(wù)最常用、最容易翻車、最影響體驗(yàn)的接口: ? 搜不到結(jié)果 ? 翻幾頁就斷 ? 排序不準(zhǔn)、價格假 ? 封號、限流、字段亂變 ? 并發(fā)一高直接崩 從到穩(wěn)定落地,我把能直接救命的經(jīng)驗(yàn)整理完了。 二
    的頭像 發(fā)表于 02-28 14:22 ?1385次閱讀

    塑料激光焊接機(jī)怎么選?看完這篇不

    追求“高配置”或“低價格”,最終返工。選購的核心邏輯是:需求匹配>參數(shù)堆砌,無需追求全能機(jī)型,精準(zhǔn)貼合自身生產(chǎn)場景才是關(guān)鍵。而深耕行業(yè)十二年、集研發(fā)、制造、銷
    的頭像 發(fā)表于 02-26 17:09 ?480次閱讀
    塑料激光焊接機(jī)怎么選?看完這篇不<b class='flag-5'>踩</b><b class='flag-5'>坑</b>

    初次編譯rk3568(rk3576)Linux 6.1內(nèi)核記錄:從報錯終止到成功解決的完整流程

    很多剛接觸瑞芯微 rk 系列芯片開發(fā)的小伙伴,在初次編譯基于 Linux 6.1 內(nèi)核的系統(tǒng)時,很容易因?yàn)榄h(huán)境依賴問題卡殼。最近我在編譯 rk3576(rk3568 流程類似)Linux 6.1 內(nèi)核時就遇到了典型報錯,從定位問題到最終解決了不少小,今天把完整過程整理
    的頭像 發(fā)表于 02-06 16:47 ?2598次閱讀
    初次編譯rk3568(rk3576)Linux 6.1內(nèi)核<b class='flag-5'>踩</b><b class='flag-5'>坑</b><b class='flag-5'>記錄</b>:從報錯終止到成功解決的完整流程

    K8s生產(chǎn)環(huán)境10大記錄復(fù)盤

    這篇文章記錄了我這些年在 K8s 生產(chǎn)環(huán)境過的。每一個案例都是血淚教訓(xùn),有些甚至導(dǎo)致了生產(chǎn)事故。希望通過分享這些經(jīng)歷,能幫助大家避免重蹈覆轍。
    的頭像 發(fā)表于 02-05 15:51 ?348次閱讀

    ESP32CAM解決指南,已反映商家

    中國香河英茂科工沒有一個鴨蛋是白吃的,沒有一分錢融資是百花的,都轉(zhuǎn)化為科技成果----ESP32CAM解決指南,已反映商家 https://user.qzone.qq.com/382905282/blog/1770127
    發(fā)表于 02-03 22:23

    RK3576+Android15+Linux6.1調(diào)試EM05 4G模塊全記錄:從底層到上層的與破局

    )服務(wù)。最近我們在 RK3576 開發(fā)板 + Android15 系統(tǒng) + Linux6.1 內(nèi)核 環(huán)境下調(diào)試EM05 4G 模塊 時,就遇到了從“RIL 起不來” 到 “庫缺失” 再到 “上層功能未開” 的一系列問題。今天就把完整的調(diào)試流程、過的和解決方案整理出來,
    的頭像 發(fā)表于 02-03 15:27 ?1601次閱讀
    RK3576+Android15+Linux6.1調(diào)試EM05 4G模塊全<b class='flag-5'>記錄</b>:從底層到上層的<b class='flag-5'>踩</b><b class='flag-5'>坑</b>與破局

    智能工廠改造?有人物聯(lián)網(wǎng)手把手教你挑對系統(tǒng)

    中小工廠砸?guī)资f搞智能改造,結(jié)果了最冤的:花 “智慧工廠系統(tǒng)” 的錢,買的只是個 “升級款工廠監(jiān)控系統(tǒng)”—— 只能看設(shè)備轉(zhuǎn)不轉(zhuǎn),沒法調(diào)生產(chǎn)、降能耗,錢直接打了水漂。智能改造選系統(tǒng),真的像開盲盒
    的頭像 發(fā)表于 12-19 14:20 ?273次閱讀
    智能工廠改造<b class='flag-5'>踩</b><b class='flag-5'>坑</b>?有人物聯(lián)網(wǎng)手把手教你挑對系統(tǒng)

    搞懂 Trinamic 這 7 個專利,電機(jī)驅(qū)動芯片選型不!

    搞懂Trinamic這7個專利電機(jī)驅(qū)動芯片選型不!“能用、好用”的專利才是真剛需。在半導(dǎo)體電機(jī)驅(qū)動領(lǐng)域頗具口碑的德國Trinamic,就藏著一批“落地即省事兒”的專利技術(shù)——今天咱們就扒一扒
    的頭像 發(fā)表于 12-10 17:47 ?323次閱讀
    搞懂 Trinamic 這 7 個專利,電機(jī)驅(qū)動芯片選型不<b class='flag-5'>踩</b><b class='flag-5'>坑</b>!

    那些年我用OpenCV+Qt趟過哪些?寫給視覺應(yīng)用開發(fā)者的避指南

    從一個寫腳本的算法愛好者,到能獨(dú)立設(shè)計交付跨平臺視覺檢測系統(tǒng),中間了無數(shù)的,也總結(jié)出一套還算行之有效的方法。今天,中際賽威劉老師不談高深理論,就想把這套從基礎(chǔ)搭建、核心架構(gòu)到項(xiàng)目實(shí)戰(zhàn)的完整
    發(fā)表于 12-02 09:43

    STM32 5 個容易的外設(shè)使用技巧

    STM32是嵌入式開發(fā)領(lǐng)域的熱門MCU,功能豐富到幾乎可以完成所有常見控制任務(wù):GPIO、ADC、UART、定時器、DMA……應(yīng)有盡有。但是,正因?yàn)楣δ軓?qiáng)大,開發(fā)中的機(jī)會也隨之增多。很多初學(xué)者
    的頭像 發(fā)表于 11-24 19:04 ?835次閱讀
    STM32 5 個容易<b class='flag-5'>踩</b><b class='flag-5'>坑</b>的外設(shè)使用技巧

    為什么360°鏡頭容易“”?

    核心參數(shù)一:焦距與視場角-解決“看廣”與“看清”的矛盾這是第一個,也是最容易選錯的參數(shù)。誤區(qū):認(rèn)為360°鏡頭的焦距和普通鏡頭一樣。真相:360°鏡頭的焦距極短(通常為1-2mm左右),我們用
    的頭像 發(fā)表于 11-18 11:29 ?536次閱讀
    為什么360°鏡頭容易“<b class='flag-5'>踩</b><b class='flag-5'>坑</b>”?

    UWB自動跟隨技術(shù)原理、算法融合優(yōu)化和實(shí)錄

    UWB為什么是最靠譜的自動跟隨技術(shù)?原理是什么?需要做什么算法融合、優(yōu)化?我們在開發(fā)過程中過的。
    的頭像 發(fā)表于 08-14 17:45 ?1554次閱讀
    UWB自動跟隨技術(shù)原理、算法融合優(yōu)化和<b class='flag-5'>踩</b><b class='flag-5'>坑</b>實(shí)錄

    使用Word/Excel管理需求的10個痛點(diǎn)及解決方案Perforce ALM

    還在用Word/Excel做需求管理?10個“翻車信號”,都是小伙伴們過的~來看看你過哪些?是不是也該升級到更專業(yè)的ALM工具了~
    的頭像 發(fā)表于 07-10 15:59 ?672次閱讀
    使用Word/Excel管理需求的10個痛點(diǎn)及解決方案Perforce ALM

    HarmonyOS:路由跳轉(zhuǎn)

    背景 ** 隨著華為純血版HarmonyOS NEXT的推出,鴻蒙系統(tǒng)脫離安卓的日子越來越近了。作為車企大廠的我們當(dāng)然要第一時間學(xué)習(xí)鴻蒙開發(fā),提前做好技術(shù)儲備,為日后鴻蒙應(yīng)用的開發(fā)做好準(zhǔn)備工作。于是
    的頭像 發(fā)表于 06-09 15:29 ?447次閱讀