1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        深度學(xué)習(xí)應(yīng)用的服務(wù)端部署

        共 9402字,需瀏覽 19分鐘

         ·

        2020-10-03 01:01



        【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)開(kāi)發(fā)這個(gè)界面。

        上傳頁(yè)面和展示頁(yè)面

        做識(shí)別演示需要用到兩個(gè)html頁(yè)面,代碼也比較簡(jiǎn)單,編寫如下:

        上傳界面


        "en">

        ????"UTF-8">
        ????Flask上傳圖片演示


        ????

        使用Flask上傳本地圖片


        ????""?enctype='multipart/form-data'?method='POST'>
        ????????type="file"?name="file"?style="margin-top:20px;"/>
        ????????

        ????????type="submit"?value="上傳"?class="button-new"?style="margin-top:15px;"/>
        ????


        展示界面


        "en">

        ????"UTF-8">
        ????Flask上傳圖片演示


        ????

        使用Flask上傳本地圖片


        ????""?enctype='multipart/form-data'?method='POST'>
        ????????type="file"?name="file"?style="margin-top:20px;"/>
        ????????

        ????????type="submit"?value="上傳"?class="button-new"?style="margin-top:15px;"/>
        ????
        ????"{{?url_for('static',?filename=?path,_t=val1)?}}"?width="400"?height="400"?alt="圖片識(shí)別失敗"/>


        上傳界面如下圖所示,覺(jué)得丑的話可以找前端同事美化一下:

        flask上傳圖片及展示功能

        然后就可以編寫flask代碼了,為了更好地展示圖片,可以向html頁(yè)面?zhèn)魅雸D片地址參數(shù)。

        from?flask?import?Flask,?render_template,?request,?redirect,?url_for,?make_response,?jsonify
        from?werkzeug.utils?import?secure_filename
        import?os
        import?cv2
        import?time
        from?datetime?import?timedelta
        from?main?import?run,?conf
        ALLOWED_EXTENSIONS?=?set([
        ????"png","jpg","JPG","PNG",?"bmp"
        ])

        def?is_allowed_file(filename):
        ????return?'.'?in?filename?and?filename.rsplit('.',?1)[1]?in?ALLOWED_EXTENSIONS

        app?=?Flask(__name__)

        #?靜態(tài)文件緩存過(guò)期時(shí)間
        app.send_file_max_age_default?=?timedelta(seconds=1)

        @app.route("/upload",methods?=?['POST',?'GET'])
        def?upload():
        ????if?request.method?==?"POST":
        ????????f?=?request.files['file']
        ????????if?not?(?f?and?is_allowed_file(f.filename)):
        ????????????return?jsonify({
        ????????????????"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)

        ????????#?return?render_template("upload_ok.html",?userinput?=?user_input,?val1=time.time(),?path?=?detected_path)
        ????????path?=?"/images/"?+?"output"?+?secure_filename(f.filename)
        ????????return?render_template("upload_ok.html",?path?=?path,?val1?=?time.time())
        ????return?render_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__?import?division

        from?models?import?*
        from?utils.utils?import?*
        from?utils.datasets?import?*

        import?os
        import?sys
        import?time
        import?datetime
        import?argparse

        from?PIL?import?Image

        import?torch
        from?torchvision?import?datasets
        from?torch.autograd?import?Variable

        import?matplotlib.pyplot?as?plt
        import?matplotlib.patches?as?patches
        from?matplotlib.ticker?import?NullLocator

        class?custom_dict(dict):
        ????def?__init__(self,?d?=?None):
        ????????if?d?is?not?None:
        ????????????for?k,v?in?d.items():
        ????????????????self[k]?=?v
        ????????return?super().__init__()

        ????def?__key(self,?key):
        ????????return?""?if?key?is?None?else?key.lower()

        ????def?__str__(self):
        ????????import?json
        ????????return?json.dumps(self)

        ????def?__setattr__(self,?key,?value):
        ????????self[self.__key(key)]?=?value

        ????def?__getattr__(self,?key):
        ????????return?self.get(self.__key(key))

        ????def?__getitem__(self,?key):
        ????????return?super().get(self.__key(key))

        ????def?__setitem__(self,?key,?value):
        ????????return?super().__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
        })

        def?run(img_path,?conf,?target_path):
        ????device?=?torch.device("cuda"?if?torch.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)

        ????if?conf.weights_path.endswith(".weights"):
        ????????#?Load?darknet?weights
        ????????model.load_darknet_weights(conf.weights_path)
        ????else:
        ????????#?Load?checkpoint?weights
        ????????model.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]
        ????
        ????with?torch.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)?for?i?in?np.linspace(0,?1,?20)]
        ????plt.figure()
        ????fig,?ax?=?plt.subplots(1)
        ????ax.imshow(img_array)
        ????if?detections?is?not?None:
        ????????#?Rescale?boxes?to?original?image
        ????????detections?=?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)
        ????????for?x1,?y1,?x2,?y2,?conf,?cls_conf,?cls_pred?in?detections:

        ????????????print("\t+?Label:?%s,?Conf:?%.5f"?%?(classes[int(cls_pred)],?cls_conf.item()))

        ????????????box_w?=?x2?-?x1
        ????????????box_h?=?y2?-?y1

        ????????????color?=?bbox_colors[int(np.where(unique_labels?==?int(cls_pred))[0])]
        ????????????#?Create?a?Rectangle?patch
        ????????????bbox?=?patches.Rectangle((x1,?y1),?box_w,?box_h,?linewidth=2,?edgecolor=color,?facecolor="none")
        ????????????#?Add?the?bbox?to?the?plot
        ????????????ax.add_patch(bbox)
        ????????????#?Add?label
        ????????????plt.text(
        ????????????????x1,
        ????????????????y1,
        ????????????????s=classes[int(cls_pred)],
        ????????????????color="white",
        ????????????????verticalalignment="top",
        ????????????????bbox={"color":?color,?"pad":?0},
        ????????????)

        ????#?Save?generated?image?with?detections
        ????plt.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,在本地打開(kāi)localhost:8888/upload就可以看到如下界面了,把圖片上傳上去,很快就能得到檢測(cè)結(jié)果。

        結(jié)果如下圖所示:

        想試用的同學(xué)可以點(diǎn)擊:http://106.13.201.241:8888/upload

        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í)候與開(kāi)發(fā)的同事一起進(jìn)行調(diào)試。

        flask服務(wù)接口

        接口不需要有界面顯示,當(dāng)然也可以添加一個(gè)API介紹界面,方便調(diào)用者查看服務(wù)是否已經(jīng)啟動(dòng)。

        from?flask?import?Flask,?request
        from?werkzeug.utils?import?secure_filename
        import?uuid
        from?PIL?import?Image
        import?os
        import?time
        import?base64
        import?json

        import?torch
        from?torchvision.models?import?resnet18
        from?torchvision.transforms?import?ToTensor

        from?keys?import?key

        app?=?Flask(__name__)
        net?=?resnet18(pretrained=True)
        net.eval()

        @app.route("/",methods=["GET"])
        def?show():
        ????return?"classifier?api"

        @app.route("/run",methods?=?["GET","POST"])
        def?run():
        ????file?=?request.files['file']
        ????base_path?=?os.path.dirname(__file__)
        ????if?not?os.path.exists(os.path.join(base_path,?"temp")):
        ????????os.makedirs(os.path.join(base_path,?"temp"))
        ????file_name?=?uuid.uuid4().hex
        ????upload_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(host="0.0.0.0",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?=?10
        workers?=?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/chiller/dec373004894e9c9bb38ac647c7ccfa8

        嚴(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

        有了gunicorn和nginx就可以輕松地實(shí)現(xiàn)PyTorch模型的多機(jī)多卡部署了。


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

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


        瀏覽 43
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

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

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91精品久 | 美女无遮挡网站 | 草婊子性爱网 | 免费的性生活视频 | 日本r级和子同居的日子观看 | 欧美性爱手机在线 | 在线播放A片 | 国产永久免费视频 | 久久久久久亚洲成人电影 | 中文字幕国内自拍 |