吴恩达深度学习笔记汇编1-2:神经网络和深度学习(Neural Networks and Deep Learning)

这部分知识是我在学习吴恩达的深度学习课程时对其笔记的汇总与编注。

内容上,原笔记是视频课程的字幕整合,我对原笔记一些无关紧要的内容进行了删减,以达到精炼的目的,并增加了一些个人见解、语义上的补充以及一部分中英对照和公式推导,以对吴老师的课程内容理解提供更充分的辅助,有一些使用了引用来加以区分,不过大部分并未进行标注(主要是没有观感很好的标注方法)。

我对原文有一些改动,主要是原文有一些地方翻译的不尽人意(这是因为视频课程中吴老师语句也并不会像课本一样做到非常条理清晰),所以我对那些词不达意、逻辑或语句结构混乱的地方进行了删改,以更清晰地表达出吴老师的原意。(这部分改动基本没有标注)

以及一些加粗,也是我自己认为相对重点的地方,带有一定主观性,见谅。

又,每节课程开始都会有简短的引言,不过看上去可能有些废话,但我认为也是必不可少的,所以用引用进行标注,可以选择性阅读

课程地址:【[双语字幕]吴恩达深度学习deeplearning.aihttps://www.bilibili.com/video/BV1FT4y1E74V

笔记链接:https://github.com/fengdu78/deeplearning_ai_books

笔记在线阅读:http://www.ai-start.com/dl2017/

浅层神经网络(Shallow neural networks)

神经网络概述(Neural Network Overview)

本周你将学习如何实现一个神经网络。在我们深入学习具体技术之前,我希望快速的带你预览一下本周你将会学到的东西。如果这个视频中的某些细节你没有看懂你也不用担心,我们将在后面的几个视频中深入讨论技术细节。

现在我们开始快速浏览一下如何实现神经网络。上周我们讨论了逻辑回归,我们了解了下面这个模型如何与下面公式3.1建立联系。

图3.1.1 :

图3.1.1

xwb}    z=wTx+b(3.1)\left. \begin{array}{l} x\\ w\\ b \end{array} \right\} \implies{z={w}^Tx+b} \tag{3.1}

如上所示,首先你需要输入特征 xx,参数 wwbb ,通过这些你就可以计算出 zz

xwb}    z=wTx+b    a=σ(z)    L(a,y)(3.2)\left. \begin{array}{l} x\\ w\\ b \end{array} \right\} \implies{z=w^Tx+b} \implies{a = \sigma(z)}\\ \implies{L(a,y)} \tag{3.2}

接下来使用 zz 就可以计算出 aa。我们将的符号换为表示输出 y^    a=σ(z)\hat{y}\implies{a = \sigma(z)} ,然后可以计算出loss function L(a,y)L(a,y)

神经网络看起来是如下这个样子。正如我之前已经提到过,你可以把许多sigmoid单元堆叠起来形成一个神经网络。对于图中的节点,它包含了之前讲的计算的两个步骤:首先通过公式3.1计算出值 zz ,然后通过 σ(z)\sigma(z) 计算值 aa

3.1.2

在这个神经网络(上图)对应的3个节点,首先计算第一层网络中的各个节点相关的数 z[1]z^{[1]},接着计算 a[1]a^{[1]},在计算下一层网络同理;

我们会使用符号上标 [m]^{[m]} 表示第 mm 层网络中节点相关的数,这些节点的集合被称为第 mm 层网络。这样可以保证[m]^{[m]}不会和我们之前用来表示单个的训练样本的 (i)^{(i)} (即我们使用表示第 ii 个训练样本)混淆

整个计算过程,公式如下:

xW[1]b[1]}    z[1]=W[1]x+b[1]    a[1]=σ(z[1])(3.3)\left. \begin{array}{r} {x }\\ {W^{[1]}}\\ {b^{[1]}} \end{array} \right\} \implies{z^{[1]}=W^{[1]}x+b^{[1]}} \implies{a^{[1]} = \sigma(z^{[1]})} \tag{3.3}

a[1]=σ(z[1])W[2]b[2]}    z[2]=W[2]a[1]+b[2]    a[2]=σ(z[2])    L(a[2],y)(3.4)\left. \begin{array}{r} \text{$a^{[1]} = \sigma(z^{[1]})$}\\ \text{$W^{[2]}$}\\ \text{$b^{[2]}$}\\ \end{array} \right\} \implies{z^{[2]}=W^{[2]}a^{[1]}+b^{[2]}} \implies{a^{[2]} = \sigma(z^{[2]})}\\ \implies L\left(a^{[2]},y \right) \tag{3.4}

类似逻辑回归,在计算后需要使用计算,接下来你需要使用另外一个线性方程对应的参数计算 z[2]z^{[2]},计算 a[2]a^{[2]},此时 a[2]a^{[2]} 就是整个神经网络最终的输出,用 y^\hat{y} 表示网络的输出。

我知道这其中有很多细节,其中有一点非常难以理解,即在逻辑回归中,通过直接计算 zz 得到结果 aa。而这个神经网络中,我们反复的计算 zzaa ,计算 aazz ,最后得到了最终的输出loss function

这里是两层隐藏层,所以经过两次计算,最后通过 σ(z[2])\sigma(z^{[2]}) 来计算隐藏函数

注意区分逻辑回归与神经网络即可

da[1]=dσ(z[1])dW[2]db[2]}    dz[2]=d(W[2]α[1]+b[2])    da[2]=dσ(z[2])    dL(a[2],y)(3.5)\left. \begin{array}{r} {da^{[1]} = {d}\sigma(z^{[1]})}\\ {dW^{[2]}}\\ {db^{[2]}}\\ \end{array} \right\} \impliedby dz^{[2]}={d}(W^{[2]}\alpha^{[1]}+b^{[2]}) \impliedby da^{[2]} = {d}\sigma(z^{[2]})\\ \impliedby dL\left(a^{[2]},y \right) \tag{3.5}

你应该记得逻辑回归中,有一些从后向前的计算用来计算导数 dadadzdz 。同样,在神经网络中我们也有从后向前的计算,最后会计算 da[2]da^{[2]}dz[2]dz^{[2]},计算出来之后,然后计算计算 dW[2]dW^{[2]}db[2]db^{[2]} 等,按公式3.5箭头表示的那样,从右到左反向计算。

现在你大概了解了一下什么是神经网络,基于逻辑回归重复使用了两次该模型得到上述例子的神经网络。我清楚这里面多了很多新符号和细节,如果没有理解也不用担心,在接下来的视频中我们会仔细讨论具体细节。

总结一下:

这部分主要通过与逻辑回归的对比,对神经网络进行了引入,如图:

上面是逻辑回归模型,而下面是有一层隐藏层的一个简单双层神经网络,在逻辑回归中,我们通过前向计算得到各个参数,最终计算损失函数。而神经网络由多个sigmoid单元堆叠而成,每个单元(即图中的圆圈)完成一次对应于逻辑回归中的 z,az,a 的计算,其输出即为下一层神经单元的输入。

不太规范的,我们甚至可以把逻辑回归当做没有隐藏层的神经网络或者单层感知机。

但相似的,他们都有前向计算与反向传播,并且在原理上也是一致的

 

神经网络的表示(Neural Network Representation)

先回顾一下我在上一个视频画几张神经网络的图片,在这次课中我们将讨论这些图片的具体含义,也就是我们画的这些神经网络到底代表什么。

我们首先关注一个例子,本例中的神经网络只包含一个隐藏层(图3.2.1)。这是一张神经网络的图片,让我们给此图的不同部分取一些名字。图3.2.1:

3.2.1

我们有输入特征 x1x_1x2x_2x3x_3 ,它们被竖直地堆叠起来,这叫做神经网络的输入层。它包含了神经网络的输入;然后这里有另外一层我们称之为隐藏层(图3.2.1的四个结点)。待会儿我会回过头来讲解术语"隐藏"的意义;在本例中最后一层只由一个结点构成,而这个只有一个结点的层被称为输出层,它负责产生预测值。

解释隐藏层的含义:在一个神经网络中,当你使用监督学习训练它的时候,训练集包含了输入 xx 也包含了目标输出 yy ,所以术语隐藏层的含义是在训练集中,这些中间结点的准确值我们是不知道到的,也就是说你看不见它们在训练集中应具有的值。你能看见输入的值,你也能看见输出的值,但是隐藏层中的东西,在训练集中你是无法看到的。所以这也解释了词语隐藏层,只是表示你无法在训练集中看到他们。

现在我们再引入几个符号,就像我们之前用向量 xx 表示输入特征。这里有个可代替的记号 a[0]a^{[0]} 可以用来表示输入特征。aa 表示激活的意思,它意味着网络中不同层的值会传递到它们后面的层中,输入层将 xx 传递给隐藏层,所以我们将输入层的激活值称为 a[0]a^{[0]};下一层即隐藏层也同样会产生一些激活值,那么我将其记作 a[1]a^{[1]},所以具体地,这里的第一个单元或结点我们将其表示为 a1[1]a^{[1]}_{1},第二个结点的值我们记为 a2[1]a^{[1]}_{2} 以此类推。所以这里的是一个四维的向量如果写成Python代码,那么它是一个规模为4x1的矩阵或一个大小为4的列向量,如下公式,它是四维的,因为在本例中,我们有四个结点或者单元,或者称为四个隐藏层单元;

a[1]=[a1[1]a2[1]a3[1]a4[1]](3.7)a^{[1]} = \left[ \begin{array}{ccc} a^{[1]}_{1}\\ a^{[1]}_{2}\\ a^{[1]}_{3}\\ a^{[1]}_{4} \end{array} \right] \tag{3.7}

最后输出层将产生某个数值 aa,它只是一个单独的实数,所以的 y^\hat{y} 值将取为 a[2]a^{[2]}。这与逻辑回归很相似,在逻辑回归中,我们有 y^\hat{y} 直接等于 aa ,在逻辑回归中我们只有一个输出层,所以我们没有用带方括号的上标。但是在神经网络中,我们将使用这种带上标的形式来明确地指出这些值来自于哪一层,有趣的是在约定俗成的符号传统中,在这里你所看到的这个例子,只能叫做一个两层的神经网络(图3.2.2)。原因是当我们计算网络的层数时,输入层是不算入总层数内,所以隐藏层是第一层,输出层是第二层。第二个惯例是我们将输入层称为第零层,所以在技术上,这仍然是一个三层的神经网络,因为这里有输入层、隐藏层,还有输出层。但是在传统的符号使用中,如果你阅读研究论文或者在这门课中,你会看到人们将这个神经网络称为一个两层的神经网络,因为我们不将输入层看作一个标准的层。

3.2.2

最后,我们要看到的隐藏层以及最后的输出层是带有参数的,这里的隐藏层将拥有两个参数 WWbb ,我将给它们加上上标 [1]^{[1]}(W[1]W^{[1]},b[1]b^{[1]}),表示这些参数是和第一层这个隐藏层有关系的。之后在这个例子中我们会看到 WW 是一个4x3的矩阵,而 bb 是一个4x1的向量,第一个数字4源自于我们有四个结点或隐藏层单元,然后数字3源自于这里有三个输入特征,我们之后会更加详细地讨论这些矩阵的维数,到那时你可能就更加清楚了。相似的输出层也有一些与之关联的参数 W[2]W^{[2]} 以及 b[2]b^{[2]} 。从维数上来看,它们的规模分别是1x4以及1x1。1x4是因为隐藏层有四个隐藏层单元而输出层只有一个单元,之后我们会对这些矩阵和向量的维度做出更加深入的解释,所以现在你已经知道一个两层的神经网络什么样的了,即它是一个只有一个隐藏层的神经网络。

在下一个视频中。我们将更深入地了解这个神经网络是如何进行计算的,也就是这个神经网络是怎么输入 xx,然后又是怎么得到 y^\hat{y}

 

计算一个神经网络的输出(Computing a Neural Network’s output)

在上一节的视频中,我们介绍只有一个隐藏层的神经网络的结构与符号表示。在这节的视频中让我们了解神经网络的输出究竟是如何计算出来的。

首先,回顾下只有一个隐藏层的简单两层神经网络结构

其中,xx 表示输入特征,aa 表示每个神经元的输出,WW 表示特征的权重,上标表示神经网络的层数(隐藏层为1),下标表示该层的第几个神经元。这是神经网络的符号惯例,下同。

神经网络的计算

关于神经网络是怎么计算的,从我们之前提及的逻辑回归开始,如下图所示。用圆圈表示神经网络的计算单元,逻辑回归的计算有两个步骤,首先你按步骤计算出zz,然后在第二步中你以sigmoid函数为激活函数计算 zz(得出 aa ),一个神经网络只是这样子做了好多次重复计算。

回到两层的神经网络,我们从隐藏层的第一个神经元开始,如上图第一个最上面的箭头所指。从上图可以看出,输入与逻辑回归相似,这个神经元的计算与逻辑回归一样分为两步,小圆圈代表了计算的两个步骤。

第一步,计算 z1[1],z1[1]=w1[1]Tx+b1[1]z^{[1]}_1,z^{[1]}_1 = w^{[1]T}_1x + b^{[1]}_1

第二步,通过激活函数计算 a1[1],a1[1]=σ(z1[1])a^{[1]}_1,a^{[1]}_1 = \sigma(z^{[1]}_1)

隐藏层的第二个以及后面两个神经元的计算过程一样,只是注意符号表示不同,最终分别得到a2[1]a3[1]a4[1]a^{[1]}_2、a^{[1]}_3、a^{[1]}_4,详细结果见下:

z1[1]=w1[1]Tx+b1[1],a1[1]=σ(z1[1])z^{[1]}_1 = w^{[1]T}_1x + b^{[1]}_1, a^{[1]}_1 = \sigma(z^{[1]}_1)

z2[1]=w2[1]Tx+b2[1],a2[1]=σ(z2[1])z^{[1]}_2 = w^{[1]T}_2x + b^{[1]}_2, a^{[1]}_2 = \sigma(z^{[1]}_2)

z3[1]=w3[1]Tx+b3[1],a3[1]=σ(z3[1])z^{[1]}_3 = w^{[1]T}_3x + b^{[1]}_3, a^{[1]}_3 = \sigma(z^{[1]}_3)

z4[1]=w4[1]Tx+b4[1],a4[1]=σ(z4[1])z^{[1]}_4 = w^{[1]T}_4x + b^{[1]}_4, a^{[1]}_4 = \sigma(z^{[1]}_4)

向量化计算

如果你执行神经网络的程序,用for循环来做这些看起来真的很低效。所以接下来我们要做的就是把这四个等式向量化。向量化的过程是将神经网络中的一层神经元参数纵向堆积起来,例如隐藏层中的 ww 纵向堆积起来变成一个(4,3)(4,3)的矩阵,用符号 W[1]W^{[1]} 表示。另一个看待这个的方法是我们有四个逻辑回归单元,且每一个逻辑回归单元都有相对应的参数——向量 ww ,把这四个向量堆积在一起,你会得出这4×3的矩阵。

z[n]=w[n]x+b[n](3.8)z^{[n]} = w^{[n]}x + b^{[n]}\tag{3.8}

a[n]=σ(z[n])(3.9)a^{[n]}=\sigma(z^{[n]})\tag{3.9}

详细过程见下:

a[1]=[a1[1]a2[1]a3[1]a4[1]]=σ(z[1])(3.10)a^{[1]} = \left[ \begin{array}{c} a^{[1]}_{1}\\ a^{[1]}_{2}\\ a^{[1]}_{3}\\ a^{[1]}_{4} \end{array} \right] = \sigma(z^{[1]}) \tag{3.10}

[z1[1]z2[1]z3[1]z4[1]]=[...W1[1]T......W2[1]T......W3[1]T......W4[1]T...]W[1][x1x2x3]input+[b1[1]b2[1]b3[1]b4[1]]b[1](3.11)\left[ \begin{array}{c} z^{[1]}_{1}\\ z^{[1]}_{2}\\ z^{[1]}_{3}\\ z^{[1]}_{4}\\ \end{array} \right] = \overbrace{ \left[ \begin{array}{c} ...W^{[1]T}_{1}...\\ ...W^{[1]T}_{2}...\\ ...W^{[1]T}_{3}...\\ ...W^{[1]T}_{4}... \end{array} \right] }^{W^{[1]}} * \overbrace{ \left[ \begin{array}{c} x_1\\ x_2\\ x_3\\ \end{array} \right] }^{input} + \overbrace{ \left[ \begin{array}{c} b^{[1]}_1\\ b^{[1]}_2\\ b^{[1]}_3\\ b^{[1]}_4\\ \end{array} \right] }^{b^{[1]}} \tag{3.11}

对于神经网络的第一层,给予一个输入 xx,得到 a[1]a^{[1]}xx 可以表示为a[0]a^{[0]}。通过相似的衍生你会发现,后一层的表示同样可以写成类似的形式,得到 a[2]a^{[2]}y^=a[2]\hat{y} = a^{[2]},具体过程见公式3.8、3.9。

如上图左半部分所示为神经网络,把网络左边部分盖住先忽略,那么最后的输出单元就相当于一个逻辑回归的计算单元。当你有一个包含一层隐藏层的神经网络,你需要去实现以计算得到输出的是右边的四个等式,并且可以看成是一个向量化的计算过程,计算出隐藏层的四个逻辑回归单元和整个隐藏层的输出结果,如果编程实现需要的也只是这四行代码。

通过本视频,你能够根据给出的一个单独的输入特征向量,运用四行代码计算出一个简单神经网络的输出。接下来你将了解的是如何一次能够计算出不止一个样本的神经网络输出,而是能一次性计算整个训练集的输出。

 

多样本向量化(Vectorizing across multiple examples)

在上一个视频,了解到如何针对于单一的训练样本,在神经网络上计算出预测值。

在这个视频,将会了解到如何向量化多个训练样本,并计算出结果。该过程与你在逻辑回归中所做类似

逻辑回归是将各个训练样本组合成矩阵,对矩阵的各列进行计算。神经网络是通过对逻辑回归中的等式简单的变形,让神经网络计算出输出值。这种计算是所有的训练样本同时进行的,以下是实现它具体的步骤:

上一节视频中得到的四个等式,它们给出了如何计算出z[1]z^{[1]}a[1]a^{[1]}z[2]z^{[2]}a[2]a^{[2]}

对于一个给定的输入特征向量 XX,这四个等式可以计算出 α[2]\alpha^{[2]} 等于 y^\hat{y} 。这是针对于单一的训练样本。如果有mm 个训练样本,那么就需要重复这个过程。

用第一个训练样本 x[1]x^{[1]} 来计算出预测值 y^[1]\hat{y}^{[1]},就是第一个训练样本上得出的结果。

然后,用 x[2]x^{[2]} 来计算出预测值 y^[2]\hat{y}^{[2]},循环往复,直至用 x[m]x^{[m]} 计算出 y^[m]\hat{y}^{[m]}

用激活函数表示法,如上图左下所示,它写成 a[2](1)a^{[2](1)}a[2](2)a^{[2](2)}a[2](m)a^{[2](m)}

【注】:a[2](i)a^{[2](i)}(i)(i) 是指第 ii 个训练样本而 [2][2] 是指第二层。

如果有一个非向量化形式的实现,而且要计算出它的预测值,对于所有训练样本,需要让 ii 从1到 mm 实现这四个等式:

z[1](i)=W[1](i)x(i)+b[1](i)z^{[1](i)}=W^{[1](i)}x^{(i)}+b^{[1](i)}

a[1](i)=σ(z[1](i))a^{[1](i)}=\sigma(z^{[1](i)})

z[2](i)=W[2](i)a[1](i)+b[2](i)z^{[2](i)}=W^{[2](i)}a^{[1](i)}+b^{[2](i)}

a[2](i)=σ(z[2](i))a^{[2](i)}=\sigma(z^{[2](i)})

对于上面的这个方程中的 (i)^{(i)},是所有依赖于训练样本的变量,即将 (i)(i) 添加到 xxzzaa。如果想计算 mm 个训练样本上的所有输出,就应该向量化整个计算,以简化这列。

本课程需要使用很多线性代数的内容,重要的是能够正确地实现这一点,尤其是在深度学习的错误中。实际上本课程认真地选择了运算符号,这些符号只是针对于这个课程的,并且能使这些向量化容易一些。

所以,希望通过这个细节可以更快地正确实现这些算法。接下来讲讲如何向量化这些:

x=[x(1)x(2)x(m)](3.12)x = \left[ \begin{array}{c} \vdots & \vdots & \vdots & \vdots\\ x^{(1)} & x^{(2)} & \cdots & x^{(m)}\\ \vdots & \vdots & \vdots & \vdots\\ \end{array} \right] \tag{3.12}

Z[1]=[z[1](1)z[1](2)z[1](m)](3.13)Z^{[1]} = \left[ \begin{array}{c} \vdots & \vdots & \vdots & \vdots\\ z^{[1](1)} & z^{[1](2)} & \cdots & z^{[1](m)}\\ \vdots & \vdots & \vdots & \vdots\\ \end{array} \right] \tag{3.13}

A[1]=[α[1](1)α[1](2)α[1](m)](3.14)A^{[1]} = \left[ \begin{array}{c} \vdots & \vdots & \vdots & \vdots\\ \alpha^{[1](1)} & \alpha^{[1](2)} & \cdots & \alpha^{[1](m)}\\ \vdots & \vdots & \vdots & \vdots\\ \end{array} \right] \tag{3.14}

z[1](i)=W[1](i)x(i)+b[1]α[1](i)=σ(z[1](i))z[2](i)=W[2](i)α[1](i)+b[2]α[2](i)=σ(z[2](i))}    {A[1]=σ(z[1])z[2]=W[2]A[1]+b[2]A[2]=σ(z[2])(3.15)\left. \begin{array}{r} \text{$z^{[1](i)} = W^{[1](i)}x^{(i)} + b^{[1]}$}\\ \text{$\alpha^{[1](i)} = \sigma(z^{[1](i)})$}\\ \text{$z^{[2](i)} = W^{[2](i)}\alpha^{[1](i)} + b^{[2]}$}\\ \text{$\alpha^{[2](i)} = \sigma(z^{[2](i)})$}\\ \end{array} \right\} \implies \begin{cases} \text{$A^{[1]} = \sigma(z^{[1]})$}\\ \text{$z^{[2]} = W^{[2]}A^{[1]} + b^{[2]}$}\\ \text{$A^{[2]} = \sigma(z^{[2]})$}\\ \end{cases} \tag{3.15}

定义矩阵 XX 等于训练样本,将它们组合成矩阵的各列,形成一个 nn 维或 nn 乘以 mm 维矩阵。接下来计算见公式3.15:

以此类推,从小写的向量 xx 到这个大写的矩阵 XX,只是通过组合 xx 向量在矩阵的各列中。

同理,z[1](1)z^{[1](1)}z[1](2)z^{[1](2)} 等等都是 Z[1]Z^{[1]} 的列向量,将所有 mm 个都组合在各列中,就的到矩阵 Z[1]Z^{[1]}

同理,a[1](1)a^{[1](1)}a[1](2)a^{[1](2)},……,a[1](m)a^{[1](m)} 将其组合在矩阵各列中,如同从向量 xx 到矩阵 XX,以及从向量 zz 到矩阵ZZ一样,就能得到矩阵 A[1]A^{[1]}

同样的,对于 Z[2]Z^{[2]}A[2]A^{[2]},也是这样得到。

这种符号其中一个作用就是,可以通过训练样本来进行索引。这就是水平索引对应于不同的训练样本的原因,从左到右扫描列向量就能得到各个训练样本的对应过程参数(X,Z,AX,Z,A 等等)。

在垂直方向,这个垂直索引对应于神经网络中的不同节点。例如,A[1]A^{[1]} 中最左上角这个节点,对应于激活单元,它是位于第一个训练样本上的第一个隐藏单元。它的下面的值对应于第二个隐藏单元的激活值,等等。

当垂直扫描,是索引到隐藏单元的数字。当水平扫描,将从第一个训练示例中从第一个隐藏的单元到第二个训练样本,第三个训练样本……直到节点对应于第一个隐藏单元的激活值,且这个隐藏单元是位于这 mm 个训练样本中的最终训练样本。

也就是说,从水平上看,矩阵 AA 代表了各个训练样本。从竖直上看,矩阵 AA 的不同的索引对应于不同的隐藏单元。

对于矩阵 ZXZ,X 情况也类似,水平方向上,对应于不同的训练样本;竖直方向上,对应不同的输入特征,而这就是神经网络输入层中各个节点。

神经网络上通过在多样本情况下的向量化来使用这些等式。

在下一个视频中,将证明为什么这是一种正确向量化的实现。这种证明将会与逻辑回归中的证明类似。

 

向量化实现的解释(Justification for vectorized implementation)

在上一个视频中,我们学习到如何将多个训练样本横向堆叠成一个矩阵 XX,然后就可以推导出神经网络中前向传播(forward propagation)部分的向量化实现。

在这个视频中,我们将会继续了解到,为什么上一节中写下的公式就是将多个样本向量化的正确实现。

我们先手动对几个样本计算一下前向传播,看看有什么规律:

z[1](1)=W[1]x(1)+b[1]z[1](2)=W[1]x(2)+b[1]z[1](3)=W[1]x(3)+b[1](3.16)z^{[1](1)} = W^{[1]}x^{(1)} + b^{[1]}\\ z^{[1](2)} = W^{[1]}x^{(2)} + b^{[1]}\\ z^{[1](3)} = W^{[1]}x^{(3)} + b^{[1]} \tag{3.16}

这里,为了描述的简便,我们先忽略掉 b[1]b^{[1]},后面你将会看到利用Python 的广播机制,可以很容易的将 b[1]b^{[1]} 加进来。

现在 W[1]W^{[1]} 是一个矩阵,x(1),x(2),x(3)x^{(1)},x^{(2)},x^{(3)} 都是列向量,矩阵乘以列向量得到列向量,下面将它们用图形直观的表示出来:

W[1]x=[]u×n[x(1)x(2)x(3)]u×m=[w(1)x(1)w(1)x(2)w(1)x(3)]u×m=[z[1](1)z[1](2)z[1](3)]u×m=Z[1](3.17)W^{[1]} x = \left[ \begin{array}{ccc} \cdots \\ \cdots \\ \cdots \\ \end{array} \right]_{u\times n} \left[ \begin{array}{c} \vdots &\vdots & \vdots & \vdots \\ x^{(1)} & x^{(2)} & x^{(3)} & \vdots\\ \vdots &\vdots & \vdots & \vdots \\ \end{array} \right]_{u\times m} = \left[ \begin{array}{c} \vdots &\vdots & \vdots & \vdots \\ w^{(1)}x^{(1)} & w^{(1)}x^{(2)} & w^{(1)}x^{(3)} & \vdots\\ \vdots &\vdots & \vdots & \vdots \\ \end{array} \right]_{u\times m} =\\ \left[ \begin{array}{c} \vdots &\vdots & \vdots & \vdots \\ z^{[1](1)} & z^{[1](2)} & z^{[1](3)} & \vdots\\ \vdots &\vdots & \vdots & \vdots \\ \end{array} \right]_{u\times m} = Z^{[1]} \tag{3.17}

这里 uu 的值因情况而定(可以参考前面章节幻灯片的举例就可以看出)(在多个隐藏层的条件下)

单隐藏层 u=n[1]u=n^{[1]} 。其实 u=n[i]u=n^{[i]} 也不是不行,但这几部分课程使用的一直都是单隐藏层神经网络,下一节中还会规定方括号上标 [1][1] 代表隐藏层,方括号上标 [2][2] 表示输出层,所以可能产生混淆。

视频中,吴恩达老师很细心的用不同的颜色表示不同的样本向量,及其对应的输出。所以从图中可以看出,当加入更多样本时,只需向矩阵XX 中加入更多列。

所以从这里我们也可以了解到,为什么之前我们对单个样本的计算要写成 z[1](i)=W[1]x(i)+b[1]z^{[1](i)} = W^{[1]}x^{(i)} + b^{[1]} 这种形式,因为当有不同的训练样本时,将它们堆到矩阵 XX 的各列中,那么它们的输出也就会相应的堆叠到矩阵 Z[1]Z^{[1]} 的各列中。现在我们就可以直接计算矩阵 Z[1]Z^{[1]} 加上 b[1]b^{[1]},因为列向量 b[1]b^{[1]} 和矩阵 Z[1]Z^{[1]} 的列向量有着相同的尺寸,而Python的广播机制对于这种矩阵与向量直接相加的处理方式是,将向量与矩阵的每一列相加。

所以这一节只是说明了为什么公式 Z[1]=W[1]X+ b[1]Z^{[1]} =W^{[1]}X + \ b^{[1]} 是前向传播的第一步计算的正确向量化实现,但事实证明,类似的分析可以发现,前向传播的其它步也可以使用非常相似的逻辑,即如果将输入按列向量横向堆叠进矩阵,那么通过公式计算之后,也能得到成列堆叠的输出。

最后,对这一段视频的内容做一个总结:

由公式3.12、公式3.13、公式3.14、公式3.15可以看出,使用向量化的方法,可以不需要显示循环,而直接通过矩阵运算从XX就可以计算出 A[1]A^{[1]} ,实际上 XX 可以记为 A[0]A^{[0]},使用同样的方法就可以由神经网络中的每一层的输入 A[i1]A^{[i-1]} 计算输出 A[i]A^{[i]} 。其实这些方程有一定对称性,其中第一个方程也可以写成 Z[1]=W[1]A[0]+b[1]Z^{[1]} = W^{[1]}A^{[0]} + b^{[1]} ,你看这对方程,还有这对方程形式其实很类似,只不过这里所有指标加了1。所以这样就显示出神经网络的不同层次,你知道大概每一步做的都是一样的,或者只不过同样的计算不断重复而已。随着网络的深度变大,基本上也还是重复这两步运算,只不过是比这里你看到的重复次数更多。

以上就是对神经网络向量化实现的正确性的解释,到目前为止,我们仅使用sigmoid函数作为激活函数,事实上这并非最好的选择,在下一个视频中,将会继续深入的讲解如何使用更多不同种类的激活函数。

 

激活函数(Activation functions)

使用一个神经网络时,需要决定使用哪种激活函数用隐藏层上,哪种用在输出节点上。到目前为止,之前的视频只用过sigmoid激活函数,但是,有时其他的激活函数效果会更好。

在神经网路的前向传播中,的a[1]=σ(z[1])a^{[1]} = \sigma(z^{[1]})a[2]=σ(z[2])a^{[2]} =\sigma(z^{[2]})这两步会使用到sigmoid函数。sigmoid函数在这里被称为激活函数。

a=σ(z)=11+ez(3.18)a = \sigma(z) = \frac{1}{1 + e^{- z}} \tag{3.18}

更通常的情况下,使用不同的函数 g(z[1])g(z^{[1]})gg 可以是除了sigmoid函数以外的非线性函数。tanh函数或者双曲正切函数是总体上都优于sigmoid函数的激活函数。

如图,a=tanh(z)a = tanh(z) 的值域是位于+1和-1之间。

a=tanh(z)=ezezez+ez(3.19)a= tanh(z) = \frac{e^z - e^{- z}}{e^z + e^{- z}} \tag{3.19}

事实上,tanh函数是sigmoid的向下平移和伸缩后的结果。对它进行了变形后,穿过了(0,0)(0,0)点,并且值域介于+1和-1之间。

结果表明,如果在隐藏层上使用函数 g(z[1])=tanh(z[1])g(z^{[1]}) = tanh(z^{[1]}) ,效果总是优于sigmoid函数。因为函数值域在-1和+1的激活函数,其均值是更接近零均值的。在训练一个算法模型时,如果使用tanh函数代替sigmoid函数中心化数据,使得数据的平均值更接近0而不是0.5.

在讨论优化算法时,有一点要说明:我基本已经不用sigmoid激活函数了,tanh函数在所有场合都优于sigmoid函数。

但有一个例外:在二分类的问题中,对于输出层,因为 yy 的值是0或1,所以想让 y^\hat{y} 的数值介于0和1之间,而不是在-1和+1之间。所以需要使用sigmoid激活函数。这里的 g(z[2])=σ(z[2])g(z^{[2]}) = \sigma(z^{[2]})

在这个例子里看到的是,对隐藏层使用tanh激活函数,输出层使用sigmoid函数

所以,在不同的神经网络层中,激活函数可以不同。为了表示不同的激活函数,在不同的层中,使用方括号上标来指出 gg 上标为 [1][1] 的激活函数,可能会跟 gg 上标为 [2][2] 不同。方括号上标 [1][1] 代表隐藏层,方括号上标 [2][2] 表示输出层

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

在机器学习另一个很流行的函数是:修正线性单元的函数(ReLu),a=max(0,z)a=max(0,z)

所以,只要 zz 是正值的情况下,导数恒等于1,当 zz 是负值的时候,导数恒等于0。从实际上来说,当使用 zz 的导数时, z=0z=0 的导数是没有定义的。但是当编程实现的时候,zz 的取值刚好等于0.00000001,这个值相当小,所以,在实践中,不需要担心这个值,zz 是等于0的时候,假设一个导数是1或者0效果都可以。

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

如果输出是0、1值(二分类问题),则输出层选择sigmoid函数,然后其它的所有单元都选择Relu函数。这是很多激活函数的默认选择,如果在隐藏层上不确定使用哪个激活函数,那么通常会使用Relu激活函数。有时,也会使用tanh激活函数,但Relu的一个优点是:zz是负值的时候,导数等于0

这里也有另一个版本的Relu被称为Leaky Relu。当 zz 是负值时,这个函数的值不是等于0,而是轻微的倾斜,如图。这个函数通常比Relu激活函数效果要好,尽管在实际中Leaky ReLu使用的并不多。

两者的优点是:

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

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

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

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

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

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

  • ReLu激活函数:最常用的默认函数,如果不确定用哪个激活函数,就使用ReLu或者Leaky ReLua=max(0.01z,z)a= max(0.01z,z)

为什么常数是0.01?当然,可以为学习算法选择不同的参数。

在选择自己神经网络的激活函数时,有一定的直观感受,在深度学习中的经常遇到一个问题:在编写神经网络的时候,会有很多选择:隐藏层单元的个数、激活函数的选择、初始化权值……这些选择想得到一个对比较好的指导原则是挺困难的。

鉴于以上三个原因,以及在工业界的见闻,提供一种直观的感受,哪一种工业界用的多,哪一种用的少。但是,自己的神经网络的应用,以及其特殊性,是很难提前知道选择哪些效果更好。所以通常的建议是:如果不确定哪一个激活函数效果更好,可以把它们都试试,然后在验证集或者发展集上进行评价。然后看哪一种表现的更好,就去使用它。

为自己的神经网络的应用测试这些不同的选择,会在以后检验自己的神经网络或者评估算法的时候,看到不同的效果。如果仅仅遵守使用默认的ReLu激活函数,而不要用其他的激励函数,那就可能在近期或者往后,每次解决问题的时候都使用相同的办法。

 

为什么需要非线性激活函数?(why need a nonlinear activation function?)

为什么神经网络需要非线性激活函数?事实证明:要让你的神经网络能够拟合出有趣的函数,你必须使用非线性激活函数,证明如下:

对于神经网络正向传播的方程,我们去掉函数 gg,然后令 a[1]=z[1]a^{[1]} = z^{[1]},或者我们也可以令 g(z)=zg(z)=z,这个有时被叫做线性激活函数(更学术点的名字是恒等激励函数,因为它们就是把输入值输出)。为了说明问题,我们把 a[2]=z[2]a^{[2]} = z^{[2]},那么这个模型的输出 yy 或仅仅只是输入特征 xx 的线性组合。

如果我们改变前面的式子,令:

a[1]=z[1]=W[1]x+b[1](1)a^{[1]} = z^{[1]} = W^{[1]}x + b^{[1]} \tag1

a[2]=z[2]=W[2]a[1]+b[2](2)a^{[2]} = z^{[2]} = W^{[2]}a^{[1]}+ b^{[2]}\tag2

将式(1)代入式(2)中,则:

a[2]=z[2]=W[2]W[1]x+W[2]b[1]+b[2](3)a^{[2]} = z^{[2]} = W^{[2]}W^{[1]}x + W^{[2]}b^{[1]} + b^{[2]}\tag3

简化多项式得

a[2]=z[2]=Wx+ba^{[2]} = z^{[2]} = W^{'}x + b^{'}

其中:

W=W[2]W[1]b=W[2]b[1]+b[2]W'=W^{[2]}W^{[1]}\\ b'=W^{[2]}b^{[1]} + b^{[2]}

如果你是用线性激活函数或者叫恒等激励函数,那么神经网络只是把输入线性组合再输出。

我们稍后会谈到深度网络,有很多层的神经网络,很多隐藏层。事实证明,如果你使用线性激活函数或者没有使用一个激活函数,那么无论你的神经网络有多少层一直在做的只是计算线性函数,所以不如直接去掉全部隐藏层。在我们的简明案例中,事实证明如果你在隐藏层用线性激活函数,在输出层用sigmoid函数,那么这个模型的复杂度和没有任何隐藏层的标准Logistic回归是一样的,如果你愿意的话,可以证明一下。

在这里线性隐层一点用也没有,因为这两个线性函数的组合本身就是线性函数,所以除非你引入非线性,否则你无法计算更有趣的函数,即使你的网络层数再多也不行;只有一个地方可以使用线性激活函数:g(z)=zg(z)=z,就是你在做机器学习中的回归问题。yy 是一个实数,举个例子,比如你想预测房地产价格,yy 就不是二分类任务0或1,而是一个实数,从0到正无穷。如果 yy 是个实数,那么在输出层用线性激活函数也许可行,你的输出也是一个实数,从负无穷到正无穷。

总而言之,不能在隐藏层用线性激活函数,可以用ReLU或者tanh或者leaky ReLU或者其他的非线性激活函数,唯一可以用线性激活函数的通常就是输出层除了一些特殊情况,会在隐层用线性函数的,比如与压缩有关的,那方面在这里将不深入讨论。在这之外,在隐层使用线性激活函数非常少见。因为房价都是非负数,所以我们也可以在输出层使用ReLU函数这样你的 y^\hat{y} 都大于等于0。

理解为什么使用非线性激活函数对于神经网络十分关键,接下来我们讨论梯度下降,并在下一个视频中开始讨论梯度下降的基础——激活函数的导数。

 

激活函数的导数(Derivatives of activation functions)

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

1)sigmoid activation function

其具体的求导如下:

ddzg(z)=11+ez(111+ez)=g(z)(1g(z))\frac{d}{dz}g(z) = {\frac{1}{1 + e^{-z}} (1-\frac{1}{1 + e^{-z}})}=g(z)(1-g(z))

注:

zz = 10 或 z=10z= -10 ; ddzg(z)0\frac{d}{dz}g(z)\approx0

z=0z=0 , ddzg(z)=g(z)(1-g(z))=1/4\frac{d}{dz}g(z)\text{=g(z)(1-g(z))=}{1}/{4}

在神经网络中 a=g(z)a= g(z); g(z)=ddzg(z)=a(1a){g(z)}^{'}=\frac{d}{dz}g(z)=a(1-a)

2)Tanh activation function

其具体的求导如下:

g(z)=tanh(z)=ezezez+ezg(z) = tanh(z) = \frac{e^{z} - e^{-z}}{e^{z} + e^{-z}}

ddzg(z)=1(tanh(z))2\frac{d}{dz}g(z) = 1 - (tanh(z))^{2}

注:

z=10z= 10z=10z= -10 ddzg(z)0\frac{d}{dz}g(z)\approx0

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

3)Rectified Linear Unit (ReLU)

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

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

注:通常在 z=0z=0 的时候给定其导数为1或为0;当然 z=0z=0 的情况很少,也无关紧要

4)Leaky linear unit (Leaky ReLU)

ReLU类似

g(z)=max(0.01z,z)g(z)={0.01if z < 01if z > 0undefinedif z = 0g(z)=\max(0.01z,z) \\ \\ \\ g(z)^{'}= \begin{cases} 0.01& \text{if z < 0}\\ 1& \text{if z > 0}\\ undefined& \text{if z = 0} \end{cases}

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

 

神经网络的梯度下降(Gradient descent for neural networks)

在这个视频中,我会给你实现反向传播或者说梯度下降算法的方程组,在下一个视频我们会介绍为什么这几个特定的方程是针对你的神经网络实现梯度下降的正确方程。

你的单隐层神经网络会有 W[1]W^{[1]}b[1]b^{[1]}W[2]W^{[2]}b[2]b^{[2]} 这些参数,还有个 nxn_x 表示输入特征的个数,n[1]n^{[1]} 表示隐藏单元个数,n[2]n^{[2]} 表示输出单元个数(在我们的例子里 n[2]=1n^{[2]}=1 ,当然以后会有多个输出的情况)。

在我们的例子中,我们只介绍过的这种情况,那么参数:

矩阵 W[1]W^{[1]} 的维度就是 (n[1],n[0]n^{[1]}, n^{[0]}),b[1]b^{[1]} 就是 n[1]n^{[1]} 维向量,可以写成 (n[1],1)(n^{[1]}, 1),就是一个的列向量。

矩阵 W[2]W^{[2]} 的维度就是 (n[2],n[1]n^{[2]}, n^{[1]}),b[2]b^{[2]} 的维度就是 (n[2],1)(n^{[2]},1)

你还有一个神经网络的成本函数,假设你在做二分类任务,那么你的成本函数等于:

J(W[1],b[1],W[2],b[2])=1mi=1mL(y^,y)J(W^{[1]},b^{[1]},W^{[2]},b^{[2]}) = {\frac{1}{m}}\sum_{i=1}^mL(\hat{y}, y)

loss function和之前做logistic回归完全一样,即:

L(y^,y)=ylog(y^)(1y)log(1y^)L\left( \hat{y},y \right)=-y\log(\hat{y})-(1-y)\log (1-\hat{y})

训练参数需要做梯度下降,在训练神经网络的时候,随机初始化参数很重要,而不是初始化成全零。当你参数初始化成某些值后,每次梯度下降都会循环计算预测值 y^(i),(i=1,2,,m)\hat{y}^{(i)},(i=1,2,…,m)

dW[1]=dJdW[1],db[1]=dJdb[1](3.28)dW^{[1]} = \frac{dJ}{dW^{[1]}},db^{[1]} = \frac{dJ}{db^{[1]}}\tag{3.28}

dW[2]=dJdW[2],db[2]=dJdb[2](3.29){d}W^{[2]} = \frac{dJ}{dW^{[2]}},{d}b^{[2]} = \frac{dJ}{db^{[2]}}\tag{3.29}

W[1]    W[1]αdW[1],b[1]    b[1]αdb[1](3.30)W^{[1]}\implies{W^{[1]} - \alpha \cdot dW^{[1]}},b^{[1]}\implies{b^{[1]} -\alpha \cdot db^{[1]}}\tag{3.30}

W[2]    W[2]αdW[2],b[2]    b[2]αdb[2](3.31)W^{[2]}\implies{W^{[2]} - \alpha \cdot {\rm d}W^{[2]}},b^{[2]}\implies{b^{[2]} - \alpha \cdot {\rm d}b^{[2]}}\tag{3.31}

正向传播方程如下(之前讲过):

z[1]=W[1]x+b[1]z^{[1]} = W^{[1]}x + b^{[1]}

a[1]=σ(z[1])a^{[1]} = \sigma(z^{[1]})

z[2]=W[2]a[1]+b[2]z^{[2]} = W^{[2]}a^{[1]} + b^{[2]}

a[2]=g[2](z[z])=σ(z[2])a^{[2]} = g^{[2]}(z^{[z]}) = \sigma(z^{[2]})

反向传播方程如下:

back propagation

dz[2]=A[2]Y,Y=[y[1]y[2]y[m]](3.32)dz^{[2]} = A^{[2]} - Y , Y = \begin{bmatrix}y^{[1]} & y^{[2]} & \cdots & y^{[m]}\\ \end{bmatrix}\tag{3.32}

dW[2]=1mdz[2]A[1]T(3.33)dW^{[2]} = {\frac{1}{m}}dz^{[2]}A^{[1]T}\tag{3.33}

db[2]=1mnp.sum(dz[2],axis=1,keepdims=True)(3.34){\rm d}b^{[2]} = {\frac{1}{m}}np.sum({d}z^{[2]},axis=1,keepdims=True)\tag{3.34}

dz[1]=W[2]Tdz[2](n[1],m)g[1]activation  function  of  hidden  layer(z[1])(n[1],m)(3.35)dz^{[1]} = \underbrace{W^{[2]T}{ d}z^{[2]}}_{(n^{[1]},m)}\quad*\underbrace{g^{[1]'}}_{activation \; function \; of \; hidden \; layer}*\quad\underbrace{(z^{[1]})}_{(n^{[1]},m)}\tag{3.35}

dW[1]=1mdz[1]xT(3.36)dW^{[1]} = {\frac{1}{m}}dz^{[1]}x^{T}\tag{3.36}

db[1](n[1],1)=1mnp.sum(dz[1],axis=1,keepdims=True)(3.37){\underbrace{db^{[1]}}_{(n^{[1]},1)}} = {\frac{1}{m}}np.sum(dz^{[1]},axis=1,keepdims=True) \tag{3.37}

注:这些都是针对所有样本进行过向量化,Y=[y(1) y(2)  y(m)]Y=[y^{(1)}\ y^{(2)}\ \cdots\ y^{(m)}]1×m1×m 的矩阵;这里np.sum是python的numpy命令,axis=1表示水平相加求和,keepdims是防止python输出那些古怪的秩数 (n,)(n,),加上这个确保阵矩阵 db[2]db^{[2]} 这个向量输出的维度为 (n,1)(n,1) 这样标准的形式。

目前为止,我们计算的都和Logistic回归十分相似,但当你开始计算反向传播时,你需要计算的是隐藏层函数的导数,输出在使用sigmoid函数进行二元分类。这里是进行逐个元素乘积,因为 W[2]Tdz[2]W^{[2]T}dz^{[2]}(z[1])(z^{[1]}) 这两个都为 (n[1],m)(n^{[1]},m) 矩阵;

还有一种防止python输出奇怪的秩数,需要显式地调用reshapenp.sum输出结果写成矩阵形式。

以上就是正向传播的4个方程和反向传播的6个方程,这里我是直接给出的,在下个视频中,我会讲如何导出反向传播的这6个式子的。如果你要实现这些算法,你必须正确执行正向和反向传播运算,你必须能计算所有需要的导数,用梯度下降来学习神经网络的参数;你也可以许多成功的深度学习从业者一样直接实现这个算法,不去了解其中的知识。

 

(选修)直观理解反向传播(Backpropagation intuition)

这个视频主要是推导反向传播。

下面是逻辑回归的推导:

回想一下逻辑回归的公式:

xwb}    z=wTx+b    a=σ(z)    L(a,y)(3.2)\left. \begin{array}{l} x\\ w\\ b \end{array} \right\} \implies z={w}^Tx+b \implies a = \sigma(z) \implies{L(a,y)} \tag{3.2}

da[1]=dσ(z[1])dW[2]db[2]}    dz[2]=d(W[2]α[1]+b[2])    da[2]=dσ(z[2])    dL(a[2],y)(3.5)\left. \begin{array}{r} {da^{[1]} = {d}\sigma(z^{[1]})}\\ {dW^{[2]}}\\ {db^{[2]}}\\ \end{array} \right\} \impliedby dz^{[2]}={d}(W^{[2]}\alpha^{[1]}+b^{[2]}) \impliedby da^{[2]} = {d}\sigma(z^{[2]})\\ \impliedby dL\left(a^{[2]},y \right) \tag{3.5}

z[1](i)=W[1](i)x(i)+b[1]α[1](i)=σ(z[1](i))z[2](i)=W[2](i)α[1](i)+b[2]α[2](i)=σ(z[2](i))}    {A[1]=σ(z[1])z[2]=W[2]A[1]+b[2]A[2]=σ(z[2])(3.15)\left. \begin{array}{r} \text{$z^{[1](i)} = W^{[1](i)}x^{(i)} + b^{[1]}$}\\ \text{$\alpha^{[1](i)} = \sigma(z^{[1](i)})$}\\ \text{$z^{[2](i)} = W^{[2](i)}\alpha^{[1](i)} + b^{[2]}$}\\ \text{$\alpha^{[2](i)} = \sigma(z^{[2](i)})$}\\ \end{array} \right\} \implies \begin{cases} \text{$A^{[1]} = \sigma(z^{[1]})$}\\ \text{$z^{[2]} = W^{[2]}A^{[1]} + b^{[2]}$}\\ \text{$A^{[2]} = \sigma(z^{[2]})$}\\ \end{cases} \tag{3.15}

所以回想当时我们讨论逻辑回归的时候,我们有这个正向传播步骤,其中我们计算 zz,然后 aa,然后损失函数 LL

(3.5)(3.5) 类似的,反向传播:

xwb}dw=dzx,db=dz    z=wTx+bLz=dz=Ladadz=dag(z),a=g(z)=σ(z)    a=σ(z)    L(a,y)da=ddaL(a,y)=(ylogα(1y)log(1a))=ya+1y1a\underbrace{ \left. \begin{array}{l} {x }\\ {w }\\ {b } \end{array} \right\} }_{dw={dz}\cdot x, db =dz} \impliedby\underbrace{z={w}^Tx+b}_{\frac{\partial L}{\partial z}=dz=\frac{\partial L}{\partial a}\cdot \frac{da}{dz}=da\cdot g^{'}(z), a=g(z)=\sigma(z)} \impliedby\underbrace{a = \sigma(z) \impliedby L(a,y)}_{da= \frac{d}{da}{L}\left(a,y \right)=(-y\log{\alpha} - (1 - y)\log(1 - a))^{'}=-\frac{y}{a} + \frac{1 - y}{1 - a}}

有些乱

神经网络的计算中,与逻辑回归十分类似,但中间会有多隐藏层的计算。我们仍然使用先前的单隐藏层的神经网络模型作为例子。并且我们只使用了一个样本集

前向传播:

计算 z[1]z^{[1]}a[1]a^{[1]},再计算 z[2]z^{[2]}a[2]a^{[2]},最后得到loss function

反向传播:

向后推算出 da[2]da^{[2]},然后推算出 dz[2]dz^{[2]},接着推算出 da[1]da^{[1]},然后推算出 dz[1]dz^{[1]}。我们不需要对 xx 求导,因为 xx 是固定的,我们也不是想优化 xx 。课程中跳过了 da[2]da^{[2]} 计算(算过好多次了),将与推算出 dz[2]dz^{[2]} 的步骤合为一步:

dz[2]=a[2]y    dW[2]=dz[2]a[1]T(3.40)dz^{[2]}=a^{[2]}-y\;,\;dW^{[2]}=dz^{[2]}{a^{[1]}}^{T}\tag{3.40}

注意:为什么 a[1]Ta^{[1]T} 多了个转置:对于逻辑回归,dw=dzxdw=dz\cdot xdwdw 中的 ww 是一个列向量,而神经网络的 W[2]W^{[2]} 是个行向量(wTw^T 按行叠放) ,故需要加个转置;

又:

dz[2]=Lz[2]=La[2]da[2]dz[2]=(ya[2]+1y1a[2])(a[2](1a[2]))  其中(a=σ(z))=a[2]y\begin{aligned} dz^{[2]} &= \frac{\partial L}{\partial z^{[2]}} \\ &= \frac{\partial L}{\partial a^{[2]}}\cdot \frac{da^{[2]}}{dz^{[2]}}\\ &=(-\frac ya^{[2]}+\frac{1-y}{1-a^{[2]}})\cdot (a^{[2]}\cdot(1-a^{[2]}))\ \ 其中(a=\sigma(z))\\ &=a^{[2]}-y \end{aligned}

dW[2]=Lz[2]dz[2]dW[2]=dz[2]a[1]TdW^{[2]}=\frac{\partial L}{\partial z^{[2]}}\cdot \frac{d z^{[2]}}{d W^{[2]}}=dz^{[2]}{a^{[1]}}^{T}

db[2]=dz[2](3.41)db^{[2]}=dz^{[2]}\tag{3.41}

dz[1]=W[2]Tdz[2]g[1](z[1])(3.42)dz^{[1]} = W^{[2]T}dz^{[2]}* g^{[1]'}(z^{[1]})\tag{3.42}

注意:这里的矩阵:W[2]W^{[2]} 的维度是:(n[2],n[1])(n^{[2]},n^{[1]})

课程中提到通常把 da[1]da^{[1]}dz[1]dz^{[1]} 合并计算,这里分开来好理解一些:

da[1]=La[1]=Lz[2]dz[2]da[1]=W[2]Tdz[2]\begin{aligned} da^{[1]} &= \frac{\partial L}{\partial a^{[1]}} \\ &= \frac{\partial L}{\partial z^{[2]}}\cdot \frac{dz^{[2]}}{da^{[1]}}\\ &=W^{[2]T}\cdot dz^{[2]} \end{aligned}

这里第三行可能有些突兀,但是不妨看上面神经网络前向计算的过程,我们在第二个方框得出 a[1]=σ(z[1])a^{[1]}=\sigma(z^{[1]}) 后是把 a[1]a^{[1]} 作为 xx 代入到了第三个方框中 z[2]=W[2]x+b[2]=W[2]a[1]+b[2]z^{[2]}=W^{[2]}x+b^{[2]}=W^{[2]}\cdot a^{[1]}+b^{[2]} 来计算,所以这个导数也就不难计算

dz[1]=Lz[1]=La[1]da[1]dz[1]=W[2]Tdz[2]g[1](z[1])\begin{aligned} dz^{[1]} &= \frac{\partial L}{\partial z^{[1]}} \\ &= \frac{\partial L}{\partial a^{[1]}}\cdot \frac{da^{[1]}}{dz^{[1]}}\\ &=W^{[2]T}\cdot dz^{[2]}*g^{[1]'}(z^{[1]}) \end{aligned}

其中 g[1](z[1])*g^{[1]'}(z^{[1]}) 指的是逐个元素进行乘积,乘完后维度为 (n[1],1)(n^{[1]},1)

又,其中 g(x)g(x) 是激活函数,这里这么写是因为前面我们已经讲过隐藏层有多种激活函数可选,虽然课程样例中用的是 σ(x)\sigma(x),所以你也可以理解为 g[1](z[1])=σ[1](z[1])g^{[1]'}(z^{[1]})=\sigma^{[1]'}(z^{[1]})

z[2]z^{[2]}dz[2]dz^{[2]} 的维度都是:(n[2],1)(n^{[2]},1),如果是二分类,那维度就是 (1,1)(1,1)

z[1]z^{[1]}dz[1]dz^{[1]} 的维度都是:(n[1],1)(n^{[1]},1)

证明过程:

见公式3.42,其中 W[2]Tdz[2]W^{[2]T}dz^{[2]} 维度为:(n[1],n[2])(n^{[1]},n^{[2]})(n[2],1)(n^{[2]},1) 相乘得到 (n[1],1)(n^{[1]},1),和 z[1]z^{[1]} 维度相同,

g[1](z[1])g[1]^{'}(z^{[1]}) 的维度为 (n[1],1)(n^{[1]},1),这就变成了两个都是 (n[1],1)(n^{[1]},1) 向量逐元素乘积。

实现后向传播有个技巧,就是要保证矩阵的维度相互匹配。最后得到 dW[1]dW^{[1]}db[1]db^{[1]}

dW[1]=dz[1]xT,db[1]=dz[1](3.43)dW^{[1]} =dz^{[1]}x^{T},db^{[1]} = dz^{[1]}\tag{3.43}

可以看出 dW[1]dW^{[1]}dW[2]dW^{[2]} 非常相似,其中 xx 扮演了 a[0]a^{[0]} 的角色,xTx^{T} 等同于 a[0]Ta^{[0]T}

由:

Z[1]=W[1]x+b[1]  ,  a[1]=g[1](Z[1])Z^{[1]} = W^{[1]}x + b^{[1]}\;,\;a^{[1]}=g^{[1]}(Z^{[1]})

得到:

Z[1]=W[1]x+b[1],A[1]=g[1](Z[1])Z^{[1]} = W^{[1]}x + b^{[1]}, A^{[1]} = g^{[1]}(Z^{[1]})

Z[1]=[z[1](1)z[1](2)z[1](m)]Z^{[1]} = \left[ \begin{array}{c} \vdots &\vdots & \vdots & \vdots \\ z^{[1](1)} & z^{[1](2)} & \vdots & z^{[1](m)} \\ \vdots &\vdots & \vdots & \vdots \\ \end{array} \right]

注意:大写的 Z[1]Z^{[1]} 表示 z[1](1),z[1](2),z[1](3)...z[1](m)z^{[1](1)},z^{[1](2)},z^{[1](3)}...z^{[1](m)} 的列向量堆叠成的矩阵,以下类同。

下面写了将样本向量化后的主要的推导过程:

dZ[2]=A[2]Y    dW[2]=1mdZ[2]A[1]TdZ^{[2]}=A^{[2]}-Y\;,\;dW^{[2]}={\frac{1}{m}}dZ^{[2]}{A^{[1]}}^{T}

L=1minL(y^,y)L = {\frac{1}{m}}\sum_i^n{L(\hat{y},y)}

db[2]=1mnp.sum(dZ[2],axis=1,keepdims=True)db^{[2]} = {\frac{1}{m}}np.sum(dZ^{[2]},axis=1,keepdims=True)

dZ[1](n[1],m)=W[2]TdZ[2](n[1],m)g[1](Z[1])(n[1],m)\underbrace{dZ^{[1]}}_{(n^{[1]}, m)} = \underbrace{W^{[2]T}dZ^{[2]}}_{(n^{[1]}, m)}*\underbrace{g[1]^{'}(Z^{[1]})}_{(n^{[1]}, m)}

dW[1]=1mdZ[1]xTdW^{[1]} = {\frac{1}{m}}dZ^{[1]}x^{T}

db[1]=1mnp.sum(dZ[1],axis=1,keepdims=True)db^{[1]} = {\frac{1}{m}}np.sum(dZ^{[1]},axis=1,keepdims=True)

吴恩达老师认为反向传播的推导是机器学习领域最难的数学推导之一,矩阵的导数要用链式法则来求,如果这章内容掌握不了也没大的关系,只要有这种直觉就可以了。

还有一点,就是初始化你的神经网络的权重,不要都是0,而是随机初始化,下一章将详细介绍原因。

 

随机初始化(Random+Initialization)

当你训练神经网络时,权重随机初始化是很重要的。对于逻辑回归,把权重初始化为0当然也是可以的。但是对于一个神经网络,如果你把权重或者参数都初始化为0,那么梯度下降将不会起作用。

让我们看看这是为什么。假设有两个输入特征,n[0]=2n^{[0]} = 2,2个隐藏层单元 n[1]n^{[1]} 就等于2。因此与一个隐藏层相关的矩阵,或者说 W[1]W^{[1]}222*2 的矩阵,假设把它初始化为0的 222*2 矩阵,b[1]b^{[1]} 也等于 [0  0]T[0\;0]^T,把偏置项 bb 初始化为0是合理的,但是把 ww 初始化为0就有问题了。

那这个问题如果按照这样初始化的话,你总是会发现 a1[1]a_{1}^{[1]}a2[1]a_{2}^{[1]} 相等,所以这两个激活函数就会一样。因为两个隐含单元计算同样的函数,当你做反向传播计算时,这会导致 dz1[1]\text{dz}_{1}^{[1]}dz2[1]\text{dz}_{2}^{[1]} 也会一样,对称这些隐含单元会初始化得一样,这样输出的权值也会一模一样,由此 W[2]W^{[2]} 等于 [0  0][0\;0]

但是如果你这样初始化这个神经网络,那么这两个隐含单元就会完全一样,因此他们完全对称,也就意味着计算同样的函数,并且肯定的是最终经过每次训练的迭代,这两个隐含单元仍然是同一个函数。如果 dWdW 是一个每一行有同样的值的矩阵,我们做权重更新把权重 W[1]    W[1]αdWW^{[1]}\implies{W^{[1]}-\alpha \cdot dW} ,那么每次迭代后的 W[1]W^{[1]} 都会是第一行等于第二行。

由此可以推导,如果你把权重都初始化为0,那么由于隐含单元开始计算同一个函数,所有的隐含单元就会对输出单元有同样的影响。一次迭代后同样的表达式结果仍然是相同的,即隐含单元仍是对称的。通过推导,两次、三次、无论多少次迭代,不管你训练网络多长时间,隐含单元仍然计算的是同样的函数。因此这种情况下超过1个隐含单元也没什么意义,因为他们计算同样的东西。当然更大的网络,比如你有3个特征,还有相当多的隐含单元。

如果你要初始化成0,由于所有的隐含单元都是对称的,无论你运行梯度下降多久,他们一直计算同样的函数。这没有任何帮助,因为你想要两个不同的隐含单元计算不同的函数,这个问题的解决方法就是随机初始化参数。你应该这么做:把 W[1]W^{[1]} 设为np.random.randn(2,2)(生成高斯分布),通常再乘上一个小的数,比如0.01,这样把它初始化为很小的随机数。然后 bb 没有这个对称的问题(叫做symmetry breaking problem),所以可以把 bb 初始化为0,因为只要随机初始化 WW 你就有不同的隐含单元计算不同的东西,因此不会有symmetry breaking问题了。相似的,对于 W[2]W^{[2]} 你可以随机初始化,b[2]b^{[2]} 可以初始化为0。

W[1]=np.random.randn(2,2)    0.01  ,  b[1]=np.zeros((2,1))W^{[1]} = np.random.randn(2,2)\;*\;0.01\;,\;b^{[1]} = np.zeros((2,1))

W[2]=np.random.randn(2,2)    0.01  ,  b[2]=0W^{[2]} = np.random.randn(2,2)\;*\;0.01\;,\;b^{[2]} = 0

你也许会疑惑,这个常数从哪里来,为什么是0.01,而不是100或者1000。我们通常倾向于初始化为很小的随机数。因为如果你用tanh或者sigmoid激活函数,或者说只在输出层有一个Sigmoid,如果(数值)波动太大,当你计算激活值时 z[1]=W[1]x+b[1]  ,  a[1]=σ(z[1])=g[1](z[1])z^{[1]} = W^{[1]}x + b^{[1]}\;,\;a^{[1]} = \sigma(z^{[1]})=g^{[1]}(z^{[1]}) 如果 WW 很大,zz 就会很大或者很小,因此这种情况下你很可能停在tanh/sigmoid函数的平坦的地方,这些地方梯度很小也就意味着梯度下降会很慢,因此学习也就很慢。

回顾一下:如果 ww 很大,那么你很可能最终停在(甚至在训练刚刚开始的时候)zz 很大的值,这会造成tanh/Sigmoid激活函数饱和在龟速的学习上,如果你没有sigmoid/tanh激活函数在你整个的神经网络里,就不成问题。但如果你做二分类并且你的输出单元是Sigmoid函数,那么你不会想让初始参数太大,因此这就是为什么乘上0.01或者其他一些小数是合理的尝试。对于 w[2]w^{[2]} 一样,就是np.random.randn((1,2)),我猜会是乘以0.01。

事实上有时有比0.01更好的常数,当你训练一个只有一层隐藏层的网络时(这是相对浅的神经网络,没有太多的隐藏层),设为0.01可能也可以。但当你训练一个非常非常深的神经网络,你可能要试试0.01以外的常数。下一节课我们会讨论怎么并且何时去选择一个不同于0.01的常数,但是无论如何它通常都会是个相对小的数。

好了,这就是这周的视频。你现在已经知道如何建立一个一层的神经网络了,初始化参数,用前向传播预测,还有计算导数,结合反向传播用在梯度下降中。

 

深层神经网络(Deep Neural Networks)

深层神经网络(Deep L-layer neural network)

目前为止我们学习了只有一个单独隐藏层的神经网络的正向传播和反向传播,还有逻辑回归,并且你还学到了向量化,这在随机初始化权重时是很重要。本周所要做的是把这些理念集合起来,就可以执行你自己的深度神经网络。

复习下前三周的课的内容:

1.逻辑回归,结构如下图左边。一个隐藏层的神经网络,结构下图右边:

注意,神经网络的层数是这么定义的:从左到右,由0开始定义,比如上边右图,x1{x}_{1}x2{x}_{2}x3{x}_{3},这层是第0层,这层左边的隐藏层是第1层,由此类推。如下图左边是两个隐藏层的神经网络,右边是5个隐藏层的神经网络。

严格上来说逻辑回归也是一个一层的神经网络,而上边右图一个深得多的模型,浅与深仅仅是指一种程度。记住以下要点:有一个隐藏层的神经网络,就是一个两层神经网络。记住当我们算神经网络的层数时,我们不算输入层,我们只算隐藏层和输出层。

但是在过去的几年中,我们已经意识到有一些函数,只有非常深的神经网络能学会,而更浅的模型则办不到。不过对于任何给定的问题很难去提前预测到底需要多深的神经网络,所以先去尝试逻辑回归,尝试一层然后两层隐含层,然后把隐含层的数量看做是另一个可以自由选择大小的超参数,然后再保留交叉验证数据上评估,或者用你的开发集来评估

我们再看下深度学习的符号定义:

上图是一个四层的神经网络,有三个隐藏层。我们可以看到,第一层(即左边数过去第二层,因为输入层是第0层)有5个神经元数目,第二层5个,第三层3个。

我们用 LL 表示层数,上图:L=4L=4,输入层的索引为“0”,第一个隐藏层 n[1]=5n^{[1]}=5,表示有5个隐藏神经元,同理 n[2]=5{n}^{[2]}=5n[3]=3{n}^{[3]}=3n[4]=n[L]=1{n}^{[4]}={n}^{[L]}=1(输出单元为1)。而输入层,n[0]=nx=3{n}^{[0]}={n}_{x}=3

在不同层所拥有的神经元的数目,对于每层 ll 都用 a[l]a^{[l]} 来记作 ll 层激活后结果,我们会在后面看到在正向传播时,最终能你会计算出 a[l]{a}^{[l]}

通过用激活函数 gg 计算 z[l]{z}^{[l]},激活函数也被索引为层数 ll,然后我们用 w[l]{w}^{[l]} 来记作在 ll 层计算 z[l]{z}^{[l]} 值的权重。类似的,z[l]z^{[l]} 里的方程 b[l]{b}^{[l]} 也一样。

最后总结下符号约定:

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

  • 最后一层的激活函数 a[L]=y^a^{[L]}=\hat y,也就是说 a[L]{a}^{[L]} 就等于这个神经网络所预测的输出结果。

 

前向传播和反向传播(Forward and backward propagation)

之前我们学习了构成深度神经网络的基本模块,比如每一层都有前向传播步骤以及一个相反的反向传播步骤,这次视频我们讲讲如何实现这些步骤。

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

所以前向传播的步骤可以写成:

z[l]=W[l]a[l1]+b[l]{z}^{[l]}={W}^{[l]}\cdot{a}^{[l-1]}+{b}^{[l]}

a[l]=g[l](z[l])a^{[l]}=g^{[l]}\left( z^{[l]}\right)

向量化实现过程可以写成:

z[l]=W[l]A[l1]+b[l]{z}^{[l]}={W}^{[l]}\cdot {A}^{[l-1]}+{b}^{[l]}

A[l]=g[l](Z[l]){A}^{[l]}={g}^{[l]}({Z}^{[l]})

前向传播需要喂入 A[0]{A}^{[0]} 也就是 XX,来初始化;初始化的是第一层的输入值。a[0]{a}^{[0]} 对应于一个训练样本的输入特征,而 A[0]A^{[0]} 对应于一整个训练样本的输入特征,所以这就是这条链的第一个前向函数的输入,重复这个步骤就可以从左到右计算前向传播。

下面讲反向传播的步骤:

输入为 da[l]da^{[l]},输出为 da[l1]da^{[l-1]}dw[l]dw^{[l]}, db[l]db^{[l]}

所以反向传播的步骤可以写成:

(1)dz[l]=da[l]g[l](z[l])dz^{[l]}=da^{[l]}*g^{[l]'}(z^{[l]})

(2)dw[l]=dz[l]a[l1]Tdw^{[l]}=dz^{[l]}\cdot {a}^{[l-1]T} (课程里应该忘记写转置了)

(3)db[l]=dz[l]db^{[l]}=dz^{[l]}

(4)da[l1]=w[l]Tdz[l]da^{[l-1]}=w^{\left[ l \right]T}\cdot dz^{[l]}

(5)dz[l]=w[l+1]Tdz[l+1]g[l](z[l])dz^{[l]}=w^{[l+1]T}dz^{[l+1]}\cdot g^{[l]'}(z^{[l]})

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

向量化实现过程可以写成:

(6)dz[l]=da[l]g[l](z[l])dz^{[l]}=da^{[l]}*g^{\left[ l \right]'}\left(z^{[l]} \right)

(7)dw[l]=1mdz[l]A[l1]Tdw^{[l]}=\frac{1}{m} dz^{[l]}\cdot A^{\left[ l-1 \right]T}

(8)db[l]=1mnp.sum(dz[l],axis=1,keepdims=True)db^{[l]}=\frac{1}{m}np.sum(dz^{[l]},axis=1,keepdims=True)

(9)dA[l1]=W[l]T.dz[l]dA^{[l-1]}=W^{\left[ l \right]T}.dz^{[l]}

总结一下:

第一层你可能有一个ReLU激活函数,第二层为另一个ReLU激活函数,第三层可能是sigmoid函数(如果你做二分类的话),输出值为,用来计算损失;这样你就可以向后迭代进行反向传播求导来求 dw[3]dw^{[3]}db[3]db^{[3]}dw[2]dw^{[2]}db[2]db^{[2]}dw[1]dw^{[1]}db[1]db^{[1]}。在计算的时候,缓存会把 z[1]z^{[1]} z[2]z^{[2]} z[3]z^{[3]} 传递过来,然后回传 da[2]da^{[2]}da[1]da^{[1]} ,可以用来计算 da[0]da^{[0]},但我们不会使用它,这里讲述了一个三层网络的前向和反向传播,还有一个细节没讲就是前向递归——用输入数据来初始化,那么反向递归(使用Logistic回归做二分类)——对 a[l]a^{[l]} 求导。

忠告:补补微积分和线性代数,多推导,多实践。

 

深层网络中的前向传播(Forward propagation in a Deep Network)

跟往常一样,我们先来看对其中一个训练样本 xx 如何应用前向传播,之后讨论向量化的版本。

第一层需要计算 z[1]=w[1]x+b[1]z^{[1]}=w^{[1]}x+b^{[1]}a[1]=g[1](z[1])a^{[1]}=g^{[1]} (z^{[1]})xx 可以看做 a[0]a^{[0]}

第二层需要计算 z[2]=w[2]a[1]+b[2]z^{[2]}=w^{[2]}a^{[1]}+b^{[2]}a[2]=g[2](z[2])a^{[2]}=g^{[2]} ({z}^{[2]})

以此类推,

第四层为 z[4]=w[4]a[3]+b[4]z^{[4]}=w^{[4]}a^{[3]}+b^{[4]}a[4]=g[4](z[4])a^{[4]}=g^{[4]} (z^{[4]})

前向传播可以归纳为多次迭代 z[l]=w[l]a[l1]+b[l]z^{[l]}=w^{[l]}a^{[l-1]}+b^{[l]}a[l]=g[l](z[l])a^{[l]}=g^{[l]} (z^{[l]})

向量化实现过程可以写成:

z[l]=W[l]a[l1]+b[l]a[l]=g[l](Z[l])(A[0]=X)z^{[l]}=W^{[l]}a^{[l-1]}+b^{[l]}\quad a^{[l]}=g^{[l]}({Z}^{[l]}) \quad({A}^{[0]} = X)

这里只能用一个显式for循环,ll 从1到 LL,一层接着一层去计算。事实上,在实现神经网络的过程中,想要减少代码中的bug,一个方法就是仔细检查和思考矩阵的维数,下一节会具体介绍该如何做

 

核对矩阵的维数(Getting your matrix dimensions right)

当实现深度神经网络的时候,其中一个我常用的检查代码是否有错的方法就是拿出一张纸过一遍算法中矩阵的维数。

  • ww 的维度是 (下一层的维数,前一层的维数)(下一层的维数,前一层的维数),即 w[l]w^{[l]} : (n[l]n^{[l]},n[l1]n^{[l-1]});

  • bb 的维度是 (下一层的维数,1)(下一层的维数,1) ,即: b[l]{b}^{[l]} : (n[l],1){n}^{[l]},1)

  • z[l]z^{[l]},a[l]a^{[l]} : (n[l],1)(n^{[l]},1);

  • dw[l]{dw}^{[l]}w[l]{w}^{[l]} 维度相同,db[l]{db}^{[l]}b[l]{b}^{[l]} 维度相同,且 wwbb 向量化维度不变,但 zz , aa 以及 xx 的维度会向量化后发生变化。

向量化后:

  • Z[l]{Z}^{[l]} 可以看成由每一个单独的 Z[l]{Z}^{[l]} 叠加而得到,Z[l]=(z[l][1]z[l][2]z[l][3]z[l][m]){Z}^{[l]}=({z}^{[l][1]},{z}^{[l][2]},{z}^{[l][3]},…,{z}^{[l][m]})

  • mm 为训练集大小,所以 Z[l]{Z}^{[l]} 的维度不再是 (n[l],1)(n^{[l]},1),而是(n[l],m)(n^{[l]},m)

  • A[l]{A}^{[l]}(n[l],m)({n}^{[l]},m)A[0]=X=(n[l],m){A}^{[0]} = X =({n}^{[l]},m)

在你做深度神经网络的反向传播时,一定要确认所有的矩阵维数是前后一致的,可以大大提高代码通过率。下一节我们讲为什么深层的网络在很多问题上比浅层的好。

 

为什么使用深层表示?(Why deep representations?)

我们都知道深度神经网络能解决好多问题,其实并不需要很大的神经网络,但是得有深度,得有比较多的隐藏层,这是为什么呢?我们一起来看几个例子来帮助理解,为什么深度神经网络会很好用。

首先,深度网络在计算什么?

首先,深度网络究竟在计算什么?如果你在建一个人脸识别或是人脸检测系统,深度神经网络所做的事就是,当你输入一张脸部的照片,然后你可以把深度神经网络的第一层,当成一个特征探测器或者边缘探测器。在这个例子里,我会建一个大概有20个隐藏单元的深度神经网络,以此来解释神经网络是怎么针对这张图计算的。隐藏单元就是这些图里这些小方块(第一张大图),举个例子,这个小方块(第一行第一列)就是一个隐藏单元,它会去找这张照片里“|”边缘的方向。那么这个隐藏单元(第四行第四列),可能是在找(“—”)水平向的边缘在哪里。之后的课程里,我们会讲专门做这种识别的卷积神经网络,到时候会细讲,为什么小单元是这么表示的。你可以先把神经网络的第一层当作看图,然后去找这张照片的各个边缘。我们可以把照片里组成边缘的像素们放在一起看,然后它可以把被探测到的边缘组合成面部的不同部分(第二张大图)。比如说,可能有一个神经元会去找眼睛的部分,另外还有别的在找鼻子的部分,然后把这许多的边缘结合在一起,就可以开始检测人脸的不同部分。最后再把这些部分放在一起,比如鼻子眼睛下巴,就可以识别或是探测不同的人脸(第三张大图)。

你可以直觉上把这种神经网络的前几层当作探测简单的函数,比如边缘,之后把它们跟后几层结合在一起,那么总体上就能学习更多复杂的函数。这些图的意义,我们在学习卷积神经网络的时候再深入了解。还有一个技术性的细节需要理解的是,边缘探测器其实相对来说都是针对照片中非常小块的面积。就像这块(第一行第一列),都是很小的区域。面部探测器就会针对于大一些的区域,但是主要的概念是,一般你会从比较小的细节入手,比如边缘,然后再一步步到更大更复杂的区域,比如一只眼睛或是一个鼻子,再把眼睛鼻子装一块组成更复杂的部分。

这种从简单到复杂的金字塔状表示方法或者组成方法,也可以应用在图像或者人脸识别以外的其他数据上。比如当你想要建一个语音识别系统的时候,需要解决的就是如何可视化语音,比如你输入一个音频片段,那么神经网络的第一层可能就会去先开始试着探测比较低层次的音频波形的一些特征,比如音调是变高了还是低了,分辨白噪音,咝咝咝的声音,或者音调,可以选择这些相对程度比较低的波形特征,然后把这些波形组合在一起就能去探测声音的基本单元。在语言学中有个概念叫做音位,比如说单词cat,c的发音,“嗑”就是一个音位,a的发音“啊”是个音位,t的发音“特”也是个音位,有了基本的声音单元以后,组合起来,你就能识别音频当中的单词,单词再组合起来就能识别词组,再到完整的句子。

所以深度神经网络的这许多隐藏层中,较早的前几层能学习一些低层次的简单特征,等到后几层,就能把简单的特征结合起来,去探测更加复杂的东西。比如你录在音频里的单词、词组或是句子,然后就能运行语音识别了。同时我们所计算的之前的几层,也就是相对简单的输入函数,比如图像单元的边缘什么的。到网络中的深层时,你实际上就能做很多复杂的事,比如探测面部或是探测单词、短语或是句子。

有些人喜欢把深度神经网络和人类大脑做类比,这些神经科学家觉得人的大脑也是先探测简单的东西,比如你眼睛看得到的边缘,然后组合起来才能探测复杂的物体,比如脸。这种深度学习和人类大脑的比较,有时候比较危险。但是不可否认的是,我们对大脑运作机制的认识很有价值,有可能大脑就是先从简单的东西,比如边缘着手,再组合成一个完整的复杂物体,这类简单到复杂的过程,同样也是其他一些深度学习的灵感来源,之后的视频我们也会继续聊聊人类或是生物学理解的大脑。

另外一个,关于神经网络为何有效的理论,来源于电路理论,它和你能够用电路元件计算哪些函数有着分不开的联系。根据不同的基本逻辑门,譬如与门、或门、非门。在非正式的情况下,这些函数都可以用相对较小,但很深的神经网络来计算,小在这里的意思是隐藏单元的数量相对比较小,但是如果你用浅一些的神经网络计算同样的函数,也就是说在我们不能用很多隐藏层时,你会需要成指数增长的单元数量才能达到同样的计算结果。

Small:隐藏单元的数量相对较少

Deep:隐藏层数目比较多

深层的网络隐藏单元数量相对较少,隐藏层数目较多,如果浅层的网络想要达到同样的计算结果则需要指数级增长的单元数量才能达到。

我再来举个例子,用没那么正式的语言介绍这个概念。假设你想要对输入特征计算异或或是奇偶性,你可以算x1 XOR x2 XOR x3 XOR  xnx_{1}\ XOR\ x_{2}\ XOR\ x_{3}\ XOR \ \dots \ x_{n} ,假设你有 nn 或者 nxn_{x} 个特征,如果你画一个异或的树图,先要计算 x1x_{1}x2x_{2} 的异或,然后是 x3x_{3}x4x_{4} 。技术上来说如果你只用或门,还有非门的话,你可能会需要几层才能计算异或函数,但是用相对小的电路,你应该就可以计算异或了。然后你可以继续建这样的一个异或树图(上图左),那么你最后会得到这样的电路来输出结果 yyy^=y\hat{y}=y,也就是输入特征的异或,或是奇偶性,要计算异或关系。这种树图对应网络的深度应该是 O(log(n))O(log(n)) ,那么节点的数量和电路部件,或是门的数量并不会很大,你也不需要太多门去计算异或。

但是如果你不能使用多隐层的神经网络的话,比如你被迫只能用单隐藏层来计算的话,这里全部都指向从这些隐藏单元到后面这个输出单元,再输出 yy,那么要计算奇偶性,或者异或关系函数就需要这一隐层(上图右方框部分)的单元数呈指数增长才行,因为本质上来说你需要列举耗尽 2n2^{n} 种可能的配置,或是 2n2^{n} 种输入比特的配置。异或运算的最终结果是1或0,那么你最终就会需要一个隐藏层,其中单元数目随输入比特指数上升。精确的说应该是 2n12^{n-1} 个隐藏单元数,也就是 O(2n)O(2^{n})

我希望这能让你有点概念,意识到有很多数学函数用深度网络计算比浅网络要容易得多,我个人倒是认为这种电路理论,对训练直觉思维没那么有用,但这个结果人们还是经常提到的,用来解释为什么需要更深层的网络。

除了这些原因,说实话,我认为“深度学习”这个名字挺唬人的,这些概念以前都统称为有很多隐藏层的神经网络,但是深度学习听起来多高大上,太深奥了,对么?这个词流传出去以后,这是神经网络的重新包装或是多隐藏层神经网络的重新包装,激发了大众的想象力。抛开这些公关概念重新包装不谈,深度网络确实效果不错,有时候人们还是会按照字面意思钻牛角尖,非要用很多隐层。但是当我开始解决一个新问题时,我通常会从logistic回归开始,再试试一到两个隐层,把隐藏层数量当作参数、超参数一样去调试,这样去找比较合适的深度。但是近几年以来,有一些人会趋向于使用非常非常深邃的神经网络,比如好几打的层数,某些问题中只有这种网络才是最佳模型。

这就是我想讲的,为什么深度学习效果拔群的直觉解释,现在我们来看看除了正向传播以外,反向传播该怎么具体实现。

 

搭建神经网络块(Building blocks of deep neural networks)

这周的前几个视频和之前几周的视频里,你已经看到过正向反向传播的基础组成部分了,它们也是深度神经网络的重要组成部分,现在我们来用它们建一个深度神经网络。

这是一个层数较少的神经网络,我们选择其中一层(方框部分),从这一层的计算着手。在第 ll 层你有参数 W[l]W^{[l]}b[l]b^{[l]},正向传播里有输入的激活函数,输入是前一层 a[l1]a^{[l-1]},输出是 a[l]a^{[l]},我们之前讲过 z[l]=W[l]a[l1]+b[l]z^{[l]} =W^{[l]}a^{[l-1]} +b^{[l]}a[l]=g[l](z[l])a^{[l]} =g^{[l]}(z^{[l]}),那么这就是你如何从输入 a[l1]a^{[l-1]} 走到输出的 a[l]a^{[l]}。之后你就可以把 z[l]z^{[l]} 的值缓存起来,我在这里也会把这包括在缓存中,因为缓存的 z[i]z^{[i]} 对以后的正向反向传播的步骤非常有用。

然后是反向步骤或者说反向传播步骤,同样也是第 ll 层的计算,你会需要实现一个函数输入为 da[l]da^{[l]} ,输出 da[l1]da^{[l-1]} 的函数。一个小细节需要注意,输入在这里其实是 da[l]da^{[l]} 以及所缓存的 z[l]z^{[l]} 值,之前计算好的 z[l]z^{[l]} 值,除了输出 da[l1]da^{[l-1]} 的值以外,也需要输出你需要的梯度 dW[l]dW^{[l]}db[l]db^{[l]},这是为了实现梯度下降学习。

这就是基本的正向步骤的结构,我把它成为称为正向函数,类似的在反向步骤中会称为反向函数。总结起来就是,在 ll 层,你会有正向函数,输入 a[l1]a^{[l-1]} 并且输出 a[l]a^{[l]},为了计算结果你需要用 W[l]W^{[l]}b[l]b^{[l]},以及输出到缓存的 z[l]z^{[l]}。然后用作反向传播的反向函数,是另一个函数,输入 da[l]da^{[l]},输出 da[l1]da^{[l-1]},你就会得到对激活函数的导数,也就是希望的导数值 da[l]da^{[l]}a[l1]a^{[l-1]} 是会变的,前一层算出的激活函数导数。在这个方块(第二个)里你需要 W[l]W^{[l]}b[l]b^{[l]},最后你要算的是 dz[l]dz^{[l]}。然后这个方块(第三个)中,这个反向函数可以计算输出 dW[l]dW^{[l]}db[l]db^{[l]}。我会用红色箭头标注标注反向步骤,如果你们喜欢,我可以把这些箭头涂成红色。

前面讲过,反向传播的步骤可以写成:

(1)dz[l]=da[l]g[l](z[l])dz^{[l]}=da^{[l]}*g^{[l]'}(z^{[l]})

(2)dw[l]=dz[l]a[l1]Tdw^{[l]}=dz^{[l]}\cdot {a}^{[l-1]T}

(3)db[l]=dz[l]db^{[l]}=dz^{[l]}

(4)da[l1]=w[l]Tdz[l]da^{[l-1]}=w^{\left[ l \right]T}\cdot dz^{[l]}

然后如果实现了这两个函数(正向和反向),然后神经网络的计算过程会是这样的:

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

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

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

继续下去之前再补充一个细节,概念上会非常有帮助,那就是把反向函数计算出来的 zz 值缓存下来。当你做编程练习的时候去实现它时,你会发现缓存可能很方便,可以迅速得到 W[l]W^{[l]}b[l]b^{[l]} 的值,这是非常方便的一个方法,在编程练习中你缓存了 zz,还有 WWbb 对吧?从实现角度上看,我认为是一个很方便的方法,可以将参数复制到你在计算反向传播时所需要的地方。好,这就是实现过程的细节,做编程练习时会用到。

现在你们见过实现深度神经网络的基本元件,在每一层中有一个正向传播步骤,以及对应的反向传播步骤,以及把信息从一步传递到另一步的缓存。下一个视频我们会讲解这些元件具体实现过程,我们来看下一个视频吧。

 

参数VS超参数(Parameters vs Hyperparameters)

想要你的深度神经网络起很好的效果,你还需要规划好你的参数以及超参数。(这其实是很玄学,或者说很吃经验的一部分)

什么是超参数?

比如算法中的learning rate aa(学习率)、iterations(梯度下降法循环的次数)、LL(隐藏层数目)、n[l]n^{[l]}(隐藏层单元数目)、choice of activation function(激活函数的选择)都需要你来设置,这些数字实际上控制了最后的参数 WWbb 的值,所以它们被称作超参数。

实际上深度学习有很多不同的超参数,之后我们也会介绍一些其他的超参数,如momentummini batch sizeregularization parameters等等。

如何寻找超参数的最优值?

Idea—Code—Experiment—Idea这个循环,尝试各种不同的参数,实现模型并观察是否成功,然后再迭代。

今天的深度学习应用领域,还是很经验性的过程,通常你有个想法,比如你可能大致知道一个最好的学习率值,可能说 a=0.01a=0.01 最好,我会想先试试看,然后你可以实际试一下,训练一下看看效果如何。然后基于尝试的结果你会发现,你觉得学习率设定再提高到0.05会比较好。如果你不确定什么值是最好的,你大可以先试试一个学习率 aa ,再看看损失函数 JJ 的值有没有下降。然后你可以试一试大一些的值,然后发现损失函数的值增加并发散了。然后可能试试其他数,看结果是否下降的很快或者收敛到在更高的位置。你可能尝试不同的 aa 并观察损失函数 JJ 这么变了,试试一组值,然后可能损失函数变成这样,这个 aa 值会加快学习过程,并且收敛在更低的损失函数值上(箭头标识),我就用这个 aa 值了。

在前面几页中,还有很多不同的超参数。然而,当你开始开发新应用时,预先很难确切知道,究竟超参数的最优值应该是什么。所以通常,你必须尝试很多不同的值,并走这个循环,试试各种参数。试试看5个隐藏层,这个数目的隐藏单元,实现模型并观察是否成功,然后再迭代。这页的标题是,应用深度学习领域,一个很大程度基于经验的过程,凭经验的过程通俗来说,就是试直到你找到合适的数值。

另一个近来深度学习的影响是它用于解决很多问题,从计算机视觉到语音识别,到自然语言处理,到很多结构化的数据应用,比如网络广告或是网页搜索或产品推荐等等。我所看到过的就有很多其中一个领域的研究员,这些领域中的一个,尝试了不同的设置,有时候这种设置超参数的直觉可以推广,但有时又不会。所以我经常建议人们,特别是刚开始应用于新问题的人们,去试一定范围的值看看结果如何。然后下一门课程,我们会用更系统的方法,用系统性的尝试各种超参数取值。然后其次,甚至是你已经用了很久的模型,可能你在做网络广告应用,在你开发途中,很有可能学习率的最优数值或是其他超参数的最优值是会变的,所以即使你每天都在用当前最优的参数调试你的系统,你还是会发现,最优值过一年就会变化,因为电脑的基础设施,CPU或是GPU可能会变化很大。所以有一条经验规律可能每几个月就会变。如果你所解决的问题需要很多年时间,只要经常试试不同的超参数,勤于检验结果,看看有没有更好的超参数数值,相信你慢慢会得到设定超参数的直觉,知道你的问题最好用什么数值。

这可能的确是深度学习比较让人不满的一部分,也就是你必须尝试很多次不同可能性。但参数设定这个领域,深度学习研究还在进步中,所以可能过段时间就会有更好的方法决定超参数的值,也很有可能由于CPUGPU、网络和数据都在变化,这样的指南可能只会在一段时间内起作用,只要你不断尝试,并且尝试保留交叉检验或类似的检验方法,然后挑一个对你的问题效果比较好的数值。

近来受深度学习影响,很多领域发生了变化,从计算机视觉到语音识别到自然语言处理到很多结构化的数据应用,比如网络广告、网页搜索、产品推荐等等;有些同一领域设置超参数的直觉可以推广,但有时又不可以,特别是那些刚开始研究新问题的人们应该去尝试一定范围内的结果如何,甚至那些用了很久的模型得学习率或是其他超参数的最优值也有可能会改变。

在下个课程我们会用系统性的方法去尝试各种超参数的取值。有一条经验规律:经常试试不同的超参数,勤于检查结果,看看有没有更好的超参数取值,你将会得到设定超参数的直觉

深度学习和大脑的关联性(What does this have to do with the brain?)

深度学习和大脑有什么关联性吗?

关联不大。

那么人们为什么会说深度学习和大脑相关呢?

当你在实现一个神经网络的时候,那些公式是你在做的东西,你会做前向传播、反向传播、梯度下降法,其实很难表述这些公式具体做了什么,深度学习像大脑这样的类比其实是过度简化了我们的大脑具体在做什么,但因为这种形式很简洁,也能让普通人更愿意公开讨论,也方便新闻报道并且吸引大众眼球,但这个类比是非常不准确的。

一个神经网络的逻辑单元可以看成是对一个生物神经元的过度简化,但迄今为止连神经科学家都很难解释究竟一个神经元能做什么,它可能是极其复杂的;它的一些功能可能真的类似logistic回归的运算,但单个神经元到底在做什么目前还没有人能够真正可以解释。

深度学习的确是个很好的工具来学习各种很灵活很复杂的函数,学习到从 xxyy 的映射,在监督学习中学到输入到输出的映射。

但这个类比还是很粗略的,这是一个logistic回归单元的sigmoid激活函数,这里是一个大脑中的神经元,图中这个生物神经元,也是你大脑中的一个细胞,它能接受来自其他神经元的电信号,比如 x1,x2,x3x_1,x_2,x_3,或可能来自于其他神经元 a1,a2,a3a_1,a_2,a_3 。其中有一个简单的临界计算值,如果这个神经元突然激发了,它会让电脉冲沿着这条长长的轴突,或者说一条导线传到另一个神经元。

所以这是一个过度简化的对比,把一个神经网络的逻辑单元和右边的生物神经元对比。至今为止其实连神经科学家们都很难解释,究竟一个神经元能做什么。一个小小的神经元其实却是极其复杂的,以至于我们无法在神经科学的角度描述清楚,它的一些功能,可能真的是类似logistic回归的运算,但单个神经元到底在做什么,目前还没有人能够真正解释,大脑中的神经元是怎么学习的,至今这仍是一个谜之过程。到底大脑是用类似于后向传播或是梯度下降的算法,或者人类大脑的学习过程用的是完全不同的原理。

所以虽然深度学习的确是个很好的工具,能学习到各种很灵活很复杂的函数来学到从x到y的映射。在监督学习中,学到输入到输出的映射,但这种和人类大脑的类比,在这个领域的早期也许值得一提。但现在这种类比已经逐渐过时了,我自己也在尽量少用这样的说法。

这就是神经网络和大脑的关系,我相信在计算机视觉,或其他的学科都曾受人类大脑启发,还有其他深度学习的领域也曾受人类大脑启发。但是个人来讲我用这个人类大脑类比的次数逐渐减少了。