当前位置: 首页 > news >正文

最小二乘问题详解6:梯度下降法

1. 引言

在之前的两篇文章《最小二乘问题详解4:非线性最小二乘》、《最小二乘问题详解5:非线性最小二乘求解实例》中,笔者介绍了非线性最小二乘问题,并使用Gauss-Newton方法来进行求解。不过,求解非线性最小二乘问题还有另外一种方法——梯度下降法。

2. 背景

梯度下降法在人工智能的机器学习中使用的非常多,因为机器学习的训练过程通常被形式化为经验风险最小化问题(Empirical Risk Minimization, ERM):即在训练数据上最小化损失函数。而最小二乘问题其实也是经验风险最小化问题的一种,甚至机器学习的某些任务(比如回归)本身就是最小二乘问题。经验风险最小化问题是一种通用的函数拟合框架,不过损失函数有所不同,通常使用梯度下降法来进行求解。

那么为什么机器学习中使用梯度下降法来求解,而计算机视觉(SLAM、SfM、相机标定、BA)中使用Gauss-Newton/Levenberg-Marquardt来进行求解呢?这是因为机器学习的问题可以只用关心局部的“好解”,而不用像计算机视觉问题那样需要求解全局的“精确最小值”;另外,机器学习问题规模巨大、结构复杂,使用梯度下降法要简单、健壮、高效的多。

3. 求解

接下来就来介绍一下使用梯度下降法求解非线性最小二乘问题。还是先看非线性最小二乘问题的定义:

min⁡θS(θ)=r(θ)2=∑i=1mri(θ)2\min_{\theta} S(\theta) = \ \mathbf{r}(\theta)\ ^2 = \sum_{i=1}^m r_i(\theta)^2 θminS(θ)= r(θ) 2=i=1mri(θ)2

其中:
θ∈Rn\theta \in \mathbb{R}^nθRn:待优化的参数向量(比如曲线的系数)
r(θ)=[r1(θ)⋮rm(θ)]\mathbf{r}(\theta) = \begin{bmatrix} r_1(\theta) \\ \vdots \\ r_m(\theta) \end{bmatrix}r(θ)=r1(θ)rm(θ):残差向量,ri(θ)=yi−f(xi;θ)r_i(\theta) = y_i - f(x_i; \theta)ri(θ)=yif(xi;θ)
S(θ)S(\theta)S(θ):目标函数(损失函数),是我们要最小化的残差平方和

梯度下降法的核心思想是:在当前点,沿着目标函数下降最快的方向走一步,然后重复。而这个“最快下降方向”就是负梯度方向−∇S(θ)-\nabla S(\theta)S(θ)。因此问题的关键在于计算目标函数S(θ)=r(θ)2S(\theta) = \ \mathbf{r}(\theta)\ ^2S(θ)= r(θ) 2的梯度。根据求导的链式法则:

∇S(θ)=∂∂θ(r(θ)Tr(θ))=2J(θ)Tr(θ)\nabla S(\theta) = \frac{\partial}{\partial \theta} \left( \mathbf{r}(\theta)^T \mathbf{r}(\theta) \right) = 2 \, J(\theta)^T \mathbf{r}(\theta) S(θ)=θ(r(θ)Tr(θ))=2J(θ)Tr(θ)

其中:
J(θ)J(\theta)J(θ):雅可比矩阵(Jacobian),大小为 m×nm \times nm×n
J(θ)=[∂r1∂θ1⋯∂r1∂θn⋮⋱⋮∂rm∂θ1⋯∂rm∂θn]=∂r∂θTJ(\theta) = \begin{bmatrix} \frac{\partial r_1}{\partial \theta_1} & \cdots & \frac{\partial r_1}{\partial \theta_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial r_m}{\partial \theta_1} & \cdots & \frac{\partial r_m}{\partial \theta_n} \end{bmatrix} = \frac{\partial \mathbf{r}}{\partial \theta^T} J(θ)=θ1r1θ1rmθnr1θnrm=θTr
即目标函数的梯度是:∇S(θ)=2J(θ)Tr(θ)\nabla S(\theta) = 2 J(\theta)^T \mathbf{r}(\theta)S(θ)=2J(θ)Tr(θ)

另一方面,在每次梯度下降之后,需要更新参数向量:

θk+1=θk−α⋅∇S(θk)=θk−2α⋅JkTrk\boxed{ \theta_{k+1} = \theta_k - \alpha \cdot \nabla S(\theta_k) = \theta_k - 2\alpha \cdot J_k^T \mathbf{r}_k } θk+1=θkαS(θk)=θk2αJkTrk

其中:
θk\theta_kθk:第 kkk 次迭代的参数
α>0\alpha > 0α>0:学习率(step size),控制步长
Jk=J(θk)J_k = J(\theta_k)Jk=J(θk)rk=r(θk)\mathbf{r}_k = \mathbf{r}(\theta_k)rk=r(θk)

因此,将梯度下降方法完整的流程总结如下:

  1. 初始化:选一个初始猜测 θ₀
  2. 设置学习率 α(例如 0.01)
  3. 对 k = 0, 1, 2, … 直到收敛:
    a. 计算残差:rk=y−f(x;θk)r_k = y - f(x; θ_k)rk=yf(x;θk)
    b. 计算雅可比矩阵:Jk=J(θk)J_k = J(θ_k)Jk=J(θk)
    c. 计算梯度:gk=2JkTrkg_k = 2 J_k^T r_kgk=2JkTrk
    d. 更新参数:θk+1=θk−αgkθ_{k+1} = θ_k - α g_kθk+1=θkαgk
    e. 检查是否收敛:Δθ=θk+1−θk<εΔθ = θ_{k+1} - θ_k < εΔθ=θk+1θk<εgk<εg_k < εgk<εS(θ)S(θ)S(θ)变化很小
  4. 输出最终参数 θ

4. 实例

从上述求解过程可以看到,梯度下降法其实比之前文章中介绍的Gauss-Newton方法要简单很多,那么这里还是给出一个只使用Eigen实现梯度下降法求解非线性最小二乘问题的例子。例子中模型函数为f(x;θ)=aebxf(x; \boldsymbol{\theta}) = a e ^{bx}f(x;θ)=aebx

#include <Eigen/Dense>
#include <cmath>
#include <iostream>
#include <random>
#include <vector>using namespace std;
using namespace Eigen;// 模型函数: y = a * exp(b * x)
double model(double x, const Vector2d& theta) {double a = theta(0);double b = theta(1);return a * exp(b * x);
}// 计算残差: r_i = y_i - f(x_i; a, b)
VectorXd computeResiduals(const vector<double>& x_data,const vector<double>& y_data, const Vector2d& theta) {int N = x_data.size();VectorXd r(N);for (int i = 0; i < N; ++i) {r(i) = y_data[i] - model(x_data[i], theta);}return r;
}// 计算 Jacobian 矩阵 (N x 2): ∂r_i/∂a, ∂r_i/∂b
MatrixXd computeJacobian(const vector<double>& x_data, const Vector2d& theta) {int N = x_data.size();MatrixXd J(N, 2);double a = theta(0);double b = theta(1);for (int i = 0; i < N; ++i) {double x = x_data[i];double exp_bx = exp(b * x);  // exp(b*x)J(i, 0) = -exp_bx;          // ∂r/∂a = -exp(b*x)J(i, 1) = -a * exp_bx * x;  // ∂r/∂b = -a * exp(b*x) * x}return J;
}int main() {// ========================// 1. 真实参数// ========================Vector2d true_params;true_params << 2.0, -0.3;  // a=2.0, b=-0.3 → y = 2 * exp(-0.3 * x)cout << "真实参数: a = " << true_params(0) << ", b = " << true_params(1)<< endl;// ========================// 2. 生成带噪声的数据// ========================int N = 20;vector<double> x_data(N), y_data(N);random_device rd;mt19937 gen(rd());normal_distribution<double> noise(0.0, 0.05);  // 小噪声for (int i = 0; i < N; ++i) {x_data[i] = -2.0 + i * 0.4;  // x 从 -2 到 6double y_true = model(x_data[i], true_params);y_data[i] = y_true + noise(gen);}// ========================// 3. 初始化参数// ========================Vector2d theta;theta << 1.0, 0.0;  // 初始猜测: a=1.0, b=0.0cout << "初始猜测: a = " << theta(0) << ", b = " << theta(1) << endl;// ========================// 4. 梯度下降法// ========================int max_iter = 500;double alpha = 5e-3;  // 学习率double tol = 1e-6;cout << "\n开始梯度下降...\n";cout << "迭代\t残差平方和\t\t参数 a\t\t参数 b\n";cout << "----\t----------\t\t------\t\t------\n";for (int iter = 0; iter < max_iter; ++iter) {// 计算残差VectorXd r = computeResiduals(x_data, y_data, theta);double cost = r.squaredNorm();// 计算梯度MatrixXd J = computeJacobian(x_data, theta);Vector2d gradient = 2.0 * J.transpose() * r;// 打印当前状态(每10次)if (iter % 10 == 0) {cout << iter << "\t" << cost << "\t\t" << theta(0) << "\t\t" << theta(1)<< endl;}// 终止条件if (gradient.norm() < tol) {cout << "收敛!梯度范数: " << gradient.norm() << endl;break;}// 更新参数theta -= alpha * gradient;}// ========================// 5. 输出结果// ========================cout << "\n--- 拟合完成 ---" << endl;cout << "估计参数: a = " << theta(0) << ", b = " << theta(1) << endl;cout << "真实参数: a = " << true_params(0) << ", b = " << true_params(1)<< endl;return 0;
}

运行结果如下:

真实参数: a = 2, b = -0.3
初始猜测: a = 1, b = 0开始梯度下降...
迭代    残差平方和              参数 a          参数 b
----    ----------              ------          ------
0       22.7591         1               0
10      1.11435         1.72284         -0.345
20      0.100641                1.93634         -0.301778
30      0.0326195               1.99193         -0.294493
40      0.0286004               2.00545         -0.292882
50      0.0283681               2.0087          -0.292503
60      0.0283548               2.00948         -0.292413
70      0.028354                2.00967         -0.292391
80      0.0283539               2.00971         -0.292386
90      0.0283539               2.00972         -0.292385
100     0.0283539               2.00972         -0.292384
110     0.0283539               2.00973         -0.292384
120     0.0283539               2.00973         -0.292384
收敛!梯度范数: 9.36104e-07--- 拟合完成 ---
估计参数: a = 2.00973, b = -0.292384
真实参数: a = 2, b = -0.3

求解的关键还是在于计算雅可比矩阵,对于问题模型函数f(x;θ)=aebxf(x; \boldsymbol{\theta}) = a e ^{bx}f(x;θ)=aebx来说,雅可比矩阵应该是:

J(θ)=[∂(y1−aebx1)∂a∂(y1−aebx1)∂b∂(y2−aebx2)∂a∂(y2−aebx2)∂b⋮⋮∂(ym−aebxm)∂a∂(ym−aebxm)∂b]=−[ebx1aebx1x1ebx2aebx2x2⋮⋮ebxmaebxmxm]J(\theta) = \begin{bmatrix} \frac{\partial (y_1-ae^{bx_1})}{\partial a} & \frac{\partial (y_1-ae^{bx_1})}{\partial b} \\ \frac{\partial (y_2-ae^{bx_2})}{\partial a} & \frac{\partial (y_2-ae^{bx_2})}{\partial b} \\ \vdots & \vdots \\ \frac{\partial (y_m-ae^{bx_m})}{\partial a} & \frac{\partial (y_m-ae^{bx_m})}{\partial b} \\ \end{bmatrix}= -\begin{bmatrix} e^{bx_1} & ae^{bx_1}x_1 \\ e^{bx_2} & ae^{bx_2}x_2 \\ \vdots & \vdots \\ e^{bx_m} & ae^{bx_m}x_m \\ \end{bmatrix} J(θ)=a(y1aebx1)a(y2aebx2)a(ymaebxm)b(y1aebx1)b(y2aebx2)b(ymaebxm)=ebx1ebx2ebxmaebx1x1aebx2x2aebxmxm

对比代码中的实现:

// 计算 Jacobian 矩阵 (N x 2): ∂r_i/∂a, ∂r_i/∂b
MatrixXd computeJacobian(const vector<double>& x_data, const Vector2d& theta) {int N = x_data.size();MatrixXd J(N, 2);double a = theta(0);double b = theta(1);for (int i = 0; i < N; ++i) {double x = x_data[i];double exp_bx = exp(b * x);  // exp(b*x)J(i, 0) = -exp_bx;          // ∂r/∂a = -exp(b*x)J(i, 1) = -a * exp_bx * x;  // ∂r/∂b = -a * exp(b*x) * x}return J;
}

另外,除了迭代过程中的初始条件和迭代停止条件,控制步长的学习率也需要注意。设置的学习率过小,迭代次数就会很长导致收敛很慢;而设置的学习率过大,就容易略过最优解导致结果不问题。因此,在实际的工程应用中,通常不会使用原始的梯度下降法,而是根据需求使用不同优化版本的梯度下降法。关于这一点,有机会的话会在后续的文章中进一步论述。

http://www.dtcms.com/a/540569.html

相关文章:

  • Linux内核RDMA计数器机制:深入解析与实现原理
  • iOS 反编译防护工具与实战组合 从静态侦察到 IPA 成品加固的工程化路径
  • 微信小程序组件中二维码生成问题解决方案
  • 网站文件解压北仑装修公司哪家最好
  • 《微信小程序》第八章:“我的“设计
  • 基于 Launcher3 的 iOS 风格桌面 04 拖拽和移位
  • django model Manager
  • 前端数据可视化实战:Chart.js vs ECharts 深度对比与实现指南
  • 霍山县网站建设公司寻花问柳专注做一家男人最爱的网站
  • LInux(一)VMware虚拟机中安装CentOS7
  • MATLAB基于对数灰关联度的IOWGA算子最优组合预测模型
  • 企业开源网站系统网页制作软件
  • Linux存储软件栈剖析之第4篇:Linux文件系统的实现
  • Excel怎么将八位数字设置为日期格式?
  • 怎么做系部网站首页做外贸的零售网站
  • 宁波企业网站排名优化公司网络系统管理技能大赛答案
  • 本地网站源码便民信息发布平台
  • Linux 内核内存屏障(中文译文)
  • “二分查找” 咋用?像 “查字典翻页码”,3 步找到目标值​
  • 在Ubuntu中使用Docker打包程序(Conda, pip)
  • 网站优化软件费用大连网站推广优化
  • 31_AI智能体工具插件之增强LangChain注册工具构建高效可控的AI工具生态
  • 怎么做自建站wordpress 导航加图标
  • 解决uni-app通用上传与后端接口不匹配问题:原生上传文件方法封装 ✨
  • 管廊建设网站线上推广网络公司
  • 汽车交互式系统专利拆解:VR/AR 画面生成与挡风玻璃异步转换的流畅性测试
  • Python爬虫实战:中信标普 50 指数数据获取与趋势分析
  • 浦江网站建设站酷app
  • 什么是技术架构、数据架构、业务架构、应用架构、产品架构和项目架构?
  • LLaMA-Factory 集成了哪些超参数调优框架?及 Optuna + Weights Biases + TensorBoard对比分析