<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>
"); //-->

博客專(zhuān)欄

EEPW首頁(yè) > 博客 > 實(shí)用教程詳解:模型部署,用DNN模塊部署YOLOv5目標檢測

實(shí)用教程詳解:模型部署,用DNN模塊部署YOLOv5目標檢測

發(fā)布人:計算機視覺(jué)工坊 時(shí)間:2021-12-15 來(lái)源:工程師 發(fā)布文章

一、什么是模型部署?

在典型的機器學(xué)習和深度學(xué)習項目中,我們通常從定義問(wèn)題陳述開(kāi)始,然后是數據收集和準備(數據預處理)和模型構建(模型訓練),對吧?但是,最后,我們希望我們的模型能夠提供給最終用戶(hù),以便他們能夠利用它。模型部署是任何機器學(xué)習項目的最后階段之一,可能有點(diǎn)棘手。如何將機器學(xué)習模型傳遞給客戶(hù)/利益相關(guān)者?模型的部署大致分為以下三個(gè)步驟:

· 模型持久化

持久化,通俗得講,就是臨時(shí)數據(比如內存中的數據,是不能永久保存的)持久化為持久數據(比如持久化至數據庫中,能夠長(cháng)久保存)。那我們訓練好的模型一般都是存儲在內存中,這個(gè)時(shí)候就需要用到持久化方式,在Python中,常用的模型持久化方式一般都是以文件的方式持久化。

· 選擇適合的服務(wù)器加載已經(jīng)持久化的模型

· 提高服務(wù)接口,拉通前后端數據交流

1.png

PPLNN

二、案例,運行操作:

· 準備ONNX模型

我們在tests/testdata下準備了一個(gè)分類(lèi)模型mnasnet0_5.onnx,可用于測試。

通過(guò)如下手段可以獲取更多的ONNX模型:

可以從OpenMMLab/PyTorch導出ONNX模型:model-convert-guide.md

從ONNX Model Zoo獲取模型:https://github.com/onnx/models

ONNX Model Zoo的模型opset版本都較低,可以通過(guò)tools下的convert_onnx_opset_version.py將opset轉換為11:

python convert_onnx_opset_version.py --input_model input_model.onnx --output_model output_model.onnx --output_opset 11

轉換opset具體請參考:onnx-model-opset-convert-guide.md

· 準備測試圖片

測試圖片使用任何格式均可。我們在tests/testdata下準備了cat0.png和cat1.jpg(ImageNet 的驗證集圖片):

2.jpg

任意大小的圖片都可以正常運行,如果想要resize到224 x 224的話(huà),可以修改程序里的如下變量:

const bool resize_input = false; // 想要resize的話(huà),修改為true即可

· 測試推理服務(wù)

運行
pplnn-build/samples/cpp/run_model/classification <image_file> <onnx_model_file>

推理完成后,會(huì )得到如下輸出:

image preprocess succeed!
[INFO][2021-07-23 17:29:31.341][simple_graph_partitioner.cc:107] total partition(s) of graph[torch-jit-export]: 1.
successfully create runtime builder!
successfully build runtime!
successfully set input data to tensor [input]!
successfully run network!
successfully get outputs!
top 5 results:
1th: 3.416199   284        n02123597 Siamese cat, Siamese
2th: 3.049764   285        n02124075 Egyptian cat
3th: 2.989676   606        n03584829 iron, smoothing iron
4th: 2.812310   283        n02123394 Persian cat
5th: 2.796991   749        n04033901 quill, quill pen

不難看出,這個(gè)程序正確判斷貓是真貓。至此OpenPPL的安裝與圖像分類(lèi)模型推理已完成。另外,在pplnn-build/tools目錄下有可執行文件pplnn,可以進(jìn)行任意模型推理、dump輸出數據、benchmark等操作,具體用法可使用--help選項查看。大家可以基于該示例進(jìn)行改動(dòng),從而更熟悉OpenPPL的用法。

三、DNN模塊部署Yolov5

用opencv的dnn模塊做yolov5目標檢測的程序,包含兩個(gè)步驟:1)、把pytorch的訓練模型pth文件轉換到onnx文件;2)、opencv的dnn模塊讀取onnx文件做前向計算。

1)、把pytorch的訓練模型pth文件轉換到onnx文件

yolov5官方代碼:https://github.com/ultralytics/yolov5

這套程序里的代碼比較亂,在pytorch里,通常是在py文件里定義網(wǎng)絡(luò )結構的,但是官方代碼是在yaml文件定義網(wǎng)絡(luò )結構,利用pytorch動(dòng)態(tài)圖特性,解析yaml文件自動(dòng)生成網(wǎng)絡(luò )結構。

在yaml文件里有depth_multiple和width_multiple,它是控制網(wǎng)絡(luò )的深度和寬度的參數。這么做的好處是能夠靈活的配置網(wǎng)絡(luò )結構,但是不利于理解網(wǎng)絡(luò )結構,假如你想設斷點(diǎn)查看某一層的參數和輸出數值,那就沒(méi)辦法了。

3.jpg

因此,在編寫(xiě)的轉換到onnx文件的程序里,網(wǎng)絡(luò )結構是在py文件里定義的。其次,在官方代碼里,還有一個(gè)奇葩的地方,那就是pth文件。起初,下載官方代碼到本地運行時(shí),torch.load讀取pth文件總是出錯,后來(lái)把pytorch升級到1.7,就讀取成功了??梢钥吹桨姹炯嫒菪圆缓?,這是它的一個(gè)不足之處。設斷點(diǎn)查看讀取的pth文件里的內容,可以看到ultralytics的pt文件里既存儲有模型參數,也存儲有網(wǎng)絡(luò )結構,還儲存了一些超參數,包括anchors,stride等等。

self.register_buffer('anchors', a)  #shape(nl,na,2)
self.register_buffer('anchor_grid', a.clone().view(self.nl, 1, -1, 1, 1, 2))

嘗試過(guò)把這兩行代碼改成:

self.anchors = a
self.anchor_grid = a.clone().view(self.nl, 1, -1, 1, 1, 2)

程序依然能正常運行,但是torch.save保存模型文件后,可以看到pth文件里沒(méi)有存儲anchors和anchor_grid了,在百度搜索register_buffer,解釋是:pytorch中register_buffer模型保存和加載的時(shí)候可以寫(xiě)入和讀出。在這兩行代碼的下一行:

self.m = nn.ModuleList(nn.Conv2d(x, self.no * self.na, 1) for x in ch)   # output conv

它的作用是做特征圖的輸出通道對齊,通過(guò)1x1卷積把三種尺度特征圖的輸出通道都調整到num_anchors*(num_classes+5)。閱讀Detect類(lèi)的forward函數代碼,可以看出它的作用是根據偏移公式計算出預測框的中心坐標和高寬,這里需要注意的是,計算高和寬的代碼:

pwh = (ps[:, 2:4].sigmoid() * 2) ** 2 * anchors[i]

沒(méi)有采用exp操作,而是直接乘上anchors[i],這是yolov5與yolov3v4的一個(gè)最大區別(還有一個(gè)區別就是在訓練階段的loss函數里,yolov5采用鄰域的正樣本anchor匹配策略,增加了正樣本。其它的是一些小區別,比如yolov5的第一個(gè)模塊采用FOCUS把輸入數據2倍下采樣切分成4份,在channel維度進(jìn)行拼接,然后進(jìn)行卷積操作,yolov5的激活函數沒(méi)有使用Mish)。

現在可以明白Detect類(lèi)的作用是計算預測框的中心坐標和高寬,簡(jiǎn)單來(lái)說(shuō)就是生成proposal,作為后續NMS的輸入,進(jìn)而輸出最終的檢測框。我覺(jué)得在Detect類(lèi)里定義的1x1卷積是不恰當的,應該把它定義在Detect類(lèi)的外面,緊鄰著(zhù)Detect類(lèi)之前定義1x1卷積。

在官方代碼里,有轉換到onnx文件的程序:

python models/export.py --weights yolov5s.pt --img 640 --batch 1

在pytorch1.7版本里,程序是能正常運行生成onnx文件的。觀(guān)察export.py里的代碼,在執行torch.onnx.export之前,有這么一段代碼:

# Input
img = torch.zeros(opt.batch_size, 3, *opt.img_size)   # image size (1, 3, 320, 192) iDetection
# Update model
for k, m in model.named_modules():
    m._non_persistent_buffers_set = set()    # pytorch 1.6.0 compatibility
    if isinstance(m, models.common.Conv):    # assign export-friendly activations
        if isinstance(m.act, nn.Hardswish):
            m.act = Hardswish()
        elif isinstance(m.act, nn.SiLU):
            m.act = SiLU()
    # elif isinstance(m, models.yolo.Detect):
    #     m.forward = m.forward_export     #assign  forward (optional)
model.model[-1].export = True   # set Detect() Layer export = True
y = model(img)   # dry run

注意其中的for循環(huán),我試驗過(guò)注釋掉它,重新運行就會(huì )出錯,打印出的錯誤如下:

4.png

由此可見(jiàn),這段for循環(huán)代碼是必需的。SiLU其實(shí)就是swish激活函數,而在onnx模型里是不直接支持swish算子的,因此在轉換生成onnx文件時(shí),SiLU激活函數不能直接使用nn.Module里提供的接口,而需要自定義實(shí)現它。

2)、opencv的dnn模塊讀取.onnx文件做前向計算

在生成onnx文件后,就可以用opencv的dnn模塊里的cv2.dnn.readNet讀取它。然而,在讀取時(shí),出現了如下錯誤:

其實(shí)是:

5.png

于是查看yolov5的代碼,在common.py文件的Focus類(lèi),torch.cat的輸入里有4次切片操作,代碼如下:

6.png

那么現在需要更換索引式的切片操作,觀(guān)察到注釋的Contract類(lèi),它就是用view和permute函數完成切片操作的,于是修改代碼如下:

7.png

其次,在models\yolo.py里的Detect類(lèi)里,也有切片操作,代碼如下:

y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i].to(x[i].device)) * self.stride[i]
y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i]   # wh

前面說(shuō)過(guò),Detect類(lèi)的作用是計算預測框的中心坐標和高寬,生成proposal,這個(gè)是屬于后處理的,因此不需要把它寫(xiě)入到onnx文件里。

總結一下,按照上面的截圖代碼,修改Focus類(lèi),把Detect類(lèi)里面的1x1卷積定義在緊鄰著(zhù)Detect類(lèi)之前的外面,然后去掉Detect類(lèi),組成新的model,作為torch.onnx.export的輸入:

torch.onnx.export(model, inputs, output_onnx, verbose=False, opset_version=12, input_names=['images'], output_names=['out0', 'out1', 'out2'])

最后生成的onnx文件,opencv的dnn模塊就能成功讀取了,接下來(lái)對照Detect類(lèi)里的forward函數,用python或者C++編寫(xiě)計算預測框的中心坐標和高寬的功能。

在github上,地址是https://github.com/hpc203/yolov5-dnn-cpp-python

四、后處理模塊

后處理模塊,python版本用numpy array實(shí)現的,C++版本的用vector和數組實(shí)現的,整套程序只依賴(lài)opencv庫(opencv4版本以上的)就能正常運行,徹底擺脫對深度學(xué)習框架pytorch,tensorflow,caffe,mxnet等等的依賴(lài)。用openvino作目標檢測,需要把onnx文件轉換到.bin和.xml文件,相比于用dnn模塊加載onnx文件做目標檢測是多了一個(gè)步驟的。因此,我就想編寫(xiě)一套用opencv的dnn模塊做yolov5目標檢測的程序,用opencv的dnn模塊做深度學(xué)習目標檢測,在win10和ubuntu,在cpu和gpu上都能運行,可見(jiàn)dnn模塊的通用性更好,很接地氣。

生成yolov5s_param.pth 的步驟:

首先下載https://github.com/ultralytics/yolov5的源碼到本地,在yolov5-master主目錄(注意不是我發(fā)布的github代碼目錄)里新建一個(gè).py文件,把下面的代碼復制到.py文件里。

import torch
from collections import OrderedDict
import pickle
import os
device = 'cuda' if torch.cuda.is_available() else 'cpu'
if __name__=='__main__':
    choices = ['yolov5s', 'yolov5l', 'yolov5m', 'yolov5x']
    modelfile = choices[0]+'.pt'
    utl_model = torch.load(modelfile, map_location=device)
    utl_param = utl_model['model'].model
    torch.save(utl_param.state_dict(), os.path.splitext(modelfile)[0]+'_param.pth')
    own_state = utl_param.state_dict()
    print(len(own_state))
    numpy_param = OrderedDict()
    for name in own_state:
        numpy_param[name] = own_state[name].data.cpu().numpy()
    print(len(numpy_param))
    with open(os.path.splitext(modelfile)[0]+'_numpy_param.pkl', 'wb') as fw:
        pickle.dump(numpy_param, fw)

運行這個(gè).py文件,這時(shí)候就可以生成yolov5s_param.pth文件。之所以要進(jìn)行這一步,我在上面講到過(guò):ultralytics的.pt文件里既存儲有模型參數,也存儲有網(wǎng)絡(luò )結構,還儲存了一些超參數,包括anchors,stride等等的。torch.load加載ultralytics的官方.pt文件,也就是utl_model = torch.load(modelfile, map_location=device)這行代碼,在這行代碼后設斷點(diǎn)查看utl_model里的內容,截圖如下:

8.png

可以看到utl_model里含有既存儲有模型參數,也存儲有網(wǎng)絡(luò )結構,還儲存了一些超參數等等的,這會(huì )嚴重影響轉onnx文件。此外,我還發(fā)現,如果pytorch的版本低于1.7,那么在torch.load加載.pt文件時(shí)就會(huì )出錯的。

因此在程序里,我把模型參數轉換到cpu.numpy形式的,最后保存在.pkl文件里。這時(shí)候在win10系統cpu環(huán)境里,即使你的電腦沒(méi)有安裝pytorch,也能通過(guò)python程序訪(fǎng)問(wèn)到模型參數。

五、pytorch轉onnx常見(jiàn)坑:

onnx只能輸出靜態(tài)圖,因此不支持if-else分支。一次只能走一個(gè)分支。如果代碼中有if-else語(yǔ)句,需要改寫(xiě)。

onnx不支持步長(cháng)為2的切片。例如a[::2,::2]

onnx不支持對切片對象賦值。例如a[0,:,:,:]=b, 可以用torch.cat改寫(xiě)

onnx里面的resize要求output shape必須為常量??梢杂靡韵麓a解決:

if isinstance(size, torch.Size):
    size = tuple(int(x) for x in size)

此外,在torch.onnx.export(model, inputs, output_onnx)的輸入參數model里,應該只包含網(wǎng)絡(luò )結構,也就是說(shuō)model里只含有nn.Conv2d, nn.MaxPool2d, nn.BatchNorm2d, F.relu等等的這些算子組件,而不應該含有后處理模塊的。圖像預處理和后處理模塊需要自己使用C++或者Python編程實(shí)現。

在明白了這些之后,在轉換生成onnx文件,你需要執行兩個(gè)步驟,第一步把原始訓練模型.pt文件里的參數保存到新的.pth文件里,第二步編寫(xiě)yolov5.py文件,把yolov5的往來(lái)結構定義在.py文件里,此時(shí)需要注意網(wǎng)絡(luò )結構里不能包含切片對象賦值操作,F.interpolate里的size參數需要加int強制轉換。在執行完這兩步之后才能生成一個(gè)opencv能成功讀取并且做前向推理的onnx文件。

不過(guò),最近我發(fā)現在yolov5-pytorch程序里,其實(shí)可以直接把原始訓練模型.pt文件轉換生成onnx文件的,而且我在一個(gè)yolov5檢測人臉+關(guān)鍵點(diǎn)的程序里實(shí)驗成功了。

*博客內容為網(wǎng)友個(gè)人發(fā)布,僅代表博主個(gè)人觀(guān)點(diǎn),如有侵權請聯(lián)系工作人員刪除。



關(guān)鍵詞: 深度學(xué)習

相關(guān)推薦

技術(shù)專(zhuān)區

關(guān)閉
国产精品自在自线亚洲|国产精品无圣光一区二区|国产日产欧洲无码视频|久久久一本精品99久久K精品66|欧美人与动牲交片免费播放
<dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><small id="yhprb"></small><dfn id="yhprb"></dfn><small id="yhprb"><delect id="yhprb"></delect></small><small id="yhprb"></small><small id="yhprb"></small> <delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"></dfn><dfn id="yhprb"></dfn><s id="yhprb"><noframes id="yhprb"><small id="yhprb"><dfn id="yhprb"></dfn></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><small id="yhprb"></small><dfn id="yhprb"><delect id="yhprb"></delect></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn> <small id="yhprb"></small><delect id="yhprb"><strike id="yhprb"></strike></delect><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn><dfn id="yhprb"><s id="yhprb"><strike id="yhprb"></strike></s></dfn><dfn id="yhprb"><s id="yhprb"></s></dfn>