5. 卷积神经网络(LeNet)
构造一个含单隐藏层的多层感知机模型来对Fashion-MNIST数据集中的图像进行分类(每张图像高和宽均是28像素,将图像中的像素逐行展开,得到长度为784的向量,并输入全连接层中),这种分类方法存在一定的局限性,包括:
(1) 图像在同一列邻近的像素在该向量中可能相距较远,它们构成的模式可能难以被模型识别。
(2) 对于大尺寸的输入图像,使用全连接层容易导致模型过大,带来过于复杂的模型和过高的存储开销。
假设输入是高和宽均为 1 , 000 1,000 1,000像素的彩色照片(含3个通道),即使全连接层输出个数仍是256,该层权重参数的形状也是 3 , 000 , 000 × 256 3,000,000\times 256 3,000,000×256,占用了约3 GB的内存或显存。
卷积层尝试解决上述两个问题:
一方面,卷积层保留输入形状,使图像的像素在高和宽两个方向上的相关性均可能被有效识别;
另一方面,卷积层通过滑动窗口将同一卷积核与不同位置的输入重复计算,从而避免参数尺寸过大。
卷积神经网络是含卷积层的网络。
本节将介绍一个早期用来识别手写数字图像的卷积神经网络:LeNet,其名字来源于LeNet论文的第一作者Yann LeCun。
LeNet展示了通过梯度下降训练卷积神经网络,可以达到当时手写数字识别最先进的结果。
这个奠基性的工作,第一次将卷积神经网络推上舞台,为世人所知。
5.1 LeNet模型
5.1.1 概念
LeNet分为卷积层块和全连接层块两个部分,分别介绍如下:
(1) 卷积层块
卷积层块中的基本单位是:卷积层后接最大池化层。
卷积层用来识别图像中的空间模式,如线条和物体局部;之后的最大池化层则用来降低卷积层对位置的敏感性。
卷积层块由上述两个基本单位重复堆叠构成:
每个卷积层都使用
5
×
5
5\times 5
5×5的窗口,并在输出上使用sigmoid激活函数。
第一个卷积层输出通道数为6,第二个卷积层输出通道数则增加到16。(这是因为第二个卷积层比第一个卷积层的输入的高和宽要小,所以增加输出通道使两个卷积层的参数尺寸类似。)
两个最大池化层的窗口形状均为
2
×
2
2\times 2
2×2,且步幅为2。(由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。)
卷积层块的输出形状为(批量大小, 通道, 高, 宽)。
(2) 全连接层块
当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。
即,全连接层的输入形状将变成二维。其中,第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道、高和宽的乘积。
全连接层块含3个全连接层,输出个数分别为120、84和10。其中,10为输出的类别个数。
![](https://i-blog.csdnimg.cn/blog_migrate/320b128b373062fe62cdc71c8e6411a8.png)
5.1.2 代码示例
import tensorflow as tf
print(tf.__version__)
2.0.0
通过Sequential
类来实现LeNet模型:
"""
tf.keras.layers.Conv2D:
filters: Integer, the dimensionality of the output space.
input_shape:
When using this layer as the first layer in a model,
provide the keyword argument `input_shape` (tuple of integers, does not include the sample axis)
e.g. `input_shape=(128, 128, 3)` for 128x128 RGB pictures in `data_format="channels_last"`.
"""
net = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(filters=6, kernel_size=5, activation='sigmoid', input_shape=(28, 28, 1)),
tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
tf.keras.layers.Conv2D(filters=16, kernel_size=5, activation='sigmoid'),
tf.keras.layers.MaxPool2D(pool_size=2, strides=2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(units=120, activation='sigmoid'),
tf.keras.layers.Dense(units=84, activation='sigmoid'),
tf.keras.layers.Dense(units=10, activation='sigmoid')
])
构造一个高和宽均为28的单通道数据样本,并逐层进行前向计算来查看每个层的输出形状:
# data_format="channels_last"
X = tf.random.uniform(shape=(1,28,28,1))
for layer in net.layers:
X = layer(X)
print(layer.name, 'output shape: ', X.shape)
输出:
conv2d output shape: (1, 24, 24, 6)
max_pooling2d output shape: (1, 12, 12, 6)
conv2d_1 output shape: (1, 8, 8, 16)
max_pooling2d_1 output shape: (1, 4, 4, 16)
flatten output shape: (1, 256)
dense output shape: (1, 120)
dense_1 output shape: (1, 84)
dense_2 output shape: (1, 10)
由此可见,
卷积层块中,输入的高和宽在逐层减小(卷积层由于使用高和宽均为5的卷积核,从而将高和宽分别减小4)。
池化层将高和宽减半(窗口形状为
2
×
2
2\times 2
2×2且步幅为2),但通道数则从1增加到16。
全连接层逐层减少输出个数,直到变成图像的类别数10。
5.2 模型训练
5.2.1 数据集
使用Fashion-MNIST作为训练数据集。
fashion_mnist = tf.keras.datasets.fashion_mnist
(train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
train_images = tf.reshape(train_images, shape=(train_images.shape[0], train_images.shape[1], train_images.shape[2], 1))
print(train_images.shape)
test_images = tf.reshape(test_images, shape=(test_images.shape[0], test_images.shape[1], test_images.shape[2], 1))
输出:
(60000, 28, 28, 1)
5.2.2 训练
损失函数和训练算法,依然采用交叉熵损失函数(cross entropy)和小批量随机梯度下降(SGD):
"""
tf.keras.optimizers.SGD:
momentum: Accelerates SGD in the relevant direction and dampens oscillations.
nesterov: Whether to apply Nesterov momentum.
"""
optimizer = tf.keras.optimizers.SGD(learning_rate=0.9, momentum=0.0, nesterov=False)
net.compile(optimizer=optimizer,
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 训练
net.fit(train_images, train_labels, epochs=5, validation_split=0.1)
输出:
net.fit(train_images, train_labels, epochs=5, validation_split=0.1)
net.fit(train_images, train_labels, epochs=5, validation_split=0.1)
Train on 54000 samples, validate on 6000 samples
Epoch 1/5
54000/54000 [==============================] - 15s 286us/sample - loss: 1.3557 - accuracy: 0.4590 - val_loss: 0.6590 - val_accuracy: 0.7437
Epoch 2/5
54000/54000 [==============================] - 15s 275us/sample - loss: 0.6410 - accuracy: 0.7432 - val_loss: 0.5498 - val_accuracy: 0.7857
Epoch 3/5
54000/54000 [==============================] - 16s 289us/sample - loss: 0.5494 - accuracy: 0.7829 - val_loss: 0.5215 - val_accuracy: 0.7867
Epoch 4/5
54000/54000 [==============================] - 15s 273us/sample - loss: 0.5168 - accuracy: 0.7999 - val_loss: 0.5084 - val_accuracy: 0.8082
Epoch 5/5
54000/54000 [==============================] - 14s 253us/sample - loss: 0.4798 - accuracy: 0.8135 - val_loss: 0.4520 - val_accuracy: 0.8265
<tensorflow.python.keras.callbacks.History at 0x13bc3bad0>
# 预测
net.evaluate(test_images, test_labels, verbose=2)
输出:
10000/1 - 1s - loss: 0.3569 - accuracy: 0.8158
[0.4777470575332642, 0.8158]
参考
《动手学深度学习》(TF2.0版)
LeCun, Y.; Bottou, L.; Bengio, Y. & Haffner, P. (1998). Gradient-based learning applied to document recognition.Proceedings of the IEEE. 86(11): 2278 - 2324.