梯度下降能求一元二次函数极值,因其损失函数可导、光滑且凸(a>0)或凹(a<0),梯度为零处即极值点;手写实现需关注学习率选取、初始值合理性、数值溢出防护、精度选择及解析解验证等关键工程细节。

梯度下降为什么能求一元二次函数极值
因为一元二次函数 f(x) = ax² + bx + c(a ≠ 0)是凸(a > 0)或凹(a )的光滑函数,其导数 <code>f'(x) = 2ax + b 连续且单调,梯度下降沿负梯度方向迭代必然收敛到唯一极值点(顶点 x* = -b/(2a))。但注意:它不保证一步到位,也不自动判断收敛精度——这些全得你手写控制。
核心迭代逻辑怎么写(C++基础实现)
关键就三步:计算当前梯度 → 更新参数 → 判断是否停。别套模板,直接用 double 和裸循环最清晰:
double x = 0.0; // 初始猜测(可任意,比如 0、1、-5)
double lr = 0.01; // 学习率,太大会震荡,太小收敛慢
double eps = 1e-6; // 收敛阈值(梯度绝对值 < eps)
int max_iter = 1000;
<p>for (int i = 0; i < max_iter; ++i) {
double grad = 2 <em> a </em> x + b; // f'(x),必须手算或用数值微分(不推荐)
if (std::abs(grad) < eps) break;
x = x - lr * grad; // 标准更新:x ← x − lr × ∇f(x)
}
// 最终 x 就是近似极值点</p>-
a、b、c是函数系数,需提前定义为const double或传入 - 学习率
lr对结果影响极大:a=1, b=-4时,lr=0.5可能发散;lr=0.001要跑上千步 - 别用
while (abs(grad) > eps)——没迭代上限容易死循环
如何避免常见数值陷阱
手写意味着所有边界都要自己兜底,这几个坑高频又隐蔽:
- 学习率选错:先试
lr = 1.0 / (2 * std::abs(a))(理论最大稳定步长),再按需下调 - 初始值离极值太远 + 大学习率 →
x溢出(变成inf或nan),加一句if (!std::isfinite(x)) break; - 用
float代替double→ 低精度下grad长期卡在1e-7级别,永远不满足eps,直接卡死 - 忘记
#include <cmath>导致std::abs、std::isfinite编译失败
和解析解对比验证是否写对了
手写梯度下降不是为了替代公式,而是理解过程。每次跑完,务必和解析解比对:
立即学习“C++免费学习笔记(深入)”;
double x_analytic = -b / (2 * a); double error = std::abs(x - x_analytic); std::cout << "GD result: " << x << ", analytic: " << x_analytic << ", error: " << error << "\n";
若 error > 1e-4,优先检查:学习率是否过大、是否漏了负号(x = x - lr * grad 不是 +)、a 是否为 0(退化成线性函数,无极值)。
真正麻烦的从来不是写几行迭代,而是当 a 接近 0、或者你把函数误写成 ax³ + bx² + c 时,梯度下降还在“认真”收敛到一个根本不存在的极值点——这时候,解析验证就是唯一的刹车片。


















