1. 加载 MNIST 数据集,得到训练集与测试集
2. 将训练集与测试集转换为DataSet对象
3. 将数据顺序打散
避免每次读取数据顺序相同,使得模型记住训练集的一些特点,降低模型泛化能力。
4. 设置批训练
从训练集总数中随机抽取batchsize个样本,来进行模型训练,相比于使用所用样本构建模型,批训练花费的时间更少,计算效率更高。每训练一个次,就叫一个step,当经历若干个step使得把训练集所有样本训练过以后,那叫一个epoch
5. 数据预处理
图片像素值进行标准化,使得处于0到1的区间
图片的类别转化成one-hot编码
图片的标签是数字0到数字10,是属于多分类问题,为了能够量化类别,将图片的类别转化成长度为10位数字的one-hot编码,便于和神经网络输出结果比较,计算其损失。
6. 神经网络构建
其主要的流程为:
设置学习率
网络结构参数初始化
计算前向传播
计算损失函数
计算梯度
根据梯度更新参数(梯度下降法)
每经过固定的step记录和输出训练误差,即均方根误差
每经过固定的step,输出测试误差,即分类正确率
1.数据处理阶段
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets # 导入经典数据集加载模块
# 1. 加载 MNIST 数据集
(x, y), (x_test, y_test) = datasets.mnist.load_data() # 返回数组的形状
# 2. 将数据集转换为DataSet对象,不然无法继续处理
train_db = tf.data.Dataset.from_tensor_slices((x, y))
# print(train_db) #
# 3. 将数据顺序打散
train_db = train_db.shuffle(10000) # 数字为缓冲池的大小
# print(train_db) #
# 4. 设置批训练
train_db = train_db.batch(512) # batch size 为 128
# print(train_db) #
# 5. 预处理函数
def preprocess(x, y): # 输入x的shape 为[b, 32, 32], y为[b]
# 将像素值标准化到 0~1区间
x = tf.cast(x, dtype=tf.float32) / 255.
# 将图片改为28*28大小的
x = tf.reshape(x, [-1, 28 * 28])
# 这个reshape我认为是和数据的存储顺序发生冲突,读取的数据应该不是原图的数据,而是被打乱的数据
# 将数据集的类别标签(数字0-10)转换为one-hot 编码
y = tf.cast(y, dtype=tf.int32) # 转成整型张量
y = tf.one_hot(y, depth=10)
return x, y
# 将数据集传入预处理函数,train_db支持map映射函数
train_db = train_db.map(preprocess)
# print(train_db) #
# 设置训练20个epoch
train_db = train_db.repeat(20) # 将train_db在内部迭代20遍
# 查看train_db的结构
x, y = next(iter(train_db))
print(x, y)
print('train sample:', x.shape, y.shape) # (512, 784) (512, 10)
# 从上面可以看出,现在的train_db已经变成可每份512*784的矩阵,有变成了每份512*10的矩阵,784表示输入的特征数,10表示输出的类别所对应的向量,即one-hot编码
# 以同样的方式处理测试集
# 转换对象
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test))
# 打乱排序,设置batchSize为128,进行预处理
test_db = test_db.shuffle(1000).batch(512).map(preprocess)
总结:tf.shuffle和tf.batch起到了打散数据顺序和设置批训练的作用,对于任何类型数据而言,在数据处理上是不可或缺的环节。对于图片数据而言,tf.one_hot可以将图片的类别进行转码,生成向量,是一个非常有用的工具。
2.构建并训练神经网络
# 7.神经网络训练
def main():
# 设置学习率
lr = 1e-2
accs, losses = [], []
# 手动设置网络层参数
# 784个节点输入 => 512个节点输出
w1, b1 = tf.Variable(tf.random.normal([784, 256], stddev=0.1)), tf.Variable(tf.zeros([256]))
# 512个节点输入 => 256个节点输出
w2, b2 = tf.Variable(tf.random.normal([256, 128], stddev=0.1)), tf.Variable(tf.zeros([128]))
# 256个节点输入 => 10个节点输入
w3, b3 = tf.Variable(tf.random.normal([128, 10], stddev=0.1)), tf.Variable(tf.zeros([10]))
#
for step, (x, y) in enumerate(train_db): # enumerate提供train_db的索引值step,表明是第几个step
# [b, 28, 28] => [b, 784]
x = tf.reshape(x, (-1, 784))
with tf.GradientTape() as tape:
# 进行前向传播
# 第一层
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
# 第二层
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# 第三层
out = h2 @ w3 + b3
# 直接输出,计算损失函数
loss = tf.square(y - out) # 10个数的方差
loss = tf.reduce_mean(loss) # 均方差
# 计算损失函数对各个参数的梯度
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
# 更新网络参数
for p, g in zip([w1, b1, w2, b2, w3, b3], grads): # 这里zip的妙用
p.assign_sub(lr * g)
# step为80次时,记录并输出损失函数结果
if step % 80 == 0:
print(step, 'loss:', float(loss))
losses.append(float(loss))
# step为80次时,用测试集验证模型
if step % 80 == 0:
total, total_correct = 0., 0
# 测试集的每一个样本
for x, y in test_db:
h1 = x @ w1 + b1
h1 = tf.nn.relu(h1)
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
out = h2 @ w3 + b3
pred = tf.argmax(out, axis=1)
y = tf.argmax(y, axis=1)
correct = tf.equal(pred, y)
# tf变为numpy计算
total_correct += tf.reduce_sum(tf.cast(correct, dtype=tf.int32)).numpy()
total += x.shape[0]
print(step, 'Evaluate Acc:', total_correct / total)
accs.append(total_correct / total)