第一门课 神经网络和深度学习(Neural Networks and Deep Learning)

Dizzyzzh 发布于 2025-01-26 519 次阅读


第一门课 神经网络和深度学习(Neural Networks and Deep Learning)

个人对 deeplearning_ai_books 的笔记进行了精简与修改。

神经网络的编程基础

二分类(Binary Classification)

假如你有一张图片作为输入,比如这只猫,如果识别这张图片为猫,则输出标签 1 作为结果;如果识别出不是猫,那么输出标签 0 作为结果。

为了保存一张图片,需要保存三个矩阵,它们分别对应图片中的红、绿、蓝三种颜色通道,如果你的图片大小为 64x64 像素,那么你就有三个规模为 64x64 的矩阵,分别对应图片中红、绿、蓝三种像素的强度值。

为了把这些像素值转换为特征向量 $x$ ,我们把所有的像素都取出来,例如 255、231 等等,直到取完所有的红色像素,接着最后是 255、134、…、255、134 等等,直到得到一个特征向量,把图片中所有的红、绿、蓝像素值都列出来。如果图片的大小为 64x64 像素,那么向量 x 的总维度,将是 64 乘以 64 乘以 3,这是三个像素矩阵中像素的总量,在这个例子中结果为 12,288。现在我们用 $n_x=12,288$,来表示输入特征向量的维度。

符号定义 :

$x$:表示一个 $n_x$ 维数据,为输入数据,维度为 $(n_x,1)$; 

$y$:表示输出结果,取值为 $(0,1)$;

$(x^{(i)},y^{(i)})$:表示第 $i$ 组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据; 

$X=[x^{(1)},x^{(2)},...,x^{(m)}]$:表示所有的训练数据集的输入值,放在一个 $n_x×m$ 的矩阵中,其中 $m$ 表示样本数目; 

$Y=[y^{(1)},y^{(2)},...,y^{(m)}]$:对应表示所有训练数据集的输出值,维度为 $1×m$。

逻辑回归(Logistic Regression)

对于二元分类问题来讲,给定一个输入特征向量 $X$,它可能对应一张图片,你想识别这张图片识别看它是否是一只猫或者不是一只猫的图片,想要一个算法能够输出预测,称之为 $\hat{y}$,也就是你对实际值 $y$ 的估计。

更正式地来说,你想让 $\hat{y}$ 表示 $y$ 等于 1 的一种可能性或者是机会,前提条件是给定了输入特征 $X$。

在之前的视频中所说的,$X$ 是一个 $n_x$ 维的向量(相当于有 $n_x$ 个特征的特征向量)。我们用 $w$ 来表示逻辑回归的参数,这也是一个 $n_x$ 维向量(因为 $w$ 实际上是特征权重,维度与特征向量相同),参数里面还有 $b$,这是一个实数(表示偏差)。

如果令 $\hat{y}={{w}^{T}}x+b$。

这时候我们得到的是一个关于输入 $x$ 的线性函数。但是 $\hat{y}$ 表示的是实际值 $y$ 等于 1 的机率,$\hat{y}$ 应该在 0 到 1 之间。${{w}^{T}}x+b$ 可能比 1 要大得多,或者甚至为一个负值。

因此在逻辑回归中,我们的输出应该是 $\hat{y}$ 等于由上面得到的线性函数式子作为自变量的 sigmoid 函数中,公式如上图最下面所示,将线性函数转换为非线性函数。

下图是 sigmoid 函数的图像,如果我把水平轴作为 $z$ 轴,那么关于 $z$ 的 sigmoid 函数是这样的,它是平滑地从 0 走向 1,让我在这里标记纵轴,这是 0,曲线与纵轴相交的截距是 0.5,这就是关于 $z$ 的 sigmoid 函数的图像。我们通常都使用 $z$ 来表示 ${{w}^{T}}x+b$ 的值。

关于 sigmoid 函数的公式是 $\sigma \left( z \right)=\frac{1}{1+{{e}^{-z}}}$, 在这里 $z$ 是一个实数

损失函数与代价函数

为什么需要代价函数

为了训练逻辑回归模型的参数参数 $w$ 和参数 $b$ 我们,需要一个代价函数,通过训练代价函数来得到参数 $w$ 和参数 $b$

先看一下逻辑回归的输出函数:

为了让模型通过学习调整参数,你需要给予一个 $m$ 样本的训练集,这会让你在训练集上找到参数 $w$ 和参数 $b$,,来得到你的输出。

对训练集的预测值,我们将它写成 $\hat{y}$,我们更希望它会接近于训练集的 $y$ 值.

训练样本 $i$ 所对应的预测值是 ${\hat{y}^{(i)}}$, 是用训练样本的 ${{w}^{T}}{{x}^{(i)}}+b$ 然后通过 sigmoid 函数来得到,也可以把 $z$ 定义为 ${{z}^{(i)}}={{w}^{T}}{{x}^{(i)}}+b$, 我们将使用这个符号 $(i)$ 注解,上标 $(i)$ 来指明数据表示 $x$ 或者 $y$ 或者 $z$ 或者其他数据的第 $i$ 个训练样本,这就是上标 $(i)$ 的含义。

损失函数

损失函数用来衡量算法的运行情况,Loss function: $L\left( \hat{y},y \right)$. 我们通过这个 $L$ 称为的损失函数,来衡量预测输出值和实际值有多接近。

一般我们用预测值和实际值的平方差或者它们平方差的一半,但是通常在逻辑回归中我们不这么做,因为当我们在学习逻辑回归参数的时候,会发现我们的优化目标不是凸优化,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值,虽然平方差是一个不错的损失函数,但是我们在逻辑回归模型中会定义另外一个损失函数。

我们在逻辑回归中用到的损失函数是:$L\left( \hat{y},y \right)=-y\log(\hat{y})-(1-y)\log (1-\hat{y})$

当 $y=1$ 时损失函数 $L=-\log (\hat{y})$,如果想要损失函数 $L$ 尽可能得小,那么 $\hat{y}$ 就要尽可能大,因为 sigmoid 函数取值 $[0,1]$,所以 $\hat{y}$ 会无限接近于 1。

当 $y=0$ 时损失函数 $L=-\log (1-\hat{y})$,如果想要损失函数 $L$ 尽可能得小,那么 $\hat{y}$ 就要尽可能小,因为 sigmoid 函数取值 $[0,1]$,所以 $\hat{y}$ 会无限接近于 0。

在这门课中有很多的函数效果和现在这个类似,就是如果 $y$ 等于 1,我们就尽可能让 $\hat{y}$ 变大,如果 $y$ 等于 0,我们就尽可能让 $\hat{y}$ 变小。

代价函数

损失函数是在单个训练样本中定义的,它衡量的是算法在单个训练样本中表现如何,为了衡量算法在全部训练样本上的表现如何,我们需要定义一个算法的代价函数,算法的代价函数是对 $m$ 个样本的损失函数求和然后除以 $m$:
$$
J\left( w, b \right)=\frac{1}{m}\sum\limits{i = 1}^{m}{L\left( {{{\hat{y}}}^{(i)}},{{y}^{(i)}} \right)}=\frac{1}{m}\sum\limits{i = 1}^{m}{\left( -{{y}^{(i)}}\log {{{\hat{y}}}^{(i)}}-(1-{{y}^{(i)}})\log (1-{{{\hat{y}}}^{(i)}}) \right)}
$$
损失函数只适用于像这样的单个训练样本,而代价函数是参数的总代价,所以在训练逻辑回归模型时候,我们需要找到合适的 $w$ 和 $b$,来让代价函数 $J$ 的总代价降到最低。

梯度下降法(Gradient Descent)

梯度下降法可以做什么?

在你测试集上,通过最小化代价函数(成本函数)$J(w,b)$ 来训练的参数 $w$ 和 $b$,

梯度下降法的形象化说明

在这个图中,横轴表示你的空间参数 $w$ 和 $b$,在实践中,$w$ 可以是更高的维度,但是为了更好地绘图,我们定义 $w$ 和 $b$,都是单一实数,代价函数(成本函数)$J(w,b)$ 是在水平轴 $w$ 和 $b$ 上的曲面,因此曲面的高度就是 $J(w,b)$ 在某一点的函数值。我们所做的就是找到使得代价函数(成本函数)$J(w,b)$ 函数值是最小值,对应的参数 $w$ 和 $b$。

如图,代价函数(成本函数)$J(w,b)$ 是一个凸函数(convex function),像一个大碗一样。

如图,这就与刚才的图有些相反,因为它是非凸的并且有很多不同的局部最小值。由于逻辑回归的代价函数(成本函数)$J(w,b)$ 特性,我们必须定义代价函数(成本函数)$J(w,b)$ 为凸函数。

如何训练的参数 $w$ 和 $b$

  1. 初始化 $w$ 和 $b$

可以用如图那个小红点来初始化参数 $w$ 和 $b$,也可以采用随机初始化的方法,对于逻辑回归几乎所有的初始化方法都有效,因为函数是凸函数,无论在哪里初始化,应该达到同一点或大致相同的点。

我们以如图的小红点的坐标来初始化参数 $w$ 和 $b$。

2. 朝最陡的下坡方向走一步,不断地迭代

我们朝最陡的下坡方向走一步,如图,走到了如图中第二个小红点处。

我们可能停在这里也有可能继续朝最陡的下坡方向再走一步,如图,经过两次迭代走到第三个小红点处。

3.直到走到全局最优解或者接近全局最优解的地方

通过以上的三个步骤我们可以找到全局最优解,也就是代价函数(成本函数)$J(w,b)$ 这个凸函数的最小值点。

基本原理

梯度下降法的细节化说明(仅有一个参数)

假定代价函数(成本函数)$J(w)$ 只有一个参数 $w$,即用一维曲线代替多维曲线,这样可以更好画出图像。

迭代就是不断重复做如图的公式, $:=$ 表示更新参数,

$a $ 表示学习率(learning rate),用来控制步长(step),即向下走一步的长度 $\frac{dJ(w)}{dw}$ 就是函数 $J(w)$ 对 $w$ 求导(derivative),在代码中我们会使用 $dw$ 表示这个结果

对于导数更加形象化的理解就是斜率(slope),如图该点的导数就是这个点相切于 $J(w)$ 的小三角形的高除宽。假设我们以如图点为初始化点,该点处的斜率的符号是正的,即 $\frac{dJ(w)}{dw}>0$,所以接下来会向左走一步。

整个梯度下降法的迭代过程就是不断地向左走,直至逼近最小值点。

假设我们以如图点为初始化点,该点处的斜率的符号是负的,即 $\frac{dJ(w)}{dw}<0$,所以接下来会向右走一步。

整个梯度下降法的迭代过程就是不断地向右走,即朝着最小值点方向走。

梯度下降法的细节化说明(两个参数)

逻辑回归的代价函数(成本函数)$J(w,b)$ 是含有两个参数的。

  • $\partial $ 表示求偏导符号,可以读作 round

  • $\frac{\partial J(w,b)}{\partial w}$ 就是函数 $J(w,b)$ 对 $w$ 求偏导,在代码中我们会使用 $dw$ 表示这个结果,

  • $ \frac{\partial J(w,b)}{\partial b}$ 就是函数 $J(w,b)$ 对 $b$ 求偏导,在代码中我们会使用 $db$ 表示这个结果,

  • 小写字母 $d$ 用在求导数(derivative),即函数只有一个参数,

  • 偏导数符号 $\partial $ 用在求偏导(partial derivative),即函数含有两个以上的参数。

计算流程图

一个神经网络的计算,都是按照前向或反向传播过程组织的.

首先我们计算出一个新的网络的输出(前向过程),紧接着进行一个反向传输操作。后者我们用来计算出对应的梯度或导数。计算图解释了为什么我们用这种方式组织这些计算过程。

前向传播

我们尝试计算函数 $J$,$J$ 是由三个变量 $a,b,c$ 组成的函数,这个函数是 $\text{3(a}+\text{bc)}$ 。

计算这个函数实际上有三个不同的步骤,首先是计算 $b$ 乘以 $c$,我们把它储存在变量 $u$ 中,因此 ${u}={bc}$;然后计算 $v=a+u$;最后输出 $J=3v$,这就是要计算的函数 $J$。

我们可以把这三步画成如下的计算图,我先在这画三个变量 $a,b,c$,第一步就是计算 $u=bc$,我在这周围放个矩形框,它的输入是 $b,c$,接着第二步 $v=a+u$,最后一步 $J=3v$。

举个例子: $a=5,b=3,c=2$ ,$u=bc$ 就是 6,$v=a+u$ ,就是 5+6 = 11。$J$ 是 3 倍的 ,因此。即 $3×(5+3×2)$。如果你把它算出来,实际上得到 33 就是 $J$ 的值。

反向传播

我们看了一个例子使用流程计算图来计算函数 J。现在我们清理一下流程图的描述,看看你如何利用它计算出函数 $J$ 的导数。

链式法则

$\frac{dJ}{du}=\frac{dJ}{dv}\frac{dv}{du}$ , $\frac{dJ}{db}=\frac{dJ}{du}\frac{du}{db}$ , $\frac{dJ}{da}=\frac{dJ}{du}\frac{du}{da}$

假设你要计算 $\frac{{dJ}}{{dv}}$,那要怎么算呢?好,比如说,我们要把这个 $v$ 值拿过来,改变一下,那么 $J$ 的值会怎么变呢?

令 $J = 3v$,现在 $v=11$,所以如果你让 $v$ 增加一点点,比如到 11.001,那么 $J =3v =33.003$,这里 $v$ 增加了 0.001,然后最终结果是 $J$ 增加了 $v$ 的增量的 3 倍,所以 $\frac{{dJ}}{{dv}}=3$,对于任何 $v$ 的增量 $J$ 都会有 3 倍增量

通过链式法则我们可以求得对应参数的偏导

当你编程实现反向传播时,通常会有一个最终输出值是你要关心的,那就是最终的输出变量。在这种情况下最终的输出变量是 J,就是流程图里最后一个符号,所以有很多计算尝试计算输出变量的导数,所以输出变量对某个变量的导数,我们就用 $dvar$ 命名.

计算图在梯度下降的应用

回想一下逻辑回归的公式定义如下:$\hat{y}=a=\sigma (z)$, 其中 $z={{w}^{T}}x+b$ , $\sigma \left( z \right)=\frac{1}{1+{{e}^{-z}}}$

损失函数:$L( {{{\hat{y}}}^{(i)}},{{y}^{(i)}})=-{{y}^{(i)}}\log {{\hat{y}}^{(i)}}-(1-{{y}^{(i)}})\log (1-{{\hat{y}}^{(i)}})$

代价函数:$J\left( w,b \right)=\frac{1}{m}\sum\nolimits_{i}^{m}{L( {{{\hat{y}}}^{(i)}},{{y}^{(i)}})}$

假设现在只考虑单个样本的情况,单个样本的代价函数定义如下:$L(a,y)=-(y\log (a)+(1-y)\log (1-a))$

其中 $a$ 是逻辑回归的输出,$y$ 是样本的标签值现在让我们画出表示这个计算的计算图。

这里先复习下梯度下降法,$w$ 和 $b$ 的修正量可以表达如下:$w:=w-a \frac{\partial J(w,b)}{\partial w}$,$b:=b-a\frac{\partial J(w,b)}{\partial b}$

现在让我们来讨论通过反向计算出导数。

因为我们想要计算出的代价函数 $L(a,y)$ 的导数,首先我们需要反向计算出代价函数 $L(a,y)$ 关于 $a$ 的导数,在编写代码时,你只需要用 $da$ 来表示 $\frac{dL(a,y)}{da}$ 。
$$
\frac{dL(a, y)}{da}=-y/a+(1-y)/(1-a)
$$

$$
\frac{da}{dz}= a\cdot (1-a)
$$

$$
{dz} = \frac{{dL}(a, y)}{{dz}} = \frac{{dL}}{{dz}} = \left( \frac{{dL}}{{da}} \right) \cdot \left(\frac{{da}}{{dz}} \right) = ( - \frac{y}{a} + \frac{(1 - y)}{(1 - a)})\cdot a(1 - a) = a - y
$$

现在进行最后一步反向推导,也就是计算 $w$ 和 $b$ 变化对代价函数 $L$ 的影响,特别地,可以用:
$$
d{{w}{1}}=\frac{1}{m}\sum\limits{i}^{m}{x_{1}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}})
$$

$$
d{{w}{2}}=\frac{1}{m}\sum\limits{i}^{m}{x_{2}^{(i)}}({{a}^{(i)}}-{{y}^{(i)}})
$$

$$
db =\frac{1}{m}\sum\limits_{i}^{m}{({{a}^{(i)}}-{{y}^{(i)}})}
$$

$d{{w}{1}}$ 表示 $\frac{\partial L}{\partial {{w}{1}}}={{x}{1}}\cdot dz$, $d{{w}{\text{2}}}$ 表示 $\frac{\partial L}{\partial {{w}{2}}}={{x}{2}}\cdot dz$,$db=dz$。

因此,关于单个样本的梯度下降算法,你所需要做的就是如下的事情:

  • 使用公式 $dz=(a-y)$ 计算 $dz$,
  • 使用 $d{{w}{1}}={{x}{1}}\cdot dz$ 计算 $d{{w}{1}}$, $d{{w}{2}}={{x}{2}}\cdot dz$ 计算 $d{{w}{2}}$,$db=dz$ 来计算 $db$,
  • 然后: 更新 ${{w}{1}}={{w}{1}}-a d{{w}{1}}$,更新 ${{w}{2}}={{w}{2}}-a d{{w}{2}}$,更新 $b=b-\alpha db$。

m 个样本的梯度下降(Gradient Descent on m Examples)

在之前的视频中, 你已经看到如何计算导数,以及应用梯度下降在逻辑回归的一个训练样本上。现在我们想要把它应用在 $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$ 表示你求得的相应的值。带有求和的全局代价函数,实际上是 1 到 $m$ 项各个损失的平均。 所以它表明全局代价函数对 ${{w}{1}}$ 的微分,对 ${{w}{1}}$ 的微分也同样是 各项损失对 ${{w}_{1}}$ 微分的平均

所以你真正需要做的是计算这些微分,如我们在之前的训练样本上做的。并且求平均,这会给你全局梯度值,你能够把它直接应用到梯度下降算法中。

我们初始化 $J=0,d{{w}{1}}=0,d{{w}{2}}=0,db=0$

代码流程:

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$ 个特征。

向量化(Vectorization)

基本原理

向量化是非常基础的去除代码中 for 循环的艺术,在深度学习安全领域、深度学习实践中,你会经常发现自己训练大数据集,因为深度学习算法处理大数据集效果很棒,所以你的代码运行速度非常重要,否则如果在大数据集上,你的代码可能花费很长时间去运行,你将要等待非常长的时间去得到结果。所以在深度学习领域,运行向量化是一个关键的技巧,让我们举个栗子说明什么是向量化。

在逻辑回归中你需要去计算 $z={{w}^{T}}x+b$,$w$、$x$ 都是列向量。如果你有很多的特征那么就会有一个非常大的向量,所以 $w\in {{\mathbb{R}}^{{{n}{x}}}}$ , $x\in{{\mathbb{R}}^{{{n}{x}}}}$,所以如果你想使用非向量化方法去计算 ${{w}^{T}}x$,你需要用如下方式(python

z=0
for i in range(n_x):
    z += w[i]*x[i]
z += b

这是一个非向量化的实现,你会发现这真的很慢,作为一个对比,向量化实现将会非常直接计算 ${{w}^{T}}x$,代码如下:

z=np.dot(w,x)+b

这是向量化计算 ${{w}^{T}}x$ 的方法,你将会发现这个非常快

import numpy as np #导入numpy库
a = np.array([1,2,3,4]) #创建一个数据a
print(a)
# [1 2 3 4]

import time #导入时间库
a = np.random.rand(1000000)
b = np.random.rand(1000000) #通过round随机得到两个一百万维度的数组
tic = time.time() #现在测量一下当前时间

#向量化的版本
c = np.dot(a,b)
toc = time.time()
print("Vectorized version:" + str(1000*(toc-tic)) +"ms") #打印一下向量化的版本的时间

#继续增加非向量化的版本
c = 0
tic = time.time()
for i in range(1000000):
    c += a[i]*b[i]
toc = time.time()
print(c)
print("For loop:" + str(1000*(toc-tic)) + "ms")#打印for循环的版本的时间

在两个方法中,向量化和非向量化计算了相同的值,如你所见,向量化版本花费了 1.5 毫秒,非向量化版本的 for 循环花费了大约几乎 500 毫秒,非向量化版本多花费了 300 倍时间。所以在这个例子中,仅仅是向量化你的代码,就会运行 300 倍快。这意味着如果向量化方法需要花费一分钟去运行的数据,for 循环将会花费 5 个小时去运行。

一句话总结,以上都是再说和 for 循环相比,向量化可以快速得到结果。

那么运用在逻辑回归的梯度下降上,看看我们是否能简化两个计算过程中的某一步。这是我们逻辑回归的求导代码,有两层循环。在这例子我们有 $n$ 个特征值。如果你有超过两个特征时,需要循环 $dw_1$ 、$dw_2$ 、$dw_3$ 等等。所以 $j$ 的实际值是 1、2 到 $n_x$,就是你想要更新的值。所以我们想要消除第二循环,在这一行,这样我们就不用初始化 $dw_1$ , $dw_2$ 都等于 0。去掉这些,而是定义 $dw$ 为一个向量,设置 $u=np.zeros(n(x),1)$。定义了一个 $x$ 行的一维向量,从而替代循环。我们仅仅使用了一个向量操作 $dw=dw+x^{(i)}dz^{(i)}$ 。最后,我们得到 $dw=dw/m$ 。现在我们通过将两层循环转成一层循环,我们仍然还有这个循环训练样本。

向量化逻辑回归(Vectorizing Logistic Regression)

我们已经讨论过向量化是如何显著加速你的代码,在本次视频中我们将讨论如何实现逻辑回归的向量化计算。这样就能处理整个数据集,甚至不会用一个明确的 for 循环就能实现对于整个数据集梯度下降算法的优化。

首先,回忆一下我们曾经定义了一个矩阵 $X$ 作为你的训练输入,(如下图中蓝色 $X$ )像这样在不同的列中堆积在一起。这是一个 $nx$ 行 $m$ 列的矩阵。我现在将它写为 Python numpy 的形式 $$(n{x},m)$$ ,这只是表示 $X$ 是一个 $n_x$ 乘以 $m$ 的矩阵 $$R^{n_x \times m}$$。

将 $[z^{(1)} z^{(2)} ... z^{(m)}]$ 定义为大写的 $Z$ ,你用小写 $z$ 表示并将它们横向排在一起。所以当你将不同训练样本对应的小写 $x$ 横向堆积在一起时得到大写变量 $X$ 并且将小写变量也用相同方法处理,将它们横向堆积起来,你就得到大写变量 $Z$ 。

结果发现,为了计算 $W^{T}X+[b b ... b]$ ,numpy 命令是 $Z=np.dot(w.T,X)+b$。这里在 Python 中有一个巧妙的地方,这里 $b$ 是一个实数,或者你可以说是一个 $1\times 1$ 矩阵,只是一个普通的实数。但是当你将这个向量加上这个实数时,Python 自动把这个实数 $b$ 扩展成一个 $1\times m$ 的行向量。所以这种情况下的操作似乎有点不可思议,它在 Python 中被称作广播(brosdcasting)

我们接下来要做的就是找到一个同时计算 $[a^{(1)} a^{(2)} ... a^{(m)}]$ 的方法。就像把小写 $x$ 堆积起来得到大写 $X$ 和横向堆积小写 $z$ 得到大写 $Z$ 一样,堆积小写变量 $a$ 将形成一个新的变量,我们将它定义为大写 $A$。

Z = np.dot(w.T,X) + b

这一行代码:$A=[a^{(1)} a^{(2)} ... a^{(m)}]=\sigma (Z)$ ,通过恰当地运用 $\sigma$ 一次性计算所有 $a$。这就是在同一时间内你如何完成一个所有 $m$ 个训练样本的前向传播向量化计算。

向量化 logistic 回归的梯度输出

首先我们来看 $db$,不难发现 $$db=\frac{1}{m}\sum_{i=1}^{m}dz^{(i)}$$ , 之前的讲解中,我们知道所有的 $dz^{(i)}$ 已经组成一个行向量 $dZ$ 了,所以在 Python 中,我们很容易地想到 $$db=\frac{1}{m}*np.sum(dZ)$$;

接下来看 $dw$,我们先写出它的公式 $$dw=\frac{1}{m}Xdz^{T}$$ 其中,$X$ 是一个行向量。因此展开后 $dw=\frac{1}{m}*(x^{(1)}dz^{(1)}+x^{(2)}dz^{(2)}+...+x^{m}dz^{m})$ 。

因此我们可以仅用两行代码进行计算:$$db=\frac{1}{m}np.sum(dZ)$$, $$dw=\frac{1}{m}X*dz^{T}$$。这样,我们就避免了在训练集上使用 for 循环。

最终逻辑回归代码可以优化为

$Z = w^{T}X + b = np.dot( w.T,X)+b$

$A = \sigma( Z )$

$dZ = A - Y$

${{dw} = \frac{1}{m}Xdz^{T}\ }$

$db= \frac{1}{m}*np.sum( dZ)$

$w: = w - a*dw$

$b: = b - a*db$

现在我们利用前五个公式完成了前向和后向传播,也实现了对所有训练样本进行预测和求导,再利用后两个公式,梯度下降更新参数。

广播机制

NumPy 的广播机制是一个强大的功能,它可以让不同形状的数组在计算时自动对齐,避免了手动扩展数组的复杂操作。通过广播,NumPy 在计算时会自动处理数组的维度,使得操作可以在更高效的方式下完成。


广播机制的规则

NumPy 的广播机制基于以下规则:

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

后缘维度的轴长度:A.shape[-1] 即矩阵元组中的最后一个维度的值


广播示例

  1. 标量与数组: 标量可以自动扩展为数组的形状。

    import numpy as np
    
    a = np.array([1, 2, 3])
    b = 2
    result = a + b
    print(result)  # 输出 [3, 4, 5]
    • 标量 2 被广播为 [2, 2, 2],然后进行加法运算。
  2. 二维数组与一维数组

    a = np.array([[1, 2, 3], [4, 5, 6]])
    b = np.array([10, 20, 30])
    result = a + b
    print(result)
    # 输出:
    # [[11, 22, 33],
    #  [14, 25, 36]]
  3. 不同形状的二维数组

    a = np.array([[1], [2], [3]])
    b = np.array([10, 20, 30])
    result = a + b
    print(result)
    # 输出:
    # [[11, 21, 31],
    #  [12, 22, 32],
    #  [13, 23, 33]]
    • 通过广播:
      • a 被扩展为 (3, 3)
      • b 被扩展为 (3, 3)
  4. 不兼容的情况: 如果两个数组在某些维度上形状不兼容,广播机制会报错。例如:

    a = np.array([1, 2, 3])
    b = np.array([1, 2])
    result = a + b  # ValueError: operands could not be broadcast together
    • a 的形状是 (3,)b 的形状是 (2,),两者在最后一个维度上不兼容。

检查广播结果形状

使用 np.broadcast_shapes 可以查看广播后结果的形状:

import numpy as np

shape1 = (3, 1)
shape2 = (1, 4)
result_shape = np.broadcast_shapes(shape1, shape2)
print(result_shape)  # 输出 (3, 4)

总结

浅层神经网络

神经网络概述

如何实现神经网络?

上周我们讨论了逻辑回归,我们了解了这个模型如何与下面公式建立联系。

公式
$$
\left.
\begin{array}{l}
x\
w\
b
\end{array}
\right}
\implies{z ={w}^Tx+b}
$$

如上所示,首先你需要输入特征 $x$,参数 $w$ 和 $b$,通过这些你就可以计算出 $z$,接下来使用 $z$ 就可以计算出 $a$。我们将的符号换为表示输出 $\hat{y}\implies{a = \sigma(z)}$, 然后可以计算出 loss function $L(a,y)$
$$
\left.
\begin{array}{l}
x\
w\
b
\end{array}
\right}
\implies{z ={w}^Tx+b}
\implies{a = \sigma(z)}\
\implies{{L}(a, y)}
$$

神经网络如下

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

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

整个计算过程,公式如下:
$$
\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]})}
$$

$$
\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)}
$$

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

在逻辑回归中,通过直接计算 $z$ 得到结果 $a$。而这个神经网络中,我们反复的计算 $z$ 和 $a$,计算 $a$ 和 $z$,最后得到了最终的输出 loss function

逻辑回归中,有一些从后向前的计算用来计算导数 $da$、$dz$。同样,在神经网络中我们也有从后向前的计算,看起来就像这样,最后会计算 $da^{[2]}$ 、$dz^{[2]}$,计算出来之后,然后计算计算 $dW^{[2]}$、$db^{[2]}$ 等,按箭头表示的那样,从右到左反向计算。

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

我们首先关注一个例子,本例中的神经网络只包含一个隐藏层

我们有输入特征 $x_1$、$x_2$、$x_3$,它们被竖直地堆叠起来,这叫做神经网络的 输入层。它包含了神经网络的输入;然后这里有另外一层我们称之为 隐藏层(图中那四个结点)。

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

在本例中最后一层只由一个结点构成,而这个只有一个结点的层被称为 输出层,它负责产生预测值。

我们再引入几个符号, 这里有个可代替的记号 $a^{[0]}$ 可以用来表示输入特征。

  • $a$ 表示激活的意思,它意味着网络中不同层的值会传递到它们后面的层中,输入层将 $x$ 传递给隐藏层,所以我们将输入层的激活值称为 $a^{[0]}$;
  • 下一层即隐藏层也同样会产生一些激活值,那么我将其记作 $a^{[1]}$,所以具体地,这里的第一个单元或结点我们将其表示为 $a^{[1]}{1}$,第二个结点的值我们记为 $a^{[1]}{2}$ 以此类推。如本例:

$$
a^{[1]} =
\left[
\begin{array}{ccc}
a^{[1]}{1}\
a^{[1]}
{2}\
a^{[1]}{3}\
a^{[1]}
{4}
\end{array}
\right]
$$

  • 最后输出层将产生某个数值 $a$,它只是一个单独的实数,所以的 $\hat{y}$ 值将取为 $a^{[2]}$。

当我们计算网络的层数时,输入层是不算入总层数内,所以隐藏层是第一层,输出层是第二层。第二个惯例是我们将输入层称为第零层

最后,我们要看到的隐藏层以及最后的输出层是带有参数的,这里的隐藏层将拥有两个参数 $W$ 和 $b$,我将给它们加上上标 $^{[1]}$($W^{[1]}$, $b^{[1]}$),表示这些参数是和第一层这个隐藏层有关系的。

之后在这个例子中我们会看到 $W$ 是一个 4x3 的矩阵,而 $b$ 是一个 4x1 的向量,第一个数字 4 源自于我们有四个结点或隐藏层单元,然后数字 3 源自于这里有三个输入特征

相似的输出层也有一些与之关联的参数 $W^{[2]}$ 以及 $b^{[2]}$。从维数上来看,它们的规模分别是 1x4 以及 1x1。1x4 是因为隐藏层有四个隐藏层单元而输出层只有一个单元

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

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

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

神经网络的计算

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

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

第一步,计算 $z^{[1]}_1,z^{[1]}_1 = w^{[1]T}_1x + b^{[1]}_1$。

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

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

$z^{[1]}_1 = w^{[1]T}_1x + b^{[1]}_1, a^{[1]}_1 = \sigma(z^{[1]}_1)$

$z^{[1]}_2 = w^{[1]T}_2x + b^{[1]}_2, a^{[1]}_2 = \sigma(z^{[1]}_2)$

$z^{[1]}_3 = w^{[1]T}_3x + b^{[1]}_3, a^{[1]}_3 = \sigma(z^{[1]}_3)$

$z^{[1]}_4 = w^{[1]T}_4x + b^{[1]}_4, a^{[1]}_4 = \sigma(z^{[1]}_4)$

向量化计算

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

$$
a^{[n]}=\sigma(z^{[n]})
$$

$$
a^{[1]} =
\left[
\begin{array}{c}
a^{[1]}{1}\
a^{[1]}
{2}\
a^{[1]}{3}\
a^{[1]}
{4}
\end{array}
\right]
= \sigma(z^{[1]})
$$

$$
\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]}}

$$

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

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

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

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

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

用激活函数表示法,把每个样本的输出值写成 $a^{2}$、$a^{2}$ 和 $a^{2}$。

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

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

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

$a^{1}=\sigma(z^{1})$

$z^{2}=W^{2}a^{1}+b^{2}$

$a^{2}=\sigma(z^{2})$

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

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

$$
Z^{[1]} =
\left[
\begin{array}{c}
\vdots & \vdots & \vdots & \vdots\
z^{1} & z^{1} & \cdots & z^{1}\
\vdots & \vdots & \vdots & \vdots\
\end{array}
\right]
$$

$$
A^{[1]} =
\left[
\begin{array}{c}
\vdots & \vdots & \vdots & \vdots\
\alpha^{1} & \alpha^{1} & \cdots & \alpha^{1}\
\vdots & \vdots & \vdots & \vdots\
\end{array}
\right]
$$

$$
\left.
\begin{array}{r}
\text{$z^{1} = W^{1}x^{(i)} + b^{[1]}$}\
\text{$\alpha^{1} = \sigma(z^{1})$}\
\text{$z^{2} = W^{2}\alpha^{1} + b^{[2]}$}\
\text{$\alpha^{2} = \sigma(z^{2})$}\
\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}
$$

从小写的向量 $x$ 到这个大写的矩阵 $X$,只是通过组合 $x$ 向量在矩阵的各列中。

同理,$z^{1}$,$z^{1}$ 等等都是 $z^{1}$ 的列向量,将所有 $m$ 都组合在各列中,就的到矩阵 $Z^{[1]}$。

同理,$a^{1}$,$a^{1}$,……,$a^{1}$ 将其组合在矩阵各列中,如同从向量 $x$ 到矩阵 $X$,以及从向量 $z$ 到矩阵 $Z$ 一样,就能得到矩阵 $A^{[1]}$。

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

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

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

我们先手动对几个样本计算一下前向传播,看看有什么规律:
$z^{1} = W^{[1]}x^{(1)} + b^{[1]}$

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

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

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

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

    \left[
    \begin{array}{c}
    \vdots &\vdots & \vdots & \vdots \\
    x^{(1)} & x^{(2)} & x^{(3)} & \vdots\\
    \vdots &\vdots & \vdots & \vdots \\
    \end{array}
    \right]
    =
    \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]
    =\\
    \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]
    =
    Z^{[1]}

$$

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

使用向量化的方法,可以不需要显示循环,而直接通过矩阵运算从 $X$ 就可以计算出 $A^{[1]}$,实际上 $X$ 可以记为 $A^{[0]}$,使用同样的方法就可以由神经网络中的每一层的输入 $A^{[i-1]}$ 计算输出 $A^{[i]}$。其实这些方程有一定对称性,其中第一个方程也可以写成 $Z^{[1]} = W^{[1]}A^{[0]} + b^{[1]}$

激活函数(Activation functions)

在神经网路的前向传播中的 $a^{[1]} = \sigma(z^{[1]})$ 和 $a^{[2]} =\sigma(z^{[2]})$ 这两步会使用到 sigmoid 函数。sigmoid 函数在这里被称为激活函数。$a = \sigma(z) = \frac{1}{{1 + e}^{- z}}$ 如图,$a = tanh(z)$ 的值域是位于+1 和-1 之间。$a= tanh(z) = \frac{e^{z} - e^{- z}}{e^{z} + e^{- z}}$

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

tanh 函数在所有场合都优于 sigmoid 函数。

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

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

在机器学习另一个很流行的函数是:修正线性单元的函数(ReLu),ReLu 函数图像是如下图 $ a =max( 0,z) $
所以,只要 $z$ 是正值的情况下,导数恒等于 1,当 $z$ 是负值的时候,导数恒等于 0。

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

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

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

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

两者的优点是:

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

第二,sigmoidtanh 函数的导数在正负饱和区的梯度都会接近于 0,这会造成梯度弥散,而 ReluLeaky ReLu 函数大于 0 部分都为常数,不会产生梯度弥散现象。(同时应该注意到的是,Relu 进入负半区的时候,梯度为 0,神经元此时不会训练,产生所谓的稀疏性,而 Leaky ReLu 不会有这问题.$z$ 在 ReLu 的梯度一半都是 0,但是,有足够的隐藏层使得 z 值大于 0,所以对大多数的训练数据来说学习过程仍然可以很快。

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

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

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

如果我们改变前面的式子,令: $a^{[1]} = z^{[1]} = W^{[1]}x + b^{[1]}$ $a^{[2]} = z^{[2]} = W^{[2]}a^{[1]}+ b^{[2]}$
则:$a^{[2]} = z^{[2]} = W^{[2]}(W^{[1]}x + b^{[1]}) + b^{[2]}$

$a^{[2]} = z^{[2]} = W^{[2]}W^{[1]}x + W^{[2]}b^{[1]} + b^{[2]} $
简化多项式得
$a^{[2]} = z^{[2]} = W^{'}x + b^{'} $

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

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

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

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

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

  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))
$$
在神经网络中 $a= g(z)$; $g{{(z)}^{'}}=\frac{d}{dz}g(z)=a(1-a)$

  1. 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}
$$

  1. 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 类似
$$
g(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 = 0$ 的时候给定其导数 1,0.01;当然 $z=0$ 的情况很少。

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

你的单隐层神经网络会有 $W^{[1]}$,$b^{[1]}$,$W^{[2]}$,$b^{[2]}$ 这些参数,还有个 $n_x$ 表示输入特征的个数,$n^{[1]}$ 表示隐藏单元个数,$n^{[2]}$ 表示输出单元个数。

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

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

训练参数需要做梯度下降,在训练神经网络的时候,随机初始化参数很重要,而不是初始化成全零。当你参数初始化成某些值后,每次梯度下降都会循环计算以下预测值:

$$
\hat{y}^{(i)},(i = 1,2,…, m)
$$

$$
dW^{[1]} = \frac{dJ}{dW^{[1]}}, db^{[1]} = \frac{dJ}{db^{[1]}}
$$

$$
{d}W^{[2]} = \frac{{dJ}}{dW^{[2]}},{d}b^{[2]} = \frac{dJ}{db^{[2]}}
$$

$$
W^{[1]}\implies{W^{[1]} - adW^{[1]}}, b^{[1]}\implies{b^{[1]} -adb^{[1]}}
$$

$$
W^{[2]}\implies{W^{[2]} - \alpha{\rm d}W^{[2]}}, b^{[2]}\implies{b^{[2]} - \alpha{\rm d}b^{[2]}}
$$

forward propagation

  • $z^{[1]} = W^{[1]}x + b^{[1]}$
  • $a^{[1]} = \sigma(z^{[1]})$
  • $z^{[2]} = W^{[2]}a^{[1]} + b^{[2]}$
  • $a^{[2]} = g^{[2]}(z^{[z]}) = \sigma(z^{[2]})$

back propagation

  • $ dz^{[2]} = A^{[2]} - Y , Y = \begin{bmatrix}y^{[1]} & y^{[2]} & \cdots & y^{[m]}\ \end{bmatrix} $
  • $ dW^{[2]} = {\frac{1}{m}}dz^{[2]}A^{[1]T} $
  • $ {\rm d}b^{[2]} = {\frac{1}{m}}np.sum({d}z^{[2]},axis=1,keepdims=True)$
  • $ dz^{[1]} = \underbrace{W^{[2]T}{\rm d}z^{[2]}}{(n^{[1]},m)}\quad*\underbrace{{g^{[1]}}^{'}}{activation \; function \; of \; hidden \; layer}*\quad\underbrace{(z^{[1]})}_{(n^{[1]},m)} $
  • $dW^{[1]} = {\frac{1}{m}}dz^{[1]}x^{T}$
  • ${\underbrace{db^{[1]}}_{(n^{[1]},1)}} = {\frac{1}{m}}np.sum(dz^{[1]},axis=1,keepdims=True)$

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

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

随机初始化(Random Initialization)

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

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

那这个问题如果按照这样初始化的话,你总是会发现 $a{1}^{[1]}$ 和 $a{2}^{[1]}$ 相等,这个激活单元和这个激活单元就会一样。因为两个隐含单元计算同样的函数,当你做反向传播计算时,这会导致 $\text{dz}{1}^{[1]}$ 和 $\text{dz}{2}^{[1]}$ 也会一样.

那么这两个隐含单元就会完全一样,因此他们完全对称,也就意味着计算同样的函数,并且肯定的是最终经过每次训练的迭代,这两个隐含单元仍然是同一个函数,令人困惑。$dW$ 会是一个这样的矩阵,每一行有同样的值因此我们做权重更新把权重 $W^{[1]}\implies{W^{[1]}-adW}$ 每次迭代后的 $W^{[1]}$,第一行等于第二行。

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

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

$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]} = 0$

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

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

深层神经网络

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

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

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

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

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

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

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

在不同层所拥有的神经元的数目,对于每层 l 都用 ${a}^{[l]}$ 来记作 l 层激活后结果,我们会在后面看到在正向传播时,最终能你会计算出 ${{a}^{[l]}}$。通过用激活函数 $g$ 计算 ${z}^{[l]}$,激活函数也被索引为层数 $l$,然后我们用 ${w}^{[l]}$ 来记作在 l 层计算 ${z}^{[l]}$ 值的权重。类似的,${{z}^{[l]}}$ 里的方程 ${b}^{[l]}$ 也一样。

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

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

先讲前向传播,输入 ${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]}}$

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

(1)$d{{z}^{[l]}}=d{{a}^{[l]}}*{{g}^{[l]}}'( {{z}^{[l]}})$

(2)$d{{w}^{[l]}}=d{{z}^{[l]}}\cdot{{a}^{[l-1]}}~$

(3)$d{{b}^{[l]}}=d{{z}^{[l]}}~~$

(4)$d{{a}^{[l-1]}}={{w}^{\left[ l \right]T}}\cdot {{dz}^{[l]}}$

(5)$d{{z}^{[l]}}={{w}^{[l+1]T}}d{{z}^{[l+1]}}\cdot \text{ }{{g}^{[l]}}'( {{z}^{[l]}})~$

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

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

(6)$d{{Z}^{[l]}}=d{{A}^{[l]}}*{{g}^{\left[ l \right]}}'\left({{Z}^{[l]}} \right)~~$

(7)$d{{W}^{[l]}}=\frac{1}{m}\text{}d{{Z}^{[l]}}\cdot {{A}^{\left[ l-1 \right]T}}$

(8)$d{{b}^{[l]}}=\frac{1}{m}\text{ }np.sum(d{{z}^{[l]}},axis=1,keepdims=True)$

(9)$d{{A}^{[l-1]}}={{W}^{\left[ l \right]T}}.d{{Z}^{[l]}}$

总结一下:

第一层你可能有一个 ReLU 激活函数,第二层为另一个 ReLU 激活函数,第三层可能是 sigmoid 函数(如果你做二分类的话),输出值为,用来计算损失;这样你就可以向后迭代进行反向传播求导来求 ${{dw}^{[3]}}$,${{db}^{[3]}}$ ,${{dw}^{[2]}}$ ,${{db}^{[2]}}$ ,${{dw}^{[1]}}$ ,${{db}^{[1]}}$。在计算的时候,缓存会把 ${{z}^{[1]}}$ ${{z}^{[2]}}$ ${{z}^{[3]}}$ 传递过来,然后回传 ${{da}^{[2]}}$,${{da}^{[1]}}$ ,可以用来计算 ${{da}^{[0]}}$

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

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

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

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

以此类推,

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

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

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

${{Z}^{[l]}}={{W}^{[l]}}{{a}^{[l-1]}}+{{b}^{[l]}}$,${{A}^{[l]}}={{g}^{[l]}}{({Z}^{[l]})}$ (${{A}^{[0]}} = X)$

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

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

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

$b$ 的维度是(下一层的维数,1),即:

${{b}^{[l]}}$ : (${{n}^{[l]}},1)$;

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

${{dw}^{[l]}}$ 和 ${{w}^{[l]}}$ 维度相同,${{db}^{[l]}}$ 和 ${{b}^{[l]}}$ 维度相同,且 $w$ 和 $b$ 向量化维度不变,但 $z$, $a$ 以及 $x$ 的维度会向量化后发生变化。

向量化后:

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

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

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

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

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

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

首先,深度网络究竟在计算什么?如果你在建一个人脸识别或是人脸检测系统,深度神经网络所做的事就是,当你输入一张脸部的照片,然后你可以把深度神经网络的第一层,当成一个特征探测器或者边缘探测器。在这个例子里,我会建一个大概有 20 个隐藏单元的深度神经网络,是怎么针对这张图计算的。隐藏单元就是这些图里这些小方块(第一张大图),举个例子,这个小方块(第一行第一列)就是一个隐藏单元,它会去找这张照片里“|”边缘的方向。那么这个隐藏单元(第四行第四列),可能是在找(“—”)水平向的边缘在哪里。你可以先把神经网络的第一层当作看图,然后去找这张照片的各个边缘。

我们可以把照片里组成边缘的像素们放在一起看,然后它可以把被探测到的边缘组合成面部的不同部分(第二张大图)。比如说,可能有一个神经元会去找眼睛的部分,另外还有别的在找鼻子的部分,然后把这许多的边缘结合在一起,就可以开始检测人脸的不同部分。最后再把这些部分放在一起,比如鼻子眼睛下巴,就可以识别或是探测不同的人脸(第三张大图)。

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

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

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

Deep:隐藏层数目比较多

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

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

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

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

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

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

把输入特征 $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$,反向传播就都计算完毕,我们有所有的导数值,那么这是神经网络一个梯度下降循环。

参数 VS 超参数(Parameters vs Hyperparameters)

什么是超参数?

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

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

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

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

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

另一个近来深度学习的影响是它用于解决很多问题,从计算机视觉到语音识别,到自然语言处理,到很多结构化的数据应用,比如网络广告或是网页搜索或产品推荐等等。我所看到过的就有很多其中一个领域的研究员,这些领域中的一个,尝试了不同的设置,有时候这种设置超参数的直觉可以推广,但有时又不会。所以我经常建议人们,特别是刚开始应用于新问题的人们,去试一定范围的值看看结果如何。

有一条经验规律:经常试试不同的超参数,勤于检查结果,看看有没有更好的超参数取值,你将会得到设定超参数的直觉。

此作者没有提供个人介绍
最后更新于 2025-01-26