</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>
- 默認日常描述圖片尺寸,采用[w,h]的形式,比如一張圖片是1280*800就是指寬w=1280, 高h=800。
因此在cfg中所指定img scale = [1333, 800]就是指w=1333, h=800
從而轉入計算機后,要從w,h變成h,w - 默認的大部分數據集,輸出格式都是n,h,w,c和bgr格式,一方面是hwc更普遍,另一方面是opencv讀取的就是bgr。
- pytorch中指定的數據格式是chw和rgb(非常重要!記住!),所以常規處理方法是:數據集輸出都統一定義成hwc和bgr,再通過
transform來轉換成chw和rgb
關於img/label與模型weight之間的數據格式匹配
-
輸入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]) -
如果要把在GPU中運算的數據可視化,必須先變換到cpu,然后解除grad,最后轉numpy才能使用。
即:x1 = x.cpu().detach().numpy()
關於圖片標簽值的定義在分類問題和檢測問題上的區別
- 在純分類任務中,數據集的label一般定義成從0開始,比如10類就是[0,9],這樣的好處是在轉獨熱編碼的時候比較容易,
比如標簽2的獨熱編碼就是[0,0,1,0], 標簽0的獨熱編碼就是[1,0,0,0] - 而在物體檢測任務中的分類子任務中,一般會把數據集的label定義成從1開始,比如10類就是[1,10], 這樣做的目的是
因為在檢測任務中需要對anchor的身份進行指定,而比較簡潔的處理是把負樣本的anchor設定為label=0。所以相當於把
label=0預留給anchor的負樣本。
關於transform中涉及的類型變換導致的錯誤
-
transform和transform_inv中涉及的數據類型變換很多種類,很容易漏掉沒有做而導致輸出形式不對。
-
對於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)范圍內的數值,因為少部分數值在變換過程中會超過這個范圍。
關於訓練過程中圖片尺寸如何變換的問題?
-
通常會定義一個邊框尺寸,比如scale = (300, 300),這是圖片的最大尺寸范圍。
-
圖片首先經過transform,按比例縮放到邊框尺寸,此時因為比例固定,所以每張圖片尺寸都不同,但都有一條片跟邊框尺寸拉平相等。
比如一個batch的圖片可能尺寸會變成(300, 256),(300, 284),(240,300)這樣的形式。 -
圖片然后經過dataloader的collate_fn,對一個batch的圖片取最大外沿,進行padding變成相同尺寸的圖片。
由於transform時所有圖片已有一條邊靠近邊框尺寸,所以取所有圖片最大外沿結果基本都是邊框尺寸,比如一個batch的圖片會變成
(300,300),(300,300),(300,300)然后堆疊成(3,300,300), 所以最終進行訓練的圖片基本都是以邊框尺寸進行訓練。
關於訓練時候的路徑問題
-
經常會產生路徑找不到的情況,比較合理的解決方案是:
- 數據集的root路徑,盡可能采用絕對路徑,即以/開頭的絕對路徑。
- 項目的root路徑盡可能加到sys.path中去。
-
如果有報錯說部分路徑無法導入,一般有2種可能性:
- 根目錄路徑不在sys.path中,可通過添加根目錄路徑到sys.path並結合根目錄下一級目錄可見的原則,實現大部分文件的導入。
- 被導入的module中含有錯誤的導入,需要修正這些錯誤,才能解決報錯
- 存在交叉導入:即A要導入B,同時B也要導入A,此時有可能產生錯誤,需要解除交叉導入才能解決報錯。
關於歸一化和標准化
-
常見訓練所采用的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]
-
如果在一個數據集上從頭訓練,則需要事先計算他的mean, std。但要注意mean,std的數值順序到底是BGR順序還是RGB順序。
-
pytorch自己的transform往往是先歸一化再標准化,即:
img = (img/255 - mean) / std
所以事先准備的mean和std都是很小的值,在-1,1之間 -
后邊看到一些其他例子,比如mmdetection里邊對其他數據集的提供的mean, std值較大。
說明對數據的處理是只做了標准化,即:
img = (img - mean)/std
所以實現准備的mean和std都是比較大的數值,在0-255之間 -
總結一下就是:
- 如果是用現有模型參數做遷移學習:采用的mean/std需要跟原來基模型一樣,數據預處理也要按這種方式。
比如采用caffe/pytorch基模型,則都是只做標准化是img = (img - mena) / std - 如果從頭開始訓練:則采用的mean/std跟實際img的預處理方式必須一樣。
比如采用在0-1之間的小的mean, std,則實際數據處理也要要歸一化+標准化。
而采用大的mean,std,則實際數據處理也只要做標准化。
- 如果是用現有模型參數做遷移學習:采用的mean/std需要跟原來基模型一樣,數據預處理也要按這種方式。
關於卷積的取整方式
-
pytorch中conv, maxpool在計算輸出w,h尺寸時,默認的取整方式都是下取整(floor)。
唯一不同的是,maxpool可以手動設置成上取整即ceil mode,但conv不能手動設置,也就只能下取整。 -
conv, maxpool兩者計算輸出尺寸的公式一樣
- 沒有dilation時,w’ = (w - ksize + 2p)/s + 1
- 有diliation時,相當於ksize被擴大,此時
w’ = (w - d(ksize-1) +2p -1)/s + 1
關於神經網絡的前向計算和反向傳播在pytorch中的對應
- 前向傳播:就是模型一層一層計算,output = model(img)
- 損失計算:采用pytorch自帶的nn.CrossEntropyLoss(y_pred, y_label),則不需要手動增加softmax,
也不需要手動把label轉換成獨熱編碼。因為這兩部分都被寫在pytorch自帶的交叉熵函數對象內部了。 - 損失反向傳播:必須針對損失的標量進行,也就是losses先做規約縮減(reduction=‘mean’),然后才能
loss.backward(),即這里loss是一個標量值,pytorch對這個標量內部梯度會自動獲取,並反向傳播。 - 優化器更新權值:必須采用optimizer.step()完成
- 額外要求:必須增加一句優化器梯度清零,optimizer.zero_grad(),這句必須放在backward之前,
確保變量的梯度不會一直累加,而是每個batch獨立計算,一個batch結束就清零一次。
(自己寫的神經網絡,梯度是整個batch一起算,不需要累加,計算以后直接賦值,所以也就不需要清零了。)
關於在GPU訓練
-
如果要在GPU訓練,只需要3步
- 創建設備device:
- 模型送入device
- batch data送入device: 注意這里理論上只要img送入device就可以,因為跟model相關的計算只需要img輸入
-
並行式GPU訓練並不一定比單GPU快,相反對於一些比較小的模型,單GPU的速度遠超過並行式訓練的速度。
可能因為並行式訓練需要讓數據在GPU之間搬運造成時間損耗,同時python的並行式訓練並不是真正的並行,
而是在同一時間下只有一塊GPU運行的假並行,只是能利用多GPU內存而不能利用多GPU算力的假並行。 -
分布式訓練才是真正的多GPU算力並行訓練。
關於如何設置DataLoader
-
對常規的數據集,如果圖片尺寸都是一樣的,那么直接使用pytorch默認DataLoader就可以。
-
對數據集中img的尺寸不一樣的,由於dataloader需要對img進行堆疊,此時圖片尺寸不同無法直接堆疊,
則需要自定義collate_fn對img進行堆疊,同時處理那些labels/bboxes/segments是堆疊還是放入list. -
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的問題
-
numpy和pytorch都有可能產生in_contiguous的問題。主要都是在transpose(),permute()之后會發生。
參考:https://discuss.pytorch.org/t/negative-strides-of-numpy-array-with-torch-dataloader/28769 -
在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. -
在tensor數據下的報錯形式是:
可通過t1.is_contiguous()查看到是否連續。 -
解決方案;
- 對於numpy: img = np.ascontiguousarray(img)
- 對於tensor: img = img.contiguous()
關於在dataset的__getitem__()中增加斷點導致程序崩潰的問題
- 現象:如果模型和數據送入GPU,dataloader會調用dataset的__getitem__函數獲取數據進行堆疊,
此時如果在__getitem__里邊有自定義斷點,會造成系統警告且暫停訓練。 - 解決方案:取消斷點后模型訓練/驗證就正常了。而如果想要調試__getitem__里邊的語法,可以設置
額外的語句img, label = dataset[0]來進入__getitem__,或者next(iter(dataloader))
關於預訓練模型的加載
- 預訓練模型加載需要分兩步:第一步加載checkpoint文件,第二部加載state_dict,且第二步需要去除多余的key以及去除param size不匹配的key.
- 對於state_dict不匹配的問題,一般兩種解決方案:
- 直接修改預訓練模型的最后一部分,然后加載參數時過濾不匹配的key
- 自己創建整個模型,然后加載參數時過濾不匹配的key
- 預訓練模型的參數如果放在默認.torch/model或者.cache/里邊,則作為隱藏文件夾中的文件,無法通過os.path.isfile()檢查出來,會報錯找不到該文件。
所以,雖然能夠加載但無法檢出。
也可以把參數移到自定義文件夾去,就可以通過os.path.isfile()檢測到了。
關於常規分類問題和物體檢測中的分類問題的差異?
-
常規分類問題是對一張圖片作為最小個體進行分類;而物體檢測問題中的分類是以一張圖片中的每一個bbox進行分類。
因此對於物體檢測問題本質上一張圖片是多個分類問題的集合。 -
因此常規分類問題是一張img對應一個獨立label, 1個batch的數據為多張img對應多個獨立label,1個batch計算一次loss,
所以需要把一個batch的label組合成一組,相當於1個batch就是一組img對應一組label。
因此分類的一個batch計算本質上相當於檢測問題的一張圖片計算。 -
但是檢測分類問題是一張img有一組bbox對應一組label,1個batch的數據為多張img擁有多組bbox對應多組label,每組bbox,label完成一次loss計算。
因此檢測的一個batch計算本質上相當於每張圖片是一次分類batch,多張圖片就要手動進行多次img循環計算,循環的主體就是常規分類問題的一個batch。
這樣理解的話,就可以統一常規分類問題和檢測中的分類問題了:
- 常規分類問題需要把labels組合成一組,變成一個標准計算。
- 檢測分類問題需要循環計算每張img,而每張img計算相當於一次常規分類問題的batch計算。
關於卷積核的作用
-
結論:
- 淺層卷積核主要過濾邊緣、紋理的初級信息;
- 中層卷積核就開始學習小物體;
- 深層卷積核開始學習大物體;
-
證明:參考https://www.leiphone.com/news/201808/DB6WARlVGdm4cqgk.html
可以看到單層(也就相當於淺層)卷積核收斂以后的輸出圖片,類似於sobel算子,是對圖片進行某個方向的邊緣過濾。
也就是說明了淺層神經網絡主要是學習邊緣、紋理等初級信息
關於全連接層的參數過多如何解決的問題
-
全連接層所占參數一般占了一個模型總參數個數的80%以上,所以為了減小模型所占內存,需要優化全連接
-
替換方式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>