基于简单的全连接网络实现MNIST手写体数据集的训练与识别

204 阅读10分钟

依据“五步法”原则,使用Tensorflow API:tf.keras搭建神经网络结构并训练(只用到了Flatten和Dense层)

  • train,test

  • Sequential / Class

  • compile

  • fit

  • summary

 

然后依据“三步法”原则,完成给图识物应用(识别模块)

  • 复现模型(前向传播)
  • 加载参数
  • 预测结果

 

 

一、训练

1.1 train,test

使用数据集官方给出的读取命令:

mnist = tf.keras.datasets.mnist               
(x_train,y_train),(x_test,y_test) = mnist.load_data()

为了保证喂入网络的数据更加规整,对x_train和x_test进行归一化处理,同除以他们的最大值255,让他们都转化为0到1之间的小数:

x_train , x_test = x_train/255.0 , x_test/255.0 

 

需要提到的是:对于单通道灰度图数据集(如mnist数据集),如果想在网络中加入卷积层,就必须重构他们的维度到四维,增加的最后一个维度通常设为它的通道数。否则会报错如下:

ValueError: Input 0 of layer conv2d is incompatible with the layer:expected ndim=4, found ndim=3. Full shape received: [None, 28, 28]

意指conv2d卷积层需要四个参数(batch数量,row,col,通道数量),而你只给了三个(batch数量,row,col)

修改方法为:

x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)  
x_test = x_test.reshape(x_test.shape[0], 28, 28, 1) 

当然,如果不加入卷积层,或者数据集本身就是三通道彩色,就不需要上面这两行代码。

思路来源:blog.csdn.net/tushuguan_s…

 

 

 

1.2 Sequential / Class

在这一步中开始描述网络结构,即都有哪几层,每层是什么样子。在最初开始的学习中,老师教给我们使用

model = tf.keras.models.Sequential([定义网络结构块])

的方法来逐层书写,但后来指出用Sequential只能搭建出上层输出就是下层输入的顺序网络结构,无法写出一些带有跳连的非顺序网络结构,所以推荐我们使用class类来封装一个神经网络结构,具体如下:

class MnistModel(Model):  #Model表
    def __init__(self):   
        super(MnistModel,self).__init__()
        定义网络结构块

    def call(self,x):    
    	调用网络结构块,实现前向传播
        return y
model = MnistModel()    #实例化

类中的Model表示继承了Tensorflow的Model类,类里定义__init__call两个函数,使用前者来定义网络结构,使用后者来使对象变为可调用。可适用各类网络结构,在以后的使用中,基本上只修改这一处即可,其余可以作为模板使用。

 

对于具体的网络结构块,有如下可选(本文只用到了前两个,Conv2D和LSTM在后续介绍):

  • 拉直层:tf.keras.layers.Flatten()

    这一层不含计算,只是形状转换,把输入特征拉直变成一维数组

  • 全连接层:tf.keras.layers.Dense(神经元个数, activation='激活函数',kernel_regularizer=正则化方法)

    • activation(字符串给出)可选:relu、softmax、sigmoid、tanh等

    • kernel_regularizer可选:tf.keras.regularizers.l1()、tf.keras.regularizers.l2()等

  • 卷积层:tf.keras.layers.Conv2D(filters=卷积核个数, kernel_size=卷积核尺寸, strides=卷积步长, padding="valid" or "same")

  • LSTM层:tf.keras.layers.LSTM()

 

 

 

1.3 compile

配置神经网络的训练方法。在这里,告知训练时选择的优化器、损失函数和评测指标。

model.compile(
    optimizer='优化器',  
    loss='损失函数'
    metrics=['评测指标']  
)
  • optimizer可选:

    • 'sgd' or tf.keras.optimizers.SGD(lr=学习率,momentum=动量参数)
    • 'adagrad' or tf.keras.optimizers.Adagrad(lr=学习率)
    • 'adadelta' or tf.keras.optimizers.Adadelta(lr=学习率)
    • 'adam' or tf.keras.optimizers.Adam(lr=学习率,beta_1=0.9,beta_2=0.999)
  • loss可选:

    • 'mse' or tf.keras.losses.MeanSquaredError()

    • 'sparse_categorical_crossentropy' or tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False)

      (如果最后一个激活函数使用的是softmax,这里填False;否则为True)

  • Metrics可选:

    • 'accuracy':y和y_都是数值,如y=[1],y__=[1]
    • 'categorical_accuracy':y和y_都是独热码(概率分布),如y=[0,1,0],y__=[0.256,0.695,0.048]
    • 'sparse_categorical_accuracy':y是独热码(概率分布),y_是数值,如y__=[0.256,0.695,0.048],y=[1]

 

 

 

1.4 fit

执行训练过程,告知训练集的输入特征与标签、每次喂入神经网络的样本数、数据集的迭代次数、测试集的输入特征与标签(两种实现方法)、每迭代多少次就使用测试集来验证一次结果。

model.fit(
    x_train,y_train,						#训练集的输入特征、标签
    batch_size=32,                          #每次喂入神经网络的样本数
    epochs=10,                              #数据集迭代次数
    validation_data=(x_test,y_test),		#测试集的输入特征、标签
    	#validation_split=0.2				#从训练集中划分多少比例给测试集,与上面方法二者选其一	
    validation_freq=1,                      #每迭代一次训练集执行一次测试集的评测

 

另外,在compile和fit步骤间,可以实现断点续训:把每次训练好的最优参数保存下来,在下一次训练时,从最优的参数开始继续训练,并且将参数写入txt文本中方便查看。代码如下:讲解会单独用一篇文章,并且记录过程中遇到的问题。。。

 

#  三:配置训练方法
model.compile(
    optimizer='adam',    #优化器选择adam
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),  #损失函数选择SparseCategoricalCrossentropy,因为前面已经保证输出满足概率分布,所以这里from_logits=False
    metrics=['sparse_categorical_accuracy']   #数据集中标签是数值,输出结果y是概率分布,所以衡量方法选择sparse_categorical_accuracy
)


#  (断点续训,存取模型参数。  在下次训练时,从之前获取的最优的参数开始,提高了准确率)
    #读取模型
checkpoint_save_path='./checkpoint/mnist.ckpt'
if os.path.exists(checkpoint_save_path+'.index'):   #生成ckpt文件时会自动生成索引文件,所以拿它的索引文件来判断
    print('------加载已有模型------')
    model.load_weights(checkpoint_save_path)       #如果存在模型,则直接读取


    #保存模型
cp_callback = tf.keras.callbacks.ModelCheckpoint(   #使用tf给出的回调函数来保存模型参数
    filepath=checkpoint_save_path,
    save_weights_only=True,    #是否只保留模型参数
    save_best_only=True        #是否只保留最优结果
)








#  四:执行训练过程
history = model.fit(
    x_train,y_train,
    batch_size=32,                          #每次喂入网络32组数据
    epochs=10,                               #数据集迭代10次
    validation_data=(x_test,y_test),
    validation_freq=1,                      #每迭代一次训练集执行一次测试集的评测
    callbacks=[cp_callback])                #加入回调选项,返回给history。(如果不用断点续训,则不用写 “history=” 和 “callbacks=[cp_callback]” )


#通过写入到txt文本的方式查看断点续训时保存的参数
file = open('./weights.txt','w')
for v in model.trainable_variables:
    file.write(str(v.name) + '\n')
    file.write(str(v.shape) + '\n')
    file.write(str(v.numpy()) + '\n')
file.close()

 

 

 

1.5 summary

打印出神经网络的结构和参数信息。如图1所示。

图1:网络结构和参数信息

 

 

*1.6 acc/loss可视化

其实这一步并不是神经网络训练的必需步骤,但我觉得如果写论文的话,这种图表是非常必要的,能够形象的展示自己的成果也是一门技巧。直接贴代码:

acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1,2,1)
plt.plot(acc,label='Training Accuracy')
plt.plot(val_acc,label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1,2,2)
plt.plot(loss,label='Training Loss')
plt.plot(val_loss,label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()

plt.show()

为减少我辣鸡GPU的消耗,epochs仅设为3........结果如图2所示。

图2:acc/loss曲线

 

 

 

训练——代码实现

import tensorflow as tf
from tensorflow.keras.layers import Flatten,Dense
from tensorflow.keras import Model
import os
import numpy as np
np.set_printoptions(threshold=np.inf)  #设置print输出格式,通过np.inf使完全输出,不允许用省略号代替
from matplotlib import pyplot as plt




#  一:导入数据集,设定训练集和测试集的特征和标签
mnist = tf.keras.datasets.mnist                         #下载手写数字数据集
(x_train,y_train),(x_test,y_test) = mnist.load_data()   #指定训练集和测试集的输入特征和标签
x_train , x_test = x_train/255.0 , x_test/255.0         #对输入网络的特征进行归一化。全部转化为0到1之间的数,数值变小有利于神经网络的吸收

""" 
#注意:如果有卷积层,数据集又是单通道灰度图片(如本数据集mnist),需要添加如下两行代码。 具体参考:https://blog.csdn.net/tushuguan_sun/article/details/105914661
x_train = x_train.reshape(x_train.shape[0], 28, 28, 1)  
x_test = x_test.reshape(x_test.shape[0], 28, 28, 1) 

"""





#  二:搭建网络结构
class Mnist_Base(Model):   #Class写法可以满足顺序和跳连两种神经网络结构;而Sequential写法只支持顺序神经网络结构
    def __init__(self):     #定义网络结构
        super(Mnist_Base,self).__init__()

        self.flatten=Flatten()    #把输入特征(二维图像矩阵)拉直为一维数组
        self.d1=Dense(128,activation='relu')   #第一层网络:全连接层,128个神经元,激活函数使用relu
        self.d2=Dense(10,activation='softmax') #第二层网络:全连接层,10个神经元(因为这是一个10分类问题),激活函数使用softmax,使输出满足概率分布

    def call(self,x):    #call函数的作用是声明对象为可调用对象(callable)。从输入x到输出y,相当于走过一次前向传播,输出y
        x = self.flatten(x)
        x = self.d1(x)
        y = self.d2(x)
        return y

model = Mnist_Base()    #实例化








#  三:配置训练方法
model.compile(
    optimizer='adam',    #优化器选择adam
    loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False),  #损失函数选择SparseCategoricalCrossentropy,因为前面已经保证输出满足概率分布,所以这里from_logits=False
    metrics=['sparse_categorical_accuracy']   #数据集中标签是数值,输出结果y是概率分布,所以衡量方法选择sparse_categorical_accuracy
)


#  (断点续训,存取模型参数。  在下次训练时,从之前获取的最优的参数开始,提高了准确率)
    #读取模型
checkpoint_save_path='./checkpoint/mnist.ckpt'
if os.path.exists(checkpoint_save_path+'.index'):   #生成ckpt文件时会自动生成索引文件,所以拿它的索引文件来判断
    print('------加载已有模型------')
    model.load_weights(checkpoint_save_path)       #如果存在模型,则直接读取


    #保存模型
cp_callback = tf.keras.callbacks.ModelCheckpoint(   #使用tf给出的回调函数来保存模型参数
    filepath=checkpoint_save_path,
    save_weights_only=True,    #是否只保留模型参数
    save_best_only=True        #是否只保留最优结果
)




#  四:执行训练过程
history = model.fit(
    x_train,y_train,
    batch_size=32,                          #每次喂入网络32组数据
    epochs=10,                              #数据集迭代10次
    validation_data=(x_test,y_test),
    validation_freq=1,                      #每迭代一次训练集执行一次测试集的评测
    callbacks=[cp_callback])                #加入回调选项,返回给history。(如果不用断点续训,则不用写 “history=” 和 “callbacks=[cp_callback]” )





#通过写入到txt文本的方式查看断点续训时保存的参数
file = open('./weights.txt','w')
for v in model.trainable_variables:
    file.write(str(v.name) + '\n')
    file.write(str(v.shape) + '\n')
    file.write(str(v.numpy()) + '\n')
file.close()









#  五:打印网络结构和参数信息
model.summary()







#  六:展示acc和loss曲线 (断点续训中history里已经保存好了)
acc = history.history['sparse_categorical_accuracy']
val_acc = history.history['val_sparse_categorical_accuracy']
loss = history.history['loss']
val_loss = history.history['val_loss']

plt.subplot(1,2,1)
plt.plot(acc,label='Training Accuracy')
plt.plot(val_acc,label='Validation Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

plt.subplot(1,2,2)
plt.plot(loss,label='Training Loss')
plt.plot(val_loss,label='Validation Loss')
plt.title('Training and Validation Loss')
plt.legend()

plt.show()

在epochs设为3的情况下,结果如图3所示。

图3:训练结果

 

 

 

 

二、识别

2.1 复现模型

相当于重新叙述了一遍网络结构(代码完全一致)。

但需要注意的是:测试时使用的网络搭建方法必须要与训练时保持一致

即:要么都用Sequential方法,要么都用Class方法,否则会在加载参数的时候报错如图4所示。

图4:模型建立方法不一致导致的错误

 

 

 

2.2 加载参数

使用load_weights方法加载训练时保存的参数信息mnist.ckpt

model_save_path = './checkpoint/mnist.ckpt'
model.load_weights(model_save_path)

 

 

 

2.3 预测结果

取测试图片做预处理,然后使用predict函数做预测。预处理包括尺寸的剪裁、灰度处理、底色与字体颜色与训练图片统一、归一化、维度变换等。送入predict函数,视返回的概率最大的数据为最终预测结果。

 

特别的,对于维度变换:

由于神经网络训练时都是按照batch送入的,所以,进入predict函数前,先要把img_arr前面添加一个维度:从28行28列的二维数据变为1个28行28列的三维数据。即:(28,28) ——> (1,28,28)

 

 

 

测试——代码实现

import tensorflow as tf
from tensorflow.keras.layers import Flatten,Dense
from tensorflow.keras import Model

import numpy as np
np.set_printoptions(threshold=np.inf)#设置print输出格式,通过np.inf使完全输出,不允许用省略号代替
from matplotlib import pyplot as plt
from PIL import Image


#  一:复现模型(前向传播)  (注意:如果训练时用的是Class,那么测试时也要用Class来复现,否则会报错)
class MnistModel(Model):  
    def __init__(self):     #定义网络结构
        super(MnistModel,self).__init__()

        self.flatten=Flatten()   
        self.d1=Dense(128,activation='relu')   
        self.d2=Dense(10,activation='softmax') 

    def call(self,x):    
        x = self.flatten(x)
        x = self.d1(x)
        y = self.d2(x)
        return y
model = MnistModel()    #实例化


""" 
#  一:复现模型(前向传播)   Sequential写法
model = tf.keras.models.Sequential([
    tf.keras.layers.Flatten(),
    tf.keras.layers.Dense(128, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')]) 
    
"""






#  二:加载参数
model_save_path = './checkpoint/mnist.ckpt'
model.load_weights(model_save_path)







#  三:取测试图片做预处理,然后使用predict函数做预测
img = Image.open('test/8.png')
img = img.resize((28,28),Image.ANTIALIAS)   
img_arr = np.array(img.convert('L'))     #转换为灰度图
img_arr = 255 - img_arr                  #第一种方法:将测试用的白底灰字图  转化为  训练用的黑底白字图
""" 
for i in range(28):                      #第二种方法:将测试用的白底灰字图  转化为  只有黑白两色的高对比度图片。在保留有效信息时滤去背景噪声,图片更纯净。阈值自定义
    for j in range(28):
        if img_arr[i][j] < 200:
            img_arr[i][j] = 255
        else:
            img_arr[i][j] = 0 
"""


img_arr = img_arr / 255.0                #归一化
x_predict = img_arr[tf.newaxis,...]      #维度变换
result = model.predict(x_predict)        #送入predict函数,返回概率数组
pred = tf.argmax(result,axis=1)          #选取概率最大的为最终结果
tf.print(pred)

输出结果很简单,就是预测得到的结果,单纯的“[8]” . 正确

 

 

 

 

www.bilibili.com/video/BV1B7…