《神经网络的梯度推导与代码验证》之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)

有了一条样本之后,我们开始写前向传播的代码:

 with tf.GradientTape(persistent=True) as t:
# -----hidden l1-------------
# DNN layer1
l1 = tf.keras.layers.Dense(4) # 输入经过DNN layer1得到输出z_l1
z_l1 = l1(x)
# 跟踪变量z_l1,用于随后计算其梯度
t.watch([z_l1])
# DNN layer1的输出再经过激活函数sigmoid
a_l1 = tf.math.sigmoid(z_l1)
# 跟踪变量a_l1 ,用于随后计算其梯度
t.watch([a_l1])
# ------hidden l2------------
l2 = tf.keras.layers.Dense(3) z_l2 = l2(a_l1)
t.watch([z_l2])
a_l2 = tf.math.sigmoid(z_l2)
t.watch([a_l2])
# -------计算loss-----------
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确实是实现了下面的计算公式:

$\left\lbrack \begin{array}{l} \begin{array}{l} a_{1}^{2} \\ a_{2}^{2} \\ \end{array} \\ a_{3}^{2} \\ a_{4}^{2} \\ \end{array} \right\rbrack = \sigma\left( {\left\lbrack \begin{array}{lll} \begin{array}{l} w_{11}^{2} \\ w_{21}^{2} \\ \end{array} & \begin{array}{l} w_{12}^{2} \\ w_{22}^{2} \\ \end{array} & \begin{array}{l} w_{13}^{2} \\ w_{23}^{2} \\ \end{array} \\ w_{31}^{2} & w_{32}^{2} & w_{33}^{2} \\ w_{41}^{2} & w_{42}^{2} & w_{43}^{2} \\ \end{array} \right\rbrack\left\lbrack \begin{array}{l} x_{1} \\ x_{2} \\ x_{3} \\ \end{array} \right\rbrack + \left\lbrack \begin{array}{l} \begin{array}{l} b_{1}^{2} \\ b_{2}^{2} \\ \end{array} \\ b_{3}^{2} \\ b_{4}^{2} \\ \end{array} \right\rbrack} \right)$

这里l1层激活函数默认是linear,sigmoid激活函数被我单独拿了出来(见前传部分的代码第11行),方便计算梯度的时候好做分解。

----------反向梯度计算的验证----------

接下来就是验证反向梯度求导公式的时候了:

 # 注意的是,在tensorflow里,W变量矩阵和数学推导的是互为转置的关系,所以在验证的时候,要注意转置关系的处理
# ------dl_da2------ sigmoid(x)的导数 = sigmoid(x) * (1-sigmoid(x))
dl_da2 = t.gradient(loss, a_l2)
my_dl_da2 = (a_l2 - y_true)
# ------dl_dz2---------
dl_dz2 = t.gradient(loss, z_l2)
my_dl_dz2 = my_dl_da2 * d_sigmoid(z_l2)
# -------dl_dW2--------
dl_dW2 = t.gradient(loss, l2.kernel)
my_dl_W2 = np.matmul(a_l1.numpy().transpose(), my_dl_dz2)
# -------dl_db2--------
dl_db2 = t.gradient(loss, l2.bias)
my_dl_db2 = my_dl_dz2
# -------dl_dz1---------
dl_dz1 = t.gradient(loss, z_l1)
my_dl_dz1 = np.matmul(my_dl_dz2, l2.weights[0].numpy().transpose()) * d_sigmoid(z_l1)
# -------dl_dW1---------
dl_dW1 = t.gradient(loss, l1.kernel)
my_dl_dW1 = np.matmul(x.transpose(), my_dl_dz1)
# -------dl_db1----------
dl_db1 = t.gradient(loss, l1.bias)
my_dl_db1 = my_dl_dz1

上面反向梯度计算的对象的顺序跟前先传播的顺序是正好相反的,因为这样方便进行梯度计算的时候,靠前的层的参数的梯度能够用得到靠后的层的梯度计算结果而不必从头开始计算,这也是反向梯度传播名字的由来,这点在上面代码中也能够体现出来。

注意:在tensorflow里,W变量矩阵和数学推导的是互为转置的关系,所以在验证的时候,要注意转置关系的处理。举个例子,l1.kernel的shape是(2, 4),即(input_dim, output_dim)这样的格式,说明在tensorflow是按照$\boldsymbol{o}\boldsymbol{u}\boldsymbol{t}\boldsymbol{p}\boldsymbol{u}\boldsymbol{t} = \boldsymbol{x}^{\boldsymbol{T}}\boldsymbol{W}\boldsymbol{~} + \boldsymbol{~}\boldsymbol{b}$这种方式做前传计算的,即输入x是一个行向量而非列向量

而我们在数学推导上,习惯写成$\boldsymbol{o}\boldsymbol{u}\boldsymbol{t}\boldsymbol{p}\boldsymbol{u}\boldsymbol{t} = \boldsymbol{W}^{\boldsymbol{T}}\boldsymbol{x}\boldsymbol{~} + \boldsymbol{~}\boldsymbol{b}$。于是在做验证的时候,需要对weights进行一下转置操作,即上面的 .transpose()操作。

回到上面的代码部分。dl_da2 = t.gradient(loss, a_l2)表示用tensorflow微分工具求得的$\frac{\partial l}{\partial a2}$;而带my_前缀的则是根据《神经网络的梯度推导与代码验证》之FNN(DNN)的前向传播和反向梯度推导中得到的结论手动计算出来的结果。我们依次对比一下是否有区别。

关于$\frac{\partial l}{\partial a2}$,根据我们得到的公式,它满足:

$\frac{\partial l}{\partial\boldsymbol{a}^{\boldsymbol{L}}} = \boldsymbol{a}^{\boldsymbol{L}} - \boldsymbol{y}$

代码验证的结果是:

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)>

没有问题,下一个是$\frac{\partial l}{\partial z2}$,根据公式,它满足:

$d\boldsymbol{a}^{\boldsymbol{L}} = d\sigma\left( \boldsymbol{z}^{L} \right) = \sigma^{'}\left( \boldsymbol{z}^{L} \right) \odot d\boldsymbol{z}^{L} = diag\left( {\sigma^{'}\left( \boldsymbol{z}^{L} \right)} \right)d\boldsymbol{z}^{L}$

代码验证的结果是:

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)>

也没有问题,其中d_sigmoid()函数是前面定义好的,用来求sigmoid导数的。至于上面0.05851868 vs 0.05851869的问题,我觉得单纯只是两种代码实现过程中调用的底层方式不同导致的不同而已。

接下来是$\frac{\partial l}{\partial W2}$,根据公式,它满足:

$\frac{\partial l}{\partial\boldsymbol{W}^{\boldsymbol{L}}} = \frac{\partial l}{\partial\boldsymbol{z}^{\boldsymbol{L}}}\left( \boldsymbol{a}^{\boldsymbol{L} - 1} \right)^{T}$

代码验证的结果是:

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)

也没有问题。剩下的大家可自行对照着《神经网络的梯度推导与代码验证》之FNN(DNN)的前向传播和反向梯度推导中的得到的公式自行验证。


(欢迎转载,转载请注明出处。欢迎留言或沟通交流: lxwalyw@gmail.com)

《神经网络的梯度推导与代码验证》之FNN(DNN)前向和反向过程的代码验证的更多相关文章

  1. 《神经网络的梯度推导与代码验证》之FNN(DNN)的前向传播和反向推导

    在<神经网络的梯度推导与代码验证>之数学基础篇:矩阵微分与求导中,我们总结了一些用于推导神经网络反向梯度求导的重要的数学技巧.此外,通过一个简单的demo,我们初步了解了使用矩阵求导来批量 ...

  2. 《神经网络的梯度推导与代码验证》之CNN的前向传播和反向梯度推导

    在FNN(DNN)的前向传播,反向梯度推导以及代码验证中,我们不仅总结了FNN(DNN)这种神经网络结构的前向传播和反向梯度求导公式,还通过tensorflow的自动求微分工具验证了其准确性.在本篇章 ...

  3. 《神经网络的梯度推导与代码验证》之CNN前向和反向传播过程的代码验证

    在<神经网络的梯度推导与代码验证>之CNN的前向传播和反向梯度推导 中,我们学习了CNN的前向传播和反向梯度求导,但知识仍停留在纸面.本篇章将基于深度学习框架tensorflow验证我们所 ...

  4. 《神经网络的梯度推导与代码验证》之vanilla RNN的前向传播和反向梯度推导

    在本篇章,我们将专门针对vanilla RNN,也就是所谓的原始RNN这种网络结构进行前向传播介绍和反向梯度推导.更多相关内容请见<神经网络的梯度推导与代码验证>系列介绍. 注意: 本系列 ...

  5. 《神经网络的梯度推导与代码验证》之vanilla RNN前向和反向传播的代码验证

    在<神经网络的梯度推导与代码验证>之vanilla RNN的前向传播和反向梯度推导中,我们学习了vanilla RNN的前向传播和反向梯度求导,但知识仍停留在纸面.本篇章将基于深度学习框架 ...

  6. 《神经网络的梯度推导与代码验证》之LSTM的前向传播和反向梯度推导

    前言 在本篇章,我们将专门针对LSTM这种网络结构进行前向传播介绍和反向梯度推导. 关于LSTM的梯度推导,这一块确实挺不好掌握,原因有: 一些经典的deep learning 教程,例如花书缺乏相关 ...

  7. 神经网络的BP推导过程

    神经网络的BP推导过程 下面我们从一个简单的例子入手考虑如何从数学上计算代价函数的梯度,考虑如下简单的神经网络,该神经网络有三层神经元,对应的两个权重矩阵,为了计算梯度我们只需要计算两个偏导数即可: ...

  8. [DeeplearningAI笔记]改善深层神经网络1.1_1.3深度学习使用层面_偏差/方差/欠拟合/过拟合/训练集/验证集/测试集

    觉得有用的话,欢迎一起讨论相互学习~Follow Me 1.1 训练/开发/测试集 对于一个数据集而言,可以将一个数据集分为三个部分,一部分作为训练集,一部分作为简单交叉验证集(dev)有时候也成为验 ...

  9. 再说表单验证,在Web Api中使用ModelState进行接口参数验证

    写在前面 上篇文章中说到了表单验证的问题,然后尝试了一下用扩展方法实现链式编程,评论区大家讨论的非常激烈也推荐了一些很强大的验证插件.其中一位园友提到了说可以使用MVC的ModelState,因为之前 ...

随机推荐

  1. Spring MVC method POST no supported

    首先:一些隐含的知识点要知道 POST 的不支持对静态资源的访问[默认情况下是这样,个人不太了解,仅总结大概思路],如果是post 而响应的是个静态资源,则很多情况下出现这种错误 因此在使用POST应 ...

  2. 【AHOI2009】中国象棋 题解(线性DP+数学)

    前言:这题主要是要会设状态,状态找对了问题迎刃而解. --------------------------- 题目描述 这次小可可想解决的难题和中国象棋有关,在一个N行M列的棋盘上,让你放若干个炮(可 ...

  3. Improving RGB-D SLAM in dynamic environments: A motion removal approach

    一.贡献 (1)提出一种针对RGB-D的新的运动分割算法 (2)运动分割采用矢量量化深度图像 (3)数据集测试,并建立RGB-D SLAM系统 二.Related work [1]R.K. Namde ...

  4. 网络安全传输系统-sprint1传输子系统

    一.产品规划与设计 二.传输子系统 基本框架:(1)不带安全功能的传输系统 (2)安全加密功能 part1:基本传输子程序设计(不带安全加密功能) 客户端 服务器 int main(int argc, ...

  5. 用 Python 了解一下最炫国漫《雾山五行》

    看动漫的小伙伴应该知道最近出了一部神漫<雾山五行>,其以极具特色的水墨画风和超燃的打斗场面广受好评,首集播出不到 24 小时登顶 B 站热搜第一,豆瓣开分 9.5,火爆程度可见一斑,就打斗 ...

  6. Linux用C语言模拟‘ls‘命令

    原理 在linux下使用C语言,通过调用Linux系统的目录访问API来实现一个类似于ls命令功能的小程序,主要是可以练习程序对命令的解析和目录API函数的使用. 实现代码 #include < ...

  7. 极简 Node.js 入门 - 2.2 事件

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  8. Android学习进程 Java引用 Rxjava MVP

    第一份Android开发工作,以便于记录学习进程 Java引用 Java没有显式的使用指针,但对象的访问仍是通过指针实现的,所以直接对象之间的赋值会导致存储空间是数据的改变,如设置两个对象,其中对象一 ...

  9. CSAPP =1= 计算机系统漫游

    思维导图 预计阅读时间:15min 阅读书籍 <深入理解计算机系统> 参考视频 [精校中英字幕]2015 CMU 15-213 CSAPP 深入理解计算机系统 课程视频 参考文章 < ...

  10. windows下RocketMQ的安装部署

    一.预备环境 1.系统 Windows 2. 环境 JDK1.8.Maven.Git 二. RocketMQ部署 1.下载 1.1地址:http://rocketmq.apache.org/relea ...