Levenberg-Macquardt python 代码

Levenberg-Macquardt python 代码

介绍

作为一个拟合常用的算法,对于我来说可以用来调参使得模拟程序生成的光谱尽可能的匹配上实验测得的光谱, L-M算法网上能找到的 python 代码有好多bug,没办法只能自己写一个啦。

网上有bug的代码

先放代码,再聊聊对 Levenberg-Macquardt 算法、The Steepest Descent Method 以及 Newton Method 的理解。

代码

把L-M算法写成了一个类,相当于一个工具箱以后想用的时候直接import就行了。

import numpy as np


class Levenberg_Macquardt:
    Experiment_Data = None  # y
    Experiment_Wavelength = None # x
    Floating_Parameter = None
    Function = None
    k_max = 200

    def __init__(self):
        self.Experiment_Data = None
        self.Experiment_Wavelength = None
        self.Floating_Parameter = None  # Parameters must be float-type numbers.
        self.k_max = 200
        self.Function = None

    def Derivation(self, Wavelength, n):
        X1 = np.array(self.Floating_Parameter.copy(), dtype=np.float64)
        X2 = np.array(self.Floating_Parameter.copy(), dtype=np.float64)
        X1[n][0] -= 1e-6 * X1[n][0]
        X2[n][0] += 1e-6 * X2[n][0]
        P1 = self.Function(X1, Wavelength)
        P2 = self.Function(X2, Wavelength)
        D = (P2 - P1) / (2e-6 * np.array(self.Floating_Parameter, dtype=np.float64)[n][0])
        return D

    def Jaccobi(self):
        num_data = self.Experiment_Data.shape[1]
        num_params = self.Floating_Parameter.shape[0]
        J = np.mat(np.zeros((num_data, num_params)))
        for i in range(num_data):
            for j in range(num_params):
                J[i, j] = self.Derivation(self.Experiment_Wavelength[i], j)
        return J

    def Residual(self, x):
        num_data = self.Experiment_Data.shape[1]
        Residual = np.mat(np.zeros((num_data, 1)))
        for i in range(num_data):
            Residual[i] = self.Function(np.array(x), self.Experiment_Wavelength[i]) - self.Experiment_Data[0, i]
        return Residual

    def Converge(self):
        k, v = 0, 2
        x = self.Floating_Parameter
        tau = 1e-3
        epsilon1 = 1e-8
        epsilon2 = 1e-8

        J = self.Jaccobi()
        A = J.T * J
        f = self.Residual(x)
        g = J.T * f

        found = np.linalg.norm(g) <= epsilon1
        u = tau * A.max()

        while (not found) and k < self.k_max:
            H = A + u * np.eye(x.shape[0])
            h = -H.I * g

            if np.linalg.norm(h) <= epsilon2 * (np.linalg.norm(x) + epsilon2):
                found = True
            else:
                x_new = x + h

                f = self.Residual(x)
                f_new = self.Residual(x_new)
                F = 0.5 * np.linalg.norm(f) ** 2
                F_new = 0.5 * np.linalg.norm(f_new) ** 2
                gain_ratio = (F - F_new) / ((0.5 * h.T * (u * h - g))[0, 0])

                if gain_ratio > 0:
                    x = x_new
                    J = self.Jaccobi()
                    A = J.T * J
                    f = self.Residual(x)
                    g = J.T * f

                    found = np.linalg.norm(g) <= epsilon1

                    u = u * max([1 / 3, 1 - (2 * gain_ratio - 1) ** 3])
                    v = 2
                else:
                    u = u * v
                    v = 2 * v
        self.Floating_Parameter = x

下面是一个简单的测试程序,验证一下我的代码是可行的。

from Levenberg_Macquardt import Levenberg_Macquardt
import numpy as np

n = 100
wl = np.linspace(0,10,n)
a_true,b_true,c_true,d_true = -3,-10,3,-2
y = [c_true*np.exp(a_true*i)+d_true*np.exp(b_true*i) for i in wl]
y = np.mat(y)


def Func(abcd:np.ndarray, iput):
    a = abcd[0][0]
    b = abcd[1][0]
    c = abcd[2][0]
    d = abcd[3][0]
    return c*np.exp(a*iput)+d*np.exp(b*iput)


L = Levenberg_Macquardt()
L.Experiment_Wavelength = wl
L.Experiment_Data = y
L.Function = Func
L.Floating_Parameter = np.mat([[-4],[-5],[4],[-4]])
L.Converge()
print(L.Floating_Parameter)

下面是程序的运行结果
程序运行结果
可以看到最后得到的参数结果接近真实值。

理解

本质上这种问题就是有一堆的数据点,然后给你一个函数让你调整函数中可以调整的参数来使得尽可能的匹配上这些数据点。其中衡量的标准就是 F ( x ) F(x) F(x) mean square error
图1
现在回归的方法都是要迭代的,相当于一步一步向着目标前进。其实很简单只需要在每步中想清楚两件事情就行了。

  1. 向什么方向走

  2. 走多远

图2
目前有三种方法来解决这个问题,最陡下降方法、高斯-牛顿法、L-M方法。本质上讲就是他们选取的前进方向和步长不一样。

最陡下降法
沿着这一点下降最快的方向前进,其实就是沿着梯度的反方向下降,如下图所示。
在这里插入图片描述

上面就是常常被提到,以及神经网络中应用很广泛的梯度下降方法。这种方法的好处是一开始下降很快因为梯度很陡,到接近极小值点时,下降很慢,因为梯度基本上没有了。所以回归刚开始的时候这种方法很好,但是到末期时这种方法收敛很慢。

牛顿方法
除了梯度下降的方法外就没有其它方法了吗?牛爵爷(牛顿)不这么认为。牛爵爷是这么思考这个问题的。怎么一步(我只要一步)就直接到极小值点处。

这件事可能吗?我们能拿到的信息就只有出发点的一阶导数、二阶导数信息呀,后面要走的路都是未知呀。等等极小值点处是一阶导数等于0,而二阶导数衡量了一阶导数的变化。那就不可以了吗

h = − F ′ ( x ) F ′ ′ ( x ) h=-\frac{F'(x)}{F''(x)} h=F(x)F(x)

只要直接变化 h 就到极小值点了。(不得不说牛爵爷还是猛呀!)
在这里插入图片描述
但是也有问题,你现在在的这一点如果里极小值点很进那还好说,这个可以估计的很准。如果你离极小值点很远怎么办呀,估计相当不准?一开始那不是乱无目的地乱跳吗。

L-M 方法
思想很简单引入了一个调整的因子,当因子很小时,L-M 方法近似为牛顿的方法,当因子很大时,L-M 方法近似为最陡下降法。再引入一个判别条件,自动调整改因子的值。即可实现在离极小值点很远时,采用最陡下降法。在离极小值很近时,采用牛顿方法。两个方法的优点都有了。

图3

很抱歉,我之前给出的代码中使用的是LBFGS优化器,而不是Levenberg-Marquardt算法。目前PyTorch并没有内置的Levenberg-Marquardt优化器,但你可以使用PyTorch提供的自动微分功能手动实现Levenberg-Marquardt算法。下面是一个使用Levenberg-Marquardt算法训练BP神经网络的示例代码: ```python import torch import torch.nn as nn from torch.autograd import Variable # 定义BP神经网络模型 class Net(nn.Module): def __init__(self, input_size, hidden_size, output_size): super(Net, self).__init__() self.fc1 = nn.Linear(input_size, hidden_size) self.fc2 = nn.Linear(hidden_size, output_size) def forward(self, x): x = torch.sigmoid(self.fc1(x)) x = self.fc2(x) return x # 定义损失函数和数据 input_size = 10 hidden_size = 20 output_size = 1 model = Net(input_size, hidden_size, output_size) criterion = nn.MSELoss() # 定义训练函数 def train(input, target): params = list(model.parameters()) num_params = sum(p.numel() for p in params if p.requires_grad) J = torch.zeros(target.size(0), num_params) e = torch.zeros(target.size()) def closure(): optimizer.zero_grad() output = model(input) loss = criterion(output, target) loss.backward() return loss optimizer = torch.optim.LBFGS(model.parameters(), lr=0.1, max_iter=20) def eval_Jacobian(e, J): for i in range(num_params): model.zero_grad() grad_params = torch.autograd.grad(e, params[i], retain_graph=True)[0] J[:, i] = grad_params.view(-1) for _ in range(10): output = model(input) e = output - target eval_Jacobian(e, J) H = torch.matmul(J.t(), J) diag_H = torch.diag(H) lambda_ = torch.max(diag_H) * 1e-2 H += lambda_ * torch.eye(num_params) delta = torch.matmul(torch.pinverse(H), torch.matmul(J.t(), e)) model.zero_grad() for param, delta_param in zip(params, delta): param.data -= delta_param.view(param.size()) optimizer.step(closure) # 训练模型 input = Variable(torch.randn(100, input_size)) target = Variable(torch.randn(100, output_size)) for i in range(10): train(input, target) # 使用训练好的模型进行预测 test_input = Variable(torch.randn(1, input_size)) print(model(test_input)) ``` 在上面的代码中,我们手动实现了Levenberg-Marquardt算法的关键步骤。首先,我们定义了BP神经网络模型、损失函数和数据。然后,在训练函数`train`中,我们使用自动微分功能计算了Jacobian矩阵和误差项e,并利用这些信息更新了模型的参数。最后,我们使用训练好的模型进行了一个简单的预测。 希望这个示例代码能满足你的需求!如果还有其他问题,请随时提问。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值