经典卷积神经网络算法(1):LeNet-5
LeNet-5科学家Yann LeCun在1998年发表论文《Gradient based learning applied to document-recognition》上提出的一个神经网络模型,是最早期的卷积神经网络,论文中,作者将LeNet-5应用于于灰度图像的数字识别中获得了不错的效果。关于LeNet-5卷积神经网络原理,在上一篇介绍卷积神经网络入门博客中已经阐述清楚,本篇中,我们主要对LeNet-5使用TensorFlow进行实现。
LeNet-5网络结构如下所示:
接下来,本文就上图所示LeNet-5结构进行实现。
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import datasets, layers, optimizers, Sequential ,metrics
TensorFlow中自带手写数字识别图像数据集,使用datasets模块进行加载即可。
(x, y), (x_test, y_test) = datasets.mnist.load_data()
查看数据的数量和图像size:
print(x.shape, y.shape)
print(x_test.shape, y_test.shape)
(60000, 28, 28) (60000,) (10000, 28, 28) (10000,)
可见,训练集中包含60000张图像,测试集中包含10000章图像,图像大小为2828。图像size与上图LeNet-5卷积网络中所用3232数据集有所不同,不过没关系,我们在第一层卷积层中对图像进行padding即可。
使用matplotlib对数据集进行展示,如下所示,图片上方数字为图像对应的数字。
index = 1
fig, axes = plt.subplots(4, 3, figsize=(8, 4), tight_layout=True)
for row in range(4):
for col in range(3):
axes[row, col].imshow(x[index])
axes[row, col].axis('off')
axes[row, col].set_title(y[index])
index += 1
plt.show()
刚加载好的图像是numpy数组形式,元素值在0~255之间,需要进行类型转换和归一化。这里我们定义一个preprocess作为预处理函数,在将数据集打包成TensorFlow的dataset形式后,使用map函数调用preprocess对数据进行预处理更加方便。
def preprocess(x, y):
x = tf.cast(x, dtype=tf.float32) / 255.
x = tf.reshape(x,[28,28,1])
y = tf.cast(y, dtype=tf.int32)
return x, y
batchs = 32 # 每个簇的大小,批量梯度下降法时每一批的包含图像的数量
打包成TensorFlow到的dataset对象,并随机打乱数据:
db = tf.data.Dataset.from_tensor_slices((x, y))
db = db.map(preprocess).shuffle(10000).batch(batchs)
对测试集同样打包成dataset,不过测试集可以不随机打乱:
db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
db_test = db_test.map(preprocess).batch(batchs)
net_layers = [ # 卷积部分网络
# 第一个卷积层:5*5*6
# 这个padding在最初的LeNet-5网络中是没有的,那时候还没有padding的概念,为了使这一层输出与元素LeNet-5网络保持一致,所以这里添加padding操作
layers.Conv2D(6, kernel_size=[5,5],padding='same', activation='relu'), # 6个5*5的卷积核,进行padding
# 池化层
layers.MaxPool2D(pool_size=[2, 2], strides=2), # 池化层大小2*2,步长2
# layers.ReLU()
# 第二个池化层:5*5*16
layers.Conv2D(16, kernel_size=[5,5],padding='valid', activation='relu'),
# 池化层
layers.MaxPool2D(pool_size=[2, 2], strides=2),
# layers.ReLU()
layers.Flatten(), # 展平成一维数组
# 全连接层
layers.Dense(120,activation='relu'),
layers.Dense(84,activation='relu'),
layers.Dense(10,activation='softmax')
]
model = tf.keras.models.Sequential(net_layers) # 将上面创建的层合并打包成模型
model.build(input_shape=(None, 28, 28, 1)) # 指定输入数据形状
optimizer = tf.keras.optimizers.SGD(learning_rate=0.01) # 创建优化器
model.compile(optimizer=optimizer, # 配置模型
loss='sparse_categorical_crossentropy', # 指定损失函数
metrics=['accuracy'])
model.fit(db,epochs=5, validation_data=db_test) # 训练模型
Epoch 1/5 1875/1875 [==============================] - 46s 25ms/step - loss: 0.5028 - accuracy: 0.8513 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00 Epoch 2/5 1875/1875 [==============================] - 42s 22ms/step - loss: 0.1257 - accuracy: 0.9619 - val_loss: 0.1051 - val_accuracy: 0.9671 Epoch 3/5 1875/1875 [==============================] - 42s 23ms/step - loss: 0.0928 - accuracy: 0.9716 - val_loss: 0.0798 - val_accuracy: 0.9742 Epoch 4/5 1875/1875 [==============================] - 43s 23ms/step - loss: 0.0746 - accuracy: 0.9766 - val_loss: 0.0666 - val_accuracy: 0.9785 Epoch 5/5 1875/1875 [==============================] - 41s 22ms/step - loss: 0.0638 - accuracy: 0.9801 - val_loss: 0.0580 - val_accuracy: 0.9800
<tensorflow.python.keras.callbacks.History at 0x7fba79d4b610>
可以看到,经过5轮迭代之后,模型的准确率达到98.01%,这在当时已经是相当不错的成绩。
上述代码对LeNet-5卷积神经网络进行实现,网络一共包含7层(激活函数不计算在内),注意,上文中实现的是现代版的LeNet卷积网络,与最初Yann LeCun论文中描述的LeNet-5在结构上是一致的,不过,现代版的LeNet-5网络更多使用ReLU激活函数作为中间层激活函数和Softmax在输出层转化为概率输出,另外在池化层现代更多用最大池化,而不是当初的平均池化。