從零開始:TensorFlow機器學習模型快速部署指南

文章選自Hive Blog,做者:Bowei,機器之心編譯node

本文將介紹一種將訓練後的機器學習模型快速部署到生產種的方式。若是你已使用 TensorFlow 或 Caffe 等深度學習框架訓練好了 ML 模型,該模型能夠做爲 demo。若是你更喜歡輕量級的解決方案,請閱讀本文。python

GitHub 地址:https://github.com/hiveml/simple-ml-servingnginx

其中包含的條目有:git

  • 檢查 TensorFlow 安裝:https://github.com/hiveml/simple-ml-serving/blob/master/test/test_tensorflow.shgithub

  • 利用 stdin 運行在線分類:https://github.com/hiveml/simple-ml-serving/blob/master/test/test_label_image.shweb

  • 在本地主機上運行在線分類:https://github.com/hiveml/simple-ml-serving/blob/master/test/test_tf_classify_server.shdocker

  • 將分類器放在硬編碼代理器後面:https://github.com/hiveml/simple-ml-serving/blob/master/test/test_basic_proxy.shshell

  • 將分類器放在可實現服務發現的代理器後面:https://github.com/hiveml/simple-ml-serving/blob/master/test/test_seaport_proxy.shjson

  • 利用僞 DN 啓用分類器:https://github.com/hiveml/simple-ml-serving/blob/master/test/test_p2p_proxy.shflask


生產環境中的機器學習

第一次進入 Hive 的機器學習空間,咱們就已經擁有數百萬個真值標註圖像,這可讓咱們在一週時間內從頭訓練(即隨機權重)適用於特定使用案例的頂尖深度卷積圖像分類模型。更典型的 ML 用例一般基於數百個圖像,這種狀況我推薦你們對現有模型進行微調。例如,https://www.tensorflow.org/tutorials/image_retraining 頁面上有如何微調 ImageNet 模型對花樣本數據集(3647 張圖像,5 個類別)進行分類的教程。

安裝 Bazel 和 TensorFlow 後,你須要運行如下代碼,構建大約須要 30 分鐘,訓練須要 5 分鐘:

(
cd "$HOME" && \
curl -O http://download.tensorflow.org/example_images/flower_photos.tgz && \
tar xzf flower_photos.tgz ;
) && \
bazel build tensorflow/examples/image_retraining:retrain \
          tensorflow/examples/image_retraining:label_image \
&& \
bazel-bin/tensorflow/examples/image_retraining/retrain \
    --image_dir "$HOME"/flower_photos \
    --how_many_training_steps=200
&& \
bazel-bin/tensorflow/examples/image_retraining/label_image \
    --graph=/tmp/output_graph.pb \
    --labels=/tmp/output_labels.txt \
    --output_layer=final_result:0 \
    --image=$HOME/flower_photos/daisy/21652746_cc379e0eea_m.jpg

複製代碼

或者,若是你有 Docker,可使用預製 Docker 圖像,

sudo docker run -it --net=host liubowei/simple-ml-serving:latest /bin/bash
>>> cat test.sh && bash test.sh複製代碼

進入容器中的交互式 shell,運行以上命令。你也能夠閱讀下文,在容器中按照下文說明進行操做。

如今,TensorFlow 將模型信息保存至/tmp/output_graph.pb 和 /tmp/output_labels.txt,兩者做爲命令行參數被輸入至 label_image.py (github.com/tensorflow/…) 腳本。谷歌的圖像識別教程也與另外一個腳本(github.com/tensorflow/…)有關,可是在這個例子中,咱們將繼續使用 label_image.py。


將單點推斷轉換成在線推斷(TensorFlow)

若是咱們只想接受標準輸入的文件名,一行一個,則咱們能夠輕鬆實現「在線」推斷:

while read line ; do
bazel-bin/tensorflow/examples/image_retraining/label_image \
--graph=/tmp/output_graph.pb --labels=/tmp/output_labels.txt \
--output_layer=final_result:0 \
--image="$line" ;
done複製代碼

若是以性能爲出發點來看,這太糟糕了:咱們須要爲每一個輸入樣本從新加載神經網絡、權重、整個 TensorFlow 架構和 Python!

咱們固然能夠作得更好。讓咱們從編輯 label_image.py script 開始。它的地址爲 bazel-bin/tensorflow/examples/image_retraining/label_image.runfiles/org_tensorflow/tensorflow/examples/image_retraining/label_image.py。

咱們將如下行

141:  run_graph(image_data, labels, FLAGS.input_layer, FLAGS.output_layer,
142:        FLAGS.num_top_predictions)複製代碼

改成:

141:  for line in sys.stdin:
142:    run_graph(load_image(line), labels, FLAGS.input_layer, FLAGS.output_layer,
142:        FLAGS.num_top_predictions)複製代碼

這樣速度快多了,可是仍然不是最好!

緣由在於第 100 行的 with tf.Session() as sess 構造。本質上,TensorFlow 在每次啓用 run_graph 時,將全部計算加載至內存中。若是你試着在 GPU 上執行推斷時就會明顯發現這一現象,你會看到 GPU 內存隨着 TensorFlow 在 GPU 上加載和卸載模型參數而升降。據我所知,該構造在其餘 ML 框架如 Caffe 或 PyTorch 中不存在。

解決方案是去掉 with 語句,向 run_graph 添加 sess 變量:

def run_graph(image_data, labels, input_layer_name, output_layer_name,
              num_top_predictions, sess):
    # Feed the image_data as input to the graph.
    # predictions will contain a two-dimensional array, where one
    # dimension represents the input image count, and the other has
    # predictions per class
    softmax_tensor = sess.graph.get_tensor_by_name(output_layer_name)
    predictions, = sess.run(softmax_tensor, {input_layer_name: image_data})
    # Sort to show labels in order of confidence
    top_k = predictions.argsort()[-num_top_predictions:][::-1]
    for node_id in top_k:
       human_string = labels[node_id]
       score = predictions[node_id]
       print('%s (score = %.5f)' % (human_string, score))
    return [ (labels[node_id], predictions[node_id].item()) for node_id in top_k ] # numpy floats are not json serializable, have to run item... with tf.Session() as sess: for line in sys.stdin:
          run_graph(load_image(line), labels, FLAGS.input_layer, FLAGS.output_layer,
                FLAGS.num_top_predictions, sess)複製代碼

代碼地址:github.com/hiveml/simp…

運行後,你會發現每張圖像花費時間約爲 0.1 秒,這樣的速度快到能夠在線使用了。


將單點推斷轉換成在線推斷(其餘 ML 框架)


部署

計劃是將代碼封裝進 Flask app。Flask 是一個輕量級 Python 網頁框架,容許用極少的工做運行 http api 服務器。

做爲快速推斷,下列 Flask app 接受 multipart/form-data 的 POST 請求:

#!/usr/bin/env python
# usage: python echo.py to launch the server ; and then in another session, do
# curl -v -XPOST 127.0.0.1:12480 -F "data=@./image.jpg"
from flask import Flask, request
app = Flask(__name__)
@app.route('/', methods=['POST'])
def classify():
    try:
       data = request.files.get('data').read()
       print repr(data)[:1000]
       return data, 200
    except Exception as e:
       return repr(e), 500
app.run(host='127.0.0.1',port=12480)複製代碼

下面是對應的 Flask app,可鏈接上文提到的 run_graph:

And here is the corresponding flask app hooked up to run_graph above:

#!/usr/bin/env python
# usage: bash tf_classify_server.shfrom flask import Flask, request
import tensorflow as tf
import label_image as tf_classify
import json
app = Flask(__name__)
FLAGS, unparsed = tf_classify.parser.parse_known_args()
labels = tf_classify.load_labels(FLAGS.labels)
tf_classify.load_graph(FLAGS.graph)
sess = tf.Session()
@app.route('/', methods=['POST'])
def classify():
    try:
       data = request.files.get('data').read()
       result = tf_classify.run_graph(data, labels, FLAGS.input_layer, FLAGS.output_layer, FLAGS.num_top_predictions, sess)
       return json.dumps(result), 200
    except Exception as e:
       return repr(e), 500
app.run(host='127.0.0.1',port=12480)
複製代碼


看起來還不錯,除了 Flask 和 TensorFlow 徹底同步之外:執行圖像分類時,Flask 按照接收請求的順序一次處理一個請求,而 TensorFlow 徹底佔用線程。

如上所述,速度的瓶頸可能仍然在於實際計算量,所以升級 Flask 封裝器代碼沒有太大意義。或許該代碼足以處理加載。有兩個明顯的方式能夠擴大請求吞吐量:經過增長工做線程的數量來水平擴大請求吞吐量(下一節將講述),或利用 GPU 和批邏輯(batching logic)垂直擴大請求吞吐量。後者的實現要求網頁服務器一次處理多個掛起請求,並決定是否等待較大批次仍是將其發送至 TensorFlow 圖線程進行分類,對此 Flask app 徹底不適合。兩種方式使用 Twisted + Klein 用 Python 寫代碼;若是你偏好第一類事件循環支持,並但願可以鏈接到非 Python ML 框架如 Torch,則須要使用 Node.js + ZeroMQ。


擴展:負載平衡和服務發現

如今咱們已經有一個模型可用的服務器,可是它可能太慢,或咱們的負載過高。咱們想運行更多此類服務器,那麼咱們應該怎樣在多個服務器上對其進行分佈呢?普通方法是添加一個代理層,能夠是 haproxy 或 nginx,能夠平衡後端服務器之間的負載,同時向用戶呈現一個統一的界面。下面是運行初級 Node.js 負載平衡器 http proxy 的示例代碼:

// Usage : node basic_proxy.js WORKER_PORT_0,WORKER_PORT_1,...
const worker_ports = process.argv[2].split(',')
if (worker_ports.length === 0) { console.err('missing worker ports') ; process.exit(1) }
const proxy = require('http-proxy').createProxyServer({})proxy.on('error', () => console.log('proxy error'))let i = 0require('http').createServer((req, res) => {
    proxy.web(req,res, {target: 'http://localhost:' + worker_ports[ (i++) % worker_ports.length ]})
}).listen(12480)
console.log(`Proxying localhost:${12480} to [${worker_ports.toString()}]`)

// spin up the ML workersconst { exec } = require('child_process')
worker_ports.map(port => exec(`/bin/bash ./tf_classify_server.sh ${port}`))

複製代碼

爲了自動檢測後端服務器的數量和地址,人們一般使用一個「服務發現」工具,它可能和負載平衡器捆綁在一塊兒,也可能分開。一些有名的工具,如 Consul 和 Zookeeper。設置並學習如何使用此類工具超出了本文範疇,所以,我使用 node.js 服務發現包 seaport 推斷了一個很是初級的代理。代理代碼:

// Usage : node seaport_proxy.js
const seaportServer = require('seaport').createServer()
seaportServer.listen(12481)
const proxy = require('http-proxy').createProxyServer({})
proxy.on('error', () => console.log('proxy error'))

let i = 0require('http').createServer((req, res) => {
    seaportServer.get('tf_classify_server', worker_ports => {
      const this_port = worker_ports[ (i++) % worker_ports.length ].port
      proxy.web(req,res, {target: 'http://localhost:' + this_port })
})
}).listen(12480)
console.log(`Seaport proxy listening on ${12480} to '${'tf_classify_server'}' servers registered to ${12481}`)複製代碼

工做線程代碼:

// Usage : node tf_classify_server.js
const port = require('seaport').connect(12481).register('tf_classify_server')
console.log(`Launching tf classify worker on ${port}`)
require('child_process').exec(`/bin/bash ./tf_classify_server.sh ${port}`)
複製代碼

可是,在應用到機器學習時,這個配置會遇到帶寬問題。

系統若是每秒鐘處理數10、數百張圖片,它就會卡在系統帶寬上。在目前的裝配上,全部的數據須要經過咱們的單個 seaport 主機,也是面向客戶端的單個端點。

爲了解決這個問題,咱們須要客戶不點擊單個端點:http://127.0.0.1:12480,而是在後端服務器間自動旋轉來點擊。若是你懂網絡架構,這聽起來更像是 DNS 的活。

可是,配置定製的 DNS 服務器不在本文的討論範圍。把客戶端代碼改編遵循成 2 階「手動 DNS」協議就行,咱們能重複使用基本的 seaport proxy 來實現「端對端的」協議,其中客戶能直接鏈接到服務器:

代理代碼:

// Usage : node p2p_proxy.js
const seaportServer = require('seaport').createServer()
seaportServer.listen(12481)

let i = 0
require('http').createServer((req, res) => {
    seaportServer.get('tf_classify_server', worker_ports => {
      const this_port = worker_ports[ (i++) % worker_ports.length ].port
      res.end(`${this_port}
`)
  })
}).listen(12480)
console.log(`P2P seaport proxy listening on ${12480} to 'tf_classify_server' servers registered to ${12481}`)
複製代碼

(worker code 和上面同樣)

客戶端代碼:

curl -v -XPOST localhost:`curl localhost:12480` -F"data=@$HOME/flower_photos/daisy/21652746_cc379e0eea_m.jpg"複製代碼


結論與拓展閱讀

這個時候,你應該上手作點什麼,但這確定也不是不會過期的技術。在此文章中,還有不少重要的主題沒被覆蓋到:

  • 在新硬件上的自動開發與裝配
  • 在本身的硬件上,值得關注的工具包括 Openstack/VMware,還有安裝 Docker、管理網絡路徑的 Chef/Puppet,安裝 TensorFlow、Python 等等的 Docker。

  • 在雲端,Kubernetes 或者 Marathon/Mesos 都很是棒


  • 模型版本管理

  • 一開始手動管理模型不是很難

  • TensorFlow Serving 是處理這個問題的不錯工具,還有批處理和總體部署,很是完全。缺點是有點難以配置,也難以編寫客戶端代碼,此外還不支持 Caffe/PyTorch。


  • 如何從 Matlab 遷移機器學習代碼?

  • 在開發產品中不要用 Matlab(譯者注:僅表明做者觀點)。


  • GPU 驅動、Cuda、CUDNN

  • 使用英偉達容器並嘗試尋找一些在線 Dorckerfiles


  • 後處理層。一旦你在開發產品過程當中找到一些不一樣的機器學習模型,你可能想要混合這些模型,併爲不一樣的使用案例匹配不一樣的模型——也就是模型 B 沒結果跑模型 A,在 Caffe 上跑模型 C,並把結果傳送到 TensorFlow 上跑的模型 D,等等。


原文連接: thehive.ai/blog/simple…
相關文章
相關標籤/搜索