前言
在《FNN(DNN)的前向传播和反向梯度推导》中,我们学习了FNN(DNN)的前向传播和反向梯度求导,但知识仍停留在纸面。本篇章将基于深度学习框架tensorflow验证我们所得结论的准确性,以便将抽象的数学符号和实际数据结合起来,将知识固化。更多相关内容请见《深度学习的数学理论与代码实战》系列介绍。
正文
需要用到的库有tensorflow和numpy,其中tensorflow其实版本>=2.0.0就行。
import tensorflow as tf
import numpy as np
然后是定义下面两个要用到的函数,一个是计算mse,另外一个是计算sigmoid的导数:
# mse def get_mse(y_pred, y_true): return 0.5 * tf.reduce_sum((y_pred - y_true)**2) # sigmoid的导数 def d_sigmoid(x): """ sigmoid(x)的导数 = sigmoid(x) * (1-sigmoid(x)) :param x: :return: sigmoid(x)的导数 """ return tf.math.sigmoid(x) * (1 - tf.math.sigmoid(x))
接着是随便产生一条样本数据:
x = np.array([[1, 2]]).astype(np.float32) y_true = np.array([[0.3, 0.5, 0.2]]).astype(np.float32)
x的是2维的,输出y是3维的:
x.shape Out[5]: (1, 2) y_true.shape Out[6]: (1, 3)
有了一条样本之后,我们开始写前向传播的代码:
1 with tf.GradientTape(persistent=True) as t: 2 # -----hidden l1------------- 3# DNN layer1 4 l1 = tf.keras.layers.Dense(4) 5 6# 输入经过DNN layer1得到输出z_l1 7 z_l1 = l1(x) 8# 跟踪变量z_l1,用于随后计算其梯度 9 t.watch([z_l1]) 10 # DNN layer1的输出再经过激活函数sigmoid 11 a_l1 = tf.math.sigmoid(z_l1) 12 # 跟踪变量a_l1 ,用于随后计算其梯度 13 t.watch([a_l1]) 14 # ------hidden l2------------ 15 l2 = tf.keras.layers.Dense(3) 16 17 z_l2 = l2(a_l1) 18 t.watch([z_l2]) 19 a_l2 = tf.math.sigmoid(z_l2) 20 t.watch([a_l2]) 21 # -------计算loss----------- 22 loss = get_mse(a_l2, y_true)
上面是一个两层的FNN(DNN)网络,激活函数都是sigmoid
这里 tf.GradientTape(persistent=True) ,t.watch()是用于后面计算变量的导数用的,不太熟悉的可参考tensorflow官方给出的关于这部分的教程(自动微分)。
这里为方便起见我就直接用tf.keras.layers.Dense()来创建DNN层了,tensorflow官方的教程也推荐用这种方法快速定义layer。
如果要看某一层内部的weights和bias也比较容易:
l1.kernel Out[7]: <tf.Variable 'dense/kernel:0' shape=(2, 4) dtype=float32, numpy= array([[-0.96988726, -0.84827805, 0.312042 , 0.8871379 ], [ 0.6567688 , -0.29099226, -0.80029106, -0.15143871]], dtype=float32)> l1.bias Out[8]: <tf.Variable 'dense/bias:0' shape=(4,) dtype=float32, numpy=array([0., 0., 0., 0.], dtype=float32)>
前向传播的验证
下面来验证上面代码第7 11行代码是否符合DNN的前传规则:
tf.math.sigmoid(tf.matmul(x, l1.kernel) l1.bias)
Out[14]: <tf.Tensor: shape=(1, 4), dtype=float32, numpy=array([[0.585077 , 0.19305778, 0.2161 , 0.64204717]], dtype=float32)>
a_l1
Out[15]: <tf.Tensor: shape=(1, 4), dtype=float32, numpy=array([[0.585077 , 0.19305778, 0.2161 , 0.64204717]], dtype=float32)>
看来tf.keras.layers.Dense确实是实现了下面的计算公式:
这里l1层激活函数默认是linear,sigmoid激活函数被我单独拿了出来(见前传部分的代码第11行),方便计算梯度的时候好做分解。
反向梯度计算的验证
接下来就是验证反向梯度求导公式的时候了:
1 # 注意的是,在tensorflow里,W变量矩阵和数学推导的是互为转置的关系,所以在验证的时候,要注意转置关系的处理 2 # ------dl_da2------ sigmoid(x)的导数 = sigmoid(x) * (1-sigmoid(x)) 3 dl_da2 = t.gradient(loss, a_l2) 4 my_dl_da2 = (a_l2 - y_true) 5 # ------dl_dz2--------- 6 dl_dz2 = t.gradient(loss, z_l2) 7 my_dl_dz2 = my_dl_da2 * d_sigmoid(z_l2) 8 # -------dl_dW2-------- 9 dl_dW2 = t.gradient(loss, l2.kernel) 10 my_dl_W2 = np.matmul(a_l1.numpy().transpose(), my_dl_dz2) 11 # -------dl_db2-------- 12 dl_db2 = t.gradient(loss, l2.bias) 13 my_dl_db2 = my_dl_dz2 14 # -------dl_dz1--------- 15 dl_dz1 = t.gradient(loss, z_l1) 16 my_dl_dz1 = np.matmul(my_dl_dz2, l2.weights[0].numpy().transpose()) * d_sigmoid(z_l1) 17 # -------dl_dW1--------- 18 dl_dW1 = t.gradient(loss, l1.kernel) 19 my_dl_dW1 = np.matmul(x.transpose(), my_dl_dz1) 20 # -------dl_db1---------- 21 dl_db1 = t.gradient(loss, l1.bias) 22 my_dl_db1 = my_dl_dz1
上面反向梯度计算的对象的顺序跟前先传播的顺序是正好相反的,因为这样方便进行梯度计算的时候,靠前的层的参数的梯度能够用得到靠后的层的梯度计算结果而不必从头开始计算,这也是反向梯度传播名字的由来,这点在上面代码中也能够体现出来。
代码验证的结果是:
t.gradient(loss, a_l2) Out[17]: <tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[ 0.1497804 , -0.05124322, 0.23775901]], dtype=float32)> a_l2 - y_true Out[18]: <tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[ 0.1497804 , -0.05124322, 0.23775901]], dtype=float32)>
代码验证的结果是:
t.gradient(loss, z_l2) Out[21]: <tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[ 0.03706735, -0.01267625, 0.05851868]], dtype=float32)> my_dl_da2 * d_sigmoid(z_l2) Out[22]: <tf.Tensor: shape=(1, 3), dtype=float32, numpy=array([[ 0.03706735, -0.01267625, 0.05851869]], dtype=float32)>
代码验证的结果是:
t.gradient(loss, l2.kernel) Out[23]: <tf.Tensor: shape=(4, 3), dtype=float32, numpy= array([[ 0.02168725, -0.00741658, 0.03423793], [ 0.00715614, -0.00244725, 0.01129749], [ 0.00801025, -0.00273934, 0.01264589], [ 0.02379899, -0.00813875, 0.03757175]], dtype=float32)> np.matmul(a_l1.numpy().transpose(), my_dl_dz2) Out[24]: array([[ 0.02168725, -0.00741658, 0.03423794], [ 0.00715614, -0.00244725, 0.01129749], [ 0.00801025, -0.00273934, 0.01264589], [ 0.02379899, -0.00813875, 0.03757175]], dtype=float32)
也没有问题。
剩下的大家可自行敲敲上面给出的验证代码看看是否跟推导的公式一致,这里就不再一一演示了。