基于c++ eigen的Nelder-Mead算法(仿照scipy)

简介

本文展示了用C++(Eigen)实现的Nelder-Mead算法,该实现仿照了Python SciPy库中的scipy.optimize.fmin函数。虽然目前仅完成了基础功能(fmin不支持full_output和retall),但已经可以应用于实际优化问题。

Nelder-Mead算法简介

Nelder-Mead算法(也称单纯形法)是一种常用的无导数优化方法,特别适合于:

  • 函数导数难以计算或不存在的情况
  • 寻找非线性函数的局部最小值
  • 维度适中的问题

这种算法被广泛应用于机器学习、计算机视觉、信号处理等领域,特别是当目标函数计算成本高昂时尤为有用。

SciPy版本的特点:

  1. 边界约束支持:SciPy版本添加了对变量边界的支持,通过bounds参数实现。它使用np.clip确保所有单纯形顶点都在指定边界内,这是标准Nelder-Mead算法的扩展。
  2. 自适应参数策略:通过adaptive参数启用,基于Gao和Han
    2012年的论文。这种策略根据问题维度自动调整反射、扩展和收缩参数,使算法在高维问题上表现更好。
  3. 双重收敛条件:同时使用函数值容差(fatol)和参数值容差(xatol)作为终止条件,这比只用单一条件更稳健。
  4. 完善的终止处理:支持通过最大迭代次数(maxiter)或最大函数评估次数(maxfev)限制算法运行时间,并提供详细的终止状态。

更详细的分析见本文结尾。

代码

纯头文件库,包含opti.h,opti.inl。还有一个测试程序main.cpp
opti.h:

#ifndef MY_OPTIMIZE_H
#define MY_OPTIMIZE_H


#include <Eigen/Core>
#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <limits>
#include <vector>

// NelderMead算法选项
struct NelderMeadOptions {
    double xatol = 1e-4;     // x收敛容差
    double fatol = 1e-4;     // 函数值收敛容差
    int maxiter = -1;        // 最大迭代次数,-1表示使用默认值
    int maxfev = -1;         // 最大函数评估次数,-1表示使用默认值
    bool disp = false;       // 是否显示收敛消息
    bool return_all = false; // 是否返回所有迭代的结果
    bool adaptive = false;   // 是否使用自适应参数

    // 边界,每个变量的(min, max)对,std::nullopt表示无边界
    std::vector<std::pair<double, double>> bounds;
};

// NelderMead算法结果
struct NelderMeadResult {
    Eigen::VectorXd x;   // 最优解
    double fun;          // 最优解对应的函数值
    int nit;             // 迭代次数
    int nfev;            // 函数评估次数
    int status;          // 状态码:0=成功,1=达到最大函数评估次数,2=达到最大迭代次数
    bool success;        // 是否成功
    std::string message; // 状态信息
    std::vector<Eigen::VectorXd> allvecs; // 所有迭代的结果(如果return_all=true)
};


/**
 * Nelder-Mead单纯形算法
 *
 * @param func 目标函数
 * @param x0 初始猜测点
 * @param initial_simplex 初始单纯形,如果提供则覆盖x0
 * @param options 算法参数选项
 * @param callback 回调函数,每次迭代后调用
 * @return 优化结果
 */
template <typename Scalar = double>
NelderMeadResult
minimize_neldermead(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
                    const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
                    const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex = nullptr,
                    const NelderMeadOptions& options = NelderMeadOptions(),
                    std::function<bool(const NelderMeadResult&)> callback = nullptr);

/**
 * 简化版Nelder-Mead优化函数,类似于scipy的fmin
 *
 * @param func 目标函数
 * @param x0 初始猜测点
 * @param initial_simplex 初始单纯形,如果提供则覆盖x0
 * @param xtol x收敛容差
 * @param ftol 函数值收敛容差
 * @param maxiter 最大迭代次数
 * @param maxfun 最大函数评估次数
 * @param full_output 是否返回完整输出
 * @param disp 是否显示收敛消息
 * @param retall 是否返回所有迭代的结果
 * @param callback 回调函数
 * @return 优化结果点或完整结果(取决于full_output)
 */
template <typename Scalar = double>
Eigen::Matrix<Scalar, Eigen::Dynamic, 1>
fmin(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
     const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
     const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex = nullptr,
     double xtol = 1e-4,
     double ftol = 1e-4,
     int maxiter = -1,
     int maxfun = -1,
     bool full_output = false,
     bool disp = true,
     bool retall = false,
     std::function<bool(const NelderMeadResult&)> callback = nullptr);


#include "opti.inl"

#endif //MY_OPTIMIZE_H

opti.inl:

#ifndef MY_OPTIMIZE_INL
#define MY_OPTIMIZE_INL

#include <algorithm>
#include <cmath>
#include <iostream>

template <typename Scalar>
NelderMeadResult minimize_neldermead(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
                                     const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
                                     const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex,
                                     const NelderMeadOptions& options,
                                     std::function<bool(const NelderMeadResult&)> callback) {
    using Vector = Eigen::Matrix<Scalar, Eigen::Dynamic, 1>;
    using Matrix = Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>;

    // 提取选项参数
    double xatol = options.xatol;
    double fatol = options.fatol;
    int maxiter = options.maxiter;
    int maxfev = options.maxfev;
    bool disp = options.disp;
    bool return_all = options.return_all;
    bool adaptive = options.adaptive;
    const auto& bounds = options.bounds;

    // 设置算法参数
    double rho, chi, psi, sigma;
    if (adaptive) {
        double dim = static_cast<double>(x0.size());
        rho = 1.0;
        chi = 1.0 + 2.0 / dim;
        psi = 0.75 - 1.0 / (2.0 * dim);
        sigma = 1.0 - 1.0 / dim;
    } else {
        rho = 1.0;
        chi = 2.0;
        psi = 0.5;
        sigma = 0.5;
    }

    double nonzdelt = 0.05;
    double zdelt = 0.00025;

    // 检查边界
    bool has_bounds = !bounds.empty();
    Vector lower_bound, upper_bound;
    if (has_bounds) {
        if (bounds.size() != static_cast<size_t>(x0.size())) {
            throw std::invalid_argument("Bounds size does not match x0 size");
        }

        lower_bound(x0.size());
        upper_bound(x0.size());

        for (int i = 0; i < x0.size(); ++i) {
            lower_bound(i) = bounds[i].first;
            upper_bound(i) = bounds[i].second;
        }

        if ((lower_bound.array() > upper_bound.array()).any()) {
            throw std::invalid_argument("Nelder Mead - one of the lower bounds is greater than an upper bound.");
        }

        if (((x0.array() < lower_bound.array()) || (x0.array() > upper_bound.array())).any() && disp) {
            std::cerr << "Warning: Initial guess is not within the specified bounds" << std::endl;
        }
    }

    // 将点裁剪到边界内
    auto clip = [&](const Vector& x) -> Vector {
        if (!has_bounds)
            return x;

        return x.cwiseMax(lower_bound).cwiseMin(upper_bound);
    };

    // 按函数值排序单纯形顶点
    auto sort_simplex = [](Vector& fsim, Matrix& sim) {
        const int N = sim.cols();

        std::vector<std::pair<Scalar, int>> pairs(N + 1);
        for (int i = 0; i < N + 1; ++i) {
            pairs[i] = {fsim(i), i};
        }

        std::sort(pairs.begin(), pairs.end());

        // 重排fsim和sim
        Vector fsim_sorted(N + 1);
        Matrix sim_sorted(N + 1, N);
        for (int i = 0; i < N + 1; ++i) {
            fsim_sorted(i) = pairs[i].first;
            sim_sorted.row(i) = sim.row(pairs[i].second);
        }

        fsim = fsim_sorted;
        sim = sim_sorted;
    };

    // 创建初始单纯形
    Vector x0_copy = x0;
    if (has_bounds) {
        x0_copy = clip(x0_copy);
    }

    int N = x0_copy.size();
    Matrix sim;

    if (initial_simplex == nullptr) {
        sim = Matrix(N + 1, N);
        sim.row(0) = x0_copy;

        for (int k = 0; k < N; ++k) {
            Vector y = x0_copy;
            if (y(k) != 0) {
                y(k) = (1.0 + nonzdelt) * y(k);
            } else {
                y(k) = zdelt;
            }
            sim.row(k + 1) = y;
        }
    } else {
        if (initial_simplex->rows() != N + 1 || initial_simplex->cols() != N) {
            throw std::invalid_argument("`initial_simplex` should be an array of shape (N+1,N)");
        }
        sim = *initial_simplex;
    }

    // 如果边界存在,确保所有单纯形顶点都在边界内
    if (has_bounds) {
        for (int i = 0; i < sim.rows(); ++i) {
            Vector vertex = sim.row(i);
            sim.row(i) = clip(vertex);
        }
    }

    // 如果既没有设置maxiter也没有设置maxfev,设置它们为默认值
    if (maxiter < 0 && maxfev < 0) {
        maxiter = N * 200;
        maxfev = N * 200;
    } else if (maxiter < 0) {
        if (maxfev == std::numeric_limits<int>::max()) {
            maxiter = N * 200;
        } else {
            maxiter = std::numeric_limits<int>::max();
        }
    } else if (maxfev < 0) {
        if (maxiter == std::numeric_limits<int>::max()) {
            maxfev = N * 200;
        } else {
            maxfev = std::numeric_limits<int>::max();
        }
    }

    // 计算初始单纯形中每个点的函数值
    Vector fsim = Vector::Constant(N + 1, std::numeric_limits<Scalar>::max());
    int fcalls = 0;

    for (int k = 0; k < N + 1; ++k) {
        fsim(k) = func(sim.row(k).transpose());
        fcalls++;

        if (fcalls >= maxfev) {
            break;
        }
    }

    // 按函数值排序单纯形顶点
    sort_simplex(fsim, sim);

    // 保存所有迭代结果
    std::vector<Vector> allvecs;
    if (return_all) {
        allvecs.push_back(sim.row(0).transpose());
    }

    // 开始迭代
    int iterations = 1;

    while (fcalls < maxfev && iterations < maxiter) {
        // 检查收敛
        double max_dist = 0.0;
        for (int i = 1; i < N + 1; ++i) {
            max_dist = std::max(max_dist, (sim.row(i) - sim.row(0)).norm());
        }

        double max_diff = 0.0;
        for (int i = 1; i < N + 1; ++i) {
            max_diff = std::max(max_diff, std::abs(fsim(i) - fsim(0)));
        }

        if (max_dist <= xatol && max_diff <= fatol) {
            break;
        }

        // 计算质心(除了最差点)
        // 1. sim.topRows(N) - 选择前N行,等价于Python的sim[:-1]
        //    (因为单纯形有N+1个顶点,选择前N行就是除了最后一行外的所有行)
        // 2. .colwise().sum() - 对每一列分别求和,结果是一个行向量
        //    (等价于Python的np.add.reduce(..., 0))
        // 3. .transpose() - 将行向量转置为列向量
        //    (因为在Eigen中Vector通常表示列向量)
        // 4. / static_cast<Scalar>(N) - 除以N得到平均值(质心)
        Vector xbar = sim.topRows(N).colwise().sum().transpose() / static_cast<Scalar>(N);

        // 执行反射
        Vector xr = (1 + rho) * xbar - rho * sim.row(N).transpose();
        if (has_bounds) {
            xr = clip(xr);
        }

        Scalar fxr = func(xr);
        fcalls++;

        bool doshrink = false;

        if (fxr < fsim(0)) {
            // 反射点比最好点还好,尝试扩展
            Vector xe = (1 + rho * chi) * xbar - rho * chi * sim.row(N).transpose();
            if (has_bounds) {
                xe = clip(xe);
            }

            Scalar fxe = func(xe);
            fcalls++;

            if (fxe < fxr) {
                // 扩展点更好,接受扩展
                sim.row(N) = xe.transpose();
                fsim(N) = fxe;
            } else {
                // 反射点更好,接受反射
                sim.row(N) = xr.transpose();
                fsim(N) = fxr;
            }
        } else {
            // 反射点不比最好点好
            if (fxr < fsim(N - 1)) {
                // 反射点至少比次差点好,接受反射
                sim.row(N) = xr.transpose();
                fsim(N) = fxr;
            } else {
                // 执行收缩
                if (fxr < fsim(N)) {
                    // 外收缩
                    Vector xc = (1 + psi * rho) * xbar - psi * rho * sim.row(N).transpose();
                    if (has_bounds) {
                        xc = clip(xc);
                    }

                    Scalar fxc = func(xc);
                    fcalls++;

                    if (fxc <= fxr) {
                        sim.row(N) = xc.transpose();
                        fsim(N) = fxc;
                    } else {
                        doshrink = true;
                    }
                } else {
                    // 内收缩
                    Vector xcc = (1 - psi) * xbar + psi * sim.row(N).transpose();
                    if (has_bounds) {
                        xcc = clip(xcc);
                    }

                    Scalar fxcc = func(xcc);
                    fcalls++;

                    if (fxcc < fsim(N)) {
                        sim.row(N) = xcc.transpose();
                        fsim(N) = fxcc;
                    } else {
                        doshrink = true;
                    }
                }

                if (doshrink) {
                    // 收缩整个单纯形
                    for (int j = 1; j < N + 1; ++j) {
                        sim.row(j) = sim.row(0) + sigma * (sim.row(j) - sim.row(0));
                        if (has_bounds) {
                            sim.row(j) = clip(sim.row(j).transpose()).transpose();
                        }

                        fsim(j) = func(sim.row(j).transpose());
                        fcalls++;

                        if (fcalls >= maxfev) {
                            break;
                        }
                    }
                }
            }
        }

        iterations++;

        // 重新排序单纯形
        sort_simplex(fsim, sim);

        // 保存当前最优解
        if (return_all) {
            allvecs.push_back(sim.row(0).transpose());
        }

        if (callback) {
            NelderMeadResult intermediate_result;
            intermediate_result.x = sim.row(0).transpose();
            intermediate_result.fun = fsim(0);
            intermediate_result.nit = iterations;
            intermediate_result.nfev = fcalls;

            if (callback(intermediate_result)) {
                break;
            }
        }
    }

    // 准备结果
    NelderMeadResult result;
    result.x = sim.row(0).transpose();
    result.fun = fsim(0);
    result.nit = iterations;
    result.nfev = fcalls;
    result.success = true;
    result.status = 0;
    result.message = "Optimization terminated successfully.";

    if (fcalls >= maxfev) {
        result.status = 1;
        result.success = false;
        result.message = "Maximum number of function evaluations exceeded.";

        if (disp) {
            std::cerr << "Warning: " << result.message << std::endl;
        }
    } else if (iterations >= maxiter) {
        result.status = 2;
        result.success = false;
        result.message = "Maximum number of iterations exceeded.";

        if (disp) {
            std::cerr << "Warning: " << result.message << std::endl;
        }
    } else if (disp) {
        std::cout << result.message << std::endl;
        std::cout << "         Current function value: " << result.fun << std::endl;
        std::cout << "         Iterations: " << result.nit << std::endl;
        std::cout << "         Function evaluations: " << result.nfev << std::endl;
    }

    if (return_all) {
        result.allvecs = allvecs;
    }

    return result;
}


template <typename Scalar>
Eigen::Matrix<Scalar, Eigen::Dynamic, 1>
fmin(std::function<Scalar(const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>&)> func,
     const Eigen::Matrix<Scalar, Eigen::Dynamic, 1>& x0,
     const Eigen::Matrix<Scalar, Eigen::Dynamic, Eigen::Dynamic>* initial_simplex,
     double xtol,
     double ftol,
     int maxiter,
     int maxfun,
     bool full_output,
     bool disp,
     bool retall,
     std::function<bool(const NelderMeadResult&)> callback) {
    NelderMeadOptions options;
    options.xatol = xtol;
    options.fatol = ftol;
    options.maxiter = maxiter;
    options.maxfev = maxfun;
    options.disp = disp;
    options.return_all = retall;

    NelderMeadResult result = minimize_neldermead(func, x0, initial_simplex, options, callback);

    if (full_output) {
        throw std::runtime_error(
                "Full output mode not implemented in wrapper function. Use minimize_neldermead directly.");
    } else {
        if (retall) {
            throw std::runtime_error(
                    "Return all mode not implemented in wrapper function. Use minimize_neldermead directly.");
        } else {
            return result.x;
        }
    }
}

#endif //MY_OPTIMIZE_INL

main.cpp:

#include <algorithm>
#include <cmath>
#include <functional>
#include <iostream>
#include <limits>
#include <vector>

#include <Eigen/Core>

#include "opti.h"

int main() {
    // Rosenbrock函数
    std::function<double(const Eigen::VectorXd&)> rosenbrock = [](const Eigen::VectorXd& x) -> double {
        return 100 * std::pow(x(1) - x(0) * x(0), 2) + std::pow(1 - x(0), 2);
    };

    // 初始猜测
    Eigen::VectorXd x0(2);
    x0 << -1.2, 1.0;

    // 设置选项
    NelderMeadOptions options;
    options.disp = true;

    // 运行优化
    NelderMeadResult result = minimize_neldermead<double>(rosenbrock, x0, nullptr, options);

    // 输出结果
    std::cout << "Optimal solution: " << result.x.transpose() << std::endl;
    std::cout << "Function value: " << result.fun << std::endl;

    return 0;
}

SciPy版Nelder-Mead算法与标准版的区别

(来自gpt o1,不保证正确性,用于辅助理解)

边界约束(bounds参数)

SciPy的Nelder-Mead实现支持简单的边界约束,而标准的Nelder-Mead算法没有原生的边界处理能力。在SciPy的minimize(method="Nelder-Mead")中,你可以传入边界参数,求解器会在每次迭代时将单纯形顶点裁剪到给定的边界内。实际上,这意味着如果反射或扩展的点在任何坐标上超出了[min, max]范围,它会被拉回到边界(在边界处饱和)。这是一种简单的方法来保持搜索在边界内(正如SciPy开发者确认的:"这只是简单地裁剪到边界")。标准的Nelder-Mead算法(按照最初描述)不包含任何约束处理机制——任何边界处理都需要外部修改或变体算法。因此,SciPy添加的边界处理是对传统Nelder-Mead算法的显著扩展。

自适应参数选项(adaptive=True)

SciPy的Nelder-Mead提供了一种"自适应"模式,可以调整算法在高维问题中的行为。当adaptive=True时,SciPy使用来自Gao和Han(2012)的修改参数方案。在代码中,这改变了单纯形变换系数如下:反射系数ρ = 1(保持不变),扩展系数χ = 1 + 2/N(而不是2),收缩系数ψ = 0.75 - 1/(2N)(而不是0.5),缩小因子σ = 1 - 1/N(而不是0.5),其中N是维度数量。如果adaptive=False(默认值),SciPy使用标准的Nelder-Mead系数:ρ = 1,χ = 2,ψ = 0.5,σ = 0.5。这些经典值对应于原始的Nelder-Mead算法。自适应模式不会在运行过程中改变系数;相反,它在开始时根据问题维度选择这些依赖于维度的值(使方法在高维中更加稳健)。总之,标准Nelder-Mead对所有问题使用固定参数,而SciPy的版本在需要时可以使用Gao-Han自适应参数集,改善高维搜索的性能(同时在adaptive=False时对2D或低维问题保持与标准Nelder-Mead相同)。

收敛准则(fatol和xatol)

SciPy的实现使用两个收敛容差阈值——一个用于函数值(fatol),一个用于解的坐标(xatol)——并要求同时满足这两个准则才能收敛。在每次迭代中,SciPy检查当前最佳顶点与其他单纯形顶点之间的最大坐标距离是否<= xatol,以及最佳顶点与其他顶点之间的函数值最大差异是否<= fatol。只有当这两个条件都成立时,才宣布收敛并停止。这种"双重停止条件"确保单纯形在空间中充分收缩,且函数值已经趋于平稳。相比之下,标准Nelder-Mead描述通常使用单一容差(例如,检查单纯形直径或函数值范围是否低于阈值)。许多实现(包括早期的SciPy fmin)也使用了两个容差(xtol和ftol)——SciPy继续这种方法但将它们重命名为xatol和fatol(明确表示"绝对"容差)。这种双重标准更加稳健:它可以防止过早停止,例如,如果单纯形很小但函数值仍然存在差异,或者反之亦然。简而言之,SciPy的Nelder-Mead只有在参数变化和函数改进都低于各自的阈值时才停止,而简单的Nelder-Mead可能使用其中之一。(事实上,SciPy的文档指出:"ftol和xtol标准必须同时满足才能收敛。")

终止规则(maxiter和maxfun)

SciPy的Nelder-Mead包含迭代次数和函数评估次数的明确限制,如果算法停滞或收敛缓慢,可以安全终止。maxiter(最大迭代次数)和maxfun(又称maxfev,最大函数评估次数)选项将在达到任一限制时停止优化(以先到者为准)。如果两者都设置了,SciPy会同时监控两者,一旦超过其中一个限制就会退出;如果只设置了一个,另一个实际上被视为无穷(或默认值),以便指定的限制起作用。默认情况下,如果用户没有提供这些参数,SciPy使用启发式规则:maxiter = maxfun = 200 * N(其中N是变量数量)。这个默认值(类似于MATLAB的fminsearch默认值)是一个合理的上限,以防容差标准永远不满足而导致无限循环。相比之下,已发表的标准Nelder-Mead算法没有定义特定的迭代限制——理论上它可以一直运行到满足收敛标准。然而,在实践中,各种实现(包括SciPy和其他如MATLAB)都施加了这样的限制,以确保即使对于困难或平坦的目标函数,程序也能终止。SciPy的结果对象会通过设置警告标志和消息(例如,warnflag=1表示达到最大函数评估次数,warnflag=2表示达到最大迭代次数)来指示优化器是否因达到maxiter或maxfun而停止。这是一个实现细节,增强了SciPy的Nelder-Mead求解器相比基础Nelder-Mead的稳健性。

结果存储(return_all)和回调机制

SciPy提供了方便的钩子来检查优化进度,这些钩子不是理论Nelder-Mead算法的一部分,但在实践中很有用。如果设置了return_all=True,SciPy的求解器会记录每次迭代中的最佳解决方案,并将其作为结果对象中的一个列表返回(result.allvecs)。这允许用户检查单纯形在搜索空间中所走的路径。此外,SciPy接受一个回调函数:如果提供了回调函数,它会在每次迭代结束时使用当前最佳点xk作为参数调用该函数。回调机制启用了用户定义的监控或日志记录(例如,打印进度,或在自定义条件下提前停止求解器)。这些特性在标准Nelder-Mead描述中没有等效项,后者只关注于找到最小值。它们纯粹是SciPy中的实现增强功能。例如,SciPy代码显示,在每次迭代时,当return_all为True时,它会将sim[0](当前最佳顶点)附加到allvecs,如果提供了回调函数,则调用callback(sim[0])。这些特性使SciPy的Nelder-Mead更加用户友好,允许在求解过程中进行自省和交互。

结论和其他总结的准确性

总之,SciPy的Nelder-Mead与"教科书"Nelder-Mead在几个方面有所不同:它通过将单纯形更新裁剪到边界内来支持有界约束变量,它提供了自适应参数变体(Gao-Han 2012)以在高维中获得更好的性能,它采用必须同时满足的双重收敛标准(xatol和fatol),它使用可配置(和默认)的迭代和函数调用限制以确保终止,它提供return_all/callback用于结果跟踪和用户干预。这些增强功能使SciPy的版本比Nelder-Mead的基本算法描述更加稳健和功能丰富。

如果其他人沿着这些线索总结了SciPy与标准Nelder-Mead的差异,那么他们的总结可能是准确的。上述每个点都由SciPy代码和文档支持(如所示)。然而,任何缺少这些关键差异或不正确描述它们的总结都不会完全准确。例如,必须注意SciPy的终止同时需要fatol和xatol条件(不是二选一),并且"自适应"模式只是基于问题维度使用不同的固定系数集(除了初始选择外,它不会在迭代过程中动态改变它们)。同样,声明SciPy通过"投影"或"反射回"处理边界应该明确意味着裁剪到边界,而不是更复杂的约束处理。总的来说,上述列出的点与正确的区别相符。因此,涵盖这些内容的总结——边界裁剪、自适应系数、双重容差、迭代/函数评估限制以及可选的回调/轨迹输出——是准确的。任何其他人的总结如果反映了这些概念可能是正确的,而遗漏或错误(如误解收敛检查方式或自适应如何工作)需要更正。代码分析确认了SciPy实现的细节,因此任何总结的正确性都可以根据上述事实进行判断。

参考文献

以下是从SciPy中提取的论文引用,并附上中文翻译:

  1. Nelder, J.A. and Mead, R. (1965), "A simplex method for function
    minimization", The Computer Journal, 7, pp. 308-313

    中文:Nelder, J.A. 和 Mead, R.
    (1965),《一种用于函数最小化的单纯形方法》,计算机杂志,第7卷,308-313页

  2. Wright, M.H. (1996), "Direct Search Methods: Once Scorned, Now
    Respectable", in Numerical Analysis 1995, Proceedings of the 1995
    Dundee Biennial Conference in Numerical Analysis, D.F. Griffiths and
    G.A. Watson (Eds.), Addison Wesley Longman, Harlow, UK, pp. 191-208.

    中文:Wright, M.H.
    (1996),《直接搜索方法:曾经被鄙视,如今受人尊敬》,载于1995年数值分析文集,1995年邓迪数值分析两年会议论文集,D.F.
    Griffiths和G.A. Watson(编辑),Addison Wesley Longman出版社,英国哈洛,191-208页

  3. Gao, F. and Han, L. (2012), "Implementing the Nelder-Mead simplex
    algorithm with adaptive parameters", Computational Optimization and
    Applications, 51:1, pp. 259-277

    中文:Gao, F. 和 Han, L.
    (2012),《使用自适应参数实现Nelder-Mead单纯形算法》,计算优化与应用,第51卷第1期,259-277页

posted @ 2025-11-16 18:41  咖啡放葡萄糖  阅读(23)  评论(0)    收藏  举报