依据“五步法”原则,使用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)
当然,如果不加入卷积层,或者数据集本身就是三通道彩色,就不需要上面这两行代码。
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'ortf.keras.optimizers.SGD(lr=学习率,momentum=动量参数)'adagrad'ortf.keras.optimizers.Adagrad(lr=学习率)'adadelta'ortf.keras.optimizers.Adadelta(lr=学习率)'adam'ortf.keras.optimizers.Adam(lr=学习率,beta_1=0.9,beta_2=0.999)
-
loss可选:
-
'mse'ortf.keras.losses.MeanSquaredError() -
'sparse_categorical_crossentropy'ortf.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.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所示。
训练——代码实现
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所示。
二、识别
2.1 复现模型
相当于重新叙述了一遍网络结构(代码完全一致)。
但需要注意的是:测试时使用的网络搭建方法必须要与训练时保持一致
即:要么都用Sequential方法,要么都用Class方法,否则会在加载参数的时候报错如图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]” . 正确