各位同学大家好,今天和大家分享一下TensorFlow2.0深度学习中前向传播的推导过程,使用系统自带的mnist数据集。
1. 数据获取
首先,我们导入需要用到的库文件和数据集。导入的x和y数据是数组类型,需要转换成tensor类型tf.convert_to_tensor(),再查看一下我们读入的数据有没有问题。
import numpy as np
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import datasets # 数据集工具
import os # 设置一下输出框打印的内容
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' # '2'输出栏只打印error信息,其他乱七八糟的信息不打印
#(1)获取mnist数据集
(x,y),_ = datasets.mnist.load_data()
#(2)转换成tensor类型,x数据类型一般为float32,y存放的是图片属于哪种具体类型,属于整型
x = tf.convert_to_tensor(x,dtype=tf.float32)
y = tf.convert_to_tensor(y,dtype=tf.int32)
#(3)查看数据内容
print('shape:',x.shape,y.shape,'\ndtype:',x.dtype,y.dtype) #查看shape和数据类型
print('x的最小值:',tf.reduce_min(x),'\nx的最大值:',tf.reduce_max(x)) #查看x的数据范围
print('y的最小值:',tf.reduce_min(y),'\ny的最大值:',tf.reduce_max(y)) #查看y的数据范围
# 打印的结果如下
shape: (60000, 28, 28) (60000,)
dtype: <dtype: 'float32'> <dtype: 'int32'>
x的最小值: tf.Tensor(0.0, shape=(), dtype=float32)
x的最大值: tf.Tensor(255.0, shape=(), dtype=float32)
y的最小值: tf.Tensor(0, shape=(), dtype=int32)
y的最大值: tf.Tensor(9, shape=(), dtype=int32)
2. 数据预处理
首先对x数据进行归一化处理,原来x的每个像素值在[0,255]之间,现在转变成[0,1]之间。刚导入的y数据的shape是[6000],一维,存放分类数,为了和最后的预测结果比较,对它one-hot编码,shape变为[6000,10]。存放每张图属于每一个分类的概率。y.numpy()[0]表示第0张图像属于第5个分类的概率是1,属于其他分类的概率是0。再设置一个学习率lr,用于每次迭代完成后更新神经网络权重参数,初始学习率以 0.01 ~ 0.001 为宜。
#(4)预处理
x = x/255. # 归一化处理,将x数据的范围从[0,255]变成[0,1]
y = tf.one_hot(y,depth=10) # y是分类数值,对它进行one-hot编码,shape变为[b,10]
y.numpy()[0] # 查看编码后的y的数据
# array([0., 0., 0., 0., 0., 1., 0., 0., 0., 0.], dtype=float32)
lr = 1e-3 # 学习率,设置过大,会导致loss震荡,学习难以收敛;设置过小,那么训练的过程将大大增加
加载数据集tf.data.Dataset.from_tensor_slices(),生成迭代器的 iter(),返回迭代器的下一个项目next()
#(5)指定一个选取数据的batch,一次取128个数据
train_db = tf.data.Dataset.from_tensor_slices((x,y)).batch(128)
train_iter = iter(train_db) # 指定迭代器
sample = next(train_iter) # 存放的每一个batch
# sample[0]存放x数据,sample[1]存放y数据,每个sample有128组图片
print('batch:',sample[0].shape,sample[1].shape)
# 打印结果: batch: (128, 28, 28) (128, 10)
3. 构建网络
输入特征x的shape为[128,28,28],即输入层有28*28个神经元,自定义隐含层1有256个神经元,隐含层2有128个神经元,最终输出结果是固定的10个分类。
根据每一层神经元的个数来确定每个连接层的shape,使用随机的截断高斯分布来初始化各个权重参数,将定义的变量从tensor类型,转变为神经网络类型variable类型。
#(6)构建网络
# 输入层由输入多少个特征点决定,输出层根据有多少个分类决定
# 输入层shape[b,784],输出层shape[b,10]
# 构建网络,自定义中间层的神经元个数
# [b,784] => [b,256] => [b,128] => [b,10]
# 第一个连接层的权重和偏置,都变成tf.Variable类型,这样tf.GradientTape才能记录梯度信息
w1 = tf.Variable(tf.random.truncated_normal([784,256], stddev=0.1)) # 截断正态分布,标准差调小一点防止梯度爆炸
b1 = tf.Variable(tf.zeros([256])) #维度为[dim_out]
# 第二个连接层的权重和偏置
w2 = tf.Variable(tf.random.truncated_normal([256,128], stddev=0.1)) # 截断正态分布,维度为[dim_in, dim_out]
b2 = tf.Variable(tf.zeros([128])) #维度为[dim_out]
# 第三个连接层的权重和偏置
w3 = tf.Variable(tf.random.truncated_normal([128,10], stddev=0.1)) # 截断正态分布,维度为[dim_in, dim_out]
b3 = tf.Variable(tf.zeros([10])) #维度为[dim_out]
4. 前向传播运算
每一次迭代从train_db中取出128个样本数据,由于取出的x数据的shape是[128,28,28],需要将它的形状转变成[128,28*28]才能传入输入层tf.reshape()。h = x @ w + b,本层的特征向量和权重做内积,再加上偏置,将计算结果放入激活函数tf.nn.relu(),得到下一层的输入特征向量。最终得到的输出结果out中存放的是每张图片属于每个分类的概率。
#(7)前向传播运算
for i in range(10): #对整个数据集迭代10次
# 对数据集的所有batch迭代一次
# x为输入的特征项,shape为[128,28,28],y为分类结果,shape为[128,10]
for step,(x,y) in enumerate(train_db): # 返回下标和对应的值
# 这里的x的shape为[b,28*28],从[b,w,h]变成[b,w*h]
x = tf.reshape(x,[-1,28*28]) #对输入特征项的维度变换,-1会自动计算b
with tf.GradientTape() as tape: # 自动求导计算梯度,只会跟踪tf.Variable类型数据
# ==1== 从输入层到隐含层1的计算方法为:h1 = w1 @ x1 + b1
# [b,784] @ [784,256] + [b,256] = [b,256]
h1 = x @ w1 + b1 # 相加时会自动广播,改变b的shape,自动进行tf.broadcast_to(b1,[x.shape[0],256])
# 激活函数,relu函数
h1 = tf.nn.relu(h1)
# ==2== 从隐含层1到隐含层2,[b,256] @ [256,128] + [b,128] = [b,128]
h2 = h1 @ w2 + b2
h2 = tf.nn.relu(h2)
# ==3== 从隐含层2到输出层,[b,128] @ [128,10] + [b,10] = [b,10]
out = h2 @ w3 + b3 # shape为[b,10]
#(8)计算误差,输出值out的shape为[b,10],onehot编码后真实值y的shape为[b,10]
# 计算均方差 mse = mean(sum((y-out)^2)
loss_square = tf.square(y-out) # shape为[b,10]
loss = tf.reduce_mean(loss_square) # 得到一个标量
# 梯度计算
grads = tape.gradient(loss,[w1,b1,w2,b2,w3,b3])
# 注意:下面的方法,运算返回值是tf.tensor类型,在下一次运算会出现错误
# w1 = w1 - lr * grads[0] # grads[0]值梯度计算返回的w1,是grad的第0个元素
# 权重更新,lr为学习率,梯度每次下降多少
# 因此需要原地更新函数,保证更新后的数据类型不变tf.Variable
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])
if step % 100 == 0: #每100次显示一次数据
print(f"第{step+1}次迭代,loss为{np.float(loss)}") #loss是tensor变量
计算输出结果和真实结果之间的均方差作为模型损失。使用tf.GradientTape()中的梯度计算方法tape.gradient(),用于更新下一次迭代的权重参数。公式为w1 = w1 - lr * grads[n],但由于该公式返回的是tensor类型的变量,而tf.GradientTape()梯度计算方法,只能跟踪计算tf.variable类型的数据。因此需要使用assign_sub()函数原地更新权重参数,不造成变量类型改变。
# 最后一次循环循环,loss的输出结果为:
第1次迭代,loss为0.08258605003356934
第101次迭代,loss为0.09005936980247498
第201次迭代,loss为0.0828738585114479
第301次迭代,loss为0.0822446346282959
第401次迭代,loss为0.08802710473537445