最近,在尝试利用tornado、tensorflow以及keras完成一个短文本分类模型训练及预测的服务。具体的逻辑是这样的:利用tornado封装一个server,然后主要提供三个API,train、apply以及predict。其中train用来传入一些参数进行模型训练,apply用于配置一个分类模型用于短文本预测,predict用来进行短文本的类别预测。因此需要有一个线程监听当前的模型配置,然后实时加载,在predict时调用新模型。
这里主要踩了两个坑:1. 原生keras多线程调用的问题;2. tensorflow封装的keras的问题。首先给出解决方案:
- 请使用原生keras,而不是tensorflow封装的keras
- 在模型加载时,请添加如下语句
keras.backend.clear_session()
self.model = keras.models.load_model(MODEL_PATH)
self.model._make_predict_function()
环境说明
mac os 10.14.2
python 3.7
tornado 5.1.1
tensorflow 1.13.1
keras 2.2.4
完整的多线程使用keras进行多线程模型调用的示例如下,其中build_model方法用于创建两个基础的keras模型,类Test用于进行多线程间模型加载及同步的测试,Test.reload_model用于动态加载模型,Test.predict将会调用最新模型并进行预测。在build_model中,我们将会创建一个模型,但保存为两个不同的模型文件。在reload_model方法中,i = numpy.random.choice([0, 1])
随机选择0或1,用于选择加载的模型,并进行模型加载。在predict方法中,将会随机生成一个10维的向量x,然后调用当前的模型进行预测,并将模型名称及预测结果进行打印。
import time
import threading
import numpy
import keras
def build_model():
inp = keras.layers.Input(shape=(10,))
h = keras.layers.Dense(8, activation='relu')(inp)
outp = keras.layers.Dense(1)(h)
model = keras.models.Model(inputs=inp, outputs=outp)
model.compile(optimizer='rmsprop',
loss='mse',
metrics=['accuracy'])
model.save('./test_model0.h5')
model.save('./test_model1.h5')
class Test(object):
def __init__(self):
self.model = None
self.moel_name = 0
t1 = threading.Thread(target=self.reload_model, name='load')
t2 = threading.Thread(target=self.predict, name='predict')
t1.start()
time.sleep(2)
t2.start()
def reload_model(self):
while True:
i = numpy.random.choice([0, 1])
keras.backend.clear_session()
self.model = keras.models.load_model('./test_model%d.h5' % i)
self.moel_name = i
self.model._make_predict_function()
print('reload successful')
time.sleep(10)
def predict(self):
while True:
x = numpy.random.normal(size=(1, 10))
print("system", x)
print(self.moel_name)
try:
print(self.moel_name, self.model.predict(x))
except Exception as exc:
print(exc)
time.sleep(3)
if __name__ == "__main__":
build_model()
t = Test()
运行结果:
踩坑之路
非原生keras踩坑
在加载keras时,如果将import keras
修改为import tensorflow.keras as keras
,将会得到如下错误结果:Tensor(“Placeholder:0”, shape=(), dtype=float32) must be from the same graph as Tensor(“total:0”, shape=(), dtype=resource)
在加载模型时未执行前述两行代码
如果在模型加载部分,将如下两行代码注释
# keras.backend.clear_session()
self.model = keras.models.load_model(MODEL_PATH)
# self.model._make_predict_function()
则将得到如下错误结果:
TypeError: Cannot interpret feed_dict key as Tensor: Tensor Tensor(“Placeholder:0”, shape=(10, 8), dtype=float32) is not an element of this graph.
原因分析
受限于博主能力,尚未能明确具体原因,大致猜测模型加载不完整,期待其他同行可以一起探讨。
结论
本文结合博主个人实践,给出了多线程使用keras的一种正确方法,并给出了两种错误示例及错误结果。
交流
欢迎添加公众号与我继续交流!