文章轉(zhuǎn)載于微信公眾號(hào):GiantPandaCV
作者: 阿呆
【GiantPandaCV導(dǎo)讀】這篇文章包含與PyTorch模型部署相關(guān)的兩部分內(nèi)容:
- PyTorch-YOLOv3模型的Web頁(yè)面展示程序的編寫
- 模型的服務(wù)接口相關(guān)工具的使用
0. 環(huán)境依賴:
系統(tǒng):Ubuntu 18.04
Python版本:3.7
依賴Python包:1. PyTorch==1.3 2. Flask==0.12 3. Gunicorn
需要注意的是Flask 0.12中默認(rèn)的單進(jìn)程單線程,而最新的1.0.2則不是(具體是多線程還是多進(jìn)程尚待考證),而中文博客里面能查到的資料基本都在說(shuō)Flask默認(rèn)單進(jìn)程單線程。
依賴工具 1. nginx 2. apache2-utils
nginx 用于代理轉(zhuǎn)發(fā)和負(fù)載均衡,apache2-utils用于測(cè)試接口
1. 制作模型演示界面
圖像識(shí)別任務(wù)的展示這項(xiàng)工程一般是面向客戶的,這種場(chǎng)景下不可能把客戶拉到你的電腦前面,敲一行命令,等matplotlib彈個(gè)結(jié)果窗口出來(lái)。總歸還是要有個(gè)圖形化界面才顯得有點(diǎn)誠(chéng)意。
為了節(jié)約時(shí)間,我們選擇了Flask框架來(lái)開發(fā)這個(gè)界面。
上傳頁(yè)面和展示頁(yè)面
做識(shí)別演示需要用到兩個(gè)html頁(yè)面,代碼也比較簡(jiǎn)單,編寫如下:
上傳界面
使用Flask上傳本地圖片
展示界面
使用Flask上傳本地圖片
%C2%A0%7D%7D)
上傳界面如下圖所示,覺得丑的話可以找前端同事美化一下:
flask上傳圖片及展示功能
然后就可以編寫flask代碼了,為了更好地展示圖片,可以向html頁(yè)面?zhèn)魅雸D片地址參數(shù)。
fromflaskimportFlask,render_template,request,redirect,url_for,make_response,jsonifyfromwerkzeug.utilsimportsecure_filenameimportosimportcv2importtimefromdatetimeimporttimedeltafrommainimportrun,confALLOWED_EXTENSIONS=set(["png","jpg","JPG","PNG","bmp"])defis_allowed_file(filename):return'.'infilenameandfilename.rsplit('.',1)[1]inALLOWED_EXTENSIONSapp=Flask(__name__)#靜態(tài)文件緩存過(guò)期時(shí)間app.send_file_max_age_default=timedelta(seconds=1)@app.route("/upload",methods=['POST','GET'])defupload():ifrequest.method=="POST":f=request.files['file']ifnot(fandis_allowed_file(f.filename)):returnjsonify({"error":1001,"msg":"請(qǐng)檢查上傳的圖片類型,僅限于png、PNG、jpg、JPG、bmp"})user_input=request.form.get("name")basepath=os.path.dirname(__file__)upload_path=os.path.join(basepath,"static/images",secure_filename(f.filename))f.save(upload_path)detected_path=os.path.join(basepath,"static/images","output"+secure_filename(f.filename))run(upload_path,conf,detected_path)#returnrender_template("upload_ok.html",userinput=user_input,val1=time.time(),path=detected_path)path="/images/"+"output"+secure_filename(f.filename)returnrender_template("upload_ok.html",path=path,val1=time.time())returnrender_template("upload.html")if__name__=="__main__":app.run(host='0.0.0.0',port=8888,debug=True)
目標(biāo)檢測(cè)函數(shù)
原項(xiàng)目中提供了detection.py來(lái)做批量的圖片檢測(cè),需要稍微修改一下才能用來(lái)做flask代碼中的接口。
from__future__importdivisionfrommodelsimport*fromutils.utilsimport*fromutils.datasetsimport*importosimportsysimporttimeimportdatetimeimportargparsefromPILimportImageimporttorchfromtorchvisionimportdatasetsfromtorch.autogradimportVariableimportmatplotlib.pyplotaspltimportmatplotlib.patchesaspatchesfrommatplotlib.tickerimportNullLocatorclasscustom_dict(dict):def__init__(self,d=None):ifdisnotNone:fork,vind.items():self[k]=vreturnsuper().__init__()def__key(self,key):return""ifkeyisNoneelsekey.lower()def__str__(self):importjsonreturnjson.dumps(self)def__setattr__(self,key,value):self[self.__key(key)]=valuedef__getattr__(self,key):returnself.get(self.__key(key))def__getitem__(self,key):returnsuper().get(self.__key(key))def__setitem__(self,key,value):returnsuper().__setitem__(self.__key(key),value)conf=custom_dict({"model_def":"config/yolov3.cfg","weights_path":"weights/yolov3.weights","class_path":"data/coco.names","conf_thres":0.8,"nms_thres":0.4,"img_size":416})defrun(img_path,conf,target_path):device=torch.device("cuda"iftorch.cuda.is_available()else"cpu")os.makedirs("output",exist_ok=True)classes=load_classes(conf.class_path)model=Darknet(conf.model_def,img_size=conf.img_size).to(device)ifconf.weights_path.endswith(".weights"):#Loaddarknetweightsmodel.load_darknet_weights(conf.weights_path)else:#Loadcheckpointweightsmodel.load_state_dict(torch.load(conf.weights_path))model.eval()img=Image.open(img_path).convert("RGB")img=img.resize(((img.size[0]//32)*32,(img.size[1]//32)*32))img_array=np.array(img)img_tensor=pad_to_square(transforms.ToTensor()(img),0)[0].unsqueeze(0)conf.img_size=img_tensor.shape[2]withtorch.no_grad():detections=model(img_tensor)detections=non_max_suppression(detections,conf.conf_thres,conf.nms_thres)[0]cmap=plt.get_cmap("tab20b")colors=[cmap(i)foriinnp.linspace(0,1,20)]plt.figure()fig,ax=plt.subplots(1)ax.imshow(img_array)ifdetectionsisnotNone:#Rescaleboxestooriginalimagedetections=rescale_boxes(detections,conf.img_size,img_array.shape[:2])unique_labels=detections[:,-1].cpu().unique()n_cls_preds=len(unique_labels)bbox_colors=random.sample(colors,n_cls_preds)forx1,y1,x2,y2,conf,cls_conf,cls_predindetections:print("/t+Label:%s,Conf:%.5f"%(classes[int(cls_pred)],cls_conf.item()))box_w=x2-x1box_h=y2-y1color=bbox_colors[int(np.where(unique_labels==int(cls_pred))[0])]#CreateaRectanglepatchbbox=patches.Rectangle((x1,y1),box_w,box_h,linewidth=2,edgecolor=color,facecolo)#Addthebboxtotheplotax.add_patch(bbox)#Addlabelplt.text(x1,y1,s=classes[int(cls_pred)],colo,verticalalignmen,bbox={"color":color,"pad":0},)#Savegeneratedimagewithdetectionsplt.axis("off")plt.gca().xaxis.set_major_locator(NullLocator())plt.gca().yaxis.set_major_locator(NullLocator())filename=img_path.split("/")[-1].split(".")[0]plt.savefig(target_path,bbox_inches='tight',pad_inches=0.0)plt.close()if__name__=="__main__":run("data/samples/dog.jpg",conf)
展示效果
編寫好了之后,啟動(dòng)server.py,在本地打開localhost:8888/upload就可以看到如下界面了,把圖片上傳上去,很快就能得到檢測(cè)結(jié)果。
結(jié)果如下圖所示:
2. 深度學(xué)習(xí)的服務(wù)接口編寫
接下來(lái)介紹的是在生產(chǎn)環(huán)境下的部署,使用的是flask+gunicorn+nginx的方式,可以處理較大規(guī)模的請(qǐng)求。
下面以圖像分類模型為例演示一下深度學(xué)習(xí)服務(wù)接口如何編寫。
對(duì)于深度學(xué)習(xí)工程師來(lái)說(shuō),學(xué)習(xí)這些內(nèi)容主要是了解一下自己的模型在生產(chǎn)環(huán)境的運(yùn)行方式,便于在服務(wù)出現(xiàn)問(wèn)題的時(shí)候與開發(fā)的同事一起進(jìn)行調(diào)試。
flask服務(wù)接口
接口不需要有界面顯示,當(dāng)然也可以添加一個(gè)API介紹界面,方便調(diào)用者查看服務(wù)是否已經(jīng)啟動(dòng)。
fromflaskimportFlask,requestfromwerkzeug.utilsimportsecure_filenameimportuuidfromPILimportImageimportosimporttimeimportbase64importjsonimporttorchfromtorchvision.modelsimportresnet18fromtorchvision.transformsimportToTensorfromkeysimportkeyapp=Flask(__name__)net=resnet18(pretrained=True)net.eval()@app.route("/",methods=["GET"])defshow():return"classifierapi"@app.route("/run",methods=["GET","POST"])defrun():file=request.files['file']base_path=os.path.dirname(__file__)ifnotos.path.exists(os.path.join(base_path,"temp")):os.makedirs(os.path.join(base_path,"temp"))file_name=uuid.uuid4().hexupload_path=os.path.join(base_path,"temp",file_name)file.save(upload_path)img=Image.open(upload_path)img_tensor=ToTensor()(img).unsqueeze(0)out=net(img_tensor)pred=torch.argmax(out,dim=1)return"result:{}".format(key[pred])if__name__=="__main__":app.run(hos,port=5555,debug=True)
在命令行輸入python server.py
即可啟動(dòng)服務(wù)。
gunicorn啟動(dòng)多個(gè)實(shí)例
新版的flask已經(jīng)支持多進(jìn)程了,不過(guò)用在生產(chǎn)環(huán)境還是不太穩(wěn)定,一般生產(chǎn)環(huán)境會(huì)使用gunicorn來(lái)啟動(dòng)多個(gè)服務(wù)。
使用如下命令即可啟動(dòng)多個(gè)圖像分類實(shí)例
gunicorn -w 4 -b 0.0.0.0:5555 server:app
輸出如下內(nèi)容代表服務(wù)創(chuàng)建成功:
[2020-02-11 14:50:24 +0800] [892] [INFO] Starting gunicorn 20.0.4[2020-02-11 14:50:24 +0800] [892] [INFO] Listening at: http://0.0.0.0:5555 (892)[2020-02-11 14:50:24 +0800] [892] [INFO] Using worker: sync[2020-02-11 14:50:24 +0800] [895] [INFO] Booting worker with pid: 895[2020-02-11 14:50:24 +0800] [896] [INFO] Booting worker with pid: 896[2020-02-11 14:50:24 +0800] [898] [INFO] Booting worker with pid: 898[2020-02-11 14:50:24 +0800] [899] [INFO] Booting worker with pid: 899
如果配置比較復(fù)雜,也可以將配置寫入一個(gè)文件中,如:
bind='0.0.0.0:5555'timeout=10workers=4
然后運(yùn)行:
gunicorn -c gunicorn.conf sim_server:app
nginx負(fù)載均衡
如果有多個(gè)服務(wù)器,可以使用nginx做請(qǐng)求分發(fā)與負(fù)載均衡。
安裝好nginx之后,修改nginx的配置文件
worker_processes auto;error_log /var/log/nginx/error.log;pid /run/nginx.pid;# Load dynamic modules. See /usr/share/nginx/README.dynamic.include /usr/share/nginx/modules/*.conf;events { worker_connections 1024;}http { server { listen 5556; # nginx端口 server_name localhost; location / { proxy_pass http://localhost:5555/run; # gunicorn的url } }}
然后按配置文件啟動(dòng)
sudo nginx -c nginx.conf
測(cè)試一下服務(wù)是否正常
啟動(dòng)了這么多服務(wù)之后,可以使用apache2-utils來(lái)測(cè)試服務(wù)的并發(fā)性能。
使用apache2-utils進(jìn)行上傳圖片的post請(qǐng)求方法參考:
https://gist.github.com/chill...
嚴(yán)格參照,注意一個(gè)標(biāo)點(diǎn),一個(gè)符號(hào)都不要錯(cuò)。使用這種方法傳輸圖片的base64編碼,在服務(wù)端不需要解碼也能使用
然后使用下面的方式訪問(wèn)
gunicorn 接口
ab -n 2 -c 2 -T "multipart/form-data; boundary=1234567890" -p turtle.txt http://localhost:5555/run
nginx 接口
ab -n 2 -c 2 -T "multipart/form-data; boundary=1234567890" -p turtle.txt http://localhost:5556/run
- END -
推薦閱讀
更多嵌入式AI技術(shù)干貨請(qǐng)關(guān)注嵌入式AI專欄。
審核編輯:符乾江
-
深度學(xué)習(xí)
+關(guān)注
關(guān)注
73文章
5560瀏覽量
122750 -
pytorch
+關(guān)注
關(guān)注
2文章
809瀏覽量
13919
發(fā)布評(píng)論請(qǐng)先 登錄
如何使用Docker部署大模型
如何在RAKsmart服務(wù)器上實(shí)現(xiàn)企業(yè)AI模型部署
K230D部署模型失敗的原因?
【ELF 2學(xué)習(xí)板試用】ELF2開發(fā)板(飛凌嵌入式)搭建深度學(xué)習(xí)環(huán)境部署(RKNN環(huán)境部署)
Flexus X 實(shí)例 ultralytics 模型 yolov10 深度學(xué)習(xí) AI 部署與應(yīng)用

AI模型部署邊緣設(shè)備的奇妙之旅:目標(biāo)檢測(cè)模型
AI模型部署邊緣設(shè)備的奇妙之旅:如何實(shí)現(xiàn)手寫數(shù)字識(shí)別
使用OpenVINO C++在哪吒開發(fā)板上推理Transformer模型

新手小白怎么通過(guò)云服務(wù)器跑pytorch?
基于Pytorch訓(xùn)練并部署ONNX模型在TDA4應(yīng)用筆記

PyTorch深度學(xué)習(xí)開發(fā)環(huán)境搭建指南
深度學(xué)習(xí)模型量化方法

評(píng)論