第8篇 Fast AI深度學習課程——單目標識別與定位
一、前情回顧與課程展望
本系列課程的第一部分——神經網路入門與實踐已結束,在該部分的1
-7
課中,我們通過影象分類網路(包括多類別分類)、時間序列處理、影評資料情感分析(包括構建語言模型、協同濾波)等例項,學習了Fast.AI
的API
,熟悉了使用神經網路解決實際問題的流程,掌握了網路調參中的常用技巧,要點如下:
1. 遷移學習
要得到一個可用的模型,其實並不需要從零開始,可以利用已在其他資料集上訓練得到的網路,提取新資料集上的特徵;然後針對實際應用場景,新增若干附加層,設定合適的損失函式,訓練新增層;然後微調已有模型。這種方法即為遷移學習。
2. 選擇模型與損失函式
- 若為固定尺寸的資料,則選用
CNN
RNN
。 - 對分類網路,若為單類別分類,則使用
Softmax
作為損失函式;若為多類別分類,則使用Sigmoid
函式。
3. 過擬合解決方法
- 增加資料;
- 資料修飾;
- 改進模型;
- 正則化(如
Dropout
和正則項); - 減小模型規模。
在第一部分的基礎上,本系列課程的第二部分將涉及如下內容:
1. 分類之外的CNN
應用
包括目標定位、影象增強(超分辨、風格遷移)、GAN
。
2. 分類之外的NLP
應用
包括機器翻譯、注意力模型等。
3. 大資料集處理
包括大尺寸的影象、大樣本的資料、大尺寸輸出的處理。
本節的主要內容是:
- 搬磚技巧與除錯方法
VS Code
中與程式碼檢視相關的快捷鍵。Jupyter Notebook
中的除錯方法。
- 目標識別與標定
- 資料預處理。
- 如何在分類網路的基礎上框定目標。
二、搬磚技巧和除錯方法
1. pathlib
這是Python 3
中的內建包,方便檔案目錄的操作。其將各個目錄視為特定物件(在Linux
和Mac
系統下,為PosixPath
物件),並支援/
連線目錄的操作(重寫了操作符/
)。大部分和檔案路徑相關的包可接受pathlib
定義的目錄物件做引數;而cv2
str()
進行轉換即可。
2. VS Code
中和程式碼檢視相關的快捷鍵
- ctrl(Mac: command)-shift-p: 開啟
VS Code
的命令列終端。 - ctrl(Mac: command)-t: 搜尋變數的定義。
- shift-F12: 搜尋變數的引用。
- F12: 由名稱跳轉到定義。
- alt-left: 跳轉到上一個瀏覽處(Mac下可能為ctrl--)。
3. Jupyter Notebook
中的除錯方法
若要使用斷點除錯,可使用pdb.set_trace()
設定斷點,然後使用pdb
中的命令:
- h: 顯示幫助。
- s: 進入函式定義。
- n: 執行下一句。
- c: 繼續直至下一斷點處。
- u: 在除錯棧中上溯,這樣即可顯示訪問外層變數。
- d: 在除錯棧中下溯。
- l: 顯示程式碼上下文。
- q: 結束。
- 遇到和命令衝突的變數名,可使用p來進行列印。
使用ipython.core.debugger
替代pdb
,可使得除錯視窗顯得五顏六色的。
事實上,使用ipdb
更方便,其命令與pdb
相容。
三、目標識別與標定
本節課中先討論單一目標的問題。基本思路是:依據圖片中面積最大的目標,對圖片進行分類,同時針對目標框的四個角點,以L1
範數構造損失函式。
1. 資料處理
資料集採用的是Pascal VOC
,原網址經常崩潰,課程中提供了一個映象網址。該資料集有2007
和2012
兩個版本,後一個版本要比前一個的資料多一些。一般文獻中經常將兩個版本合併(注意合併過程中可能會存在圖片重複或遺失的問題),本例中則只選用了2007
版。
Pascal VOC
中關於圖片的分類與目標的標定的資訊,是通過xml
檔案給出的。後續有人整理成了json
格式。課程中給的連結下載不到(科學上網也不行),一個替代地址是Kaggle
上的連結。
以pascal_train2007.json
為例說明資訊檔案的組織結構。該檔案中包含如下4個欄位:
-
images
: 儲存了圖片的檔名,檔案id
,以及長和寬:{'file_name': '000012.jpg', 'height': 333, 'width': 500, 'id': 12} {'file_name': '000017.jpg', 'height': 364, 'width': 480, 'id': 17}
-
annotations
: 儲存了圖片id
,圖中目標的分類(編號)、目標框的起始位置、長和寬以及面積,目標的分割(一組多邊形的角點),是否為群目標,等(這個等省略的是ignore
欄位)。一個目標為一條記錄,一張圖可能有多條記錄。{'segmentation': [[155, 96, 155, 270, 351, 270, 351, 96]], 'area': 34104, 'iscrowd': 0, 'image_id': 12, 'bbox': [155, 96, 196, 174]'category_id': 7, 'id': 1, 'ignore': 0}, {'segmentation': [[184, 61, 184, 199, 279, 199, 279, 61]], 'area': 13110, 'iscrowd': 0, 'image_id': 17, 'bbox': [184, 61, 95, 138],'category_id': 15, 'id': 2, 'ignore': 0}
-
categories
: 儲存了父類,類別名稱,類別編號。{'supercategory': 'none', 'id': 1, 'name': 'aeroplane'} {'supercategory': 'none', 'id': 2, 'name': 'bicycle'}
-
type
: 僅有一個單詞—instances
。
訓練資料集的影象共有2501
張,目標共有7844
個。
然後提取每張圖片中面積最大目標的資訊,並將圖片名-類別儲存為CSV
檔案。
2. 構建分類網路
這與第一部分的Lesson 1
中的流程大致相同:
tfms = tfms_from_model(f_model, sz, aug_tfms=transforms_side_on, crop_type=CropType.NO)
md = ImageClassifierData.from_csv(PATH, JPEGS, CSV, tfms=tfms, bs=bs)
learn = ConvLearner.pretrained(f_model, md, metrics=[accuracy])
learn.opt_fn = optim.Adam
其中網路模型f_model
選為resnet34
;圖片sz
設為224
;圖片裁剪crop_type
設為CropType.NO
,表示將圖片不按比例縮放為正方形,不進行裁剪。Fast.AI
中預設對圖片進行的操作是:將影象的窄邊縮放至224
,在長邊方向進行裁剪—在訓練階段隨機裁剪,驗證階段使用中心裁剪。
此步驟結束後繪製圖片時,使用如下語句:
x,y=next(iter(md.val_dl))
show_img(md.val_ds.denorm(to_np(x))[0])
要點如下:使用md.val_dl
獲取資料迭代器;獲取的x
為cuda
上的資料,該資料按照aug_tfms
引數的設定進行了修飾;為生成標準正太分佈的資料,還對x
做了歸一化;使用md.val_ds.denorm()
獲取反歸一化的影象,才可用於顯示。
3. 訓練分類網路
找到合適的學習速率:
lrf=learn.lr_find(1e-5,100)
由於開頭和結尾處學習速率太過離譜時,損失函式值太大,導致無法整個損失函式曲線中觀察到起伏,因此要跳過這些部分。而直接呼叫learn.sched.plot()
,引數預設值為開頭跳過10
個,結尾跳過5
個,這樣會有問題,因此使用如下語句:
learn.sched.plot(n_skip=5, n_skip_end=1)
圖 2. 使用learn.sched.plot()的結果
採用lr=2E-2
後,訓練網路,解鎖全部引數,設定lrs=[lr/5000,lr/500,lr/5]
,繼續訓練一輪,可以獲得82%
左右的準確率。考慮到大多圖片中包含了不止一種的目標,這個準確率還是可以接受的。
4. 目標標定
現考慮確定目標框的四個角點。
-
準備資料
這一問題類似於第一部分中遙感影象的多類別分類。將角點資訊整理成多類別分類所需的
CSV
檔案格式—每個圖片對應著一個以空格分隔的"類別組"(在此即為角點座標值):'fn,bbox\n', '000012.jpg,96 155 269 350\n', '000017.jpg,77 89 335 402\n',
接著構建合適的資料物件。不同於遙感影象的多類別分類,此處輸出值為連續值,而且需要根據分類網路所做的預處理做相應調整(影象被縮放後,目標框的座標自然也會隨之變化):
tfms = tfms_from_model(f_model, sz, crop_type=CropType.NO, tfm_y=TfmType.COORD) md = ImageClassifierData.from_csv(PATH, JPEGS, BB_CSV, tfms=tfms, continuous=True)
其中tfm_y=TfmType.COORD
告知網路要對四個角點的座標值做變換,continuous=True
告知網路這個是迴歸問題,而非分類問題。
另外,如果需要資料修飾,對圖片所做的變換,也要指明引數tfm_y=TfmType.COORD
:
tfm_y = TfmType.COORD
augs = [RandomFlip(tfm_y=tfm_y),
RandomRotate(3, p=0.5, tfm_y=tfm_y),
RandomLighting(0.05,0.05, tfm_y=tfm_y)]
tfms = tfms_from_model(f_model, sz, crop_type=CropType.NO,
tfm_y=tfm_y, aug_tfms=augs)
-
構建網路
在resnet34
的基礎上,新增輸出4
個角點座標的全連線層,這一操作是通過設定ConvLearner
的custom_head
引數完成的(即設定自定義的末尾層)。head_reg4 = nn.Sequential(Flatten(), nn.Linear(25088,4)) learn = ConvLearner.pretrained(f_model, md, custom_head=head_reg4) learn.opt_fn = optim.Adam learn.crit = nn.L1Loss()
損失函式採用
L1
範數,可更好的懲罰與目標值過大的偏差。 -
訓練網路
按前述課程中的步驟訓練網路,所得結果如下所示:
圖 3. 目標框定結果
一些有用的連結
- 課程wiki : 本節課程的一些相關資源,包括課程筆記、課上提到的部落格地址等。
- Pathlib金手指。
- Pascal VOC。
- Pascal Json : 以
json
格式提供了目標分類與標定的資訊。
幾點需要注意的
- 當描述一個螢幕大小為
640x480
時,通常表達的是橫x縱
;而當描述一個矩陣(numpy
物件)為640x480
時,表達的是行x列
,即縱x橫
。這關係到使用目標標註的資訊和繪圖結果的對應。 - 使用
open cv
預處理圖片,要比使用torch vision
或PIL
快很多。 - 使用
Matplotlib
的面向物件的API
,而儘量避免使用Matplotlib
模仿Matlab
的繪圖介面。 - 使用
learn.save()
儲存網路模型時,儲存路徑為PATH/models/
,可通過learn.get_model_path()
檢視。