Keras 作为 TensorFlow 的简化接口:教程

注意:这篇文章写于 2016 年 4 月。它不再反映 TensorFlow 和 Keras 的最佳实践。Keras 现已集成到 TensorFlow 中。有关详细信息,请参阅 keras.io 文档

将 Keras 作为 TensorFlow 工作流程的一部分使用的完整指南

如果 TensorFlow 是您的主要框架,并且您正在寻找一个简单且高级的模型定义接口来简化您的工作,那么本教程适合您。

Keras 层和模型与纯 TensorFlow 张量完全兼容,因此,Keras 是 TensorFlow 的一个很好的模型定义插件,甚至可以与其他 TensorFlow 库一起使用。让我们看看如何做到这一点。

**请注意,本教程假设您已将 Keras 配置为使用 TensorFlow 后端(而不是 Theano)**。 以下是有关如何执行此操作的说明

我们将涵盖以下几点

一:在 TensorFlow 张量上调用 Keras 层

二:将 Keras 模型与 TensorFlow 一起使用

三:多 GPU 和分布式训练

四:使用 TensorFlow-serving 导出模型

keras+tensorflow


一:在 TensorFlow 张量上调用 Keras 层

让我们从一个简单的示例开始:MNIST 数字分类。我们将使用一堆 Keras Dense 层(全连接层)构建一个 TensorFlow 数字分类器。

我们应该首先创建一个 TensorFlow 会话并将其注册到 Keras。这意味着 Keras 将使用我们注册的会话来初始化它在内部创建的所有变量。

import tensorflow as tf
sess = tf.Session()

from keras import backend as K
K.set_session(sess)

现在让我们开始构建我们的 MNIST 模型。我们可以像在 TensorFlow 中一样开始构建分类器

# this placeholder will contain our input digits, as flat vectors
img = tf.placeholder(tf.float32, shape=(None, 784))

然后,我们可以使用 Keras 层来加速模型定义过程

from keras.layers import Dense

# Keras layers can be called on TensorFlow tensors:
x = Dense(128, activation='relu')(img)  # fully-connected layer with 128 units and ReLU activation
x = Dense(128, activation='relu')(x)
preds = Dense(10, activation='softmax')(x)  # output layer with 10 units and a softmax activation

我们定义标签的占位符,以及我们将使用的损失函数

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)

# Initialize all variables
init_op = tf.global_variables_initializer()
sess.run(init_op)

# Run training loop
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 用作语法上的快捷方式,以生成将某些张量输入映射到某些张量输出的操作,仅此而已。优化是通过原生 TensorFlow 优化器而不是 Keras 优化器完成的。我们甚至根本没有使用任何 Keras Model

关于原生 TensorFlow 优化器和 Keras 优化器相对性能的说明:“Keras 方式”优化模型与使用 TensorFlow 优化器相比,速度略有差异。有点违反直觉的是,Keras 在大多数情况下似乎更快,速度提高了 5-10%。但是,这些差异足够小,以至于最终使用 Keras 优化器还是原生 TF 优化器来优化模型并不重要。

训练和测试期间的不同行为

一些 Keras 层(例如 DropoutBatchNormalization)在训练时和测试时的行为不同。您可以通过打印 layer.uses_learning_phase(一个布尔值)来判断一个层是否使用“学习阶段”(训练/测试):如果该层在训练模式和测试模式下具有不同的行为,则为 True,否则为 False

如果您的模型包含此类层,则需要在 feed_dict 中指定学习阶段的值,以便您的模型知道是否应用 dropout 等。

可以通过 Keras 后端访问 Keras 学习阶段(一个标量 TensorFlow 张量)

from keras import backend as K
print K.learning_phase()

要使用学习阶段,只需将值“1”(训练模式)或“0”(测试模式)传递给 feed_dict

# train mode
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 名称范围完全兼容。例如,请考虑以下代码片段

x = tf.placeholder(tf.float32, shape=(None, 20, 64))
with tf.name_scope('block1'):
    y = LSTM(32, name='mylstm')(x)

然后,我们的 LSTM 层的权重将被命名为 block1/mylstm_W_iblock1/mylstm_U_i 等...

类似地,设备范围按预期工作

with tf.device('/gpu:0'):
    x = tf.placeholder(tf.float32, shape=(None, 20, 64))
    y = LSTM(32)(x)  # all ops / variables in the LSTM layer will live on GPU:0

与图范围的兼容性

您在 TensorFlow 图范围中定义的任何 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)  # all ops / variables in the LSTM layer are created as part of our graph

与变量范围的兼容性

变量共享应该通过多次调用相同的 Keras 层(或模型)实例来完成,而不是通过 TensorFlow 变量范围。TensorFlow 变量范围对 Keras 层或模型没有影响。有关使用 Keras 进行权重共享的更多信息,请参阅函数式 API 指南中的“权重共享”部分

简要总结一下 Keras 中的权重共享是如何工作的:通过重复使用相同的层实例或模型实例,您就是在共享其权重。这是一个简单的示例

# instantiate a Keras layer
lstm = LSTM(32)

# instantiate two TF placeholders
x = tf.placeholder(tf.float32, shape=(None, 20, 64))
y = tf.placeholder(tf.float32, shape=(None, 20, 64))

# encode the two tensors with the *same* LSTM weights
x_encoded = lstm(x)
y_encoded = lstm(y)

收集可训练权重和状态更新

一些 Keras 层(有状态 RNN 和 BatchNormalization 层)具有需要作为每个训练步骤的一部分运行的内部更新。它们存储为张量元组列表,layer.updates。您应该为它们生成 assign 操作,以便在每个训练步骤中运行。这是一个示例

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)来完成,这是一个 TensorFlow Variable 实例列表

from keras.layers import Dense

layer = Dense(32)(x)  # instantiate and call a layer
print layer.trainable_weights  # list of TensorFlow Variables

了解这一点后,您就可以基于 TensorFlow 优化器实现自己的训练例程。


二:将 Keras 模型与 TensorFlow 一起使用

转换 Keras Sequential 模型以在 TensorFlow 工作流程中使用

您找到了一个想要在 TensorFlow 项目中重复使用的 Keras Sequential 模型(例如,请考虑这个具有预训练权重的 VGG16 图像分类器)。如何进行?

首先,请注意,如果您的预训练权重包含使用 Theano 训练的卷积(层 Convolution2DConvolution1D),则在加载权重时需要翻转卷积核。这是因为 Theano 和 TensorFlow 以不同的方式实现卷积(TensorFlow 实际上实现的是相关性,很像 Caffe)。 这是在这种情况下您需要做什么的简要指南

假设您从以下 Keras 模型开始,并且想要对其进行修改,使其将特定的 TensorFlow 张量 my_input_tensor 作为输入。此输入张量可以是数据馈送操作,例如,也可以是先前 TensorFlow 模型的输出。

# this is our initial Keras model
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

# this is our modified Keras model
model = Sequential()
model.add(InputLayer(input_tensor=custom_input_tensor,
                     input_shape=(None, 784)))

# build the rest of the model as before
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 操作。

在 TensorFlow 张量上调用 Keras 模型

Keras 模型的行为与层相同,因此可以在 TensorFlow 张量上调用

from keras.models import Sequential

model = Sequential()
model.add(Dense(32, activation='relu', input_dim=784))
model.add(Dense(10, activation='softmax'))

# this works! 
x = tf.placeholder(tf.float32, shape=(None, 784))
y = model(x)

注意:通过调用 Keras 模型,您将重复使用其架构和权重。当您在张量上调用模型时,您将在输入张量之上创建新的 TF 操作,并且这些操作将重复使用模型中已经存在的 TF Variable 实例。


三:多 GPU 和分布式训练

将 Keras 模型的一部分分配给不同的 GPU

TensorFlow 设备范围与 Keras 层和模型完全兼容,因此您可以使用它们将图的特定部分分配给不同的 GPU。这是一个简单的示例

with tf.device('/gpu:0'):
    x = tf.placeholder(tf.float32, shape=(None, 20, 64))
    y = LSTM(32)(x)  # all ops in the LSTM layer will live on GPU:0

with tf.device('/gpu:1'):
    x = tf.placeholder(tf.float32, shape=(None, 20, 64))
    y = LSTM(32)(x)  # all ops in the LSTM layer will live on GPU:1

请注意,LSTM 层创建的变量不会驻留在 GPU 上:所有 TensorFlow 变量始终驻留在 CPU 上,而与其创建所在的设备范围无关。TensorFlow 在后台处理设备到设备的变量传输。

如果您想在不同的 GPU 上训练同一个模型的多个副本,同时在不同副本之间共享相同的权重,则应首先在一个设备范围内实例化您的模型(或层),然后在不同的 GPU 设备范围内多次调用相同的模型实例,例如

with tf.device('/cpu:0'):
    x = tf.placeholder(tf.float32, shape=(None, 784))

    # shared model living on CPU:0
    # it won't actually be run during training; it acts as an op template
    # and as a repository for shared variables
    model = Sequential()
    model.add(Dense(32, activation='relu', input_dim=784))
    model.add(Dense(10, activation='softmax'))

# replica 0
with tf.device('/gpu:0'):
    output_0 = model(x)  # all ops in the replica will live on GPU:0

# replica 1
with tf.device('/gpu:1'):
    output_1 = model(x)  # all ops in the replica will live on GPU:1

# merge outputs on CPU
with tf.device('/cpu:0'):
    preds = 0.5 * (output_0 + output_1)

# we only run the `preds` tensor, so that only the two
# replicas on GPU get run (plus the merge op on CPU)
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 导出模型

TensorFlow Serving 是一个用于在生产环境中提供 TensorFlow 模型的库,由 Google 开发。

任何 Keras 模型都可以使用 TensorFlow-serving 导出(只要它只有一个输入和一个输出,这是 TF-serving 的限制),无论它是否作为 TensorFlow 工作流程的一部分进行训练。事实上,您甚至可以使用 Theano 训练您的 Keras 模型,然后切换到 TensorFlow Keras 后端并导出您的模型。

以下是它的工作原理。

如果您的图使用了 Keras 学习阶段(训练时和测试时的行为不同),那么在导出模型之前,您需要做的第一件事就是将学习阶段的值(大概是 0,即测试模式)硬编码到您的图中。这可以通过以下步骤完成:1)使用 Keras 后端注册一个常量学习阶段,以及 2)之后重新构建您的模型。

以下是这两个简单步骤的实际操作

from keras import backend as K

K.set_learning_phase(0)  # all new operations will be in test mode from now on

# serialize the model and get its weights, for quick re-building
config = previous_model.get_config()
weights = previous_model.get_weights()

# re-build a model where the learning phase is now hard-coded to 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 = ... # where to save the exported graph
export_version = ... # version number (integer)

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), sess)

想在本指南中看到新的主题?请在 Twitter 上联系我们