3-6 修正牛顿法
修正牛顿法
引例:一道"切线点"例题
上一篇中,阻尼牛顿法解决了"步长"问题,但如果问题出在"方向"上,它也无能为力。请看下面这道特意构造的题:
\f(X) = \\frac{1}{2}x_1\^2 + \\frac{1}{2}x_2\^4 - 2x_2\^2 + 2x_2,\\qquad X\^{(0)} = \[1,\\ 0^T,\qquad \varepsilon = 0.01 \]

它的梯度与Hessian矩阵为:
\\\nabla f(X) = \\left(\\begin{array}{c} x_1 \\\\ 2x_2\^3 - 4x_2 + 2 \\end{array}\\right),\\qquad H(X) = \\left(\\begin{array}{cc} 1 \& 0 \\\\ 0 \& 6x_2\^2 - 4 \\end{array}\\right) \\
当 \(|x_2| < \sqrt{2/3} \approx 0.816\) 时,\(\frac{\partial^2 f}{\partial x_2^2} = 6x_2^2 - 4 < 0\),Hessian矩阵不定。
该函数共有三个驻点:
- \((0,\ 1)\)(局部极小,\(f = 0.5\))
- \((0,\ 0.618)\)(局部极大,\(f \approx 0.545\))
- \((0,\ -1.618)\)(全局极小,\(f \approx -5.045\))。
起点 \(X^{0}(1,\ 0)\) 恰好是一个特殊的点:此处
\\\nabla f(X\^{(0)}) = \[1,\\ 2^T,\qquad H(X^{(0)}) = \left(\begin{array}{cc} 1 & 0 \\ 0 & -4 \end{array}\right),\qquad d^{(0)} = -H(X\^{(0)})^{-1}\nabla f(X^{(0)}) = -1,\\ 0.5^T \]
于是
\\[\\nabla f(X\^{(0)})^T d^{(0)} = 1 \times (-1) + 2 \times 0.5 = 0 \]
即 \(X^{0}\) 处的牛顿方向恰好与梯度正交------它恰好是等值线的切线方向。如果沿着这个方向进行一维搜索:
\\\phi(\\alpha) = f(1 - \\alpha,\\ 0.5\\alpha) = \\frac{1}{2} + \\frac{\\alpha\^4}{32} \\ge \\frac{1}{2} = f(X\^{(0)}) \\
无论正步长还是负步长,函数值都无法下降,因为 \(X^{0}\) 正是这个方向上的最小点了。
标准牛顿法和阻尼牛顿法在这道题上的实际表现:
(1)标准牛顿法:方向不能下降,步长又固定为 1,程序只能"照直走"。三次迭代中函数值步步上升(\(0.500 \to 0.531 \to 0.545\)),最后因梯度趋零而停止:
| 步骤 | 点 X | 函数值 f(X) | 梯度范数 |
|---|---|---|---|
| 0 | 0.000000, 0.500000 | 0.531250 | 2.236068 |
| 1 | 0.000000, 0.600000 | 0.544800 | 0.250000 |
| 2 | 0.000000, 0.617391 | 0.545085 | 0.032000 |
| 3 | 0.000000, 0.617391 | 0.545085 | 0.001099 (收敛) |
"收敛"到的是 \((0,\ 0.6174)\)、\(f = +0.545\)------这正是局部极大点 \((0,\ 0.618)\) 附近!牛顿法不区分极小和极大,只要梯度为零就停,把极大点当成了最优解返回。
(2)阻尼牛顿法:前面分析已经提过了,问题是出在方向上,一维搜索也无用武之地:沿 \(d^{(0)}\) 整条直线都没有能下降的点。因此这个方法也是无能为力。
不过...... 不过实际运行程序时你会看到一个有趣的现象:本文的进退法会向负方向探索,程序靠 -0.001 量级的负步长缓慢"蠕动",居然一点点绕出了切线区,9 次迭代后到达了全局极小:
| 步骤 | 点 X | 函数值 f(X) | 梯度范数 | 步长 |
|---|---|---|---|---|
| 0 | 1.001060, -0.000530 | 0.500000 | 2.236068 | -0.001060 |
| 1 | 1.016416, -0.008208 | 0.500000 | 2.238439 | -0.015339 |
| 2 | 1.099988, -0.049998 | 0.499994 | 2.272774 | -0.082222 |
| 3 | 1.361779, -0.181372 | 0.499225 | 2.459439 | -0.237995 |
| 4 | 1.956949, -0.493254 | 0.471314 | 3.036088 | -0.437053 |
| 5 | 2.780805, -1.111928 | -0.065861 | 4.214846 | -0.420990 |
| 6 | 1.003256, -1.803485 | -4.319258 | 4.627025 | 0.639221 |
| 7 | -0.036899, -1.635229 | -5.042657 | 2.710454 | 1.036779 |
| 8 | 0.000415, -1.618083 | -5.045085 | 0.207513 | 1.011237 |
| 9 | 0.000415, -1.618083 | -5.045085 | 0.000713 (收敛) |
根据理论进行手算是不可能出现这样的情况的,造成这种情况的原因是程序实现的细节在起作用:
- 黄金分割法只把步长定位到 \(\varepsilon\) 的精度、返回末区间的中点 ;而切点附近的函数值极其平坦(与 \(0.5\) 之差只有 \(10^{-13}\sim10^{-16}\) 量级),于是程序给出的必然是一个 \(-0.001\) 量级的小非零步长(进退法允许向负方向探索,中点便落在负侧);
- 一旦离开切点,牛顿方向相对切线就有极小的倾角:严格展开可得 \(\\nabla f(X)^T d \approx \frac{3}{2}x_2^2 > 0\)(二阶小量),射线的负方向上出现一条极浅的下降谷,一维搜索于是"顺坡下滑",一步步绕了出去。
换句话说:可复现,但没有一般保证------若一维搜索只搜正方向,切点上将寸步难行。
标准牛顿法收敛到了错误的点,阻尼牛顿法也束手无策,根源在于:当 \(H\) 不正定时,牛顿方向本身可能就不是下降方向。
要治本,得从方向入手------这正是本节要介绍的方法。
问题与改进
前面提到,标准牛顿法的三个问题里,阻尼牛顿法通过一维搜索解决了步长 带来的问题,但方向问题依然存在。
由方向导数的公式 \(\frac{\partial f}{\partial S} = \\nabla f(X)^T \boldsymbol{S}\) 可知,方向 \(d\) 是下降方向当且仅当
\\[\\nabla f(X)^T d < 0 \]
对于牛顿方向 \(d = -H(X)^{-1}\nabla f(X)\),有
\\[\\nabla f^T d = -\\nabla f^T H^{-1} \\nabla f \]
当Hessian矩阵 \(H\) 正定时,\(H^{-1}\) 也正定,上式恒为负,牛顿方向一定是下降方向;但当 \(H\) 不正定时,牛顿方向可能满足 \(\\nabla f^T d > 0\),即它本身就是上升方向。此时无论怎样调整正步长,函数值都无法下降,一维搜索也无能为力。
修正牛顿法 的思路是:从方向 入手,把Hessian矩阵修正为正定矩阵 。一种常用而简单的修正是给 \(H\) 的对角线加上正数 \(\lambda\):
\H \\to H + \\lambda I \\qquad (\\lambda \\ge 0) \\
设 \(\lambda_{\min}\) 为 \(H\) 的最小特征值,则当 \(\lambda > -\lambda_{\min}\) 时,\(H + \lambda I\) 正定,此时
\d = -\[H + \\lambda I^{-1} \nabla f \]
必为下降方向,因为正定矩阵的逆也是正定矩阵,故
\\[\\nabla f^T d = -\\nabla f^T H + \\lambda I^{-1} \\nabla f < 0 \]
迭代公式与具体步骤
采用阻尼形式的修正牛顿法,迭代公式为:
\X\^{(k+1)} = X\^{(k)} + \\alpha\^{(k)} d\^{(k)},\\qquad d\^{(k)} = -\[H(X\^{(k)}) + \\lambda I^{-1} \nabla f(X^{(k)}) \]
其中 \(\alpha^{(k)}\) 仍由一维搜索确定。
\(\qquad\)实际程序计算中,一般不直接求特征值来判断 \(\lambda\),而是从 \(\lambda = 0\) 开始倍增 (0,1,2,4,\(\cdots\)),直到 \(H + \lambda I\) 正定为止;是否正定可以用 Cholesky 分解 是否成功来判断。
对于给定初始点 \(X^{(0)}\) 和收敛精度 \(\varepsilon\):
- ① 确定初始点 \(X^{(0)}\),收敛精度 \(\varepsilon\),令 \(k=0\);
- ② 计算梯度 \(\nabla f(X^{(k)})\),若 \(\|\nabla f(X^{(k)})\| \le \varepsilon\) 则停止;
- ③ 计算Hessian矩阵 \(H(X^{(k)})\),从 \(\lambda=0\) 开始倍增,直到 \(H + \lambda I\) 正定;
- ④ 解线性方程组 \((H + \lambda I)d^{(k)} = -\nabla f(X^{(k)})\),得修正牛顿方向 \(d^{(k)}\);
- ⑤ 一维搜索求最优步长 \(\alpha^{(k)}\),更新迭代点 \(X^{(k+1)} = X^{(k)} + \alpha^{(k)} d^{(k)}\),\(k=k+1\);
- ⑥ 重复②~⑤步,直到满足收敛条件。
若 \(H\) 本身正定,则取到 \(\lambda = 0\),修正牛顿法退化为阻尼牛顿法;在极值点附近,\(\lambda = 0\) 且 \(\alpha \to 1\),二阶收敛速度得以保持。\(\lambda > 0\) 时方向偏向负梯度方向,收敛速度可能变慢,但保证了每一步都是下降方向。
代码示例
修正牛顿法 C++ 代码示例:
cpp
// 判断对称矩阵是否正定(Cholesky 分解是否成功)
bool is_positive_definite(const vector<vector<double>>& A) {
int n = A.size();
vector<vector<double>> L(n, vector<double>(n, 0.0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
double sum = A[i][j];
for (int k = 0; k < j; ++k) {
sum -= L[i][k] * L[j][k];
}
if (i == j) {
if (sum <= 0)
return false;
L[i][i] = sqrt(sum);
} else {
L[i][j] = sum / L[j][j];
}
}
}
return true;
}
// 修正牛顿法 求多元函数最小值点(H 修正为 H + lambda*I 使其正定)
vector<double> modified_newton_method(
function<double(const vector<double>&)> func,
function<vector<double>(const vector<double>&)> grad,
function<vector<vector<double>>(const vector<double>&)> hess,
const vector<double>& x0,
double eps,
int max_iter = 1000
) {
vector<double> x = x0;
int n = x0.size();
cout << "\n迭代过程:" << endl;
cout << "步骤\t点 X\t\t\t函数值 f(X)\t\t梯度范数\t步长\t\tlambda" << endl;
cout << "----------------------------------------------------------------------------------" << endl;
for (int iter = 0; iter < max_iter; ++iter) {
vector<double> g = grad(x);
double grad_norm = norm(g);
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\t\t(收敛)\n", func(x), grad_norm);
break;
}
// 1. 计算 Hessian 矩阵,并从 lambda = 0 开始倍增,直到 H + lambda*I 正定
vector<vector<double>> H = hess(x);
double lambda = 0.0;
vector<vector<double>> H_mod = H;
while (!is_positive_definite(H_mod)) {
lambda = (lambda == 0.0) ? 1.0 : lambda * 2.0;
H_mod = H;
for (int i = 0; i < n; ++i) {
H_mod[i][i] += lambda;
}
}
// 2. 修正牛顿方向 d = -(H + lambda*I)^(-1) * g
vector<double> d = solve_linear_system(H_mod, g);
for (int i = 0; i < n; ++i) {
d[i] = -d[i];
}
// 3. 一维搜索确定步长
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);
};
pair<double, double> bracket;
try {
bracket = bracket_minimum(phi, 0.0, 0.1);
} catch (...) {
printf("[Warning] bracket minimum failed!\n");
bracket = make_pair(0.0, 10.0);
}
double alpha_star = golden_section(phi, bracket.first, bracket.second, eps);
// 4. 更新迭代点
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\t\t%.6lf\t%.1lf\n", func(x), grad_norm, alpha_star, lambda);
}
return x;
}
完整示例
C++代码实现(完整):
cpp
#include <iostream>
#include <vector>
#include <cmath>
#include <functional>
#include <iomanip>
#include <utility>
using namespace std;
// 计算向量范数
double norm(const vector<double>& v) {
double sum = 0.0;
for (double vi : v) {
sum += vi * vi;
}
return sqrt(sum);
}
// 进退法求初始区间
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)) {
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");
x0 = x1;
x1 = x2;
h *= 2;
x2 = x1 - h;
}
return make_pair(x2, x0);
} else {
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");
x0 = x1;
x1 = x2;
h *= 2;
x2 = x1 + h;
}
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;
while (b - a > eps) {
aa = b - 0.618 * (b - a);
bb = a + 0.618 * (b - a);
if (func(aa) < func(bb))
b = bb;
else
a = aa;
}
return (a + b) / 2;
}
// 求解线性方程组 H * x = b (高斯消元法)
vector<double> solve_linear_system(vector<vector<double>> H, vector<double> b) {
int n = b.size();
// 增广矩阵
for (int i = 0; i < n; ++i) {
H[i].push_back(b[i]);
}
// 高斯消元
for (int i = 0; i < n; ++i) {
// 找主元
int max_row = i;
for (int k = i + 1; k < n; ++k) {
if (abs(H[k][i]) > abs(H[max_row][i])) {
max_row = k;
}
}
// 交换行
swap(H[i], H[max_row]);
// 消元
for (int k = i + 1; k < n; ++k) {
double factor = H[k][i] / H[i][i];
for (int j = i; j <= n; ++j) {
H[k][j] -= factor * H[i][j];
}
}
}
// 回代
vector<double> x(n);
for (int i = n - 1; i >= 0; --i) {
x[i] = H[i][n];
for (int j = i + 1; j < n; ++j) {
x[i] -= H[i][j] * x[j];
}
x[i] /= H[i][i];
}
return x;
}
// 判断对称矩阵是否正定(Cholesky 分解是否成功)
bool is_positive_definite(const vector<vector<double>>& A) {
int n = A.size();
vector<vector<double>> L(n, vector<double>(n, 0.0));
for (int i = 0; i < n; ++i) {
for (int j = 0; j <= i; ++j) {
double sum = A[i][j];
for (int k = 0; k < j; ++k) {
sum -= L[i][k] * L[j][k];
}
if (i == j) {
if (sum <= 0)
return false;
L[i][i] = sqrt(sum);
} else {
L[i][j] = sum / L[j][j];
}
}
}
return true;
}
// 修正牛顿法 求多元函数最小值点(H 修正为 H + lambda*I 使其正定)
vector<double> modified_newton_method(
function<double(const vector<double>&)> func,
function<vector<double>(const vector<double>&)> grad,
function<vector<vector<double>>(const vector<double>&)> hess,
const vector<double>& x0,
double eps,
int max_iter = 1000
) {
vector<double> x = x0;
int n = x0.size();
cout << "\n迭代过程:" << endl;
cout << "步骤\t点 X\t\t\t函数值 f(X)\t\t梯度范数\t步长\t\tlambda" << endl;
cout << "----------------------------------------------------------------------------------" << endl;
for (int iter = 0; iter < max_iter; ++iter) {
vector<double> g = grad(x);
double grad_norm = norm(g);
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\t\t(收敛)\n", func(x), grad_norm);
break;
}
// 1. 计算 Hessian 矩阵,并从 lambda = 0 开始倍增,直到 H + lambda*I 正定
vector<vector<double>> H = hess(x);
double lambda = 0.0;
vector<vector<double>> H_mod = H;
while (!is_positive_definite(H_mod)) {
lambda = (lambda == 0.0) ? 1.0 : lambda * 2.0;
H_mod = H;
for (int i = 0; i < n; ++i) {
H_mod[i][i] += lambda;
}
}
// 2. 修正牛顿方向 d = -(H + lambda*I)^(-1) * g
vector<double> d = solve_linear_system(H_mod, g);
for (int i = 0; i < n; ++i) {
d[i] = -d[i];
}
// 3. 一维搜索确定步长
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);
};
pair<double, double> bracket;
try {
bracket = bracket_minimum(phi, 0.0, 0.1);
} catch (...) {
printf("[Warning] bracket minimum failed!\n");
bracket = make_pair(0.0, 10.0);
}
double alpha_star = golden_section(phi, bracket.first, bracket.second, eps);
// 4. 更新迭代点
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\t\t%.6lf\t%.1lf\n", func(x), grad_norm, alpha_star, lambda);
}
return x;
}
// ==========================================================
// 示例:Hessian 会变号(不正定)的函数
// f(X) = 0.5*x1^2 + 0.5*x2^4 - 2*x2^2 + 2*x2
// 全局极小点: X* = [0.0, -1.618034]^T, f(X*) = -5.045085
// ==========================================================
int main() {
cout << "修正牛顿法求解多元函数极小值" << endl;
cout << "目标函数: f(X) = 0.5*x1^2 + 0.5*x2^4 - 2*x2^2 + 2*x2" << endl;
cout << "理论全局最优解: X* = [0.0, -1.618034]^T, f(X*) = -5.045085" << endl << endl;
// 目标函数
auto f = [](vector<double> x) {
return 0.5 * x[0] * x[0] + 0.5 * x[1] * x[1] * x[1] * x[1] - 2 * x[1] * x[1] + 2 * x[1];
};
// 梯度函数 ∇f(X) = [x1, 2*x2^3 - 4*x2 + 2]^T
auto grad = [](vector<double> x) {
vector<double> g(2);
g[0] = x[0];
g[1] = 2 * x[1] * x[1] * x[1] - 4 * x[1] + 2;
return g;
};
// Hessian矩阵函数 H(X) = [[1, 0], [0, 6*x2^2 - 4]]
auto hess = [](vector<double> x) {
vector<vector<double>> H(2, vector<double>(2));
H[0][0] = 1; // ∂²f/∂x1²
H[0][1] = 0; // ∂²f/∂x1∂x2
H[1][0] = 0; // ∂²f/∂x2∂x1
H[1][1] = 6 * x[1] * x[1] - 4; // ∂²f/∂x2²
return H;
};
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};
vector<double> result = modified_newton_method(f, grad, hess, 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;
}
回到引例:修正牛顿法求解
回到开头那道切线点的题目(初始点 1 0,精度 0.01),用本节带一维搜索的修正牛顿法求解。
第 1 次迭代 :\(H(X^{(0)}) = \mathrm{diag}(1, -4)\) 不正定,\(\lambda\) 从 0 开始倍增:\(\lambda = 0, 1, 2\) 时 \(H + \lambda I\) 仍不定;\(\lambda = 4\) 时 \(H + 4I = \mathrm{diag}(5, 0)\) 只是半正定(Cholesky 分解第二个主元为 0,判定失败);继续倍增到 \(\lambda = 8\),\(H + 8I = \mathrm{diag}(9, 4)\) 正定,通过。修正方向
\d\^{(0)} = -\[H(X\^{(0)}) + 8I^{-1} \nabla f(X^{(0)}) = -1/9,\\ -0.5^T \]
一维搜索求得步长 \(\alpha^{(0)} = 3.259937\),于是
\\\begin{aligned} X\^{(1)} \&= \[1 - 0.1111 \\times 3.259937,\\ -0.5 \\times 3.259937^T \\ &= 0.637785,\\ -1.629968^T,\\ f(X^{(1)}) &= -4.840861 \end{aligned} \]
一步就跳到了全局极小附近。
第 2 次迭代 :该点的 Hessian 已正定(\(6x_2^2 - 4 = 11.94 > 0\)),\(\lambda\) 取 0,修正牛顿法退化为阻尼牛顿法,一维搜索后一步精确命中:
\X\^{(2)} = \[-0.000945,\\ -1.618133^T,\quad f(X^{(2)}) = -5.045084 \]
此时 \(\|\nabla f\| = 0.001494 < \varepsilon\),收敛。完整迭代过程:
| 步骤 | 点 X | 函数值 f(X) | 梯度范数 | 步长 | lambda |
|---|---|---|---|---|---|
| 0 | 0.637785, -1.629968 | -4.840861 | 2.236068 | 3.259937 | 8.0 |
| 1 | -0.000945, -1.618133 | -5.045084 | 0.653210 | 1.001482 | 0.0 |
| 2 | -0.000945, -1.618133 | -5.045084 | 0.001494 (收敛) |
三种方法同题对比:
| 方法 | 迭代次数 | 结果 |
|---|---|---|
| 标准牛顿法 | 3 | 停在局部极大点附近 (0, 0.6174),f = +0.545 |
| 阻尼牛顿法 | 9 | 靠负步长蠕动"绕"到全局极小,无一般保证 |
| 修正牛顿法 | 2 | 到达全局极小 (0, -1.618),f = -5.045 |
从切线点出发、Hessian不定、方向一度完全失效------修正牛顿法只用 2 次迭代就稳稳到达了全局极小点。
总结
牛顿法、阻尼牛顿法与修正牛顿法的比较:
| 特性 | 牛顿法 | 阻尼牛顿法 | 修正牛顿法 |
|---|---|---|---|
| 步长 | 固定为1 | 通过一维搜索确定 | 通过一维搜索确定 |
| 方向 | 牛顿方向,H不正定时可能上升 | 牛顿方向(不修正) | 经 H+λI 修正,保证为下降方向 |
| 收敛性 | 局部收敛,要求初始点靠近极值点 | 全局收敛性更好 | 对Hessian不正定的情形更稳健 |
| 计算量 | 较小(无需一维搜索) | 较大(需一维搜索) | 最大(还需正定判定与修正) |
| 适用场景 | 二次函数或初始点较好时 | 一般情况,更稳健 | Hessian不定或接近奇异时 |
牛顿法的优缺点:
优点:
- 收敛速度最快(二阶收敛)
- 对于二次函数,一次迭代即可收敛到精确解
- 充分利用了函数的二阶信息
缺点:
- 需要计算Hessian矩阵(所有二阶偏导数)
- 需要求解线性方程组或矩阵求逆,计算量大
- 当Hessian矩阵不正定或奇异时,可能不收敛
- 对初始点要求较高(标准牛顿法)
阻尼牛顿法的改进:
- 通过一维搜索确定最优步长,保证了函数值下降
- 具有更好的全局收敛性
- 即使初始点远离极值点,也能稳定收敛
适用场景:
- 目标函数的二阶导数容易计算
- 对收敛速度要求较高
- 问题规模适中(Hessian矩阵不太大)
修正牛顿法的改进:
- 将Hessian修正为正定矩阵,保证搜索方向一定是下降方向
- 在Hessian不正定(如鞍点附近)时仍能稳定迭代
- Hessian正定时自动退化为阻尼牛顿法(\(\lambda = 0\)),不损失原来的收敛速度
适用场景:Hessian矩阵不正定或接近奇异、且二阶导数可以计算的场合。
\(\qquad\)一般地,将牛顿法、阻尼牛顿法和修正牛顿法统称为牛顿型方法。牛顿型方法总体上迭代次数较少、计算速度较快。但是这类方法的主要缺点是每次迭代都要计算函数的二阶导数矩阵,并对该矩阵求逆。当目标函数的维数高时,算量和存储量大的缺点尤为明显。最速下降法的收敛速度比牛顿型方法慢,而牛顿型方法又存在上述缺点。
\(\qquad\)至此,我们已经介绍了两种基于梯度信息的优化方法:最速下降法原理简单但收敛较慢,牛顿法收敛速度快但需要计算Hessian矩阵。在接下来的文章中,我们将首先介绍坐标轮换法 ------一种无需计算梯度的直接搜索方法,它通过沿坐标轴方向依次搜索来寻找极值点,实现更为简单。之后,我们将介绍共轭梯度法,它巧妙地结合了最速下降法和牛顿法的优点,既避免了Hessian矩阵的计算,又克服了最速下降法的锯齿现象,具有较快的收敛速度。
本学习笔记参考资料:
1 白清顺, 孙靖民, 梁迎春. 机械优化设计 第7版M. 北京: 机械工业出版社, 2024. ISBN: 978-7-111-75103-8 在线链接
2 武汉理工大学《最优化技术B》课程课件,授课教师:颜彬老师