Tensorflow2.0 发布以来,越来越感觉到Google应该是大力推行Keras而不是Estimator。虽然Bert为首的Transformer大行其道,在有些情况下,我们依然需要从零开始搭建自己的模型。
这篇文章基于 Github 项目地址,我们用一个简单的RNN 模型对数据进行三分类情感分析,并将训练好的模型部署到服务器。
代码的流程大致是
- 数据预处理
- tf.data 生成数据流
- 训练模型并保存
- 部署
一,数据
数据是一个手机评论的情感分析,正面,负面,中性,数据样例如下:
1|挺好的,赞一个,手机很好,很喜欢
0|手机还行,但是手机刚开箱时屏幕和背面有很多指纹痕迹,手机壳跟**在地上磨过似的,好几条印子。要不是看在能把这些痕迹擦掉,和闲退货麻烦,就给退了。就不能规规矩矩做生意么。还有送的都是什么吊东西,运动手环垃圾一比,贴在手机后面的固定手环还**是塑料的渡了一层银色,耳机也和图片描述不符,碎屏险已经注册,不知道怎么样。讲真的,要不就别送或者少送,要不,就规规矩矩的,不然到最后还让人觉得不舒服。其他没什么。
2|手机整体还可以,拍照也很清楚,也很流畅支持华为。给一星是因为有缺陷,送的耳机是坏的!评论区好评太多,需要一些差评来提醒下,以后更加注意细节,提升质量。
0|前天刚买的, 看着还行, 指纹解锁反应不错。
其中0是中性,1是正面,2是负面,我们将数据分为train,dev,test
我们来看如何喂数据:
dataset = tf.data.TextLineDataset(path)
dataset = dataset.map(
lambda line: (tf.compat.v1.string_split([line]).values[1:], tf.compat.v1.string_split([line]).values[0]),
num_parallel_calls=configs.num_parallel_calls
)
其中num_parallel_calls,用来做多线程,配合后面的一步可以加快读取速度。
dataset = dataset.map(lambda text,label: (text,tf.strings.to_number(
label,
out_type=tf.dtypes.int64,
name=None
)))
这边需要将label的格式改为int64来匹配tensorflow的数据格式。
dataset = dataset.padded_batch(batch_size,padded_shapes=([-1],[]),padding_values=(tf.constant(pad_value, dtype=tf.string),tf.constant(1, dtype=tf.int64)))
这边的dataset就是pad并分batch的数据,等待模型的读取。
三,训练模型并保存
首先先定义一个input函数,这个函数将之前读入的文字数据映射成了模型可以读取的数字数据,注意到prefetch(1),就是搭配之前的num_parallel_calls,加速读取。
def input_data_func(data_path,shuffle=True):
vocab = vocab_func(vocab_path)
input_data = build_dataset(data_path)
indices = tf.range(len(vocab), dtype=tf.int64)
table_init = tf.lookup.KeyValueTensorInitializer(vocab, indices)
table = tf.lookup.StaticVocabularyTable(table_init,num_oov_buckets)
def encode_words(X_batch, y_batch):
return table.lookup(X_batch), y_batch
input_data = input_data.map(encode_words).prefetch(1)
return input_data
主的model我们这样定义:
model = keras.models.Sequential([
keras.layers.Embedding(len(vocab)+num_oov_buckets, embed_size,
mask_zero=True,
# embeddings_initializer=tf.keras.initializers.Constant(matrix),
# trainable=True,
input_shape=[None]),
keras.layers.GRU(n_units,return_sequences=True),
keras.layers.GRU(n_units),
keras.layers.Dense(n_outputs, activation="softmax")
])
这边embeddings_initializer可以选择随机初始化,也可以选择预训练好的字向量。注意到这边用了两层GRU,所以第一层要把所有的输出都返回return_sequences=True。
checkpoint_cb = keras.callbacks.ModelCheckpoint(check_file_path,
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=5,
restore_best_weights=True)
tensorboard_cb = keras.callbacks.TensorBoard(run_logdir)
定义check_point,格式是H5。这一步主要是防止训练过程意外中断。
optimizer = keras.optimizers.Adam(lr=lr, beta_1=0.9, beta_2=0.999)
loss = keras.losses.CategoricalCrossentropy()
model.compile(loss=loss, optimizer=optimizer,
metrics=["accuracy"])
使用Adam,loss要使用CategoricalCrossentropy()。开始训练:
history = model.fit(train_data, epochs=n_epochs,
validation_data=dev_data,
callbacks=[tensorboard_cb,checkpoint_cb,early_stopping_cb])
训练结束后,大概是97%的准确率。我们先不管这个,先把模型存下来:
model_version = "0001"
model_name = "my_cls_model"
model_path = os.path.join(model_name, model_version)
tf.saved_model.save(model, model_path)
到此,pb格式的模型已经存下来了。下面就是看如何部署了。
四,部署
部署可以分为如下几个步骤:
- 安装docker和tf.serving
- 部署模型
- rest简单测试
- 安装中间件Flask
- 完成部署
一,安装docker和tf.serving
sudo apt-get install docker
docker pull tensorflow/serving
这边的docker是cpu版本的。
二,部署模型
将上面的模型文件夹my_cls_model拷贝到目标目录,这边的示例是拷到了根目录,运行如下命令起服务:
docker run -it --rm -p 8500:8500 -p 8501:8501 -v "/my_cls_model:/models/my_cls_model" -e MODEL_NAME=my_cls_model tensorflow/serving
三,rest简单测试
当在服务器上部署好过后,可以在本机测试下是否能够联通:
X_new = [[1,4,5,7,8],[2,4,5,9,10]]
input_data_json = json.dumps({
"signature_name": "serving_default",
"instances": X_new,
})
SERVER_URL = 'http://144.202.100.179:8501/v1/models/my_cls_model:predict'
response = requests.post(SERVER_URL, data=input_data_json)
response.raise_for_status() # raise an exception in case of error
response = response.json()
y_proba = np.array(response["predictions"])
print(y_proba)
这边144.202.100.179是我部署的服务器的地址,需要相应的修改成自己服务器的地址。如果打印成功,则进行下一步
四,中间件Flask
这个中间件Flask的作用是连接了模型和request,并且在这中间件里面处理数据,变成model可以读取的形式,如将文本数据转化为数字传入。
import numpy as np
import requests
from flask import Flask, request, jsonify
# from flask_cors import CORS
app = Flask(__name__)
# Uncomment this line if you are making a Cross domain request
# CORS(app)
# Testing URL
@app.route('/cls/', methods=['POST'])
def sentiment_cls():
sentence = request.get_data()
file_vocab = open('data/vocab.txt','r')
vocab = []
for line in file_vocab:
vocab.append(line.strip())
file_vocab.close()
words = list(sentence.decode('utf-8').strip())
ids=[]
for word in words:
if word in vocab:
ids.append(vocab.index(word))
else:
ids.append(len(vocab)-1)
X_new = [ids]
input_data_json = json.dumps({
"signature_name": "serving_default",
"instances": X_new,
})
SERVER_URL = 'http://144.202.100.179:8501/v1/models/my_cls_model:predict'
response = requests.post(SERVER_URL, data=input_data_json)
response = json.loads(response.text)
predictions = response['predictions'][0]
return str(predictions)
if __name__ == '__main__':
app.run('0.0.0.0',port=5000)
可以把这个app.py放到对应的服务器上,然后运行app.py,此时Flask服务已经起来了。
五,完成部署
API_ENDPOINT = "http://localhost:5000/cls/"
sentence = '这个手机很垃圾'
r = requests.post(url=API_ENDPOINT,data=sentence.encode('utf-8'))
print(r.text)
这时候,可以测试rest是否能够连接Flask,需要注意的是,API_ENDPOINT需要改成Flask部署的地址。
五,总结
Keras比Estimator更加的便捷和一目了然,不再显示的用session减少了很多麻烦。serving的出现让部署更加的便捷。
Tensorflow2.0真香。
本文Github项目地址