通過MXnet詳細瞭解YOLOv2的演算法以及其運算過程
YOLO演算法在object_detection領域中算是比較有意思的分支,2017年CVPR上的YOLOv2對原來的YOLO演算法進行了提升,本次通過對Mxnet來對YOLOv2的演算法進行詳細的瞭解。
1.模型載入
模型載入中,主要是使用pre-training,將整個模型的除最後兩層的引數載入到模型中,然後通過將要訓練的類別以及對應的初始anchor輸入,得到predict_layer,將predict_layer載入進去即可得到整個的網路結構。
2.訓練程式碼
首先初始化一個訓練器,然後訓練的迴圈,在訓練初始先將幾個Loss值重置為,然後for i, batch in enumerate(train_data)每次迴圈都讀取一個batch的資料。x = net(x)是資料從輸入到輸出預測結果,比如當anchor數量為2、輸入影象大小為256*256,batch size為32時,該行程式碼的輸入是32*3*256*256,輸出是32*14*16*16,其中14是2*(2+1+4),括號中的三個值分別表示類別數、score和座標資訊。output, cls_pred, score, xywh = yolo2_forward(x, 2, scales)一行呼叫yolo2_forward函式
2.1
yolo2_forward函式用來將網路輸出進行整理和轉換。以這份程式碼的網路結構以及輸入尺寸是3*256*256,batch_size=32為例,yolo2_forward函式的輸入x是32*14*16*16。stride = num_class + 5這裡的5是一個score加上四個座標相關的值。x = x.transpose((0, 2, 3, 1))是將輸出channel移到最後一個維度,然後通過x = x.reshape((0, 0, 0, -1, stride))得到5維的輸出,前面3維不變,分別是batch size,weight,height,第4維是anchor的數量,第5維就是每個anchor對應的引數(2個類別數+1個score+4個座標值),所以得到的x是32*16*16*2*7。cls_pred = x.slice_axis(begin=0, end=num_class, axis=-1)是取x的最後一維的前num_class個矩陣(這裡是2)作為類別預測結果。 score_pred = x.slice_axis(begin=num_class, end=num_class + 1, axis=-1)是取x的最後一維的接下來1個矩陣作為score的預測結果。 xy_pred = x.slice_axis(begin=num_class + 1, end=num_class + 3, axis=-1)是取x的最後一維的再接下來的2個矩陣作為box的中心點座標預測結果。 wh = x.slice_axis(begin=num_class + 3, end=num_class + 5, axis=-1)是取x的最後一維的再接下來的2個矩陣作為box的寬高預測結果。這樣長度為7的最後一維就分清楚了。這裡score = nd.sigmoid(score_pred)和 xy = nd.sigmoid(xy_pred)都是做歸一化,前者是因為score的範圍在0到1之間,後者是因為要用到grid cell的相對座標,所以需要0到1範圍(可以看原文Figure3的bx和by計算,這裡模型預測得到的xy對應Figure3中的tx和ty)。transform_center是用來將每個grid cell裡面的相對座標轉換成圖片上的相對座標。transform_size函式是將模型輸出的寬高處理成實際的寬高。cid是預測的每個box的類別。left、top、right、bottom是預測的box的邊界。
transform_center函式是用來將每個grid cell裡面的相對座標轉換成圖片上的相對座標。首先輸入xy是32*16*16*2*2大小,那麼xy[0,1,1,0,:]就表示第一個輸入的16*16的feature map上的(1,1)位置的第0個anchor的weight和height,feature map上的每個點代表一個grid cell,這個weight和height就是這個grid cell中某個點相對於grid cell的左上角的距離,如果weight=height=1,那麼這個點就是grid cell的右下角點。offset_y是32*16*16*2*1大小,其中16*16是第一行為0,第二行為1…最後一行為15的二維矩陣,其他維度都是直接broadcast過去的,offset_x同理。這樣在執行x + offset_x操作時,對於x[b,h,2,n,0]就是加上2,x[b,h,4,n,0]就是加上4。最後除以w或者除以h也是歸一化的操作,使得最後得到的x和y範圍在0到1之間。因此這個函式的作用就是實現論文中Figure3的加號這一步。
transform_size函式和transform_center函式類似。實現的是論文中Figure3的這一步(如下圖公式)。輸入wh對應tw和th。aw和ah就是box的寬高資訊。
2.2
yolo2_target函式構造模型訓練目標。這裡輸入labels就是ground truth,尺寸是32*1*5,1表示只有1個object,5包含1個class標籤和4個座標資訊。for b in range(output.shape[0])是遍歷batch中的每個輸入,label是k*5大小的numpy array,k就是object數量,一般正常的object標籤都是大於0的,所以這裡valid_label是為了過濾掉那些錯誤的標籤。輸入scores的尺寸中n表示anchor的數量,h和w針對輸入影象是256*256的情況分別是16和16。for l in valid_label就遍歷一張圖中所有有效的object標註資訊,因為標註資料的座標是採取框的左上角和右下角座標(還是相對座標,也就是值在0到1),所以通過簡單的加減可得到gx、gy、gw和gh;ind_x和ind_y則是對應於輸入的座標,比如你的輸入feature map是16*16,換句話說ind_x和ind_y就是16*16的feature map上的某個grid cell的座標。因此重點來了:tx = gx * w - ind_x和ty = gy * h - ind_y,tx和ty是模型迴歸的目標值。intersect是計算每個anchor和ground truth的交集面積,因此是一個1*n的numpy array,n是anchor的數量;ovps是計算交集面積佔並集面積的比例,也就是IOU,也是1*n大小。best_match是選擇IOU最大的那個anchor的index。接下來的幾行賦值語句中都用到了ind_x和ind_y,這就是為什麼說在YOLO演算法中是以object的ground truth框的中心所在的box來預測該object,實際上所謂的box都是隱式的,從這裡的介紹也可以看出,先是按照box的尺寸去匹配目前的這個object,找IOU最大的box,然後再一個ndarray中將ground truth的資訊賦給該box,包括socre、座標、類別標籤、哪個box以及中心點座標。target_id[b, ind_y, ind_x, best_match, :] = l[0]是將IOU最大的anchor的標籤賦值為ground truth的標籤,只要沒進行這個賦值的點的anchor的標籤都是-1,表示背景。target_score[b, ind_y, ind_x, best_match, :] = 1.0是將best_match的box的score賦值為1,也就是置信度為1,其他都為0。tw和th的計算是論文中Figure3的公式的相反過程。因此最後的target_box放的就是模型訓練的目標,符號和論文中的公式符號都是一一對應的。sample_weight表示權重。最後得到的tx和ty對應論文中sigmoid函式處理過的結果。