1、问题描述
MNIST数据集包含70K张手写数字体,如下图,每张为28*28的像素图,使用其中的60K张手写数字体图片训练模型,使用10K张手写数字体测试模型的权重w和偏置b的性能
2、解决原理
数据处理:
(1)将每张图片28*28的二维数组表示为一个长度为28*28=784的一维数组存储图片信息
(2)多张图片表示方式为X:[b,784],其中b表示图片数量
(3)将标签进行独热编码:
dog = [1, 0, 0, …]
cat = [0, 1, 0, …]
fish = [0, 0, 1, …]
...
计算:
(1)简单流程
▪ out = X@W + b //输出out=矩阵X乘矩阵W加类别b
▪ X : [b, 784]
▪ W: [784, 10] //W根据相应矩阵维度匹配
▪ b : [10] //标签为10类
▪ out: [b, 10]
(2)添加激活函数与隐藏层
▪out = relu(X@W + b) //添加relu函数(引入非线性因子)
▪h1= relu(X@W1 + b1) //隐藏层1
▪h2 = relu(h1@W2 + b2) //隐藏层2
▪out = relu(h2@W3 + b3) //输出层
▪loop
(3)预测与loss
pred = argmax(out) //最大值为预测结果
loss=MSE(out, label) //loss值为预测结果与真实值的欧氏距离
3、代码实现
import os
# cpp会打印无关信息,默认为0全部打印,设置为2只打印error信息
# 该行代码有时候写在import tensorflow前才有效
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'
import tensorflow as tf
from tensorflow.keras import datasets
from tensorflow import keras
# 自动下载数据集,x:[60, 28, 28] y:[60k]
(x, y), _ = datasets.mnist.load_data()
# 将numpy数据转变为Tensor数据,且将x:[0~255]->[0~1.]
x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.
y = tf.convert_to_tensor(y, dtype=tf.int32)
# 观察数据shape与类型是否与预期一致
print(x.shape, y.shape, x.dtype, y.dtype)
# 输出x、y最大值与最小值
print(tf.reduce_min(x), tf.reduce_max(x))
print(tf.reduce_min(y), tf.reduce_max(y))
# 创建数据集对象
train_db = tf.data.Dataset.from_tensor_slices((x, y)).batch(128)
# 创建迭代器
train_iter = iter(train_db)
# 用next对迭代器不停迭代,直到获得最终数据
sample = next(train_iter)
# 输出一个batch的第一个元素和第二个元素
print('batch:', sample[0].shape, sample[1].shape)
# batch: (128, 28, 28) (128,)
# 降维:[b, 784] => [b, 256] => [b, 128] => [b, 10]
# w.shape:[dim_in, dim_out], b.shape:[dim_out]
# 用tf.Variable方法包装权值,使之可以求导训练
# w使用截断分布truncated_normal随机初始化,默认方差mean=0,均值stddev=1
# 如果stddev设置不恰当可能会出现梯度爆炸(not a number)、梯度离散的问题
w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))
# b一般初始化为0
b1 = tf.Variable(tf.zeros([256]))
w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1))
b2 = tf.Variable(tf.zeros([128]))
w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1))
b3 = tf.Variable(tf.zeros([10]))
# 设置参数lr,表示梯度步长(学习速度)
lr = 1e-3
#增加迭代次数
for epoch in range(10): # iterate db for 10
# 使train_db进行迭代,使用enumerate、step会在每一个batch后返回step数值
for step,(x, y) in enumerate(train_db):
# train_db中x与y的shape,x:[128, 28,28],y:[128]
# 维度变换:[b, 28, 28] => [b, 28 * 28],“-1”使b自动计算
x = tf.reshape(x, [-1, 28 * 28])
# 使用自动求导过程
with tf.GradientTape() as tape: # tf.Variable,使tensor数据增加自动求导属性
# 设置神经网络层
# 向前传播中x的shape,x:[b, 28 * 28]
# h1 = x @ w1 + b1
# x[b,784] @ w1[784,256] + b1[256] => h1[b,256] + b2[256] => h2[b,256] + b3[b,256]
# b1可自动broadcast,使b1升维并可以与x@w1结果相加得到h1
# 完成第一层嵌套,相当于神经网络隐藏层:layers.Dense(256)
h1 = x @ w1 + tf.broadcast_to(b1, [x.shape[0], 256])
# 完成第二层嵌套,相当于神经网络隐藏层:layers.Dense(128, activation='relu')
# [b, 256] => [b, 128]
h2 = h1 @ w2 + b2
# 引入非线性因子
h2 = tf.nn.relu(h2)
# 完成第三层嵌套,相当于神经网络输出层:layers.Dense(10)
# [b, 128] => [b, 10]
out = h2 @ w3 + b3
# compute loss 计算误差,mse = mean(sum(y-out)^2)
# out: [b, 10],使y: [b] => y_onehot: [b, 10]
# 使分类进行独热编码(实际上是一种升维),depth表示类型(标签)数量
y_onehot = tf.one_hot(y, depth=10)
# 每个元素进行平方,得到[b, 10]
loss = tf.square(y_onehot - out)
# mean: 求均值(相当于loss/b/10),得到scalar
loss = tf.reduce_mean(loss)
# 计算梯度compute gradients,得到grads列表,按照顺序存储[w1, b1, w2, b2, w3, b3]
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
# print(grads)
# 更新公式w1 = w1 - lr * w1_grad
# 易错点:新的w1为Tenor类型,没有经过Variable,不能自动求导
# w1 = w1 - lr * grads[0]
# b1 = b1 - lr * grads[1]
# w2 = w2 - lr * grads[2]
# b2 = b2 - lr * grads[3]
# w3 = w3 - lr * grads[4]
# b3 = b3 - lr * grads[5]
# 原地更新方法:assign_sub(ref, value)方法:ref = ref - value
# tensor. assign_sub(value):功能tensor = tensor - assign_sub
w1.assign_sub(lr * grads[0])
b1.assign_sub(lr * grads[1])
w2.assign_sub(lr * grads[2])
b2.assign_sub(lr * grads[3])
w3.assign_sub(lr * grads[4])
b3.assign_sub(lr * grads[5])
# 每100个batch计算之后输出loss数值,用float将loss由tensor转换为numpy数值
if step % 100 == 0:
print(epoch,step, 'loss:', float(loss))
运行结果: