一,思路: 分为两步,第一步是训练模型然后把模型保存到磁盘,第二步是复现模型结构然后读取模型权重系数,输入手写数字图片验证模型识别能力。
二,模型训练 在这部分使用mnist数据集,该数据集含有七万张28*28的灰度化的手写数字图片,其中六万张用于训练,一万张用于训练阶段的测试。 过程分为六步:(1.)import 相关模块 (2.)读入训练集与测试集 (3.)用Sequential模型(或者有的人叫它神经网络容器)搭建神经网络 (4.)定义神经网络训练时的优化器(或者叫参数更新的方法,例如随机梯度下降法SGD),损失函数(MSE或者交叉熵),测评指标(用来计算损失函数)(5.)训练模型 ,把最优模型保存到磁盘(6.)打印训练总览 代码如下:
import tensorflow as tf import os checkpoint_save_path = "./mnist.ckpt" # 指定模型路径 mnist = tf.keras.datasets.mnist (train_data, train_label), (test_data, test_label) = mnist.load_data() train_data = train_data / 255.0 test_data = test_data / 255.0 # 归一化,一是神经网络对0附近敏感,二是不归一化可能无法收敛 model = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), # 拉直层参数可以省略,拉直层的作用是把输入神经网络的多维数组拉直为一维数组,拉直层长度是784 tf.keras.layers.Dense(128, activation='relu'), # 隐藏层 tf.keras.layers.Dense(10, activation='softmax') # 输出层,软判决输出,输出为概率值 ]) model.compile( optimizer='adam', loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=False), metrics=['sparse_categorical_accuracy'] ) if os.path.exists(checkpoint_save_path + '.index'): print('-------------load the model-----------------') model.load_weights(checkpoint_save_path) # 从保存的模型中读取权重系数 # 如果之前并没有存储训练过的模型就忽略这个if语句块,如果有的话就从保存的权重系数开始训练,而不是随机选取初始权重 cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_save_path, save_weights_only=True, save_best_only=True) history = model.fit(train_data, train_label, batch_size=32, epochs=5, validation_data=(test_data, test_label), validation_freq=1, callbacks=[cp_callback]) # 在训练模型时,根据callbacks=[cp_callback]的配置选择保存模型最优时的权重系数 # history保存了训练阶段的loss,acc以及测试阶段时的loss,acc model.summary()对于该程序的分析,还是要抓住数据结构的变化: 刚刚读入数据集后train_dat是三维数组,shape:(60000,28,28)。第一个维度的每个元素是一个二维数组即一张图片的数据。train_label是一个shape为(60000)的一维数组,每个元素是一个数值即标签。test_data的shape(10000,28,28),test_label的shape为:(10000)。然后数据输入神经网络。神经网络拉直层无运算能力,不算层数,仅仅是把多维数组拉伸为一维数组。拉伸层之后为隐藏层,具有128个神经元,使用relu激活函数。最后是输出层,具有十个神经元,使用sotmax激活函数使得神经网络输出为概率形式。 神经网络结构示意图如下: 现在分析数据结构的变换: 输入数据按32个分成一个batch,即shape:(32,28,28)。经过拉伸层变为shape:(32,784) 拉直层到隐藏层的全连接权重系数矩阵shape:(784,128)。shape:(32,784)Xshape:(784,128) 得到的矩阵shape为(32,128),这就是隐藏层输出的数据结构。隐藏层到输出层的全连接结系数矩阵shape:(128,10)。shape(32,128)Xshape:(128,10)=shape:(32,10),这就是输出层数据数据的结构,一共有32行,每行是一个特征数据(输入的待识别图片)对于十分类的概率值,取其中概率值最大的分类对应的标签作为手写数字图片的识别结果。
然后再来看看神经网络的权重系数参数示意(如下图): 拉直层没有运算能力,没有可训练参数;全连接层1一共有784X128(隐藏层的系数矩阵元素数目)+128(偏置项元素数目)=100480个可训练参数;输出层有128X10(隐藏层与输出层之间的系数矩阵元素数目)+10(偏置项元素数目)=1290个参数。仅仅一个两层全连接神经网络就有超过十万个参数需要训练,因此在拉直层之前加入卷积层搭建卷积神经网络减少待训练参数数目是必要的。
三,复现模型结构,读取训练后得到的最优模型,然后进行实际测试
from PIL import Image import numpy as np import tensorflow as tf model_save_path = './mnist.ckpt' # 指定模型的路径 # 复现模型结构 model = tf.keras.models.Sequential([ tf.keras.layers.Flatten(), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dense(10, activation='softmax')]) # 读入已经经过训练的权重系数 model.load_weights(model_save_path) while True: image_path = input("输入图片名") img = Image.open(image_path) # 缩放输入图片,使其大小固定 img = img.resize((28, 28), Image.ANTIALIAS) # Image.ANTIALIAS抗锯齿,抗图像折叠失真 img_arr = np.array(img.convert('L')) # 转为8位宽的np.array数据类型 # 遍历每一个像素,进行阈值二值化处理,把输入图片转换为黑底白字图片,因为训练时的图片 全是黑的白字的 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, ...] # 添加一个维度,变为shape:(1,28,28),为输入predict函数做准备; # predict函数默认参数batch=32,要求输入数据是三维的。 result = model.predict(x_predict) # model.predict执行神经网络前向传播过程,得到神经网络预测结果 print(result) # 打印十分类概率值 pred = tf.argmax(result, axis=1) # 取概率最大的分类的标签值,标签值与0~9数字一一对应 print('\n') tf.print("识别结果为:", pred)四,实际测试 手写了几张数字图片(就比如下面那几张),大小可以任意,因为输入验证的时候会调整大小。图片名例如为2.png,3.png等
注:数据增强(随机旋转,随机偏移等增大数据量)之后的识别结果会更好一点。 如果不想使用keras而只用原生的tensorflow的话可以参考我的一篇博客: 最简单的单层神经网络实现鸢尾花分类
参考:北京大学《人工智能实践-Tensorflow笔记》课程