轉pytorch中訓練深度神經網絡模型的關鍵知識點


版權聲明:本文為博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
本文鏈接: https://blog.csdn.net/weixin_42279044/article/details/101053719
            </div>
                                                <!--一個博主專欄付費入口-->
         
         <!--一個博主專欄付費入口結束-->
        <link rel="stylesheet" href="https://csdnimg.cn/release/phoenix/template/css/ck_htmledit_views-4a3473df85.css">
                                    <div id="content_views" class="markdown_views prism-github-gist">
                <!-- flowchart 箭頭圖標 勿刪 -->
                <svg xmlns="http://www.w3.org/2000/svg" style="display: none;">
                    <path stroke-linecap="round" d="M5,0 0,2.5 5,5z" id="raphael-marker-block" style="-webkit-tap-highlight-color: rgba(0, 0, 0, 0);"></path>
                </svg>
                                        <h3><a name="t0"></a><a id="_0"></a>關於數據格式</h3>
  1. 默認日常描述圖片尺寸,采用[w,h]的形式,比如一張圖片是1280*800就是指寬w=1280, 高h=800。
    因此在cfg中所指定img scale = [1333, 800]就是指w=1333, h=800
    從而轉入計算機后,要從w,h變成h,w
  2. 默認的大部分數據集,輸出格式都是n,h,w,c和bgr格式,一方面是hwc更普遍,另一方面是opencv讀取的就是bgr。
  3. pytorch中指定的數據格式是chw和rgb(非常重要!記住!),所以常規處理方法是:數據集輸出都統一定義成hwc和bgr,再通過
    transform來轉換成chw和rgb

關於img/label與模型weight之間的數據格式匹配

  1. 輸入img要修改為float()格式float32,否則跟weight不匹配報錯
    輸入label要修改為long()格式int64,否則跟交叉熵公式不匹配報錯
    img = img.float()
    label = label.long()
    這兩句要放在每個batch開始位置

    為了避免遺忘,可以把這部分操作集成到自定義的to_tensor()函數中,在每次開始轉tensor的時候自動轉換:
    if isinstance(data, torch.Tensor):
    return data
    elif isinstance(data, np.ndarray):
    return torch.from_numpy(data)
    elif isinstance(data, Sequence) and not mmcv.is_str(data):
    return torch.tensor(data)
    elif isinstance(data, int):
    return torch.LongTensor([data])
    elif isinstance(data, float):
    return torch.FloatTensor([data])

  2. 如果要把在GPU中運算的數據可視化,必須先變換到cpu,然后解除grad,最后轉numpy才能使用。
    即:x1 = x.cpu().detach().numpy()

關於圖片標簽值的定義在分類問題和檢測問題上的區別

  1. 在純分類任務中,數據集的label一般定義成從0開始,比如10類就是[0,9],這樣的好處是在轉獨熱編碼的時候比較容易,
    比如標簽2的獨熱編碼就是[0,0,1,0], 標簽0的獨熱編碼就是[1,0,0,0]
  2. 而在物體檢測任務中的分類子任務中,一般會把數據集的label定義成從1開始,比如10類就是[1,10], 這樣做的目的是
    因為在檢測任務中需要對anchor的身份進行指定,而比較簡潔的處理是把負樣本的anchor設定為label=0。所以相當於把
    label=0預留給anchor的負樣本。

關於transform中涉及的類型變換導致的錯誤

  1. transform和transform_inv中涉及的數據類型變換很多種類,很容易漏掉沒有做而導致輸出形式不對。

  2. 對於transform_inv的變換,需要重點關注

    • 逆變換需要把數據先從GPU取出來到cpu中並轉換成numpy形式,才能進行后續逆變換和顯示。
    • 默認數據集輸出類型:hwc, bgr。采用這種默認輸出形式,主要是因為用opencv作為底層函數的輸出就是這種形式。
      而pytorch需要的形式是chw, rgb,所以經過transform后輸出就是chw,rgb
    • 逆變換需要先變換chw為hwc,然后才變換rgb為bgr:因為rgb2bgr是基於最后一個維度是c來寫的。
    • 逆變換需要把rgb轉換為bgr
    • 逆變換需要把歸一化和標准化的圖片恢復成原始圖片的數據形式,並且需要從float轉換為unit8,這樣才能被opencv識別。
    • 最后進行截取(0-255)范圍內的數值,因為少部分數值在變換過程中會超過這個范圍。

關於訓練過程中圖片尺寸如何變換的問題?

  1. 通常會定義一個邊框尺寸,比如scale = (300, 300),這是圖片的最大尺寸范圍。

  2. 圖片首先經過transform,按比例縮放到邊框尺寸,此時因為比例固定,所以每張圖片尺寸都不同,但都有一條片跟邊框尺寸拉平相等。
    比如一個batch的圖片可能尺寸會變成(300, 256),(300, 284),(240,300)這樣的形式。

  3. 圖片然后經過dataloader的collate_fn,對一個batch的圖片取最大外沿,進行padding變成相同尺寸的圖片。
    由於transform時所有圖片已有一條邊靠近邊框尺寸,所以取所有圖片最大外沿結果基本都是邊框尺寸,比如一個batch的圖片會變成
    (300,300),(300,300),(300,300)然后堆疊成(3,300,300), 所以最終進行訓練的圖片基本都是以邊框尺寸進行訓練。

關於訓練時候的路徑問題

  1. 經常會產生路徑找不到的情況,比較合理的解決方案是:

    • 數據集的root路徑,盡可能采用絕對路徑,即以/開頭的絕對路徑。
    • 項目的root路徑盡可能加到sys.path中去。
  2. 如果有報錯說部分路徑無法導入,一般有2種可能性:

    • 根目錄路徑不在sys.path中,可通過添加根目錄路徑到sys.path並結合根目錄下一級目錄可見的原則,實現大部分文件的導入。
    • 被導入的module中含有錯誤的導入,需要修正這些錯誤,才能解決報錯
    • 存在交叉導入:即A要導入B,同時B也要導入A,此時有可能產生錯誤,需要解除交叉導入才能解決報錯。

關於歸一化和標准化

  1. 常見訓練所采用的mean,std並不是跟訓練數據集相關,而是跟基模型所訓練的數據集相關,這是
    因為這些訓練都是在基模型的基礎上進行finetunning做遷移學習來訓練的。
    由於pytorch的基模型基本都是在imagenet中訓練的,所以這些mean, std都是imagenet的參數。
    而caffe的基模型雖然也是在imagenet中訓練的,但因為處理方式不同所以std取成了1(待在caffe中確認原因)
    比如:

    • 來自pytorch的基模型:[123.675, 116.28, 103.53], std=[58.395, 57.12, 57.375]
    • 來自caffe的基模型:[123.675, 116.28, 103.53], std=[1, 1, 1]
  2. 如果在一個數據集上從頭訓練,則需要事先計算他的mean, std。但要注意mean,std的數值順序到底是BGR順序還是RGB順序。

  3. pytorch自己的transform往往是先歸一化再標准化,即:
    img = (img/255 - mean) / std
    所以事先准備的mean和std都是很小的值,在-1,1之間

  4. 后邊看到一些其他例子,比如mmdetection里邊對其他數據集的提供的mean, std值較大。
    說明對數據的處理是只做了標准化,即:
    img = (img - mean)/std
    所以實現准備的mean和std都是比較大的數值,在0-255之間

  5. 總結一下就是:

    • 如果是用現有模型參數做遷移學習:采用的mean/std需要跟原來基模型一樣,數據預處理也要按這種方式。
      比如采用caffe/pytorch基模型,則都是只做標准化是img = (img - mena) / std
    • 如果從頭開始訓練:則采用的mean/std跟實際img的預處理方式必須一樣。
      比如采用在0-1之間的小的mean, std,則實際數據處理也要要歸一化+標准化。
      而采用大的mean,std,則實際數據處理也只要做標准化。

關於卷積的取整方式

  1. pytorch中conv, maxpool在計算輸出w,h尺寸時,默認的取整方式都是下取整(floor)。
    唯一不同的是,maxpool可以手動設置成上取整即ceil mode,但conv不能手動設置,也就只能下取整。

  2. conv, maxpool兩者計算輸出尺寸的公式一樣

    • 沒有dilation時,w’ = (w - ksize + 2p)/s + 1
    • 有diliation時,相當於ksize被擴大,此時
      w’ = (w - d(ksize-1) +2p -1)/s + 1

關於神經網絡的前向計算和反向傳播在pytorch中的對應

  1. 前向傳播:就是模型一層一層計算,output = model(img)
  2. 損失計算:采用pytorch自帶的nn.CrossEntropyLoss(y_pred, y_label),則不需要手動增加softmax,
    也不需要手動把label轉換成獨熱編碼。因為這兩部分都被寫在pytorch自帶的交叉熵函數對象內部了。
  3. 損失反向傳播:必須針對損失的標量進行,也就是losses先做規約縮減(reduction=‘mean’),然后才能
    loss.backward(),即這里loss是一個標量值,pytorch對這個標量內部梯度會自動獲取,並反向傳播。
  4. 優化器更新權值:必須采用optimizer.step()完成
  5. 額外要求:必須增加一句優化器梯度清零,optimizer.zero_grad(),這句必須放在backward之前,
    確保變量的梯度不會一直累加,而是每個batch獨立計算,一個batch結束就清零一次。
    (自己寫的神經網絡,梯度是整個batch一起算,不需要累加,計算以后直接賦值,所以也就不需要清零了。)

關於在GPU訓練

  1. 如果要在GPU訓練,只需要3步

    • 創建設備device:
    • 模型送入device
    • batch data送入device: 注意這里理論上只要img送入device就可以,因為跟model相關的計算只需要img輸入
  2. 並行式GPU訓練並不一定比單GPU快,相反對於一些比較小的模型,單GPU的速度遠超過並行式訓練的速度。
    可能因為並行式訓練需要讓數據在GPU之間搬運造成時間損耗,同時python的並行式訓練並不是真正的並行,
    而是在同一時間下只有一塊GPU運行的假並行,只是能利用多GPU內存而不能利用多GPU算力的假並行。

  3. 分布式訓練才是真正的多GPU算力並行訓練。

關於如何設置DataLoader

  1. 對常規的數據集,如果圖片尺寸都是一樣的,那么直接使用pytorch默認DataLoader就可以。

  2. 對數據集中img的尺寸不一樣的,由於dataloader需要對img進行堆疊,此時圖片尺寸不同無法直接堆疊,
    則需要自定義collate_fn對img進行堆疊,同時處理那些labels/bboxes/segments是堆疊還是放入list.

  3. pytorch默認的collate_fn設置不是None而是default_collate_fn,所以即使不用collate_fn
    選項,也不要去把collate_fn設置為None,這會導致collate_fn找不到可用的函數導致錯誤。
    (從這個角度說,pytorch的官方英文文檔有問題,注明DataLoader的collate_fn默認=None,
    但實際上collate_fn的默認=default_collate_fn。)

關於contiguous的問題

  1. numpy和pytorch都有可能產生in_contiguous的問題。主要都是在transpose(),permute()之后會發生。
    參考:https://discuss.pytorch.org/t/negative-strides-of-numpy-array-with-torch-dataloader/28769

  2. 在numpy數據下的報錯形式是:ValueError: some of the strides of a given numpy array are negative.
    This is currently not supported, but will be added in future releases.

  3. 在tensor數據下的報錯形式是:
    可通過t1.is_contiguous()查看到是否連續。

  4. 解決方案;

    • 對於numpy: img = np.ascontiguousarray(img)
    • 對於tensor: img = img.contiguous()

關於在dataset的__getitem__()中增加斷點導致程序崩潰的問題

  1. 現象:如果模型和數據送入GPU,dataloader會調用dataset的__getitem__函數獲取數據進行堆疊,
    此時如果在__getitem__里邊有自定義斷點,會造成系統警告且暫停訓練。
  2. 解決方案:取消斷點后模型訓練/驗證就正常了。而如果想要調試__getitem__里邊的語法,可以設置
    額外的語句img, label = dataset[0]來進入__getitem__,或者next(iter(dataloader))

關於預訓練模型的加載

  1. 預訓練模型加載需要分兩步:第一步加載checkpoint文件,第二部加載state_dict,且第二步需要去除多余的key以及去除param size不匹配的key.
  2. 對於state_dict不匹配的問題,一般兩種解決方案:
    • 直接修改預訓練模型的最后一部分,然后加載參數時過濾不匹配的key
    • 自己創建整個模型,然后加載參數時過濾不匹配的key
  3. 預訓練模型的參數如果放在默認.torch/model或者.cache/里邊,則作為隱藏文件夾中的文件,無法通過os.path.isfile()檢查出來,會報錯找不到該文件。
    所以,雖然能夠加載但無法檢出。
    也可以把參數移到自定義文件夾去,就可以通過os.path.isfile()檢測到了。

關於常規分類問題和物體檢測中的分類問題的差異?

  1. 常規分類問題是對一張圖片作為最小個體進行分類;而物體檢測問題中的分類是以一張圖片中的每一個bbox進行分類。
    因此對於物體檢測問題本質上一張圖片是多個分類問題的集合。

  2. 因此常規分類問題是一張img對應一個獨立label, 1個batch的數據為多張img對應多個獨立label,1個batch計算一次loss,
    所以需要把一個batch的label組合成一組,相當於1個batch就是一組img對應一組label。
    因此分類的一個batch計算本質上相當於檢測問題的一張圖片計算。

  3. 但是檢測分類問題是一張img有一組bbox對應一組label,1個batch的數據為多張img擁有多組bbox對應多組label,每組bbox,label完成一次loss計算。
    因此檢測的一個batch計算本質上相當於每張圖片是一次分類batch,多張圖片就要手動進行多次img循環計算,循環的主體就是常規分類問題的一個batch。
    這樣理解的話,就可以統一常規分類問題和檢測中的分類問題了:
    - 常規分類問題需要把labels組合成一組,變成一個標准計算。
    - 檢測分類問題需要循環計算每張img,而每張img計算相當於一次常規分類問題的batch計算。

關於卷積核的作用

  1. 結論:

    • 淺層卷積核主要過濾邊緣、紋理的初級信息;
    • 中層卷積核就開始學習小物體;
    • 深層卷積核開始學習大物體;
  2. 證明:參考https://www.leiphone.com/news/201808/DB6WARlVGdm4cqgk.html
    可以看到單層(也就相當於淺層)卷積核收斂以后的輸出圖片,類似於sobel算子,是對圖片進行某個方向的邊緣過濾。
    也就是說明了淺層神經網絡主要是學習邊緣、紋理等初級信息

關於全連接層的參數過多如何解決的問題

  1. 全連接層所占參數一般占了一個模型總參數個數的80%以上,所以為了減小模型所占內存,需要優化全連接

  2. 替換方式1:resnet/resnext的最后一部分就是這樣實現。
    用一個adaptiveAvgpooling(1), 先把卷積的每個通道收縮為1,即(c,h,w)->(c,1,1),再reshape后接一個簡單全連接linear(c, 10)即可
    變換前reshape后需要至少2個全連接(chw->256),再(256->10),參數總量256chw + 25610;
    avgpool變換后reshape后只需要1個全連接(256->10),參數總量為25610, 減少了256chw個參數。

                                </div>
            <link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-b6c3c6d139.css" rel="stylesheet">
                </div>
posted @ 2019-12-04 11:30  core!  閱讀( 276)  評論( 0編輯  收藏


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM