MXnet轉caffe

吃不胖的卷卷發表於2019-05-30

mxnet雖好,但是mxnet框架還是有點小眾,MXnet現在越來越受歡迎了,不過現在要把mxnet訓練的模型部落地到移動端,有必要在Inference階段將其轉換為其他框架,以便後續統一部署和管理。Caffe作為小巧靈活的老資格框架,使用靈活,部署方便,所以嘗試將mxnet訓練的mobilefacenet模型轉換為Caffe。這裡簡單記錄用mxnet訓的mobilefacenet模型轉換為Caffe模型過程遇到的坑,以及完美解決(轉換後精度幾乎無損,並沒有網上說的會掉點)

先寫到這裡 以後再慢慢補充(逃跑.JPG)

繼續填坑。。。。。。

首先展示下結果,下面這個圖是mxnet訓的mobilefacenet模型對對一張人臉圖片輸出的128維特徵值

下面這張是將maxnet模型轉為caffemodel後,對同一張人臉圖片用caffemodel輸出的結果

可以看出,基本上是無損的,小數點後5、6位才會出現誤差。

下面說明一下實現流程

環境:ubuntu16.04 64bit

          mxnet版本1.4

 

 主要是站在巨人肩膀上,從github上下載相關mxnet2caffe的demo,然後做各種完善。 從此博主開始了填坑之旅~~

  首先下載相應的demo

git clone https://github.com/cypw/MXNet2Caffe.git

  閱覽下程式碼,大概知道流程,就是通過  json2prototxt.py將json轉為prototxt, 再執行mxnet2caffe.py,將params轉為caffemodel,    這裡我們要做一些修改,如下:

 開啟json2prototxt.py ,將模型改為我要轉的mobilefacenet模型;

   開啟prototxt_basic.py,將輸入維度改為要轉換的模型對應的維度

 開啟json檔案,看一下里面格式是怎麼樣的

裡面有 "op" 、"name" 和 "attrs"三個索引,“attrs”中又包含其他索引

但是,prototxt_basic.py中,是下面這種形式

如果直接執行json2prototxt.py,會報下面這個錯誤

修改如下:將'param'改為'attrs' ,就可以了

並新增PReLU和Eltwise層(我們模型需要這兩層,原始碼中並沒有實現,原始碼對層支援的很少,不知道為什麼還有那麼對   start 無奈.gif)

def LeakyReLU(txt_file, info):
  if info['attrs']['act_type'] == 'elu':
    txt_file.write('layer {\n')
    txt_file.write('  bottom: "%s"\n'     % info['bottom'][0])
    txt_file.write('  top: "%s"\n'        % info['top'])
    txt_file.write('  name: "%s"\n'       % info['top'])
    txt_file.write('  type: "ELU"\n')
    txt_file.write('  elu_param { alpha: 0.25 }\n')
    txt_file.write('}\n')
    txt_file.write('\n')
  elif info['attrs']['act_type'] == 'prelu':
    txt_file.write('layer {\n')
    txt_file.write('  bottom: "%s"\n'     % info['bottom'][0])
    txt_file.write('  top: "%s"\n'        % info['top'])
    txt_file.write('  name: "%s"\n'       % info['top'])
    txt_file.write('  type: "PReLU"\n')
    txt_file.write('}\n')
    txt_file.write('\n')      
  else:
    raise Exception("unsupported Activation")

def Eltwise(txt_file, info, op):
  txt_file.write('layer {\n')
  txt_file.write('  type: "Eltwise"\n')
  txt_file.write('  top: "%s"\n'        % info['top'])
  txt_file.write('  name: "%s"\n'       % info['top'])
  for btom in info['bottom']:
      txt_file.write('  bottom: "%s"\n' % btom)
  txt_file.write('  eltwise_param { operation: %s }\n' % op)
  txt_file.write('}\n')
  txt_file.write('\n')

  在prototxt_basic.py檔案的write_node函式中新增下面幾行

    elif info['op'] == 'LeakyReLU':
        LeakyReLU(txt_file, info)
    elif info['op'] == 'elemwise_add':
        ElementWiseSum(txt_file, info)

 

 執行json2prototxt.py ,結果出現下面這個警告並退出

 從警告可以看出,程式碼無法識別_minus_scalar這個op

 開啟model_mobile_glint_amsoftmax-symbol.json

可以看出,_minus_scalar和_mul_scalar這兩個op對輸入資料做了歸一化,mxnet資料預處理放在了模型裡面

在prototxt_basic中,程式沒有對這個op做處理,其實也沒必要對它做處理,這個op可以直接skip,

在basic_prototxt.py的Convolution函式中,新增如下程式碼

  if('scalar' in info['bottom'][0] ):
      info['bottom'][0] = 'data'

如下圖所示

至此,我們就可以成功將json轉為prototxt了

 

為了解決在轉引數時的名字匹配問題

開啟mxnet2caffe.py,將第42行的elif '_gamma' in key_i: 改為 elif '_gamma' in key_i  and 'relu' not in key_i :

取消第44行到底46行的註釋,如下圖所示

至此,就可以成功執行mxnet2caffe.py了

 

 

但是,校驗最後一層輸出的時候 ,發現資料對不上,差異非常大,後來又對比了第一個卷積層的輸出,資料完全對的上。

後來發現是卷積之後第一個全連線層的問題,,名字不匹配,轉出來的caffe模型全連線層的name="pre_fc1", 全連線層之後接的batchnorm層名字叫"fc1",但是mxnet獲得的全連線層權重資訊欄位是“fc1_weight”,replace("_weight")後變為“fc1”,程式相當於把mxnet全連線層的權重資訊賦值給net.params["fc1"],也就是賦值給caffe全連線之後的batchnorm層,所有出現了錯誤

手動將mxnet全連線層的引數賦值給caffe對應的全連線層,全連線之後的batchnorm和scale層的值也有重新賦值

有一點要注意,我們mobilefacenet全連線之後的bn用的是fix_gamma形式

但是mxnet模型對應的gamma引數為0.00030139,這個值並沒有用到,caffe 相應的gamma引數有等於1才行,eps的值也要改為2e-5。

至此,模型轉換成功~~~

 

對mxnet轉caffe的一點見解:

 如果出現誤差,往往都是eps引起的,mxnet模型1e-3 caffe預設1e-5 這點要注意,fix_gamma的問題也要注意,同時也要保證每一層的引數都要正確賦值。在驗證的時候,一定會要保證資料預處理的方式一致。

 

 

相關文章