【转载】TensorFlow Serving 尝尝鲜

作者:Mao Chan

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2016年,机器学习在 Alpha Go 与李世石的世纪之战后变得更加炙手可热。Google也在今年推出了 TensorFlow Serving 又加了一把火。

TensorFlow Serving 是一个用于机器学习模型 serving 的高性能开源库。它可以将训练好的机器学习模型部署到线上,使用 gRPC 作为接口接受外部调用。更加让人眼前一亮的是,它支持模型热更新与自动模型版本管理。这意味着一旦部署 TensorFlow Serving 后,你再也不需要为线上服务操心,只需要关心你的线下模型训练。

今天我就带大家来用 TensorFlow Serving 部署一个简单的 Linear Regression 模型。

以下演示运行在 Ubuntu 16.04 LTS 之上。

TensorFlow Serving 处于快速迭代期。如果本文内容与官方文档矛盾,请以官方文档为参考。

环境

TensorFlow Serving 目前依赖 Google 的开源编译工具 Bazel。Bazel 是 Google 内部编译工具 Blaze 的开源版本,功能与性能基本一致。具体的安装可以参考官方文档。此外还需要安装 gRPC (Google 又一个内部工具的开源版)。

之后请参考官方安装指南完成。值得注意的是,最后的 bazel build 将会需要大约30分钟时间并占用约5-10G的空间(时间取决于机器性能)。配合使用 -c opt 能一定程度加快 build。

模型训练

接下来我们用 TensorFlow 写一个简单的测试用 Linear Regression 模型。数据的话我就使用正弦函数生成 1000 个点,尝试用一条直线去拟合。

样本数据生成如下:

# Generate input data

x_data = np.arange(100, step=.1)

y_data = x_data + 20 * np.sin(x_data / 10)

# Reshape data

x_data = np.reshape(x_data, (n_samples, 1))

y_data = np.reshape(y_data, (n_samples, 1))

然后用一个简单的 y = wx + b 来做一个训练,使用 Adam 算法。简单调整了下参数:

sample = 1000, learning_rate = 0.01, batch_size = 100, n_steps = 500

# Placeholders for batched input

x = tf.placeholder(tf.float32, shape=(batch_size, 1))

y = tf.placeholder(tf.float32, shape=(batch_size, 1))

# Do training

with tf.variable_scope('test'):

    w = tf.get_variable('weights', (1, 1), initializer=tf.random_normal_initializer())

    b = tf.get_variable('bias', (1,), initializer=tf.constant_initializer(0))

    y_pred = tf.matmul(x, w) + b

    loss = tf.reduce_sum((y - y_pred) ** 2 / n_samples)

    opt = tf.train.AdamOptimizer(learning_rate=learning_rate).minimize(loss)

    with tf.Session() as sess:

        sess.run(tf.initialize_all_variables())

        for _ in range(n_steps):

            indices = np.random.choice(n_samples, batch_size)

            x_batch = x_data[indices]

            y_batch = y_data[indices]

            _, loss_val = sess.run([opt, loss], feed_dict={x:x_batch, y:y_batch})

        print w.eval()

        print b.eval()

        print loss_val

大致把 loss 收敛在 15.8 左右。精度应该足够了,毕竟只是一个简单的测试用模型。

模型导出

接下来的就是本文的重点:导出模型。

tf.train.Saver

用于保存和恢复Variable。它可以非常方便的保存当前模型的变量或者倒入之前训练好的变量。一个最简单的运用:

saver - tf.train.Saver()

# Save the variables to disk.

saver.save(sess, "/tmp/test.ckpt")

# Restore variables from disk.

saver.restore(sess, "/tmp/test.ckpt")

tf.contrib.session_bundle.exporter.Exporter

导出模型还需要这个 Exporter 的协助。令人尴尬的是这个 Exporter 太新了,还没有 API 文档支持,只能参考 Github 的代码实现

Exporter 的基本使用方式是

传入 saver 构造一个实例

调用 init 定义模型的 graph 和 input/output

使用 export 导出为文件

model_exporter = exporter.Exporter(saver)

model_exporter.init(

    sess.graph.as_graph_def(),

    named_graph_signatures={

        'inputs': exporter.generic_signature({'x': x}),

        'outputs': exporter.generic_signature({'y': y_pred})})

model_exporter.export(FLAGS.work_dir,       

                      tf.constant(FLAGS.export_version),

                      sess)

大功告成!编译!我们成功导出了一个可以部署在 TensorFlow Serving 上的模型。它接受一个 x 值然后返回一个 y 值。导出的文件夹以 version 命名,包含用于部署的 meta 文件, 模型 checkpoint 文件和序列化的模型 graph:

/tmp/test/00000001checkpoint export-00000-of-00001 export.meta

模型部署

部署的方式非常简单,只需要以下两步:

$ bazel build //tensorflow_serving/model_servers:tensorflow_model_server$ bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server --port=9000 --model_name=test --model_base_path=/tmp/test/

我们看到 TensorFlow Serving 成功加载了我们刚刚导出的 model。并且还在不断尝试 poll 新的 model:

$ bazel build //tensorflow_serving/model_servers:tensorflow_model_server

$bazel-bin/tensorflow_serving/model_servers/tensorflow_model_server --port=9000 --model_name=test --model_base_path=/tmp/test/

我们看到 TensorFlow Serving 成功加载了我们刚刚导出的 model。并且还在不断尝试 poll 新的 model:

客户端

接下来我们写一个简单的 Client 来调用下我们部署好的 Model。这里我们需要用到 TensorFlow Serving 的 Predict API 和 gRPC 的 implementations.insecure_channel 来construct 一个 request。特别要注意的是 input 的 signature 和数据必须和之前 export 的模型匹配。本例中为 名称为 x, float32类型,大小为 [100, 1] 的 Tensor。

from grpc.beta import implementations

import numpy as np

import tensorflow as tf

from tensorflow_serving.apis import predict_pb2

from tensorflow_serving.apis import prediction_service_pb2

tf.app.flags.DEFINE_string('server', 'localhost:9000',

                          'PredictionService host:port')

FLAGS = tf.app.flags.FLAGS

n_samples = 100

host, port = FLAGS.server.split(':')

channel = implementations.insecure_channel(host, int(port))

stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)

# Generate test data

x_data = np.arange(n_samples, step=1, dtype=np.float32)

x_data = np.reshape(x_data, (n_samples, 1))

# Send request

request = predict_pb2.PredictRequest()

request.model_spec.name = 'test'

  request.inputs['x'].CopyFrom(tf.contrib.util.make_tensor_proto(x_data, shape=[100, 1]))

result = stub.Predict(request, 10.0)  # 10 secs timeout

别忘了配置一下 bazel 的 BUILD 文件:

py_binary(

    name = "test_client",

    srcs = [

        "test_client.py",

    ],

    deps = [

        "//tensorflow_serving/apis:predict_proto_py_pb2",

        "//tensorflow_serving/apis:prediction_service_proto_py_pb2",

        "@org_tensorflow//tensorflow:tensorflow_py",

    ],

)

最后编译运行,就能看到在线预测结果啦!

bazel build //tensorflow_serving/test:test_client && ./bazel-bin/tensorflow_serving/test/test_client

延伸

TensorFlow 封装了众多常用模型成为 Estimator,帮助用户避免了冗长易错的算法实现部分。比如以上的例子就可以完全用 LinearRegressor 来替换。只需要几行代码简单地调用 fit() 函数就能轻松得到收敛的模型。唯一不足的是目前与 TensorFlow Serving 还不能 100% 兼容。虽然 Google 还在全力完善 TensorFlow Serving,但是距离完善还需要一定的时间。

如果既想要使用方便快捷的的 Estimator ,又想线上部署呢?当然也是有办法的,笔者钻研了一下后,实现了一个用 Estimator 训练数据,导出模型后再部署上线的方法。最后用这个线上部署的模型实现一个在线评估房屋价值的系统。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,869评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,716评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,223评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,047评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,089评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,839评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,516评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,410评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,920评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,052评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,179评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,868评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,522评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,070评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,186评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,487评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,162评论 2 356

推荐阅读更多精彩内容