如今在神经网络上获得更好性能的最可靠的方法是什么?
- 训练一个更大的神经网络
- 投入更多数据
这只能在一定程度上起作用,因为最终你耗尽了数据,或者最终你的网络是如此大规模导致将要用太久的时间去训练。
所以如果你没有大量的训练集,那效果会取决于你的特征工程能力,那将决定最终的性能。
最终的性能更多的是取决于你在用工程选择特征方面的能力以及算法处理方面的一些细节,只是在某些大数据规模非常庞大的训练集,我们能更加持续地看到更大的由神经网络控制的其它方法。
为什么说神经网络的巨大突破是从sigmoid函数转换到一个ReLU函数?
- 在某些极端区域,这个sigmoid函数的梯度会接近零,所以学习的速度会变得非常缓慢,因为当你实现梯度下降以及梯度接近零的时候,参数会更新的很慢,所以学习的速率也会变的很慢。
- 通过改变这个被叫做激活函数的东西,神经网络换用这一个函数,叫做ReLU的函数(修正线性单元),ReLU它的梯度对于所有输入的负值都是零,因此梯度更加不会趋向逐渐减少到零,使得梯度下降(gradient descent)的算法运行的更快
什么是二分类算法?(前向传播和反向传播的基础)
逻辑回归(logistic regression)是一个用于二分类的算法,我们的目标是习得一个分类器,它以图片的特征向量作为输入,然后预测输出结果y为1还是0,也就是预测图片中是否有我们需要的对象:
符号定义
$x$:表示一个$n_x$维数据,为输入数据,维度为$\left(n_x, 1\right)$
$y$:表示输出结果,取值为$(0,1)$;
$\left(x^{(i)}, y^{(i)}\right)$:表示第$i$组数据,可能是训练数据,也可能是测试数据,此处默认为训练数据;
$X=\left[x^{(1)}, x^{(2)}, \ldots, x^{(m)}\right]$:表示所有的训练数据集的输入值,放在一个$n_x \times m$的矩阵中,其中$m$表示样本数目
$Y=\left[y^{(1)}, y^{(2)}, \ldots, y^{(m)}\right]$:对应表示所有训练数据集的输出值,维度为$1 \times m$
用一对$(x,y)$来表示一个单独的样本,$x$代表$n_{x}$维的特征向量,$y$表示标签(输出结果)只能为0或1。而训练集将由$m$个训练样本组成,其中$(x^{(1)},y^{(1)})$表示第一个样本的输入和输出,以此类推,然后所有的这些一起表示整个训练集。有时候为了强调这是训练样本的个数,会写作$M_{train}$
注意:在Python中可将向量表示为矩阵的常用手段:
X.shape
等于$(n_{x},m)$,以及Y.shape
等于$(1,m)$
逻辑回归
Hypothesis Function(假设函数)为什么不可以使用线性函数?
你想要一个算法能够输出预测,你只能称之为 $\hat{y}$ ,也就是你对实际值 $y$ 的估计。更正式地来说,你想让 $\hat{y}$ 表示 $y$ 等于1的一种可能性或者是机会,前提条件是给定了输入特征 $X$ 。换句话来说,如果 $X$ 是我们在上个视频看到的图片,你想让 $\hat{y}$ 来告诉你这是一只猫的图片的机率有多大。
在之前的视频中所说的, $X$ 是一个 $n_x$ 维的向量(相当于有 $n_x$ 个特征的特征向量)。我们用 $w$ 来表示逻辑回归的参数,这也是一个 $n_x$ 维向量(因为 $w$ 实际上是特征权重,维度与特征向量相同),参数里面还有 $b$ ,这是一个实数 (表示偏差)。所以给出输入 $x$ 以及参数 $w$和 $b$ 之后,我们怎样产生输出预测值 $\hat{y}$ ,一件你可以尝试却不可行的事是让 $\hat{y}=w^T x+b$ 。
原因:因为 $w^T x+b$ 可能比1要大得多,或者甚至为一个负值。
对于你想要的在 0 和 1 之间的概率来说它是没有意义的,因此在逻辑回归中,我们的输出应该是 $\hat{y}$ 等于由上面得到的线性函数式子作为自变量的sigmoid函数中,将线性函数转换为非线性函数。
关于$z$的sigmoid函数的图像:
关于sigmoid函数的公式:$\sigma(z)=\frac{1}{1+e^{-z}}$ ,在这里 $z$ 是一个实数。
注意:如果 $z$ 非常大那么 $e^{-z}$ 将会接近于 0 ,关于 $z$ 的sigmoid函数将会近似等于 1 除以 1 加上某个非常接近于 0 的项,因为 $e$ 的指数如果是个绝对值很大的负数的话,这项将会接近于 0 ,所以如果 $z$ 很大的话那么关于 $z$ 的sigmoid函数会非常接近1。
相反地,如果 $z$ 非常小或者说是一个绝对值很大的负数,那么关于 $e^{-z}$ 这项会变成一个很大的数,你可以认为这是 1 除以 1 加上一个非常非常大的数,所以关于 $z$ 的 $\operatorname{sigmoid}$ 函数就会非常接近于 0 ,因此当你实现逻辑回归时,你的工作就是去让机器学习参数 $w$ 以及 $b$ 这样才使得 $\hat{y}$ 成为对 $y=1$ 这一情况的概率的一个很好的估计。
备选的符号惯例:
在这个备选的符号惯例里,你有一个参数向量 $\theta_0, \theta_1, \theta_2, \ldots, \theta_{n_x}$ ,这样 $\theta_0$ 就充当了 $b$ ,这是一个实数,而剩下的 $\theta_1$ 直到 $\theta_{n_x}$ 充当了 $w$ ,结果就是当你实现你的神经网络时,有一个比较简单的方法是保持 $b$ 和 $w$ 分开。
逻辑回归的代价函数
为什么需要逻辑回归的代价函数
损失函数(误差函数):用来衡量预测输出值和实际值有多接近:Loss function: $L(\hat{y}, y)$
一般我们用预测值和实际值的平方差或者它们平方差的一半,但是通常在逻辑回归中我们不这么做,因为当我们在学习逻辑回归参数的时候,会发现我们的优化目标不是凸优化,只能找到多个局部最优值,梯度下降法很可能找不到全局最优值,虽然平方差是一个不错的损失函数,但是我们在逻辑回归模型中会定义另外一个损失函数。
那么在逻辑回归中用到的损失函数是:$L(\hat{y}, y)=-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(w, b)=\frac{1}{m} \sum_{i=1}^m L\left(\hat{y}^{(i)}, y^{(i)}\right)=\frac{1}{m} \sum_{i=1}^m\left(-y^{(i)} \log \hat{y}^{(i)}-\left(1-y^{(i)}\right) \log \left(1-\hat{y}^{(i)}\right)\right)$
损失函数只适用于像这样的单个训练样本,而代价函数是参数的总代价,所以在训练笽辑回归模型时候,我们需要找到合适的 $w$ 和 $b$ ,来让代价函数 $J$ 的总代价降到最低。
为什么损失函数是这种形式?
梯度下降法
梯度下降法的图示:
代价函数(成本函数)是一个凸函数(convex function),具有全局最小值:
- 初始化 $w$ 和 $b$ 向量,可采用随机初始化的方法,因为对于逻辑回归几乎所有的初始化方法都有效,因为函数是凸函数,无论在哪里初始化,应该达到同一点或大致相同的点。
- 朝最陡的下坡方向走一步,不断地迭代
- 直到走到全局最优解或者接近全局最优解的地方
梯度下降法的公式:
$$
\begin{gathered}
w:=w-\alpha \frac{\partial J(w, b)}{\partial w} \newline
b:=b-\alpha \frac{\partial J(w, b)}{\partial b}
\end{gathered}
$$
$\partial$ 表示求偏导符号,可以读作round, $\frac{\partial J(w, b)}{\partial w}$ 就是函数 $J(w, b)$ 对 $w$ 求偏导,在代码中我们会使用 $d w$ 表示这个结果, $\frac{\partial J(w, b)}{\partial b}$ 就是函数 $J(w, b)$ 对 $b$ 求偏导,迭代就是不断重复做如图的公式。
:=表示更新参数,
$a$ 表示学习率 (learning rate),用来控制步长 (step) ,即向下走一步的长度 $\frac{d J(w)}{d w}$ 就是函数 $J(w)$ 对 $w$ 求导(derivative),在代码中我们会使用 $d w$ 表示这个结果,即导数的定义的应用。
前向传播和反向传播
前向传播指从输入层开始,通过一系列的计算和传递,将输入数据逐层传递至输出层的过程。
而反向传播我们给出以下几个步骤:
- 进行前馈传导计算, 利用前向传导公式, 得到 $L_2, L_3, \ldots$ 直到输出层 $L_{n_l}$ 的激活值。
- 对输出层 (第 $n_l$ 层), 计算:
$$
\delta^{\left(n_l\right)}=-\left(y-a^{\left(n_l\right)}\right) \bullet f^{\prime}\left(z^{\left(n_l\right)}\right)
$$
注意:此处公式是根据代价函数为平方差的一半进行推导的:
$$
\delta_i^{\left(n_l\right)}=\frac{\partial}{\partial z_i^{n_l}} J(W, b ; x, y)=\frac{\partial}{\partial z_i^{n_l}} \frac{1}{2}\left|y-h_{W, b}(x)\right|^2=-\left(y_i-a_i^{\left(n_l\right)}\right) \cdot f^{\prime}\left(z_i^{\left(n_l\right)}\right)
$$
- 对于 $l=n_l-1, n_l-2, n_l-3, \ldots, 2$ 的各层, 计算:
$$
\delta^{(l)}=\left(\left(W^{(l)}\right)^T \delta^{(l+1)}\right) \bullet f^{\prime}\left(z^{(l)}\right)
$$
注意:完整推导过程如下:
$$
\begin{aligned}
& \delta_i^{\left(n_{l-1}\right)}=\frac{\partial}{\partial z_i^{n_l-1}} J(W, b ; x, y)=\frac{\partial}{\partial z^{n_l}} J(W, b ; x, y) \cdot \frac{\partial z_i^{n_l}}{\partial z_i^{n_{l-1}}} \newline
& \left.=\delta_i^{\left(n_l\right)} \cdot \frac{\partial z_i^{n_l}}{\partial z_i^{n_{l-1}}}=\delta_i^{\left(n_l\right)} \cdot \frac{\partial}{\partial z_i^{n_{l-1}}} \sum_{j=1}^{s_{l-1}} W_{j i}^{n_l-1} f\left(\sum_i^{n_l-1}\right)\right)=\left(\sum_{j=1}^{s_{l-1}} W_{j i}^{n_l-1} \delta_i^{\left(n_l\right)}\right) f^{\prime}\left(z_i^{n_l-1}\right) \
&
\end{aligned}
$$
根据递推过程, 将 $n_l-1$ 与 $n_l$ 的关系替换为 $l$ 与 $l+1$ 的关系, 可以得到上面的结果:
$$
\delta_i^{(l)}=\left(\sum_{j=1}^{s_{l+1}} W_{j i}^{(l)} \delta_j^{(l+1)}\right) f^{\prime}\left(z_i^{(l)}\right)
$$
-
计算最终需要的偏导数值:
$$
\begin{aligned}
\nabla_{W^{(l)}} J(W, b ; x, y) & =\delta^{(l+1)}\left(a^{(l)}\right)^T, \newline
\nabla_{b^{(l)}} J(W, b ; x, y) & =\delta^{(l+1)} .
\end{aligned}
$$注意:
- 在以上的第2步和第3步中, 我们需要为每一个 $i$ 值计算其 $f^{\prime}\left(z_i^{(l)}\right)$ 。假设 $f(z)$ 是sigmoid函数, 并且我们已经在前向传导运算中得到了 $a_i^{(l)}$ 。那么, 使用我们早先推导出的 $f^{\prime}(z)$ 表达式, 就可以计算得到 $f^{\prime}\left(z_i^{(l)}\right)=a_i^{(l)}\left(1-a_i^{(l)}\right)^{\circ}$
- 我们使用 “$\cdot$” 表示向量乘积运算符(在Matlab或Octave里用 “.*”表示, 也称作阿达马乘积) 。若 $a=b \bullet c$, 则 $a_i=b_i c_i$ 。
下面我们实现批量梯度下降法中的一次迭代:
- 对于所有 $l$, 令 $\Delta W^{(l)}:=0, \Delta b^{(l)}:=0$ (设置为全零矩阵或全零向量)
- 对于 $i=1$ 到 $m$,
a. 使用反向传播算法计算 $\nabla_{W^{(l)}} J(W, b ; x, y)$ 和 $\nabla_{b^{(l)}} J(W, b ; x, y)$ 。
b. 计算 $\Delta W^{(l)}:=\Delta W^{(l)}+\nabla_{W^{(l)}} J(W, b ; x, y)$ 。
c. 计算 $\Delta b^{(l)}:=\Delta b^{(l)}+\nabla_{b^{(l)}} J(W, b ; x, y)$ 。 - 更新权重参数:
$$
\begin{aligned}
W^{(l)} & =W^{(l)}-\alpha\left[\left(\frac{1}{m} \Delta W^{(l)}\right)+\lambda W^{(l)}\right] \newline
b^{(l)} & =b^{(l)}-\alpha\left[\frac{1}{m} \Delta b^{(l)}\right]
\end{aligned}
$$
现在, 我们可以重复梯度下降法的迭代步骤来减小代价函数 $J(W, b)$ 的值, 进而求解我们的神经网络。
向量化
为什么需要向量化?
因为使用非向量化的实现,算法实现速度很慢,而向量化实现将会非常直接进行计算。例如计算$W^T x$,代码如下:
z=np.dot(w,x)+b
如何在深度学习中使用向量化?
首先我们回顾一下逻辑回归的前向传播步骤。
-
如果你有 $m$ 个训练样本,然后对第一个样本进行预测,你需要这样计算。计算 $z$ ,我正在使用这个熟悉的公式 $z^{(1)}=w^T x^{(1)}+b$然后计算激活函数 $a^{(1)}=\sigma\left(z^{(1)}\right)$ ,计算第一个样本的预测值 $y$ 。
-
然后对第二个样本进行预测,你需要计算 $z^{(2)}=w^T x^{(2)}+b , a^{(2)}=\sigma\left(z^{(2)}\right)$ 。然后对第三个样本进行预测,你需要计算 $z^{(3)}=w^T x^{(3)}+b , a^{(3)}=\sigma\left(z^{(3)}\right)$ ,依次类推。
-
如果你有 $m$ 个训练样本,你可能需要这样做 $m$ 次,可以看出,为了完成前向传播步骤,即对我们的 $m$ 个样本都计算出预测值。有一个办法可以并且不需要任何一个明确的for循环:
-
首先,回忆一下我们曾经定义了一个矩阵 $X$ 作为你的训练输入,像这样在不同的列中堆积在一起。这是一个 $n_x$ 行 $m$ 列的矩阵。我现在将它写为Python numpy的形式 $\left(n_x, m\right)$ ,这只是表示 $X$ 是一个 $n_x$ 乘以 $m$ 的矩阵 $R^{n_x \times m}$ 。
-
我打算先构建一个 $1 \times m$ 的矩阵,实际上它是一个行向量,同时我准备计算 $z^{(1)} , z^{(2)}$ … 一直到 $z^{(m)}$ ,所有值都是在同一时间内完成。结果发现它可以表达为 $w$ 的转置乘以大写矩阵 $x$然后加上向量 $[b b \ldots b] ,\left(\left[z^{(1)} z^{(2)} \ldots z^{(m)}\right]=w^T+[b b \ldots b]\right)$ 。 $[b b \ldots b]$ 是一个 $1 \times m$ 的向量或者 $1 \times m$ 的矩阵或者是一个 $m$ 维的行向量。所以希望你熟悉矩阵乘法,你会发现的 $w$ 转置乘以 $x^{(1)} , x^{(2)}$ 一直到 $x^{(m)}$ 。
所以 $w$ 转置可以是一个行向量。所以第一项 $w^T X$ 将计算 $w$ 的转置乘以 $x^{(1)} , w$ 转置乘以 $x^{(2)}$ 等等。然后我们加上第二项 $[b b \ldots b]$ ,你最终将 $b$ 加到了每个元素上。所以你最终得到了另一个 $1 \times m$ 的向量:
$$
\left[z^{(1)} z^{(2)} \ldots z^{(m)}\right]=w^T X+[b b \ldots b]=\left[w^T x^{(1)}+b, w^T x^{(2)}+b \ldots w^T x^{(m)}+b\right]
$$
那么用numpy命令是Z = np.dot(w.T, X) + b
。注意:python中有一个巧妙的地方,这里 $b$ 是一个实数,或者说是一个 $1 \times m$ 的行向量 。但是当你将这个向量加上这个实数时,Python自动把这个实数 $b$ 扩展成一个 $1 \times m$ 的行向量,它在Python中被称作广播(brosdcasting)
-
向量化如何在逻辑回归的梯度输出中使用?
前向传播步骤:
(1) $z^{[l]}=W^{[l]} \cdot a^{[l-1]}+b^{[l]}$
(2) $a^{[l]}=g^{[l]}\left(z^{[l]}\right)$
向量化实现过程:
(1) $z^{[l]}=W^{[l]} \cdot A^{[l-1]}+b^{[l]}$
(2) $A^{[l]}=g^{[l]}\left(Z^{[l]}\right)$
所以反向传播的步骤可以写成:
(1) $d z^{[l]}=d a^{[l]} * g^{[l]^{\prime}}\left(z^{[l]}\right)$
(2) $d w^{[l]}=d z^{[l]} \cdot a^{[l-1]}$
(3) $d b^{[l]}=d z^{[l]}$
(4) $d a^{[l-1]}=w^{[l] T} \cdot d z^{[l]}$
(5) $d z^{[l]}=w^{[l+1] T} d z^{[l+1]} \cdot g^{[l]^{\prime}}\left(z^{[l]}\right)$
式子 (5) 由式子 (4) 带入式子 (1) 得到,前四个式子就可实现反向函数。
向量化实现过程可以写成:
(6) $d Z^{[l]}=d A^{[l]} * g^{[l]^{\prime}}\left(Z^{[l]}\right)$
(7) $d W^{[l]}=\frac{1}{m} d Z^{[l]} \cdot A^{[l-1] T}$
(8) $d b^{[l]}=\frac{1}{m}$ np. $\operatorname{sum}\left(d z^{[l]}\right.$, axis $=1$, keepdims $=$ True $)$
(9) $d A^{[l-1]}=W^{[l] T} \cdot d Z^{[l]}$
什么是numpy中的广播机制?
在进行广播时,NumPy会自动调整数组的形状,使得它们具有相容的维度。具体来说,广播机制遵循以下规则:
- 如果两个数组的维度数不同,那么维度较少的数组会在其前面补1,直到维度数相同。
- 如果两个数组在某个维度上的大小不同,但其中一个数组的大小为1,那么可以将该数组在该维度上进行扩展,使得维度大小相同。
- 如果两个数组在某个维度上的大小既不相等,也不为1,那么会引发错误,表示无法进行广播。
神经网络概述
例如一个层数为2的神经网络,整个计算过程如下:
$$
\left.\begin{array}{r}
x \
W^{[1]} \
b^{[1]}
\end{array}\right} \Longrightarrow z^{[1]}=W^{[1]} x+b^{[1]} \Longrightarrow a^{[1]}=\sigma\left(z^{[1]}\right)
$$
类似逻辑回归,接下来需要使用另一个线性方程对应的参数计算 $z^{[2]}$ ,计算 $a^{[2]}$ ,此时 $a^{[2]}$ 就是整个神经网络的输出,用 $\hat{y}$ 表示网络的输出:
$$
\begin{aligned}
& \left.\begin{array}{r}
a^{[1]}=\sigma\left(z^{[1]}\right) \
W^{[2]} \
b^{[2]}
\end{array}\right} \Longrightarrow z^{[2]}=W^{[2]} a^{[1]}+b^{[2]} \Longrightarrow a^{[2]}=\sigma\left(z^{[2]}\right) \
& \Longrightarrow L\left(a^{[2]}, y\right) \
&
\end{aligned}
$$
同时也需要进行反向传播来更新参数,从右到左反向计算:
$$
\begin{aligned}
& \left.\begin{array}{r}
d a^{[1]}=d \sigma\left(z^{[1]}\right) \
d W^{[2]} \
d b^{[2]}
\end{array}\right} \Longleftarrow d z^{[2]}=d\left(W^{[2]} \alpha^{[1]}+b^{[2]}\right) \Longleftarrow d a^{[2]}=d \sigma\left(z^{[2]}\right) \
& \Longleftarrow d L\left(a^{[2]}, y\right) \
&
\end{aligned}
$$
神经网络表示
这是一张神经网络的图片,让我们给此图的不同部分取一些名字:
-
输入特征 $x_1, x_2, x_3$,被称为神经网络的输入层。
如同我们使用向量 $X$ 表示输入特征一样,有一个可代替的记号 $a^{[0]}$ 可以用来表示输入特征,它意味着网络中不同层的值会传递到它们后面的层中:
-
输入层将 $x$ 传递给隐藏层,所以我们将输入层的激活值称为 $a^{[0]}$;
-
下一层即隐藏层也同样会产生一些激活值,那么我将其记作 $a^{[1]}$ ,所以具体地,这里的第一个单元或结点我们将其表示为 $a_1^{[1]}$ ,第二个结点的值我们记为 $a_2^{[1]}$ 以此类推。
-
所以这里的是一个四维的向量如果写成Python代码,那么它是一个规模为 $4 \times 1$ 的矩阵或一个大小为 4 的列向量,如下公式,它是四维的,因为在本例中,我们有四个结点或者单元,或者称为四个隐藏层单元:
$$
a^{[1]}=\left[\begin{array}{c}
a_1^{[1]} \
a_2^{[1]} \
a_3^{[1]} \
a_4^{[1]}
\end{array}\right]
$$ -
最后输出层将产生某个数值 $a$,它只是一个单独的实数,所以 $\hat{y}$ 值将取为 $a^{[2]}$。
-
-
中间的层被称为神经网络的隐藏层,原因是在你使用监督学习训练一个神经网络的时候,训练集包含了输入也包含了目标输出,所以术语隐藏层的含义是在训练集中,这些中间结点的准确值我们是不知道的,也就是说你看不见它们在训练集中应具有的值。
-
最后一层称为神经网络的输出层,它负责产生预测值。
注意:
-
图中的神经网络只能称为是一个两层的神经网络,原因是当我们计算网络的层数时,输入层是不算入总层数内,所以隐藏层是第一层,输出层是第二层。
-
我们将输入层称为第零层,所以在技术上,这仍然是一个三层的神经网络,因为这里有输入层、隐藏层,还有输出层。
-
隐藏层以及最后的输出层是带有参数的,这里的隐藏层将拥有两个参数 $W$ 和 $b$ ,我将给它们加上上标 ${ }^{[1]}\left(W^{[1]}, b^{[1]}\right)$ ,表示这些参数是和第一层这个隐藏层有关系的。
之后在这个例子中我们会看到 $W$ 是一个 $4 \times 3$ 的矩阵,而 $b$ 是一个 $4 \times 1$ 的向量,第一个数字 4 源自于我们有四个结点或隐藏层单元,然后数字 3 源自于这里有三个输入特征;
类似的输出层也有一些与之关联的参数 $W^{[2]}$ 以及 $b^{[2]}$ 。从维数上来看,它们的规模分别是 $1 \times 4$ 以及 $1 \times 1$ 。 $1 \times 4$ 是因为隐藏层有四个隐藏层单元而输出层只有一个单元。
-
神经网络的计算
逻辑回归的计算有两个步骤:
- 按步骤计算出 $z$
- 以sigmoid函数为激活函数计算 $z$(得出 $a$)
神经网络只是如此做了很多重复计算,如图所示:
向量化计算
向量化的过程是将神经网络中的一层神经元参数纵向堆积起来,例如隐藏层中的 $w$ 纵向堆积起来变成一个 (4,3) 的矩阵,用符号 $W^{[1]}$ 表示。因此公式改为:
- $z^{[n]}=w^{[n]} x+b^{[n]}$
- $a^{[n]}=\sigma\left(z^{[n]}\right)$
即
$$
\left[\begin{array}{c}
z_1^{[1]} \
z_2^{[1]} \
z_3^{[1]} \
z_4^{[1]}
\end{array}\right]=\overbrace{\left[\begin{array}{c}
\ldots W_1^{[1] T} \ldots \
\ldots W_2^{[1] T} \ldots \
\ldots W_3^{[1] T} \ldots \
\ldots W_4^{[1] T} \ldots
\end{array}\right]}^{W^{[1]}} * \overbrace{\left[\begin{array}{c}
x_1 \
x_2 \
x_3
\end{array}\right]}^{\text {input }}+\overbrace{\left[\begin{array}{c}
b_1^{[1]} \
b_2^{[1]} \
b_3^{[1]} \
b_4^{[1]}
\end{array}\right]}^{b^{[1]}}
$$
如果要加入更多样本,只需向矩阵X中加入更多列:
$$
\begin{aligned}
& W^{[1]} x=\left[\begin{array}{l}
\cdots \
\cdots \
\cdots
\end{array}\right]\left[\begin{array}{cccc}
\vdots & \vdots & \vdots & \vdots \
x^{(1)} & x^{(2)} & x^{(3)} & \vdots \
\vdots & \vdots & \vdots & \vdots
\end{array}\right]=\left[\begin{array}{cccc}
\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}{cccc}
\vdots & \vdots & \vdots & \vdots \
z^{1} & z^{1} & z^{1} & \vdots \
\vdots & \vdots & \vdots & \vdots
\end{array}\right]=Z^{[1]}} \
&
\end{aligned}
$$
即当有不同的训练样本时,将它们堆到矩阵 $X$ 的各列中,那么它们的输出也会相应地堆叠到矩阵 $Z^{[1]}$ 的各列中;之后就可以直接计算矩阵 $Z^{[1]}$ 加上 $b^{[1]}$,因为它们的列向量具有相同的尺寸,Python的广播机制可以处理这种矩阵与向量直接相加的情况,最终得到公式 $Z^{[n]}=W^{[n]} x+B^{[n]}$
激活函数
有些激活函数比sigmoid激活函数效果更好,例如tanh函数或者双曲正切函数是总体上都优于sigmoid函数的激活函数。
如图, $a=\tan (z)$ 的值域是位于+1和-1之间,公式为: $a=\tanh (z)=\frac{e^z-e^{-z}}{e^z+e^{-z}}$
事实上,tanh函数是sigmoid的向下平移和伸缩后的结果。对它进行了变形后,穿过了零点,并且值域介于+1和-1之间。
tanh函数的优势在于其函数值域在-1和+1的激活函数,其均值是更接近零均值的,在训练一个算法模型时,如果使用tanh函数代替sigmoid函数中心化数据,使得数据的平均值更接近0而不是0.5。
注意:有一个例外,在在二分类的问题中,对于输出层,因为 $y$ 的值是0或1,所以想让 $\hat{y}$ 的数值介于0和1之间,而不是在-1和+1之间,需要使用sigmoid激活函数。
sigmoid函数和tanh函数两者共同的缺点
在 $z$ 特别大或者特别小的情况下,导数的梯度或者函数的斜率会变得特别小,最后就会接近于0,导致降低梯度下降的速度。
修正线性单元的函数(ReLu)
公式:$a=\max (0, z)$
只要 $z$ 是正值的情况下,导数恒等于1;当 $z$ 是负值的时候,导数恒等于0;$z=0$ 的导数是没有定义的,如果有这种情况出现,假设其导数为1或者0的效果都可以。
ReLu函数相比较sigmoid函数和tanh函数的优点是:当 $z$ 是负值的时候,导数等于0。
也有另一个版本的ReLu被称为Leaky ReLu:
当是 $z$ 负值时,这个函数的值不是等于0,而是轻微的倾斜,如上图所示。这个函数通常比ReLu激活函数效果要好,尽管在实际中Leaky ReLu使用的并不多。
ReLu函数和Leaky ReLu函数两者共同的优点
- 在 $z$ 的区间变动很大的情况下,激活函数的导数或者激活函数的斜率都会远大于0,在程序实现就是一个if-else语句,而sigmoid函数需要进行浮点四则运算,在实践中,使用ReLu激活函数神经网络通常会比使用sigmoid或者tanh激活函数学习的更快。
- sigmoid和tanh函数的导数在正负饱和区的梯度都会接近于0,这会造成梯度弥散,而Relu和Leaky ReLu函数大于0部分都为常数,不会产生梯度弥散现象。(同时应该注意到的是,Relu进入负半区的时候,梯度为0,神经元此时不会训练,产生所谓的稀疏性,而Leaky ReLu不会有这问题)。
- $z$ 在ReLu的梯度一半都是0,但是,有足够的隐藏层使得z值大于0,所以对大多数的训练数据来说学习过程仍然可以很快。
选择激活函数的经验法则
- 如果输出是0、1值(二分类问题),则输出层选择sigmoid函数,然后其它的所有单元都选择Relu函数。
- 如果在隐藏层上不确定使用哪个激活函数,那么通常会使用Relu激活函数。有时,也会使用tanh激活函数。
总结
sigmoid激活函数:除了输出层是一个二分类问题基本不会用它。
tanh激活函数:tanh是非常优秀的,几乎适合所有场合。
ReLu激活函数:最常用的默认函数,,如果不确定用哪个激活函数,就使用ReLu或者Leaky ReLu。
为什么需要非线性函数?
因为如果使用线性激活函数或者叫恒等激励函数,那么神经网络只是把输入线性组合再输出。即无论你的神经网络有多少层一直在做的只是计算线性函数,所以不如直接去掉全部隐藏层。
唯一可以用线性激活函数的通常就是输出层,除了这种情况,会在隐藏层使用的线性函数的是一些特殊情况,例如压缩等相关。除此之外,在隐藏层使用线性激活函数非常少见。
激活函数的导数
在神经网络中使用反向传播时,需要计算激活函数的斜率或者导数。对于常见的四种激活函数,求其导数如下:
-
sigmoid activation function
求导公式: $\frac{d}{d z} g(z)=\frac{1}{1+e^{-z}}\left(1-\frac{1}{1+e^{-z}}\right)=g(z)(1-g(z))$
注意:
- 当 $z=10$ 或 $z=-10 ; \frac{d}{d z} g(z) \approx 0$
- 当 $z=0 , \frac{d}{d z} g(z)=\mathrm{g}(\mathrm{z})(1-\mathrm{g}(\mathrm{z}))=1 / 4$
- 在神经网络中 $a=g(z) ; g(z)^{\prime}=\frac{d}{d z} g(z)=a(1-a)$
-
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}{d z} g(z) \approx 0$
- 当 $z=0, \frac{d}{d z} g(z)=1-(0)=1$
-
Rectified Linear Unit (ReLU)
$$
g(z)=\max (0, z)
$$
$$
g(z)^{\prime}= \begin{cases}0 & \text { if } \mathrm{z}<0 \ 1 & \text { if } \mathrm{z}>0 \ \text { undefined } & \text { if } \mathrm{z}=0\end{cases}
$$注意:通常在 $z=0$ 的时候给定其导数 1 或 0 ;当然 $z=0$ 的情况很少。
-
Leaky linear unit (Leaky ReLU)
$$
g(z)=\max (0.01z, z)
$$$$
g(z)^{\prime}= \begin{cases}0.01 & \text { if } \mathrm{z}<0 \ 1 & \text { if } \mathrm{z}>0 \ \text { undefined } & \text { if } \mathrm{z}=0\end{cases}
$$注意:通常在 $z=0$ 的时候给定其导数 1 或 0.01 ;当然 $z=0$ 的情况很少。
随机初始化
对于逻辑回归,把权重初始化为0当然也是可以的。但是对于一个神经网络,如果你把权重或者参数都初始化为0,那么梯度下降将不会起作用。
原因:例如将偏置项 $b$ 初始化为0是合理的,但是将 $w$ 初始化为0就有问题。因为如此初始化,你会发现输入层的下一层的隐藏层的激活值都是相等的,与此同时由于隐藏层计算相同的函数,当你做反向传播时,这会导致 $\mathrm{dz}_1^{[1]}$ 和 $\mathrm{dz}_2^{[1]}$ 也会一样,对称这些隐含单元会初始化得一样,这样输出的权值也会一模一样,由此 $W^{[2]}$ 等于 [0 0] 。
所以应当将 $W$ 随机初始化,在Python中应该这么做:
- 将 $W^{[1]}}$ 设为
np.random.randn(2,2)
(生成高斯分布) - 通常再乘以一个小的数,例如0.01,这样就将 $W^{[1]}}$ 初始化为很小的随机数
- $b$ 没有这个对称的问题(symmetry breaking problem),所以可以将 $b$ 初始化为0
为什么要初始化为很小的随机数?
因为如果你用tanh或者sigmoid激活函数,或者说只在输出层有一个Sigmoid,如果(数值)波动太大,当你计算激活值时如果 $W$ 很大,$z$ 就会很大或者很小,因此这种情况算法可能停在tanh/sigmoid函数的平坦的地方。这些地方梯度很小,也就意味着梯度下降会很慢,因此学习也很慢。
如果 $w$ 很大,那么你很可能最终停在(甚至在训练刚刚开始的时候)很大的值,这会造成tanh/Sigmoid激活函数饱和在龟速的学习上。如果你做二分类并且你的输出单元是Sigmoid函数,那么你不会想让初始参数太大,因此这就是为什么乘上0.01或者其他一些小数是合理的尝试。对于 $W^{[2]}}$ 一样,就是np.random.randn((1,2))
,我猜会是乘以0.01。
核对矩阵的维数
$w$ 的维度是 (下一层的维数,前一层的维数),即 $w^{[l]}:\left(n^{[l]}, n^{[l-1]}\right)$ ;
$b$ 的维度是 (下一层的维数,1),即:
$b^{[l]}:\left(n^{[l]}, 1\right)$;
$z^{[l]}, a^{[l]}:\left(n^{[l]}, 1\right)$;
$d w^{[l]}$ 和 $w^{[l]}$ 维度相同, $d b^{[l]}$ 和 $b^{[l]}$ 维度相同,且 $w$ 和 $b$ 向量化维度不变,但 $z, a$ 以及 $x$ 的维度会向量化后发生变化。
向量化后:
$Z^{[l]}$ 可以看成由每一个单独的 $Z^{[l]}$ 叠加而得到, $Z^{[l]}=\left(z^{[l][1]}, z^{[l][2]}, z^{[l][3]} , \ldots, z^{[l][m]}\right)$ , $m$ 为训练集大小,所以 $Z^{[l]}$ 的维度不再是 $\left(n^{[l]}, 1\right)$ ,而是 $\left(n^{[l]}, m\right)$ 。
$$
A^{[l]}:\left(n^{[l]}, m\right), A^{[0]}=X=\left(n^{[l]}, m\right)
$$
训练,验证,测试集
小数据量:70%验证集+30%测试集或60%训练+20%验证+20%测试集
大数据量:98%训练集+1%验证集+1%测试集
-
尽量确保验证集和测试集的数据来自同一分布。
-
就算没有测试集也不要紧,测试集的目的是对最终所选定的神经网络系统做出无偏估计,如果不需要无偏估计,也可以不设置测试集。
所以如果只有验证集,没有测试集,我们要做的就是,在训练集上训练,尝试不同的模型框架,在验证集上评估这些模型,然后迭代并选出适用的模型。因为验证集中已经涵盖测试集数据,其不再提供无偏性能评估。当然,如果你不需要无偏估计,那就再好不过了。
偏差和方差
- 如果给这个数据集拟合一条直线,可能得到一个逻辑回归拟合,但它并不能很好地拟合该数据,这是高偏差(high bias)的情况,我们称为“欠拟合”(underfitting)。
- 相反的如果我们拟合一个非常复杂的分类器,比如深度神经网络或含有隐藏单元的神经网络,可能就非常适用于这个数据集,但是这看起来也不是一种很好的拟合方式分类器方差较高(high variance),数据过度拟合(overfitting)。
- 在两者之间,可能还有一些像图中这样的,复杂程度适中,数据拟合适度的分类器,这个数据拟合看起来更加合理,我们称之为“适度拟合”(just right)是介于过度拟合和欠拟合中间的一类。
例如:
- 假设训练集误差是15%,验证集误差是16%,人的错误率几乎为0%,那么此算法并没有在训练集中得到很好训练,训练数据的拟合度不高,可以说这种算法偏差高。相反,它对于验证集产生的结果却是合理的,验证集中的错误率只比训练集的多了1%。
- 假设训练集误差是15%,但是错误率达到30%,那么此算法偏差高,因为他在训练集上结果不理想,而且方差也很高,这是方差偏差都很糟糕的情况。
只要正则适度,通常构建一个更大的网络便可以,在不影响方差的同时减少偏差,而采用更多数据通常可以在不过多影响偏差的同时减少方差。
正则化
高方差的解决方法:
- 正则化
- 准备更多数据
在逻辑回归函数中加入正则化:
-
$L2$ 正则化:只需在成本函数J的后加上 $\frac{\lambda}{2 m}$ 乘以 $w$ 范数的平方,此方法称为 $L2$ 正则化。
-
$L1$ 正则化:是正则项 $\frac{\lambda}{m}$ 乘以 $\sum_{j=1}^{n_x}|w|$,$\sum_{j=1}^{n_x}|w|$ 也被称为参数 $w$ 向量的 $L1$ 范数,无论分母是m还是2m,它都是一个比例常量。
如果用的是 $L1$ 正则化,$w$ 最终会是稀疏的,也就是说 $w$ 向量中很多0,有人说这样有利于压缩模型,因为集合中参数均为0,存储模型所占用的内存更少。实际上,虽然 $L1$ 正则化使模型变得稀疏,却没有降低太多存储内存,所有我认为这并不是 $L1$ 正则化的目的,至少不是为了压缩模型,人们在训练网络,越来越倾向于使用 $L2$ 正则化。
$λ$ 是正则化参数,我们通常使用验证集或交叉验证集来配置这个参数,我们要考虑训练集之间的权衡,把参数设置为较小值,这样可以避免过拟合。
使用该范数实现梯度下降呢?
用backprop计算出 $d W$ 的值,backprop会给出 $J$ 对 $W$ 的偏导数,实际上是 $W^{[l]}$ ,把 $W^{[l]}$ 替换为 $W^{[l]}$ 减去学习率乘以 $d W$ 。
既然已经增加了这个正则项,现在我们要做的就是给 $d W$ 加上这一项 $\frac{\lambda}{m} W^{[l]}$,然后计算这个更新想,使用新定义的 $dW^{[l]}$,这也是 $L2$ 正则化有时被称为“权重衰减”的原因:
实际上,相当于我们给矩阵 $W$ 乘以 $\left(1-a \frac{\lambda}{m}\right)$ 倍的权重,也就是用这个系数 $\left(1-a \frac{\lambda}{m}\right)$ 乘以矩阵 $W$,该系数小于1,所以被称为“权重衰减”。
为什么正则化有利于预防过拟合呢?
直观上理解就是如果正则化 $λ$ 设置得足够大,权重矩阵 $W$ 被设置为接近于0的值,直观理解就是把多隐藏单元的权重设为0,于是基本上消除了这些隐藏单元的许多影响。
如果是这种情况,这个被大大简化了的神经网络会变成一个很小的网络,小到如同一个逻辑回归单元,可是深度却很大,它会使这个网络从过度拟合的状态更接近左图的高偏差状态。
但是 $λ$ 会存在一个中间值,于是会有一个接近“Just Right”的中间状态。
直观理解就是 $λ$ 增加到足够大,$W$ 会接近于0,实际上是不会发生这种情况的,我们尝试消除或至少减少许多隐藏单元的影响,最终这个网络会变得更简单,这个神经网络越来越接近逻辑回归,我们直觉上认为大量隐藏单元被完全消除了,其实不然,实际上是该神经网络的所有隐藏单元依然存在,但是它们的影响变得更小了。
所以神经网络变得更简单了,貌似这样更不容易发生过拟合,因此不确定这个直觉经验是否有用,不过在编程中执行正则化时,你实际看到一些方差减少的结果。
从图像上来看:
假设我们用的是这样的双曲线激活函数:
用 $g(z)$ 表示 $\tanh (z)$,那么我们发现,只要 $z$ 非常小,如果 $z$ 只涉及少量参数,这里我们利用了双曲正切函数的线性状态,只要 $z$ 可以扩展为这样的更大值或者更小值,激活函数开始变得非线性。
即正则化参数 $λ$ 很大,激活函数的参数会相对较小,因为代价函数中的参数变大了,如果 $W$ 很小,相对 $z$ 也会很小,如上图所示,$g(z)$ 大致呈线性,每层几乎都是线性的,和线性回归函数一样。
注意:如果每层都是线性的,那么整个网络就是一个线性网络,即使是一个非常深的深层网络,因具有线性激活函数的特征,最终我们只能计算线性函数。
因此,它不适用于非常复杂的决策,以及过度拟合数据集的非线性决策边界,如同我们在幻灯片中看到的过度拟合高方差的情况。
可以看到,使用这样的代价函数对于梯度下降的每个调幅都单调递减。
dropout正则化
除了 $L2$ 正则化,还有一个非常实用的正则化方法——" Dropout(随机失活)"
dropout会遍历网络的每一层,并设置消除神经网络中节点的概率,假设网络中的每一层,每个节点都以抛硬币的方式设置概率,每个节点得以保留和消除的概率都是0.5,设置完节点概率,我们会消除一些节点,然后删除掉从该节点进出的连线,最后得到一个节点更少,规模更小的网络,然后用backprop方法进行训练。
如何实施dropout?
最常用的方法inverted dropout(反向随机失活):
首先要定义向量 $d, d^{[3]}$ 表示一个三层的dropout向量:
d3 = np.random.rand(a3.shape[0],a3.shape[1])
-
然后看它是否小于某数,我们称之为keep-prob,keep-prob是一个具体数字,上个示例中它是 0.5 ,而本例中它是 0.8 ,它表示保留某个隐藏单元的概率,此处keep-prob等于 0.8 ,它意味着消除任意一个隐藏单元的概率是 0.2 ,它的作用就是生成随机矩阵,如果对 $a^{[3]}$ 进行因子分解,效果也是一样的。
$d^{[3]}$ 是一个矩阵,每个样本和每个隐藏单元,其中 $d^{[3]}$ 中的对应值为 1 的概率都是 0.8 ,对应为 0 的概率是 0.2 , 随机数字小于 0.8即它等于 1 的概率是 0.8 ,等于 0 的概率是 0.2 。
-
接下来要做的就是从第三层中获取激活函数,这里我们叫它 $a^{[3]} , a^{[3]}$ 含有要计算的激活函数, $a^{[3]}$等于上面的 $a^{[3]}$ 乘以 $d^{[3]}$ ,
a3 =np.multiply(a3,d3)
,这里是元素相乘,也可写为 $a 3 *=d 3$ ,它的作用就是让 $d^{[3]}$ 中所有等于 0 的元素 (输出),而各个元素等于 0 的概率只有 $20 %$ ,乘法运算最终把 $d^{[3]}$ 中相应元素输出,即让 $d^{[3]}$ 中0元素与 $a^{[3]}$ 中相对元素归零。注意:在Python中实现该算法的话,$d^{[3]}$则是一个布尔型数组,值为true和false,而不是1和0,乘法运算依然有效,Python会把true和false翻译为1和0。
-
最后,我们向外扩展$d^{[3]}$,用它除以0.8,或者除以keep-prob参数
下面我解释一下为什么要这么做,为方便起见,我们假设第三隐藏层上有 50 个单元或 50 个神经元,在一维上 $a^{[3]}$ 是 50 ,我们通过因子分解将它拆分成 $50 \times m$ 维的,保留和删除它们的概率分别为 $80 %$ 和 $20 %$ ,这意味着最后被删除或归零的单元平均有 $10(50 \times 20 %=10)$ 个,现在我们看下 $z^{[4]}$ , $z^{[4]}=w^{[4]} a^{[3]}+b^{[4]}$ ,我们的预期是, $a^{[3]}$ 减少 $20 %$ ,也就是说 $a^{[3]}$ 中有 $20 %$ 的元素被归零,为了不影响 $z^{[4]}$ 的期望值,我们需要用 $w^{[4]} a^{[3]} / 0.8$ ,它将会修正或弥补我们所需的那 $20 % , a^{[3]}$ 的期望值不会变。
在测试阶段时,我们不期望输出结果是随机的,如果测试阶段应用dropout函数,预测会受到干扰。理论上,你只需要多次运行预测处理过程,每一次,不同的隐藏单元会被随机归零,预测处理遍历它们,但计算效率低,得出的结果也几乎相同,与这个不同程序产生的结果极为相似。
Inverted dropout函数在除以keep-prob时可以记住上一步的操作,目的是确保即使在测试阶段不执行dropout来调整数值范围,激活函数的预期结果也不会发生变化,所以没必要在测试阶段额外添加尺度参数,这与训练阶段不同。
为什么dropout可以通过正则化发挥如此大的作用呢?
不要依赖于任何一个特征,因为该单元的输入可能随时被清除,因此该单元通过这种方式传播下去,并为单元的四个输入增加一点权重,通过传播所有权重,dropout将产生收缩权重的平方范数的效果,和之前讲的 $L2$ 正则化类似:
- 实施dropout的结果实它会压缩权重,并完成一些预防过拟合的外层正则化。
- $L2$ 对不同权重的衰减是不同的,却取决于激活函数倍增的大小。
总结一下,dropout的功能类似于 $L2$ 正则化,与 $L2$ 正则化不同的是应用方式不同会带来一点点小变化,甚至更适用于不同的输入范围。
- 因为某单元的输入可能被随时清除,所以我们不愿给任何一个输入加上太多权重,因为它可能会被删除,因此该单元将通过这种方式积极地传播开,并为单元的每个输入增加一点权重。
- 如果担心在某些层比其它层更容易发生过拟合,可以把某些层的keep-prob值设置得比其它层更低,缺点是为了使用交叉验证,你要搜索更多的超级参数。
- 另一种方案是在一些层上应用dropout,而有些层不用dropout,应用dropout的层只含有一个超级参数,就是keep-prob。
牢记:dropout是一种正则化方法,它有助于预防过拟合,因此除非算法过拟合,不然我是不会使用dropout的
缺点:代价函数 $J$ 不再被明确定义,每次迭代,都会随机移除一些节点,如果再三检查梯度下降的性能,实际上是很难进行复查的。定义明确的代价函数每次迭代后都会下降,因为我们所优化的代价函数实际上并没有明确定义,或者说在某种程度上很难计算
其他正则化方法
-
数据扩增
例如水平翻转图片,将其添加到训练集;随意裁剪图片,可以将原图旋转并随意放大后裁剪。
-
early stopping
验证集误差通常会先呈下降趋势,然后在某个节点处开始上升,而early stopping的作用是我们在验证集误差上升时停止训练,得到验证集误差。
原因是因为还未在神经网络上运行太多迭代过程的时候,参数 $w$ 接近0,因为随机初始化 $w$ 值时,它的值可能都是较小的随机值,所以在你长期训练神经网络之前 $w$ 依然很小,在迭代过程和训练过程中 $w$ 的值会变得越来越大。所以我们应该在中间点停止迭代过程,得到一个 $w$ 值中等大小的弗罗贝尼乌斯范数。
缺点:early stopping是不能独立处理两个问题:一是不希望出现过拟合,二是因为提早停止梯度下降,也就停止了优化代价函数 $J$,所以代价函数 $J$ 的值可能不够小。
优点:只运行一次梯度下降,你可以找出 $W$ 的较小值,中间值和较大值,而无需尝试 $L2$ 正则化超级参数 $λ$ 的很多值。
归一化输入
训练神经网络,其中一个加速训练的方法就是归一化输入。假设一个训练集有两个特征,输入特征为2维,归一化需要两个步骤:
-
零均值
第一步是零均值化, $\mu=\frac{1}{m} \sum_{i=1}^m x^{(i)}$ ,它是一个向量, $x$ 等于每个训练数据 $x$ 减去 $\mu$ ,意思是移动训练集,直到它完成零均值化。
-
归一化方差
第二步是归一化方差,注意特征 $x_1$ 的方差比特征 $x_2$ 的方差要大得多,我们要做的是给 $\sigma$ 赋值, $\sigma^2=\frac{1}{m} \sum_{i=1}^m\left(x^{(i)}\right)^2$ ,这是节点 $y$ 的平方, $\sigma^2$ 是一个向量,它的每个特征都有方差。
注意:我们已经完成零值均化, $\left(x^{(i)}\right)^2$ 元素 $y^2$ 就是方差,我们把所有数据除以向量 $\sigma^2$ ,最后变成下图形式:
提示:如果你用它来调整训练数据,那么用相同的 $\mu$ 和 $\sigma^2$ 来归一化测试集,即不论是训练数据还是测试数据,都是通过相同 $\mu$ 和 $\sigma^2$ 定义的相同数据转换,其中 $\mu$ 和 $\sigma^2$ 是由训练数据集计算得来的。
为什么要归一化?
回顾所定义的代价函数:
$$
J(w, b)=\frac{1}{m} \sum_{i=1}^m L\left(\hat{y}^{(i)}, y^{(i)}\right)
$$
如果使用非归一化的输入特征,代价函数会是这样:
如果特征值在不同范围,假如 $x_1$ 取值范围从 1 到 1000 ,特征 $x_2$ 的取值范围从 0 到 1 ,结果是参数 $w_1$ 和 $w_2$ 值的范围或比率将会非常不同,这些数据轴应该是 $w_1$ 和 $w_2$ ,但直观理解,我标记为 $w$ 和 $b$ ,代价函数就有点像狭长的碗一样。
如果你归一化特征,代价函数平均起来更对称,如果你在上图这样的代价函数上运行梯度下降法,你必须使用一个非常小的学习率。因为如果是在这个位置,梯度下降法可能需要多次迭代过程,直到最后找到最小值。但如果函数是一个更圆的球形轮廓,那么不论从哪个位置开始,梯度下降法都能够更直接地找到最小值,你可以在梯度下降法中使用较大步长,而不需要像在上图中那样反复执行。
注意:当然实际上 $w$ 是一个高维向量,所以特征在相似范围内,而不是从1到1000,0到1的范围,而是在-1到1范围内相似偏差,这使得代价函数 $J$ 优化起来更简单快速。
实际上如果假设特征 $x_1$ 范围在0-1之间, $x_2$ 的范围在 -1 到 1 之间, $x_3$ 范围在1-2之间,它们是相似范围,所以会表现得很好。
梯度消失和梯度爆炸
梯度爆炸或梯度消失的意思就是你训练神经网络的时候,导数或坡度有时会变得非常大,或者非常小,甚至于以指数方式变小,这加大了训练的难度。
假设每个权重矩阵 $W^{[l]}=\left[\begin{array}{cc}1.5 & 0 \ 0 & 1.5\end{array}\right]$ ,从技术上来讲,最后一项有不同维度,可能它就是余下的权重矩阵, $y=W^{[1]}\left[\begin{array}{cc}1.5 & 0 \ 0 & 1.5\end{array}\right]^{(L-1)} x$ ,因为我们假设所有矩阵都等于它,它是 1.5 倍的单位矩阵,最后的计算结果就是 $\hat{y} , \hat{y}$ 也就是等于 $1.5^{(L-1)} x$ 。
如果对于一个深度神经网络来说 $L$ 值较大,那么 $\hat{y}$ 的值也会非常大,实际上它呈指数级增长的,它增长的比率是 $1.5^L$ ,因此对于一个深度神经网络, $y$ 的值将爆炸式增长。
综上,如果激活函数或梯度函数与 $L$ 相关的指数增长或递减,它们的值会变得极大或极小,尤其是梯度指数小于 $L$ 时,梯度下降算法的步长会非常非常小,梯度下降算法将花费很长时间来学习。
神经网络的权重初始化
- 方法一:如果激活函数的输入特征被零均值和标准方差化,方差是1,$z$ 也会被调整到相似范围,虽没有解决问题,但确实降低了梯度消失与爆炸问题。
- 方法二:可以给参数 $w$ 添加一个乘数参数,例如 $w^{[l]}=n p \cdot$ random. $\operatorname{randn}($ shape $) * \operatorname{np} \cdot \operatorname{sqrt}\left(\frac{1}{n^{[l-1]}}\right)$
梯度检验
梯度检验是用它来调试或检验反向传播的实施是否正确:
-
将所有参数转换成一个巨大的向量数据,即将矩阵 $W$ 转换成一个向量,将所有的 $W$ 矩阵转换成向量之后,做连接运算,得到一个巨型向量 $\theta$ ,该向量表示为参数 $\theta$ 。
-
代价函数 $J$ 是所有 $W$ 和 $b$ 的函数,现在你得到了一个 $\theta$ 的代价函数 $J$ (即 $J(\theta)$ )。接着,你得到与 $W$ 和 $b$ 顺序相同的数据,你同样可以把 $d W^{[1]}$ 和 $d b^{[1]} \ldots . . . d W^{[l]}$ 和 $d b^{[l]}$ 转换成一个新的向量,用它们来初始化大向量 $d \theta$ ,它与 $\theta$ 具有相同维度。
注意: $d W^{[1]}$ 与 $W^{[1]}$ 具有相同维度, $d b^{[1]}$ 与 $b^{[1]}$ 具有相同维度。
-
回顾导数的定义:
$$
d \theta_{\text {approx }}[i]=\frac{J\left(\theta_1, \theta_2, \ldots \theta_i+\varepsilon, \ldots\right)-J\left(\theta_1, \theta_2, \ldots \theta_i-\varepsilon, \ldots\right)}{2 \varepsilon}
$$
这个值 $\left(d \theta_{\text {approx }}[i]\right)$ 应该逼近 $d \theta[i]=\frac{\partial J}{\partial \theta_i} , d \theta[i]$ 是代价函数的偏导数,然后你需要对i每个值都执行这个运算,最后得到两个向量,得到 $d \theta$ 的逼近值 $d \theta_{\text {approx }}$ ,它与 $d \theta$ 具有相同维度,它们两个与 $\theta$ 具有相同维度,你要做的就是验证这些向量是否彼此接近。 -
那么如何定义两个向量是否真的接近彼此?
一般是计算这两个向量的距离:$d \theta_{\text {approx }}[i]-d \theta[i]$ 的欧几里得范数,
注意这里 $\left(\left|d \theta_{\text {approx }}-d \theta\right|_2\right)$ 没有平方,它是误差平方之和,然后求平方根,得到欧式距离,然后用向量长度归一化,使用向量长度的欧几里得范数。分母只是用于预防这些向量太小或太大,分母使得这个方程式变成比率,我们实际执行这个方程式, $\varepsilon$可能为 $10^{-7}$ ,使用这个取值范围内的 $\varepsilon$ ,如果你发现计算方程式得到的值为 $10^{-7}$ 或更小,这就很好,这就意味着导数逼近很有可能是正确的,它的值非常小。
梯度检测注意事项
- 不要在训练中使用梯度检验,它只用于调试,因为必须使用 $W$ 和 $b$ 的反向传播来计算 $d \theta$ ,并使用反向传播来计算导数,所以梯度检验的每一个迭代过程都很慢。
- 如果算法的梯度检验失败,要检查所有项,检查每一项,并试着找出bug。
- 在实施梯度检验时,如果使用正则化,请注意正则项。如果使用了带有正则项的代价函数,在梯度检测时要带有正则项。
- 梯度检验不能与dropout同时使用,因为每次迭代过程中,dropout会随机消除隐藏层单元的不同子集,难以计算代价函数。