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在輸出層轉化為概率輸出,另外在池化層現代更多用最大池化,而不是當初的平均池化。