投稿/爆料
厂商入驻

【原创】教程帖:深度学习模型的部署

原创|论智|2018-01-19 14:39

编者按:当你训练好一个机器学习模型了,下一步该做什么?数据科学公司Hive又在他们的博客上发布了一篇教程,一步步教你部署深度学习模型。以下是论智对原文的编译。

如果你成功地用TensorFlow或Caffe训练了一个机器学习模型,并且喜欢更简单的解决方案,那就请读下去吧!本文将教你如何将训练好的机器学习模型部署到实际工作中去。

实际中的机器学习

Hive第一次进入机器学习领域时,我们就已经有了百万张带有标准标签的图片,能让我们用不到一周的时间从零开始训练最先进的深度卷积图片分类模型。不过,一般的机器学习案例通常只需数百幅图像,为此我们建议微调现有的模型。例如,www.tensorflow.org/tutorials/image_retraining网站有关于如何微调ImageNet模型(在120万张图片中训练,共有1000个类别),以分类花的样本数据集(3647张图片,5个类别)。

安装好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脚本。谷歌的image_recognition教程也连接到另一个推理脚本,但是现在我们仍然用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脚本。我的脚本位于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上进行推理,你就可以看到,随着TensorFlow从GPU中加载和卸载模型参数,GPU内存也会跟着变化。据我所知,Caffe或PyTorch等机器学习框架中不存在这种结构。

最后的解决方案是提出with语句,并传递一个sess变量到run_graph

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/simple-ml-serving/blob/master/label_image.py

如果运行了上面这个,每张图像只需要0.1秒,比在线使用更快。

将one-shot推断转换成在线推断(其他ML框架)

Caffe使用net.forward代码,这个代码很容易放入可调用的框架中。可参阅:nbviewer.jupyter.org/github/BVLC/caffe/blob/master/examples/00-classification.ipynb

Mxnet有随时可用的公开的推理服务器代码:github.com/awslabs/mxnet-model-server

未来将有更多细节!

部署

我们计划将代码封装成一个Flask应用。Flask是一个轻量级Python web框架,它允许你以最少的工作启动一个http api服务器。

这是一个接受多部分表单数据的POST请求的Flask应用:

#!/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)

这是相应的搭配上文run_graph的Flask应用:

#!/usr/bin/env python
# usage: bash tf_classify_server.sh
from 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包装代码没有多大意义。也许现在这个代码足以处理你的负载。

有两种方法可以扩大请求的吞吐量:通过增加worker的数量横向扩大(接下来会介绍),或者通过使用GPU和批量处理纵向扩展。后者需要一个能够一次处理多个请求的web服务器,并决定是否继续等待更大批量的处理或是将其发送到TensorFlow图形线程进行分类,这对于Flask是非常不合适的。另外两种方法是使用Twisted+Klein来保存Python代码,或者Node.js+ZeroMQ,如果你更喜欢顶尖的时间循环支持,并能够连接到非Python ML框架(如PyTorch)。

扩展:负载平衡和发现服务

现在我们已经有了一个服务器,但是它太慢了,或者因为负载太高,所以我们想要启动更多的服务器——如何将请求分配到各服务器呢?

一般的方法是添加一个代理层,也许是haproxy或nginx,它可以平衡后端服务器之间的负载,同时提供一个统一的到客户端的接口。以下是运行基本Node.js负载均衡器http代理的代码示例:

// 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 = 0
require('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 workers
const { 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 = 0
require('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}`)

worker代码:

// 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}`)

然而,当应用到机器学习上时,这一配置会遇到带宽的问题。

每秒处理几十到几百张图像,系统就会阻碍网络带宽。在目前的设置中,所有数据都要经过seaport主站,这是提供给客户端的单个端点。

为了解决这个问题,我们需要客户端不要只访问单个端点:http://127.0.0.1:12480,而是要在后端服务器之间轮流访问。如果你懂一些网络,这听起来就像DNS的工作!

但是,本文不讨论设置自定义的DNS服务器。相反,通过更改客户端,遵循“手动DNS”的协议,我们可以重新使用基本的seaport代理来实现客户端直接连接到其服务器的“点对点”协议:

代理代码(worker代码与其相同):

// 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}`)

客户端代码:

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

RPC安装

即将完成!一个将上文的Flask替换为ZeroMQ的版本。

结语

看到这,你应该能将模型部署到工作中了,但学会本教程并非一劳永逸了。其中还有几处未涉及的要点:

  • 自动部署和设置新的硬件:

如果在自己的硬件上,推荐Openstack或者VMware,安装Docker和处理网络路由则需要Chef或Puppet。推荐使用Docker安装TensorFlow、Python和其他框架;

如果用云,则推荐Kubernetes或者Marathon/Mesos。

  • 模型版本管理

刚开始手工操作并不是很困难;

TensorFlow Serving在处理、批量处理和统一部署方面都非常强大。但缺点是有些难设置,编写客户端代码有点难。另外不支持Caffe和PyTorch。

  • 如何从Matlab中移植你的ML代码

不要在生产环境中运行matlab。

  • CPU驱动程序Cuda,CUDNN

使用nvidia-docker并尝试在线查找一些Docker文件。

  • 后处理图层。

一旦在工作中得到了不同的ML模型,你可能开始想要将他们混合匹配不同的案例——如果模型B没有结果就运行模型A,在Caffe中运行模型C并将结果传递给TensorFlow中的模型D等等。

原文地址:thehive.ai/blog/simple-ml-serving

本文来自机器人之家,如若转载,请注明出处:https://www.jqr.com/news/009644
爆料投稿,欢迎投递至邮箱:service@jqr.com
机器学习深度学习
推荐阅读

最新评论(0

暂无回帖,快来抢沙发吧

评论

游客
robot
发布需求
联系客服联系客服
robot
联系客服