卷积神经网络识别MNIST手写数字设计
import tensorflow as tf
import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
from pylab import xticks,yticks
from tensorflow import keras
# 导入误差计算losses,优化器optimizers,模型容器模块Sequential
from tensorflow.keras import layers,Sequential,losses,optimizers,datasets
1.加载MNIST数据集并数据预先处理
1.1 导入数据
MNIST数据集
大多数示例使用手写数字的MNIST数据集.该数据集包含60,000个用于训练的示例和10,000个用于测试的示例.
mnist=tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()# x_train, y_train分别是图像数据和标签数据
1.2 数据归一化
x_train, x_test = x_train / 255.0, x_test / 255.0
1.3 抽取验证集
实际上可以用测试集来代替
# 从训练集抽取3000张作为验证集
x_split =tf.split(x_train,axis=0, num_or_size_splits=[57000,3000])# num_or_size_splits:可传入数字,列表。分别表示分割几份,自定义分割。
y_split =tf.split(y_train,axis=0, num_or_size_splits=[57000,3000])
x_train, x_valid=x_split[0], x_split[1]
y_train, y_valid=y_split[0], y_split[1]
x_train.shape, x_valid.shape, x_test.shape,y_train.shape, y_valid.shape, y_test.shape,
(TensorShape([57000, 28, 28]),
TensorShape([3000, 28, 28]),
(10000, 28, 28),
TensorShape([57000]),
TensorShape([3000]),
(10000,))
1.4 查看训练集,验证集,测试集,的数据类型
x_train.dtype, x_valid.dtype, x_test.dtype,y_train.dtype, y_valid.dtype, y_test.dtype
(tf.float64, tf.float64, dtype('float64'), tf.uint8, tf.uint8, dtype('uint8'))
1.5 将x_test,y_test转化为张量,统一数据类型
x_test, y_test=tf.constant(x_test), tf.constant(y_test)
x_train.dtype, x_valid.dtype, x_test.dtype,y_train.dtype, y_valid.dtype, y_test.dtype
(tf.float64, tf.float64, tf.float64, tf.uint8, tf.uint8, tf.uint8)
1.6 独热编码
1.6.1独热编码数学理解
独热编码的目的是让标签与得分匹配
例如一个3分类问题,类别分别是abc
假设传入数据对a,b.c三个类别的得分为5,2,4
神经网络计算原理如下:
a, b, c 【1. 类别】
5, -2, 4 【2. 每个类别的得分】
148, 0.13, 54 【3. 将得分指数扩大映射】
0.705, 0.035, 0.25 【4. 将3的结果归一化(符合概率分布)】
1, 0 0 【5. 输出4概率最大值的位置 】
1, 0, 0 【6. a类别的独热编码】
独热编码<==>得分 【7. 通过5,6将独热编码与得分匹配】
说明:
输入的数据经过网络模型后,通过得分函数的计算,给出输入数据属于每个类别的得分(上述例子中的【2】)
多分类问题通过softmax函数将输入数据属于每个类别的得分转换为概率分布(上述例子中的【3,4】)
然后通过tf.reduce_max()函数计算出最大值,再通过argmax() 选取概率最大的位置(上述例子中的【5】)
经过softmax()、reduce_max()转化后的列表最后与独热编码匹配。
下面是的softmax函数图像
import math
z = np.arange(-100,101)
# z= (5,-2,4)
z_exp = [math.exp(i) for i in z]
# print(z_exp)
sum_z_exp = sum(z_exp)
softmax = [round(i / sum_z_exp, 3) for i in z_exp]
# print(softmax)
sum(softmax)
0.9970000000000001
plt.plot(softmax, color="m",label='softmax')
xticks(np.linspace(-5,100,10,endpoint=True))
# 修改纵坐标的刻度
yticks(np.linspace(0,0.2,20,endpoint=True))
plt.legend()
1.6.2 将标签数据独热编码
y_train_0 = tf.one_hot(y_train, depth=10)
y_valid_0 = tf.one_hot(y_valid, depth=10)
y_test_0= tf.one_hot(y_test, depth=10)
y_train[8],y_valid[8],y_test[8]
(<tf.Tensor: shape=(), dtype=uint8, numpy=1>,
<tf.Tensor: shape=(), dtype=uint8, numpy=9>,
<tf.Tensor: shape=(), dtype=uint8, numpy=5>)
print('训练集第20个图像的标签是:{}\n独热码是:{}'.format(y_train[20],y_train_0[20]))
print('验证集第20个图像的标签是:{}\n独热码是:{}'.format(y_valid[20],y_valid_0[20]))
print('测试集第20个图像的标签是:{}\n独热码是:{}'.format(y_test[20],y_test_0[20]))
训练集第20个图像的标签是:4
独热码是:[0. 0. 0. 0. 1. 0. 0. 0. 0. 0.]
验证集第20个图像的标签是:0
独热码是:[1. 0. 0. 0. 0. 0. 0. 0. 0. 0.]
测试集第20个图像的标签是:9
独热码是:[0. 0. 0. 0. 0. 0. 0. 0. 0. 1.]
1.7 查看图像信息
plt.imshow(x_train[20],
cmap = 'binary'# 二进制数组图像信息
)
2,构建模型
设计思路与思考
1,从整个mnist数据集看,mnist数据可分为10种类型(0-9),是一个典型的分类问题。
2,从单个图像数据(1,28,28)看,图片数据中有28x28个像素点
3,建立4层全连接网络
4,分批次每次将100张图片的数据信息投入网络
2.1创建卷积神经网络network
network = Sequential([ # 网络容器/mnt/4D30D7CFB7ACA3ED/ProgramData/PycharmProjects/TF-GPU_py3.8.3/TF大作业/大作业2.py
layers.Conv1D(32,
kernel_size=3,
input_shape=(28,28),
strides=1), # 第一个卷积层, 6 个 3x3 卷积核
layers.Dropout(rate=0.3), # 丢弃层,防止过拟合
layers.MaxPooling1D(pool_size=2,strides=2), # 高宽各减半的池化层
layers.ReLU(), # 激活函数
layers.Conv1D(16,kernel_size=3,strides=1), # 第二个卷积层, 16 个 3x3 卷积核
layers.Dropout(rate=0.3), # 丢弃层,防止过拟合
layers.MaxPooling1D(pool_size=2,strides=2), # 高宽各减半的池化层
layers.ReLU(), # 激活函数
layers.Flatten(), # 打平层,方便全连接层处理
layers.Dense(120, activation='relu'), # 全连接层,120 个节点
layers.Dense(84, activation='relu'), # 全连接层,84 节
layers.Dense(84, activation='relu'),
layers.Dense(10, activation='softmax') # 全连接层,10 个节点
])
# build 一次网络模型,给输入 X 的形状,其中 4 为随意给的 batchsz
network.build(input_shape=(100,32,32,1))
# 统计网络信息
network.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv1d (Conv1D) (None, 26, 32) 2720
_________________________________________________________________
dropout (Dropout) (None, 26, 32) 0
_________________________________________________________________
max_pooling1d (MaxPooling1D) (None, 13, 32) 0
_________________________________________________________________
re_lu (ReLU) (None, 13, 32) 0
_________________________________________________________________
conv1d_1 (Conv1D) (None, 11, 16) 1552
_________________________________________________________________
dropout_1 (Dropout) (None, 11, 16) 0
_________________________________________________________________
max_pooling1d_1 (MaxPooling1 (None, 5, 16) 0
_________________________________________________________________
re_lu_1 (ReLU) (None, 5, 16) 0
_________________________________________________________________
flatten (Flatten) (None, 80) 0
_________________________________________________________________
dense (Dense) (None, 120) 9720
_________________________________________________________________
dense_1 (Dense) (None, 84) 10164
_________________________________________________________________
dense_2 (Dense) (None, 84) 7140
_________________________________________________________________
dense_3 (Dense) (None, 10) 850
=================================================================
Total params: 32,146
Trainable params: 32,146
Non-trainable params: 0
_________________________________________________________________
2.2创建 4 层全连接网络model
model = keras.Sequential([
layers.Flatten(), # 打平层,方便全连接层处理
layers.Dense(120, activation='relu'), # 全连接层,120 个节点
layers.Dense(84, activation='relu'), # 全连接层,84 节点
layers.Dense(84, activation='relu'),
layers.Dense(10, activation='softmax') # 全连接层,10 个节点
])
# build 模型,并打印模型信息
model.build(input_shape=(100,28,28))
model.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten_1 (Flatten) (100, 784) 0
_________________________________________________________________
dense_4 (Dense) (100, 120) 94200
_________________________________________________________________
dense_5 (Dense) (100, 84) 10164
_________________________________________________________________
dense_6 (Dense) (100, 84) 7140
_________________________________________________________________
dense_7 (Dense) (100, 10) 850
=================================================================
Total params: 112,354
Trainable params: 112,354
Non-trainable params: 0
_________________________________________________________________
2.3 两个网络结构对比
借助全连接神经网络导致计算量过大,数据特征利用效率低。
卷积神经网络通过卷积与池化的计算,提高了数据特征的利用,减少了参数,推动了深度学习。
2.4 卷积神经网络模型参数笔记
Conv2D卷积的参数用法
tf.keras.layers.Conv2D(
filters,
kernel_size,
strides=(1, 1),
padding=‘valid’,
data_format=None,
dilation_rate=(1, 1),
activation=None
)
1.filter:卷积核的个数
2.kenel_size:卷积核尺寸,
如果是正方形,则用一个整数表示;x
如果是长方形,则需要明确指明高用h表示,宽用w表示,
可以用元组或者列表的形式表示两者(如:[h,w]或者{h, w})
3.strides:滑动步长,默认横纵向滑动步长均为1(1,1),
也可以设置其他步长(纵向步长,横向步长)
4.padding:补零策略,
当padding = 'same’时,全零填充,
当padding = ‘valid‘则不需要填充。不区分大小写
5.data_format:输入数据的格式,
值有两种channels_first(输入和输出的shape为(batch_size, channels,height, width),
即为(图片数量,通道数, 长,宽))、
channels_last(默认值通道数为左最后一个)
6.dalition_rate:数组或者列表,
卷积核的膨胀系数(将卷积核进行形状膨胀,新的位置用0填充)
新卷积核的尺寸核膨胀系数的计算公式如下:
原卷积核的尺寸S,膨胀系数K,则膨胀后的卷积核尺寸为
size = K*(S-1)+1
7.activaton:激活函数,
相当于经过卷积输出后,再经过一次激活函数
(常见的激活函数有relu,softmax,selu)Pooling池化层的用法
为了减少运算量和数据维度而设置的一种层。
层类型:Pooling
必须设置的参数:
kernel_size: 池化的核大小。
也可以用kernel_h和kernel_w分别设定(w,h)。
其它参数:
pool: 池化方法,默认为MAX。目前可用的方法有MAX, AVE, 或STOCHASTIC
pad: 和卷积层的pad的一样,进行边缘扩充。默认为0
stride: 池化的步长,
默认为1。一般我们设置为2,即不重叠。
也可以用stride_h和stride_w来设置。
3,编译模型
# 编译全连接网络模型model,使用Adam优化器
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
# 编译卷积网络模型network,使用Adam优化器
network.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
4,训练模型
4.1训练全连接网络模型model
train_1= model.fit(x_train,y_train,
validation_split=0.2,
epochs=230,
batch_size=100,
verbose=2)
Epoch 220/230
456/456 - 1s - loss: 4.9671e-11 - accuracy: 1.0000 - val_loss: 0.2933 - val_accuracy: 0.9777
Epoch 221/230
456/456 - 1s - loss: 3.3985e-11 - accuracy: 1.0000 - val_loss: 0.2935 - val_accuracy: 0.9778
Epoch 222/230
456/456 - 1s - loss: 4.4442e-11 - accuracy: 1.0000 - val_loss: 0.2938 - val_accuracy: 0.9778
Epoch 223/230
456/456 - 1s - loss: 3.9214e-11 - accuracy: 1.0000 - val_loss: 0.2940 - val_accuracy: 0.9778
Epoch 224/230
456/456 - 1s - loss: 3.6599e-11 - accuracy: 1.0000 - val_loss: 0.2943 - val_accuracy: 0.9778
Epoch 225/230
456/456 - 1s - loss: 5.2285e-11 - accuracy: 1.0000 - val_loss: 0.2946 - val_accuracy: 0.9777
Epoch 226/230
456/456 - 1s - loss: 4.1828e-11 - accuracy: 1.0000 - val_loss: 0.2948 - val_accuracy: 0.9777
Epoch 227/230
456/456 - 1s - loss: 2.6142e-11 - accuracy: 1.0000 - val_loss: 0.2951 - val_accuracy: 0.9778
Epoch 228/230
456/456 - 1s - loss: 2.8757e-11 - accuracy: 1.0000 - val_loss: 0.2955 - val_accuracy: 0.9778
Epoch 229/230
456/456 - 1s - loss: 3.9214e-11 - accuracy: 1.0000 - val_loss: 0.2957 - val_accuracy: 0.9778
Epoch 230/230
456/456 - 1s - loss: 3.3985e-11 - accuracy: 1.0000 - val_loss: 0.2960 - val_accuracy: 0.9778
4.2训练卷积网络模型network
train_2= network.fit(x_train,y_train,
validation_split=0.2,
epochs=230,
batch_size=100,
verbose=2)
456/456 - 1s - loss: 0.0311 - accuracy: 0.9901 - val_loss: 0.0461 - val_accuracy: 0.9875
Epoch 220/230
456/456 - 1s - loss: 0.0329 - accuracy: 0.9889 - val_loss: 0.0445 - val_accuracy: 0.9882
Epoch 221/230
456/456 - 1s - loss: 0.0314 - accuracy: 0.9895 - val_loss: 0.0463 - val_accuracy: 0.9882
Epoch 222/230
456/456 - 1s - loss: 0.0309 - accuracy: 0.9899 - val_loss: 0.0518 - val_accuracy: 0.9860
Epoch 223/230
456/456 - 1s - loss: 0.0342 - accuracy: 0.9897 - val_loss: 0.0439 - val_accuracy: 0.9877
Epoch 224/230
456/456 - 1s - loss: 0.0323 - accuracy: 0.9890 - val_loss: 0.0469 - val_accuracy: 0.9885
Epoch 225/230
456/456 - 1s - loss: 0.0278 - accuracy: 0.9908 - val_loss: 0.0479 - val_accuracy: 0.9871
Epoch 226/230
456/456 - 1s - loss: 0.0328 - accuracy: 0.9898 - val_loss: 0.0477 - val_accuracy: 0.9868
Epoch 227/230
456/456 - 1s - loss: 0.0314 - accuracy: 0.9898 - val_loss: 0.0428 - val_accuracy: 0.9880
Epoch 228/230
456/456 - 1s - loss: 0.0319 - accuracy: 0.9898 - val_loss: 0.0464 - val_accuracy: 0.9879
Epoch 229/230
456/456 - 1s - loss: 0.0297 - accuracy: 0.9901 - val_loss: 0.0479 - val_accuracy: 0.9868
Epoch 230/230
456/456 - 1s - loss: 0.0315 - accuracy: 0.9896 - val_loss: 0.0499 - val_accuracy: 0.9871
4.3训练过程损失可视化
plt.plot(train_1.history["loss"],color="b",label='CF_loss')
plt.plot(train_2.history["loss"],color="r",label='CNN_loss')
plt.plot(train_1.history["val_loss"],color="g",label='CF_val_loss')
plt.plot(train_2.history["val_loss"],color="m",label='CNN_val_loss')
plt.legend()
4.4训练过程准确度可视化
plt.plot(train_1.history["accuracy"],color="b",label='CF_accuracy')
plt.plot(train_2.history["accuracy"],color="r",label='CNN_accuracy')
plt.plot(train_1.history["val_accuracy"],color="g",label='CF_val_accuracy')
plt.plot(train_2.history["val_accuracy"],color="m",label='CNN_val_accuracy')
plt.legend()
5.评估模型
5.1根据损失和准确度数据评估
对比CF与CNN模型,在两个模型全连接二都没加丢弃层的情况下,结论如下:
CNN:train loss 不断下降,val_loss不断下降,说明网络仍在学习;(最好的)
CF: train loss 不断下降,val_loss先增大,然后收敛,说明验证集准确率在下降,网络模型训练过拟合;(可以考虑加正则化惩罚)
5.2通过验证集评估
评估模型时model和network(CF,CNN)中保存了训练集训练后的w,和b
为了观测训练后的两模型是否具良好的泛化、满意的准确率,需要对模型进行评估。
1,先将一部分未训练的数据
(这里用到的数据是数据准备阶段分割出来的验证集x_valid,y_valid表示验证集的标签)
2,判断网络对不认识的数据(这里指验证集x_valid)识别的准确率是否达到要求。
# 没经过训练集(x_train)训练前模型,对验证集识别准确率应该特别低,
# 以network(CNN)为例,模型没有训练前对验证集预测的准确率(y_valid)如下
# 94/94 [==============================] - 0s 2ms/step - loss: 2.3018 - accuracy: 0.1183
# 验证集准确率accuracy: 0.11833333224058151
3,根据评估判断是否需要调节参数
# 评估CF模型
valid_loss1, valid_acc1 = model.evaluate(x_valid,y_valid)
# 评估cnn模型
valid_loss2, valid_acc2 = network.evaluate(x_valid,y_valid)
print('CF 验证集准确率accuracy:',valid_acc1)
print('CNN验证集准确率accuracy:',valid_acc2)
WARNING:tensorflow:Layer flatten_1 is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2. The layer has dtype float32 because its dtype defaults to floatx.
If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.
To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.
94/94 [==============================] - 0s 1ms/step - loss: 0.2315 - accuracy: 0.9860
94/94 [==============================] - 1s 11ms/step - loss: 0.0325 - accuracy: 0.9907
CF 验证集准确率accuracy: 0.9860000014305115
CNN验证集准确率accuracy: 0.9906666874885559
上面的结论与模型的评估是一致的
从评估的数据得出CNN在验证集上的表现更好,说明该网络泛化的更好
6.使用模型
predictions0 = model.predict(x_test)# CF使用模型model
predictions = network.predict(x_test)# CNN 使用模型 network
6.1CF模型测试可视化
# 定义CF显示图像数据及其对应标签可视化函数
def test_plot(images, labels, preds, index=0, num=10):
fig = plt.gcf()
fig.set_size_inches(10,4)
if num > 10:
num = 10
for i in range(0,num):
ax = plt.subplot(2, 5, i+1)
ax.imshow(np.reshape(images[index], (28, 28)), cmap='binary')
title = "label=" + str(y_test.numpy().tolist()[index])# y_test为张量,用索引访问会输出形状,数据类型,以及array,这里需要先转换为列表再索引。
if len(predictions0)>0:
title += ",perdict" + str(np.argmax(predictions0[index]))
ax.set_title(title, fontsize=10)
ax.set_xticks([]);
ax.set_yticks([])
index = index + 1
plt.show()
# CF显示图像数据及其对应标签
test_plot(x_test, y_test, predictions0,10,10)
6.2CNN模型图像检测可视化
# 定义CNN显示图像数据及其对应标签可视化函数
def plot_images_labels_prediction(
images, # 图像列表
labels, # 标签列表
prediction, # 预测值列表
index, # 从第index个开始显示
num = 5 # 确定一次显示5幅
):
fig = plt.gcf() # 获取当前图表,Get Current Figure
fig.set_size_inches(12, 6) # 1英寸等于 2.54 cm
if num > 10:
num = 10 # 最多显示10个子图
for i in range(0, num):
ax = plt.subplot(2, 5, i + 1) # 获取当前要处理的子图
ax.imshow(images[index], # 显示第index个图像
cmap = 'binary')
title = 'labels:'+ str( # 构建该图上要显示的titl
y_test.numpy().tolist()[index]# y_test为张量,用索引访问会输出形状,数据类型,以及array,这里需要先转换为列表再索引。
)
if len(prediction) > 0:
title += ' => '+"test result:" + str(np.argmax(predictions[index]))
ax.set_title(title,fontsize = 10) # 显示图上的title信息
index += 1
plt.show()
# CNN显示图像数据及其对应标签
plot_images_labels_prediction(x_test,y_test,predictions,50,10)