YOLO,即You Only Look Once(你只能看一次)的縮寫,是一個基於卷積神經網路(CNN)的物體檢測演算法。而YOLO v3是YOLO的第3個版本,即YOLO、YOLO 9000、YOLO v3,檢測效果,更準更強。
YOLO v3的更多細節,可以參考YOLO的官網。
YOLO是一句美國的俗語,You Only Live Once,你只能活一次,即人生苦短,及時行樂。
本文主要分享,如何實現YOLO v3的演算法細節,Keras框架。這是第2篇,模型。當然還有第3篇,至第n篇,畢竟,這是一個完整版 :)
本文的GitHub原始碼:github.com/SpikeKing/k…
已更新:
- 第1篇 訓練:mp.weixin.qq.com/s/T9LshbXoe…
- 第2篇 模型:mp.weixin.qq.com/s/N79S9Qf1O…
- 第3篇 網路:mp.weixin.qq.com/s/hC4P7iRGv…
- 第4篇 真值:mp.weixin.qq.com/s/5Sj7QadfV…
- 第5篇 Loss:mp.weixin.qq.com/s/4L9E4WGSh…
歡迎關注,微信公眾號 深度演算法 (ID: DeepAlgorithm) ,瞭解更多深度技術!
入口
在訓練中,呼叫create_model
方法建立演算法模型。因此,本節重點分析create_model
的實現邏輯。
在create_model
方法中,建立YOLO v3的演算法模型,其中方法引數是:
input_shape
:輸入圖片的尺寸,預設是(416, 416);anchors
:預設的9種anchor box,結構是(9, 2);num_classes
:類別個數,在建立網路時,只需類別數即可。在網路中,類別值按0~n排列,同時,輸入資料的類別也是用索引表示;load_pretrained
:是否使用預訓練權重。預訓練權重,既可以產生更好的效果,也可以加快模型的訓練速度;freeze_body
:凍結模式,1或2。其中,1是凍結DarkNet53網路中的層,2是隻保留最後3個1×1的卷積層,其餘層全部凍結;weights_path
:預訓練權重的讀取路徑;
如下:
def create_model(input_shape, anchors, num_classes, load_pretrained=True, freeze_body=2,
weights_path=`model_data/yolo_weights.h5`):
複製程式碼
邏輯
在create_model
方法中,先將輸入引數,進行處理:
- 拆分圖片尺寸的寬h和高w;
- 建立圖片的輸入層
image_input
。在輸入層中,既可顯式指定圖片尺寸,如(416, 416, 3),也可隱式指定,用“?”代替,如(?, ?, 3); - 計算anchor的數量
num_anchors
; - 根據anchor的數量,建立真值
y_true
的輸入格式。
具體的實現,如下:
h, w = input_shape # 尺寸
image_input = Input(shape=(w, h, 3)) # 圖片輸入格式
num_anchors = len(anchors) # anchor數量
# YOLO的三種尺度,每個尺度的anchor數,類別數+邊框4個+置信度1
y_true = [Input(shape=(h // {0: 32, 1: 16, 2: 8}[l], w // {0: 32, 1: 16, 2: 8}[l],
num_anchors // 3, num_classes + 5)) for l in range(3)]
複製程式碼
其中,真值y_true
,真值即Ground Truth。“//”是Python語法中的整除符號。通過迴圈,建立3個Input層的列表,作為y_true
。y_true
的張量(Tensor)結構,如下:
Tensor("input_2:0", shape=(?, 13, 13, 3, 6), dtype=float32)
Tensor("input_3:0", shape=(?, 26, 26, 3, 6), dtype=float32)
Tensor("input_4:0", shape=(?, 52, 52, 3, 6), dtype=float32)
複製程式碼
其中,在真值y_true
中,第1位是輸入的樣本數,第2~3位是特徵圖的尺寸,如13×13,第4位是每個圖中的anchor數,第5位是:類別(n)+4個框值(xywh)+框的置信度(是否含有物體)。
接著,通過傳入,輸入Input層image_input
、每個尺度的anchor數num_anchors//3
和類別數num_classes
,構建YOLO v3的網路yolo_body
,這個yolo_body
方法是核心邏輯。
即:
model_body = yolo_body(image_input, num_anchors // 3, num_classes)
複製程式碼
下一步,是載入預訓練權重的邏輯塊:
- 根據預訓練權重的地址
weights_path
,載入權重檔案,設定引數為,按名稱對應by_name
,略過不匹配skip_mismatch
; - 選擇凍結模式:
- 模式1是凍結185層,模式2是保留最底部3層,其餘全部凍結。整個模型共有252層;
- 將所凍結的層,設定為不可訓練,
model_body.layers[i].trainable=False
;
實現:
if load_pretrained: # 載入預訓練模型
model_body.load_weights(weights_path, by_name=True, skip_mismatch=True)
if freeze_body in [1, 2]:
# Freeze darknet53 body or freeze all but 3 output layers.
num = (185, len(model_body.layers) - 3)[freeze_body - 1]
for i in range(num):
model_body.layers[i].trainable = False # 將其他層的訓練關閉
複製程式碼
其中,185層是DarkNet53網路的層數,而最底部3層是3個1×1的卷積層,用於預測最終結果。185層是DarkNet53網路的最後一個殘差(Residual)單元,其輸入和輸出如下:
input: [(None, 13, 13, 1024), (None, 13, 13, 1024)]
output: (None, 13, 13, 1024)
複製程式碼
最底部3個1×1的卷積層,將3個特徵矩陣轉換為3個預測矩陣,其格式如下:
1: (None, 13, 13, 1024) -> (None, 13, 13, 18)
2: (None, 26, 26, 512) -> (None, 26, 26, 18)
3: (None, 52, 52, 256) -> (None, 52, 52, 18)
複製程式碼
下一步,構建模型的損失層model_loss
,其內容如下:
- Lambda是Keras的自定義層,輸入為
model_body.output
和y_true
,輸出output_shape
是(1,),即一個損失值; - 自定義Lambda層的名字name為
yolo_loss
; - 層的引數是錨框列表anchors、類別數
num_classes
和IoU閾值ignore_thresh
。其中,ignore_thresh
用於在物體置信度損失(object confidence loss)中過濾IoU(Intersection over Union,重疊度)較小的框; yolo_loss
是損失函式的核心邏輯。
實現如下:
model_loss = Lambda(yolo_loss,
output_shape=(1,), name=`yolo_loss`,
arguments={`anchors`: anchors,
`num_classes`: num_classes,
`ignore_thresh`: 0.5}
)(model_body.output + y_true)
複製程式碼
下一步,構建完整的演算法模型,步驟如下:
- 模型的輸入層:
model_body
的輸入(即image_input
)和真值y_true
; - 模型的輸出層:自定義的
model_loss
層,其輸出是一個損失值(None,1); - 儲存模型的網路圖
plot_model
和列印網路結構model.summary()
;
即:
model = Model(inputs=[model_body.input] + y_true, outputs=model_loss) # 模型
plot_model(model, to_file=os.path.join(`model_data`, `model.png`), show_shapes=True, show_layer_names=True) # 儲存網路結構
model.summary() # 列印網路
複製程式碼
其中,model_body.input
是任意(?)個(416,416,3)
的圖片;y_true
是已標註資料所轉換的真值結構,即:
[Tensor("input_2:0", shape=(?, 13, 13, 3, 6), dtype=float32),
Tensor("input_3:0", shape=(?, 26, 26, 3, 6), dtype=float32),
Tensor("input_4:0", shape=(?, 52, 52, 3, 6), dtype=float32)]
複製程式碼
最終,這些邏輯,完成演算法模型model的構建。
補充1. IoU
IoU,即Intersection over Union,用於計算兩個圖的重疊度,用於計算兩個標註框(bounding box)之間的相關度,值越高,相關度越高。在NMS(Non-Maximum Suppression,非極大值抑制)或計算mAP(mean Average Precision)中,都會使用IoU判斷兩個框的相關性。
如圖:
實現:
def bb_intersection_over_union(boxA, boxB):
boxA = [int(x) for x in boxA]
boxB = [int(x) for x in boxB]
xA = max(boxA[0], boxB[0])
yA = max(boxA[1], boxB[1])
xB = min(boxA[2], boxB[2])
yB = min(boxA[3], boxB[3])
interArea = max(0, xB - xA + 1) * max(0, yB - yA + 1)
boxAArea = (boxA[2] - boxA[0] + 1) * (boxA[3] - boxA[1] + 1)
boxBArea = (boxB[2] - boxB[0] + 1) * (boxB[3] - boxB[1] + 1)
iou = interArea / float(boxAArea + boxBArea - interArea)
return iou
複製程式碼
補充2. 凍結網路層
在微調(fine-tuning)中,需要確定凍結的層數和可訓練的層數,主要取決於,資料集相似度和新資料集的大小。原則上,相似度越高,則固定(fix)的層數越多;新資料集越大,不考慮訓練時間的成本,則可訓練更多的層數。然後可能也要考慮資料集本身的類別間差異度,但上面說的規則基本上還是成立的。
例如,在圖片分類的網路中,底層一般是顏色、輪廓、紋理等基礎結構,顯然大部分問題都由這些相同的基礎結構組成,所以可以凍結這些層。層數越高,所具有泛化性越高,例如這些層會包含對鞋子、裙子和眼睛等,具體語義資訊,比較敏感的神經元。所以,對於新的資料集,就需要訓練這些較高的層。同時,比如一個高層神經元對車的輪胎較為敏感,不等於輸入其它影像,就無法啟用,因而,普通問題甚至可以只訓練最後全連線層。
在Keras中,通過設定每層的trainable引數,即可控制是否凍結該層,如:
model_body.layers[i].trainable = False
複製程式碼
OK, that`s all! Enjoy it!
歡迎關注,微信公眾號 深度演算法 (ID: DeepAlgorithm) ,瞭解更多深度技術!