<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          人臉106點(diǎn)Caffe模型如何部署到MsnhNet

          共 10313字,需瀏覽 21分鐘

           ·

          2020-11-20 00:51

          ?

          【GiantPandaCV導(dǎo)語(yǔ)】大家好,今天為大家介紹一下如何部署一個(gè)人臉106關(guān)鍵點(diǎn)模型到MsnhNet上,涉及到Caffe和Pytorch,MsnhNet模型轉(zhuǎn)換,融合BN簡(jiǎn)化網(wǎng)絡(luò)和如何編寫(xiě)MsnhNet預(yù)測(cè)代碼等等。

          ?

          1. 前言

          之前,MsnhNet主要支持了將Pytorch模型轉(zhuǎn)換為MsnhNet框架可以運(yùn)行的模型文件(*.msnhnet*.bin),并且我們?cè)谥暗?a style="text-decoration: none;word-wrap: break-word;color: #40B8FA;font-weight: normal;border-bottom: 1px solid #3BAAFA;" data-linktype="2">Pytorch轉(zhuǎn)Msnhnet模型思路分享文章中分享了這個(gè)轉(zhuǎn)換的思路。

          最近嘗試部署一個(gè)開(kāi)源的人臉106點(diǎn)Caffe模型(https://github.com/dog-qiuqiu/MobileNet-Yolo/tree/master/yoloface50k-landmark106)到MsnhNet中,所以這篇文章就記錄了我是如何將這個(gè)Caffe模型轉(zhuǎn)換到MsnhNet并進(jìn)行部署的。

          2. 通用的轉(zhuǎn)換思路

          由于我們已經(jīng)在Pytroch2Msnhnet這個(gè)過(guò)程上花費(fèi)了比較大的精力,所以最直接的辦法就是直接將Caffe模型轉(zhuǎn)為Pytorch模型,然后調(diào)用已有的Pytorch2Msnhnet工具完成轉(zhuǎn)換,這樣是比較快捷省事的。

          我參考https://github.com/UltronAI/pytorch-caffe這個(gè)工程里面的caffe2pytorch工具新增了一些上面提到的yoloface50k-landmark106關(guān)鍵點(diǎn)模型要用到的OP,如PReLU,nn.BatchNorm1D以及只有2個(gè)維度的Scale層等,例如將Scale層重寫(xiě)為:

          class?Scale(nn.Module):
          ????def?__init__(self,?channels):
          ????????super(Scale,?self).__init__()
          ????????self.weight?=?Parameter(torch.Tensor(channels))
          ????????self.bias?=?Parameter(torch.Tensor(channels))
          ????????self.channels?=?channels
          ?#?Python?有一個(gè)內(nèi)置的函數(shù)叫?repr,它能把一個(gè)對(duì)象用字符串的形式表達(dá)出來(lái)以便辨認(rèn),這就是“字符串表示形式”
          ????def?__repr__(self):
          ????????return?'Scale(channels?=?%d)'?%?self.channels

          ????def?forward(self,?x):
          ??#?landmark網(wǎng)絡(luò)最后的全連接層后面接了Scale,所以需要考慮Scale層輸入為2維的情況
          ????????if?x.dim()?==?2:
          ????????????nB?=?x.size(0)
          ????????????nC?=?x.size(1)
          ????????????x?=?x?*?self.weight.view(1,?nC).expand(nB,?nC)?+?\
          ????????????????self.bias.view(1,?nC).expand(nB,?nC)
          ????????else:
          ????????????nB?=?x.size(0)
          ????????????nC?=?x.size(1)
          ????????????nH?=?x.size(2)
          ????????????nW?=?x.size(3)
          ????????????x?=?x?*?self.weight.view(1,?nC,?1,?1).expand(nB,?nC,?nH,?nW)?+?\
          ????????????????self.bias.view(1,?nC,?1,?1).expand(nB,?nC,?nH,?nW)
          ????????return?x

          可以看到這個(gè)Caffe里面的Scale層Pytorch是不原生支持的,這是Caffe特有的層,所以這里寫(xiě)一個(gè)Scale類繼承nn.Module來(lái)拼出一個(gè)Scale層。除了Scale層還有其它的很多層是這種做法,例如Eletwise層可以這樣來(lái)拼:

          class?Eltwise(nn.Module):
          ????def?__init__(self,?operation='+'):
          ????????super(Eltwise,?self).__init__()
          ????????self.operation?=?operation

          ????def?__repr__(self):
          ????????return?'Eltwise?%s'?%?self.operation

          ????def?forward(self,?*inputs):
          ????????if?self.operation?==?'+'?or?self.operation?==?'SUM':
          ????????????x?=?inputs[0]
          ????????????for?i?in?range(1,len(inputs)):
          ????????????????x?=?x?+?inputs[i]
          ????????elif?self.operation?==?'*'?or?self.operation?==?'MUL':
          ????????????x?=?inputs[0]
          ????????????for?i?in?range(1,len(inputs)):
          ????????????????x?=?x?*?inputs[i]
          ????????elif?self.operation?==?'/'?or?self.operation?==?'DIV':
          ????????????x?=?inputs[0]
          ????????????for?i?in?range(1,len(inputs)):
          ????????????????x?=?x?/?inputs[i]
          ????????elif?self.operation?==?'MAX':
          ????????????x?=?inputs[0]
          ????????????for?i?in?range(1,len(inputs)):
          ????????????????x?=torch.max(x,?inputs[i])
          ????????else:
          ????????????print('forward?Eltwise,?unknown?operator')
          ????????return?x

          介紹了如何在Pytorch中拼湊出Caffe的特有層之后,我們就可以對(duì)Caffe模型進(jìn)行解析,然后利用解析后的層關(guān)鍵信息完成Caffe模型到Pytorch模型的轉(zhuǎn)換了。解析Caffe模型的代碼實(shí)現(xiàn)在https://github.com/msnh2012/Msnhnet/blob/master/tools/caffe2Msnhnet/prototxt.py文件,我們截出一個(gè)核心部分說(shuō)明一下,更多細(xì)節(jié)讀者可以親自查看。

          我們以一個(gè)卷積層為例,來(lái)理解一下這個(gè)Caffe模型中的prototxt解析函數(shù):

          layer?{
          ??name:?"conv1_conv2d"
          ??type:?"Convolution"
          ??bottom:?"data"
          ??top:?"conv1_conv2d"
          ??convolution_param?{
          ????num_output:?8
          ????bias_term:?false
          ????group:?1
          ????stride:?2
          ????pad_h:?1
          ????pad_w:?1
          ????kernel_h:?3
          ????kernel_w:?3
          ??}
          }

          解析prototxt文件的代碼實(shí)現(xiàn)如下,結(jié)合上面卷積層的prototxt表示和下面代碼的注釋?xiě)?yīng)該很好理解:

          def?parse_prototxt(protofile):
          ?#?caffe的每個(gè)layer以{}包起來(lái)
          ????def?line_type(line):
          ????????if?line.find(':')?>=?0:
          ????????????return?0
          ????????elif?line.find('{')?>=?0:
          ????????????return?1
          ????????return?-1

          ????def?parse_block(fp):
          ????????#?使用OrderedDict會(huì)根據(jù)放入元素的先后順序進(jìn)行排序,所以輸出的值是排好序的
          ????????block?=?OrderedDict()
          ????????line?=?fp.readline().strip()
          ????????while?line?!=?'}':
          ????????????ltype?=?line_type(line)
          ????????????if?ltype?==?0:?#?key:?value
          ????????????????#print?line
          ????????????????line?=?line.split('#')[0]
          ????????????????key,?value?=?line.split(':')
          ????????????????key?=?key.strip()
          ????????????????value?=?value.strip().strip('"')
          ????????????????if?key?in??block:
          ????????????????????if?type(block[key])?==?list:
          ????????????????????????block[key].append(value)
          ????????????????????else:
          ????????????????????????block[key]?=?[block[key],?value]
          ????????????????else:
          ????????????????????block[key]?=?value
          ????????????elif?ltype?==?1:?#?獲取塊名,以卷積層為例返回[layer,?convolution_param]
          ????????????????key?=?line.split('{')[0].strip()
          ????????????????#?遞歸
          ????????????????sub_block?=?parse_block(fp)
          ????????????????block[key]?=?sub_block
          ????????????line?=?fp.readline().strip()
          ????????????#?忽略注釋
          ????????????line?=?line.split('#')[0]
          ????????return?block

          ????fp?=?open(protofile,?'r')
          ????props?=?OrderedDict()
          ????layers?=?[]
          ????line?=?fp.readline()
          ????counter?=?0
          ????while?line:
          ????????line?=?line.strip().split('#')[0]
          ????????if?line?==?'':
          ????????????line?=?fp.readline()
          ????????????continue
          ????????ltype?=?line_type(line)
          ????????if?ltype?==?0:?#?key:?value
          ????????????key,?value?=?line.split(':')
          ????????????key?=?key.strip()
          ????????????value?=?value.strip().strip('"')
          ????????????if?key?in??props:
          ???????????????if?type(props[key])?==?list:
          ???????????????????props[key].append(value)
          ???????????????else:
          ???????????????????props[key]?=?[props[key],?value]
          ????????????else:
          ????????????????props[key]?=?value
          ????????elif?ltype?==?1:?#?獲取塊名,以卷積層為例返回[layer,?convolution_param]
          ????????????key?=?line.split('{')[0].strip()
          ????????????if?key?==?'layer':
          ????????????????layer?=?parse_block(fp)
          ????????????????layers.append(layer)
          ????????????else:
          ????????????????props[key]?=?parse_block(fp)
          ????????line?=?fp.readline()

          ????if?len(layers)?>?0:
          ????????net_info?=?OrderedDict()
          ????????net_info['props']?=?props
          ????????net_info['layers']?=?layers
          ????????return?net_info
          ????else:
          ????????return?props

          然后解析CaffeModel比較簡(jiǎn)單,直接調(diào)用caffe提供的接口即可,代碼實(shí)現(xiàn)如下:

          def?parse_caffemodel(caffemodel):
          ????model?=?caffe_pb2.NetParameter()
          ????print?('Loading?caffemodel:?'),?caffemodel
          ????with?open(caffemodel,?'rb')?as?fp:
          ????????model.ParseFromString(fp.read())

          ????return?model

          解析完Caffe模型之后,我們就拿到了所有Layer的參數(shù)信息和權(quán)重,我們只需要將其對(duì)應(yīng)放到Pytorch實(shí)現(xiàn)的Layer就可以了,這部分的代碼實(shí)現(xiàn)就是https://github.com/msnh2012/Msnhnet/blob/master/tools/caffe2Msnhnet/caffenet.py#L332這里的CaffeNet類這里就不再過(guò)多解釋了,因?yàn)檫@僅僅是一個(gè)構(gòu)件Pytorch模型并加載權(quán)重的過(guò)程,相信熟悉Pytorch的同學(xué)不難看懂和寫(xiě)出這部分代碼。執(zhí)行完這個(gè)過(guò)程之后我們就可以獲得Caffe模型對(duì)應(yīng)的Pytorch模型了。

          3. 精簡(jiǎn)網(wǎng)絡(luò)

          為了讓Pytorch模型轉(zhuǎn)出來(lái)的MsnhNet模型推理更快,我們可以考慮在Caffe轉(zhuǎn)到Pytorch模型時(shí)就精簡(jiǎn)一些網(wǎng)絡(luò)層,比如常規(guī)的Convolution+BN+Scale可以融合為一個(gè)層。我們發(fā)現(xiàn)這里還存在一個(gè)FC+BN+Scale的結(jié)構(gòu),我們也可以一并融合了。這里可以再簡(jiǎn)單回顧一下融合的原理。

          3.1 融合BN原理介紹

          「我們知道卷積層的計(jì)算可以表示為:」

          「然后BN層的計(jì)算可以表示為:」

          「我們把二者組合一下,公式如下:」

          然后令

          「那么,合并BN層后的卷積層的權(quán)重和偏置可以表示為:」

          這個(gè)公式同樣可以用于反卷積,全連接和BN+Scale的組合情況。

          3.2 融合BN

          基于上面的理論,我們可以在轉(zhuǎn)Caffe模型之前就把BN融合掉,這樣我們?cè)贛snhNet上推理更快(另外一個(gè)需要融合的原因是目前MsnhNet的圖優(yōu)化工具還在開(kāi)發(fā)中,暫時(shí)不支持帶BN+Scale層的融合)。Caffe模型融合的代碼我放在https://github.com/msnh2012/Msnhnet/blob/master/tools/caffe2Msnhnet/caffeOptimize/caffeOptimize.py這里了,簡(jiǎn)要介紹如下:

          Caffe BN融合工具

          4. MsnhNet推理

          精簡(jiǎn)網(wǎng)絡(luò)之后我們就可以重新將沒(méi)有BN的Caffe模型轉(zhuǎn)到Pytorch再轉(zhuǎn)到MsnhNet了,這部分的示例如下:

          #?-*-?coding:?utf-8
          #?from?pytorch2caffe?import?plot_graph,?pytorch2caffe
          import?sys
          import?cv2
          import?caffe
          import?numpy?as?np
          import?os
          from?caffenet?import?*
          import?argparse
          import?torch
          from?PytorchToMsnhnet?import?*

          ################################################################################################???
          parser?=?argparse.ArgumentParser(description='Convert?Caffe?model?to?MsnhNet?model.',
          ?????????????????????????????????formatter_class=argparse.ArgumentDefaultsHelpFormatter)
          parser.add_argument('--model',?type=str,?default=None)
          parser.add_argument('--weights',?type=str,?default=None)
          parser.add_argument('--height',?type=int,?default=None)
          parser.add_argument('--width',?type=int,?default=None)
          parser.add_argument('--channels',?type=int,?default=None)

          args?=?parser.parse_args()

          model_def?=?args.model
          model_weights?=?args.weights
          name?=?model_weights.split('/')[-1].split('.')[0]
          width?=?args.width
          height?=?args.height
          channels?=?args.channels


          net?=?CaffeNet(model_def,?width=width,?height=height,?channels=channels)
          net.load_weights(model_weights)
          net.to('cpu')
          net.eval()

          input=torch.ones([1,channels,height,width])

          model_name?=?name?+?".msnhnet"

          model_bin?=?name?+?".msnhbin"

          trans(net,?input,model_name,model_bin)

          獲得了MsnhNet的模型文件之后,我們就可以使用MsnhNet進(jìn)行推理了,推理部分的代碼在https://github.com/msnh2012/Msnhnet/blob/master/examples/landmark106/landmark106.cpp。

          我們來(lái)看看效果,隨便拿一張人臉圖片來(lái)測(cè)試一下:

          原圖
          結(jié)果圖

          landmark的結(jié)果還是比較正確的,另外我們對(duì)比了Caffe/Pytorch/MsnhNet的每層特征值,F(xiàn)loat32情況下相似度均為100%,證明我們的轉(zhuǎn)換過(guò)程是正確的。

          我們?cè)?strong style="color: #3594F7;font-weight: bold;">「X86 CPU」 i7 10700F上測(cè)一下速度(float32推理),結(jié)果如下:

          分辨率線程數(shù)時(shí)間
          112x11215ms
          112x11223.5ms
          112x11242.7ms

          速度還是挺快的,由于本框架目前在x86沒(méi)有太多優(yōu)化,所以這個(gè)速度后面會(huì)進(jìn)一步優(yōu)化的。感興趣的讀者也可以測(cè)試在其它平臺(tái)上這個(gè)模型的速度。

          5. 轉(zhuǎn)換工具支持的OP和用法

          5.1 介紹

          Caffe2msnhnet工具首先將你的Caffe模型轉(zhuǎn)換為Pytorch模型,然后調(diào)用Pytorch2msnhnet工具將Caffe模型轉(zhuǎn)為*.msnhnet*.bin。

          5.2 依賴

          • Pycaffe
          • Pytorch

          5.3 計(jì)算圖優(yōu)化

          • 在調(diào)用caffe2msnhnet.py之前建議使用caffeOPtimize文件夾中的caffeOptimize.py對(duì)原始的Caffe模型進(jìn)行圖優(yōu)化,目前已支持的操作有:

          • Conv+BN+Scale 融合到 Conv

          • Deconv+BN+Scale 融合到Deconv

          • InnerProduct+BN+Scale 融合到InnerProduct

          5.4 Caffe2Pytorch支持的OP

          • Convolution 轉(zhuǎn)為 nn.Conv2d
          • Deconvolution 轉(zhuǎn)為 nn.ConvTranspose2d
          • BatchNorm 轉(zhuǎn)為 nn.BatchNorm2d或者nn.BatchNorm1d
          • Scale 轉(zhuǎn)為 乘/加
          • ReLU 轉(zhuǎn)為 nn.ReLU
          • LeakyReLU 轉(zhuǎn)為 nn.LeakyReLU
          • PReLU 轉(zhuǎn)為 nn.PReLU
          • Max Pooling 轉(zhuǎn)為 nn.MaxPool2d
          • AVE Pooling 轉(zhuǎn)為 nn.AvgPool2d
          • Eltwise 轉(zhuǎn)為 加/減/乘/除/torch.max
          • InnerProduct 轉(zhuǎn)為nn.Linear
          • Normalize 轉(zhuǎn)為 pow/sum/sqrt/加/乘/除拼接
          • Permute 轉(zhuǎn)為torch.permute
          • Flatten 轉(zhuǎn)為torch.view
          • Reshape 轉(zhuǎn)為numpy.reshape/torch.from_numpy拼接
          • Slice 轉(zhuǎn)為torch.index_select
          • Concat 轉(zhuǎn)為torch.cat
          • Crop 轉(zhuǎn)為torch.arange/torch.resize_拼接
          • Softmax 轉(zhuǎn)為torch.nn.function.softmax

          5.5 Pytorch2Msnhnet支持的OP

          • conv2d
          • max_pool2d
          • avg_pool2d
          • adaptive_avg_pool2d
          • linear
          • flatten
          • dropout
          • batch_norm
          • interpolate(nearest, bilinear)
          • cat
          • elu
          • selu
          • relu
          • relu6
          • leaky_relu
          • tanh
          • softmax
          • sigmoid
          • softplus
          • abs
          • acos
          • asin
          • atan
          • cos
          • cosh
          • sin
          • sinh
          • tan
          • exp
          • log
          • log10
          • mean
          • permute
          • view
          • contiguous
          • sqrt
          • pow
          • sum
          • pad
          • +|-|x|/|+=|-=|x=|/=|

          5.6 使用方法舉例

          • python caffe2msnhnet --model landmark106.prototxt --weights landmark106.caffemodel --height 112 --width 112 --channels 3,執(zhí)行完之后會(huì)在當(dāng)前目錄下生成lanmark106.msnhnetlandmark106.bin文件。

          6. 總結(jié)

          至此,我們完成了yoloface50k-landmark106在MsnhNet上的模型轉(zhuǎn)換和部署測(cè)試,如果對(duì)本框架感興趣可以嘗試部署自己的一個(gè)模型試試看,如果轉(zhuǎn)換工具有問(wèn)題請(qǐng)?jiān)趃ithub提出issue或者直接聯(lián)系我們。點(diǎn)擊閱讀原文可以快速關(guān)注MsnhNet,這是我們業(yè)余開(kāi)發(fā)的一個(gè)輕量級(jí)推理框架,如果對(duì)模型部署和算法優(yōu)化感興趣的讀者可以看看,我們也會(huì)在GiantPandaCV公眾號(hào)分享我們的框架開(kāi)發(fā)和算子優(yōu)化相關(guān)的經(jīng)歷。

          7. 參考

          • https://github.com/UltronAI/pytorch-caffe
          • https://github.com/msnh2012/Msnhnet

          為了感謝讀者們的長(zhǎng)期支持,我們今天將送出3本由人民郵電出版社提供的《深入淺出 GAN 生成對(duì)抗網(wǎng)絡(luò)》書(shū)籍,感興趣的小伙伴可以在下方留言板留言,我們將從中抽取幾位小伙伴分別送出一本正版書(shū)籍。

          點(diǎn)擊這里留言

          書(shū)籍封面

          沒(méi)中獎(jiǎng)并且對(duì)本書(shū)也感興趣的小伙伴可以考慮點(diǎn)擊下方的當(dāng)當(dāng)網(wǎng)鏈接自行購(gòu)買:


          歡迎關(guān)注GiantPandaCV, 在這里你將看到獨(dú)家的深度學(xué)習(xí)分享,堅(jiān)持原創(chuàng),每天分享我們學(xué)習(xí)到的新鮮知識(shí)。( ? ?ω?? )?

          有對(duì)文章相關(guān)的問(wèn)題,或者想要加入交流群,歡迎添加BBuf微信:

          二維碼

          為了方便讀者獲取資料以及我們公眾號(hào)的作者發(fā)布一些Github工程的更新,我們成立了一個(gè)QQ群,二維碼如下,感興趣可以加入。

          公眾號(hào)QQ交流群
          瀏覽 76
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  人妻无码在线观看 | 夜夜爽AV | 精品中文字幕视频在线 | 男人的天堂久久 | 91久久久久久久久18 |