【问题标题】:Add Tensorflow pre-processing to existing Keras model (for use in Tensorflow Serving)将 Tensorflow 预处理添加到现有的 Keras 模型(用于 Tensorflow Serving)
【发布时间】:2017-05-31 00:56:29
【问题描述】:

我想在我导出的 Keras 模型中包含我的自定义预处理逻辑,以便在 Tensorflow Serving 中使用。

我的预处理执行字符串标记化并使用外部字典将每个标记转换为索引以输入到嵌入层:

from keras.preprocessing import sequence

token_to_idx_dict = ... #read from file

# Custom Pythonic pre-processing steps on input_data
tokens = [tokenize(s) for s in input_data]
token_idxs = [[token_to_idx_dict[t] for t in ts] for ts in tokens]
tokens_padded = sequence.pad_sequences(token_idxs, maxlen=maxlen)

模型架构和训练:

model = Sequential()
model.add(Embedding(max_features, 128, input_length=maxlen))
model.add(LSTM(128, activation='sigmoid'))
model.add(Dense(n_classes, activation='softmax'))
model.compile(loss='sparse_categorical_crossentropy', optimizer='adam')

model.fit(x_train, y_train)

由于模型将在 Tensorflow Serving 中使用,我想将所有预处理逻辑合并到模型本身(在导出的模型文件中编码)。

问:如何仅使用 Keras 库来做到这一点?

我发现this guide 解释了如何结合 Keras 和 Tensorflow。但我仍然不确定如何将所有内容导出为一个模型。

我知道 Tensorflow 有内置的字符串拆分、file I/Odictionary lookup operations

使用 Tensorflow 操作的预处理逻辑:

# Get input text
input_string_tensor = tf.placeholder(tf.string, shape={1})
# Split input text by whitespace
splitted_string = tf.string_split(input_string_tensor, " ")
# Read index lookup dictionary
token_to_idx_dict = tf.contrib.lookup.HashTable(tf.contrib.lookup.TextFileInitializer("vocab.txt", tf.string, 0, tf.int64, 1, delimiter=","), -1)
# Convert tokens to indexes
token_idxs = token_to_idx_dict.lookup(splitted_string)
# Pad zeros to fixed length
token_idxs_padded = tf.pad(token_idxs, ...)

问:如何将这些 Tensorflow 预定义的预处理操作和我的 Keras 层一起用于训练模型,然后将模型导出为“黑盒”以用于 Tensorflow Serving?

【问题讨论】:

  • 找到解决方案了吗?
  • @OphirYoktan 请参阅下面的答案。

标签: python tensorflow keras tensorflow-serving


【解决方案1】:

接受的答案非常有帮助,但是它使用了@Qululu 提到的过时的 Keras API,以及过时的 TF Serving API (Exporter),并且它没有显示如何导出模型以便其输入是原始 tf占位符(与 Keras model.input 相比,后者是后期预处理)。以下是与 TF v1.4 和 Keras 2.1.2 兼容的版本:

sess = tf.Session()
K.set_session(sess)

K._LEARNING_PHASE = tf.constant(0)
K.set_learning_phase(0)

max_features = 5000
max_lens = 500

dict_table = tf.contrib.lookup.HashTable(tf.contrib.lookup.TextFileInitializer("vocab.txt",tf.string, 0, tf.int64, TextFileIndex.LINE_NUMBER, vocab_size=max_features, delimiter=" "), 0)

x_input = tf.placeholder(tf.string, name='x_input', shape=(None,))
sparse_tokenized_input = tf.string_split(x_input)
tokenized_input = tf.sparse_tensor_to_dense(sparse_tokenized_input, default_value='')
token_idxs = dict_table.lookup(tokenized_input)
token_idxs_padded = tf.pad(token_idxs, [[0,0],[0, max_lens]])
token_idxs_embedding = tf.slice(token_idxs_padded, [0,0], [-1, max_lens])

model = Sequential()
model.add(InputLayer(input_tensor=token_idxs_embedding, input_shape=(None, max_lens)))

 ...REST OF MODEL...

model.load_weights("model.h5")

x_info = tf.saved_model.utils.build_tensor_info(x_input)
y_info = tf.saved_model.utils.build_tensor_info(model.output)

prediction_signature = tf.saved_model.signature_def_utils.build_signature_def(inputs={"text": x_info}, outputs={"prediction":y_info}, method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME)

builder = saved_model_builder.SavedModelBuilder("/path/to/model")

legacy_init_op = tf.group(tf.tables_initializer(), name='legacy_init_op')

init_op = tf.group(tf.global_variables_initializer(), tf.local_variables_initializer())
sess.run(init_op)


# Add the meta_graph and the variables to the builder
builder.add_meta_graph_and_variables(
  sess, [tag_constants.SERVING],
  signature_def_map={
       signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY:
           prediction_signature,
  },
  legacy_init_op=legacy_init_op)

builder.save()  

更新 使用 Tensorflow 进行推理预处理是一项 CPU 操作,如果模型部署在 GPU 服务器上,则无法有效执行。 GPU停顿非常糟糕,吞吐量非常低。因此,我们为了在客户端进程中进行高效的预处理而放弃了它。

【讨论】:

  • 你是怎么在这里调用model.fit()的?输入只是一个字符串列表吗?我应该把要处理的原始字符串放在哪里?
  • @chattrat423 这是一个导出模型进行推理的脚本,这样 Tensorflow 可以代替客户端进行预处理。对于训练,您可以使用仅 Keras 的版本,无需 Tensorflow 预处理代码(您可以在任何 python 库中进行预处理)。
  • 谢谢,我非常感谢您的清晰。您是否碰巧有一个用于后处理的示例,以及我们采用预测的标签索引并将它们转换为它们的字符串标签形式?我正在尝试使用我的 Keras 模型为 tensorflow 服务创建这个
  • @chattrat423 我不确定我是否理解你的问题。当您将模型导出为 SavedModel 时,您应该使用 tf.saved_model.signature_def_utils.build_signature_def 定义输出签名,如代码所示。这是特定于模型的,应该适合模型的输出层。客户端应该从 TF Serving 调用中提取预测并解析它们。如果您的 SavedModel 已成功保存,则编写一个小的 Python 脚本来调用它并打印响应,它会显示您需要解析的数据结构。
  • @chattrat423 我没有做过任何类似的事情(即使用 Tensorflow 进行“后处理”)。如前所述,我发现最好在客户端进程中进行所有前/后处理,而不是在 Tensorflow 中。
【解决方案2】:

我想通了,所以我将在这里回答我自己的问题。

要点如下:

首先,(在单独的代码文件中)我只使用 Keras 和我自己的预处理函数训练了模型,导出了 Keras 模型权重文件和我的令牌到索引字典。

然后,我只复制了 Keras 模型架构,将输入设置为预处理后的张量输出,从之前训练的 Keras 模型中加载权重文件,并将其夹在 Tensorflow 预处理操作和 Tensorflow 导出器之间。

最终产品:

import tensorflow as tf
from keras import backend as K
from keras.models import Sequential, Embedding, LSTM, Dense
from tensorflow.contrib.session_bundle import exporter
from tensorflow.contrib.lookup import HashTable, TextFileInitializer

# Initialize Keras with Tensorflow session
sess = tf.Session()
K.set_session(sess)

# Token to index lookup dictionary
token_to_idx_path = '...'
token_to_idx_dict = HashTable(TextFileInitializer(token_to_idx_path, tf.string, 0, tf.int64, 1, delimiter='\t'), 0)

maxlen = ...

# Pre-processing sub-graph using Tensorflow operations
input = tf.placeholder(tf.string, name='input')
sparse_tokenized_input = tf.string_split(input)
tokenized_input = tf.sparse_tensor_to_dense(sparse_tokenized_input, default_value='')
token_idxs = token_to_idx_dict.lookup(tokenized_input)
token_idxs_padded = tf.pad(token_idxs, [[0,0],[0,maxlen]])
token_idxs_embedding = tf.slice(token_idxs_padded, [0,0], [-1,maxlen])

# Initialize Keras model
model = Sequential()
e = Embedding(max_features, 128, input_length=maxlen)
e.set_input(token_idxs_embedding)
model.add(e)
model.add(LSTM(128, activation='sigmoid'))
model.add(Dense(num_classes, activation='softmax'))

# Load weights from previously trained Keras model
weights_path = '...'
model.load_weights(weights_path)

K.set_learning_phase(0)

# Export model in Tensorflow format
# (Official tutorial: https://github.com/tensorflow/serving/blob/master/tensorflow_serving/g3doc/serving_basic.md)
saver = tf.train.Saver(sharded=True)
model_exporter = exporter.Exporter(saver)
signature = exporter.classification_signature(input_tensor=model.input, scores_tensor=model.output)
model_exporter.init(sess.graph.as_graph_def(), default_graph_signature=signature)
model_dir = '...'
model_version = 1
model_exporter.export(model_dir, tf.constant(model_version), sess)

# Input example
with sess.as_default():
    token_to_idx_dict.init.run()
    sess.run(model.output, feed_dict={input: ["this is a raw input example"]})

【讨论】:

  • 仅供参考,Layer 方法set_input() 仅适用于 Keras 版本 1.1.1。之后,它被删除了。我不知道如何在以后的版本中将层的输入设置为 Tensorflow 张量。如果有人这样做,请发表评论。
  • 嗨@Qululu,在 Keras 2.0+ 中,您现在可以使用 Keras 模型/层自动调用 Tensorflow 张量/占位符(就像您通常使用 Keras 层/张量等一样)。 ..例如,看到这个官方页面:blog.keras.io/…...希望这有帮助! ;)
  • 如何使用 tf 预处理训练模型?如何调用 .fit() 以及 tf 占位符是如何输入的?
  • 目前似乎不支持 github.com/keras-team/keras/issues/7503。为了调用 .fit(),我最终从模型中剔除了 InputLayer,并像这样调用 fit():model.fit(token_idxs_embedding.eval(session=sess, feed_dict={x_input: X_train}), y_train...。这实现了 TF 占位符。为了推理,我将 InputLayer 放回模型中并保存。
猜你喜欢
  • 2017-09-24
  • 2018-08-03
  • 1970-01-01
  • 2019-04-21
  • 2017-09-10
  • 1970-01-01
  • 2019-03-08
  • 2020-01-07
  • 2018-09-29
相关资源
最近更新 更多