《神经网络的梯度推导与代码验证》之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. 关于idea 在创建maven 骨架较慢问题解决

    在设置中->maven>runner>VM Options 粘贴     -DarchetypeCatalog=internal 其中 -D archetype:原型,典型的意思 ( ...

  2. Nginx的基本使用和配置

    2.1什么是Nginx Nginx 是一款高性能的 http 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器.由俄罗斯的程序设计师伊戈尔·西索夫(Igor Sysoev)所开发,官方 ...

  3. java数组的拷贝和扩容

    1.拷贝arraycopy方法 // 拷贝:arraycopy方法 // arraycopy // 第一个参数:原数组 // 第二个参数:原数组元素的起始位置 // 第三个参数:目标数组 // 第四个 ...

  4. mqtt第一次接触

    一.接触的原因 公司最近要求接手关于系统集成道闸的部分,其中系统和第三方系统是通过mqtt进行交互的.所以提前了解一下mqtt的基础概念. 二.mqtt的基本概念 1. mqtt的定义 Message ...

  5. Schema约束, dom4j解析

    Schema是新的XML文档约束:Schema要比DTD强大很多,是DTD替代者;Schema本身也是XML文档,但Schema文档的扩展名为xsd,而不是xml .Schema功能更强大,数据类型更 ...

  6. 性能分析(4)- iowait 使用率过高案例

    性能分析小案例系列,可以通过下面链接查看哦 https://www.cnblogs.com/poloyy/category/1814570.html 前言 前面两个案例讲的都是上下文切换导致的 CPU ...

  7. C#设计模式之4-原型模式

    原型模式(Prototype Pattern) 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/395 访问. 原型模式属 ...

  8. troubleshoot之:使用JFR解决内存泄露

    目录 简介 一个内存泄露的例子 使用JFR和JMC来分析内存泄露 OldObjectSample 总结 简介 虽然java有自动化的GC,但是还会有内存泄露的情况.当然java中的内存泄露跟C++中的 ...

  9. Spring Cloud Config Client 超时与重试

    简介 有时客户端需要在 config server 无响应时进行重试,以给 config server 时间进行恢复.利用 spring 提供的重试组件,我们可以方便的配置重试机制,包括重试间隔,重试 ...

  10. 微信小程序自动化测试最佳实践(附 Python 源码)

    本文为霍格沃兹测试学院测试大咖公开课<微信小程序自动化测试>图文整理精华版. 随着微信小程序的功能和生态日益完善,很多公司的产品业务形态逐渐从 App 延升到微信小程序.微信公众号等.小程 ...