构建卷积神经网络 (CNN) 以增强计算机视觉

1. 准备工作

在此 Codelab 中,您将学习如何使用 CNN 改进图像分类模型。

前提条件

此 Codelab 基于前两个 Codelab 中完成的工作:构建计算机视觉模型 Codelab(我们在其中介绍了一些您将在此处使用的代码),以及构建卷积并执行池化 Codelab(我们在其中介绍了卷积和池化)。

学习内容

  • 如何使用卷积提高计算机视觉和准确率

构建内容

  • 用于增强神经网络的层

所需条件

您可以找到在 Colab 中运行其余 Codelab 的代码。

您还需要安装 TensorFlow 以及您在上一个 Codelab 中安装的库。

2. 使用卷积提高计算机视觉准确率

您现在已知道如何使用深度神经网络 (DNN) 进行时尚图像识别,该网络包含三层:输入层(输入数据的形状)、输出层(理想输出的形状)和隐藏层。您使用数个影响最终准确率的参数(例如隐藏层的不同大小和训练周期数)进行了实验。

为方便起见,我们重新在下面提供了完整的代码。运行该代码并记下最后输出的测试准确率。

import tensorflow as tf
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images/255.0
test_images=test_images/255.0
model = tf.keras.models.Sequential([
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(training_images, training_labels, epochs=5)
test_loss, test_accuracy = model.evaluate(test_images, test_labels)
print ('Test loss: {}, Test accuracy: {}'.format(test_loss, test_accuracy*100))

您的训练准确率可能约为 89%,验证准确率约为 87%。使用卷积可以进一步提升准确率,卷积会缩小图像内容的范围,以便专注于特定的细节。

如果您曾使用过滤器进行图像处理,则卷积看起来会非常熟悉。

简而言之,您需要一个数组(通常为 3x3 或 5x5)并将其传递给图像。通过基于矩阵中的公式更改底层像素,您可以执行边缘检测等操作。例如,通常定义一个 3x3 数组用于边缘检测,其中中间单元格为 8,并且其所有相邻单元格均为 -1。在本例中,您需将每个像素的值乘以 8,然后减去每个相邻像素的值。为每个像素执行上述操作,最终得到一个边缘已增强的新图像。

这非常适合计算机视觉,因为增强边缘等特征有助于计算机区分不同事物。更好的是,所需的信息量会少得多,因为您只要针对突出特征进行训练。

这就是卷积神经网络的概念。在生成密集层之前添加一些层进行卷积,然后到达密集层的信息会变得更加集中,并且可能更加准确。

3. 试用代码

运行以下代码。它与之前的神经网络相同,但这次首先添加了卷积层。这将需要更长的时间,但请观察对准确率的影响:

import tensorflow as tf
print(tf.__version__)
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images.reshape(60000, 28, 28, 1)
training_images=training_images / 255.0
test_images = test_images.reshape(10000, 28, 28, 1)
test_images=test_images / 255.0
model = tf.keras.models.Sequential([
  tf.keras.layers.Conv2D(64, (3, 3), activation='relu', input_shape=(28, 28, 1)),
  tf.keras.layers.MaxPooling2D(2, 2),
  tf.keras.layers.Conv2D(64, (3, 3), activation='relu'),
  tf.keras.layers.MaxPooling2D(2,2),
  tf.keras.layers.Flatten(),
  tf.keras.layers.Dense(128, activation='relu'),
  tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.summary()
model.fit(training_images, training_labels, epochs=5)
test_loss, test_accuracy = model.evaluate(test_images, test_labels)
print ('Test loss: {}, Test accuracy: {}'.format(test_loss, test_accuracy*100))

训练数据准确率可能提升到约 93%,验证数据准确率可能提升到约 91%。

现在,请尝试将其运行更多周期(例如大约 20 个周期)并探索其结果。虽然训练结果可能看起来非常好,但验证结果实际上可能会因为被称为过拟合的现象而导致效果下降。

如果网络从训练集学习数据表现太好,就会发生过拟合,它会仅专注于识别这样的数据,因此在更普遍的情况下学习其他数据时效果就会不佳。例如,如果您针对大量高跟鞋数据进行训练,那么该网络可能非常擅长识别高跟鞋,但对于运动鞋可能就会发生混乱。

再次查看代码,并逐步了解如何构建卷积。

4. 收集数据

第一步是收集数据。

您会发现这里发生了一些变化,并且需要对训练数据进行改造。这是因为第一个卷积需要一个包含所有内容的张量,因此,您只有一个 60000x28x28x1 的 4D 列表,而不是在列表中包含 60000 个 28x28x1 项,测试图像也是如此。否则,您在训练时将收到错误消息,因为卷积无法识别其形状。

import tensorflow as tf
mnist = tf.keras.datasets.fashion_mnist
(training_images, training_labels), (test_images, test_labels) = mnist.load_data()
training_images=training_images.reshape(60000, 28, 28, 1)
training_images = training_images/255.0
test_images = test_images.reshape(10000, 28, 28, 1)
test_images = test_images/255.0

5. 定义模型

接下来,定义您的模型。您将添加卷积层,而不是顶部的输入层。具体参数如下:

  • 您要生成的卷积数。诸如 32 之类的值是不错的起点。
  • 卷积矩阵的大小,在本例中为 3x3 网格。
  • 需要使用的激活函数,在本例中使用 relu
  • 在第一层中,输入数据的形状。

您将使用最大池化层紧接卷积,该层旨在压缩图像,同时保留由卷积突出显示的特征内容。为最大池化指定 (2,2),结果会将图像的大小减至四分之一。它会创建一个 2x2 像素数组,并选择最大的像素值,从而将 4 个像素转换为 1 个像素。它会在整个图像中重复此计算,其结果是将水平像素数和垂直像素数均减半。

您可以调用 model.summary() 查看网络的大小和形状。请注意,在每个最大池化层之后,图像大小都会通过以下方式减小:

_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
conv2d_2 (Conv2D)            (None, 26, 26, 64)        640
_________________________________________________________________
max_pooling2d_2 (MaxPooling2 (None, 13, 13, 64)        0
_________________________________________________________________
conv2d_3 (Conv2D)            (None, 11, 11, 64)        36928
_________________________________________________________________
max_pooling2d_3 (MaxPooling2 (None, 5, 5, 64)          0
_________________________________________________________________
flatten_2 (Flatten)          (None, 1600)              0
_________________________________________________________________
dense_4 (Dense)              (None, 128)               204928
_________________________________________________________________
dense_5 (Dense)              (None, 10)                1290
=================================================================

以下是 CNN 的完整代码:

model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)),
tf.keras.layers.MaxPooling2D(2, 2),
#Add another convolution
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2, 2),
#Now flatten the output. After this you'll just have the same DNN structure as the non convolutional version
tf.keras.layers.Flatten(),
#The same 128 dense layers, and 10 output layers as in the pre-convolution example:
tf.keras.layers.Dense(128, activation='relu'),
tf.keras.layers.Dense(10, activation='softmax')
])

6. 编译和训练模型

编译模型,调用拟合方法进行训练,然后评估测试集的损失和准确率。

model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])
model.fit(training_images, training_labels, epochs=5)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print ('Test loss: {}, Test accuracy: {}'.format(test_loss, test_acc*100))

7. 直观呈现卷积和池化

此代码以图形方式显示卷积。print (test_labels[:100]) 显示了测试集内的前 100 个标签,可以看到索引 0、索引 23 和索引 28 处的标签都是相同的值 (9)。它们都是鞋子。查看对各种鞋运行卷积的结果,您会开始发现它们之间出现一些共同特征。现在,DNN 在基于该数据进行训练时,处理的信息会少得多,并且它可能根据这种卷积和池化组合找到鞋子之间的共同性。

print(test_labels[:100])
[9 2 1 1 6 1 4 6 5 7 4 5 7 3 4 1 2 4 8 0 2 5 7 9 1 4 6 0 9 3 8 8 3 3 8 0 7
 5 7 9 6 1 3 7 6 7 2 1 2 2 4 4 5 8 2 2 8 4 8 0 7 7 8 5 1 1 2 3 9 8 7 0 2 6
 2 3 1 2 8 4 1 8 5 9 5 0 3 2 0 6 5 3 6 7 1 8 0 1 4 2]

现在,您可以为这些标签选择一些相应的图像,并呈现它们在卷积过程中的显示效果。因此,在以下代码中,FIRST_IMAGESECOND_IMAGETHIRD_IMAGE 都是值 9(踝靴)的索引。

import matplotlib.pyplot as plt
f, axarr = plt.subplots(3,4)
FIRST_IMAGE=0
SECOND_IMAGE=23
THIRD_IMAGE=28
CONVOLUTION_NUMBER = 6
from tensorflow.keras import models
layer_outputs = [layer.output for layer in model.layers]
activation_model = tf.keras.models.Model(inputs = model.input, outputs = layer_outputs)
for x in range(0,4):
  f1 = activation_model.predict(test_images[FIRST_IMAGE].reshape(1, 28, 28, 1))[x]
  axarr[0,x].imshow(f1[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
  axarr[0,x].grid(False)
  f2 = activation_model.predict(test_images[SECOND_IMAGE].reshape(1, 28, 28, 1))[x]
  axarr[1,x].imshow(f2[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
  axarr[1,x].grid(False)
  f3 = activation_model.predict(test_images[THIRD_IMAGE].reshape(1, 28, 28, 1))[x]
  axarr[2,x].imshow(f3[0, : , :, CONVOLUTION_NUMBER], cmap='inferno')
  axarr[2,x].grid(False)

您应该能看到如下所示的内容,其中卷积获取鞋底的本质特征,实际上是将其看作所有鞋子的共同特征。

6c9109bcc640a1ec.png

8. 练习

练习 1

尝试修改卷积。将卷积数从 32 更改为 16 或 64。这对准确率和训练时间有何影响?

练习 2

移除最终卷积。这对准确率或训练时间有何影响?

练习 3

添加更多卷积。这会带来什么影响?

练习 4

移除第一个卷积外的所有卷积。这会带来什么影响?对此进行实验。

9. 恭喜

您已构建了首个 CNN!如需了解如何进一步增强计算机视觉模型,请继续学习使用卷积神经网络 (CNN) 处理复杂图像这个 Codelab。