首页 > 编程语言 >基于共轭梯度法的 BP 网络学习改进算法详解

基于共轭梯度法的 BP 网络学习改进算法详解

时间:2024-11-19 09:47:09浏览次数:3  
标签:mathbf 梯度 self wk 详解 BP np old 共轭

基于共轭梯度法的 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=1n​wij1​xi​+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=1mk​​wjlk+1​ajk​+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 算法的问题分析

(一)收敛速度问题

  1. 学习率的影响
    在传统 BP 算法中,学习率是一个关键参数。如果学习率设置不当,会严重影响收敛速度。若学习率过大,权重更新可能会过度,导致网络在误差曲面上跳过最小值点,甚至使训练过程发散;而学习率过小,则会使权重更新过于缓慢,需要大量的训练迭代才能使误差降低到可接受的水平。
  2. 梯度下降的局限性
    传统 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)=21​xTAx+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 diT​Adj​=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​+αk​dk​

其中, 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​)+βk​dk−1​​k=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​+αk​dk​) 最小化。一种常见的线搜索方法是精确线搜索,但在实际应用中,为了降低计算复杂度,也可以使用近似线搜索方法,如 Wolfe 条件或 Armijo 条件。

五、基于共轭梯度法的 BP 网络学习改进算法步骤

(一)初始化

  1. 网络参数初始化
    初始化 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​)(通过计算初始误差梯度得到)以及其他相关参数,如线搜索的初始步长等。
  2. 算法参数初始化
    选择共轭梯度法的参数计算方法(如 Fletcher - Reeves 或 Polak - Ribière),并设置停止准则相关参数,如最大训练次数、误差阈值等。

(二)训练过程

  1. 前向传播
    对于每个训练样本,按照传统 BP 算法的前向传播方式计算网络的输出。
  2. 误差计算与梯度计算
    计算当前样本的误差,并根据误差函数通过反向传播计算权重的梯度向量 ∇ E ( w k ) \nabla E(\mathbf{w}_{k}) ∇E(wk​)。
  3. 搜索方向更新
    根据选择的 β 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​)+βk​dk−1​。
  4. 步长确定
    通过线搜索(可以是精确线搜索或近似线搜索)确定步长 α k \alpha_{k} αk​,使得误差函数在 w k + α k d k \mathbf{w}_{k}+\alpha_{k}\mathbf{d}_{k} wk​+αk​dk​ 处取得最小值或满足一定的线搜索条件。
  5. 权重更新
    根据权重更新公式 w k + 1 = w k + α k d k \mathbf{w}_{k + 1}=\mathbf{w}_{k}+\alpha_{k}\mathbf{d}_{k} wk+1​=wk​+αk​dk​ 更新网络的权重和偏置。
  6. 停止准则判断
    检查是否满足停止准则,如达到最大训练次数或者当前误差小于设定的误差阈值。如果满足,则停止训练;否则,返回步骤 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

相关文章

  • 遗传算法工具箱详解
    遗传算法工具箱详解一、引言遗传算法作为一种强大的优化算法,在解决复杂的优化问题中得到了广泛应用。为了方便用户使用遗传算法,许多编程语言都提供了相应的遗传算法工具箱。这些工具箱集成了遗传算法的核心功能,包括种群初始化、适应度评估、选择、交叉、变异等操作,使用户......
  • 遗传算法原理与详解
    遗传算法原理与详解一、引言遗传算法(GeneticAlgorithm,GA)是一种基于自然选择和遗传学原理的优化搜索算法。它模拟生物进化过程中的遗传、变异、交叉等机制,在复杂的搜索空间中寻找最优解或近似最优解。遗传算法具有广泛的应用,包括函数优化、组合优化、机器学习、自动控制等......
  • Python用subprocess管理子进程在Windows平台实现平行效果
    在Python中,使用subprocess模块管理子进程时,如果你在Windows平台上尝试实现类似于Unix系统的“平行效果”(即父子进程可以同时运行),你可能会遇到一些问题。在Unix系统中,子进程是独立于父进程的,它们可以同时运行。但在Windows系统中,当你使用subprocess创建子进程时,默认情况下会存在父......
  • Python设计模式详解之1 —— 单例模式
    单例模式(SingletonPattern)是一种创建型设计模式,它确保一个类只有一个实例,并提供全局访问点。单例模式适用于需要确保全局唯一实例的场景,例如配置管理、日志记录器、数据库连接等。1.单例模式的特点全局唯一性:在整个应用程序的生命周期内,单例类只能有一个实例。全局访问:......
  • Python设计模式详解之2 —— 工厂模式
    工厂模式(FactoryPattern)是一种创建型设计模式,旨在定义一个用于创建对象的接口,但由子类决定实例化哪个类。工厂模式可以帮助我们将对象的创建与其使用分离,增强代码的可扩展性和维护性。工厂模式的分类简单工厂模式(SimpleFactoryPattern)工厂方法模式(FactoryMethodPatte......
  • Python设计模式详解之3 —— 抽象工厂模式
    抽象工厂模式也是一种创建型设计模式,它提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。它特别适合在需要创建多个相关对象且这些对象在逻辑上属于一个“产品族”时使用。结构:抽象产品:定义了产品家族中每个产品的接口。具体产品:实现抽象产品接口......
  • Java设计模式 —— Java七大设计原则详解
    文章目录前言一、单一职责原则1、概述2、案例演示二、接口隔离原则1、概述2、案例演示三、依赖倒转原则1、概述2、案例演示四、里氏替换原则1、概述2、案例演示五、开闭原则1、概述2、案例演示六、迪米特法则1、概述2、案例演示七、合成/聚合复用原则1、概述......
  • 24-OpenCVSharp —- Cv2.GetPerspectiveTransform()函数功能(透视变换矩阵)详解
    专栏地址:《OpenCV功能使用详解200篇》《OpenCV算子使用详解300篇》《Halcon算子使用详解300篇》内容持续更新,欢迎点击订阅Cv2.GetPerspectiveTransform()是OpenCV中用于计算透视变换矩阵的函数。透视变换(PerspectiveTransform)是计算机视觉和图像处理中常见......
  • 26-OpenCVSharp —- Cv2.WarpPerspective()函数功能(透视变换)详解
    专栏地址:《OpenCV功能使用详解200篇》《OpenCV算子使用详解300篇》《Halcon算子使用详解300篇》内容持续更新,欢迎点击订阅OpenCVSharp—Cv2.WarpPerspective()函数详解Cv2.WarpPerspective()是OpenCV中用于执行透视变换的函数。透视变换(PerspectiveTra......
  • 低级格式化和高级格式化有什么区别 低级格式化和高级格式化区别介绍【详解】
    根据:https://g.pconline.com.cn/x/1672/16727429.html整理 指代不同特点不同作用不同低级格式化被用于指代对磁盘进行划分柱面、磁道、扇区的操作。软盘的低级格式化通常是系统所内置支持的,通常情况下,对软盘的格式化操作即包含了低级格式化操作和高级格式化操作两个......