SSD算法的實現


本文目的:介紹一個超贊的項目——用Keras來實現SSD算法

本文目錄:

  • 0 前言
  • 1 如何訓練SSD模型
  • 2 如何評估SSD模型
  • 3 如何微調SSD模型
  • 4 其他注意點

0 前言

我在學習完SSD算法之后,對具體細節有很多的疑惑,記錄如下:

  • SSD的網絡是怎么實現的?
  • 已有的數據是什么樣子的?
  • 如何把一張圖像打散成anchors?
  • 如何根據標注把各anchors打上標簽?
  • 正負樣本是如何定義的?匹配策略是咋回事?
  • 困難負樣本挖掘是怎么實現的?
  • 數據是怎么喂進去的?出來的又是什么?
  • L2 Normalization在哪,如何實現的?
  • Atrous層在哪?
  • SSD的損失函數是怎么實現的?
  • 數據在模型中是怎么流動的?
  • 數據增強是怎么實現的?
  • 預測的結果如何在原圖上畫框?
  • 如何計算模型在Pascal VOC或MS COCO上的mAP?

在github上搜索時,發現了這個超贊的項目——用Keras來實現SSD算法,非常適合那些學習了SSD算法,但具體細節有些模糊的同學(主要是我自己)。文檔注釋非常詳細,且提供非常清晰的操作指導,比如如何訓練SSD模型,如何評估模型的性能,如何在自己的數據集上微調預訓練的模型等等。

為了便於快速理解,現以其中一個簡單版本的SSD網絡為例(ssd7_training.ipynb文件)來記錄總結,具體細節可參考項目文檔注釋。

1 如何訓練SSD模型

主函數流程中,除了庫函數的引入,以及通用參數的預定義之外,主要分為四塊內容:

  • 准備模型;
    • 構建模型;
    • 自定義損失函數,並編譯;
  • 准備數據;
    • 定義訓練集和驗證集的圖像生成器對象datagen;
    • 利用圖像生成器的函數讀取文件圖像和標簽信息;
    • 定義圖像增強方法鏈;
    • 利用編碼器將標簽信息編碼成損失函數需要的格式;
    • 定義數據對的迭代器generator;
  • 訓練;
    • 定義回調函數;
    • 訓練;
    • 訓練結果可視化;
  • 預測(可視化檢測效果);
    • 定義數據迭代器,並獲取一個batch的樣本;
    • 將樣本送入模型進行預測,並解碼得到預測框;
    • 將預測框和真值框畫在原圖上,對比效果。

1.1 准備模型

1.1.1 搭建模型

以training模式搭建一個小型的SSD模型。從4個地方引出predictor。各個預測特征圖上每一個像素點均對應4個錨框。

模型搭建流程:

  • 搭建base network;
  • 從四個特征圖處分別引出predictor;
  • 每個predictor分為三條路徑(按照第一篇參考文章的圖示);
  • 分類那路需要softmax,最后三路在最后一維度concatenate, 得到(batches, n_total_boxes, 21+4+8),即為模型原始輸出raw output,其中n_boxes_total表示四個做預測的特征圖對應的錨框總數,最后一個維度是21個類別+gt框偏移量+錨框坐標+variance;
  • 若模式為inference,還需要在最后一層接上解碼器DecodeDetections(輸出經過置信度閾值、NMS等篩選后的預測框)

備注:

  • 用AnchorBoxes層生成錨框,但為啥接在boxes4后面,而不是conv4后面,兩者一樣嗎?答:一樣的,因為只用了中間兩個維度的數值,即特征圖的高寬。但根據函數描述,應該接在conv4后面,即輸入為(batch, height, width, channels)

AnchorBoxes層

  • 目的是為了根據輸入的特征圖,將原圖打散成一系列的錨框。
  • 過程:根據參數縮放因子和高寬比,可以計算出特征圖一個像素點對應的錨框數量和尺寸,再有特征圖的高和寬,即可求得錨框的中心。
  • 輸入(batch, height, width, channels),即特征圖的尺寸。
  • 輸出(batch, height, width, n_boxes, 8),這里n_boxes表示一個特征圖對應的錨框總數,8表示錨框信息,即坐標+variance;

DecodeDetections層

  • 在建模中mode=inference時,接在predictor后面的解碼器;
  • 過程:根據置信度閾值、NMS、最大輸出數量等參數,對每張圖篩選出前top_K個預測框;
  • 輸入即為模型的原始輸出(batch,n_boxes_total,n_classes+4+8),最后一維是 類別(21)+框偏移量+錨框和variance(centroids格式);
  • 輸出(batch,top_k,6),最后一維是 (class_id, confidences, box_coordinates),坐標格式是xmin, ymin, xmax, ymax。這里top_K=200,即便合理的預測框不夠,也會湊出200個。

備注:

  • 輸入參數說明里要求,只支持坐標輸出為coords='centroids',這里coords='centroids'指的是輸入的格式,實際輸出格式是[xmin, ymin, xmax, ymax]。

1.1.2 自定義損失函數,並編譯

(在SSD300模型中,需要先加載預訓練的VGG16權重。)

自定義損失函數keras_ssd_loss.py

  • 定義了一個損失類SSDLoss,里面有各種具體的損失函數,比如smooth L1和log損失;
  • smooth L1損失:兩個參數都是(batch_size, #boxes, 4),輸出(batch_size, #boxes)。疑惑:這是直接求smooth L1,直接用坐標值求損失?照理說應該是求偏移量的損失啊?還是說輸入的本來就應該是偏移量而非直接坐標值?答:在compute_loss函數中調用時傳入的就是偏移量,所以OK。log損失很簡單;
  • compute_loss函數計算總損失,參數y_true和y_pred都是(batch_size, #boxes, #classes+12),輸出scalar。疑問1,總損失為啥除以正樣本個數而非總個數?答:正負樣本比例為1:3,只是差個倍數,對結果不影響。疑問2,返回結果仍然是(batch,),並非標量?那乘以batch_size還有意義嗎?答:keras強制以batch的方式計算各個值,即始終保證batch維度,實際運算的時候會給出(batch,...)對batch的平均值,因為compute_loss計算的是一個batch總的損失,所以keras強制平均后再乘以batch_size即為總和。

備注:

  • 自定義的損失函數,傳給compile的是對象的一個函數,這個函數返回的是根據y_true和y_predict計算的損失;
  • 這個y_pred格式是(batch_size, #boxes, #classes + 12),即模型的raw output;y_true是后續SSDInputEncoder類實例將真值框編碼后的輸出;
  • 如果是加載保存的模型,注意通過load_model中custom_objects傳入自定義的層和函數。

1.2 准備數據

這一塊內容大體上是通過自定義的圖像生成器DataGenerator類及其方法來實現的。其中DataGenerator里面的函數generate()需要接收圖像增強鏈和真值框編碼器等參數,所以需要另外自定義兩個類。

DataGenerator類

  • DataGenerator實例化時自動調用__ init __ (),在這里面可以先進行圖像增強處理(keras就是這么處理的,此處是在generate函數中做變形處理);
  • DataGenerator里面的函數parser_csv()從文件中讀入數據和標簽(即真值框),讀進來的真值框格式是一個長度為樣本個數的list,其中每個元素為一個2D array,即array[[class_id, xmin, ymin, xmax, ymax]...],shape為(n_boxes,5),其中n_boxes為該樣本的真值框個數;
  • DataGenerator里面的函數generate()接收圖像增強鏈和真值框編碼器等參數,作用是產生一批批的數據對(X,y);(注意:keras內置的flow_from_directory實現了讀取文件數據和生成(X,y)兩個功能,但由於此處需要解析的文件除了CSV,可能還有其他形式,所以分成了兩個函數);
  • 關於加速的方法之一:第一次先用parser_csv讀取圖像和標簽,然后利用create_hdf5_dataset()函數,將圖像和標簽轉成h5文件(訓練集近8G,驗證集近2G,均已包含真值框,但未編碼)。以后創建DataGenerator時就可以直接讀取h5文件,然后用generate函數生成數據對,不再需要用parser_csv。但是經過測試,訓練時用不用h5文件貌似沒有區別,訓練一個epoch的時間均為12分鍾。

定義數據增強鏈

  • DataAugmentationConstantInputSize類中,圖像變形,真值框也要變形?否則就對不上了。如何變?答:在形變模塊data_generator.object_detection_2d_geometric_ops中的方法,將labels一同放入進行了處理。
  • Python中,如果在創建class的時候寫了__ call __ ()方法,那么該class實例化出實例后,實例名()就是調用__ call __ ()方法;在keras中自定義層時用call(),而不是__ call __ ();
  • DataAugmentationConstantInputSize中__ init __ ()集成了一系列變形對象置於sequence中,並在__ call __ ()函數中調用。

用SSDInputEncoder類實例將真值框編碼成損失函數需要的格式y_true(這里用y_encoded表示)

  • 輸入的gt_labels是一個長度為batch_size的list,其中每個元素為一個2D array,即array[[class_id, xmin, ymin, xmax, ymax]...];
  • 主要功能在__ call __ ()函數中實現,分為三步:
    • 根據原圖尺寸、縮放因子與高寬比、特征圖尺寸三個條件,創建y_encoded模板(即一系列的anchors,shape為(batch,#boxes,21+4+4+4),最后為21個類別+gt框坐標+錨框坐標+variance);
    • 匹配真值框和錨框,即更新最后一個維度的21+4;
    • 將gt的坐標轉換為錨框的偏移量;

調用train_dataset.generate()生成需要的數據對(X,y)

  • 准備好數據增強鏈對象和SSDInputEncoder對象后,同其他參數一起傳入train_dataset.generate中,指定生成器返回數據格式為(processed_images,encoded_labels),(前者shape為(batch,h_img,w_img,channel),后者即為用SSDInputEncoder類實例的輸出結果),供后續model.fit_generator使用。

1.3 訓練

定義了幾個有用的回調函數,ModelCheckpoint(保存每次epoch之后的模型)、CSVLogger(將損失和指標保存至CSV文件)、EarlyStopping(早停)、ReduceLROnPlateau(平緩區自動減小學習率),在SSD300中還用了LearningRateScheduler(按計划調整學習率)、TerminateOnNaN(遇到NaN數據即停止訓練)。其中最為常用的是ModelCheckpoint和CSVLogger。

訓練時參數initial_epoch和final_epoch也很有意思,允許用戶從上次中斷的地方開始訓練。(再也不怕中午睡覺被吵了:-))

訓練結果可視化:可以直接調用fit的返回值,也可以讀取CSV文件中記錄值。

1.4 預測(可視化檢測效果)

獲取預測值

  • 定義數據迭代器,並獲取一個batch的樣本;
  • 將這個batch的樣本送入模型進行預測,得到預測值;(這時候得到的y_pred是模型的raw output)

解碼器對預測值進行后處理

  • decode_detections函數功能同模型架構中解碼器層DecodeDetections功能一樣,都是:
    • 偏移量轉為坐標(可以是絕對坐標,也可以是相對坐標),同時后12個數轉成4個數;
    • 針對每一個類別,進行置信度過濾和NMS;
    • 選取前top_k個預測框(若設置top_k),不足top_k的話直接輸出。
  • 輸入的y_pred參數:training模式下SSD模型的原始輸出(batch,#boxes,21+4+4+4),其中#boxes為所有錨框;
  • 返回值:(batch,filtered_boxes,6),其中filtered_boxes為經過篩選后的預測框數量,6為[class_id, confidence, xmin, ymin, xmax, ymax];
  • 注意: decode_detections函數和DecodeDetections層有不一樣處:若經過篩選后預測框數量不足top_k,前者是直接輸出,但后者會填充成top_k個(為了計算損失時維度一致)。

將預測框顯示在圖像上,對比效果

  • 顯示圖像,畫標注框和預測框的方法;
  • plt中plt.cm可將數值映射成偽色彩,(很有用,因為相對於亮度,人們對顏色的變化更敏感),參考

1.5 SSD300訓練的區別

  • 訓練SSD300的模型時,用的是Pascal VOC的數據,標簽文件是XML文件;
  • SSD300的模型結構中,有三點需要注意:
    • 模型的結構按照原生SSD搭建;
    • 空洞卷積層:fc6 = Conv2D(1024, (3, 3), dilation_rate=(6, 6),...);
    • L2 Normalization層:conv4_3_norm = L2Normalization(gamma_init=20,...)(conv4_3);
  • 疑問:SSD300定義模型參數的時候,將圖像通道換成了BGR來訓練,但是最后預測的時候圖像通道沒有換成BGR?

2 如何評估SSD模型

大致要點:

  • 這一塊單獨列了一個文件,即SSD300_evaluation.ipynb;
  • SSD Evaluation中,創建模型用的是inference模式,下載的權重文件VGG_VOC0712Plus_SSD_300x300_ft_iter_160000.h5 是以training模式創建的模型訓練的(既然是權重文件,那肯定是訓練得到的,所以模型肯定是以training模式創建的),所以model.load_weights(weights_path, by_name=True)中需要加上by_name,否則對不上號;
  • 繪制PR曲線的方法。

3 如何微調SSD模型

這一塊內容詳見 weight_sampling_tutorial.ipynb。

作者提供了幾種訓練好的SSD模型,那么如何微調這些模型,使其能在自己的數據集上完成自己的任務?比如現在我想識別8種物體,而作者提供的是在MS COCO上訓練的能識別80種物體的模型,那么該如何操作?

作者提出了3種方法,並認為最好的方法是直接對分類器的結果進行下采樣。比如SSD第一個predictor的分類器輸出是(batch, h1, w1, 81 * 4),其中h1和w1是conv4_3特征圖的高度和寬度,對輸出下采樣得到(batch, h1, w1, 9 * 4),其中9表示8種物體和背景,然后在自己的數據集上微調即可。這種方法對那些目標物體在MS COCO的80個類別之內的任務特別有效。

4 其他注意點

  • model.load_weights('./ssd7_weights.h5', by_name=True):這里by_name是指只加載同名層的權重,適合加載那些結構不同的模型權重,詳見
  • 盡量使用model.save保存模型整體,因為分開保存后,重新加載時optimizer的狀態會被重置,詳見

Reference:


免責聲明!

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



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