3-4 最速下降法
最速下降法
简述
\(\qquad\)最速下降法(Gradient Descent Method)又称梯度法 ,是最早提出且最为经典的多元函数无约束优化方法之一。该方法形式直观、原理简单,是其他无约数和约数优化方法的理论基础。由第二章 优化方法的数学基础可知,梯度方向是函数值增加最快的方向,而负梯度方向是函数值下降最快的方向。
\(\qquad\)该方法的基本思想是:从某点 \(\mathbf{x}\) 出发,取该点的负梯度方向作为搜索方向 \(\mathbf{d}\),使函数值在该点附近下降最快,直到满足优化设计目标。
原理
\(\qquad\)对于多元函数 \(f(X)\),在某一点 \(X^{(k)}\) 处,其负梯度方向 \(-\nabla f(X^{(k)})\) 是该点邻域内函数值下降最快的方向。
搜索方向:
\S\^{(k)} = -\\nabla f(X\^{(k)}) \\
迭代公式:
\X\^{(k+1)} = X\^{(k)} + \\alpha\^{(k)} \\cdot S\^{(k)} \\
其中:
- \(X^{(k)}\) 为第 \(k\) 次迭代的点
- \(S^{(k)}\) 为第 \(k\) 次迭代的搜索方向
- \(\alpha^{(k)}\) 为第 \(k\) 次迭代的步长(通过一维搜索确定)
几何性质
负梯度方向与等值线的关系:
- 负梯度方向与当前点的等值线垂直
- 负梯度方向与下一搜索点处的等值线相切
因此,相邻两次搜索方向相互垂直 ,梯度下降法的路径呈现锯齿形(zigzag)。
收敛特性:
- 越靠近极值点,前进速度越慢
- 缺点:收敛速度较慢(线性收敛)

特殊情况
若等高线为同心圆,即函数形如:
\F(X) = C(x_1\^2 + x_2\^2) + \\text{线性项} \\
则可以从任一点沿负梯度方向一步到达极值点。

具体步骤
对于给定初始点 \(X^{(0)}\) 和收敛精度 \(\varepsilon\):
- ① 确定初始点 \(X^{(0)}\),收敛精度 \(\varepsilon\),令 \(k=0\);
- ② 计算梯度 \(\nabla f(X^{(k)})\);
- ③ 一维搜索 :求最优步长 \(\alpha^{(k)}\),使 \(f(X^{(k)} + \alpha^{(k)} \cdot S^{(k)})\) 最小;
- ④ 更新迭代点:\(X^{(k+1)} = X^{(k)} + \alpha^{(k)} \cdot S^{(k)}\),\(k=k+1\);
- ⑤ 重复②~④步,直到:\(\|\nabla f(X^{(k)})\| \le \varepsilon\)。
实例计算
这里我们举个例子,手把手带大家使用最速下降法(梯度法),一步步来计算一个二元二次凸函数的最优解。
题目
\f(X) = x_1\^2 + 2x_2\^2 - 4x_1 - 2x_1 x_2,\\;\\; X\^{(0)} = \[1,1^T,\;\varepsilon = 0.01 \]
解答
首先计算 \(f(X)\) 的梯度函数:
\\\nabla f(X) = \\left(\\begin{array}{c} 2x_1 - 2x_2 -4 \\\\ -2x_1 + 4x_2 \\end{array}\\right) \\
代入 \(X^{(0)} = 1,1^T\),得:
\\\nabla f(X\^{(0)}) = \\left(\\begin{array}{c} 2(1) - 2(1) -4 \\\\ -2(1) + 4(1) \\end{array}\\right) = \\left(\\begin{array}{c} -4 \\\\ 2 \\end{array}\\right) \\
计算梯度的模长:
\\\\|\\nabla f(X\^{(0)})\\\| = \\sqrt{(-4)\^2 + 2\^2} = \\sqrt{20} = 2\\sqrt{5} \\approx 4.47 \> \\varepsilon \\
不满足收敛条件,迭代继续。
第 1 次迭代 (\(k=0\)):
- 确定搜索方向:
\S\^{(0)} = -\\nabla f(X\^{(0)}) = \\left(\\begin{array}{c} 4 \\\\ -2 \\end{array}\\right) \\
- 构造新迭代点:
\X\^{(1)} = X\^{(0)} + \\alpha\^{(0)} \\cdot S\^{(0)} = \\left(\\begin{array}{c} 1 \\\\ 1 \\end{array}\\right) + \\alpha\^{(0)} \\left(\\begin{array}{c} 4 \\\\ -2 \\end{array}\\right) = \\left(\\begin{array}{c} 4\\alpha\^{(0)} + 1 \\\\ -2\\alpha\^{(0)} + 1 \\end{array}\\right) \\
- 一维搜索求最优步长 \(\alpha^{(0)}\):
将 \(X^{(1)}\) 代入原函数,构造关于 \(\alpha\) 的一元函数 \(\varphi(\alpha)\):
\\\begin{aligned} \\varphi(\\alpha) \&= f(X\^{(1)}) \\\\ \&= (4\\alpha + 1)\^2 + 2(-2\\alpha + 1)\^2 - 4(4\\alpha + 1) - 2(4\\alpha + 1)(-2\\alpha + 1) \\\\ \&= 16\\alpha\^2 + 8\\alpha + 1 + 2(4\\alpha\^2 - 4\\alpha + 1) - 16\\alpha - 4 - 2(-8\\alpha\^2 + 6\\alpha - 1) \\\\ \&= 40\\alpha\^2 - 20\\alpha - 3 \\end{aligned} \\
对 \(\varphi(\alpha)\) 求导并令其为 0(或使用前面学过的一维搜索方法):
\\\varphi'(\\alpha) = 80\\alpha - 20 = 0 \\implies \\alpha\^{(0)} = 0.25 \\
此时得到新的迭代点:
\X\^{(1)} = \\left(\\begin{array}{c} 4(0.25) + 1 \\\\ -2(0.25) + 1 \\end{array}\\right) = \\left(\\begin{array}{c} 2 \\\\ 0.5 \\end{array}\\right) \\
- 检验收敛条件:
计算 \(X^{(1)}\) 处的梯度:
\\\nabla f(X\^{(1)}) = \\left(\\begin{array}{c} 2(2) - 2(0.5) - 4 \\\\ -2(2) + 4(0.5) \\end{array}\\right) = \\left(\\begin{array}{c} -1 \\\\ -2 \\end{array}\\right) \\
梯度的模长:
\\\\|\\nabla f(X\^{(1)})\\\| = \\sqrt{(-1)\^2 + (-2)\^2} = \\sqrt{5} \\approx 2.236 \> \\varepsilon \\
不满足收敛条件,迭代继续。
第 2 次迭代 (\(k=1\)):
- 确定搜索方向:
\S\^{(1)} = -\\nabla f(X\^{(1)}) = \\left(\\begin{array}{c} 1 \\\\ 2 \\end{array}\\right) \\
- 构造新迭代点:
\X\^{(2)} = X\^{(1)} + \\alpha\^{(1)} \\cdot S\^{(1)} = \\left(\\begin{array}{c} 2 \\\\ 0.5 \\end{array}\\right) + \\alpha\^{(1)} \\left(\\begin{array}{c} 1 \\\\ 2 \\end{array}\\right) = \\left(\\begin{array}{c} \\alpha\^{(1)} + 2 \\\\ 2\\alpha\^{(1)} + 0.5 \\end{array}\\right) \\
\(\qquad\cdots\)
\(\qquad\)后面以此类推,重复上述"一维搜索求步长 \(\to\) 更新迭代点 \(\to\) 计算梯度并判断收敛"的过程,直到梯度的模长小于等于 \(\varepsilon = 0.01\) 为止。
注:由于最速下降法在接近极值点时收敛速度较慢,搜索路径呈现"锯齿形",实际手写计算可能需要较多轮迭代才能满足精度要求,此处仅展示前两步以说明计算流程。在实际编程中,计算机可以在毫秒级完成成百上千次迭代。
程序框图

代码示例
\(\qquad\)对于最速下降法,给出以下 C++ 函数仅供参考。
C++代码示例:
cpp
// 计算向量范数
double norm(const vector<double>& v) {
double sum = 0.0;
for (double vi : v) {
sum += vi * vi;
}
return sqrt(sum);
}
// 最速下降法 求多元函数最小值点(支持任意维度)
// func: 目标函数
// grad: 梯度函数
// x0: 初始点(向量)
// eps: 收敛精度
vector<double> gradient_descent(
function<double(const vector<double>&)> func,
function<vector<double>(const vector<double>&)> grad,
const vector<double>& x0,
double eps,
int max_iter = 1000
) {
vector<double> x = x0;
cout << "\n迭代过程:" << endl;
cout << "步骤\t点 X\t\t\t函数值 f(X)\t\t梯度范数" << endl;
cout << "-----------------------------------------------------------------" << endl;
for (int iter = 0; iter < max_iter; ++iter) {
// 1. 计算当前点的梯度
vector<double> g = grad(x);
// 2. 计算梯度范数 ||∇f(X)||
double grad_norm = norm(g);
// 3. 检查收敛条件
if (grad_norm < eps) {
cout << iter << "\t";
for (size_t i = 0; i < x.size(); ++i) {
printf("%.6lf", x[i]);
cout << (i == x.size() - 1 ? "" : ", ");
}
printf("\t%.6lf\t\t%.6lf (收敛)\n", func(x), grad_norm);
break;
}
// 4. 确定搜索方向(负梯度方向) S^(k) = -∇f(X^(k))
vector<double> d(g.size());
for (size_t i = 0; i < g.size(); ++i)
d[i] = -g[i];
// 5. 定义一维搜索函数 phi(alpha) = f(x + alpha * d)
auto phi = [&](double alpha) {
vector<double> new_x(x.size());
for (size_t i = 0; i < x.size(); ++i)
new_x[i] = x[i] + alpha * d[i];
return func(new_x);
};
// 6. 使用进退法确定一维搜索区间 (复用之前的函数)
pair<double, double> bracket;
try {
bracket = bracket_minimum(phi, 0.0, 0.1); // 从 alpha=0 开始,初始步长 0.1
} catch (...) {
// 如果进退法失败,使用默认区间
printf("[Warning] bracket minimum failed! bracket = [0, 10]\n");
bracket = make_pair(0.0, 10.0);
}
// 7. 使用黄金分割法确定最优步长 alpha*
double alpha_star = golden_section(phi, bracket.first, bracket.second, eps);
// 8. 更新迭代点 X^(k+1) = X^(k) + alpha* * S^(k)
for (size_t i = 0; i < x.size(); ++i) {
x[i] += alpha_star * d[i];
}
// 打印迭代信息
cout << iter << "\t";
for (size_t i = 0; i < x.size(); ++i) {
printf("%.6lf", x[i]);
cout << (i == x.size() - 1 ? "" : ", ");
}
printf("\t%.6lf\t\t%.6lf\n", func(x), grad_norm);
}
return x;
}
完整示例
\(\qquad\)结合一维搜索方法,下面给出一个完整的二维函数优化示例。
完整 C++ 代码实现:
cpp
#include <iostream>
#include <vector>
#include <cmath>
#include <functional>
#include <iomanip>
#include <utility>
using namespace std;
// 进退法求初始区间
pair<double, double> bracket_minimum(function<double(double)> func, double a0, double h) {
// 进退法最大搜索次数
const int MAX_ITER = 1e4;
if (h <= 0)
throw invalid_argument("Step size h must be positive.");
// 递增,向左搜索
if (func(a0) <= func(a0 + h)) {
// printf("[debug] left search.\n");
double x0 = a0 + h, x1 = a0;
h *= 2;
double x2 = x1 - h; // 步长加倍
int iter = 0;
while (func(x2) < func(x1)) {
if (iter++ > MAX_ITER)
throw runtime_error("Max iterations exceeded in left expansion");
// printf("[debug] step %d: (%.2lf, %.2lf, %.2lf)\n", iter, x2, x1, x0);
x0 = x1;
x1 = x2;
h *= 2;
x2 = x1 - h; // 步长加倍
}
// printf("[debug] step %d: (%.2lf, %.2lf, %.2lf)\n", iter + 1, x2, x1, x0);
return make_pair(x2, x0);
}
// 递减,向右搜索
else {
// printf("[debug] right search.\n");
double x0 = a0;
double x1 = a0 + h;
h *= 2;
double x2 = x1 + h;
int iter = 0;
while (func(x2) < func(x1)) {
if (iter++ > MAX_ITER)
throw runtime_error("Max iterations exceeded in right expansion");
// printf("[debug] step %d: (%.2lf, %.2lf, %.2lf)\n", iter, x0, x1, x2);
x0 = x1;
x1 = x2;
h *= 2;
x2 = x1 + h; // 步长加倍
}
// printf("[debug] step %d: (%.2lf, %.2lf, %.2lf)\n", iter + 1, x0, x1, x2);
return make_pair(x0, x2);
}
}
// 黄金分割法 求最小值点(横坐标)
double golden_section(function<double(double)> func, double l, double r, double eps) {
double a = l, b = r, aa, bb;
int step = 0;
// printf("[debug] step %d: (%.5lf, %.5lf)\n", step, a, b);
while (b - a > eps) { // 点距准则
aa = b - 0.618 * (b - a); // 黄金分割点
bb = a + 0.618 * (b - a); // 黄金分割点
step++;
if (func(aa) < func(bb))
b = bb;
else
a = aa;
// printf("[debug] step %d: (%.5lf, %.5lf)\n", step, a, b);
}
return (a + b) / 2;
}
// 计算向量范数
double norm(const vector<double>& v) {
double sum = 0.0;
for (double vi : v) {
sum += vi * vi;
}
return sqrt(sum);
}
// 最速下降法 求多元函数最小值点(支持任意维度)
vector<double> gradient_descent(
function<double(const vector<double>&)> func,
function<vector<double>(const vector<double>&)> grad,
const vector<double>& x0,
double eps,
int max_iter = 1000
) {
vector<double> x = x0;
cout << "\n迭代过程:" << endl;
cout << "步骤\t点 X\t\t\t函数值 f(X)\t\t梯度范数" << endl;
cout << "-----------------------------------------------------------------" << endl;
for (int iter = 0; iter < max_iter; ++iter) {
// 1. 计算当前点的梯度
vector<double> g = grad(x);
// 2. 计算梯度范数 ||∇f(X)||
double grad_norm = norm(g);
// 3. 检查收敛条件
if (grad_norm < eps) {
cout << iter << "\t";
for (size_t i = 0; i < x.size(); ++i) {
printf("%.6lf", x[i]);
cout << (i == x.size() - 1 ? "" : ", ");
}
printf("\t%.6lf\t\t%.6lf (收敛)\n", func(x), grad_norm);
break;
}
// 4. 确定搜索方向(负梯度方向) S^(k) = -∇f(X^(k))
vector<double> d(g.size());
for (size_t i = 0; i < g.size(); ++i)
d[i] = -g[i];
// 5. 定义一维搜索函数 phi(alpha) = f(x + alpha * d)
auto phi = [&](double alpha) {
vector<double> new_x(x.size());
for (size_t i = 0; i < x.size(); ++i)
new_x[i] = x[i] + alpha * d[i];
return func(new_x);
};
// 6. 使用进退法确定一维搜索区间 (复用之前的函数)
pair<double, double> bracket;
try {
bracket = bracket_minimum(phi, 0.0, 0.1); // 从 alpha=0 开始,初始步长 0.1
} catch (...) {
// 如果进退法失败,使用默认区间
printf("[Warning] bracket minimum failed! bracket = [0, 10]\n");
bracket = make_pair(0.0, 10.0);
}
// 7. 使用黄金分割法确定最优步长 alpha*
double alpha_star = golden_section(phi, bracket.first, bracket.second, eps);
// 8. 更新迭代点 X^(k+1) = X^(k) + alpha* * S^(k)
for (size_t i = 0; i < x.size(); ++i) {
x[i] += alpha_star * d[i];
}
// 打印迭代信息
cout << iter << "\t";
for (size_t i = 0; i < x.size(); ++i) {
printf("%.6lf", x[i]);
cout << (i == x.size() - 1 ? "" : ", ");
}
printf("\t%.6lf\t\t%.6lf\n", func(x), grad_norm);
}
return x;
}
// ==========================================================
// 示例:二元二次凸函数
// f(X) = x1^2 + 2*x2^2 - 4*x1 - 2*x1*x2
// ==========================================================
int main() {
cout << "最速下降法求解多元函数极小值" << endl;
cout << "目标函数: f(X) = x1^2 + 2*x2^2 - 4*x1 - 2*x1*x2" << endl;
cout << "理论最优解: X* = [4.0, 2.0]^T, f(X*) = -8.0" << endl << endl;
// 定义一个二元函数 f(x1, x2) = x1^2 + 2*x2^2 - 4*x1 - 2*x1*x2
auto f = [](vector<double> x) {
return x[0] * x[0] + 2 * x[1] * x[1] - 2 * x[0] * x[1] - 4 * x[0];
};
// 梯度函数 ∇f(X) = [2*x1 - 2*x2 - 4, -2*x1 + 4*x2]^T
auto grad = [](vector<double> x) {
vector<double> g(2);
g[0] = 2 * x[0] - 2 * x[1] - 4; // ∂f/∂x1
g[1] = 4 * x[1] - 2 * x[0]; // ∂f/∂x2
return g;
};
double x1_init, x2_init, eps;
cout << "输入初始点 x1: ";
cin >> x1_init;
cout << "输入初始点 x2: ";
cin >> x2_init;
cout << "输入精度 eps: "; // 精度 ε
cin >> eps;
// 定义初始条件
vector<double> x0 = {x1_init, x2_init}; // X^(0)
// 执行最速下降法
vector<double> result = gradient_descent(f, grad, x0, eps);
// 输出最终结果
cout << "\n最终结果:" << endl;
cout << "最优解 X* = [";
for (size_t i = 0; i < result.size(); ++i) {
cout << result[i] << (i == result.size() - 1 ? "" : ", ");
}
cout << "]^T" << endl;
cout << "目标函数值 f(X*) = " << f(result) << endl;
vector<double> final_grad = grad(result);
double final_norm = norm(final_grad);
cout << "最终梯度范数 = " << final_norm << endl;
return 0;
}
总结
\(\qquad\)最速下降法作为经典的优化算法,具有以下特点:
优点:
- 理论明确、方法简单、概念清楚;
- 每迭代一次,除需进行一维搜索外,只需计算函数的一阶偏导数,计算量小;
- 对初始点要求不高;
- 存储需求小。
缺点:
- 收敛速度较慢,线性收敛
- 靠近极值点时搜索路径呈锯齿形,效率降低
- 对于等高线为扁椭圆的情况收敛很慢
适用场景:
- 作为其他优化算法的基础
- 对精度要求不高的初步优化
- 与其他方法结合使用(如作为牛顿法的初始阶段)
\(\qquad\)**按最速下降法搜索并不等同于以最快的速度到达最优点。**因为"负梯度方向是函数值最速下降方向"仅是迭代点邻域内的一种局部性质,从局部上看,在一点附近函数的下降是快的,但从整体上看则走了许多弯路,因此最速下降法下降速度并不算快,所以从整个迭代过程来看,最速下降法不具有"最速下降"的性质。
\(\qquad\)因为最速下降法采用了函数的负梯度方向作为下一步的搜索方向,所以收敛速度较慢。越是接近极值点,收敛越慢 ,这是它的主要缺点。但是,应用最速下降法可以使目标函数在最初几步下降很快,所以它可与其他无约束优化方法配合使用。特别是一些更有效的方法都是在对它进行改进后,或在它的启发下获得的,因此最速下降法仍是许多无约束和有约束优化方法的基础。在接下来的文章中,我们将介绍收敛速度更快的优化方法,如牛顿法和共轭梯度法。
本学习笔记参考资料:
1 白清顺, 孙靖民, 梁迎春. 机械优化设计 第7版M. 北京: 机械工业出版社, 2024. ISBN: 978-7-111-75103-8
2 武汉理工大学《最优化技术B》课程课件,授课教师:颜彬老师