文章目录

  • 前言
  • 一.全连接神经网络
  • 1.1从单个感知机到神经网络
  • 1.2DNN的基本结构
  • 1.3前向传播算法
  • 1.4梯度下降算法
  • 1.5反向传播算法
  • 1.6激活函数
  • 1.relu函数
  • 2.sigmoid函数
  • 3.tanh函数
  • 4.softmax函数
  • 二.卷积神经网络
  • 2.1卷积神经网络的基本结构
  • 1.卷积
  • 2.池化
  • 2.2卷积神经网络的前向传播
  • 1.卷积层
  • 2.池化层
  • 3.全连接层
  • 2.3卷积神经网络的反向传播
  • 1.已知池化层的 δ l δ^l δl,推到上一层的 δ l − 1 δ^{l-1} δl−1
  • 2已知卷积层的 δ l δ^{l} δl,求上一层的 δ l − 1 δ^{l-1} δl−1
  • 三.用numpy搭建神经网络
  • 3.1DNN的numpy实现:
  • 1.定义激活函数以及激活函数的导数:
  • 2参数w,b初始化:
  • 3.定义前向传播函数:
  • 4.计算损失:
  • 5.定义反向传播函数:
  • 6.参数更新:
  • 7.模型定义:
  • 3.2 CNN的numpy实现:
  • 1.卷积层的前向传播:
  • 2.池化层的前向传播:
  • 3.卷积层的反向传播:
  • 4.池化层的反向传播:
  • 四.参考资料


前言

近期学习了神经网络前向传播和反向传播的数学原理,希望通过写下这篇文章进一步巩固自己对近期学习知识的理解,以及帮助同样有相同需求的人。若有错误,望指出。

一.全连接神经网络

1.1从单个感知机到神经网络

感知机是神经网络的基本组成单元,其基本结构如图1所示:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python


图1.单个感知机的结构

感知机是由多个输入和一个输出的模型。输入与权重相乘再求和,得到一个线性关系的结果:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_02

得到的线性结果再通过激活函数:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_03

最后得到的输出只能应用与二元分类,并且无法学习更复杂非线性模型,这是单个感知机的缺陷。

因此在单个感知机的基础上,通过增加单层感知机的个数以及感知机的层数,构成了人工神经网络(ANN),因此,人工神经网络也被称作多层感知机(MLP),如图2。

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_04


图2 多层感知机结构

1.2DNN的基本结构

神经网络之所以被称作网络,是因为它们通常是用许多不同函数复合在一起来表示。该模型与一个有向无环图相关联,而图描述了函数是如何复合在一起的。例如,我们有三个函数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_05,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_06,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_07,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_08,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_09,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_10连接在一个链上以形成matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_11。 在这种情况下,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_05被称为网络的第一层,也就是输入层,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_06matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_09被称为隐藏层,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_10被称为输出层。也就是说,DNN按照层的位置来划分的话,可以分为3类:输入层,隐藏层,输出层。一般来说,输入层为网络的第一层,输出层为最后一层。如图3为DNN结构样例:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_16


图3:DNN结构样例

1.3前向传播算法

前向传播其实就是向神经网络中输入X得到Y的过程。也就是可以把神经网络写成matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_17函数,而函数的系数就是神经网络训练的参数W。接下来我们就从数学的角度来推导前向传播的过程

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_18


图4

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_19为第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_20-1层的第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_21个神经元到第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_20层第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_23个神经元的权重,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_24为第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_20层第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_21个神经元的偏置,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_27为第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_20层第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_21个神经元的输出值,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_30为激活函数,所以
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_31
写成矩阵形式:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_32
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_33,则matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_34
利用式子matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_34一层层计算,就能根据matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_36得到相应的输出matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_37

1.4梯度下降算法

梯度下降算法是神经网络进行反向传播的基础,因此,在介绍反向传播算法之前,我们先介绍一下什么是梯度下降算法以及梯度下降算法的数学原理。

1.梯度下降算法的解释:

假设甲现在处在一个山沟当中,如图5所示。在山沟的最低点有一个宝藏,甲希望能够找到这个宝藏。那么甲如何才能够沿着正确的方向找到宝藏呢?由于宝藏在山沟的最低点,所以甲会按着一定的步伐沿着下坡的方向前进,于是就有了①→②→③→④→⑤,由于甲的步伐过大,所以直接跨过了最低点,甲于是沿着另一个坡下坡的方向前进,于是有了⑤→⑥,最终到达了最低点。

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_38


图5

2.梯度下降的数学解释:
首先我们引入梯度下降算法的公式:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_39
假设我们有一个目标函数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_40,我们希望我们有一种算法,能够找到目标函数的最低值,有什么办法能够帮助我们找到呢?参考甲寻宝的过程,我们知道沿着下坡的方向,就能找到最低值。而在函数中,上坡与下坡代表着函数在该点梯度的方向与梯度的反方向。在多变量函数中,梯度是一个向量,用符号▽表示,向量有方向,梯度的方向就指出了函数在给定点的上升最快的方向,反方向就是下降最快的方向。 针对目标函数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_40,我们来求解他的梯度,其实就是求个matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_42关于matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_43的偏微分。
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_44
在知道我们应该沿着哪个方向下坡时,我们又会面临另一个问题,我们每次迈出的步伐是多少,于是我们引入matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_45来表示步伐,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_45也称为学习率。如果matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_45设置过大,会导致我们直接迈过最低点,就如第⑤步一样;如果我们matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_45设置过小,会导致我们走了很久也没有到达最低点,因此设置合理的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_45值很重要。
有了梯度matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_50和学习率matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_45我们就能够通过多次迭代,找到matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_52

1.5反向传播算法

有了对梯度下降算法的了解,我们现在开始去看看神经网络是如何通过反向传播来更新参数的。
在介绍反向传播之前,我们引入损失函数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_53,用来描述预测值与真实值之间的差异。
在反向传播的过程中,我们需要计算matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_54来对参数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_55进行更新。在这里我们假设我们的误差函数为均方误差:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_56
其中matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_57为网络第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_58层的输出,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_59为真实值,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_60表示matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_61范数
我们又知道matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_57matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_55满足:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_64
所以我们可以把损失函数写成:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_65
于是损失函数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_42关于matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_55的梯度为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_68
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_69
注:matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_70表示两向量的内积,例:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_71
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_72
在求解matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_73时,我们根据微分的链式法则,可以分解为matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_74
我们可以先求解出公共部分matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_75
记为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_76
对于第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_20层的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_78matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_79,matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_80又为多少呢,根据链式求导法则,我们可以知道:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_81
我们再根据链式求导法则:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_82
因此我们根据上一层matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_83的梯度来求解matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_84的梯度,这也是反向传播的特点,从matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_58层一步一步求解到第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_86层。
又因为 matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_87,所以我们可以计算出第matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_20matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_55的梯度值:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_90
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_91
到此,我们就求解出了matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_92matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_93的梯度,然后我们用梯度下降算法,来对参数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_55进行更新:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_95
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_96
如此一来,我们就实现了神经网络的反向传播。

1.6激活函数

我们知道,如果没有激活函数,那么整个神经网络都是线性模型,就算网络有多深,其拟合能力和logistics回归差不多,而在添加了激活函数后,模型才有线性变成了非线性,因此,激活函数在神经网络中起了十分重要的作用。在这一部分,我讲介绍一些激活函数,以及这些激活函数的作用。

1.relu函数

relu全称为整流线性单元(rectified linear unit)其函数表达式为:matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_97,其函数如图6所示:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_98


图6

ReLU函数其实是分段线性函数,把所有的负值都变为0,而正值不变,这种操作被成为单侧抑制。(也就是说:在输入是负值的情况下,它会输出0,那么神经元就不会被激活。这意味着同一时间只有部分神经元会被激活,从而使得网络很稀疏,进而对计算来说是非常有效率的。)正因为有了这单侧抑制,才使得神经网络中的神经元也具有了稀疏激活性。尤其体现在深度神经网络模型(如CNN)中,当模型增加N层之后,理论上ReLU神经元的激活率将降低2的N次方倍。
其优点有:
1.没有饱和区,不存在梯度消失问题。
2.没有复杂的指数运算,计算简单、效率提高。
3.实际收敛速度较快,比 Sigmoid/tanh 快很多。
4.比 Sigmoid 更符合生物学神经激活机制。

relu函数的一些变种:
(1).Leaky ReLU:带泄露线性整流函数(Leaky ReLU)的梯度为一个常数matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_99,通常取0.01。在输入值为正的时候,带泄露线性整流函数和普通斜坡函数保持一致。函数为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_100
(2).绝对值整流:固定matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_101来得到matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_102,它一般用于图像中的对象识别,其中寻找输入照明极性反转下不变的特性是有意义的。

2.sigmoid函数

sigmoid函数也叫Logistic函数,用于隐层神经元输出,取值范围为(0,1),它可以将一个实数映射到(0,1)的区间,可以用来做二分类。在特征相差比较复杂或是相差不是特别大时效果比较好。Sigmoid作为激活函数有以下优缺点:

优点:平滑、易于求导。

缺点:激活函数计算量大,反向传播求误差梯度时,求导涉及除法;反向传播时,很容易就会出现梯度消失的情况,从而无法完成深层网络的训练。

其函数式为:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_103

函数图像如下图7

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_104


图7

3.tanh函数

tanh是双曲函数中的一个,tanh()为双曲正切。在数学中,双曲正切“tanh”是由基本双曲函数双曲正弦和双曲余弦推导而来,其函数表达式为:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_105

tanh其实是sigmoid经过放大后得到的:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_106

因此tanh的取值范围为matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_107,如下图

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_108

4.softmax函数

在数学,尤其是概率论和相关领域中,归一化指数函数,或称Softmax函数,是逻辑函数的一种推广。它能将一个含任意实数的K维向量z“压缩”到另一个K维实向量σ(z)中,使得每一个元素的范围都在(0,1)之间,并且所有元素的和为1。该函数多于多分类问题中。
函数表达式为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_109

二.卷积神经网络

2.1卷积神经网络的基本结构

卷积神经网络通常是又卷积层,池化层,全连接层组合而成,图8展示的是卷积神经网络中最经典的模型之一:VGG模型的网络结构。

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_110


图8 VGG模型

图9展示的是VGG模型各层的组成:

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_111


图9 VGG模型具体细节

其中conv代表卷积层,maxpool表示池化层,FC表示全连接层。
接下来我们来简述一下卷积神经网络中的卷积和池化是什么。

1.卷积

卷积概念的提出,是在信号与系统这么学科上,它的定义是一个信号(可以理解成一个函数)经过一个线性系统以后发生的变化。
对于连续信号(函数),卷积的定义为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_112
对于离散信号(函数),卷积的定义为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_113
对于二维离散信号(函数)的卷积,定义为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_114
在卷积神经网络中,卷积的定义和上述定义略有不同,例如对于二维卷积,有:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_115
虽然两个式子上形式上很相识,可是按照按个的数学定义来说是不同,产生两个式子不同的原因是因为对于普通二维信号的卷积,卷积核需要进行翻转操作,而在卷积神经网络的卷积中,卷积核不需要进行翻转操作。
卷积的作用:特征提取。

2.池化

池化是使用某一个位置的总体统计特征来代替网络在该位置的输出。例如最大池化(maxpooling)会给出相邻矩阵区域内的最大值。其他常用的池化函数包括平均值。matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_116范数等。
池化的作用:
(1)帮助输入的表示近似不变。
(2)保留主要特征的同时减少参数个数(降维)。

2.2卷积神经网络的前向传播

1.卷积层

matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_117
其中matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_118表示激活函数,一般为matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_119函数。matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_120表示卷积操作。

2.池化层

如果输入矩阵的大小为matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_121,池化层的filter大小为matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_122,则输出矩阵的大小为matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_123
如果是最大值池化,则选取对应区域的最大值。如果是均值池化,则求对应区域的平均值。

3.全连接层

全连接层的前向传播和我们在第一部分讲述的一样,这里就不再重复,只给出对应的数学公式。
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_124

2.3卷积神经网络的反向传播

在进行CNN反向传播推到之前,回顾我们需要在第一部分反向传播中定义的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_125,我们将根据matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_125来进行推到。
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_127

1.已知池化层的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_125,推到上一层的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_129

假设池化层的filter大小为2×2。
池化后的矩阵为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_130
如果我们用的最大值池化,我们对矩阵进行还原,即:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_131
如果我们用的均值池化,我们对矩阵进行还原,即:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_132
这样我们求出上一层的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_129
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_134
其中,upsample函数完成了池化误差矩阵放大与误差重新分配的逻辑。

2已知卷积层的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_84,求上一层的matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_129

我们呢以及卷积层的前向传播公式为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_117
在DNN中,我们知道matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_138matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_139存在递推关系,并且根据matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_140matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_141的关系:matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_142
因此我们有:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_143
含有卷积的式子求导时,卷积核被旋转了180度。即式子中的rot180(),翻转180度的意思是上下翻转一次,接着左右翻转一次。
假设matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_144矩阵,卷积核为2×2,pad=1.忽略matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_79,根据:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_146
具体为:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_147
利用卷积的定义,我们知道:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_148
根据上式,我们对matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_149求导:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_神经网络_150
因此:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_151
以上就是卷积层的反向传播,接下来我们来求解matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_55的梯度
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_算法_153
对于matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_深度学习_154我们有:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_155
对于matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_python_79,我们有:
matlab创建一个前向神经网络函数及训练误差曲线 使用numpy实现前馈神经网络_卷积_157
上述内容就是CNN反向传播的全部过程。

三.用numpy搭建神经网络

本部分在仅导入numpy包的条件下实现对神经网络的搭建。

3.1DNN的numpy实现:

1.定义激活函数以及激活函数的导数:

def sigmod(x):
    return 1/(1+np.exp(-x))

def relu(x):
    if x >= 0:
        return x
    else:
        return 0
        
def relu_backward(dA, cache):
	"""
	cache - 缓存了一些反向传播函数conv_backward()需要的一些数据
	"""
    Z = cache
    dz = np.array(dA,copy = True)
    dz[Z<=0] = 0
    return dz

def sigmoid_backward(dA, cache):
	"""
	cache - 缓存了一些反向传播函数conv_backward()需要的一些数据
	"""
    z = cache
    s = 1/(1+np.exp(-z))
    dz = dA*s*(1-s)
    return dz

2参数w,b初始化:

def init_parameter(layers_dim):
	"""
	layers_dim:DNN中各层的节点数
	"""
    np.random.seed(3)
    parameters = {}

    for i in range(1,len(layers_dim)):
        #parameters['w'+str(i)] = np.random.randn(layers_dim[i],layers_dim[i-1])*0.01
        parameters['w' + str(i)] = np.random.randn(layers_dim[i], layers_dim[i - 1]) *np.sqrt(2/layers_dim[i-1])
        parameters['b'+str(i)] = np.zeros((layers_dim[i],1))
        #print(str(i)+"+"+str(parameters["w"+str(i)].shape)+"+"+str(parameters['b'+str(i)].shape))
    return parameters

3.定义前向传播函数:

def linear_forward(A,w,b):
    """
    实现前向传播的线性部分。

    参数:
        A - 来自上一层(或输入数据)的激活,维度为(上一层的节点数量,示例的数量)
        W - 权重矩阵,numpy数组,维度为(当前图层的节点数量,前一图层的节点数量)
        b - 偏向量,numpy向量,维度为(当前图层节点数量,1)

    返回:
         Z - 激活功能的输入,也称为预激活参数
         cache - 一个包含“A”,“W”和“b”的字典,存储这些变量以有效地计算后向传递
    """
    z = np.dot(w,A)+b
    cache = (A,w,b)
    return z,cache

def linear_activation_forward(A_prev,w,b,activation):
	"""
	A_prev - 来自上一层(或输入层)的激活,维度为(上一层的节点数量,示例数)
	 activation - 选择在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
	 cache - 一个包含“linear_cache”和“activation_cache”的字典,我们需要存储它以有效地计算后向传递
    """
    if activation == 'sigmoid':
        Z, linear_cache = linear_forward(A_prev, w, b)
        A,activation_cache = sigmoid(Z)
    elif activation =='relu':
        Z, linear_cache = linear_forward(A_prev, w, b)
        A ,activation_cache= relu(Z)
    cache = (linear_cache,activation_cache)
    return A,cache

def L_model_forward(X,parameters):
	"""
	AL - 最后的激活值
        caches - 包含以下内容的缓存列表:
                 linear_relu_forward()的每个cache(有L-1个,索引为从0到L-2)
                 linear_sigmoid_forward()的cache(只有一个,索引为L-1)
    """
    caches = []
    A = X
    L = len(parameters) // 2
    for l in range(1,L):
        A_prev = A
        A,cache = linear_activation_forward(A_prev,parameters['w'+str(l)],parameters['b'+str(l)],'relu')
        caches.append(cache)
    A_prev = A
    AL,cache = linear_activation_forward(A_prev,parameters['w'+str(L)],parameters['b'+str(L)],'sigmoid')
    caches.append(cache)
    return AL,caches

4.计算损失:

def compute_cost(Y,AL):
	"""
	 AL - 与标签预测相对应的概率向量,维度为(1,示例数量)
        Y - 标签向量(例如:如果不是猫,则为0,如果是猫则为1),维度为(1,数量)
	"""
    m = Y.shape[1]
    cost =  -1/m*np.sum(np.multiply(np.log(AL),Y)+np.multiply(np.log(1-AL),(1-Y)))
    cost = np.squeeze(cost)
    return cost

5.定义反向传播函数:

def linear_backward(dz,cache):
   """
    为单层实现反向传播的线性部分(第L层)

    参数:
         dZ - 相对于(当前第l层的)线性输出的成本梯度
         cache - 来自当前层前向传播的值的元组(A_prev,W,b)

    返回:
         dA_prev - 相对于激活(前一层l-1)的成本梯度,与A_prev维度相同
         dW - 相对于W(当前层l)的成本梯度,与W的维度相同
         db - 相对于b(当前层l)的成本梯度,与b维度相同
    """
    A_prev,w,b = cache
    m = A_prev.shape[1]

    dw = np.dot(dz,A_prev.T)/m
    db = np.sum(dz,axis=1,keepdims=True)/m
    dA_prev = np.dot(w.T,dz)

    return dA_prev,dw,db

def linear_activation_backward(dA,cache,activation):
"""
    实现LINEAR-> ACTIVATION层的后向传播。
    
    参数:
         dA - 当前层l的激活后的梯度值
         cache - 我们存储的用于有效计算反向传播的值的元组(值为linear_cache,activation_cache)
         activation - 要在此层中使用的激活函数名,字符串类型,【"sigmoid" | "relu"】
    返回:
         dA_prev - 相对于激活(前一层l-1)的成本梯度值,与A_prev维度相同
         dW - 相对于W(当前层l)的成本梯度值,与W的维度相同
         db - 相对于b(当前层l)的成本梯度值,与b的维度相同
    """
    linear_cache,activation_cache = cache
    if activation == "relu":
        dz = relu_backward(dA,activation_cache)
        dA_prev,dw,db = linear_backward(dz,linear_cache)
    elif activation == "sigmoid":
        dz = sigmoid_backward(dA,activation_cache)
        dA_prev,dw,db = linear_backward(dz,linear_cache)
    return dA_prev,dw,db

def L_model_backward(AL,Y,caches):
	"""
	    grads - 具有梯度值的字典
              grads [“dA”+ str(l)] = ...
              grads [“dW”+ str(l)] = ...
              grads [“db”+ str(l)] = ...
    """
    grads = {}
    L = len(caches)
    m = AL.shape[1]
    Y = Y.reshape(AL.shape)
    
    dAL = -(np.divide(Y,AL)-np.divide(1-Y,1-AL))
    current_cache = caches[L-1]
    grads["dA"+str(L)],grads["dw"+str(L)],grads["db"+str(L)] = linear_activation_backward(dAL,current_cache,"sigmoid")
    for l in reversed(range(L-1)):
        current_cache = caches[l]
        dA_prev_temp,dw_temp,db_temp = linear_activation_backward(grads["dA"+str(l+2)],current_cache,"relu")
        grads["dA"+str(l+1)] = dA_prev_temp
        grads["dw"+str(l+1)] = dw_temp
        grads["db"+str(l+1)] = db_temp
    return grads

6.参数更新:

使用梯度下降法,对参数进行更新

def update_parameters(parameters,grads,learning_rate = 0.1):
    L = len(parameters)//2
    for l in range(L):
        parameters["w"+str(l+1)] -= learning_rate*grads["dw"+str(l+1)]
        parameters["b"+str(l+1)] -= learning_rate*grads["db"+str(l+1)]
    return parameters

7.模型定义:

def dnn_model(X_total,Y_total,layers_dims,num_iterations = 10000,print_cost = True):
    np.random.seed(1)
    costs = []
    batchsize = 16
    parameters = init_parameter(layers_dims)
    v,s = init_adam(parameters)
    t = 0
    for i in range(0,num_iterations):
        X,Y = batch_data(X_total,Y_total,batchsize)
        AL,caches = L_model_forward(X,parameters)
        cost = compute_cost(Y,AL)
        # acc = comput_arccuracy(Y,AL)
        grads = L_model_backward(AL,Y,caches)
        t = t+1
        parameters,v,s = update_parameters_with_Adam(parameters,grads,v,s,t)
        if print_cost and i%100 == 0:
            print("cost  after iteration %i:%f"%(i,cost))
            costs.append(cost)
    plt.plot(np.squeeze(costs))
    plt.ylabel('cost')
    plt.xlabel('iterations (per tens')
    plt.show()
    test_file_path = "C:\\Users\\10372\\Desktop\\Learning\\机器学习\\数据集\\考生本地用资源\\test_data.txt"
    test_x,test_y = data(test_file_path)
    AL, caches = L_model_forward(test_x, parameters)
    cost = compute_cost(test_y, AL)
    print("test cost is %f"%(cost))
    return parameters

完整代码请参看:
链接: CNN代码.

3.2 CNN的numpy实现:

1.卷积层的前向传播:

def zero_pad(X,pad):
    x_paded = np.pad(X,(
        (0,0),
        (pad,pad),
        (pad,pad),
        (0,0)),
        'constant',constant_values=0)
    return x_paded

def conv_single_step(a_slice_prev,w,b):
    s = np.multiply(a_slice_prev,w)+b
    z = np.sum(s)
    return z


def conv_forward(A_prev, W, b, hparameters):

    # 获取来自上一层数据的基本信息
    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape

    # 获取权重矩阵的基本信息
    (f, f, n_C_prev, n_C) = W.shape

    # 获取超参数hparameters的值
    stride = hparameters["stride"]
    pad = hparameters["pad"]

    n_H = int((n_H_prev - f + 2 * pad) / stride) + 1
    n_W = int((n_W_prev - f + 2 * pad) / stride) + 1
    Z = np.zeros((m, n_H, n_W, n_C))
    A_prev_pad = zero_pad(A_prev, pad)

    for i in range(m):
        a_prev_pad = A_prev_pad[i]
        for h in range(n_H):
            for w in range(n_W):
                for c in range(n_C):
                    vert_start = h * stride
                    vert_end = vert_start + f
                    horiz_start = w * stride
                    horiz_end = horiz_start + f
                    a_slice_prev = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]
                    Z[i, h, w, c] = conv_single_step(a_slice_prev, W[:, :, :, c], b[0, 0, 0, c])
    assert (Z.shape == (m, n_H, n_W, n_C))
    cache = (A_prev, W, b, hparameters)

    return (Z, cache)

2.池化层的前向传播:

def pool_forward(A_prev,hparameters,mode = "max"):
    (m, n_H_prev, n_W_prev, n_c_prev) = A_prev.shape
    f = hparameters["f"]
    stride = hparameters["stride"]

    n_H = int((n_H_prev - f) / stride) + 1
    n_W = int((n_W_prev - f) / stride) + 1
    n_C = n_c_prev

    A = np.zeros((m,n_H,n_W,n_C))

    for i in range(m):  # 遍历样本
        for h in range(n_H):  # 在输出的垂直轴上循环
            for w in range(n_W):  # 在输出的水平轴上循环
                for c in range(n_C):  # 循环遍历输出的通道
                    # 定位当前的切片位置
                    vert_start = h * stride  # 竖向,开始的位置
                    vert_end = vert_start + f  # 竖向,结束的位置
                    horiz_start = w * stride  # 横向,开始的位置
                    horiz_end = horiz_start + f  # 横向,结束的位置
                    # 定位完毕,开始切割
                    a_slice_prev = A_prev[i, vert_start:vert_end, horiz_start:horiz_end, c]

                    # 对切片进行池化操作
                    if mode == "max":
                        A[i, h, w, c] = np.max(a_slice_prev)
                    elif mode == "average":
                        A[i, h, w, c] = np.mean(a_slice_prev)

    cache = (A_prev,hparameters)
    return (A,cache)

3.卷积层的反向传播:

def conv_backward(dZ, cache):
    # 获取cache的值
    (A_prev, W, b, hparameters) = cache

    # 获取A_prev的基本信息
    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape

    # 获取dZ的基本信息
    (m, n_H, n_W, n_C) = dZ.shape

    # 获取权值的基本信息
    (f, f, n_C_prev, n_C) = W.shape

    # 获取hparaeters的值
    pad = hparameters["pad"]
    stride = hparameters["stride"]

    # 初始化各个梯度的结构
    dA_prev = np.zeros((m, n_H_prev, n_W_prev, n_C_prev))
    dW = np.zeros((f, f, n_C_prev, n_C))
    db = np.zeros((1, 1, 1, n_C))

    # 前向传播中我们使用了pad,反向传播也需要使用,这是为了保证数据结构一致
    A_prev_pad = zero_pad(A_prev, pad)
    dA_prev_pad = zero_pad(dA_prev, pad)

    # 现在处理数据
    for i in range(m):
        # 选择第i个扩充了的数据的样本,降了一维。
        a_prev_pad = A_prev_pad[i]
        da_prev_pad = dA_prev_pad[i]

        for h in range(n_H):
            for w in range(n_W):
                for c in range(n_C):
                    # 定位切片位置
                    vert_start = h
                    vert_end = vert_start + f
                    horiz_start = w
                    horiz_end = horiz_start + f

                    # 定位完毕,开始切片
                    a_slice = a_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :]

                    # 切片完毕,使用上面的公式计算梯度
                    da_prev_pad[vert_start:vert_end, horiz_start:horiz_end, :] += W[:, :, :, c] * dZ[i, h, w, c]
                    dW[:, :, :, c] += a_slice * dZ[i, h, w, c]
                    db[:, :, :, c] += dZ[i, h, w, c]
        # 设置第i个样本最终的dA_prev,即把非填充的数据取出来。
        dA_prev[i, :, :, :] = da_prev_pad[pad:-pad, pad:-pad, :]

    # 数据处理完毕,验证数据格式是否正确
    assert (dA_prev.shape == (m, n_H_prev, n_W_prev, n_C_prev))

    return (dA_prev, dW, db)

4.池化层的反向传播:

def create_mask_from_window(x):
    mask = x == np.max(x)

    return mask


def distribute_value(dz, shape):
    # 获取矩阵的大小
    (n_H, n_W) = shape

    # 计算平均值
    average = dz / (n_H * n_W)

    # 填充入矩阵
    a = np.ones(shape) * average

    return a


def pool_backward(dA, cache, mode="max"):
    # 获取cache中的值
    (A_prev, hparaeters) = cache

    # 获取hparaeters的值
    f = hparaeters["f"]
    stride = hparaeters["stride"]

    # 获取A_prev和dA的基本信息
    (m, n_H_prev, n_W_prev, n_C_prev) = A_prev.shape
    (m, n_H, n_W, n_C) = dA.shape

    # 初始化输出的结构
    dA_prev = np.zeros_like(A_prev)

    # 开始处理数据
    for i in range(m):
        a_prev = A_prev[i]
        for h in range(n_H):
            for w in range(n_W):
                for c in range(n_C):
                    # 定位切片位置
                    vert_start = h
                    vert_end = vert_start + f
                    horiz_start = w
                    horiz_end = horiz_start + f

                    # 选择反向传播的计算方式
                    if mode == "max":
                        # 开始切片
                        a_prev_slice = a_prev[vert_start:vert_end, horiz_start:horiz_end, c]
                        # 创建掩码
                        mask = create_mask_from_window(a_prev_slice)
                        # 计算dA_prev
                        dA_prev[i, vert_start:vert_end, horiz_start:horiz_end, c] += np.multiply(mask, dA[i, h, w, c])

                    elif mode == "average":
                        # 获取dA的值
                        da = dA[i, h, w, c]
                        # 定义过滤器大小
                        shape = (f, f)
                        # 平均分配
                        dA_prev[i, vert_start:vert_end, horiz_start:horiz_end, c] += distribute_value(da, shape)
    # 数据处理完毕,开始验证格式
    assert (dA_prev.shape == A_prev.shape)

    return dA_prev