1. 程式人生 > >第8篇 Fast AI深度學習課程——單目標識別與定位

第8篇 Fast AI深度學習課程——單目標識別與定位

一、前情回顧與課程展望

本系列課程的第一部分——神經網路入門與實踐已結束,在該部分的1-7課中,我們通過影象分類網路(包括多類別分類)、時間序列處理、影評資料情感分析(包括構建語言模型、協同濾波)等例項,學習了Fast.AIAPI,熟悉了使用神經網路解決實際問題的流程,掌握了網路調參中的常用技巧,要點如下:

1. 遷移學習

要得到一個可用的模型,其實並不需要從零開始,可以利用已在其他資料集上訓練得到的網路,提取新資料集上的特徵;然後針對實際應用場景,新增若干附加層,設定合適的損失函式,訓練新增層;然後微調已有模型。這種方法即為遷移學習。

2. 選擇模型與損失函式
  • 若為固定尺寸的資料,則選用CNN
    ;若為序列資料,則選用RNN
  • 對分類網路,若為單類別分類,則使用Softmax作為損失函式;若為多類別分類,則使用Sigmoid函式。
3. 過擬合解決方法
  • 增加資料;
  • 資料修飾;
  • 改進模型;
  • 正則化(如Dropout和正則項);
  • 減小模型規模。

在第一部分的基礎上,本系列課程的第二部分將涉及如下內容:

1. 分類之外的CNN應用

包括目標定位、影象增強(超分辨、風格遷移)、GAN

2. 分類之外的NLP應用

包括機器翻譯、注意力模型等。

3. 大資料集處理

包括大尺寸的影象、大樣本的資料、大尺寸輸出的處理。

本節的主要內容是:

  • 搬磚技巧與除錯方法
    • VS Code中與程式碼檢視相關的快捷鍵。
    • Jupyter Notebook中的除錯方法。
  • 目標識別與標定
    • 資料預處理。
    • 如何在分類網路的基礎上框定目標。

二、搬磚技巧和除錯方法

1. pathlib

這是Python 3中的內建包,方便檔案目錄的操作。其將各個目錄視為特定物件(在LinuxMac系統下,為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,原網址經常崩潰,課程中提供了一個映象網址。該資料集有20072012兩個版本,後一個版本要比前一個的資料多一些。一般文獻中經常將兩個版本合併(注意合併過程中可能會存在圖片重複或遺失的問題),本例中則只選用了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獲取資料迭代器;獲取的xcuda上的資料,該資料按照aug_tfms引數的設定進行了修飾;為生成標準正太分佈的資料,還對x做了歸一化;使用md.val_ds.denorm()獲取反歸一化的影象,才可用於顯示。

圖 1. crop_type=NO時的效果
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個角點座標的全連線層,這一操作是通過設定ConvLearnercustom_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. 目標框定結果

一些有用的連結

幾點需要注意的

  • 當描述一個螢幕大小為640x480時,通常表達的是橫x縱;而當描述一個矩陣(numpy物件)為640x480時,表達的是行x列,即縱x橫。這關係到使用目標標註的資訊和繪圖結果的對應。
  • 使用open cv預處理圖片,要比使用torch visionPIL快很多。
  • 使用Matplotlib的面向物件的API,而儘量避免使用Matplotlib模仿Matlab的繪圖介面。
  • 使用learn.save()儲存網路模型時,儲存路徑為PATH/models/,可通過learn.get_model_path()檢視。