吴恩达机器学习第九章【Neural Networks: Learning】

吴恩达机器学习第九章【Neural Networks: Learning】

Cost Function 【代价函数】

首先引入一些便于稍后讨论的新标记方法:

假设神经网络的训练样本有 m m 个,每个包含一组输入 x x 和一组输出信号 y y L L 表示神经网络层数, S I S_I 表示每层的neuron个数( S l S_l 表示输出层神经元个数), S L S_L 代表最后一层中处理单元的个数。

将神经网络的分类定义为两种情况:二类分类和多类分类,

二类分类: S L = 0 , y = 0 o r 1 S_L=0, y=0\, or\, 1 表示哪一类;

K K 类分类: S L = k , y i = 1 S_L=k, y_i = 1 表示分到第 i i 类; ( k > 2 ) (k>2)

m = 3 , L = 4 , S L = 4 m=3,L=4,S_L=4

$ J\left(\theta \right)=-\frac{1}{m}\left[\sum_\limits{i=1}{m}{y}{(i)}\log{h_\theta({x}{(i)})}+\left(1-{y}{(i)}\right)log\left(1-h_\theta\left({x}{(i)}\right)\right)\right]+\frac{\lambda}{2m}\sum_\limits{j=1}{n}{\theta_j}^{2} $

其中 + λ 2 m j = 1 θ j 2 n +\frac{\lambda}{2m}\overset{n}{\underset{j=1}{\sum}\theta_j^2} 为正则表达式。

在逻辑回归中,我们只有一个输出变量,又称标量(scalar),也只有一个因变量 y y ,但是在神经网络中,我们可以有很多输出变量,我们的 h θ ( x ) h_\theta(x) 是一个维度为 K K 的向量,并且我们训练集中的因变量也是同样维度的一个向量,因此我们的代价函数会比逻辑回归更加复杂一些,为: \newcommand{\subk}[1]{ #1_k } h θ ( x ) R K h_\theta\left(x\right)\in \mathbb{R}^{K} ( h θ ( x ) ) i = i t h output {\left({h_\theta}\left(x\right)\right)}_{i}={i}^{th} \text{output}

KaTeX parse error: Undefined control sequence: \subk at position 94: …_k}^{(i)} \log \̲s̲u̲b̲k̲{(h_\Theta(x^{(…

这个看起来复杂很多的代价函数背后的思想还是一样的,我们希望通过代价函数来观察算法预测的结果与真实情况的误差有多大,唯一不同的是,对于每一行特征,我们都会给出 K K 个预测,基本上我们可以利用循环,对每一行特征都预测 K K 个不同结果,然后在利用循环在 K K 个预测中选择可能性最高的一个,将其与 y y 中的实际数据进行比较。

正则化的那一项只是排除了每一层 θ 0 \theta_0 后,每一层的 θ \theta 矩阵的和。最里层的循环 j j 循环所有的行(由 s l + 1 s_{l+1} 层的激活单元数决定),循环 i i 则循环所有的列,由该层( s l s_l 层)的激活单元数所决定。即: h θ ( x ) h_\theta(x) 与真实值之间的距离为每个样本-每个类输出的加和,对参数进行regularizationbias项处理所有参数的平方和。

Backpropagation Algorithm【反向传播算法】

之前我们在计算神经网络预测结果的时候我们采用了一种正向传播方法,我们从第一层开始正向一层一层进行计算,直到最后一层的 h θ ( x ) h_{\theta}\left(x\right)

现在,为了计算代价函数的偏导数 Θ i j ( l ) J ( Θ ) \frac{\partial}{\partial\Theta^{(l)}_{ij}}J\left(\Theta\right) ,我们需要采用一种反向传播算法,也就是首先计算最后一层的误差,然后再一层一层反向求出各层的误差,直到倒数第二层(因为第一层是输入,所以不存在误差)。

假设我们的训练集只有一个样本 ( x ( 1 ) , y ( 1 ) ) \left({x}^{(1)},{y}^{(1)}\right) ,我们的神经网络是一个四层的神经网络,其中 K = 4 S L = 4 L = 4 K=4,S_{L}=4,L=4

前向传播算法:

a ( 1 ) = x z ( 2 ) = Θ ( 1 ) a ( 1 )     ( a d d   a 0 ( 2 ) ) a ( 1 ) = g ( z ( 2 ) ) z ( 2 ) = Θ ( 2 ) a ( 2 )     ( a d d   a 0 ( 3 ) ) a ( 1 ) = g ( z ( 3 ) ) z ( 4 ) = Θ ( 3 ) a ( 3 ) a ( 4 ) = h θ ( x ) = g ( z ( 4 ) ) a^{(1)}=x\\ z^{(2)}=\Theta^{(1)}a^{(1)}\ \ \ (add\ a_0^{(2)})\\ a^{(1)}=g(z^{(2)})\\ z^{(2)}=\Theta^{(2)}a^{(2)}\ \ \ (add\ a_0^{(3)})\\ a^{(1)}=g(z^{(3)})\\ z^{(4)}=\Theta^{(3)}a^{(3)} a^{(4)}=h_\theta(x)=g(z^{(4)})

我们从最后一层的误差开始计算,误差是激活单元的预测( a ( 4 ) {a^{(4)}} )与实际值( y k y^k )之间的误差,( k = 1 : k k=1:k )。
我们用 δ \delta 来表示误差,则: δ ( 4 ) = a ( 4 ) y \delta^{(4)}=a^{(4)}-y
我们利用这个误差值来计算前一层的误差: δ ( 3 ) = ( Θ ( 3 ) ) T δ ( 4 ) g ( z ( 3 ) ) \delta^{(3)}=\left({\Theta^{(3)}}\right)^{T}\delta^{(4)}\ast g'\left(z^{(3)}\right)
其中 g ( z ( 3 ) ) g'(z^{(3)}) S S 形函数的导数, g ( z ( 3 ) ) = a ( 3 ) ( 1 a ( 3 ) ) g'(z^{(3)})=a^{(3)}\ast(1-a^{(3)}) 。而 ( θ ( 3 ) ) T δ ( 4 ) (θ^{(3)})^{T}\delta^{(4)} 则是权重导致的误差的和。下一步是继续计算第二层的误差:
$ \delta{(2)}=(\Theta{(2)}){T}\delta{(3)}\ast g’(z^{(2)})$
因为第一层是输入变量,不存在误差。我们有了所有的误差的表达式后,便可以计算代价函数的偏导数了,假设 λ = 0 λ=0 ,即我们不做任何正则化处理时有:
Θ i j ( l ) J ( Θ ) = a j ( l ) δ i l + 1 \frac{\partial}{\partial\Theta_{ij}^{(l)}}J(\Theta)=a_{j}^{(l)} \delta_{i}^{l+1}

重要的是清楚地知道上面式子中上下标的含义:

l l 代表目前所计算的是第几层。

j j 代表目前计算层中的激活单元的下标,也将是下一层的第 j j 个输入变量的下标。

i i 代表下一层中误差单元的下标,是受到权重矩阵中第 i i 行影响的下一层中的误差单元的下标。

如果我们考虑正则化处理,并且我们的训练集是一个特征矩阵而非向量。在上面的特殊情况中,我们需要计算每一层的误差单元来计算代价函数的偏导数。在更为一般的情况中,我们同样需要计算每一层的误差单元,但是我们需要为整个训练集计算误差单元,此时的误差单元也是一个矩阵,我们用 Δ i j ( l ) \Delta^{(l)}_{ij} 来表示这个误差矩阵。第 l l 层的第 i i 个激活单元受到第 j j 个参数影响而导致的误差。

我们的算法表示为:

即首先用正向传播方法计算出每一层的激活单元,利用训练集的结果与神经网络预测的结果求出最后一层的误差,然后利用该误差运用反向传播法计算出直至第二层的所有误差。

在求出了 Δ i j ( l ) \Delta_{ij}^{(l)} 之后,我们便可以计算代价函数的偏导数了,计算方法如下:
$ D_{ij}^{(l)} :=\frac{1}{m}\Delta_{ij}{(l)}+\lambda\Theta_{ij}{(l)}$ i f    j 0 {if}\; j \neq 0

$ D_{ij}^{(l)} :=\frac{1}{m}\Delta_{ij}^{(l)}$ i f    j = 0 {if}\; j = 0

Backpropagation Intuition【 反向传播算法的直观理解】

反向传播算法:
J ( Θ ) = 1 m [ i = 1 m y ( i ) l g ( h Θ ( x ( i ) ) ) + ( 1 y ( i ) ) l g ( 1 h Θ ( x ( i ) ) ) ] + λ 2 m l = 1 L 1 i = 1 S l j = 1 S l + 1 ( Θ j i ( l ) ) 2 J(\Theta)=-\frac{1}{m}[\overset{m}{\underset{i=1}{\sum}}y^{(i)}lg(h_\Theta(x^{(i)}))+(1-y^{(i)})lg(1-h_\Theta(x^{(i)}))]+\frac{\lambda}{2m}\overset{L-1}{\underset{l=1}{\sum}}\overset{S_l}{\underset{i=1}{\sum}}\overset{S_l+1}{\underset{j=1}{\sum}}(\Theta_{ji}^{(l)})^2
当仅关注一个例子 x ( i ) , y ( i ) x^{(i)},y^{(i)} ,只有一个输出单元式,忽视正则化( λ = 0 \lambda=0 )
c o s t ( i ) = y ( i ) l g ( h Θ ( x ( i ) ) ) + ( 1 y ( i ) ) l g ( h Θ ( x ( i ) ) ) cost(i)=y^{(i)}lg(h_\Theta(x^{(i)}))+(1-y^{(i)})lg(h_\Theta(x^{(i)}))

上图中的 δ j ( l ) = " e r r o r "   o f c o s t   f o r   a j ( l )   ( u n i t   j   i n   l a y e r   l ) \delta^{(l)}_{j}="error" \ of cost \ for \ a^{(l)}_{j} \ (unit \ j \ in \ layer \ l) 理解如下:

δ j ( l ) \delta^{(l)}_{j} 相当于是第 l l 层的第 j j 单元中得到的激活项的“误差”,即”正确“的 a j ( l ) a^{(l)}_{j} 与计算得到的 a j ( l ) a^{(l)}_{j} 的差。

其中:
δ 1 ( 4 ) = y ( i ) a 1 ( i ) δ 2 ( 2 ) = Θ 12 ( 2 ) δ 1 ( 3 ) + Θ 22 ( 2 ) δ 2 ( 3 ) \delta_1^{(4)}=y^{(i)}-a_1^{(i)}\\\delta_2^{(2)}=\Theta_{12}^{(2)}\delta_1^{(3)}+\Theta_{22}^{(2)}\delta_2^{(3)}
a j ( l ) = g ( z ( l ) ) a^{(l)}_{j}=g(z^{(l)}) ,(g为sigmoid函数)。我们可以想象 δ j ( l ) \delta^{(l)}_{j} 为函数求导时迈出的那一丁点微分,所以更准确的说 δ j ( l ) = z j ( l ) c o s t ( i ) \delta^{(l)}_{j}=\frac{\partial}{\partial z^{(l)}_{j}}cost(i)

Implementation Note_ Unrolling Parameters【实现注意:展开参数】

本次讨论的是怎么把矩阵展开成向量

在octave中:

import numpy as np

a = np.ones((10, 11))
b = 2 * np.ones((10, 11))
c = 3 * np.ones((1, 11))

x = np.vstack((a, b, c))
print(x)
print(x[0:10])
print(x[10:20])
print(x[20:21])
print(x.shape)
x = x.reshape(-1, 1) # 向量化
print(x)

Gradient Checking【梯度检验】

当我们对一个较为复杂的模型(例如神经网络)使用梯度下降算法时,可能会存在一些不容易察觉的错误,意味着,虽然代价看上去在不断减小,但最终的结果可能并不是最优解。为了避免这样的问题,我们采取一种叫做梯度的数值检验(Numerical Gradient Checking)方法。这种方法的思想是通过估计梯度值来检验我们计算的导数值是否真的是我们要求的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QpGfD80G-1581857068937)(/Users/wugaowei/Library/Application%20Support/typora-user-images/image-20200216165048629.png)]

反向传播算法很难调试得到正确结果,尤其是当实现程序存在很多难于发现的bug 时。举例来说,索引的缺位错误(off-by-one error)会导致只有部分层的权重得到训练(for(i=1; i<=m; ++i) 被漏写为 for(i=1; i<m; ++i)),再比如忘记计算偏置项。这些错误会使你得到一个看似十分合理的结果(但实际上比正确代码的结果要差)。因此,仅从计算结果上来看,我们很难发现代码中有什么东西遗漏了,所以我们用梯度检验来验证。

梯度下降更新公式:
θ : = θ α d d θ J ( θ ) \theta:=\theta-\alpha\frac{d}{d\theta}J(\theta)
s i g m o i d sigmoid 函数为例,也即 f ( z ) = 1 1 + e z f(z)=\frac{1}{1+e^{-z}}
,其导数形式为 f ( z ) = g ( z ) = f ( z ) ( 1 f ( z ) ) f′(z)=g(z)=f(z)(1−f(z))
,我们可轻易地编程实践,接着我们便可使用 θ : = θ α d d θ J ( θ ) \theta:=\theta-\alpha\frac{d}{d\theta}J(\theta) 。来实现梯度下降算法,那么我们如何知道 g(z)梯度的正确性呢。

在实际应用中,我们常将 ϵ 设为一个很小的常量,,比如 1 0 4 10^{-4} 数量级,因为假定 ϵ= 1 0 4 10^{−4} 的情况 下,通常会发现左右两端至少有四位有效数字是一致的(或者说精度至少在0.0001一级)。
d d θ J ( θ ) = l i m ϵ 0 J ( θ + ϵ ) J ( θ ϵ ) 2 ϵ \frac{d}{d\theta}J(\theta)=\underset{\epsilon \to 0}{lim}\frac{J(\theta+\epsilon)-J(\theta-\epsilon)}{2\epsilon}
由此我们可得梯度校验的数值校验公式:
g ( θ ) J ( θ + ϵ ) J ( θ ϵ ) 2 ϵ g(\theta)\approx\frac{J(\theta+\epsilon)-J(\theta-\epsilon)}{2\epsilon}
Python代码

import numpy as np

def sigmoid(z):
    return 1./(1+np.exp(-z))
def sigmoid_prime(z):
    return sigmoid(z)*(1-sigmoid(z))
def check_gradient(f, x0, epsilon):
    return (f(x0+epsilon) - f(x0-epsilon))/2/epsilon

if __name__ == '__main__':
    x0 = np.array([1, 2, 3])
    epsilon = 1e-4
    print(sigmoid_prime(x0))
            # [ 0.19661193  0.10499359  0.04517666]
    print(check_gradient(sigmoid, x0, epsilon))
            # [ 0.19661193  0.10499359  0.04517666]

Random Initialization【随机初始化】

任何优化算法都需要一些初始的参数。到目前为止我们都是初始所有参数为0,这样的初始方法对于逻辑回归来说是可行的,但是对于神经网络来说是不可行的。如果我们令所有的初始参数都为0,这将意味着我们第二层的所有激活单元都会有相同的值。同理,如果我们初始所有的参数都为一个非0的数,结果也是一样的。

我们通常初始参数为正负ε之间的随机值,假设我们要随机初始一个尺寸为10×11的参数矩阵,代码如下:

import numpy as np

epsilon = 1e-4
print(np.random.rand(10, 11) * 2 * epsilon - epsilon)

Putting It Together【 综合起来】

第一层的单元数即我们训练集的特征数量。

最后一层的单元数是我们训练集的结果的类的数量。

如果隐藏层数大于1,确保每个隐藏层的单元个数相同,通常情况下隐藏层单元的个数越多越好。

我们真正要决定的是隐藏层的层数和每个中间层的单元数。

训练神经网络:

  1. 参数的随机初始化

  2. 利用正向传播方法计算所有的 h θ ( x ) h_{\theta}(x)

  3. 编写计算代价函数 J J 的代码

  4. 利用反向传播方法计算所有偏导数

  5. 利用数值检验方法检验这些偏导数

  6. 使用优化算法来最小化代价函数

Autonomous Driving 【自主驾驶】

在图中你依稀能看出一条道路,朝左延伸了一点,又向右了一点,然后上面的这幅图,你可以看到一条水平的菜单栏显示的是驾驶操作人选择的方向。就是这里的这条白亮的区段显示的就是人类驾驶者选择的方向。比如:最左边的区段,对应的操作就是向左急转,而最右端则对应向右急转的操作。因此,稍微靠左的区段,也就是中心稍微向左一点的位置,则表示在这一点上人类驾驶者的操作是慢慢的向左拐。

这幅图的第二部分对应的就是学习算法选出的行驶方向。并且,类似的,这一条白亮的区段显示的就是神经网络在这里选择的行驶方向,是稍微的左转,并且实际上在神经网络开始学习之前,你会看到网络的输出是一条灰色的区段,就像这样的一条灰色区段覆盖着整个区域这些均称的灰色区域,显示出神经网络已经随机初始化了,并且初始化时,我们并不知道汽车如何行驶,或者说我们并不知道所选行驶方向。只有在学习算法运行了足够长的时间之后,才会有这条白色的区段出现在整条灰色区域之中。显示出一个具体的行驶方向这就表示神经网络算法,在这时候已经选出了一个明确的行驶方向,不像刚开始的时候,输出一段模糊的浅灰色区域,而是输出一条白亮的区段,表示已经选出了明确的行驶方向。

ALVINN (Autonomous Land Vehicle In a Neural Network)是一个基于神经网络的智能系统,通过观察人类的驾驶来学习驾驶,ALVINN能够控制NavLab,装在一辆改装版军用悍马,这辆悍马装载了传感器、计算机和驱动器用来进行自动驾驶的导航试验。实现ALVINN功能的第一步,是对它进行训练,也就是训练一个人驾驶汽车。

然后让ALVINN观看,ALVINN每两秒将前方的路况图生成一张数字化图片,并且记录驾驶者的驾驶方向,得到的训练集图片被压缩为30x32像素,并且作为输入提供给ALVINN的三层神经网络,通过使用反向传播学习算法,ALVINN会训练得到一个与人类驾驶员操纵方向基本相近的结果。一开始,我们的网络选择出的方向是随机的,大约经过两分钟的训练后,我们的神经网络便能够准确地模拟人类驾驶者的驾驶方向,对其他道路类型,也重复进行这个训练过程,当网络被训练完成后,操作者就可按下运行按钮,车辆便开始行驶了。

每秒钟ALVINN生成12次数字化图片,并且将图像传送给神经网络进行训练,多个神经网络同时工作,每一个网络都生成一个行驶方向,以及一个预测自信度的参数,预测自信度最高的那个神经网络得到的行驶方向。比如这里,在这条单行道上训练出的网络将被最终用于控制车辆方向,车辆前方突然出现了一个交叉十字路口,当车辆到达这个十字路口时,我们单行道网络对应的自信度骤减,当它穿过这个十字路口时,前方的双车道将进入其视线,双车道网络的自信度便开始上升,当它的自信度上升时,双车道的网络,将被选择来控制行驶方向,车辆将被安全地引导进入双车道路。

发布了81 篇原创文章 · 获赞 27 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_43309286/article/details/104348491