精通 TensorFlow 1.x(三)
九、TensorFlow 和 Keras 中的 CNN
卷积神经网络(CNN)是一种特殊的前馈神经网络,在其架构中包含卷积和汇聚层。也称为 ConvNets,CNN 架构的一般模式是按以下顺序包含这些层:
- 完全连接的输入层
- 卷积,池化和全连接层的多种组合
- 完全连接的输出层,具有 softmax 激活函数
CNN 架构已被证明在解决涉及图像学习的问题(例如图像识别和对象识别)方面非常成功。
在本章中,我们将学习与卷积网络相关的以下主题:
- 理解卷积
- 理解池化
- CNN 架构模式 - LeNet
- 用于 MNIST 数据集的 LeNet
- 使用 TensorFlow 和 MNIST 的 LeNet
- 使用 Keras 和 MNIST 的 LeNet
- 用于 CIFAR 数据集的 LeNet
- 使用 TensorFlow 和 CIFAR10 的 LeNet CNN
- 使用 Keras 和 CIFAR10 的 LeNet CNN
让我们从学习卷积网络背后的核心概念开始。
理解卷积
卷积是 CNN 架构背后的核心概念。简单来说,卷积是一种数学运算,它结合了两个来源的信息来产生一组新的信息。具体来说,它将一个称为内核的特殊矩阵应用于输入张量,以产生一组称为特征图的矩阵。可以使用任何流行的算法将内核应用于输入张量。
生成卷积矩阵的最常用算法如下:
N_STRIDES = [1,1]
1\. Overlap the kernel with the top-left cells of the image matrix.
2\. Repeat while the kernel overlaps the image matrix:
2.1 c_col = 0
2.2 Repeat while the kernel overlaps the image matrix:
2.1.1 set c_row = 0 2.1.2 convolved_scalar = scalar_prod(kernel, overlapped cells)
2.1.3 convolved_matrix(c_row,c_col) = convolved_scalar
2.1.4 Slide the kernel down by N_STRIDES[0] rows.
2.1.5 c_row = c_row + 1
2.3 Slide the kernel to (topmost row, N_STRIDES[1] columns right)
2.4 c_col = c_col + 1
例如,我们假设核矩阵是2 x 2矩阵,输入图像是3 x 3矩阵。下图逐步显示了上述算法:
在卷积操作结束时,我们得到以下特征图:
| -6 | -8 |
| -12 | -14 |
在上面的示例中,与卷积的原始输入相比,生成的特征映射的大小更小。通常,特征图的大小减小(内核大小减 1)。因此,特征图的大小为:
三维张量
对于具有额外深度尺寸的三维张量,您可以将前面的算法视为应用于深度维度中的每个层。将卷积应用于 3D 张量的输出也是 2D 张量,因为卷积运算添加了三个通道。
步幅
数组N_STRIDES中的步长是您想要将内核滑过的行或列的数字。在我们的例子中,我们使用了 1 的步幅。如果我们使用更多的步幅,那么特征图的大小将根据以下等式进一步减小:
填充
如果我们不希望减小特征映射的大小,那么我们可以在输入的所有边上使用填充,使得特征的大小增加填充大小的两倍。使用填充,可以按如下方式计算特征图的大小:
TensorFlow 允许两种填充:SAME或VALID。 SAME填充意味着添加填充,使输出特征图与输入特征具有相同的大小。 VALID填充意味着没有填充。
应用前面提到的卷积算法的结果是特征图,是原始张量的滤波版本。例如,特征图可能只有从原始图像中过滤出的轮廓。因此,内核也称为过滤器。对于每个内核,您将获得单独的 2D 特征图。
根据您希望网络学习的特征,您必须应用适当的过滤器来强调所需的特征。 但是,使用 CNN,模型可以自动了解哪些内核在卷积层中最有效。
TensorFlow 中的卷积运算
TensorFlow 提供实现卷积算法的卷积层。例如,具有以下签名的tf.nn.conv2d()操作:
tf.nn.conv2d(
input,
filter,
strides,
padding,
use_cudnn_on_gpu=None,
data_format=None,
name=None
)
input和filter表示形状[batch_size, input_height, input_width, input_depth]的数据张量和形状[filter_height, filter_width, input_depth, output_depth]的核张量。内核张量中的output_depth表示应该应用于输入的内核数量。strides张量表示每个维度中要滑动的单元数。如上所述,padding是有效的或相同的。
您可以在此链接中找到有关 TensorFlow 中可用卷积操作的更多信息
您可以在此链接中找到有关 Keras 中可用卷积层的更多信息
此链接提供了卷积的详细数学解释:
ufldl.stanford.edu/tutorial/su…
卷积层或操作将输入值或神经元连接到下一个隐藏层神经元。每个隐藏层神经元连接到与内核中元素数量相同数量的输入神经元。所以在前面的例子中,内核有 4 个元素,因此隐藏层神经元连接到输入层的 4 个神经元(3×3 个神经元中)。在我们的例子中,输入层的 4 个神经元的这个区域被称为 CNN 理论中的感受域。
卷积层具有每个内核的单独权重和偏差参数。权重参数的数量等于内核中元素的数量,并且只有一个偏差参数。内核的所有连接共享相同的权重和偏差参数。因此在我们的例子中,将有 4 个权重参数和 1 个偏差参数,但如果我们在卷积层中使用 5 个内核,则总共将有5 x 4个权重参数和5 x 1个偏差参数(每个特征图 4 个权重,1 个偏差)。
理解池化
通常,在卷积操作中,应用几个不同的内核,这导致生成若干特征映射。因此,卷积运算导致生成大尺寸数据集。
例如,将形状为3 x 3 x 1的内核应用于具有28 x 28 x 1像素形状的图像的 MNIST 数据集,可生成形状为26 x 26 x 1的特征映射。如果我们在其中应用 32 个这样的滤波器卷积层,则输出的形状为32 x 26 x 26 x 1,即形状为26 x 26 x 1的 32 个特征图。
与形状为28 x 28 x 1的原始数据集相比,这是一个庞大的数据集。因此,为了简化下一层的学习,我们应用池化的概念。
聚合是指计算卷积特征空间区域的聚合统计量。两个最受欢迎的聚合统计数据是最大值和平均值。应用最大池化的输出是所选区域的最大值,而应用平均池的输出是区域中数字的平均值。
例如,假设特征图的形状为3 x 3,池化区域形状为2 x 2。以下图像显示了使用[1, 1]的步幅应用的最大池操作:
在最大池操作结束时,我们得到以下矩阵:
| 5 | 6 |
| 8 | 9 |
通常,池化操作应用非重叠区域,因此步幅张量和区域张量被设置为相同的值。
例如,TensorFlow 具有以下签名的max_pooling操作:
max_pool(
value,
ksize,
strides,
padding,
data_format='NHWC',
name=None
)
value表示形状[batch_size, input_height, input_width, input_depth]的输入张量。对矩形形状区域ksize执行合并操作。这些区域被形状strides抵消。
您可以在此链接中找到有关 TensorFlow 中可用的池化操作的更多信息
CNN 架构模式 - LeNet
LeNet 是实现 CNN 的流行架构模式。在本章中,我们将学习如何通过按以下顺序创建层来构建基于 LeNet 模式的 CNN 模型:
- 输入层
- 卷积层 1,它产生一组特征映射,具有 ReLU 激活
- 池化层 1 产生一组统计聚合的特征映射
- 卷积层 2,其产生一组特征映射,具有 ReLU 激活
- 池化层 2 产生一组统计聚合的特征映射
- 完全连接的层,通过 ReLU 激活来展平特征图
- 通过应用简单线性激活产生输出的输出层
LeNet 系列模型由 Yann LeCun 及其研究员介绍。有关 LeNet 系列模型的更多详细信息,请访问此链接。
Yann LeCun 通过此链接维护 LeNet 系列模型列表。
用于 MNIST 数据的 LeNet
您可以按照 Jupyter 笔记本中的代码ch-09a_CNN_MNIST_TF_and_Keras。
准备 MNIST 数据到测试和训练集:
from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets(os.path.join('.','mnist'), one_hot=True)
X_train = mnist.train.images
X_test = mnist.test.images
Y_train = mnist.train.labels
Y_test = mnist.test.labels
TensorFlow 中的用于 MNIST 的 LeNet CNN
在 TensorFlow 中,应用以下步骤为 MNIST 数据构建基于 LeNet 的 CNN 模型:
- 定义超参数,以及 x 和 y 的占位符(输入图像和输出标签) :
n_classes = 10 # 0-9 digits
n_width = 28
n_height = 28
n_depth = 1
n_inputs = n_height * n_width * n_depth # total pixels
learning_rate = 0.001
n_epochs = 10
batch_size = 100
n_batches = int(mnist.train.num_examples/batch_size)
# input images shape: (n_samples,n_pixels)
x = tf.placeholder(dtype=tf.float32, name="x", shape=[None, n_inputs])
# output labels
y = tf.placeholder(dtype=tf.float32, name="y", shape=[None, n_classes])
将输入 x 重塑为形状(n_samples, n_width, n_height, n_depth):
x_ = tf.reshape(x, shape=[-1, n_width, n_height, n_depth])
- 使用形状为
4 x 4的 32 个内核定义第一个卷积层,从而生成 32 个特征图。
- 首先,定义第一个卷积层的权重和偏差。我们使用正态分布填充参数:
layer1_w = tf.Variable(tf.random_normal(shape=[4,4,n_depth,32],
stddev=0.1),name='l1_w')
layer1_b = tf.Variable(tf.random_normal([32]),name='l1_b')
- 接下来,用
tf.nn.conv2d函数定义卷积层。函数参数stride定义了内核张量在每个维度中应该滑动的元素。维度顺序由data_format确定,可以是'NHWC'或'NCHW'(默认为'NHWC')。 通常,stride中的第一个和最后一个元素设置为 1。函数参数padding可以是SAME或VALID。SAMEpadding表示输入将用零填充,以便在卷积后输出与输入的形状相同。使用tf.nn.relu()函数添加relu激活:
layer1_conv = tf.nn.relu(tf.nn.conv2d(x_,layer1_w,
strides=[1,1,1,1],
padding='SAME'
) +
layer1_b
)
- 使用
tf.nn.max_pool()函数定义第一个池化层。参数ksize表示使用2×2×1个区域的合并操作,参数stride表示将区域滑动2×2×1个像素。因此,区域彼此不重叠。由于我们使用max_pool,池化操作选择2 x 2 x 1区域中的最大值:
layer1_pool = tf.nn.max_pool(layer1_conv,ksize=[1,2,2,1],
strides=[1,2,2,1],padding='SAME')
第一个卷积层产生 32 个大小为28 x 28 x 1的特征图,然后池化成32 x 14 x 14 x 1的数据。
- 定义第二个卷积层,它将此数据作为输入并生成 64 个特征图。
- 首先,定义第二个卷积层的权重和偏差。我们用正态分布填充参数:
layer2_w = tf.Variable(tf.random_normal(shape=[4,4,32,64],
stddev=0.1),name='l2_w')
layer2_b = tf.Variable(tf.random_normal([64]),name='l2_b')
- 接下来,用
tf.nn.conv2d函数定义卷积层:
layer2_conv = tf.nn.relu(tf.nn.conv2d(layer1_pool,
layer2_w,
strides=[1,1,1,1],
padding='SAME'
) +
layer2_b
)
- 用
tf.nn.max_pool函数定义第二个池化层:
layer2_pool = tf.nn.max_pool(layer2_conv,
ksize=[1,2,2,1],
strides=[1,2,2,1],
padding='SAME'
)
第二卷积层的输出形状为64×14×14×1,然后池化成64×7×7×1的形状的输出。
- 在输入 1024 个神经元的完全连接层之前重新整形此输出,以产生大小为 1024 的扁平输出:
layer3_w = tf.Variable(tf.random_normal(shape=[64*7*7*1,1024],
stddev=0.1),name='l3_w')
layer3_b = tf.Variable(tf.random_normal([1024]),name='l3_b')
layer3_fc = tf.nn.relu(tf.matmul(tf.reshape(layer2_pool,
[-1, 64*7*7*1]),layer3_w) + layer3_b)
- 完全连接层的输出馈入具有 10 个输出的线性输出层。我们在这一层没有使用 softmax,因为我们的损失函数自动将 softmax 应用于输出:
layer4_w = tf.Variable(tf.random_normal(shape=[1024, n_classes],
stddev=0.1),name='l)
layer4_b = tf.Variable(tf.random_normal([n_classes]),name='l4_b')
layer4_out = tf.matmul(layer3_fc,layer4_w)+layer4_b
这创建了我们保存在变量model中的第一个 CNN 模型:
model = layer4_out
鼓励读者探索具有不同超参数值的 TensorFlow 中可用的不同卷积和池操作符。
为了定义损失,我们使用tf.nn.softmax_cross_entropy_with_logits函数,对于优化器,我们使用AdamOptimizer函数。您应该尝试探索 TensorFlow 中可用的不同优化器函数。
entropy = tf.nn.softmax_cross_entropy_with_logits(logits=model, labels=y)
loss = tf.reduce_mean(entropy)
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss)
最后,我们通过迭代n_epochs来训练模型,并且在n_batches上的每个周期列中,每批batch_size的大小:
with tf.Session() as tfs:
tf.global_variables_initializer().run()
for epoch in range(n_epochs):
total_loss = 0.0
for batch in range(n_batches):
batch_x,batch_y = mnist.train.next_batch(batch_size)
feed_dict={x:batch_x, y: batch_y}
batch_loss,_ = tfs.run([loss, optimizer],
feed_dict=feed_dict)
total_loss += batch_loss
average_loss = total_loss / n_batches
print("Epoch: {0:04d} loss = {1:0.6f}".format(epoch,average_loss))
print("Model Trained.")
predictions_check = tf.equal(tf.argmax(model,1),tf.argmax(y,1))
accuracy = tf.reduce_mean(tf.cast(predictions_check, tf.float32))
feed_dict = {x:mnist.test.images, y:mnist.test.labels}
print("Accuracy:", accuracy.eval(feed_dict=feed_dict))
我们得到以下输出:
Epoch: 0000 loss = 1.418295
Epoch: 0001 loss = 0.088259
Epoch: 0002 loss = 0.055410
Epoch: 0003 loss = 0.042798
Epoch: 0004 loss = 0.030471
Epoch: 0005 loss = 0.023837
Epoch: 0006 loss = 0.019800
Epoch: 0007 loss = 0.015900
Epoch: 0008 loss = 0.012918
Epoch: 0009 loss = 0.010322
Model Trained.
Accuracy: 0.9884
现在,与我们在前几章中看到的方法相比,这是一个非常好的准确率。从图像数据中学习 CNN 模型是不是很神奇?
Keras 中的用于 MNIST 的 LeNet CNN
让我们重新审视具有相同数据集的相同 LeNet 架构,以在 Keras 中构建和训练 CNN 模型:
- 导入所需的 Keras 模块:
import keras
from keras.models import Sequential
from keras.layers import Conv2D,MaxPooling2D, Dense, Flatten, Reshape
from keras.optimizers import SGD
- 定义每个层的过滤器数量:
n_filters=[32,64]
- 定义其他超参数:
learning_rate = 0.01
n_epochs = 10
batch_size = 100
- 定义顺序模型并添加层以将输入数据重新整形为形状
(n_width,n_height,n_depth):
model = Sequential()
model.add(Reshape(target_shape=(n_width,n_height,n_depth),
input_shape=(n_inputs,))
)
- 使用
4 x 4内核过滤器,SAME填充和relu激活添加第一个卷积层:
model.add(Conv2D(filters=n_filters[0],kernel_size=4,
padding='SAME',activation='relu')
)
- 添加区域大小为
2 x 2且步长为2 x 2的池化层:
model.add(MaxPooling2D(pool_size=(2,2),strides=(2,2)))
- 以与添加第一层相同的方式添加第二个卷积和池化层:
model.add(Conv2D(filters=n_filters[1],kernel_size=4,
padding='SAME',activation='relu')
)
model.add(MaxPooling2D(pool_size=(2,2),strides=(2,2)))
- 添加层以展平第二个层的输出和 1024 个神经元的完全连接层,以处理展平的输出:
model.add(Flatten())
model.add(Dense(units=1024, activation='relu'))
- 使用
softmax激活添加最终输出层:
model.add(Dense(units=n_outputs, activation='softmax'))
- 使用以下代码查看模型摘要:
model.summary()
该模型描述如下:
Layer (type) Output Shape Param #
=================================================================
reshape_1 (Reshape) (None, 28, 28, 1) 0
_________________________________________________________________
conv2d_1 (Conv2D) (None, 28, 28, 32) 544
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 14, 14, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 14, 14, 64) 32832
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 7, 7, 64) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 3136) 0
_________________________________________________________________
dense_1 (Dense) (None, 1024) 3212288
_________________________________________________________________
dense_2 (Dense) (None, 10) 10250
=================================================================
Total params: 3,255,914
Trainable params: 3,255,914
Non-trainable params: 0
_________________________________________________________________
- 编译,训练和评估模型:
model.compile(loss='categorical_crossentropy',
optimizer=SGD(lr=learning_rate),
metrics=['accuracy'])
model.fit(X_train, Y_train,batch_size=batch_size,
epochs=n_epochs)
score = model.evaluate(X_test, Y_test)
print('\nTest loss:', score[0])
print('Test accuracy:', score[1])
我们得到以下输出:
Epoch 1/10
55000/55000 [===================] - 267s - loss: 0.8854 - acc: 0.7631
Epoch 2/10
55000/55000 [===================] - 272s - loss: 0.2406 - acc: 0.9272
Epoch 3/10
55000/55000 [===================] - 267s - loss: 0.1712 - acc: 0.9488
Epoch 4/10
55000/55000 [===================] - 295s - loss: 0.1339 - acc: 0.9604
Epoch 5/10
55000/55000 [===================] - 278s - loss: 0.1112 - acc: 0.9667
Epoch 6/10
55000/55000 [===================] - 279s - loss: 0.0957 - acc: 0.9714
Epoch 7/10
55000/55000 [===================] - 316s - loss: 0.0842 - acc: 0.9744
Epoch 8/10
55000/55000 [===================] - 317s - loss: 0.0758 - acc: 0.9773
Epoch 9/10
55000/55000 [===================] - 285s - loss: 0.0693 - acc: 0.9790
Epoch 10/10
55000/55000 [===================] - 217s - loss: 0.0630 - acc: 0.9804
Test loss: 0.0628845927377
Test accuracy: 0.9785
准确率的差异可归因于我们在这里使用 SGD 优化器这一事实,它没有实现我们用于 TensorFlow 模型的AdamOptimizer提供的一些高级功能。
用于 CIFAR10 数据的 LeNet
现在我们已经学会了使用 TensorFlow 和 Keras 的 MNIST 数据集构建和训练 CNN 模型,让我们用 CIFAR10 数据集重复练习。
CIFAR-10 数据集包含 60,000 个32x32像素形状的 RGB 彩色图像。图像被平均分为 10 个不同的类别或类别:飞机,汽车,鸟,猫,鹿,狗,青蛙,马,船和卡车。 CIFAR-10 和 CIFAR-100 是包含 8000 万个图像的大图像数据集的子集。 CIFAR 数据集由 Alex Krizhevsky,Vinod Nair 和 Geoffrey Hinton 收集和标记。数字 10 和 100 表示图像类别的数量。
有关 CIFAR 数据集的更多详细信息,请访问此链接:
www.cs.toronto.edu/~kriz/cifar…
和
www.cs.toronto.edu/~kriz/learn…
我们选择了 CIFAR 10,因为它有 3 个通道,即图像的深度为 3,而 MNIST 数据集只有一个通道。 为了简洁起见,我们将详细信息留给下载并将数据拆分为训练和测试集,并在本书代码包中的datasetslib包中提供代码。
您可以按照 Jupyter 笔记本中的代码ch-09b_CNN_CIFAR10_TF_and_Keras。
我们使用以下代码加载和预处理 CIFAR10 数据:
from datasetslib.cifar import cifar10
from datasetslib import imutil
dataset = cifar10()
dataset.x_layout=imutil.LAYOUT_NHWC
dataset.load_data()
dataset.scaleX()
加载数据使得图像采用'NHWC'格式,使数据变形(number_of_samples, image_height, image_width, image_channels)。我们将图像通道称为图像深度。图像中的每个像素是 0 到 255 之间的数字。使用 MinMax 缩放来缩放数据集,以通过将所有像素值除以 255 来标准化图像。
加载和预处理的数据在数据集对象变量中可用作dataset.X_train,dataset.Y_train,dataset.X_test和dataset.Y_test。
TensorFlow 中的用于 CIFAR10 的卷积网络
我们保持层,滤波器及其大小与之前的 MNIST 示例中的相同,增加了一个正则化层。由于此数据集与 MNIST 相比较复杂,因此我们为正则化目的添加了额外的丢弃层:
tf.nn.dropout(layer1_pool, keep_prob)
在预测和评估期间,占位符keep_prob设置为 1。这样我们就可以重复使用相同的模型进行训练以及预测和评估。
有关 CIFAR10 数据的 LeNet 模型的完整代码在笔记本ch-09b_CNN_CIFAR10_TF_and_Keras中提供。
在运行模型时,我们得到以下输出:
Epoch: 0000 loss = 2.115784
Epoch: 0001 loss = 1.620117
Epoch: 0002 loss = 1.417657
Epoch: 0003 loss = 1.284346
Epoch: 0004 loss = 1.164068
Epoch: 0005 loss = 1.058837
Epoch: 0006 loss = 0.953583
Epoch: 0007 loss = 0.853759
Epoch: 0008 loss = 0.758431
Epoch: 0009 loss = 0.663844
Epoch: 0010 loss = 0.574547
Epoch: 0011 loss = 0.489902
Epoch: 0012 loss = 0.410211
Epoch: 0013 loss = 0.342640
Epoch: 0014 loss = 0.280877
Epoch: 0015 loss = 0.234057
Epoch: 0016 loss = 0.195667
Epoch: 0017 loss = 0.161439
Epoch: 0018 loss = 0.140618
Epoch: 0019 loss = 0.126363
Model Trained.
Accuracy: 0.6361
与我们在 MNIST 数据上获得的准确率相比,我们没有获得良好的准确率。通过调整不同的超参数并改变卷积和池化层的组合,可以实现更好的准确率。我们将其作为挑战,让读者探索并尝试不同的 LeNet 架构和超参数变体,以实现更高的准确率。
Keras 中的用于 CIFAR10 的卷积网络
让我们在 Keras 重复 LeNet CNN 模型构建和 CIFAR10 数据训练。我们保持架构与前面的示例相同,以便轻松解释概念。在 Keras 中,丢弃层添加如下:
model.add(Dropout(0.2))
用于 CIFAR10 CNN 模型的 Keras 中的完整代码在笔记本ch-09b_CNN_CIFAR10_TF_and_Keras中提供。
在运行模型时,我们得到以下模型描述:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d_1 (Conv2D) (None, 32, 32, 32) 1568
_________________________________________________________________
max_pooling2d_1 (MaxPooling2 (None, 16, 16, 32) 0
_________________________________________________________________
dropout_1 (Dropout) (None, 16, 16, 32) 0
_________________________________________________________________
conv2d_2 (Conv2D) (None, 16, 16, 64) 32832
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 8, 8, 64) 0
_________________________________________________________________
dropout_2 (Dropout) (None, 8, 8, 64) 0
_________________________________________________________________
flatten_1 (Flatten) (None, 4096) 0
_________________________________________________________________
dense_1 (Dense) (None, 1024) 4195328
_________________________________________________________________
dropout_3 (Dropout) (None, 1024) 0
_________________________________________________________________
dense_2 (Dense) (None, 10) 10250
=================================================================
Total params: 4,239,978
Trainable params: 4,239,978
Non-trainable params: 0
_________________________________________________________________
我们得到以下训练和评估结果:
Epoch 1/10
50000/50000 [====================] - 191s - loss: 1.5847 - acc: 0.4364
Epoch 2/10
50000/50000 [====================] - 202s - loss: 1.1491 - acc: 0.5973
Epoch 3/10
50000/50000 [====================] - 223s - loss: 0.9838 - acc: 0.6582
Epoch 4/10
50000/50000 [====================] - 223s - loss: 0.8612 - acc: 0.7009
Epoch 5/10
50000/50000 [====================] - 224s - loss: 0.7564 - acc: 0.7394
Epoch 6/10
50000/50000 [====================] - 217s - loss: 0.6690 - acc: 0.7710
Epoch 7/10
50000/50000 [====================] - 222s - loss: 0.5925 - acc: 0.7945
Epoch 8/10
50000/50000 [====================] - 221s - loss: 0.5263 - acc: 0.8191
Epoch 9/10
50000/50000 [====================] - 237s - loss: 0.4692 - acc: 0.8387
Epoch 10/10
50000/50000 [====================] - 230s - loss: 0.4320 - acc: 0.8528
Test loss: 0.849927025414
Test accuracy: 0.7414
再次,我们将其作为挑战,让读者探索并尝试不同的 LeNet 架构和超参数变体,以实现更高的准确率。
总结
在本章中,我们学习了如何使用 TensorFlow 和 Keras 创建卷积神经网络。我们学习了卷积和池化的核心概念,这是 CNN 的基础。我们学习了 LeNet 系列架构,并为 MNIST 和 CIFAR 数据集创建,训练和评估了 LeNet 族模型。 TensorFlow 和 Keras 提供了许多卷积和池化层和操作。鼓励读者探索本章未涉及的层和操作。
在下一章中,我们将继续学习如何使用自编码器架构将 TensorFlow 应用于图像数据。
十、TensorFlow 和 Keras 中的自编码器
自编码器是一种神经网络架构,通常与无监督学习,降维和数据压缩相关联。自编码器通过使用隐藏层中较少数量的神经元来学习产生与输入层相同的输出。这允许隐藏层以较少数量的参数学习输入的特征。使用较少数量的神经元来学习输入数据的特征的这个过程反过来减少了输入数据集的维度。
自编码器架构有两个阶段:编码器和解码器。在编码器阶段,模型学习表示具有较小维度的压缩向量的输入,并且在解码器阶段,模型学习将压缩向量表示为输出向量。损失计算为输出和输入之间的熵距离,因此通过最小化损失,我们学习将输入编码成能够产生输入的表示的参数,以及另一组学习参数。
在本章中,您将学习如何使用 TensorFlow 和 Keras 在以下主题中创建自编码器架构:
- 自编码器类型
- TensorFlow 和 Keras 中的栈式自编码器
- 在 TensorFlow 和 Keras 中对自编码器进行去噪
- TensorFlow 和 Keras 中的变分自编码器
自编码器类型
自编码器架构可以在各种配置中找到,例如简单自编码器,稀疏自编码器,去噪自编码器和卷积自编码器。
-
简单自编码器:在简单的自编码器中,与输入相比,隐藏层具有较少数量的节点或神经元。例如,在 MNIST 数据集中,784 个特征的输入可以连接到 512 个节点的隐藏层或 256 个节点,其连接到 784 特征输出层。因此,在训练期间,仅由 256 个节点学习 784 个特征。 简单自编码器也称为欠完整自编码器。
简单的自编码器可以是单层或多层。通常,单层自编码器在生产中表现不佳。多层自编码器具有多个隐藏层,分为编码器和解码器分组。编码器层将大量特征编码为较少数量的神经元,然后解码器层将学习的压缩特征解码回原始特征或减少数量的特征。多层自编码器被称为栈式自编码器 。
-
稀疏自编码器:在稀疏自编码器中,添加正则化项作为惩罚,因此,与简单自编码器相比,表示变得更稀疏。
-
去噪自编码器(DAE):在 DAE 架构中,输入带有随机噪声。 DAE 重新创建输入并尝试消除噪音。 DAE 中的损失函数将去噪重建输出与原始未损坏输入进行比较。
-
卷积自编码器(CAE):前面讨论过的自编码器使用全连接层,这种模式类似于多层感知机模型。我们也可以使用卷积层而不是完全连接或密集层。当我们使用卷积层来创建自编码器时,它被称为卷积自编码器。作为一个例子,我们可以为 CAE 提供以下层:
输入 -> 卷积 -> 池化 -> 卷积 -> 池化 -> 输出
第一组卷积和池化层充当编码器,将高维输入特征空间减少到低维特征空间。第二组卷积和池化层充当解码器,将其转换回高维特征空间。
-
变分自编码器(VAE):变分自编码器架构是自编码器领域的最新发展。 VAE 是一种生成模型,即它产生概率分布的参数,从中可以生成原始数据或与原始数据非常相似的数据。
在 VAE 中,编码器将输入样本转换为潜在空间中的参数,使用该参数对潜在点进行采样。然后解码器使用潜点重新生成原始输入数据。因此,在 VAE 中学习的重点转移到最大化输入数据的概率,而不是试图从输入重建输出。
现在让我们在以下部分中在 TensorFlow 和 Keras 中构建自编码器。我们将使用 MNIST 数据集来构建自编码器。自编码器将学习表示具有较少数量的神经元或特征的 MNIST 数据集的手写数字。
您可以按照 Jupyter 笔记本中的代码ch-10_AutoEncoders_TF_and_Keras。
像往常一样,我们首先使用以下代码读取 MNIST 数据集:
from tensorflow.examples.tutorials.mnist.input_data import input_data
dataset_home = os.path.join(datasetslib.datasets_root,'mnist')
mnist = input_data.read_data_sets(dataset_home,one_hot=False)
X_train = mnist.train.images
X_test = mnist.test.images
Y_train = mnist.train.labels
Y_test = mnist.test.labels
pixel_size = 28
我们从训练和测试数据集中提取四个不同的图像及其各自的标签:
while True:
train_images,train_labels = mnist.train.next_batch(4)
if len(set(train_labels))==4:
break
while True:
test_images,test_labels = mnist.test.next_batch(4)
if len(set(test_labels))==4:
break
现在让我们看看使用 MNIST 数据集构建自编码器的代码。
您可以按照 Jupyter 笔记本中的代码ch-10_AutoEncoders_TF_and_Keras。
TensorFlow 中的栈式自编码器
在 TensorFlow 中构建栈式自编码器模型的步骤如下:
- 首先,定义超参数如下:
learning_rate = 0.001
n_epochs = 20
batch_size = 100
n_batches = int(mnist.train.num_examples/batch_size)
- 定义输入(即特征)和输出(即目标)的数量。输出数量与输入数量相同:
# number of pixels in the MNIST image as number of inputs
n_inputs = 784
n_outputs = n_inputs
- 定义输入和输出图像的占位符:
x = tf.placeholder(dtype=tf.float32, name="x", shape=[None, n_inputs])
y = tf.placeholder(dtype=tf.float32, name="y", shape=[None, n_outputs])
- 添加编码器和解码器层的神经元数量为
[512,256,256,512]:
# number of hidden layers
n_layers = 2
# neurons in each hidden layer
n_neurons = [512,256]
# add number of decoder layers:
n_neurons.extend(list(reversed(n_neurons)))
n_layers = n_layers * 2
- 定义
w和b参数:
w=[]
b=[]
for i in range(n_layers):
w.append(tf.Variable(tf.random_normal([n_inputs \
if i==0 else n_neurons[i-1],n_neurons[i]]),
name="w_{0:04d}".format(i)
)
)
b.append(tf.Variable(tf.zeros([n_neurons[i]]),
name="b_{0:04d}".format(i)
)
)
w.append(tf.Variable(tf.random_normal([n_neurons[n_layers-1] \
if n_layers > 0 else n_inputs,n_outputs]),
name="w_out"
)
)
b.append(tf.Variable(tf.zeros([n_outputs]),name="b_out"))
- 构建网络并为每个层使用 sigmoid 激活函数:
# x is input layer
layer = x
# add hidden layers
for i in range(n_layers):
layer = tf.nn.sigmoid(tf.matmul(layer, w[i]) + b[i])
# add output layer
layer = tf.nn.sigmoid(tf.matmul(layer, w[n_layers]) + b[n_layers])
model = layer
- 使用
mean_squared_error定义loss函数,使用AdamOptimizer定义optimizer函数:
mse = tf.losses.mean_squared_error
loss = mse(predictions=model, labels=y)
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
optimizer = optimizer.minimize(loss)
- 训练模型并预测
train和test集的图像:
with tf.Session() as tfs:
tf.global_variables_initializer().run()
for epoch in range(n_epochs):
epoch_loss = 0.0
for batch in range(n_batches):
X_batch, _ = mnist.train.next_batch(batch_size)
feed_dict={x: X_batch,y: X_batch}
_,batch_loss = tfs.run([optimizer,loss], feed_dict)
epoch_loss += batch_loss
if (epoch%10==9) or (epoch==0):
average_loss = epoch_loss / n_batches
print('epoch: {0:04d} loss = {1:0.6f}'
.format(epoch,average_loss))
# predict images using trained autoencoder model
Y_train_pred = tfs.run(model, feed_dict={x: train_images})
Y_test_pred = tfs.run(model, feed_dict={x: test_images})
- 我们看到以下输出,因为损失在 20 个周期后显着减少:
epoch: 0000 loss = 0.156696
epoch: 0009 loss = 0.091367
epoch: 0019 loss = 0.078550
- 现在模型已经过训练,让我们显示训练模型中的预测图像。我们写了一个辅助函数
display_images来帮助我们显示图像:
import random
# Function to display the images and labels
# images should be in NHW or NHWC format
def display_images(images, labels, count=0, one_hot=False):
# if number of images to display is not provided, then display all the images
if (count==0):
count = images.shape[0]
idx_list = random.sample(range(len(labels)),count)
for i in range(count):
plt.subplot(4, 4, i+1)
plt.title(labels[i])
plt.imshow(images[i])
plt.axis('off')
plt.tight_layout()
plt.show()
使用此函数,我们首先显示训练集中的四个图像和自编码器预测的图像。
第一行表示实际图像,第二行表示生成的图像:
生成的图像有一点点噪音,可以通过更多训练和超参数调整来消除。现在预测训练集图像并不神奇,因为我们在这些图像上训练了自编码器,因此它知道它们。让我们看一下预测测试集图像的结果。 第一行表示实际图像,第二行表示生成的图像:
哇!经过训练的自编码器能够生成相同的数字,只有从 768 中学到的 256 个特征。生成的图像中的噪声可以通过超参数调整和更多训练来改善。
Keras 中的栈式自编码器
现在让我们在 Keras 中构建相同的自编码器。
我们使用以下命令清除笔记本中的图,以便我们可以构建一个新图,该图不会占用上一个会话或图中的任何内存:
tf.reset_default_graph()
keras.backend.clear_session()
- 首先,我们导入 keras 库并定义超参数和层:
import keras
from keras.layers import Dense
from keras.models import Sequential
learning_rate = 0.001
n_epochs = 20
batch_size = 100
n_batches = int(mnist.train.num_examples/batch_sizee
# number of pixels in the MNIST image as number of inputs
n_inputs = 784
n_outputs = n_i
# number of hidden layers
n_layers = 2
# neurons in each hidden layer
n_neurons = [512,256]
# add decoder layers:
n_neurons.extend(list(reversed(n_neurons)))
n_layers = n_layers * 2
- 接下来,我们构建一个顺序模型并为其添加密集层。对于更改,我们对隐藏层使用
relu激活,为最终层使用linear激活:
model = Sequential()
# add input to first layer
model.add(Dense(units=n_neurons[0], activation='relu',
input_shape=(n_inputs,)))
for i in range(1,n_layers):
model.add(Dense(units=n_neurons[i], activation='relu'))
# add last layer as output layer
model.add(Dense(units=n_outputs, activation='linear'))
- 现在让我们显示模型摘要以查看模型的外观:
model.summary()
该模型在五个密集层中共有 1,132,816 个参数:
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_1 (Dense) (None, 512) 401920
_________________________________________________________________
dense_2 (Dense) (None, 256) 131328
_________________________________________________________________
dense_3 (Dense) (None, 256) 65792
_________________________________________________________________
dense_4 (Dense) (None, 512) 131584
_________________________________________________________________
dense_5 (Dense) (None, 784) 402192
=================================================================
Total params: 1,132,816
Trainable params: 1,132,816
Non-trainable params: 0
_________________________________________________________________
- 让我们用上一个例子中的均方损失编译模型:
model.compile(loss='mse',
optimizer=keras.optimizers.Adam(lr=learning_rate),
metrics=['accuracy'])
model.fit(X_train, X_train,batch_size=batch_size,
epochs=n_epochs)
在 20 个周期,我们能够获得 0.0046 的损失,相比之前我们得到的 0.078550:
Epoch 1/20
55000/55000 [==========================] - 18s - loss: 0.0193 - acc: 0.0117
Epoch 2/20
55000/55000 [==========================] - 18s - loss: 0.0087 - acc: 0.0139
...
...
...
Epoch 20/20
55000/55000 [==========================] - 16s - loss: 0.0046 - acc: 0.0171
现在让我们预测并显示模型生成的训练和测试图像。第一行表示实际图像,第二行表示生成的图像。以下是 t 降雨设置图像:
以下是测试集图像:
这是我们在能够从 256 个特征生成图像时实现的非常好的准确率。
TensorFlow 中的去噪自编码器
正如您在本章的第一部分中所了解的那样,可以使用去噪自编码器来训练模型,以便它们能够从输入到训练模型的图像中去除噪声:
- 出于本示例的目的,我们编写以下辅助函数来帮助我们为图像添加噪声:
def add_noise(X):
return X + 0.5 * np.random.randn(X.shape[0],X.shape[1])
- 然后我们为测试图像添加噪声并将其存储在单独的列表中:
test_images_noisy = add_noise(test_images)
我们将使用这些测试图像来测试我们的去噪模型示例的输出。
- 我们按照前面的例子构建和训练去噪自编码器,但有一点不同:在训练时,我们将噪声图像输入到输入层,我们用非噪声图像检查重建和去噪误差,如下面的代码所示:
X_batch, _ = mnist.train.next_batch(batch_size)
X_batch_noisy = add_noise(X_batch)
feed_dict={x: X_batch_noisy, y: X_batch}
_,batch_loss = tfs.run([optimizer,loss], feed_dict=feed_dict)
笔记本ch-10_AutoEncoders_TF_and_Keras中提供了去噪自编码器的完整代码。
现在让我们首先显示从 DAE 模型生成的测试图像;第一行表示原始的非噪声测试图像,第二行表示生成的测试图像:
display_images(test_images.reshape(-1,pixel_size,pixel_size),test_labels)
display_images(Y_test_pred1.reshape(-1,pixel_size,pixel_size),test_labels)
上述代码的结果如下:
接下来,当我们输入噪声测试图像时,我们显示生成的图像:
display_images(test_images_noisy.reshape(-1,pixel_size,pixel_size),
test_labels)
display_images(Y_test_pred2.reshape(-1,pixel_size,pixel_size),test_labels)
上述代码的结果如下:
那太酷了!!该模型学习了图像并生成了几乎正确的图像,即使是非常嘈杂的图像。通过适当的超参数调整可以进一步提高再生质量。
Keras 中的去噪自编码器
现在让我们在 Keras 中构建相同的去噪自编码器。
由于 Keras 负责按批量大小喂养训练集,我们创建了一个嘈杂的训练集作为我们模型的输入:
X_train_noisy = add_noise(X_train)
Keras 中 DAE 的完整代码在笔记本ch-10_AutoEncoders_TF_and_Keras中提供。
DAE Keras 模型如下所示:
Layer (type) Output Shape Param #
=================================================================
dense_1 (Dense) (None, 512) 401920
_________________________________________________________________
dense_2 (Dense) (None, 256) 131328
_________________________________________________________________
dense_3 (Dense) (None, 256) 65792
_________________________________________________________________
dense_4 (Dense) (None, 512) 131584
_________________________________________________________________
dense_5 (Dense) (None, 784) 402192
=================================================================
Total params: 1,132,816
Trainable params: 1,132,816
Non-trainable params: 0
由于 DAE 模型很复杂,为了演示,我们不得不将周期数增加到 100 来训练模型:
n_epochs=100
model.fit(x=X_train_noisy, y=X_train,
batch_size=batch_size,
epochs=n_epochs,
verbose=0)
Y_test_pred1 = model.predict(test_images)
Y_test_pred2 = model.predict(test_images_noisy)
打印生成的图像:
display_images(test_images.reshape(-1,pixel_size,pixel_size),test_labels)
display_images(Y_test_pred1.reshape(-1,pixel_size,pixel_size),test_labels)
第一行是原始测试图像,第二行是生成的测试图像:
display_images(test_images_noisy.reshape(-1,pixel_size,pixel_size),
test_labels)
display_images(Y_test_pred2.reshape(-1,pixel_size,pixel_size),test_labels)
第一行是噪声测试图像,第二行是生成的测试图像:
正如我们所看到的,去噪自编码器可以很好地从噪声版本的图像中生成图像。
TensorFlow 中的变分自编码器
变分自编码器是自编码器的现代生成版本。让我们为同一个前面的问题构建一个变分自编码器。我们将通过提供来自原始和嘈杂测试集的图像来测试自编码器。
我们将使用不同的编码风格来构建此自编码器,以便使用 TensorFlow 演示不同的编码风格:
- 首先定义超参数:
learning_rate = 0.001
n_epochs = 20
batch_size = 100
n_batches = int(mnist.train.num_examples/batch_size)
# number of pixels in the MNIST image as number of inputs
n_inputs = 784
n_outputs = n_inputs
- 接下来,定义参数字典以保存权重和偏差参数:
params={}
- 定义每个编码器和解码器中隐藏层的数量:
n_layers = 2
# neurons in each hidden layer
n_neurons = [512,256]
- 变分编码器中的新增加是我们定义潜变量
z的维数:
n_neurons_z = 128 # the dimensions of latent variables
- 我们使用激活
tanh:
activation = tf.nn.tanh
- 定义输入和输出占位符:
x = tf.placeholder(dtype=tf.float32, name="x",
shape=[None, n_inputs])
y = tf.placeholder(dtype=tf.float32, name="y",
shape=[None, n_outputs])
- 定义输入层:
# x is input layer
layer = x
- 定义编码器网络的偏差和权重并添加层。变分自编码器的编码器网络也称为识别网络或推理网络或概率编码器网络:
for i in range(0,n_layers):
name="w_e_{0:04d}".format(i)
params[name] = tf.get_variable(name=name,
shape=[n_inputs if i==0 else n_neurons[i-1],
n_neurons[i]],
initializer=tf.glorot_uniform_initializer()
)
name="b_e_{0:04d}".format(i)
params[name] = tf.Variable(tf.zeros([n_neurons[i]]),
name=name
)
layer = activation(tf.matmul(layer,
params["w_e_{0:04d}".format(i)]
) + params["b_e_{0:04d}".format(i)]
)
- 接下来,添加潜在变量的均值和方差的层:
name="w_e_z_mean"
params[name] = tf.get_variable(name=name,
shape=[n_neurons[n_layers-1], n_neurons_z],
initializer=tf.glorot_uniform_initializer()
)
name="b_e_z_mean"
params[name] = tf.Variable(tf.zeros([n_neurons_z]),
name=name
)
z_mean = tf.matmul(layer, params["w_e_z_mean"]) +
params["b_e_z_mean"]
name="w_e_z_log_var"
params[name] = tf.get_variable(name=name,
shape=[n_neurons[n_layers-1], n_neurons_z],
initializer=tf.glorot_uniform_initializer()
)
name="b_e_z_log_var"
params[name] = tf.Variable(tf.zeros([n_neurons_z]),
name="b_e_z_log_var"
)
z_log_var = tf.matmul(layer, params["w_e_z_log_var"]) +
params["b_e_z_log_var"]
- 接下来,定义表示与
z方差的变量相同形状的噪声分布的epsilon变量:
epsilon = tf.random_normal(tf.shape(z_log_var),
mean=0,
stddev=1.0,
dtype=tf.float32,
name='epsilon'
)
- 根据均值,对数方差和噪声定义后验分布:
z = z_mean + tf.exp(z_log_var * 0.5) * epsilon
- 接下来,定义解码器网络的权重和偏差,并添加解码器层。变分自编码器中的解码器网络也称为概率解码器或生成器网络。
# add generator / probablistic decoder network parameters and layers
layer = z
for i in range(n_layers-1,-1,-1):
name="w_d_{0:04d}".format(i)
params[name] = tf.get_variable(name=name,
shape=[n_neurons_z if i==n_layers-1 else n_neurons[i+1],
n_neurons[i]],
initializer=tf.glorot_uniform_initializer()
)
name="b_d_{0:04d}".format(i)
params[name] = tf.Variable(tf.zeros([n_neurons[i]]),
name=name
)
layer = activation(tf.matmul(layer, params["w_d_{0:04d}".format(i)]) +
params["b_d_{0:04d}".format(i)])
- 最后,定义输出层:
name="w_d_z_mean"
params[name] = tf.get_variable(name=name,
shape=[n_neurons[0],n_outputs],
initializer=tf.glorot_uniform_initializer()
)
name="b_d_z_mean"
params[name] = tf.Variable(tf.zeros([n_outputs]),
name=name
)
name="w_d_z_log_var"
params[name] = tf.Variable(tf.random_normal([n_neurons[0],
n_outputs]),
name=name
)
name="b_d_z_log_var"
params[name] = tf.Variable(tf.zeros([n_outputs]),
name=name
)
layer = tf.nn.sigmoid(tf.matmul(layer, params["w_d_z_mean"]) +
params["b_d_z_mean"])
model = layer
- 在变异自编码器中,我们有重建损失和正则化损失。将损失函数定义为重建损失和正则化损失的总和:
rec_loss = -tf.reduce_sum(y * tf.log(1e-10 + model) + (1-y)
* tf.log(1e-10 + 1 - model), 1)
reg_loss = -0.5*tf.reduce_sum(1 + z_log_var - tf.square(z_mean)
- tf.exp(z_log_var), 1)
loss = tf.reduce_mean(rec_loss+reg_loss)
- 根据
AdapOptimizer定义优化器函数:
optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)
.minimize(loss)
- 现在让我们训练模型并从非噪声和噪声测试图像生成图像:
with tf.Session() as tfs:
tf.global_variables_initializer().run()
for epoch in range(n_epochs):
epoch_loss = 0.0
for batch in range(n_batches):
X_batch, _ = mnist.train.next_batch(batch_size)
feed_dict={x: X_batch,y: X_batch}
_,batch_loss = tfs.run([optimizer,loss],
feed_dict=feed_dict)
epoch_loss += batch_loss
if (epoch%10==9) or (epoch==0):
average_loss = epoch_loss / n_batches
print("epoch: {0:04d} loss = {1:0.6f}"
.format(epoch,average_loss))
# predict images using autoencoder model trained
Y_test_pred1 = tfs.run(model, feed_dict={x: test_images})
Y_test_pred2 = tfs.run(model, feed_dict={x: test_images_noisy})
我们得到以下输出:
epoch: 0000 loss = 180.444682
epoch: 0009 loss = 106.817749
epoch: 0019 loss = 102.580904
现在让我们显示图像:
display_images(test_images.reshape(-1,pixel_size,pixel_size),test_labels)
display_images(Y_test_pred1.reshape(-1,pixel_size,pixel_size),test_labels)
结果如下:
display_images(test_images_noisy.reshape(-1,pixel_size,pixel_size),
test_labels)
display_images(Y_test_pred2.reshape(-1,pixel_size,pixel_size),test_labels)
结果如下:
同样,可以通过超参数调整和增加学习量来改善结果。
Keras 中的变分自编码器
在 Keras 中,构建变分自编码器更容易,并且代码行更少。 Keras 变分自编码器最好使用函数式风格构建。到目前为止,我们已经使用了在 Keras 中构建模型的顺序样式,现在在这个例子中,我们将看到在 Keras 中构建 VAE 模型的函数式风格。在 Keras 建立 VAE 的步骤如下:
- 定义隐藏层和潜在变量层中的超参数和神经元数量:
import keras
from keras.layers import Lambda, Dense, Input, Layer
from keras.models import Model
from keras import backend as K
learning_rate = 0.001
batch_size = 100
n_batches = int(mnist.train.num_examples/batch_size)
# number of pixels in the MNIST image as number of inputs
n_inputs = 784
n_outputs = n_inputs
# number of hidden layers
n_layers = 2
# neurons in each hidden layer
n_neurons = [512,256]
# the dimensions of latent variables
n_neurons_z = 128
- 构建输入层:
x = Input(shape=(n_inputs,), name='input')
- 构建编码器层,以及潜在变量的均值和方差层:
# build encoder
layer = x
for i in range(n_layers):
layer = Dense(units=n_neurons[i], activation='relu',name='enc_{0}'.format(i))(layer)
z_mean = Dense(units=n_neurons_z,name='z_mean')(layer)
z_log_var = Dense(units=n_neurons_z,name='z_log_v')(layer)
- 创建噪声和后验分布:
# noise distribution
epsilon = K.random_normal(shape=K.shape(z_log_var),
mean=0,stddev=1.0)
# posterior distribution
z = Lambda(lambda zargs: zargs[0] + K.exp(zargs[1] * 0.5) * epsilon,
name='z')([z_mean,z_log_var])
- 添加解码器层:
# add generator / probablistic decoder network layers
layer = z
for i in range(n_layers-1,-1,-1):
layer = Dense(units=n_neurons[i], activation='relu',
name='dec_{0}'.format(i))(layer)
- 定义最终输出层:
y_hat = Dense(units=n_outputs, activation='sigmoid',
name='output')(layer)
- 最后,从输入层和输出层定义模型并显示模型摘要:
model = Model(x,y_hat)
model.summary()
我们看到以下摘要:
_________________________________________________________________________
Layer (type) Output Shape Param # Connected to
=========================================================================
input (InputLayer) (None, 784) 0
_________________________________________________________________________
enc_0 (Dense) (None, 512) 401920 input[0][0]
_________________________________________________________________________
enc_1 (Dense) (None, 256) 131328 enc_0[0][0]
_________________________________________________________________________
z_mean (Dense) (None, 128) 32896 enc_1[0][0]
_________________________________________________________________________
z_log_v (Dense) (None, 128) 32896 enc_1[0][0]
_________________________________________________________________________
z (Lambda) (None, 128) 0 z_mean[0][0]
z_log_v[0][0]
_________________________________________________________________________
dec_1 (Dense) (None, 256) 33024 z[0][0]
_________________________________________________________________________
dec_0 (Dense) (None, 512) 131584 dec_1[0][0]
_________________________________________________________________________
output (Dense) (None, 784) 402192 dec_0[0][0]
=========================================================================
Total params: 1,165,840
Trainable params: 1,165,840
Non-trainable params: 0
_________________________________________________________________________
- 定义一个计算重建和正则化损失之和的函数:
def vae_loss(y, y_hat):
rec_loss = -K.sum(y * K.log(1e-10 + y_hat) + (1-y) *
K.log(1e-10 + 1 - y_hat), axis=-1)
reg_loss = -0.5 * K.sum(1 + z_log_var - K.square(z_mean) -
K.exp(z_log_var), axis=-1)
loss = K.mean(rec_loss+reg_loss)
return loss
- 使用此损失函数来编译模型:
model.compile(loss=vae_loss,
optimizer=keras.optimizers.Adam(lr=learning_rate))
- 让我们训练 50 个周期的模型并预测图像,正如我们在前面的部分中所做的那样:
n_epochs=50
model.fit(x=X_train_noisy,y=X_train,batch_size=batch_size,
epochs=n_epochs,verbose=0)
Y_test_pred1 = model.predict(test_images)
Y_test_pred2 = model.predict(test_images_noisy)
让我们显示结果图像:
display_images(test_images.reshape(-1,pixel_size,pixel_size),test_labels)
display_images(Y_test_pred1.reshape(-1,pixel_size,pixel_size),test_labels)
我们得到如下结果:
display_images(test_images_noisy.reshape(-1,pixel_size,pixel_size),
test_labels)
display_images(Y_test_pred2.reshape(-1,pixel_size,pixel_size),test_labels)
我们得到以下结果:
这很棒!!生成的图像更清晰,更清晰。
总结
自编码器是无监督数据学习的绝佳工具。它们通常用于降低维数,因此数据可以用较少数量的特征来表示。在本章中,您了解了各种类型的自编码器。我们使用 TensorFlow 和 Keras 练习构建三种类型的自编码器:栈式自编码器,去噪自编码器和变分自编码器。我们使用 MNIST 数据集作为示例。
在最后的章节中,您学习了如何使用 TensorFlow 和 Keras 构建各种机器学习和深度学习模型,例如回归,分类,MLP,CNN,RNN 和自编码器。在下一章中,您将了解 TensorFlow 和 Keras 的高级功能,这些功能允许我们将模型投入生产。
十一、TF 服务:生产中的 TensorFlow 模型
TensorFlow 模型在开发环境中经过训练和验证。一旦发布,它们需要托管在某个地方,提供用工程师和软件工程师使用,以集成到各种应用中。 TensorFlow 为此提供了一个高表现服务器,称为 TensorFlow 服务。
要在生产中提供 TensorFlow 模型,需要在离线训练后保存它们,然后在生产环境中恢复经过训练的模型。 TensorFlow 模型在保存时包含以下文件:
- 元图:元图表示图的协议缓冲区定义。元图保存在具有
.meta扩展名的文件中。 - 检查点:检查点代表各种变量的值。检查点保存在两个文件中:一个带有
.index扩展名,另一个带有.data-00000-of-00001扩展名。
在本章中,我们将学习各种保存和恢复模型的方法以及如何使用 TF 服务来提供模型。我们将使用 MNIST 示例来简化操作并涵盖以下主题:
- 使用
Saver类在 TensorFlow 中保存和恢复模型 - 保存和恢复 Keras 模型
- TensorFlow 服务
- 安装 TF 服务
- 为 TF 服务保存模型
- 用 TF 服务来提供模型
- 在 Docker 容器中提供 TF 服务
- Kubernetes 上的 TF 服务
在 TensorFlow 中保存和恢复模型
您可以通过以下两种方法之一在 TensorFlow 中保存和恢复模型和变量:
- 从
tf.train.Saver类创建的保存器对象 - 从
tf.saved_model_builder.SavedModelBuilder类创建的基于SavedModel格式的对象
让我们看看两种方法的实际应用。
您可以按照 Jupyter 笔记本中的代码ch-11a_Saving_and_Restoring_TF_Models。
使用保存器类保存和恢复所有图变量
我们进行如下:
- 要使用
saver类,首先要创建此类的对象:
saver = tf.train.Saver()
- 保存图中所有变量的最简单方法是使用以下两个参数调用
save()方法:会话对象和磁盘上保存变量的文件的路径:
with tf.Session() as tfs:
...
saver.save(tfs,"saved-models/model.ckpt")
- 要恢复变量,调用
restore()方法:
with tf.Session() as tfs:
saver.restore(tfs,"saved-models/model.ckpt")
...
- 让我们重温一下第 1 章,TensorFlow 101 的例子,在简单的例子中保存变量的代码如下:
# Assume Linear Model y = w * x + b
# Define model parameters
w = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
# Define model input and output
x = tf.placeholder(tf.float32)
y = w * x + b
output = 0
# create saver object
saver = tf.train.Saver()
with tf.Session() as tfs:
# initialize and print the variable y
tfs.run(tf.global_variables_initializer())
output = tfs.run(y,{x:[1,2,3,4]})
saved_model_file = saver.save(tfs,
'saved-models/full-graph-save-example.ckpt')
print('Model saved in {}'.format(saved_model_file))
print('Values of variables w,b: {}{}'
.format(w.eval(),b.eval()))
print('output={}'.format(output))
我们得到以下输出:
Model saved in saved-models/full-graph-save-example.ckpt
Values of variables w,b: [ 0.30000001][-0.30000001]
output=[ 0\. 0.30000001 0.60000002 0.90000004]
- 现在让我们从刚刚创建的检查点文件中恢复变量:
# Assume Linear Model y = w * x + b
# Define model parameters
w = tf.Variable([0], dtype=tf.float32)
b = tf.Variable([0], dtype=tf.float32)
# Define model input and output
x = tf.placeholder(dtype=tf.float32)
y = w * x + b
output = 0
# create saver object
saver = tf.train.Saver()
with tf.Session() as tfs:
saved_model_file = saver.restore(tfs,
'saved-models/full-graph-save-example.ckpt')
print('Values of variables w,b: {}{}'
.format(w.eval(),b.eval()))
output = tfs.run(y,{x:[1,2,3,4]})
print('output={}'.format(output))
您会注意到在恢复代码中我们没有调用tf.global_variables_initializer(),因为不需要初始化变量,因为它们将从文件中恢复。我们得到以下输出,它是根据恢复的变量计算得出的:
INFO:tensorflow:Restoring parameters from saved-models/full-graph-save-example.ckpt
Values of variables w,b: [ 0.30000001][-0.30000001]
output=[ 0\. 0.30000001 0.60000002 0.90000004]
使用保存器类保存和恢复所选变量
默认情况下,Saver()类将所有变量保存在图中,但您可以通过将变量列表传递给Saver()类的构造器来选择要保存的变量:
# create saver object
saver = tf.train.Saver({'weights': w})
变量名称可以作为列表或字典传递。如果变量名称作为列表传递,则列表中的每个变量将以其自己的名称保存。变量也可以作为由键值对组成的字典传递,其中键是用于保存的名称,值是要保存的变量的名称。
以下是我们刚看到的示例的代码,但这次我们只保存w变量的权重;保存时将其命名为weights:
# Saving selected variables in a graph in TensorFlow
# Assume Linear Model y = w * x + b
# Define model parameters
w = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
# Define model input and output
x = tf.placeholder(tf.float32)
y = w * x + b
output = 0
# create saver object
saver = tf.train.Saver({'weights': w})
with tf.Session() as tfs:
# initialize and print the variable y
tfs.run(tf.global_variables_initializer())
output = tfs.run(y,{x:[1,2,3,4]})
saved_model_file = saver.save(tfs,
'saved-models/weights-save-example.ckpt')
print('Model saved in {}'.format(saved_model_file))
print('Values of variables w,b: {}{}'
.format(w.eval(),b.eval()))
print('output={}'.format(output))
我们得到以下输出:
Model saved in saved-models/weights-save-example.ckpt
Values of variables w,b: [ 0.30000001][-0.30000001]
output=[ 0\. 0.30000001 0.60000002 0.90000004]
检查点文件仅保存权重而不是偏差。现在让我们将偏差和权重初始化为零,并恢复权重。此示例的代码在此处给出:
# Restoring selected variables in a graph in TensorFlow
tf.reset_default_graph()
# Assume Linear Model y = w * x + b
# Define model parameters
w = tf.Variable([0], dtype=tf.float32)
b = tf.Variable([0], dtype=tf.float32)
# Define model input and output
x = tf.placeholder(dtype=tf.float32)
y = w * x + b
output = 0
# create saver object
saver = tf.train.Saver({'weights': w})
with tf.Session() as tfs:
b.initializer.run()
saved_model_file = saver.restore(tfs,
'saved-models/weights-save-example.ckpt')
print('Values of variables w,b: {}{}'
.format(w.eval(),b.eval()))
output = tfs.run(y,{x:[1,2,3,4]})
print('output={}'.format(output))
如您所见,这次我们必须使用b.initializer.run()初始化偏差。我们不使用tfs.run(tf.global_variables_initializer())因为它会初始化所有变量,并且不需要初始化权重,因为它们将从检查点文件中恢复。
我们得到以下输出,因为计算仅使用恢复的权重,而偏差设置为零:
INFO:tensorflow:Restoring parameters from saved-models/weights-save-example.ckpt
Values of variables w,b: [ 0.30000001][ 0.]
output=[ 0.30000001 0.60000002 0.90000004 1.20000005]
保存和恢复 Keras 模型
在 Keras 中,保存和恢复模型非常简单。 Keras 提供三种选择:
- 使用其网络架构,权重(参数),训练配置和优化器状态保存完整模型。
- 仅保存架构。
- 仅保存权重。
要保存完整模型,请使用model.save(filepath)函数。这将把完整的模型保存在 HDF5 文件中。可以使用keras.models.load_model(filepath)函数加载保存的模型。此函数将所有内容加载回来,然后还编译模型。
要保存模型的架构,请使用model.to_json()或model.to_yaml()函数。这些函数返回一个可以写入磁盘文件的字符串。在恢复架构时,可以回读字符串,并使用keras.models.model_from_json(json_string)或keras.models.model_from_yaml(yaml_string)函数恢复模型架构。这两个函数都返回一个模型实例。
要保存模型的权重,请使用model.save_weights(path_to_h5_file)函数。可以使用model.load_weights(path_to_h5_file)函数恢复权重。
TensorFlow 服务
TensorFlow 服务(TFS)是一种高表现服务器架构,用于为生产中的机器学习模型提供服务。它提供与使用 TensorFlow 构建的模型的开箱即用集成。
在 TFS 中,模型由一个或多个可服务对象组成。 可服务对象用于执行计算,例如:
- 用于嵌入查找的查找表
- 返回预测的单个模型
- 返回一组预测的一组模型
- 查找表或模型的分片
管理器组件管理可服务对象的整个生命周期,包括加载/卸载可服务对象并提供可服务对象。
TensorFlow 服务的内部架构和工作流程在此链接中描述。
安装 TF 服务
按照本节中的说明使用aptitude在 Ubuntu 上安装 TensorFlow ModelServer。
- 首先,在 shell 提示符下使用以下命令添加 TensorFlow 服务分发 URI 作为包源(一次性设置):
$ echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" | sudo tee /etc/apt/sources.list.d/tensorflow-serving.list
$ curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg | sudo apt-key add -
- 在 shell 提示符下使用以下命令安装和更新 TensorFlow ModelServer:
$ sudo apt-get update && sudo apt-get install tensorflow-model-server
这将安装使用特定于平台的编译器优化的 ModelServer 版本,例如使用 SSE4 和 AVX 指令。但是,如果优化版本安装在旧计算机上不起作用,则可以安装通用版本:
$ sudo apt-get remove tensorflow-model-server
$ sudo apt-get update && sudo apt-get install tensorflow-model-server-universal
对于其他操作系统以及从源安装,请参阅此链接。
发布新版本的 ModelServer 时,可以使用以下命令升级到较新版本:
$ sudo apt-get update && sudo apt-get upgrade tensorflow-model-server
- 现在已安装 ModelServer,使用以下命令运行服务器:
$ tensorflow-model-server
- 要连接到
tensorflow-model-server,请使用 PIP 安装 python 客户端包:
$ sudo pip2 install tensorflow-serving-api
TF 服务 API 仅适用于 Python 2,但尚不适用于 Python 3。
为 TF 服务保存模型
为了服务模型,需要先保存它们。在本节中,我们将从官方 TensorFlow 文档中演示 MNIST 示例的略微修改版本,可从此链接获得。
TensorFlow 团队建议使用 SavedModel 来保存和恢复在 TensorFlow 中构建和训练的模型。根据 TensorFlow 文档:
SavedModel 是一种语言中立的,可恢复的,密集的序列化格式。SavedModel 支持更高级别的系统和工具来生成,使用和转换 TensorFlow 模型。您可以按照 Jupyter 笔记本中的代码ch-11b_Saving_TF_Models_with_SavedModel_for_TF_Serving。我们按照以下方式继续保存模型:
- 定义模型变量:
model_name = 'mnist'
model_version = '1'
model_dir = os.path.join(models_root,model_name,model_version)
- 像我们在第 4 章中所做的那样获取 MNIST 数据 - MLP 模型:
from tensorflow.examples.tutorials.mnist import input_data
dataset_home = os.path.join('.','mnist')
mnist = input_data.read_data_sets(dataset_home, one_hot=True)
x_train = mnist.train.images
x_test = mnist.test.images
y_train = mnist.train.labels
y_test = mnist.test.labels
pixel_size = 28
num_outputs = 10 # 0-9 digits
num_inputs = 784 # total pixels
- 定义将构建并返回模型的 MLP 函数:
def mlp(x, num_inputs, num_outputs,num_layers,num_neurons):
w=[]
b=[]
for i in range(num_layers):
w.append(tf.Variable(tf.random_normal(
[num_inputs if i==0 else num_neurons[i-1],
num_neurons[i]]),name="w_{0:04d}".format(i)
)
)
b.append(tf.Variable(tf.random_normal(
[num_neurons[i]]),
name="b_{0:04d}".format(i)
)
)
w.append(tf.Variable(tf.random_normal(
[num_neurons[num_layers-1] if num_layers > 0 \
else num_inputs, num_outputs]),name="w_out"))
b.append(tf.Variable(tf.random_normal([num_outputs]),
name="b_out"))
# x is input layer
layer = x
# add hidden layers
for i in range(num_layers):
layer = tf.nn.relu(tf.matmul(layer, w[i]) + b[i])
# add output layer
layer = tf.matmul(layer, w[num_layers]) + b[num_layers]
model = layer
probs = tf.nn.softmax(model)
return model,probs
上述mlp()函数返回模型和概率。概率是应用于模型的 softmax 激活。
- 为图像输入和目标输出定义
x_p和y_p占位符:
# input images
serialized_tf_example = tf.placeholder(tf.string,
name='tf_example')
feature_configs = {'x': tf.FixedLenFeature(shape=[784],
dtype=tf.float32),}
tf_example = tf.parse_example(serialized_tf_example,
feature_configs)
# use tf.identity() to assign name
x_p = tf.identity(tf_example['x'], name='x_p')
# target output
y_p = tf.placeholder(dtype=tf.float32, name="y_p",
shape=[None, num_outputs])
- 创建模型,以及损失,优化器,准确率和训练函数:
num_layers = 2
num_neurons = []
for i in range(num_layers):
num_neurons.append(256)
learning_rate = 0.01
n_epochs = 50
batch_size = 100
n_batches = mnist.train.num_examples//batch_size
model,probs = mlp(x=x_p,
num_inputs=num_inputs,
num_outputs=num_outputs,
num_layers=num_layers,
num_neurons=num_neurons)
loss_op = tf.nn.softmax_cross_entropy_with_logits
loss = tf.reduce_mean(loss_op(logits=model, labels=y_p))
optimizer = tf.train.GradientDescentOptimizer(learning_rate)
train_op = optimizer.minimize(loss)
pred_check = tf.equal(tf.argmax(probs,1), tf.argmax(y_p,1))
accuracy_op = tf.reduce_mean(tf.cast(pred_check, tf.float32))
values, indices = tf.nn.top_k(probs, 10)
table = tf.contrib.lookup.index_to_string_table_from_tensor(
tf.constant([str(i) for i in range(10)]))
prediction_classes = table.lookup(tf.to_int64(indices))
- 在 TensorFlow 会话中,像以前一样训练模型,但使用构建器对象来保存模型:
from tf.saved_model.signature_constants import \
CLASSIFY_INPUTS
from tf.saved_model.signature_constants import \
CLASSIFY_OUTPUT_CLASSES
from tf.saved_model.signature_constants import \
CLASSIFY_OUTPUT_SCORES
from tf.saved_model.signature_constants import \
CLASSIFY_METHOD_NAME
from tf.saved_model.signature_constants import \
PREDICT_METHOD_NAME
from tf.saved_model.signature_constants import \
DEFAULT_SERVING_SIGNATURE_DEF_KEY
with tf.Session() as tfs:
tfs.run(tf.global_variables_initializer())
for epoch in range(n_epochs):
epoch_loss = 0.0
for batch in range(n_batches):
x_batch, y_batch = mnist.train.next_batch(batch_size)
feed_dict = {x_p: x_batch, y_p: y_batch}
_,batch_loss = tfs.run([train_op,loss],
feed_dict=feed_dict)
epoch_loss += batch_loss
average_loss = epoch_loss / n_batches
print("epoch: {0:04d} loss = {1:0.6f}" .format(epoch,average_loss))
feed_dict={x_p: x_test, y_p: y_test}
accuracy_score = tfs.run(accuracy_op, feed_dict=feed_dict)
print("accuracy={0:.8f}".format(accuracy_score))
# save the model
# definitions for saving the models builder = tf.saved_model.builder.SavedModelBuilder(model_dir)
# build signature_def_map
bti_op = tf.saved_model.utils.build_tensor_info
bsd_op = tf.saved_model.utils.build_signature_def
classification_inputs = bti_op(serialized_tf_example)
classification_outputs_classes = bti_op(prediction_classes)
classification_outputs_scores = bti_op(values)
classification_signature = (bsd_op(
inputs={CLASSIFY_INPUTS: classification_inputs},
outputs={CLASSIFY_OUTPUT_CLASSES:
classification_outputs_classes,
CLASSIFY_OUTPUT_SCORES:
classification_outputs_scores
},
method_name=CLASSIFY_METHOD_NAME))
tensor_info_x = bti_op(x_p)
tensor_info_y = bti_op(probs)
prediction_signature = (bsd_op(
inputs={'inputs': tensor_info_x},
outputs={'outputs': tensor_info_y},
method_name=PREDICT_METHOD_NAME))
legacy_init_op = tf.group(tf.tables_initializer(),
name='legacy_init_op')
builder.add_meta_graph_and_variables(
tfs, [tf.saved_model.tag_constants.SERVING],
signature_def_map={
'predict_images':prediction_signature,
DEFAULT_SERVING_SIGNATURE_DEF_KEY:
classification_signature,
},
legacy_init_op=legacy_init_op)
builder.save()
一旦看到以下输出,就会保存模型:
accuracy=0.92979997
INFO:tensorflow:No assets to save.
INFO:tensorflow:No assets to write.
INFO:tensorflow:SavedModel written to: b'/home/armando/models/mnist/1/saved_model.pb'
接下来,我们运行 ModelServer 并提供刚刚保存的模型。
使用 TF 服务提供模型
要运行 ModelServer,请执行以下命令:
$ tensorflow_model_server --model_name=mnist --model_base_path=/home/armando/models/mnist
服务器开始在端口 8500 上提供模型:
I tensorflow_serving/model_servers/main.cc:147] Building single TensorFlow model file config: model_name: mnist model_base_path: /home/armando/models/mnist
I tensorflow_serving/model_servers/server_core.cc:441] Adding/updating models.
I tensorflow_serving/model_servers/server_core.cc:492] (Re-)adding model: mnist
I tensorflow_serving/core/basic_manager.cc:705] Successfully reserved resources to load servable {name: mnist version: 1}
I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: mnist version: 1}
I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: mnist version: 1}
I external/org_tensorflow/tensorflow/contrib/session_bundle/bundle_shim.cc:360] Attempting to load native SavedModelBundle in bundle-shim from: /home/armando/models/mnist/1
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:236] Loading SavedModel from: /home/armando/models/mnist/1
I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:155] Restoring SavedModel bundle.
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running LegacyInitOp on SavedModel bundle.
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:284] Loading SavedModel: success. Took 29853 microseconds.
I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: mnist version: 1}
E1121 ev_epoll1_linux.c:1051] grpc epoll fd: 3
I tensorflow_serving/model_servers/main.cc:288] Running ModelServer at 0.0.0.0:8500 ...
要通过调用模型对图像进行分类来测试服务器,请按照笔记本ch-11c_TF_Serving_MNIST进行操作。
笔记本电脑的前两个单元提供了服务仓库中 TensorFlow 官方示例的测试客户端功能。我们修改了示例以发送'input'并在函数签名中接收'output'以调用 ModelServer。
使用以下代码调用笔记本的第三个单元中的测试客户端函数:
error_rate = do_inference(hostport='0.0.0.0:8500',
work_dir='/home/armando/datasets/mnist',
concurrency=1,
num_tests=100)
print('\nInference error rate: %s%%' % (error_rate * 100))
我们得到差不多 7% 的错误率! (您可能会得到不同的值):
Extracting /home/armando/datasets/mnist/train-images-idx3-ubyte.gz
Extracting /home/armando/datasets/mnist/train-labels-idx1-ubyte.gz
Extracting /home/armando/datasets/mnist/t10k-images-idx3-ubyte.gz
Extracting /home/armando/datasets/mnist/t10k-labels-idx1-ubyte.gz
..................................................
..................................................
Inference error rate: 7.0%
在 Docker 容器中提供 TF 服务
Docker 是一个用于在容器中打包和部署应用的平台。如果您还不知道 Docker 容器,请访问此链接中的教程和信息 。
我们还可以在 Docker 容器中安装和运行 TensorFlow 服务。本节中提供的 Ubuntu 16.04 的说明源自 TensorFlow 官方网站上的链接:
让我们一起潜入!
安装 Docker
我们按如下方式安装 Docker:
- 首先,删除之前安装的 Docker:
$ sudo apt-get remove docker docker-engine docker.io
- 安装必备软件:
$ sudo apt-get install \
apt-transport-https \
ca-certificates \
curl \
software-properties-common
- 添加 Docker 仓库的 GPG 密钥:
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
- 添加 Docker 仓库:
$ sudo add-apt-repository \
"deb [arch=amd64] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) \ stable"
- 安装 Docker 社区版:
$ sudo apt-get update && sudo apt-get install docker-ce
- 安装成功完成后,将 Docker 添加为系统服务:
$ sudo systemctl enable docker
- 要以非
root用户身份运行 Docker 或不使用sudo,请添加docker组:
$ sudo groupadd docker
- 将您的用户添加到
docker组:
$ sudo usermod -aG docker $USER
- 现在注销并再次登录,以便组成员身份生效。登录后,运行以下命令来测试 Docker 安装:
$ docker run --name hello-world-container hello-world
您应该看到类似于以下内容的输出:
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Already exists
Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1\. The Docker client contacted the Docker daemon.
2\. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3\. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4\. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://cloud.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
Docker 已成功安装。现在让我们为 TensorFlow 服务构建一个 Docker 镜像。
为 TF 服务构建 Docker 镜像
我们继续使用 Docker 镜像进行如下操作:
- 使用以下内容创建名为
dockerfile的文件:
FROM ubuntu:16.04
MAINTAINER Armando Fandango <armando@geekysalsero.com>
RUN apt-get update && apt-get install -y \
build-essential \
curl \
git \
libfreetype6-dev \
libpng12-dev \
libzmq3-dev \
mlocate \
pkg-config \
python-dev \
python-numpy \
python-pip \
software-properties-common \
swig \
zip \
zlib1g-dev \
libcurl3-dev \
openjdk-8-jdk\
openjdk-8-jre-headless \
wget \
&& \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
RUN echo "deb [arch=amd64] http://storage.googleapis.com/tensorflow-serving-apt stable tensorflow-model-server tensorflow-model-server-universal" \
| tee /etc/apt/sources.list.d/tensorflow-serving.list
RUN curl https://storage.googleapis.com/tensorflow-serving-apt/tensorflow-serving.release.pub.gpg \
| apt-key add -
RUN apt-get update && apt-get install -y \
tensorflow-model-server
RUN pip install --upgrade pip
RUN pip install mock grpcio tensorflow tensorflow-serving-api
CMD ["/bin/bash"]
- 运行以下命令从此
dockerfile构建 Docker 镜像:
$ docker build --pull -t $USER/tensorflow_serving -f dockerfile .
- 创建图像需要一段时间。当您看到类似于以下内容的内容时,您就会知道图像已构建:
Removing intermediate container 1d8e757d96e0
Successfully built 0f95ddba4362
Successfully tagged armando/tensorflow_serving:latest
- 运行以下命令以启动容器:
$ docker run --name=mnist_container -it $USER/tensorflow_serving
- 当您看到以下提示时,您将登录到容器:
root@244ea14efb8f:/#
- 使用
cd命令转到主文件夹。 - 在主文件夹中,提供以下命令以检查 TensorFlow 是否正在提供代码。我们将使用此代码中的示例来演示,但您可以查看自己的 Git 仓库来运行您自己的模型:
$ git clone --recurse-submodules https://github.com/tensorflow/serving
克隆仓库后,我们就可以构建,训练和保存 MNIST 模型了。
- 使用以下命令删除临时文件夹(如果尚未删除):
$ rm -rf /tmp/mnist_model
- 运行以下命令以构建,训练和保存 MNIST 模型。
$ python serving/tensorflow_serving/example/mnist_saved_model.py /tmp/mnist_model
您将看到类似于以下内容的内容:
Training model...
Successfully downloaded train-images-idx3-ubyte.gz 9912422 bytes.
Extracting /tmp/train-images-idx3-ubyte.gz
Successfully downloaded train-labels-idx1-ubyte.gz 28881 bytes.
Extracting /tmp/train-labels-idx1-ubyte.gz
Successfully downloaded t10k-images-idx3-ubyte.gz 1648877 bytes.
Extracting /tmp/t10k-images-idx3-ubyte.gz
Successfully downloaded t10k-labels-idx1-ubyte.gz 4542 bytes.
Extracting /tmp/t10k-labels-idx1-ubyte.gz
2017-11-22 01:09:38.165391: I tensorflow/core/platform/cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA
training accuracy 0.9092
Done training!
Exporting trained model to /tmp/mnist_model/1
Done exporting!
- 按
Ctrl + P和Ctrl + Q从 Docker 镜像中分离。 - 提交对新映像的更改并使用以下命令停止容器:
$ docker commit mnist_container $USER/mnist_serving
$ docker stop mnist_container
- 现在,您可以通过提供以下命令随时运行此容器:
$ docker run --name=mnist_container -it $USER/mnist_serving
- 删除我们为保存图像而构建的临时 MNIST 容器:
$ docker rm mnist_container
在 Docker 容器中提供模型
要在容器中提供模型,说明如下:
- 启动上一节中构建的 MNIST 容器:
$ docker run --name=mnist_container -it $USER/mnist_serving
- 使用
cd命令转到主文件夹。 - 使用以下命令运行 ModelServer:
$ tensorflow_model_server --model_name=mnist --model_base_path=/tmp/mnist_model/ &> mnist_log &
- 使用示例客户端检查模型中的预测:
$ python serving/tensorflow_serving/example/mnist_client.py --num_tests=100 --server=localhost:8500
- 我们看到错误率为 7%,就像我们之前的笔记本示例执行一样:
Extracting /tmp/train-images-idx3-ubyte.gz
Extracting /tmp/train-labels-idx1-ubyte.gz
Extracting /tmp/t10k-images-idx3-ubyte.gz
Extracting /tmp/t10k-labels-idx1-ubyte.gz
....................................................................................................
Inference error rate: 7.0%
而已!您已经构建了 Docker 镜像,并在 Docker 镜像中为您的模型提供服务。发出exit命令退出容器。
Kubernetes 中的 TensorFlow 服务
根据 {Kubernetes](kubernets.io):
Kubernetes 是一个开源系统,用于自动化容器化应用的部署,扩展和管理。
TensorFlow 模型可以扩展为使用生产环境中的 Kubernetes 集群从数百或数千个TF Serving服务中提供服务。 Kubernetes 集群可以在所有流行的公共云上运行,例如 GCP,AWS,Azure,以及您的本地私有云。因此,让我们直接学习安装 Kubernetes,然后在 Kubernetes 集群上部署 MNIST 模型。
安装 Kubernetes
我们按照以下步骤在单节点本地群集模式下在 Ubuntu 16.04 上安装了 Kubernetes:
- 安装 LXD 和 Docker,这是在本地安装 Kubernetes 的先决条件。 LXD 是与 Linux 容器一起使用的容器管理器。我们已经在上一节中学习了如何安装 Docker。要安装 LXD,请运行以下命令:
$ sudo snap install lxd
lxd 2.19 from 'canonical' installed
- 初始化
lxd并创建虚拟网络:
$ sudo /snap/bin/lxd init --auto
LXD has been successfully configured.
$ sudo /snap/bin/lxc network create lxdbr0 ipv4.address=auto ipv4.nat=true ipv6.address=none ipv6.nat=false
If this is your first time using LXD, you should also run: lxd init
To start your first container, try: lxc launch ubuntu:16.04
Network lxdbr0 created
- 将您的用户添加到
lxd组:
$ sudo usermod -a -G lxd $(whoami)
- 安装
conjure-up并重启机器:
$ sudo snap install conjure-up --classic
conjure-up 2.3.1 from 'canonical' installed
- 启动
conjure-up以安装 Kubernetes:
$ conjure-up kubernetes
- 从法术列表中选择
Kubernetes Core。 - 从可用云列表中选择
localhost。 - 从网络列表中选择
lxbr0网桥。 - 提供选项的
sudo密码:将 kubectl 和 kubefed 客户端程序下载到本地主机。 - 在下一个屏幕中,它会要求选择要安装的应用。安装其余五个应用。
您知道当安装期间的最终屏幕如下所示时,Kubernetes 集群已准备好进行酿造:
如果您在安装时遇到问题,请在互联网上搜索帮助,从此链接的文档开始:
tutorials.ubuntu.com/tutorial/in…
将 Docker 镜像上传到 dockerhub
将 Docker 镜像上传到 dockerhub 的步骤如下:
- 如果您还没有,请在 dockerhub 上创建一个帐户。
- 使用以下命令登录 dockerhub 帐户:
$ docker login --username=<username>
- 使用您在 dockerhub 上创建的仓库标记 MNIST 图像。例如,我们创建了
neurasights/mnist-serving:
$ docker tag $USER/mnist_serving neurasights/mnist-serving
- 将标记的图像推送到 dockerhub 帐户。
$ docker push neurasights/mnist-serving
在 Kubernetes 部署
我们继续在 Kubernotes 中进行部署,如下所示:
- 使用以下内容创建
mnist.yaml文件:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mnist-deployment
spec:
replicas: 3
template:
metadata: labels: app: mnist-server
spec:
containers: - name: mnist-container
image: neurasights/mnist-serving
command:
- /bin/sh
args:
- -c
- tensorflow_model_server --model_name=mnist --model_base_path=/tmp/mnist_model
ports:
- containerPort: 8500
---
apiVersion: v1
kind: Service
metadata:
labels: run: mnist-service
name: mnist-service
spec:
ports: - port: 8500
targetPort: 8500
selector:
app: mnist-server
# type: LoadBalancer
如果您在 AWS 或 GCP 云中运行它,则取消注释前一个文件中的LoadBalancer行。 由于我们在单个节点上本地运行整个集群,因此我们没有外部负载均衡器。
- 创建 Kubernetes 部署和服务:
$ kubectl create -f mnist.yaml
deployment "mnist-deployment" created
service "mnist-service" created
- 检查部署,窗格和服务:
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
mnist-deployment 3 3 3 0 1m
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
default-http-backend-bbchw 1/1 Running 3 9d
mnist-deployment-554f4b674b-pwk8z 0/1 ContainerCreating 0 1m
mnist-deployment-554f4b674b-vn6sd 0/1 ContainerCreating 0 1m
mnist-deployment-554f4b674b-zt4xt 0/1 ContainerCreating 0 1m
nginx-ingress-controller-724n5 1/1 Running 2 9d
$ kubectl get services
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default-http-backend ClusterIP 10.152.183.223 <none> 80/TCP 9d
kubernetes ClusterIP 10.152.183.1 <none> 443/TCP 9d
mnist-service LoadBalancer 10.152.183.66 <pending> 8500:32414/TCP 1m
$ kubectl describe service mnist-service
Name: mnist-service
Namespace: default
Labels: run=mnist-service
Annotations: <none>
Selector: app=mnist-server
Type: LoadBalancer
IP: 10.152.183.66
Port: <unset> 8500/TCP
TargetPort: 8500/TCP
NodePort: <unset> 32414/TCP
Endpoints: 10.1.43.122:8500,10.1.43.123:8500,10.1.43.124:8500
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
- 等到所有 pod 的状态为
Running:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
default-http-backend-bbchw 1/1 Running 3 9d
mnist-deployment-554f4b674b-pwk8z 1/1 Running 0 3m
mnist-deployment-554f4b674b-vn6sd 1/1 Running 0 3m
mnist-deployment-554f4b674b-zt4xt 1/1 Running 0 3m
nginx-ingress-controller-724n5 1/1 Running 2 9d
- 检查其中一个 pod 的日志,您应该看到如下内容:
$ kubectl logs mnist-deployment-59dfc5df64-g7prf
I tensorflow_serving/model_servers/main.cc:147] Building single TensorFlow model file config: model_name: mnist model_base_path: /tmp/mnist_model
I tensorflow_serving/model_servers/server_core.cc:441] Adding/updating models.
I tensorflow_serving/model_servers/server_core.cc:492] (Re-)adding model: mnist
I tensorflow_serving/core/basic_manager.cc:705] Successfully reserved resources to load servable {name: mnist version: 1}
I tensorflow_serving/core/loader_harness.cc:66] Approving load for servable version {name: mnist version: 1}
I tensorflow_serving/core/loader_harness.cc:74] Loading servable version {name: mnist version: 1}
I external/org_tensorflow/tensorflow/contrib/session_bundle/bundle_shim.cc:360] Attempting to load native SavedModelBundle in bundle-shim from: /tmp/mnist_model/1
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:236] Loading SavedModel from: /tmp/mnist_model/1
I external/org_tensorflow/tensorflow/core/platform/cpu_feature_guard.cc:137] Your CPU supports instructions that this TensorFlow binary was not compiled to use: AVX2 FMA
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:155] Restoring SavedModel bundle.
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running LegacyInitOp on SavedModel bundle.
I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:284] Loading SavedModel: success. Took 45319 microseconds.
I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: mnist version: 1}
E1122 12:18:04.566415410 6 ev_epoll1_linux.c:1051] grpc epoll fd: 3
I tensorflow_serving/model_servers/main.cc:288] Running ModelServer at 0.0.0.0:8500 ...
- 您还可以使用以下命令查看 UI 控制台:
$ kubectl proxy xdg-open http://localhost:8001/ui
Kubernetes UI 控制台如下图所示:
由于我们在单个节点上本地运行集群,因此我们的服务仅在集群中公开,无法从外部访问。登录我们刚刚实例化的三个 pod 中的一个:
$ kubectl exec -it mnist-deployment-59dfc5df64-bb24q -- /bin/bash
切换到主目录并运行 MNIST 客户端来测试服务:
$ kubectl exec -it mnist-deployment-59dfc5df64-bb24q -- /bin/bash
root@mnist-deployment-59dfc5df64-bb24q:/# cd
root@mnist-deployment-59dfc5df64-bb24q:~# python serving/tensorflow_serving/example/mnist_client.py --num_tests=100 --server=10.152.183.67:8500
Extracting /tmp/train-images-idx3-ubyte.gz
Extracting /tmp/train-labels-idx1-ubyte.gz
Extracting /tmp/t10k-images-idx3-ubyte.gz
Extracting /tmp/t10k-labels-idx1-ubyte.gz
....................................................................................................
Inference error rate: 7.0%
root@mnist-deployment-59dfc5df64-bb24q:~#
我们学习了如何在本地单个节点上运行的 Kubernetes 集群上部署 TensorFlow 服务。您可以使用相同的概念知识在您的场所内的公共云或私有云上部署服务。
总结
在本章中,我们学习了如何利用 TensorFlow 服务来为生产环境中的模型提供服务。我们还学习了如何使用 TensorFlow 和 Keras 保存和恢复完整模型或选择模型。我们构建了一个 Docker 容器,并从官方 TensorFlow 服务仓库中提供了 Docker 容器中的示例 MNIST 示例代码。我们还安装了一个本地 Kubernetes 集群,并部署了 MNIST 模型,用于在 Kubernetes pod 中运行的 TensorFlow 服务。我们鼓励读者在这些例子的基础上进行尝试并尝试提供不同的模型。 TF 服务文档描述了各种选项,并提供了其他信息,使您可以进一步探索此主题。
在接下来的章节中,我们将继续使用迁移学习的高级模型。 TensorFlow 仓库中提供的预训练模型是使用 TF 服务练习服务 TF 模型的最佳候选者。我们使用 Ubuntu 包安装了 TF 服务,但您可能希望尝试从源代码构建以优化生产环境的 TF 服务安装。