5. 神经网络
5.1 前向传播
神经网络分为很多层,包括输入层、输出层和中间的隐层。
使用的符号如下:
a(j)i a i ( j ) 第j层,第i个单元的输出(activity)
Θ(j) Θ ( j ) 第j层向第j+1层传播的系数矩阵,如果在j层有 Sj S j 个节点,j+1层有 Sj+1 S j + 1 个节点,那么, Θ(j)∈RSj+1×(Sj+1) Θ ( j ) ∈ R S j + 1 × ( S j + 1 ) ,其中的+1 是由于上述节点没有包含偏倚节点(bias)即逻辑回归中的常数项b。
通过调节权重系数,神经网络可以得到XOR, XNOR, AND, NOT, OR
多分类问题
神经网络处理多分类问题采用的是one-vs-all的方法,对于k类问题,输出层为k个节点,正确的分类输出节点为1,其余的为0.
5.2 损失函数
神经网络中节点的输入到输出的非线性函数一般选用sigmoid函数,因此借助逻辑回归的损失函数,可以写出输出层有K个节点有k个样本的神经网络的损失函数
上式中, L L 为神经网络的总层数,每一层的节点数为 Sl S l ,
Θ(l)j,i Θ j , i ( l ) 表示第 l l 层的第j 个节点到第 层的第i 个节点的链接的权重。
上面的损失函数就是将每个输出节点的损失函数加和同时考虑所有权重的正则化。需要注意的是,第一个2重求和中的i为遍历样本,k为遍历输出层节点,第二个3重求和中的i,j都是指节点,不是样本。
5.3 BP算法
为了优化损失函数,需求计算 J(Θ),∂J(Θ)∂Θ J ( Θ ) , ∂ J ( Θ ) ∂ Θ ,这里采用向后传播(BP)算法,推导过程如下:
观察损失函数,先忽略正则化项,第一项等于所有输出节点的损失函数加和,我们首先对其中一个损失函数求导,首先,令函数
C=−∑Kk=1y(i)klog(hΘ(x(i))k)−(1−y(i)k)log(1−hΘ(x(i))k
C
=
−
∑
k
=
1
K
y
k
(
i
)
l
o
g
(
h
Θ
(
x
(
i
)
)
k
)
−
(
1
−
y
k
(
i
)
)
l
o
g
(
1
−
h
Θ
(
x
(
i
)
)
k
由于需要对每一层之间的各个权重系数进行求导,这里我们先从输出层开始,利用求导链式法则,可以得到:
其中, z(L)j z j ( L ) 是L层 j 节点的输入。此后,我们定义损失函数对某节点的输入为:
δ(l)j=∂C∂z(l)j(5-2) (5-2) δ j ( l ) = ∂ C ∂ z j ( l )
因此,(5-1)可以写为:
这里通过求导可以求得 δ(L)j=a(L)j−yj δ j ( L ) = a j ( L ) − y j 。
利用(5-3)式,可以C关于求得(L-1)层权重的导数,对于(L-2)层,同样使用链式法则,可以得到:
从(5-4)式,我们可以得到
δ(L−1)j=∑SLt=1[δ(L)t⋅Θ(L−1)t,j]⋅a(L−1)j(1−a(L−1)j) δ j ( L − 1 ) = ∑ t = 1 S L [ δ t ( L ) ⋅ Θ t , j ( L − 1 ) ] ⋅ a j ( L − 1 ) ( 1 − a j ( L − 1 ) )
写成向量形式可以得到:
δ(L−1)=(Θ(L−1))Tδ(L).∗a(L−1).∗(1−a(L−1))(5-5) (5-5) δ ( L − 1 ) = ( Θ ( L − 1 ) ) T δ ( L ) . ∗ a ( L − 1 ) . ∗ ( 1 − a ( L − 1 ) )
由此,可以得到
δ
δ
的递进关系式。从而,
所以,
考虑正则化项后,
经过上面的推导,我们可以得到BP算法:
令 Δ(l)i,j=0 Δ i , j ( l ) = 0
对于 t=1,...,m t = 1 , . . . , m ,进行下面的运算:
- a(1)=x(t) a ( 1 ) = x ( t ) 并通过正向传播FP计算得到 a(l),l=2,3,...,L a ( l ) , l = 2 , 3 , . . . , L 。这里的 a(l) a ( l ) 包含了偏倚项,除了输出层。
- δ(L)=a(L)−y(t) δ ( L ) = a ( L ) − y ( t )
- 对于
l=L−1,...,2
l
=
L
−
1
,
.
.
.
,
2
(不等于1,因为是输入层),计算
δ(l)=((Θ(l))Tδ(l+1)).∗a(l).∗(1−a(l))
δ
(
l
)
=
(
(
Θ
(
l
)
)
T
δ
(
l
+
1
)
)
.
∗
a
(
l
)
.
∗
(
1
−
a
(
l
)
)
。
根据 δ δ 的定义,这是损失函数对于第l层的输入的偏导,因此是不包含偏倚项的(偏倚项没有输入,其激活直接等于1)。不过使用上面的公式计算得到的 δ δ 包含了偏倚项,在实际中舍弃这一项即可。 - 累加: Δ(l)i,j=Δ(l)i,j+a(l)jδ(l+1)i Δ i , j ( l ) = Δ i , j ( l ) + a j ( l ) δ i ( l + 1 ) 或 Δ(l)=Δ(l)+δ(l+1)(a(l))T Δ ( l ) = Δ ( l ) + δ ( l + 1 ) ( a ( l ) ) T
计算偏导,对于偏置项不进行正则化:
D(l)i,j=1m(Δ(l)i,j+λΘ(l)i,j)if j≠0D(l)i,j=1mΔ(l)i,jif j=0 D i , j ( l ) = 1 m ( Δ i , j ( l ) + λ Θ i , j ( l ) ) i f j ≠ 0 D i , j ( l ) = 1 m Δ i , j ( l ) i f j = 0
5.4 梯度检查
梯度检查可以确保BP算法的正确性,其思想是使用以下近似公式计算梯度,并与BP算法得到的梯度进行比较:
∂∂ΘJ(Θ)≈J(Θ+ϵ)−J(Θ−ϵ)2ϵ ∂ ∂ Θ J ( Θ ) ≈ J ( Θ + ϵ ) − J ( Θ − ϵ ) 2 ϵ
对于向量 Θ Θ 可以使用下式计算:
∂∂ΘjJ(Θ)≈J(Θ1,…,Θj+ϵ,…,Θn)−J(Θ1,…,Θj−ϵ,…,Θn)2ϵ ∂ ∂ Θ j J ( Θ ) ≈ J ( Θ 1 , … , Θ j + ϵ , … , Θ n ) − J ( Θ 1 , … , Θ j − ϵ , … , Θ n ) 2 ϵ
一般选取 ϵ=10−4 ϵ = 10 − 4 ,太小的话会出现数值计算问题。
在实际应用时,通过对比数值梯度与BP梯度判断正确性,这个过程只对比一次,然后关掉对比,进行训练。这是由于这个近似梯度的算法十分耗时。
计算数值梯度的Octave代码如下:
function numgrad = computeNumericalGradient(J, theta)
%COMPUTENUMERICALGRADIENT Computes the gradient using "finite differences"
%and gives us a numerical estimate of the gradient.
% numgrad = COMPUTENUMERICALGRADIENT(J, theta) computes the numerical
% gradient of the function J around theta. Calling y = J(theta) should
% return the function value at theta.
% Notes: The following code implements numerical gradient checking, and
% returns the numerical gradient.It sets numgrad(i) to (a numerical
% approximation of) the partial derivative of J with respect to the
% i-th input argument, evaluated at theta. (i.e., numgrad(i) should
% be the (approximately) the partial derivative of J with respect
% to theta(i).)
%
numgrad = zeros(size(theta));
perturb = zeros(size(theta));
e = 1e-4;
for p = 1:numel(theta)
% Set perturbation vector
perturb(p) = e;
loss1 = J(theta - perturb);
loss2 = J(theta + perturb);
% Compute Numerical Gradient
numgrad(p) = (loss2 - loss1) / (2*e);
perturb(p) = 0;
end
end
在实际使用时,可以通过构造一个小的神经网络来减小计算量。这里的一个技巧就是将多参数的计算 J(Θ) J ( Θ ) 的函数封装成单参数函数。
costFunc = @(p) nnCostFunction(p, input_layer_size, hidden_layer_size, ...
num_labels, X, y, lambda);
[cost, grad] = costFunc(nn_params);
numgrad = computeNumericalGradient(costFunc, nn_params);
5.5 随机初始化
在进行优化之前,需要初始化权重系数 Θ Θ ,如果所有的权重系数,都初始化为相同的数,例如0,那么,同一隐层的所有数据都将相同,通过反向传播得到的偏导也是相同的,经过一次迭代之后,同一层的权重系数都发生了相同的变化,从而同一层的权重系数又相同的,这样就导致了隐层仅学习到了相同的特征,不能发挥神经网络的作用。
为了解决这个问题,可以使用随机初始化的方式:
在
[−ϵ,ϵ]
[
−
ϵ
,
ϵ
]
之间的随机数初始化
Θ(l)i,j
Θ
i
,
j
(
l
)
,在代码中实现时,可以借助rand
函数先产生[0,1]的随机数,代码如下:
Theta1 = rand(10,11)*(2*Init_epsilon)-Init_epsilon
这里的
ϵ
ϵ
与梯度确认中的
ϵ
ϵ
没有关系。一个比较有效的选择
ϵ
ϵ
的方式是基于网络中的节点数目:对于
Θ(l)
Θ
(
l
)
选择如下
ϵ
ϵ
:
ϵ=6–√Sl+Sl+1−−−−−−−√
ϵ
=
6
S
l
+
S
l
+
1
其中,
Sl,Sl+1
S
l
,
S
l
+
1
分别是
Θ(l)
Θ
(
l
)
连接的相邻两层的节点数。
5.5 训练流程汇总
选择网络架构
首先要选择网络架构,包括层数和每层的节点数。其中:
* 输入层节点数 = 样本特征维度
* 输出层节点数 = 分类类别数
* 隐层节点数 = 通常越多越好,但是要平衡节点数增多带来的计算量增加
* 一般默认采用1层隐层,如果>1层,那么推荐每个隐层的节点数一样。
训练步骤
- 随机初始化权重
- 进行FP
- 计算损失函数,并通过BP计算偏导数
- 使用梯度确认确保BP算法的正确性,然后去掉梯度确认
- 使用梯度下降或其他算法优化损失函数,求得权重