前向传播实战
- 1、简介
- 2、前向传播实战
- 2.1 导入依赖
- 2.2 加载数据集
- 2.3 创建每个非线性层的W和b参数
- 2.4 前向计算
- 2.5 自动梯度与梯度更新
- 2.6 完整代码
- 3、总结
1、简介
我们这里使用张量的基本操作去完成三层神经网络的实现:
out=ReLU(ReLu(ReLU(X@W1+b1)@W2+b2)@W3+b3)out=ReLU(ReLu(ReLU(X@W_1+b_1)@W_2+b_2)@W_3+b3) out=ReLU(ReLu(ReLU(X@W1+b1)@W2+b2)@W3+b3)
采用的数据集是MNIST手写数字图片数据集,输入节点数为784,第一层的输出节点数是256,第二层的输出节点数是128,第三层的输出节点数是10,也就是当前样本属于10个类别的概率。
2、前向传播实战
2.1 导入依赖
import os
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras.datasets as datasets
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
plt.rcParams['font.size'] = 16
plt.rcParams['font.family'] = ['STKaiti']
plt.rcParams['axes.unicode_minus'] = False
2.2 加载数据集
def load_data():(x, y), (x_val, y_val) = datasets.mnist.load_data()x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.y = tf.convert_to_tensor(y, dtype=tf.int32)y = tf.one_hot(y, depth=10)x = tf.reshape(x, (-1, 28 * 28))train_dataset = tf.data.Dataset.from_tensor_slices((x, y))train_dataset = train_dataset.batch(200)return train_dataset
2.3 创建每个非线性层的W和b参数
每个张量都需要被优化,所以使用Variable类型,并使用截断的正态分布初始化权值向量
def init_paramaters():w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))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]))return w1, b1, w2, b2, w3, b3
2.4 前向计算
第一层计算,这里显示地进行自动扩展操作:
h1 = x @ w1 + tf.broadcast_to(b1, (x.shape[0], 256))h1 = tf.nn.relu(h1)
用同样地方法完成第二个和第三个非线性函数层地前向计算,输出层可以不使用ReLU激活函数:
h2 = h1 @ w2 + b2h2 = tf.nn.relu(h2)out = h2 @ w3 + b3
将真实地标注张量y转变为独热编码,并计算与out的均方误差,代码如下:
loss = tf.square(y - out)loss = tf.reduce_mean(loss)
上述的前向计算过程都包裹在with tf.GradientTape() as tape
上下文中,使得前向计算时候能够保存计算图信息,方便自动求导操作。
2.5 自动梯度与梯度更新
通过tape.gradient()
函数求得网络参数得到梯度信息,结果保存在grads列表变量中,实现如下:
grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])
并按照公式:
θ′=θ−η⋅∂ζ∂θ\theta '=\theta -\eta \cdot \frac{\partial \zeta }{\partial \theta } θ′=θ−η⋅∂θ∂ζ
来更新网络参数:
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])
其中,assign_sub()
将自身减去给定的参数值,实现参数的原地(In-place)更新操作。
网络训练误差值的变化曲线如下图所示:
2.6 完整代码
import os
import matplotlib.pyplot as plt
import tensorflow as tf
import tensorflow.keras.datasets as datasets
os.environ['TF_CPP_MIN_LOG_LEVEL']='2'
plt.rcParams['font.size'] = 16
plt.rcParams['font.family'] = ['STKaiti']
plt.rcParams['axes.unicode_minus'] = Falsedef load_data():(x, y), (x_val, y_val) = datasets.mnist.load_data()x = tf.convert_to_tensor(x, dtype=tf.float32) / 255.y = tf.convert_to_tensor(y, dtype=tf.int32)y = tf.one_hot(y, depth=10)x = tf.reshape(x, (-1, 28 * 28))train_dataset = tf.data.Dataset.from_tensor_slices((x, y))train_dataset = train_dataset.batch(200)return train_dataset
def init_paramaters():w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))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]))return w1, b1, w2, b2, w3, b3def train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr=0.001):for step, (x, y) in enumerate(train_dataset):with tf.GradientTape() as tape: h1 = x @ w1 + tf.broadcast_to(b1, (x.shape[0], 256))h1 = tf.nn.relu(h1) h2 = h1 @ w2 + b2h2 = tf.nn.relu(h2)out = h2 @ w3 + b3loss = tf.square(y - out)loss = tf.reduce_mean(loss)grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3])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:print(epoch, step, 'loss:', loss.numpy())return loss.numpy()def train(epochs):losses = []train_dataset = load_data()w1, b1, w2, b2, w3, b3 = init_paramaters()for epoch in range(epochs): loss = train_epoch(epoch, train_dataset, w1, b1, w2, b2, w3, b3, lr=0.001)losses.append(loss)x = [i for i in range(0, epochs)]plt.plot(x, losses, color='blue', marker='s', label='训练')plt.xlabel('Epoch')plt.ylabel('MSE')plt.legend()plt.savefig('MNIST数据集的前向传播训练误差曲线.png')plt.show()if __name__ == '__main__':train(epochs=20)
看懂上面的代码必须将tensorflow的张量计算十分熟练才可以。
3、总结
刚开始我只会调用keras
的API,不知道底层是怎么计算的,导致遇到复杂的模型看不懂别人的代码,现在终于将梯度下降法和前向传播搞明白了(无非就是通过偏导数去更新参数W和b,然后再计算损失)