Tensorflow Servingで使用するために、エクスポートしたKerasモデルにカスタム前処理ロジックを含めたいと思います。
私の前処理は文字列のトークン化を実行し、外部辞書を使用して各トークンを埋め込みレイヤーへの入力用のインデックスに変換します:
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)
モデルはTensorflowServingで使用されるため、すべての前処理ロジックをモデル自体に組み込みます(エクスポートされたモデルファイルにエンコードされます)。
Q:Kerasライブラリのみを使用してこれを行うにはどうすればよいですか?
私が見つけた このガイド KerasとTensorflowを組み合わせる方法を説明しています。しかし、すべてを1つのモデルとしてエクスポートする方法がまだわかりません。
Tensorflowには組み込みの文字列分割、 ファイルI/O 、および 辞書ルックアップ操作 があることを知っています。
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, ...)
Q:これらのTensorflowの事前定義された前処理操作とKerasレイヤーを一緒に使用して、モデルをトレーニングし、Tensorflowサービングで使用するための「ブラックボックス」としてエクスポートするにはどうすればよいですか?
私はそれを理解したので、ここで私自身の質問に答えるつもりです。
要点は次のとおりです:
まず、(別のコードファイルで)独自の前処理関数のみを使用して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"]})
受け入れられた答えは非常に役立ちますが、@ Qululuが述べたように古いKerasAPIと古いTFServing API(Exporter)を使用し、入力が元のtfプレースホルダーになるようにモデルをエクスポートする方法を示していません(後処理であるKerasmodel.input)。以下は、TFv1.4およびKeras2.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()
[〜#〜] update [〜#〜] Tensorflowを使用した推論の前処理は、CPU操作であり、モデルがGPUサーバーにデプロイされている場合は効率的に実行されません。 GPUのストールが非常に悪く、スループットが非常に低くなっています。したがって、代わりに、クライアントプロセスでの効率的な前処理のためにこれを廃止しました。