coursera机器学习课程笔记 【week 5】

week 4的内容中学习了神经网络,但仅仅是简单接触,这周进行更加深入地学习。在开始之前先提一下两个变量:

在神经网络中,我们用\(L\)表示网络中的总层数,用\(s_l\)表示第\(l\)层(从1开始)中神经元的数量(不包括偏执神经元)。

另外,在视频的课程中,只focus神经网络在分类问题上的应用。分类问题主要分为两种:

  1. 第一种是multi-class classification,此时有\(K\)种类别,标签也是一个\(K\)维向量,分别表示是不是某种分类,这里\(K \ge 3\)
  2. 第二种是binary classification,此时\(K = 1\)

注意没有出现\(K = 2\),是因为当只有两个分类时,我们不再需要二维的标签,而只需要用实数即可,也就是\(K = 1\)对应的二元分类。

代价函数

首先先来看一下代价函数。神经网络中的代价函数是逻辑回归的代价函数的一般化形式。考虑到在上一周的课程中我们发现,神经网络的一张张子网的计算过程其实和逻辑回归很相似,那么这里两者的代价函数有一定联系也不足为奇。

逻辑回归的代价函数如下表示: \[ J(\theta) = -\frac1m[\sum_{i=1}^m(y^{(i)}logh_\theta(x^{(i)}) + (1-y^{(i)})log(1-h_\theta(x^{(i)})))] + \frac{\lambda}{2m}\sum_{j=1}^m\theta_j^2 \] 而作为其概化形式,神经网络的代价函数写成: \[ J(\Theta) = -\frac1m[\sum_{i=1}^m\sum_{k=1}^K(y_k^{(i)}log(h_\Theta(x^{(i)}))_k + (1-y_k^{(i)})log(1-(h_\Theta(x^{(i)}))_k))] + \frac{\lambda}{2m}\sum_{l=1}^{L-1}\sum_{i=1}^{s_l}\sum_{j=1}^{s_{l+1}}(\Theta_{ji}^{(l)})^2 \] 这个式子看似复杂,但的确和上面的式子很像,我们把它拆成两部分看。

  • 首先看第一部分,下式比上式多了一层\(\sum_{k=1}^K\)的求和。这是因为,在神经网络中,输出并不一定是实数,而可能是\(K\)维的向量。因此,在计算误差时,输出层的\(K\)个神经元的误差都要考虑到。所以,这里在\(\sum_{i=1}^m\)的求和式内部,表示每个样例对于\(K\)个神经元的误差之和。整个求和式也就表示所有\(m\)个训练数据对于所有\(K\)个神经元的误差总和了。这里如果令\(K = 1\),我们发现和逻辑回归的第一部分相同。
  • 接着是第二项,正则化的项。我们知道正则化的目的是为了给参数\(\theta\)添加“惩罚”,那么在神经网络中,所有相邻的两层之间都有\(s_i \times s_{i+1}\)个参数,所以计算惩罚项也就需要三层求和了。

反向传播算法(BackPropagation)

在逻辑回归中,我们如果想用梯度下降的方法最小化代价函数,就必须根据预测结果的误差来求出梯度,并用梯度更新参数。在神经网络中也是同理,但是问题在于,神经网络有那么多层,相邻两层之间又都有不同的参数,该怎么算梯度、优化参数呢?在神经网络中,求梯度的关键,在于“链式求导法则”。利用该法则,我们可以求出所有权值的梯度。但是,还有一个问题,就是如果直接利用链式求导法则会导致有很多的冗余计算,很耗时间。因此,就引入了\(\delta\)

我们令\(\delta_j^{(l)}\)表示第\(l\)层第\(j\)个神经元的误差。在使用前向传播从输入层达到输出层后,我们还需要转过身,接着反向传播。只不过此时传播的不再是计算出的输出,而是各个神经元的误差。比如说我们计算出了第\(l\)层的所有误差并记录了下来,那么在接下来计算第\(l-1\)层的误差时,就能直接使用第\(l\)层的误差了而不用再去从输出层再算一次了。这就有点动态规划的感觉。

那么\(\delta\)怎么计算呢?假设我们要求第\(l\)层的第\(j\)个神经元的误差,那么它可以这样求得: \[ \delta_j^{(l)} = \sum_{i=1}^{s_{l+1}}\Theta_{ij}^{(l)}\delta_i^{(l+1)} \] 这之后,就可以进行权重的更新了: \[ \Theta_{ij}^{(l)} := \Theta_{ij}^{(l)} -\alpha a_j^{(l)}\delta_i^{(l+1)} \]

反向传播算法这里不详细写了,分享一篇个人认为讲解很清晰的文章:一文搞懂反向传播算法

梯度检验

我们知道,反向传播算法是在神经网络中从后往前把模型的误差传递给每一个神经元,并以此来算出网络中各条权重进行梯度下降所需的权重。这种方法的效率很高,但是在传播过程中涉及到的参数很多,很容易出现一些细微的bug。这些bug导致梯度计算出了错误,却难以发现,因此我们需要用梯度检验来验证我们的反向传播算法的实现是否正确、算出的梯度是否正确。

要判断梯度计算是否正确,我们就需要知道正确的梯度是多少。考虑梯度的定义,其实就是要求函数的导数。假设我们要求\(J(\theta)\)\(\theta\)的梯度,可以这样来求: \[ \frac{J(\theta + \epsilon) - J(\theta - \epsilon)}{2\epsilon} \] 其中\(\epsilon\)取一个很小的常数值,我们一般取\(10^{-4}\)

目前考虑的是\(\theta\)为实数的情况,现在考虑神经网络中的情况:参数是个多维的矩阵。此时要做的是,首先把矩阵平铺成一个向量(为了计算方便),接着对每个元素依次求梯度,大致代码形式如下:

1
2
3
4
5
6
for i in range(len(theta_vector)):
theta_plus = theta
theta_plus[i] = theta_plus[i] + epsilon
theta_minus = theta
theta_minus[i] = theta_minus[i] - epsilon
grad_approx[i] = (J(theta_plus) - J(theta_minus)) / (2 * epsilon)

其中,theta_vector为\(\begin{bmatrix}\theta_1 \\ \theta_2 \\ \vdots \\ \theta_i \\ \vdots \\ \theta_n\end{bmatrix}\),而theta_plus和theta_minus依次为:\(\begin{bmatrix}\theta_1 \\ \theta_2 \\ \vdots \\ \theta_i + \epsilon \\ \vdots \\ \theta_n\end{bmatrix}\)\(\begin{bmatrix}\theta_1 \\ \theta_2 \\ \vdots \\ \theta_i - \epsilon \\ \vdots \\ \theta_n\end{bmatrix}\)

算出正确的梯度后,再和反向传播算法的结果进行比较,如果两者很相近,就可以认为我们的反向传播算法正确地实现了。

需要注意,因为通过\(\epsilon\)直接算出梯度很费时间,在不需要的时候,我们要取消梯度检验,否则训练模型的过程会非常慢。通常,我们会在正式训练模型之前先取一个小的子集,并通过梯度检验验证反向传播算法正确实现了,然后再开始正式的训练。

随机初始化

在梯度下降法和其他的一些算法中,参数的初始化也是需要考虑的一个问题。在线性回归、逻辑回归的问题中,我们只要把参数全部设为0即可。但在神经网络中,如果参数全置0,会导致对称权重问题,因此每一层的所有神经元中都会有相同的输入和相同的输出,且这个状态将一直维持。所以在神经网络中,我们使用随机初始化。

神经网络的整体过程

首先,决定一个神经网络的架构,也就是隐层的层数以及三种层的神经元个数。

  • 输入层的神经元个数等同于特征维数。
  • 输出层的神经元个数等同于类别数(分类问题),注意多分类问题的输出是一个向量,而二分类问题只需要一个实数。
  • 对于隐层的层数,默认结构是只有一个隐层,这个结构也是最通用的。而有多个隐层时,通常需要每个隐层的神经元个数都是相同的。
  • 最后,隐层的神经元个数则是越多越好,不过个数越多也代表着计算量越大。

接下来就需要训练我们的神经网络了,它包含以下6个步骤:

  1. 随机初始化权重,通常是接近零的很小的数。
  2. 实现前向传播算法,对于输入\(x^{(i)}\)计算出输出\(h_\Theta(x^{(i)})\)
  3. 计算代价函数\(J(\Theta)\)
  4. 实现反向传播,计算出代价函数对各个权重的偏导\(\frac{\partial}{\partial\Theta_{jk}^{(l)}}J(\Theta)\)
  5. 使用梯度检验进行验证。记得验证正确之后不再运行梯度检验的代码了。
  6. 结合运用反向传播和梯度下降或其他优化算法来最小化代价函数\(J(\Theta)\)。这里需要注意,线性回归、逻辑回归等模型的代价函数都是凸函数,所以是可以用梯度下降求得全局最优解的凸优化问题。但神经网络的代价函数不是凸函数,因此使用梯度下降不能保证找到全局最优解。但至少它能找到局部最优解,有的时候也能有还算不错的表现。