[最优化技术] 3-6 修正牛顿法

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 (收敛)

根据理论进行手算是不可能出现这样的情况的,造成这种情况的原因是程序实现的细节在起作用:

  1. 黄金分割法只把步长定位到 \(\varepsilon\) 的精度、返回末区间的中点 ;而切点附近的函数值极其平坦(与 \(0.5\) 之差只有 \(10^{-13}\sim10^{-16}\) 量级),于是程序给出的必然是一个 \(-0.001\) 量级的小非零步长(进退法允许向负方向探索,中点便落在负侧);
  2. 一旦离开切点,牛顿方向相对切线就有极小的倾角:严格展开可得 \(\\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不定或接近奇异时

牛顿法的优缺点:

优点:

  1. 收敛速度最快(二阶收敛)
  2. 对于二次函数,一次迭代即可收敛到精确解
  3. 充分利用了函数的二阶信息

缺点:

  1. 需要计算Hessian矩阵(所有二阶偏导数)
  2. 需要求解线性方程组或矩阵求逆,计算量大
  3. 当Hessian矩阵不正定或奇异时,可能不收敛
  4. 对初始点要求较高(标准牛顿法)

阻尼牛顿法的改进:

  1. 通过一维搜索确定最优步长,保证了函数值下降
  2. 具有更好的全局收敛性
  3. 即使初始点远离极值点,也能稳定收敛

适用场景:

  1. 目标函数的二阶导数容易计算
  2. 对收敛速度要求较高
  3. 问题规模适中(Hessian矩阵不太大)

修正牛顿法的改进:

  1. 将Hessian修正为正定矩阵,保证搜索方向一定是下降方向
  2. 在Hessian不正定(如鞍点附近)时仍能稳定迭代
  3. Hessian正定时自动退化为阻尼牛顿法(\(\lambda = 0\)),不损失原来的收敛速度

适用场景:Hessian矩阵不正定或接近奇异、且二阶导数可以计算的场合。

\(\qquad\)一般地,将牛顿法、阻尼牛顿法和修正牛顿法统称为牛顿型方法。牛顿型方法总体上迭代次数较少、计算速度较快。但是这类方法的主要缺点是每次迭代都要计算函数的二阶导数矩阵,并对该矩阵求逆。当目标函数的维数高时,算量和存储量大的缺点尤为明显。最速下降法的收敛速度比牛顿型方法慢,而牛顿型方法又存在上述缺点。

\(\qquad\)至此,我们已经介绍了两种基于梯度信息的优化方法:最速下降法原理简单但收敛较慢,牛顿法收敛速度快但需要计算Hessian矩阵。在接下来的文章中,我们将首先介绍坐标轮换法 ------一种无需计算梯度的直接搜索方法,它通过沿坐标轴方向依次搜索来寻找极值点,实现更为简单。之后,我们将介绍共轭梯度法,它巧妙地结合了最速下降法和牛顿法的优点,既避免了Hessian矩阵的计算,又克服了最速下降法的锯齿现象,具有较快的收敛速度。


本学习笔记参考资料:

1 白清顺, 孙靖民, 梁迎春. 机械优化设计 第7版M. 北京: 机械工业出版社, 2024. ISBN: 978-7-111-75103-8 在线链接

2 武汉理工大学《最优化技术B》课程课件,授课教师:颜彬老师