除了SVM、决策树等算法,人工神经网络是机器学习的另一个重要分支,它是深度学习的基础。人工神经网络是通过模仿生物神经网络系统结构和功能,提出了一种 非线性统计性模型 ,用于对函数的近似和估计。人工神经网络以其独特的网络结构和处理信息的方法,在自动控制领域、组合优化问题、模式识别、图形处理、自然语言处理等诸多领域,已经取得了辉煌的成绩,本文将介绍其基本模型和核心算法实现。
神经网络概述
神经元
神经网络最早的设计思路来自于生物学中的神经元,从结构、实现机理和功能上模拟神经网络系统:
回想下高中生物知识,传统的神经元模型由树突、细胞核、细胞体、突触和神经末梢组成:
- 突触前(神经元)细胞的树突或细胞体接受刺激,产生兴奋或抑制。
- 动作电位传到神经末梢,导致神经递质释放。
- 使突触后(神经元)细胞的树突或细胞体接受刺激。
对于人工神经网络,神经元的输入 $x_i$ 对应于生物神经元的树突,输入 $x_i$ 向细胞体传播脉冲,相当于输入权值 $w_i$,通过细胞核对输入的数据和权值参数进行加权求和。传播细胞体的脉冲相当于人工神经元的激活函数,最终输出结果 $y$ 作为下一个神经元的输入。
可以看到人工神经网络最基本的处理单元 —— 神经元的基本组成单位为:
- 连接 Connetion:神经元中数据流动的表达方式
- 求和节点 Summation Node :对输入信号和权值的乘积进行求和
- 激活函数 Activate Function:一个非线性函数,对输出信号进行控制
将上述模型进行抽象,得到神经元基本模型:
- $x_1, x_2, …, x_n$ 为输入信号的各个分量
- $w_1, w_2, …, w_n$ 为神经元各个突触的权值
- $b$ 为神经元的偏置参数
- $\sum$ 为求和节点,$ z = \sum_{i=1}^n w_i * x_i + b $
- $f$ 为激活函数,一般为非线性函数
- $y$ 为该神经元的输出
该神经元模型的数学表达式为:
可以看到,神经元模型就是一个基本的函数,本质上做的是数据的映射,$ \mathbf{X} $ 为输入向量,$y$ 为输出变量,神经元对应着 $y = \varphi(\mathbf{X})$ ,而 $\mathbf{W}$ 和 $b$ 则是这个函数的参数。单个神经元如果没有加上激活函数,可以看作是一个线性模型,而 $\mathbf{W}$ 和 $b$ 则是这个线性模型的参数。
回想下本科的线性几何课程,线性模型的任何组合仍然是线性模型。但是对于现实数据而言,很多数据都是线性不可分的,需要的是非线性模型。另外,对于一个拥有很多特征的复杂数据集进行线性回归是代价很高的,需要高昂的计算代价。因此,我们需要在神经元模型中引入一个 非线性单元,也就是这里的 激活函数,使得神经元模型能够更好的解决复杂的数据分布问题。
多层神经网络
人工神经网络由许多神经元组合而成,神经元组成的信息处理网络具有并行分布结构,因此有了更复杂的人工神经网络。一个多层人工神经网络 ANN 由输入层、隐藏层、输出层组成,第 k-1
层网络神经元的输出是第 k
层神经元的输入。下面是一个简单的两层神经网络,我们将输入层称为第零层:
人工神经网络的输入层与输出层的节点数往往固定,这取决于我们的输入和输出,而隐层数和隐层节点则可以自由指定。人工神经网络的关键不是节点而是连接,每层的神经元与下一层的多个神经元相连接,每条连接线都有独自的权重参数,这些参数往往通过训练得到。
在这个图中,$w_{ij}$ 表示第 $k-1$ 层的第 $i$ 个节点到的权重第 $k$ 层的第 $j$ 个节点
根据上面的公式,我们可以容易得出:
令
则
令
则有,
在这个公式说明神经网络的每一层的作用实际上就是先将输入向量左乘一个数组进行线性变换,得到一个新的向量,然后再对这个向量逐元素应用一个激活函数,其中每个变量的定义如下:
- $f$ 是激活函数
- $\mathbf{W}$ 是第 $k$ 层的权重矩阵
- 它的每一个行向量对应着第 $k$ 层的每个节点,也就是说如果第 $k$ 层的有 $N$ 个节点,则 $\mathbf{W}$ 共有 $N$ 个行向量
- 如果第$k-1$层有 $M$ 个节点,则 $\mathbf{W}$ 的每个行向量的长度为 $M$ ,对应着第$k-1$层$M$ 个节点的求和
- $B$ 是第 $k$ 层的偏置向量,其长度与第 $k$ 层的节点数相同
- $\mathbf{X}$ 是第 $k$ 层的输入向量,也正是第 $k-1$ 层的输出向量
- $\mathbf{o}$ 是第 $k$ 层输出向量
因此,如果我们将上面的简单神经网络增加层数到4层,如下图所示(注意,这里画图有点偷懒,中间应该是全连接网络,为了简单这里没有全部连起来)
则我们可以算出每一层的输出向量如下:
训练与预测
前向传播算法
OK,假设我们现在根据某个应用场景,构建了一个多层神经网络的模型,并且根据训练数据获得了网络的所有参数(输入层、输出层、隐层的节点数、权重矩阵 $W$ 和偏置向量 $B$)。如果这个模型参数合理的话,那么对于新的输入数据,这个模型能够预测出合理的输出结果。所谓的预测,就是将向量化的数据从神经网络的输入层开始输入,顺着数据流动的方向在网络中计算,直到数据传输到输出层并输出,这也就是 前向传播算法。
假设我们有如下定义:
- $w_{ij}^k$:第 $k$ 层的第 $j$ 个节点对于来自第 $k-1$层的第 $i$ 个节点的权重
- $b_j^k$:第 $k$ 层的第 $j$ 个节点的偏置
- $net_j^k$:第 $k$ 层的第 $j$ 个节点的
net input value
,也就是激活函数的输入 - $o_j^k$:第 $k$ 层的第 $j$ 个节点的输出,也就是激活函数的输出
- $r_k$:第 $k$ 层的节点数目
- $M$:神经网络输入向量 $X$ 的大小,即 $r_0 = M$
- $L$:全连接神经网络的层数,最简单的神经元的层数为 $1$
- $N$:神经网络输出向量 $Y$ 的大小,即 $r_L = N$
则对于第 $k$ 层的第 $j$ 个节点,有
也就是说,第 $k$ 层的第 $j$ 个节点的净输入为第 $k-1$ 层的所有节点输出值的加权和再加上第 $k$ 层第 $j$ 个节点的偏置。
- 当 $k=1$时,第 $k-1=0$ 层的输出向量 $O^0$ 就是输入向量 $X$,此时 $\begin{bmatrix}o_1^0, o_2^0, \dots, o_M^0 \end{bmatrix} = \begin{bmatrix}x_1, x_2, \dots, x_M \end{bmatrix} $
- 当 $k = L$时,也即是最后一层的输出向量 $O^L$ 就是输出向量 $Y$,此时 $\begin{bmatrix}o_1^L, o_2^L, \dots, o_N^L \end{bmatrix} = \begin{bmatrix}y_1, y_2, \dots, y_N \end{bmatrix} $
因此,一旦确定了神经网络的参数,就可以通过上述公式迭代算出神经网络的输出:
1 | def forward_propagation(network_structure, weight, bias): |
梯度下降算法
如上所说,一旦确定好神经网络模型中的权值矩阵 $W$ 和偏置向量 $B$ ,就可以基于模型进行预测了。现在的问题是,如何得到这些参数的值呢?这就要通过原始数据训练得到了。神经网络的训练实际上是通过算法不算修改权值矩阵$W$ 和偏置向量 $B$ ,使其尽可能与真实模型逼近,以使得整个神经网络的预测效果最佳。具体做法如下:
- 给所有权值矩阵$W$ 和偏置向量 $B$ 赋予随机值
- 利用前向传播算法基于随机的权值矩阵$W$ 和偏置向量 $B$ 来得到训练样本的预测值 $\hat{y}$
- 计算损失函数 $loss = (\hat{y} - y)^2$,优化目标是改变神经网络中的参数,使得损失函数的值最小
因此,对于神经网络的优化问题就转换为对参数的优化,减少损失直至损失收敛,当损失函数收敛到一定程度时就可以结束训练,保存训练后神经网络的参数。
在微积分中,对多元函数的参数求偏导,求得参数的偏导数以向量的形式表达就是 梯度。如下图所示,对于损失函数 $loss$ 的参数 $\theta$ 求梯度即是 $\frac{\partial{loss}}{\partial{\theta}}$ 。在数学上,梯度越大,则函数的变化越大。也就是说,沿着梯度向量的方向函数增加最快,易于找到函数的最大值;沿着与梯度向量相反的方向函数减少最快,易于找到函数的最小值。
对于损失函数来说,为了找到其最小值,需要沿着与梯度向量相反的方向 $-\frac{\partial{loss}}{\partial{\theta}}$ 更新参数 $\theta$,这样可以使得梯度减少最快,直至损失收敛至最小值。这即是 梯度下降算法 (Gradient Descent),其基本公式为:
其中,$\alpha \in \mathbf{R}$ 为学习率,用于控制梯度下降的幅度。我们可以将损失函数看成是参数 $\theta$ 的函数,优化的目的就是找到参数 $\theta_x$ 使得损失函数最小。具体的做法就是,每次计算参数 $\theta_i$ 在当前位置时函数的梯度,然后让参数 $\theta_i$ 顺着梯度的反方向前进一段距离,不断重复该过程,直到梯度趋近于零的时候,算法认为找到的损失函数的最小值并停止计算。此时的参数即是目标 $\theta_x$ 神经网络的参数。
梯度下降的算法变种有很多,下面对常用的梯度下降算法进行介绍。
批量梯度下降算法 BGD
批量梯度下降算法 Batch Gradient Descent 中,所有样本都参与参数 $w$ 的更新。假设有 $m$ 个样本,$m$ 个样本都参与调整参数 $w$,因此得到一个标准的梯度。
- 优点:易于得到全局最优解,总体迭代次数不多
- 缺点:当样本数目很多时,训练时间过长,收敛速度变慢
随机梯度下降算法 SGD
随机梯度下降算法 Stochastic Gradient Descent 中,梯度是从 $m$ 个样本中随机抽取 $n$ 个样本进行求解的
- 优点:训练速度快,每次迭代计算量少
- 缺点:准确度下降,得到的不一定是全局最优,总体迭代次数比较多
小批量随机梯度下降算法 Min-batch SGD
小批量随机梯度下降算法是对BGD和SGD的折衷方法:每次随机从 $m$ 个样本中抽取 $k$ 进行迭代求梯度,每一次迭代的抽取方式都是随机的,因此部分样本会重复。这样做的好处是,计算梯度时让数据和数据之间产生关联,避免数据最终只能收敛到局部最优解。
反向传播算法
反向传播算法,全称是 back propagation of errors,本质上就是利用梯度下降算法来计算神经网络的参数,它从输出层开始向输入层的方向一层一层往前算起,计算出每一层误差的梯度,从而更新神经网络的参数,下面将从数学上推导反向传播算法的具体实现。
假设我们有以下定义:
- $w_{ij}^k$:第 $k$ 层的第 $j$ 个节点对于来自第 $k-1$层的第 $i$ 个节点的权重
- $b_j^k$:第 $k$ 层的第 $j$ 个节点的偏置
- $net_j^k$:第 $k$ 层的第 $j$ 个节点的
net input value
,也就是激活函数的输入 - $o_j^k$:第 $k$ 层的第 $j$ 个节点的输出,也就是激活函数的输出
- $r_k$:第 $k$ 层的节点数目
- $M$:神经网络输入向量 $X$ 的大小,即 $r_0 = M$
- $L$:全连接神经网络的层数,最简单的神经元的层数为 $1$
- $N$:神经网络输出向量 $Y$ 的大小,即 $r_L = N$
- 前馈神经网络,其中 $\theta$ 是网络的参数,对应的就是权值 $w_{ij}^k$ 和偏置 $b_j^k$
- $E(\theta)$:神经网络的预测值与实际值的误差函数
- $f$:激活函数
- $f_o$:输出层的激活函数
根据梯度下降算法,我们的目标是找到最佳的网络参数,使得误差函数最小:
其中 $\theta^t$ 是神经网络在计算梯度的第 $t$ 次迭代中的参数。
根据最小均方误差,我们可以得到
计算梯度
计算梯度
其中,
这里为了表达方便,省略了 $E_d$, $\hat{y_d}$, $y_d$ 中的下标 $d$
也就是说,总的误差函数梯度是输出层每一个节点误差梯度的算术平均值,接下来我们看如何计算 $\frac{\partial E}{\partial w_{ij}^k}$
根据链式法则,
我们将右式第一项记作误差,也就是第 $k$ 层第 $j$ 个节点的误差
对于右式第二项,我们先回顾前向传播算法有
为了简化数学表达,我们可以把第 $k$ 层第 $j$ 个节点的偏置视作来自第$k-1$ 层的节点0的输入,其中$o_0^{k-1} = 1$,则
故
所以计算梯度简化如下
综上,
得到梯度的表达式之后,可以根据是输出层还是隐藏层具体计算。
输出层
如向所述,我们将通过梯度下降的方法来迭代计算神经网络的参数,首先看输出层,我们需要计算出 $\delta_j^L$。
而
则有
于是得到梯度的计算公式:
隐藏层
对于隐藏层,我们也需要算出第 $k$ 层第 $j$ 个节点的误差 $\delta_j^k$ ,它将通过影响第 $k+1$ 层所有节点的净输入 $net_i^{k+1}$来影响最终的误差$E$。
因此,我们通过链式法则将 $E$ 先对第 $k+1$ 层所有节点的净输入 $net_i^{k+1}$ 求导,然后再将 $net_i^{k+1}$ 对 $net_i^{k}$ 求导:
注意这里的 $l$ 范围是 1 到 $r^{k+1}$,$l$ 没有从$0$开始是因为,第 $k+1$ 层的净输入 $net_0^{k+1}$ 实际上为第 $k$ 层节点$0$的输出 $o_{0}^{k}$ 乘以权值 $ w_{0j}^{k+1} $ 是固定的,它不取决于第 $k$ 层的输出。
我们知道,上式的第一个偏微分已经在第 $k+1$ 层计算误差得到,
而对于第二个偏微分,我们将 $net_l^{k+1}$ 展开,
这里的 $f(x)$ 是隐藏层的激活函数,所以可以得到第二个偏微分的公式,
故我们得到了反向传播公式,
因此,可以从 $\delta_l^{k+1}$ 迭代计算出 $\delta_j^k$ ,换句话说,第 $k$ 层的误差 $\delta_j^k$ 依赖于第 $k+1$ 层的误差 $\delta_l^{k+1}$计算而来。这就是反向传播名称的来源,误差沿着神经网络反向流动,从最后一层流向第一层。一旦计算出了输出层的误差,我们就可以沿着神经网络迭代算出隐藏层的误差,通过乘上一个系数 $f^\prime(net_j^k)$。
计算出误差之后,我们就可以得到梯度的公式,
注意到在这个公式中,我们需要知道 $net_j^k$ 和 $o_i^{k-1}$,这些需要在前向传播的时候计算并保存。也就是说,每一次迭代中,
- 首先进行前向传播的计算,根据设定的模型参数,从输入层到输出层,同时保存每一层的 $net_j^k$ 和 $o_j^k$
- 然后进行反向传播的计算,从输出层开始,以输出层的误差作为输入,计算每一层每个节点中误差的梯度
- 最后我们通过算出的梯度更新参数的值,然后进入下一次迭代
反向传播
最后在这里梳理下上面推导的公式和整个算法的流程。
对于梯度计算:
对于输出层的误差计算:
对于隐藏层的误差计算:
将所有的误差结合起来:
更新参数:
整体流程如下:
随机初始化权值参数 $w_{ij}^k$
前向传播计算,从输入层到输出层,对于第 $k$ 层的第 $j$ 个节点,基于 $w_{ij}^k$ 计算出 $net_j^k$,$o_j^k$ 和 $\hat{y_d}$
反向传播计算,从输出层到输入层,对于第 $k$ 层的第 $j$ 个节点,通过公式2和公式3计算出 $\delta_j^k$,
然后通过公式1计算出梯度 $\frac{\partial E}{\partial w_{ij}^k}$
将所有节点的误差结合起来,通过公式4将所有的输出节点的误差结合起来
更新权值参数,根据公式5更新权值参数,然后进入第2步进行下一轮迭代计算,直到误差收敛
调参与正则化优化
上一小节中,我们介绍了神经网络的训练与预测:在明确了神经网络的模型后,我们就可以通过定义合理的损失函数,模型根据反向传播算法和随机梯度下降算法,自动地修正网络模型的参数($W$ 和 $b$ ),并对训练数据的特征进行学习。
但是,这里似乎还有很多问题没有解决:
- 神经网络应该有多少层
- 每一层应该有多少隐藏单元
- 学习速率应该是多少
- 各层应该采用哪些激活函数
- 应该选用哪种损失函数
- 梯度下降算法的参数应该如何选择
- ……
所有的这些超参数不可能在一开始就预测出来,实际上通常的情况是,首先有个初步想法,比如构建一个含有特定层数、隐藏单元等等的神经网络,然后在运行和测试中得到该神经网络的运行结果,并不断迭代更新自己的方案。
激活函数
线性函数
线性函数是最基本的激活函数,其因变量与自变量有直接的比例关系,因此线性变换类似于线性回归。
1 | def linear(x, a, b): |
Sigmoid 函数
Sigmoid
函数是一种在不删除数据的情况下,减少数据的极值或异常值的函数。
1 | def sigmoid(x, w = 1): |
双曲正切函数
双曲正切函数 tanh
与 sigmoid
函数蕾丝,不同的是,tanh
的归一范围是 -1 到 1,而不是 0 到 1,因此 tanh 的优点是可以更容易地处理附属。
1 | def tanh(x): |
ReLU 函数
ReLU
函数满足仿生学中的稀疏性,只有当输入值高于一定数目时才激活该神经元节点。当输入值低于0时进行限制,当输入值上升到某一阈值以上时,函数中的自变量与因变量成线性关系。
1 | def relu(x): |
Softmax 函数
Softmax
函数的本质是将一个 K 维的任意实数向量,映射成另一个 K 维的实数向量,其中向量中的每一个元素取值都介于(0,1)范围内。
1 | def softmax(x): |
激活函数的选择
损失函数
在神经网络中,损失函数用来评价网络模型输出的预测值 $\hat{\vec{Y}} = f(\vec{X})$ 与真实值 $\vec{Y}$ 之间的差异。这里使用 $L(\vec{Y}, \hat{\vec{Y}})$ 来表示损失函数,它是一个非负值函数。损失值越小,网络模型的性能就越好,所以优化算法目的就是让损失函数尽可能的小。
损失函数的定义
假设网络模型中有 N 个样本,样本的输入和输出向量为 $(\vec{X}, \vec{Y}) = (x_i, y_i), i \in [1, N]$ ,那么总损失函数 $L(\vec{Y}, \hat{\vec{Y}})$ 为每一个输出预测值与真实值的误差之和。
值得注意的是,机器学习问题主要分为回归和分类问题,对分类模型和回归模型进行评估时会使用不同的损失函数,下面将分别对回归模型和分类模型的损失函数进行介绍。
回归损失函数
均方误差损失函数,MSE
平均绝对误差损失函数,MAE
均方误差对数损失函数,MSLE
分类损失函数
Logistic 损失函数
负对数似然损失函数
交叉熵损失函数
Hinge损失函数
指数损失函数
常用的损失函数
超参数
学习率
动量
数据集准备
数据集扩展
数据预处理
Zero Centralization
Normalization
Principal Component Analysis, PCA
Whitening
网络初始化
网络过度拟合
正则化方法
正则化的最大作用是防止过度拟合,提高网络模型的泛化能力,具体实现方法是在损失函数中增加惩罚因子。