吴恩达CS230深度学习笔记(一)

2020-02-07

涉及 基本的神经网络和深度学习的概念。

深度学习引言

神经网络展现出的是,如果你训练一个小型的神经网络,那么这个性能可能会像下图黄色曲线表示那样;如果你训练一个稍微大一点的神经网络,比如说一个中等规模的神经网络(下图蓝色曲线),它在某些数据上面的性能也会更好一些;如果你训练一个非常大的神经网络,它就会变成下图绿色曲线那样,并且保持变得越来越好。因此可以注意到两点:如果你想要获得较高的性能体现,那么你有两个条件要完成,第一个是你需要训练一个规模足够大的神经网络,以发挥数据规模量巨大的优点,另外你需要能画到$x$轴的这个位置,所以你需要很多的数据。因此我们经常说规模一直在推动深度学习的进步,这里的规模指的也同时是神经网络的规模,我们需要一个带有许多隐藏单元的神经网络,也有许多的参数及关联性,就如同需要大规模的数据一样。事实上如今最可靠的方法来在神经网络上获得更好的性能,往往就是要么训练一个更大的神经网络,要么投入更多的数据,这只能在一定程度上起作用,因为最终你耗尽了数据,或者最终你的网络是如此大规模导致将要用太久的时间去训练,但是仅仅提升规模的的确确地让我们在深度学习的世界中摸索了很多时间。

作为一个具体的例子,神经网络方面的一个巨大突破是从sigmoid函数转换到一个ReLU函数,这个函数我们在之前的课程里提到过。

可以知道的一个使用sigmoid函数和机器学习问题是,在这个区域,也就是这个sigmoid函数的梯度会接近零,所以学习的速度会变得非常缓慢,因为当你实现梯度下降以及梯度接近零的时候,参数会更新的很慢,所以学习的速率也会变的很慢,而通过改变这个被叫做激活函数的东西,神经网络换用这一个函数,叫做ReLU的函数(修正线性单元),ReLU它的梯度对于所有输入的负值都是零,因此梯度更加不会趋向逐渐减少到零。而这里的梯度,这条线的斜率在这左边是零,仅仅通过将Sigmod函数转换成ReLU函数,便能够使得一个叫做梯度下降(gradient descent)的算法运行的更快,这就是一个或许相对比较简单的算法创新的例子。但是根本上算法创新所带来的影响,实际上是对计算带来的优化,所以有很多像这样的例子,我们通过改变算法,使得代码运行的更快,这也使得我们能够训练规模更大的神经网络,或者是多端口的网络。即使我们从所有的数据中拥有了大规模的神经网络,快速计算显得更加重要的另一个原因是,训练你的神经网络的过程,很多时候是凭借直觉的,往往你对神经网络架构有了一个想法,于是你尝试写代码实现你的想法,然后让你运行一个试验环境来告诉你,你的神经网络效果有多好,通过参考这个结果再返回去修改你的神经网络里面的一些细节,然后你不断的重复上面的操作,当你的神经网络需要很长时间去训练,需要很长时间重复这一循环。

神经网络的编程基础

二分类、逻辑回归、逻辑回归的代价函数、梯度下降法、导数、更多导数例子,已经在机器学习课程中学过,不再赘述。

计算图

将计算的过程形式化为一个流程图。

下面用到的公式:

假设你要计算$\frac{{dJ}}{{dv}}$,那要怎么算呢?好,比如说,我们要把这个$v$值拿过来,改变一下,那么$J$的值会怎么变呢? 所以定义上$J = 3v$,现在$v=11$,所以如果你让$v$增加一点点,比如到11.001,那么$J =3v =33.003$,所以我这里$v$增加了0.001,然后最终结果是$J$上升到原来的3倍,所以$\frac{{dJ}}{{dv}}=3$,因为对于任何 $v$ 的增量$J$都会有3倍增量,而且这类似于我们在上一个视频中的例子,我们有$f(a)=3a$,然后我们推导出$\frac{{df}(a)}{{da}}= 3$,所以这里我们有$J=3v$,所以$\frac{{dJ}}{{dv}} =3$,这里$J$扮演了$f$的角色,在之前的视频里的例子。

在反向传播算法中的术语,我们看到,如果你想计算最后输出变量的导数,使用你最关心的变量对的导数,那么我们就做完了一步反向传播,在这个流程图中是一个反向步骤。
我们来看另一个例子,$\frac{{dJ}}{da}$是多少呢?换句话说,如果我们提高$a$的数值,对$J$的数值有什么影响?

好,我们看看这个例子。变量$a=5$,我们让它增加到5.001,那么对v的影响就是$a+u$,之前$v=11$,现在变成11.001,我们从上面看到现在$J$ 就变成33.003了,所以我们看到的是,如果你让$a$增加0.001,$J$增加0.003。那么增加$a$,我是说如果你把这个5换成某个新值,那么$a$的改变量就会传播到流程图的最右,所以$J$最后是33.003。所以J的增量是3乘以$a$的增量,意味着这个导数是3。要解释这个计算过程,其中一种方式是:如果你改变了$a$,那么也会改变$v$,通过改变$v$,也会改变$J$,所以$J$值的净变化量,当你提升这个值(0.001),当你把$a$值提高一点点,这就是$J$的变化量(0.003)。

m个样本的梯度下降

首先,让我们时刻记住有关于损失函数$J(w,b)$的定义。
$$J(w,b)=\frac{1}{m}\sum\limits_{i=1}^{m}{L({{a}^{(i)}},{{y}^{(i)}})}$$

当你的算法输出关于样本$y$的${{a}^{(i)}}$,${{a}^{(i)}}$是训练样本的预测值,即:$\sigma ( {{z}^{(i)}})=\sigma( {{w}^{T}}{{x}^{\left( i \right)}}+b)$。 所以我们在前面的幻灯中展示的是对于任意单个训练样本,如何计算微分当你只有一个训练样本。因此$d{{w}*{1}}$,$d{{w}*{\text{2}}}$和$db$ 添上上标$i$表示你求得的相应的值。如果你面对的是我们在之前的幻灯中演示的那种情况,但只使用了一个训练样本$({{x}^{(i)}},{{y}^{(i)}})$。 现在你知道带有求和的全局代价函数,实际上是1到$m$项各个损失的平均。 所以它表明全局代价函数对${{w}*{1}}$的微分,对${{w}*{1}}$的微分也同样是各项损失对${{w}_{1}}$微分的平均。

循环的方式 - 代码流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
J=0;dw1=0;dw2=0;db=0;
for i = 1 to m
z(i) = wx(i)+b;
a(i) = sigmoid(z(i));
J += -[y(i)log(a(i))+(1-y(i))log(1-a(i));
dz(i) = a(i)-y(i);
dw1 += x1(i)dz(i);
dw2 += x2(i)dz(i);
db += dz(i);
J/= m;
dw1/= m;
dw2/= m;
db/= m;
w=w-alpha*dw
b=b-alpha*db
但这种计算中有两个缺点,也就是说应用此方法在逻辑回归上你需要编写两个for循环。第一个for循环是一个小循环遍历$m$个训练样本,第二个for循环是一个遍历所有特征的for循环。这个例子中我们只有2个特征,所以$n$等于2并且${{n}*{x}}$ 等于2。 但如果你有更多特征,你开始编写你的因此$d{{w}*{1}}$,$d{{w}*{2}}$,你有相似的计算从$d{{w}*{3}}$一直下去到$d{{w}_{n}}$。所以看来你需要一个for循环遍历所有$n$个特征。所以这里有一些叫做向量化技术,它可以允许你的代码摆脱这些显式的for循环。

向量化:把for循环改成向量,矩阵运算从而加快计算速度。CPUGPU都有并行化的指令,他们有时候会叫做SIMD指令,这个代表了一个单独指令多维数据,这个的基础意义是,如果你使用了built-in函数,像np.function或者并不要求你实现循环的函数,它可以让python的充分利用并行化计算,这是事实在GPUCPU上面计算,GPU更加擅长SIMD计算,但是CPU事实上也不是太差,可能没有GPU那么擅长吧。

再使用向量化实现逻辑回归就可以写成:
$$Z = w^{T}X + b = np.dot( w.T,X)+b$$ $$A = \sigma( Z )$$ $$dZ = A - Y$$ $${{dw} = \frac{1}{m}*X*dz^{T}\ }$$ $$db= \frac{1}{m}*np.sum( dZ)$$ $$w: = w - a*dw$$ $$b: = b - a*db$$

广播机制

$Z=np.dot(w.T,X)+b$的计算在Python中有一个巧妙的地方,这里 $b$ 是一个实数,或者你可以说是一个 $1\times 1$ 矩阵,只是一个普通的实数。但是当你将这个向量加上这个实数时,Python自动把这个实数 $b$ 扩展成一个 $1\times m$ 的行向量。所以这种情况下的操作似乎有点不可思议,它在Python中被称作广播(brosdcasting)。参数axis用来指明将要进行的运算是沿着哪个轴执行,在numpy中,0轴是垂直的,也就是列,而1轴是水平的,也就是行。

numpy的广播机制中,如果两个数组的后缘维度(shape[-1])的轴长度相符或其中一方的轴长度为1,则认为它们是广播兼容的。广播会在缺失维度和轴长度为1的维度上进行。

举例:
矩阵 $A_{m,n}$ 和矩阵 $B_{1,n}$ 进行四则运算,后缘维度轴长度相符,可以广播,广播沿着轴长度为1的轴进行,即 $B_{1,n}$ 广播成为 ${B_{m,n}}'$ ,之后做逐元素四则运算。

矩阵 $A_{m,n}$ 和矩阵 $B_{m,1}$ 进行四则运算,后缘维度轴长度不相符,但其中一方轴长度为1,可以广播,广播沿着轴长度为1的轴进行,即 $B_{m,1}$ 广播成为 ${B_{m,n}}'$ ,之后做逐元素四则运算。 矩阵 $A_{m,1}$ 和常数$ R$ 进行四则运算,后缘维度轴长度不相符,但其中一方轴长度为1,可以广播,广播沿着缺失维度和轴长度为1的轴进行,缺失维度就是`axis=0`,轴长度为1的轴是`axis=1`,即$R$广播成为 ${B_{m,1}}'$ ,之后做逐元素四则运算。

Python的特性允许你使用广播(broadcasting)功能,这是Pythonnumpy程序语言库中最灵活的地方。而我认为这是程序语言的优点,也是缺点。优点的原因在于它们创造出语言的表达性,Python语言巨大的灵活性使得你仅仅通过一行代码就能做很多事情。但是这也是缺点,由于广播巨大的灵活性,有时候你对于广播的特点以及广播的工作原理这些细节不熟悉的话,你可能会产生很细微或者看起来很奇怪的bug。例如,如果你将一个列向量添加到一个行向量中,你会以为它报出维度不匹配或类型错误之类的错误,但是实际上你会得到一个行向量和列向量的求和。

所以建议你编写神经网络时,不要使用shape为 (5,)(n,) 或者其他一维数组的数据结构。相反,如果你设置 $a$ 为$(5,1)$,那么这就将置于5行1列向量中。在先前的操作里 $a$ 和 $a$ 的转置看起来一样,而现在这样的 $a$ 变成一个新的 $a$ 的转置,并且它是一个行向量。请注意一个细微的差别,在这种数据结构中,当我们输出 $a$ 的转置时有两对方括号,而之前只有一对方括号,所以这就是1行5列的矩阵和一维数组的差别。

我写代码时还有一件经常做的事,那就是如果我不完全确定一个向量的维度(dimension),我经常会扔进一个断言语句(assertion statement)。像这样,去确保在这种情况下是一个$(5,1)$向量,或者说是一个列向量。这些断言语句实际上是要去执行的,并且它们也会有助于为你的代码提供信息。所以不论你要做什么,不要犹豫直接插入断言语句。如果你不小心以一维数组来执行,你也能够重新改变数组维数 $a=reshape$,表明一个$(5,1)$数组或者一个$(1,5)$数组,以致于它表现更像列向量或行向量。

通过在原先的代码里清除一维数组,我的代码变得更加简洁。而且实际上就我在代码中表现的事情而言,我从来不使用一维数组。因此,要去简化你的代码,而且不要使用一维数组。总是使用 $n \times 1$ 维矩阵(基本上是列向量),或者 $1 \times n$ 维矩阵(基本上是行向量),这样你可以减少很多assert语句来节省核矩阵和数组的维数的时间。另外,为了确保你的矩阵或向量所需要的维数时,不要羞于 reshape 操作。

Logistic损失函数的解释

回想一下,在逻辑回归中,需要预测的结果$\hat{y}$,可以表示为$\hat{y}=\sigma(w^{T}x+b)$,$\sigma$是我们熟悉的$S$型函数 $\sigma(z)=\sigma(w^{T}x+b)=\frac{1}{1+e^{-z}}$ 。我们约定 $\hat{y}=p(y=1|x)$ ,即算法的输出$\hat{y}$ 是给定训练样本 $x$ 条件下 $y$ 等于1的概率。换句话说,如果$y=1$,在给定训练样本 $x$ 条件下$y=\hat{y}$;反过来说,如果$y=0$,在给定训练样本$x$条件下 $y$ 等于1减去$\hat{y}(y=1-\hat{y})$,因此,如果 $\hat{y}$ 代表 $y=1$ 的概率,那么$1-\hat{y}$就是 $y=0$的概率。接下来,我们就来分析这两个条件概率公式。

这两个条件概率公式定义形式为 $p(y|x)$并且代表了 $y=0$ 或者 $y=1$ 这两种情况,我们可以将这两个公式合并成一个公式。需要指出的是我们讨论的是二分类问题的损失函数,因此,$y$的取值只能是0或者1。上述的两个条件概率公式可以合并成如下公式:
$$p(y|x)={\hat{y}}^{y}{(1-\hat{y})}^{(1-y)}$$

接下来我会解释为什么可以合并成这种形式的表达式:$(1-\hat{y})$的$(1-y)$次方这行表达式包含了上面的两个条件概率公式,我来解释一下为什么。

第一种情况,假设 $y=1$,由于$y=1$,那么${(\hat{y})}^{y}=\hat{y}$,因为 $\hat{y}$的1次方等于$\hat{y}$,$1-{(1-\hat{y})}^{(1-y)}$的指数项$(1-y)$等于0,由于任何数的0次方都是1,$\hat{y}$乘以1等于$\hat{y}$。因此当$y=1$时 $p(y|x)=\hat{y}$(图中绿色部分)。

第二种情况,当 $y=0$ 时 $p(y|x)$ 等于多少呢? 假设$y=0$,$\hat{y}$的$y$次方就是 的0次方,任何数的0次方都等于1,因此 $p(y|x)=1×{(1-\hat{y})}^{1-y}$ ,前面假设 $y=0$ 因此$(1-y)$就等于1,因此 $p(y|x)=1×(1-\hat{y})$。因此在这里当$y=0$时,$p(y|x)=1-\hat{y}$。这就是这个公式(第二个公式,图中紫色字体部分)的结果。
因此,刚才的推导表明 $p(y|x)={\hat{y}}^{(y)}{(1-\hat{y})}^{(1-y)}$,就是 $p(y|x)$ 的完整定义。由于 log 函数是严格单调递增的函数,最大化 $log(p(y|x))$ 等价于最大化 $p(y|x)$ 并且地计算 $p(y|x)$ 的 log对数,就是计算 $log({\hat{y}}^{(y)}{(1-\hat{y})}^{(1-y)})$ (其实就是将 $p(y|x)$ 代入),通过对数函数化简为:

而这就是我们前面提到的损失函数的负数 $(-L(\hat{y},y))$ ,前面有一个负号的原因是当你训练学习算法时需要算法输出值的概率是最大的(以最大的概率预测这个值),然而在逻辑回归中我们需要最小化损失函数,因此最小化损失函数与最大化条件概率的对数 $log(p(y|x))$ 关联起来了,因此这就是单个训练样本的损失函数表达式。

在 $m$个训练样本的整个训练集中又该如何表示呢,让我们一起来探讨一下。

让我们一起来探讨一下,整个训练集中标签的概率,更正式地来写一下。假设所有的训练样本服从同一分布且相互独立,也即独立同分布的,所有这些样本的联合概率就是每个样本概率的乘积:

如果你想做最大似然估计,需要寻找一组参数,使得给定样本的观测值概率最大,但令这个概率最大化等价于令其对数最大化,在等式两边取对数:

在统计学里面,有一个方法叫做最大似然估计,即求出一组参数,使这个式子取最大值,也就是说,使得这个式子取最大值,$\sum_{i= 1}^{m}{- L(\hat y^{(i)},y^{(i)})}$,可以将负号移到求和符号的外面,$- \sum_{i =1}^{m}{L(\hat y^{(i)},y^{(i)})}$,这样我们就推导出了前面给出的logistic回归的成本函数$J(w,b)= \sum_{i = 1}^{m}{L(\hat y^{(i)},y^{\hat( i)})}$。由于训练模型时,目标是让成本函数最小化,所以我们不是直接用最大似然概率,要去掉这里的负号,最后为了方便,可以对成本函数进行适当的缩放,我们就在前面加一个额外的常数因子$\frac{1}{m}$,即:

浅层神经网络

神经网络的表示、输出、解释 :略。

激活函数

更通常的情况下,使用不同的函数$g( z^{[1]})$,$g$可以是除了sigmoid函数以外的非线性函数。tanh函数或者双曲正切函数是总体上都优于sigmoid函数的激活函数。公式: $a= tanh(z) = \frac{e^{z} - e^{- z}}{e^{z} + e^{- z}}$ 。事实上,tanh函数是sigmoid的向下平移和伸缩后的结果。对它进行了变形后,穿过了$(0,0)$点,并且值域介于+1和-1之间。在训练一个算法模型时,如果使用tanh函数代替sigmoid函数中心化数据,使得数据的平均值更接近0而不是0.5。有一点要说明:我基本已经不用sigmoid激活函数了,tanh函数在所有场合都优于sigmoid函数。

sigmoid函数和tanh函数两者共同的缺点是,在$z$特别大或者特别小的情况下,导数的梯度或者函数的斜率会变得特别小,最后就会接近于0,导致降低梯度下降的速度。

在机器学习另一个很流行的函数是:修正线性单元的函数(ReLu), 公式: $ a =max( 0,z) $ 所以,只要$z$是正值的情况下,导数恒等于1,当$z$是负值的时候,导数恒等于0。从实际上来说,当使用$z$的导数时,$z$=0的导数是没有定义的。但是当编程实现的时候,$z$的取值刚好等于0.00000001,这个值相当小,所以,在实践中,不需要担心这个值,$z$是等于0的时候,假设一个导数是1或者0效果都可以。

这有一些选择激活函数的经验法则:

如果输出是0、1值(二分类问题),则输出层选择sigmoid函数,然后其它的所有单元都选择Relu函数。

这是很多激活函数的默认选择,如果在隐藏层上不确定使用哪个激活函数,那么通常会使用Relu激活函数。有时,也会使用tanh激活函数,但Relu的一个优点是:当$z$是负值的时候,导数等于0。

两者的优点是:

第一,在$z$的区间变动很大的情况下,激活函数的导数或者激活函数的斜率都会远大于0,在程序实现就是一个if-else语句,而sigmoid函数需要进行浮点四则运算,在实践中,使用ReLu激活函数神经网络通常会比使用sigmoid或者tanh激活函数学习的更快。

第二,sigmoidtanh函数的导数在正负饱和区的梯度都会接近于0,这会造成梯度弥散,而ReluLeaky ReLu函数大于0部分都为常数,不会产生梯度弥散现象。(同时应该注意到的是,Relu进入负半区的时候,梯度为0,神经元此时不会训练,产生所谓的稀疏性,而Leaky ReLu不会有这问题)

$z$在ReLu的梯度一半都是0,但是,有足够的隐藏层使得z值大于0,所以对大多数的训练数据来说学习过程仍然可以很快。

快速概括一下不同激活函数的过程和结论:

sigmoid激活函数:除了输出层是一个二分类问题基本不会用它。

tanh激活函数:tanh是非常优秀的,几乎适合所有场合。

ReLu激活函数:最常用的默认函数,如果不确定用哪个激活函数,就使用ReLu或者Leaky ReLu(公式: $a = max( 0.01z,z)$ 为什么常数是0.01?)当然,可以为学习算法选择不同的参数。

除此之外,对于使用场合:不能在隐藏层用线性激活函数,可以用ReLU或者tanh或者leaky ReLU或者其他的非线性激活函数,唯一可以用线性激活函数的通常就是输出层;除了这种情况,会在隐层用线性函数的。

激活函数的导数

在神经网络中使用反向传播的时候,你真的需要计算激活函数的斜率或者导数。针对以下四种激活,求其导数如下:

1)sigmoid activation function
其具体的求导如下: $\frac{d}{dz}g(z) = {\frac{1}{1 + e^{-z}} (1-\frac{1}{1 + e^{-z}})}=g(z)(1-g(z))$

注:当$z$ = 10或$z= -10$ ; $\frac{d}{dz}g(z)\approx0$

当$z $= 0 , $\frac{d}{dz}g(z)\text{=g(z)(1-g(z))=}{1}/{4}$
在神经网络中$a= g(z)$; $g{{(z)}^{'}}=\frac{d}{dz}g(z)=a(1-a)$

2)Tanh activation function
其具体的求导如下: $g(z) = tanh(z) = \frac{e^{z} - e^{-z}}{e^{z} + e^{-z}} $ , $\frac{d}{{d}z}g(z) = 1 - (tanh(z))^{2}$

注:当$z$ = 10或$z= -10$ $\frac{d}{dz}g(z)\approx0$

当$z$ = 0, $\frac{d}{dz}g(z)\text{=1-(0)=}1$

3)Rectified Linear Unit (ReLU)

$g(z) =max (0,z)$

$ g(z)^{‘}= \begin{cases} 0& \text{if z < 0} 1& \text{if z > 0} undefined& \text{if z = 0} \end{cases} $

注:通常在$z$= 0的时候给定其导数1,0;当然$z$=0的情况很少

4)Leaky linear unit (Leaky ReLU)

ReLU类似

注:通常在$z = 0$的时候给定其导数1,0.01;当然$z=0$的情况很少。

反向传播

这部分比较难,但是很重要,通常需要链式求导一堆一堆的手推。自己多推吧。

深度神经网络

神经网络的层数是这么定义的:从左到右,由0开始定义

符号约定:

输入的特征记作$x$,但是$x$同样也是0层的激活函数,所以$x={a}^{[0]}$。

最后一层的激活函数,所以${a}^{[L]}$是等于这个神经网络所预测的输出结果。

前向传播:输入${a}^{[l-1]}$,输出是${a}^{[l]}$,缓存为${z}^{[l]}$;从实现的角度来说我们可以缓存下${w}^{[l]}$和${b}^{[l]}$,这样更容易在不同的环节中调用函数。


实现过程可以写成: ${z}^{[l]}={W}^{[l]}\cdot{a}^{[l-1]}+{b}^{[l]}$ , ${{a}^{[l]}}={{g}^{[l]}}\left( {{z}^{[l]}}\right)$

向量化实现过程可以写成: ${z}^{[l]}={W}^{[l]}\cdot {A}^{[l-1]}+{b}^{[l]}$ , ${A}^{[l]}={g}^{[l]}({Z}^{[l]})$
前向传播需要喂入${A}^{[0]}$也就是$X$,来初始化;初始化的是第一层的输入值。${a}^{[0]}$对应于一个训练样本的输入特征,而${{A}^{[0]}}$对应于一整个训练样本的输入特征,所以这就是这条链的第一个前向函数的输入,重复这个步骤就可以从左到右计算前向传播。

反向传播:输入为${{da}^{[l]}}$,输出为${{da}^{[l-1]}}$,${{dw}^{[l]}}$, ${{db}^{[l]}}$

所以反向传播的步骤可以写成:
$$d{{z}^{[l]}}=d{{a}^{[l]}}*{{g}^{[l]}}'( {{z}^{[l]}}) (1)$$ $$d{{w}^{[l]}}=d{{z}^{[l]}}\cdot{{a}^{[l-1]}}~ (2)$$ $$d{{b}^{[l]}}=d{{z}^{[l]}}~~ (3)$$ $$d{{a}^{[l-1]}}={{w}^{\left[ l \right]T}}\cdot {{dz}^{[l]}} (4)$$ $$d{{z}^{[l]}}={{w}^{[l+1]T}}d{{z}^{[l+1]}}\cdot \text{ }{{g}^{[l]}}'( {{z}^{[l]}})~ (5)$$

式子(5)由式子(4)带入式子(1)得到,前四个式子就可实现反向函数。

向量化实现过程可以写成:
$$d{{Z}^{[l]}}=d{{A}^{[l]}}*{{g}^{\left[ l \right]}}'\left({{Z}^{[l]}} \right)~~ (6)$$ $$d{{W}^{[l]}}=\frac{1}{m}\text{}d{{Z}^{[l]}}\cdot {{A}^{\left[ l-1 \right]T}} (7)$$ $$d{{b}^{[l]}}=\frac{1}{m}\text{ }np.sum(d{{z}^{[l]}},axis=1,keepdims=True) (8)$$ $$d{{A}^{[l-1]}}={{W}^{\left[ l \right]T}}.d{{Z}^{[l]}} (9)$$

深层的网络隐藏单元数量相对较少,隐藏层数目较多,如果浅层的网络想要达到同样的计算结果则需要指数级增长的单元数量才能达到。(举例:用NN实现XOR异或操作)

神经网络的计算过程会是这样的:

把输入特征$a^{[0]}​$,放入第一层并计算第一层的激活函数,用$a^{[1]}​$表示,你需要$W^{[1]}​$和$b^{[1]}​$来计算,之后也缓存$z^{[l]}​$值。之后喂到第二层,第二层里,需要用到$W^{[2]}​$和$b^{[2]}​$,你会需要计算第二层的激活函数$a^{[2]}​$。后面几层以此类推,直到最后你算出了$a^{[L]}​$,第$L​$层的最终输出值$\hat y​$。在这些过程里我们缓存了所有的$z​$值,这就是正向传播的步骤。

对反向传播的步骤而言,我们需要算一系列的反向迭代,就是这样反向计算梯度,你需要把$da^{[L]}$的值放在这里,然后这个方块会给我们${da}^{[L-1]}$的值,以此类推,直到我们得到${da}^{[2]}$和${da}^{[1]}$,你还可以计算多一个输出值,就是${da}^{[0]}$,但这其实是你的输入特征的导数,并不重要,起码对于训练监督学习的权重不算重要,你可以止步于此。反向传播步骤中也会输出$dW^{[l]}$和$db^{[l]}$,这会输出$dW^{[3]}$和$db^{[3]}$等等。目前为止你算好了所有需要的导数,稍微填一下这个流程图。

神经网络的一步训练包含了,从$a^{[0]}$开始,也就是 $x$ 然后经过一系列正向传播计算得到$\hat y$,之后再用输出值计算这个(第二行最后方块),再实现反向传播。现在你就有所有的导数项了,$W$也会在每一层被更新为$W=W-αdW$,$b$也一样,$b=b-αdb$,反向传播就都计算完毕,我们有所有的导数值,那么这是神经网络一个梯度下降循环。

【原文出自黄海广博士 deeplearning_ai_books

支付宝打赏 微信打赏

如果文章对你有帮助,欢迎点击上方按钮打赏作者,更多文章请访问想飞的小菜鸡