基于共轭梯度法的 BP 网络学习改进算法详解
一、引言
BP(Back Propagation)神经网络是一种强大的机器学习工具,广泛应用于模式识别、函数逼近、数据分类等领域。然而,传统的 BP 算法在训练过程中存在一些问题,例如收敛速度慢、容易陷入局部最优解等。共轭梯度法作为一种高效的优化算法,被引入到 BP 网络学习中,有效地改善了这些问题。本文将详细阐述基于共轭梯度法的 BP 网络学习改进算法,包括其原理、算法步骤以及丰富的代码示例。
二、传统 BP 网络学习算法概述
(一)网络结构与前向传播
BP 神经网络通常包含输入层、一个或多个隐藏层和输出层。设输入层有 n n n 个神经元,输入向量为 x = ( x 1 , x 2 , ⋯ , x n ) \mathbf{x}=(x_1,x_2,\cdots,x_n) x=(x1,x2,⋯,xn);第 i i i 个隐藏层有 m i m_i mi 个神经元;输出层有 p p p 个神经元,输出向量为 y = ( y 1 , y 2 , ⋯ , y p ) \mathbf{y}=(y_1,y_2,\cdots,y_p) y=(y1,y2,⋯,yp)。
在前向传播过程中,对于输入层到第一个隐藏层,第一个隐藏层神经元的输入为:
z j 1 = ∑ i = 1 n w i j 1 x i + b j 1 z_{j}^{1}=\sum_{i = 1}^{n}w_{ij}^{1}x_{i}+b_{j}^{1} zj1=∑i=1nwij1xi+bj1
其输出经过激活函数(如 Sigmoid 函数 f ( x ) = 1 1 + e − x f(x)=\frac{1}{1 + e^{-x}} f(x)=1+e−x1)处理:
a j 1 = f ( z j 1 ) a_{j}^{1}=f(z_{j}^{1}) aj1=f(zj1)
对于相邻隐藏层之间以及最后一个隐藏层到输出层,计算方式类似。例如,从第 k k k 个隐藏层到第 k + 1 k + 1 k+1 个隐藏层,第 k + 1 k + 1 k+1 个隐藏层神经元的输入为:
z l k + 1 = ∑ j = 1 m k w j l k + 1 a j k + b l k + 1 z_{l}^{k + 1}=\sum_{j = 1}^{m_{k}}w_{jl}^{k + 1}a_{j}^{k}+b_{l}^{k + 1} zlk+1=∑j=1mkwjlk+1ajk+blk+1
输出为 a l k + 1 = f ( z l k + 1 ) a_{l}^{k + 1}=f(z_{l}^{k + 1}) alk+1=f(zlk+1)。
(二)误差计算与反向传播
对于训练样本集 { ( x ( μ ) , t ( μ ) ) } μ = 1 N \{(\mathbf{x}^{(\mu)},\mathbf{t}^{(\mu)})\}_{\mu = 1}^{N} {(x(μ),t(μ))}μ=1N,其中 x ( μ ) \mathbf{x}^{(\mu)} x(μ) 是第 μ \mu μ 个输入样本, t ( μ ) \mathbf{t}^{(\mu)} t(μ) 是对应的目标输出。常用的误差函数是均方误差(MSE):
E = 1 2 N ∑ μ = 1 N ∑ i = 1 p ( y i ( μ ) − t i ( μ ) ) 2 E=\frac{1}{2N}\sum_{\mu = 1}^{N}\sum_{i = 1}^{p}(y_{i}^{(\mu)}-t_{i}^{(\mu)})^{2} E=2N1∑μ=1N∑i=1p(yi(μ)−ti(μ))2
在反向传播过程中,根据误差函数对权重的梯度来更新权重。以输出层到最后一个隐藏层的权重更新为例,计算误差对权重的梯度:
∂ E ∂ w i j L = ∑ μ = 1 N ( y i ( μ ) − t i ( μ ) ) f ′ ( z i L ) a j L − 1 \frac{\partial E}{\partial w_{ij}^{L}}=\sum_{\mu = 1}^{N}(y_{i}^{(\mu)}-t_{i}^{(\mu)})f^{\prime}(z_{i}^{L})a_{j}^{L - 1} ∂wijL∂E=∑μ=1N(yi(μ)−ti(μ))f′(ziL)ajL−1
其中, L L L 表示网络的总层数, f ′ ( x ) f^{\prime}(x) f′(x) 是激活函数的导数。然后根据梯度下降法更新权重:
w i j L ( t + 1 ) = w i j L ( t ) − η ∂ E ∂ w i j L w_{ij}^{L}(t + 1)=w_{ij}^{L}(t)-\eta\frac{\partial E}{\partial w_{ij}^{L}} wijL(t+1)=wijL(t)−η∂wijL∂E
其中, η \eta η 是学习率, t t t 表示训练次数。类似地,可以计算其他层之间的权重更新公式。
三、传统 BP 算法的问题分析
(一)收敛速度问题
- 学习率的影响
在传统 BP 算法中,学习率是一个关键参数。如果学习率设置不当,会严重影响收敛速度。若学习率过大,权重更新可能会过度,导致网络在误差曲面上跳过最小值点,甚至使训练过程发散;而学习率过小,则会使权重更新过于缓慢,需要大量的训练迭代才能使误差降低到可接受的水平。 - 梯度下降的局限性
传统 BP 算法基于简单的梯度下降方法,每次权重更新仅沿着当前梯度的负方向进行。在复杂的误差曲面中,这种更新方式可能会在一些平坦区域或狭长山谷区域花费大量时间,因为梯度方向可能并不直接指向全局最小值,导致收敛速度很慢。
(二)局部最优问题
由于传统 BP 算法沿着梯度下降方向更新权重,容易陷入局部最优解。在误差曲面中,当达到局部最优点时,梯度为零或接近零,此时权重更新停止,但这个局部最优点可能并非全局最优解,使得网络的性能无法进一步提升。
四、共轭梯度法原理
(一)共轭方向的概念
共轭梯度法基于共轭方向的概念。对于一个正定二次函数 f ( x ) = 1 2 x T A x + b T x + c f(\mathbf{x})=\frac{1}{2}\mathbf{x}^{T}\mathbf{A}\mathbf{x}+\mathbf{b}^{T}\mathbf{x}+c f(x)=21xTAx+bTx+c(其中 A \mathbf{A} A 是正定对称矩阵, x \mathbf{x} x、 b \mathbf{b} b 是向量, c c c 是常数),如果两个方向 d i \mathbf{d}_{i} di 和 d j \mathbf{d}_{j} dj 满足 d i T A d j = 0 \mathbf{d}_{i}^{T}\mathbf{A}\mathbf{d}_{j}=0 diTAdj=0( i ≠ j i\neq j i=j),则称这两个方向是关于矩阵 A \mathbf{A} A 共轭的。
在这种情况下,从任意初始点出发,依次沿着共轭方向进行一维搜索,可以在 n n n 步( n n n 是问题的维数)内收敛到二次函数的最小值点。对于非二次函数,共轭梯度法在一定程度上也能加速收敛,因为在局部范围内,许多函数可以近似看作二次函数。
(二)共轭梯度法的迭代公式
在共轭梯度法中,权重更新的迭代公式为:
w k + 1 = w k + α k d k \mathbf{w}_{k + 1}=\mathbf{w}_{k}+\alpha_{k}\mathbf{d}_{k} wk+1=wk+αkdk
其中, w k \mathbf{w}_{k} wk 是第 k k k 次迭代的权重向量, α k \alpha_{k} αk 是通过线搜索确定的步长, d k \mathbf{d}_{k} dk 是搜索方向。搜索方向的更新公式为:
d k = { − ∇ E ( w 0 ) k = 0 − ∇ E ( w k ) + β k d k − 1 k > 0 \mathbf{d}_{k}=\begin{cases}-\nabla E(\mathbf{w}_{0}) & k = 0\\-\nabla E(\mathbf{w}_{k})+\beta_{k}\mathbf{d}_{k - 1} & k > 0\end{cases} dk={−∇E(w0)−∇E(wk)+βkdk−1k=0k>0
其中, ∇ E ( w k ) \nabla E(\mathbf{w}_{k}) ∇E(wk) 是误差函数在 w k \mathbf{w}_{k} wk 处的梯度向量, β k \beta_{k} βk 有多种计算方法,常见的有 Fletcher - Reeves 公式:
β k = ∇ E ( w k ) T ∇ E ( w k ) ∇ E ( w k − 1 ) T ∇ E ( w k − 1 ) \beta_{k}=\frac{\nabla E(\mathbf{w}_{k})^{T}\nabla E(\mathbf{w}_{k})}{\nabla E(\mathbf{w}_{k - 1})^{T}\nabla E(\mathbf{w}_{k - 1})} βk=∇E(wk−1)T∇E(wk−1)∇E(wk)T∇E(wk)
以及 Polak - Ribière 公式:
β k = ( ∇ E ( w k ) − ∇ E ( w k − 1 ) ) T ∇ E ( w k ) ∇ E ( w k − 1 ) T ∇ E ( w k − 1 ) \beta_{k}=\frac{(\nabla E(\mathbf{w}_{k})-\nabla E(\mathbf{w}_{k - 1}))^{T}\nabla E(\mathbf{w}_{k})}{\nabla E(\mathbf{w}_{k - 1})^{T}\nabla E(\mathbf{w}_{k - 1})} βk=∇E(wk−1)T∇E(wk−1)(∇E(wk)−∇E(wk−1))T∇E(wk)
线搜索用于确定步长 α k \alpha_{k} αk,使得 E ( w k + α k d k ) E(\mathbf{w}_{k}+\alpha_{k}\mathbf{d}_{k}) E(wk+αkdk) 最小化。一种常见的线搜索方法是精确线搜索,但在实际应用中,为了降低计算复杂度,也可以使用近似线搜索方法,如 Wolfe 条件或 Armijo 条件。
五、基于共轭梯度法的 BP 网络学习改进算法步骤
(一)初始化
- 网络参数初始化
初始化 BP 网络的权重和偏置。可以将权重随机初始化为较小的值,例如在区间 [ − 0.5 , 0.5 ] [-0.5,0.5] [−0.5,0.5] 内。同时,初始化当前权重向量 w 0 \mathbf{w}_{0} w0、初始搜索方向 d 0 = − ∇ E ( w 0 ) \mathbf{d}_{0}=-\nabla E(\mathbf{w}_{0}) d0=−∇E(w0)(通过计算初始误差梯度得到)以及其他相关参数,如线搜索的初始步长等。 - 算法参数初始化
选择共轭梯度法的参数计算方法(如 Fletcher - Reeves 或 Polak - Ribière),并设置停止准则相关参数,如最大训练次数、误差阈值等。
(二)训练过程
- 前向传播
对于每个训练样本,按照传统 BP 算法的前向传播方式计算网络的输出。 - 误差计算与梯度计算
计算当前样本的误差,并根据误差函数通过反向传播计算权重的梯度向量 ∇ E ( w k ) \nabla E(\mathbf{w}_{k}) ∇E(wk)。 - 搜索方向更新
根据选择的 β k \beta_{k} βk 计算公式(如 Fletcher - Reeves 或 Polak - Ribière)计算 β k \beta_{k} βk,并更新搜索方向 d k = − ∇ E ( w k ) + β k d k − 1 \mathbf{d}_{k}=-\nabla E(\mathbf{w}_{k})+\beta_{k}\mathbf{d}_{k - 1} dk=−∇E(wk)+βkdk−1。 - 步长确定
通过线搜索(可以是精确线搜索或近似线搜索)确定步长 α k \alpha_{k} αk,使得误差函数在 w k + α k d k \mathbf{w}_{k}+\alpha_{k}\mathbf{d}_{k} wk+αkdk 处取得最小值或满足一定的线搜索条件。 - 权重更新
根据权重更新公式 w k + 1 = w k + α k d k \mathbf{w}_{k + 1}=\mathbf{w}_{k}+\alpha_{k}\mathbf{d}_{k} wk+1=wk+αkdk 更新网络的权重和偏置。 - 停止准则判断
检查是否满足停止准则,如达到最大训练次数或者当前误差小于设定的误差阈值。如果满足,则停止训练;否则,返回步骤 1 继续下一次迭代。
六、代码示例
以下是使用 Python 实现的基于共轭梯度法(采用 Fletcher - Reeves 公式)的 BP 网络学习改进算法的代码示例:
import numpy as np
# Sigmoid 激活函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# Sigmoid 函数的导数
def sigmoid_derivative(x):
return x * (1 - x)
class NeuralNetwork:
def __init__(self, input_size, hidden_size, output_size):
self.input_size = input_size
self.hidden_size = hidden_size
self.output_size = output_size
# 随机初始化权重
self.W1 = np.random.rand(self.input_size, self.hidden_size)
self.b1 = np.zeros((1, self.hidden_size))
self.W2 = np.random.rand(self.hidden_size, self.output_size)
self.b2 = np.zeros((1, self.output_size))
self.delta_W1 = np.zeros_like(self.W1)
self.delta_b1 = np.zeros_like(self.b1)
self.delta_W2 = np.zeros_like(self.W2)
self.delta_b2 = np.zeros_like(self.b2)
def forward_propagation(self, X):
self.z1 = np.dot(X, self.W1) + self.b1
self.a1 = sigmoid(self.z1)
self.z2 = np.dot(self.a1, self.W2) + self.b2
self.a2 = sigmoid(self.z2)
return self.a2
def back_propagation(self, X, y):
m = X.shape[0]
dZ2 = (self.a2 - y) * sigmoid_derivative(self.z2)
dW2 = np.dot(self.a1.T, dZ2)
db2 = np.sum(dZ2, axis=0, keepdims=True)
dZ1 = np.dot(dZ2, self.W2.T) * sigmoid_derivative(self.z1)
dW1 = np.dot(X.T, dZ1)
db1 = np.sum(dZ1, axis=0, keepdims=True)
return dW1, db1, dW2, db2
def conjugate_gradient_update(self, X, y, dW1, db1, dW2, db2, old_dW1, old_db1, old_dW2, old_db2):
# 计算当前梯度的范数平方
current_gradient_norm_squared_W1 = np.sum(dW1 ** 2)
current_gradient_norm_squared_b1 = np.sum(db1 ** 2)
current_gradient_norm_squared_W2 = np.sum(dW2 ** 2)
current_gradient_norm_squared_b2 = np.sum(db2 ** 2)
if np.sum(old_dW1 ** 2) + np.sum(old_db1 ** 2) + np.sum(old_dW2 ** 2) + np.sum(old_db2 ** 2) == 0:
beta_W1 = 0
beta_b1 = 0
beta_W2 = 0
beta_b2 = 0
else:
beta_W1 = current_gradient_norm_squared_W1 / np.sum(old_dW1 ** 2)
beta_b1 = current_gradient_norm_squared_b1 / np.sum(old_db1 ** 2)
beta_W2 = current_gradient_norm_squared_W2 / np.sum(old_dW2 ** 2)
beta_b2 = current_gradient_norm_squared_b2 / np.sum(old_db2 ** 2)
self.delta_W1 = -dW1 + beta_W1 * self.delta_W1
self.delta_b1 = -db1 + beta_b1 * self.delta_b1
self.delta_W2 = -dW2 + beta_W2 * self.delta_W2
self.delta_b2 = -db2 + beta_b2 * self.delta_b2
# 线搜索(这里简单示例,使用固定步长,实际可改进)
alpha = 0.01
self.W1 += alpha * self.delta_W1
self.b1 += alpha * self.delta_b1
self.W2 += alpha * self.delta_W2
self.b2 += alpha * self.delta_b2
def train(self, X, y, epochs):
for epoch in range(epochs):
output = self.forward_propagation(X)
dW1, db1, dW2, db2 = self.back_propagation(X, y)
if epoch == 0:
old_dW1 = dW1.copy()
old_db1 = db1.copy()
old_dW2 = dW2.copy()
old_db2 = db2.copy()
self.conjugate_gradient_update(X, y, dW1, db1, dW2, db2, old_dW1, old_db1, old_dW2, old_db2)
old_dW1 = dW1.copy()
old_db1 = db1.copy()
old_dW2 = dW2.copy()
old_db2 = db2.copy()
if epoch % 100 == 0:
error = np.mean((output - y) ** 2)
print(f'Epoch {epoch}: Error = {error}')
以下是一个简单的测试示例:
# 示例用法
X = np.array([[0, 0], [0, 1], [1, 0], [1, 1]])
y = np.array([[0], [1], [1], [0]])
neural_network = NeuralNetwork(2, 3, 1)
neural_network.train(X, y, 1000)
七、总结
基于共轭梯度法的 BP 网络学习改进算法通过利用共轭方向的性质,在每次迭代中选择更有利的搜索方向,克服了传统 BP 算法中简单梯度下降的一些局限性。这种方法在一定程度上提高了收敛速度,并减少了陷入局部最优解的可能性。代码示例展示了该算法在 Python 中的一种实现方式,在实际应用中,可以根据具体问题进一步优化算法,如改进线搜索方法、调整共轭梯度参数计算方法等,以获得更好的训练效果和网络性能。同时,与其他改进的 BP 算法相比,共轭梯度法在处理大规模数据和复杂网络结构时具有一定的优势,为解决实际的机器学习问题提供了更有效的手段。
标签:mathbf,梯度,self,wk,详解,BP,np,old,共轭 From: https://blog.csdn.net/ashyyyy/article/details/143872257