周日 2016年4月24日 由弗朗索瓦Chollet 在教程中。
如果TensorFlow是您的主要框架,并且您正在寻找一个简单且高级模型定义界面以使您的工作更轻松,那么本教程适合您。
Keras层和模型完全兼容纯TensorFlow张量,因此,Keras为TensorFlow提供了一个很好的模型定义附加功能,甚至可以与其他TensorFlow库一起使用。让我们看看这是如何做的。
请注意,本教程假定您已经配置Keras使用TensorFlow后端(而不是Theano)。这里是如何做到这一点的说明。
我们将涵盖以下几点:
I:在TensorFlow张量上调用Keras层
II:在TensorFlow中使用Keras模型
III:多GPU和分布式训练
IV:用TensorFlow-serving导出模型
我们从一个简单的例子开始:MNIST数字分类。我们将使用一堆KerasDense
层(全连接层)来构建一个TensorFlow数字分类器。
我们应该首先创建一个TensorFlow会话并注册到Keras。这意味着Keras将使用我们注册的会话来初始化它在内部创建的所有变量。
import tensorflow as tf
sess = tf.Session()
from keras import backend as K
K.set_session(sess)
现在让我们开始使用我们的MNIST模型。我们可以像在TensorFlow中那样开始构建一个分类器:
# 这个占位符将包含我们输入的所有数字作为平面向量
img = tf.placeholder(tf.float32, shape=(None, 784))
然后,我们可以使用Keras层来加速模型定义过程:
from keras.layers import Dense
# 可以在TensorFlow张量中调用Keras层
x = Dense(128, activation='relu')(img) # 128个单元的全连接层和ReLU激活函数
x = Dense(128, activation='relu')(x)
preds = Dense(10, activation='softmax')(x) # 10个单元的输出层和softmax激活函数
我们定义标签的占位符,以及我们将使用的损失函数:
labels = tf.placeholder(tf.float32, shape=(None, 10))
from keras.objectives import categorical_crossentropy
loss = tf.reduce_mean(categorical_crossentropy(labels, preds))
我们用TensorFlow优化器训练模型:
from tensorflow.examples.tutorials.mnist import input_data
mnist_data = input_data.read_data_sets('MNIST_data', one_hot=True)
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(loss)
# 初始化所有变量
init_op = tf.global_variables_initializer()
sess.run(init_op)
# 运行训练循环
with sess.as_default():
for i in range(100):
batch = mnist_data.train.next_batch(50)
train_step.run(feed_dict={img: batch[0],
labels: batch[1]})
我们现在可以评估模型:
from keras.metrics import categorical_accuracy as accuracy
acc_value = accuracy(labels, preds)
with sess.as_default():
print acc_value.eval(feed_dict={img: mnist_data.test.images,
labels: mnist_data.test.labels})
在这种情况下,我们只使用Keras作为语法的快捷方式来生成一个op,将一些张量输入映射到某个张量输出,就是这样。优化是通过原生TensorFlow优化器而不是Keras优化器完成的。我们甚至不使用任何Keras Model
!
关于原生TensorFlow优化器和Keras优化器相对性能的说明:在使用TensorFlow优化器对“Keras方式”进行优化时,速度差异很小。甚至有点反直觉,Keras大部分时间似乎更快,大约5-10%。然而,这些差异是足够小的,最终总结出,无论您是通过Keras优化器还是原生TF优化器优化您的模型,都无关紧要。
一些Keras层(例如Dropout
, BatchNormalization
)在训练时期和测试时期表现不同。可以通过打印layer.uses_learning_phase
来判断一个层是否使用“学习阶段”(训练/测试) :如果层在训练模式和测试模式下有不同的行为则为True
,否则为False
。
如果您的模型包含这样的层,那么您需要指定学习阶段的值作为feed_dict
的一部分,以便您的模型知道是否应用或丢失等。
Keras学习阶段(标量TensorFlow张量)可通过Keras后端访问:
from keras import backend as K
print K.learning_phase()
要使用学习阶段,只需简单地把值“1”(训练模式)或“0”(测试模式)传递给feed_dict
:
# 训练模式
train_step.run(feed_dict={x: batch[0], labels: batch[1], K.learning_phase(): 1})
例如,以下是如何将Dropout
层添加到我们以前的MNIST示例中:
from keras.layers import Dropout
from keras import backend as K
img = tf.placeholder(tf.float32, shape=(None, 784))
labels = tf.placeholder(tf.float32, shape=(None, 10))
x = Dense(128, activation='relu')(img)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu')(x)
x = Dropout(0.5)(x)
preds = Dense(10, activation='softmax')(x)
loss = tf.reduce_mean(categorical_crossentropy(labels, preds))
train_step = tf.train.GradientDescentOptimizer(0.5).minimize(loss)
with sess.as_default():
for i in range(100):
batch = mnist_data.train.next_batch(50)
train_step.run(feed_dict={img: batch[0],
labels: batch[1],
K.learning_phase(): 1})
acc_value = accuracy(labels, preds)
with sess.as_default():
print acc_value.eval(feed_dict={img: mnist_data.test.images,
labels: mnist_data.test.labels,
K.learning_phase(): 0})
Keras层和模型与TensorFlow name scope完全兼容。例如,请考虑以下代码片段:
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
with tf.name_scope('block1'):
y = LSTM(32, name='mylstm')(x)
我们LSTM层的权重将被命名block1/mylstm_W_i
,block1/mylstm_U_i
等...
同样,devide scope也可以按照您的预期工作:
with tf.device('/gpu:0'):
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
y = LSTM(32)(x) # 所有op/变量都存在于GPU:0中
您在TensorFlow graph scope内定义的任何Keras层或模型都将具有作为指定图的一部分创建的所有变量和操作。例如,下面的工作就像你所期望的那样:
from keras.layers import LSTM
import tensorflow as tf
my_graph = tf.Graph()
with my_graph.as_default():
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
y = LSTM(32)(x) # LSTM层的所有op/变量都被创建作为图的一部分
与variable scope的兼容性
变量共享应通过多次调用相同的Keras层(或模型)实例来完成,而不是通过TensorFlow variable scope。TensorFlow variable scope对Keras层或模型没有影响。有关Keras权重共享的更多信息,请参阅功能性API指南中的“权重共享”部分。
快速总结Keras中的权重分配的工作原理:通过重用相同的层实例或模型实例,您可以共享其权重。这是一个简单的例子:
# 实例化一个Keras层
lstm = LSTM(32)
# 实例化两个TF占位符
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
y = tf.placeholder(tf.float32, shape=(None, 20, 64))
# 用*相同的* LSTM权重对两个张量进行编码
x_encoded = lstm(x)
y_encoded = lstm(y)
一些Keras层(有状态的RNN和BatchNormalization
层)具有需要作为每个训练步骤的一部分运行的内部更新。存储为张量元组列表layer.updates
。你应该为那些生成assign
op,在每个训练阶段运行。这是一个例子:
from keras.layers import BatchNormalization
layer = BatchNormalization()(x)
update_ops = []
for old_value, new_value in layer.updates:
update_ops.append(tf.assign(old_value, new_value))
请注意,如果您使用的是Keras模型(Model
实例或Sequential
实例),则model.udpates
其行为方式相同(并收集模型中所有底层的更新)。
此外,如果您需要明确收集层的可训练权重,可以通过layer.trainable_weights
(或者model.trainable_weights
), TensorFlowVariable
实例的列表:
from keras.layers import Dense
layer = Dense(32)(x) # 实例化并调用层
print layer.trainable_weights # TensorFlow Variables列表
这个可以让你实现基于TensorFlow优化器的自己的训练程序。
Sequential
模型以用于TensorFlow工作流您已经找到在TensorFlow项目中找到想要重复使用的Keras 模型Sequential
(例如,考虑使用带有预先训练权重的VGG16图像分类器)。如何进行?
首先,请注意,如果您的预先训练的权重包含用Theano训练的卷积(Convolution2D
或Convolution1D
层),则在加载权重时需要翻转卷积核心。这是由于Theano和TensorFlow以不同的方式实现卷积(TensorFlow实际上实现了相关性,非常像Caffe)。这里有一个关于你在这种情况下需要做的简短指南。
假设您从下面的Keras模型开始,并且修改它,以便输入一个特定的TensorFlow张量my_input_tensor
。这个输入张量可以是一个数据馈送op,或者是之前的TensorFlow模型的输出。
# 这是我们最初的Keras模型
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=784))
model.add(Dense(10, activation='softmax'))
您只需要使用keras.layers.InputLayer
在自定义TensorFlow占位符之上开始构建Sequential模型,然后在顶部构建模型的其余部分:
from keras.layers import InputLayer
# 这是修改后的Keras模型
model = Sequential()
model.add(InputLayer(input_tensor=custom_input_tensor,
input_shape=(None, 784)))
# 像以前一样构建模型的剩余部分
model.add(Dense(32, activation='relu'))
model.add(Dense(10, activation='softmax'))
在这个阶段,你可以调用model.load_weights(weights_file)
来加载预先训练的权重。
那么你可能会想要收集Sequential
模型的输出张量:
output_tensor = model.output
您现在可以在output_tensor
顶部添加新的TensorFlow op等
Keras模型与层相同,因此可以在TensorFlow张量上调用:
from keras.models import Sequential
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=784))
model.add(Dense(10, activation='softmax'))
# 起作用了!
x = tf.placeholder(tf.float32, shape=(None, 784))
y = model(x)
注意:通过调用Keras模型,您将重用其架构和权重。当您在张量上调用模型时,您将在输入张量之上创建新的TF op,并且这些op将重新使用Variable
已存在于模型中的TF实例。
TensorFlow device scope与Keras层和模型完全兼容,因此可以使用它们将图的特定部分分配给不同的GPU。这是一个简单的例子:
with tf.device('/gpu:0'):
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
y = LSTM(32)(x) # 在LSTM层中的所有op存在于GPU:0中
with tf.device('/gpu:1'):
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
y = LSTM(32)(x) # 在LSTM层中的所有op存在于GPU:1中
请注意,由LSTM层创建的变量不会存在于GPU中:所有的TensorFlow变量总是独立于CPU创建的device scope而独立于CPU上。TensorFlow在幕后处理设备到设备的变量传输。
如果您想要在不同的GPU上训练同一个模型的多个副本,同时在不同的副本上共享相同的权重,则应首先在一个device scope下实例化您的模型(或多个层),然后以不同的方式多次调用相同的模型实例GPU device scope,如:
with tf.device('/cpu:0'):
x = tf.placeholder(tf.float32, shape=(None, 784))
# 共享的模型存在于CPU:0中
# 在训练期间它不会运行,仅充当一个op模板
# 并作为共享变量的存储库
model = Sequential()
model.add(Dense(32, activation='relu', input_dim=784))
model.add(Dense(10, activation='softmax'))
# 副本 0
with tf.device('/gpu:0'):
output_0 = model(x) # 在副本中的所有op存在于GPU:0中
# 副本 1
with tf.device('/gpu:1'):
output_1 = model(x) # 在副本中的所有op存在于GPU:1中
# 在CPU上合并输出
with tf.device('/cpu:0'):
preds = 0.5 * (output_0 + output_1)
# 我们只运行`preds`张量,所以只有两个
# 在GPU上的副本运行(加上CPU上的合并op)
output_value = sess.run([preds], feed_dict={x: data})
通过向Keras注册链接到群集的TF会话,您可以轻松地使用TensorFlow分布式训练:
server = tf.train.Server.create_local_server()
sess = tf.Session(server.target)
from keras import backend as K
K.set_session(sess)
有关在分布式设置中使用TensorFlow的更多信息,请参阅此教程。
TensorFlow Serving是由Google开发的用于在生产环境中提供TensorFlow模型的库。
任何Keras模型都可以使用TensorFlow服务(只要它只有一个输入和一个输出,这是TF服务的限制)导出,不管它是否作为TensorFlow工作流的一部分进行训练。事实上,你甚至可以用Theano训练你的Keras模型,然后切换到TensorFlow Keras后端并导出你的模型。
这是如何工作的。
如果你的图使用了Keras学习阶段(训练时期和测试时期不同的行为),那么在导出你的模型之前要做的第一件事就是对学习阶段的值进行硬编码(假设为0,也就是测试模式)到你的图。这是通过 1) 与Keras后端注册一个不变的学习阶段,2) 之后重新建立你的模型。
这里有两个简单的步骤:
from keras import backend as K
K.set_learning_phase(0) # 所有新的op从现在开始将处于测试模式
# 序列化模型并获得它的权重,以便快速重建
config = previous_model.get_config()
weights = previous_model.get_weights()
# 重新建立一个学习阶段当前被硬编码为0的模型
from keras.models import model_from_config
new_model = model_from_config(config)
new_model.set_weights(weights)
我们现在可以使用TensorFlow-serving来导出模型,按照官方教程中的说明进行操作:
from tensorflow_serving.session_bundle import exporter
export_path = ... # 导出的图保存路径
export_version = ... # 版本号(整数)
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_exporter.export(export_path, tf.constant(export_version)
想要查看本指南中涵盖的新主题?在Twitter上获取。