Keras作为TensorFlow的简化界面:教程

周日 2016年4月24日 由弗朗索瓦Chollet 教程中

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

如果TensorFlow是您的主要框架,并且您正在寻找一个简单且高级模型定义界面以使您的工作更轻松,那么本教程适合您。

Keras层和模型完全兼容纯TensorFlow张量,因此,Keras为TensorFlow提供了一个很好的模型定义附加功能,甚至可以与其他TensorFlow库一起使用。让我们看看这是如何做的。

请注意,本教程假定您已经配置Keras使用TensorFlow后端(而不是Theano)这里是如何做到这一点的说明

我们将涵盖以下几点:

I:在TensorFlow张量上调用Keras层

II:在TensorFlow中使用Keras模型

III多GPU和分布式训练

IV:用TensorFlow-serving导出模型

I:在TensorFlow张量上调用Keras层

我们从一个简单的例子开始: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})

与name scope,devide scope兼容

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_iblock1/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中

与graph scope的兼容性

您在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。你应该为那些生成assignop,在每个训练阶段运行。这是一个例子:

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优化器的自己的训练程序。

II:在TensorFlow中使用Keras模型

转换KerasSequential模型以用于TensorFlow工作流

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

首先,请注意,如果您的预先训练的权重包含用Theano训练的卷积(Convolution2DConvolution1D层),则在加载权重时需要翻转卷积核心。这是由于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等

在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'))

# 起作用了!
x = tf.placeholder(tf.float32, shape=(None, 784))
y = model(x)

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

III:多GPU和分布式训练

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

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的更多信息,请参阅此教程

IV:用TensorFlow-serving导出模型

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上获取

本文的版权归 anthlu 所有,如需转载请联系作者。

编辑于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏机器之心

终于!Keras官方中文版文档正式发布了

3546
来自专栏人工智能

使用Keras在训练深度学习模型时监控性能指标

Keras库提供了一套供深度学习模型训练时的用于监控和汇总的标准性能指标并且开放了接口给开发者使用。

1.9K10
来自专栏Spark学习技巧

译:Tensorflow实现的CNN文本分类

1665
来自专栏专知

【最新TensorFlow1.4.0教程02】利用Eager Execution 自定义操作和梯度 (可在 GPU 运行)

点击上方“专知”关注获取更多AI知识! 【导读】主题链路知识是我们专知的核心功能之一,为用户提供AI领域系统性的知识学习服务,一站式学习人工智能的知识,包含人工...

5266
来自专栏机器之心

NIPS 2018 | 将RNN内存占用缩小90%:多伦多大学提出可逆循环神经网络

循环神经网络(RNN)在语音识别 [1]、语言建模 [2,3] 和机器翻译 [4,5] 等多种任务上都取得了极优的性能。然而,训练 RNN 需要大量的内存。标准...

844
来自专栏技术翻译

回归问题的深层神经网络

众所周知,神经网络可用于解决分类问题,例如,它们被用于手写体数字分类,但问题是,如果我们将它们用于回归问题,它会有效果吗?

7632
来自专栏AI研习社

按照这几个步骤操作,不实现 RNN 都难!

最近在看RNN模型,为简单起见,本篇就以简单的二进制序列作为训练数据,而不实现具体的论文仿真,主要目的是理解RNN的原理和如何在TensorFlow中构造一个简...

2967
来自专栏用户2442861的专栏

tensorflow mnist神经网络示例

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haluoluo211/article/d...

2693
来自专栏贾志刚-OpenCV学堂

SSD网络tensorflow版本源码深入分析

以VGG-16作为特征提取层实现SSD网络的代码,解读SSD网络代码实现的各个细节,从输入参数、默认框的位置匹配、宽高比率、放缩比率、各层默认框的生成、到损失函...

2994
来自专栏ATYUN订阅号

马尔可夫链文本生成的简单应用:不足20行的Python代码生成鸡汤文

提到自然语言的生成时,人们通常认为要会使用高级数学来思考先进的AI系统,然而,并不一定要这样。在这篇文章中,我将使用马尔可夫链和一个小的语录数据集来产生新的语录...

2786

扫码关注云+社区