C代碼版本的MTCNN 從tensorflow權重參數生成bin檔案
- 摘要
- 如何生成bin檔案
- 權重檔案寫入順序
-
- 1、conv層權重參數
- 2、conv層偏置、prelu層的寫入
- 3、fc層
- 調試方法介紹
- C代碼修改
- 其他問題
摘要
MTCNN是一個優秀的人臉檢測模型,在網上有各種架構下的版本,在項目中需要使用MTCNN的C代碼版本,該版本的作者并沒有提供生成代碼運作的txt權重參數檔案的程式,同時使用txt檔案來存參數,檔案的體積比較大,在項目中我們需要使用tensorflow訓練出來的權重參數給模型運作,是以就涉及到如何把tensorflow訓練出來的權重檔案轉化為bin檔案,用于替代程式中的txt檔案
如何生成bin檔案
以pnet.bin的生成為例:
PnetOutFile = "Pnet.bin"
PnetBinFile = open(PnetOutFile,'wb')
PnetBinFile.write(struct.pack('f', Pconv1_w[y,x,i,j]))
PnetBinFile.close()
其中的“f”是寫入浮點數的意思,Pconv1_w[y,x,i,j]是我們從tensorflow權重檔案中取出來的參數,每執行一次則向bin檔案中存入一個參數,存取都是按順序的。最後記得close檔案
權重檔案寫入順序
1、conv層權重參數
Pconv1_w = sess.run(pnet_var[0])
ky, kx, num_in_map, num_out_kerns = Pconv1_w.shape
print(0,Pconv1_w.shape)
for j in range(num_out_kerns):
for i in range(num_in_map):
for y in range(ky):
for x in range(kx):
PnetBinFile.write(struct.pack('f', Pconv1_w[y,x,i,j]))
卷積層通過sess.run讀取到了一個權重矩陣Pconv1_w[y,x,i,j],這個矩陣是四維的,次元分别為y方向權重、x方向權重、輸入通道、輸出通道。
我們需要把這個四維矩陣轉化為一列,存入bin檔案中,用于給c代碼讀取。
2、conv層偏置、prelu層的寫入
#Rconv1_b
Rconv1_b = sess.run(rnet_var[1])
print(1,Rconv1_b.shape)
for item in range(0,len(Rconv1_b[:])):
RnetBinFile.write(struct.pack('f',Rconv1_b[item]))
#RPReLU1
RPReLU1 = sess.run(rnet_var[2])
print(2,RPReLU1.shape)
for item in range(0,len(RPReLU1[:])):
RnetBinFile.write(struct.pack('f',RPReLU1[item]))
這兩個層都是一維的原理相同,比較簡單,直接按順序寫就好了
3、fc層
#Rconv4_w (576,128)
Rconv4_w = sess.run(rnet_var[9])
print(9,Rconv4_w.shape)
num_in_map, num_out_kerns = Rconv4_w.shape
RfcTemp = []
RfcTemp2 = []
for j in range(num_out_kerns):#128
for i in range(num_in_map):#576
RfcTemp.append(Rconv4_w[i,j])
for n in range(64):
for k in range(9):
RfcTemp2.append(RfcTemp[n + k*64])
for l in range(576):
RnetBinFile.write(struct.pack('f', RfcTemp2[l]))
RfcTemp = []
RfcTemp2 = []
這一層比較難,需要做順序的調換
做調換的原因是Tensorflow架構和C代碼下的全連接配接輸出一直不一緻,原因在于把上一層卷積出來的結果reshape成一列的過程不正确,C代碼中并沒有寫這個reshape成一列的函數,在存儲中上一層輸出的資料conv3_out->pdata在記憶體中就是存層一列的,C代碼把它直接送入fc層,導緻了reshape順序的錯誤。
詳細的排列順序如下:在TF或C代碼中,Rnet的FC前一層(PReLU3)的輸出為(3,3, 64),對其進行重排如下:每個channel有9個參數,一共64個kernel,把每個kernel同個位置的參數取出,按順序排列在一起。如下圖:
知道了c代碼和TF的排列順序,就可以按照順序去做轉化了
調試方法介紹
在調試過程中需要我們需要一層一層調,先保證TF和C代碼的輸入一緻,通過相同的層,如果輸出一緻的話,那麼權重的順序就排列對了,在c代碼中直接使用自帶的權重列印函數pBoxShow把資料列印出來:
feature2Matrix(this->rgb, this->conv1_matrix, this->conv1_wb);
convolution(this->conv1_wb, this->rgb, this->conv1_out, this->conv1_matrix);
prelu(this->conv1_out, this->conv1_wb->pbias, this->prelu_gmma1->pdata);
pBoxShow(this->conv1_out);
在TF中,我們采用這樣的政策,把我們要看的層後面的操作屏蔽,然後輸出我們要看的層的輸出作為最後的網絡輸出:
這樣網絡輸出就是prelu1層操作後的結果,然後再列印出來:
out = pnet(img)
im_data = np.array(out)
for c in range(3):
print("channel:",c)
tempImg = []
for y in range(17):
for x in range(21):
tempImg.append(im_data[y,x,c])
print(tempImg)
tempImg = []
print("\n")
C代碼修改
網絡的輸出結果一緻後,運作程式發現框可以準确預測,可是關鍵并不能準确預測。
這是因為在TF架構和C代碼中對于網絡最終輸出資料轉換成坐标的程式有差異,直接使用會造成關鍵點不正确
解決方法:修改Onet中的關鍵點的計算代碼
其他問題
C代碼中也有一些缺陷:
network.cpp代碼中的maxpooling()函數有缺陷,在pooling過程中如果遇見需要補零的feature map,隻對x方向補零,卻沒有y方向補零,導緻最後一行的maxpooling輸出有差錯
解決辦法:在network.cpp中增加了y方向的補零代碼