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

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

          共 9402字,需瀏覽 19分鐘

           ·

          2020-10-03 01:01



          【GiantPandaCV導(dǎo)讀】這篇文章包含與PyTorch模型部署相關(guān)的兩部分內(nèi)容:

          • PyTorch-YOLOv3模型的Web頁面展示程序的編寫

          • 模型的服務(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)程尚待考證),而中文博客里面能查到的資料基本都在說Flask默認(rèn)單進(jìn)程單線程。

          依賴工具 1. nginx 2. apache2-utils

          nginx 用于代理轉(zhuǎn)發(fā)和負(fù)載均衡,apache2-utils用于測試接口


          1. 制作模型演示界面

          圖像識(shí)別任務(wù)的展示這項(xiàng)工程一般是面向客戶的,這種場景下不可能把客戶拉到你的電腦前面,敲一行命令,等matplotlib彈個(gè)結(jié)果窗口出來。總歸還是要有個(gè)圖形化界面才顯得有點(diǎn)誠意。

          為了節(jié)約時(shí)間,我們選擇了Flask框架來開發(fā)這個(gè)界面。

          上傳頁面和展示頁面

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

          上傳界面


          "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í)別失敗"/>


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

          flask上傳圖片及展示功能

          然后就可以編寫flask代碼了,為了更好地展示圖片,可以向html頁面?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)文件緩存過期時(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":"請檢查上傳的圖片類型,僅限于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)檢測函數(shù)

          原項(xiàng)目中提供了detection.py來做批量的圖片檢測,需要稍微修改一下才能用來做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,在本地打開localhost:8888/upload就可以看到如下界面了,把圖片上傳上去,很快就能得到檢測結(jié)果。

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

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

          2. 深度學(xué)習(xí)的服務(wù)接口編寫

          接下來介紹的是在生產(chǎn)環(huán)境下的部署,使用的是flask+gunicorn+nginx的方式,可以處理較大規(guī)模的請求。

          下面以圖像分類模型為例演示一下深度學(xué)習(xí)服務(wù)接口如何編寫。

          對(duì)于深度學(xué)習(xí)工程師來說,學(xué)習(xí)這些內(nèi)容主要是了解一下自己的模型在生產(chǎn)環(huán)境的運(yùn)行方式,便于在服務(wù)出現(xiàn)問題的時(shí)候與開發(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)程了,不過用在生產(chǎn)環(huán)境還是不太穩(wěn)定,一般生產(chǎn)環(huán)境會(huì)使用gunicorn來啟動(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做請求分發(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

          測試一下服務(wù)是否正常

          啟動(dòng)了這么多服務(wù)之后,可以使用apache2-utils來測試服務(wù)的并發(fā)性能。

          使用apache2-utils進(jìn)行上傳圖片的post請求方法參考:

          https://gist.github.com/chiller/dec373004894e9c9bb38ac647c7ccfa8

          嚴(yán)格參照,注意一個(gè)標(biāo)點(diǎn),一個(gè)符號(hào)都不要錯(cuò)。使用這種方法傳輸圖片的base64編碼,在服務(wù)端不需要解碼也能使用

          然后使用下面的方式訪問

          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)的問題,或者想要加入交流群,歡迎添加BBuf微信:


          瀏覽 43
          點(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>
                  污视频在线免费观看一区 | 激情久久九九 | 骚妇网| 色色色欧美| 少妇白洁在线观看 |