LeNet-5卷积神经网络是最经典的卷积网络之一,这篇文章就在LeNet-5的基础上加入了一些tensorflow的有趣函数,对LeNet-5做了改动,也是对一些tf函数的实例化笔记吧。
环境 Pycharm2019+Python3.7.6+tensorflow 2.0
话不多说,先放完整源码
from tensorflow.keras import layers, datasets, Sequential, losses, optimizers
import tensorflow as tf
import matplotlib.pyplot as pltdef get_data():(train_images, train_labels), (val_images, val_labels) = datasets.mnist.load_data()return train_images, train_labels, val_images, val_labelsdef model_build():network = Sequential([layers.Conv2D(6, kernel_size=3, strides=1, input_shape=(28, 28, 1)),layers.MaxPool2D(pool_size=2, strides=2),layers.ReLU(),layers.Conv2D(16, kernel_size=3, strides=1),layers.MaxPool2D(pool_size=2, strides=2),layers.ReLU(),layers.Conv2D(24, kernel_size=3, strides=1),layers.MaxPool2D(pool_size=2, strides=2),layers.ReLU(),layers.Flatten(),layers.Dense(120, activation='relu'),layers.Dropout(0.5),layers.Dense(84, activation='relu'),layers.Dense(10) # 因为输出的是独热编码设置为10])network.summary()return networktrain_images, train_labels, val_images, val_labels = get_data()
plt.figure()
plt.imshow(train_images[0]) # 打印第一张图片检查数据
plt.colorbar() # 色度条显示
plt.grid(False) # 不显示网格
plt.show()
print(train_images.shape, train_labels.shape)
'''检查数据标签是否正确'''lables = ['0', '1', '2', '3', '4','5', '6', '7', '8', '9']
plt.figure(figsize=(10, 10))
for i in range(25):plt.subplot(5, 5, i + 1)plt.xticks([])plt.yticks([])plt.grid(False)plt.imshow(train_images[i], cmap=plt.cm.binary)plt.xlabel(lables[train_labels[i]])
plt.show()
train_images = tf.expand_dims(train_images, axis=3)
val_images = tf.expand_dims(val_images, axis=3)
train_labels = tf.cast(train_labels, tf.int32)
val_labels = tf.cast(val_labels, tf.int32)
train_labels = tf.one_hot(train_labels, depth=10)
val_labels = tf.one_hot(val_labels, depth=10)
train_images = tf.convert_to_tensor(train_images)
print(train_images.dtype, train_labels.dtype)
if train_images.dtype != tf.float32:train_images = tf.cast(train_images, tf.float32)
print(train_images, train_labels)model = model_build()
earlystop_callback = tf.keras.callbacks.EarlyStopping(monitor='val_acc', min_delta=0.001, patience=112)
model.compile(loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True), optimizer='adam', metrics=["acc"])
hist = model.fit(train_images, train_labels, epochs=20, batch_size=28, validation_data=[val_images, val_labels],callbacks=[earlystop_callback])print(hist.history.keys())
print(hist.history['acc'])
from tensorflow.keras import layers, datasets, Sequential, losses, optimizers
import tensorflow as tf
import matplotlib.pyplot as plt
先导入我们所需要的库,为了方便,我把 layers, datasets, Sequential, losses, optimizers做了特别导入。
def get_data():(train_images, train_labels), (val_images, val_labels) = datasets.mnist.load_data()return train_images, train_labels, val_images, val_labels
定义数据获取函数,从tensorflow的mnist中使用load_data()获取手写数字数据集,我们在这里会得到四个张量,(train_images, train_labels), (val_images, val_labels),分别为训练图像,训练标签,验证图像,验证标签。其中images的张量形状为(60000, 28, 28) labels张量形状为(60000, )
labels和images图像和标签索引相互对应。
def model_build():network = Sequential([layers.Conv2D(6, kernel_size=3, strides=1, input_shape=(28, 28, 1)),layers.MaxPool2D(pool_size=2, strides=2),layers.ReLU(),layers.Conv2D(16, kernel_size=3, strides=1),layers.MaxPool2D(pool_size=2, strides=2),layers.ReLU(),layers.Conv2D(24, kernel_size=3, strides=1),layers.MaxPool2D(pool_size=2, strides=2),layers.ReLU(),layers.Flatten(),layers.Dense(120, activation='relu'),layers.Dropout(0.5),layers.Dense(84, activation='relu'),layers.Dense(10) # 因为输出的是独热编码设置为10])network.summary()return network
定义模型搭建函数
使用了Sequential封装网络
layers.Conv2D(6, kernel_size=3, strides=1, input_shape=(28, 28, 1)),
加入第一层,为Conv2D卷积层,卷积核个数为6个, 感受野为3*3,卷积步长为1,网格输入张量形状为(28, 28,1)现在我们主要讨论卷积在tf里的实现方式,卷积算法我会在未来另一篇文章中介绍,这里不再赘述。
layers.MaxPool2D(pool_size=2, strides=2),
池化层,选用了MaxPool2D最大池化层,池化域2*2, 步长为2,pool_size=2, strides=2是一种常见的参数设置,可以使数据宽高缩小到原来的一半,算法到时候和卷积一并介绍。
layers.ReLU(),
激活函数层, 选用‘relu’函数
layers.Flatten(),
layers.Dense(120, activation='relu'),
layers.Dropout(0.5),
layers.Dense(84, activation='relu'),
layers.Dense(10)
这一部分为全连接层,在将数据输入全连接层前,要先使用flatten层对数据进行铺平处理。
可以设置dropout层“退火”防止过拟合
因为我们最后实现分类时,我们使用了独热编码来代替原来的label ,所以在最后的输出层设置了10。
train_images, train_labels, val_images, val_labels = get_data()
plt.figure()
plt.imshow(train_images[0]) # 打印第一张图片检查数据
plt.colorbar() # 色度条显示
plt.grid(False) # 不显示网格
plt.show()
print(train_images.shape, train_labels.shape)
'''检查数据标签是否正确'''
lables = ['0', '1', '2', '3', '4','5', '6', '7', '8', '9']
plt.figure(figsize=(10, 10))
for i in range(25):plt.subplot(5, 5, i + 1)plt.xticks([])plt.yticks([])plt.grid(False)plt.imshow(train_images[i], cmap=plt.cm.binary)plt.xlabel(lables[train_labels[i]])
从get_data()函数中加载我们所需要的数据集
打印第一张图片,检查数据集标签是否正确,正确的标签使我们训练的关键因素。
我们应该能得到这样的图片,标签表,发现标签对应是正确的,我们便可以继续。
图像展示函数使用的matplotlib库,具体不再介绍。
train_images = tf.expand_dims(train_images, axis=3)
val_images = tf.expand_dims(val_images, axis=3)
train_labels = tf.cast(train_labels, tf.int32)
val_labels = tf.cast(val_labels, tf.int32)
train_labels = tf.one_hot(train_labels, depth=10)
val_labels = tf.one_hot(val_labels, depth=10)
network = model_build()
train_images = tf.convert_to_tensor(train_images)
print(train_images.dtype, train_labels.dtype)
if train_images.dtype != tf.float32:train_images = tf.cast(train_images, tf.float32)
print(train_images, train_labels)
对数据的预处理,我觉得这部分很重要,在编写这个卷积网络时,我在数据的准备上犯了很多错误,导致程序无法运行或训练效果很差等。
train_images = tf.expand_dims(train_images, axis=3)
val_images = tf.expand_dims(val_images, axis=3)
我们在上面说过,我们image的shape为(60000, 28, 28),但2d卷积层的输入要求为4个维度,我们便将所有的image数据扩充了一个维度,变为(60000, 28, 28, 1) 灰白图像。
train_labels = tf.cast(train_labels, tf.int32)
val_labels = tf.cast(val_labels, tf.int32)
train_labels = tf.one_hot(train_labels, depth=10)
val_labels = tf.one_hot(val_labels, depth=10)
我们要将标签形式转化为独热编码,比如 [1]-->[0, 1, 0, 0, 0, 0, 0 ,0 ,0, 0] 2-->[0, 0, 1, 0, 0, 0, 0 ,0 ,0, 0],这样的好处是规避了标签本身可能存在的数据比较,比如 ‘1’标签大于‘2’标签,但在分类时标签‘1’和‘2’并没有大小关系,独热编码就很好的规避了这种可能存在的比较。
但在将编码独热化前,需要将label的数据转化成int32,否则会报错
network = model_build()
train_images = tf.convert_to_tensor(train_images)
print(train_images.dtype, train_labels.dtype)
if train_images.dtype != tf.float32:train_images = tf.cast(train_images, tf.float32)
print(train_images, train_labels)
Conv2D的输入类型为 tf.float32
model = model_build()
earlystop_callback = tf.keras.callbacks.EarlyStopping(monitor='val_acc', min_delta=0.001, patience=112)
model.compile(loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True), optimizer='adam', metrics=["acc"])
hist = model.fit(train_images, train_labels, epochs=20, batch_size=28, validation_data=[val_images, val_labels],callbacks=[earlystop_callback])
编译训练的设置,十分重要,这里的参数对准确率有很大的影响。
我们先搭建网络
earlystop_callback = tf.keras.callbacks.EarlyStopping(monitor='val_acc', min_delta=0.001, patience=112)
使用了一个早停callback ,监控对象为验证集准确率,监控的分度值为0.001,步数112,实现了如果模型在连续在112次内val_acc始终没有在0.001的分度上有所提升,就认为已经收敛了,训练结束。这个callback内的参数是随意设置的,根据自己的目的参数可以调整,合理范围内一般不影响准确率。
model.compile(loss=tf.keras.losses.CategoricalCrossentropy(from_logits=True), optimizer='adam', metrics=["acc"])
hist = model.fit(train_images, train_labels, epochs=20, batch_size=28, validation_data=[val_images, val_labels],callbacks=[earlystop_callback])
模型编译中,我们在分类的损失函数中一般选择CategoricalCrossentropy(from_logits=True)
交叉熵损失函数,其中在最后参数选定为True,使用softmax修订结果,softmax可以将值转化为概率值,这个选项会对训练的准确度产生巨大影响。softmax()
训练模型
print(hist.history.keys())
print(hist.history['acc'])
在最后我用hist变量接受我们训练的数据
我们可以看到在epoch 16中我们的准确率已经达到了较高的水平。
可以通过hist.history返回的字典得到我们训练的误差等信息进行误差图像等可视化的设置,我在上一篇的全连接网络实例中介绍了一种简单的可视化方法。
在tensorflow的官网中可以学习他们对数据结果可视化的样例。
比如,我大概修改后,我们可以得到这样的效果
蓝色条为模型对他分类的信任度,比如第一个模型认为他有100%的概率认为这个数字是7。
这篇CNN LeNet-5的实例笔记结束了,顺便提一句,这个手写数字数据集在上一篇提到的全连接神经网络中的准确率高达99.5%。LeNet-5在简单的灰色手写数字数据集的识别效果很好,但在复杂彩色图像下,性能就会急剧下降,下一篇介绍预计为VGG13卷积神经网络,可以对更复杂图像进行识别。最近时间不太充裕,晚一些我会继续发布在tensorflow与神经网络专栏中。