0%

在keras中使用tf.dataset作为输入

tf.dataset的API支持keras,是Tensorflow-1.9.0的一个新特性。使用tf.dataset作为输入的pipeline可以减少系统内存和显存的占用率,使得在训练大规模数据时,避免内存容量不够的问题出现。

在tensorflow的官方文档中,有简单的使用说明,但仍有一些bug在tensorflow-1.10.0中才将被修复。针对这个版本尚存在的一些问题,以下介绍一些可以规避这些问题的使用办法。

将tf.dataset作为输入传入可以在fit()函数中,也可以在Input()层和compiler()函数中分别传入

1
2
3
4
5
6
7
#在fit()函数传入
model.fit(x=iter_x.get_next(),y=iter_y.get_next(),
epochs=epochs,steps_per_epoch=steps_per_epoch)
# 在Input()层和compiler()函数传入
inputs = Input(tensor=iter_x.get_next())
model.compile(loss=loss,optimizer=optimizer,
target_tensors=[iter_y.get_next()])

传入时需要将dataset类转换为tensor类。这一步涉及两个步骤,首先需要生成dataset的iterator,对于不同的dataset有不同生成方法,常用的有make_one_shot_iterator和make_initializable_iterator两种。之后通过iterator的get_next()函数迭代获取tensor类数据。

1
2
3
iterator = dataset.make_initializable_iterator()
iterator = dataset.make_one_shot_iterator()
iterator = iterator.get_next()

keras对于传入的tensor有tf.dtype的要求,x需要是tf.float32类型。如果类型不符,可以通过tf.cast()进行类型转换

1
inputs = Input(tensor=tf.cast(iter_x.get_next(),tf.float32))

面对一系列初始化的需要时,可以先获取kera的session,并在keras的session中对table和iterator进行初始化

1
2
3
from keras import backend as K
K.get_session().run(tf.tables_initializer())
K.get_session().run(iter_data.initializer)

训练时如果采用在Input()层和compiler()函数中传入x和y的方法,后续进行交叉验证、测试集测试以及结果生成时需要构建新的模型。因为这种方式相当于将模型的输入输出固定,将使模型不受输入输出的影响。新构建的模型需要符合原来模型的结构,但在Input()层和compiler()函数中的相应参数可以改为新的x和y。再传入训练时模型的权重并调用evaluate或者predict进行验证和预测。一个kears官方的示例
如果在notebook中使用这种方法需要注意在内存中清除原来的会话。

1
K.clear_session()

将keras模型存成protobuf的格式

tensorflow 有许多种不同的模型存储格式,不同的存储方式调用的存储函数是不一样的,使用tensorflow作为后端的keras也同样支持这些模型保存的方法。这里介绍一种能够方便部署在服务器端和移动端的protobuf格式。
建议采用from tensorflow import keras以及from tensorflow.python.keras.models import...来使用keras。
在构建好keras的网络结构后就可以开始对模型进行保存。为了使之后能便捷找到模型的结点,可以构建模型时对每一层的name参数进行赋值,对相关层命名。

1
2
context_input = Input(shape=(10,),name='context_input')
out = Dense((1), activation = "sigmoid",name="out")

构建完模型后可以获得模型在tensorflow下的graph,查看网络结构是否如愿搭建以及此前命名是否成功。

1
2
3
4
graph = K.get_session().graph
K.set_learning_phase(0)
for op in graph.get_operations():
print(op.name)

模型在保存前,可以通过K.set_learning_phase(0)将模型的参数设为不可变化的非训练模式。
模型的保存可以采用SaveModel的API进行保存,保存的结果将得到一个目录,目录里包含模型结构的pb文件以及包含参数名称和值的另一个目录。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from tensorflow.python.keras.models import Model
from tensorflow.keras import backend as K
import numpy as np
export_path = './keras_save'
signature = tf.saved_model.signature_def_utils.predict_signature_def(
inputs={'sentence_name': dual_encoder.input}, #没有太明白signature的作用 #TODO
outputs={'outputs_name': dual_encoder.output})
builder = tf.saved_model.builder.SavedModelBuilder(export_path)

with K.get_session() as sess:
builder.add_meta_graph_and_variables(
sess,['eval'],
signature_def_map={'predict': signature})
builder.save(True)
print('Finished export', export_path)

其中add_meta_graph_and_variables的第二个参数是可选字符
虽然保存之后目录里有.pb文件,但这个.pb文件的格式并不能通过graph_def.ParseFromString(f.read())的方式进行读入,因为tensorflow的文件读写需要完全使用成对的api进行完成。对应于f.saved_model.builder.SavedModelBuilder()的是tf.saved_model.loader.load()具体的使用方式如下:

1
2
3
4
5
6
7
8

with tf.Session(graph=tf.Graph()) as sess:
tf.saved_model.loader.load(sess, ['eval'], pb_file_path+'keras_save')
sess.run(tf.global_variables_initializer())
input_x1 = sess.graph.get_tensor_by_name('context_input:0')
output_y = sess.graph.get_tensor_by_name('out/Sigmoid:0')
#其实是有更好的办法读预测结构 #TODO
ret = sess.run(output_y,feed_dict={input_x1:data1})

这样ret得到的值便是对应输入data1的模型前馈的结果。
这部分有两个比较耗时的地方,首先是在load部分,其次是在get_tensor_by_name部分。因此在实际部署时,可以保持sess处于常开状态来减小i/o开销。此外,每个sess第一次run的时间将为之后run时间的数十倍。

reference