忆盎 發表於 2025-11-16 18:41:00

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

<h1 id="简介">简介</h1>
<p>本文展示了用C++(Eigen)实现的Nelder-Mead算法,该实现仿照了Python SciPy库中的scipy.optimize.fmin函数。虽然目前仅完成了基础功能(fmin不支持full_output和retall),但已经可以应用于实际优化问题。</p>
<h1 id="nelder-mead算法简介">Nelder-Mead算法简介</h1>
<p>Nelder-Mead算法(也称单纯形法)是一种常用的无导数优化方法,特别适合于:</p>
<ul>
<li>函数导数难以计算或不存在的情况</li>
<li>寻找非线性函数的局部最小值</li>
<li>维度适中的问题</li>
</ul>
<p>这种算法被广泛应用于机器学习、计算机视觉、信号处理等领域,特别是当目标函数计算成本高昂时尤为有用。</p>
<p>SciPy版本的特点:</p>
<ol>
<li>边界约束支持:SciPy版本添加了对变量边界的支持,通过bounds参数实现。它使用np.clip确保所有单纯形顶点都在指定边界内,这是标准Nelder-Mead算法的扩展。</li>
<li>自适应参数策略:通过adaptive参数启用,基于Gao和Han<br>
2012年的论文。这种策略根据问题维度自动调整反射、扩展和收缩参数,使算法在高维问题上表现更好。</li>
<li>双重收敛条件:同时使用函数值容差(fatol)和参数值容差(xatol)作为终止条件,这比只用单一条件更稳健。</li>
<li>完善的终止处理:支持通过最大迭代次数(maxiter)或最大函数评估次数(maxfev)限制算法运行时间,并提供详细的终止状态。</li>
</ol>
<p>更详细的分析见本文结尾。</p>
<h1 id="代码">代码</h1>
<p>纯头文件库,包含opti.h,opti.inl。还有一个测试程序main.cpp<br>
opti.h:</p>
<pre><code class="language-cpp">#ifndef MY_OPTIMIZE_H
#define MY_OPTIMIZE_H


#include &lt;Eigen/Core&gt;
#include &lt;algorithm&gt;
#include &lt;cmath&gt;
#include &lt;functional&gt;
#include &lt;iostream&gt;
#include &lt;limits&gt;
#include &lt;vector&gt;

// 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&lt;std::pair&lt;double, double&gt;&gt; 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&lt;Eigen::VectorXd&gt; allvecs; // 所有迭代的结果(如果return_all=true)
};


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


#include "opti.inl"

#endif //MY_OPTIMIZE_H

</code></pre>
<p>opti.inl:</p>
<pre><code class="language-cpp">#ifndef MY_OPTIMIZE_INL
#define MY_OPTIMIZE_INL

#include &lt;algorithm&gt;
#include &lt;cmath&gt;
#include &lt;iostream&gt;

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

    // 提取选项参数
    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&amp; bounds = options.bounds;

    // 设置算法参数
    double rho, chi, psi, sigma;
    if (adaptive) {
      double dim = static_cast&lt;double&gt;(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&lt;size_t&gt;(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 &lt; x0.size(); ++i) {
            lower_bound(i) = bounds.first;
            upper_bound(i) = bounds.second;
      }

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

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

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

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

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

      std::vector&lt;std::pair&lt;Scalar, int&gt;&gt; pairs(N + 1);
      for (int i = 0; i &lt; N + 1; ++i) {
            pairs = {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 &lt; N + 1; ++i) {
            fsim_sorted(i) = pairs.first;
            sim_sorted.row(i) = sim.row(pairs.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 &lt; 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-&gt;rows() != N + 1 || initial_simplex-&gt;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 &lt; sim.rows(); ++i) {
            Vector vertex = sim.row(i);
            sim.row(i) = clip(vertex);
      }
    }

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

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

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

      if (fcalls &gt;= maxfev) {
            break;
      }
    }

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

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

    // 开始迭代
    int iterations = 1;

    while (fcalls &lt; maxfev &amp;&amp; iterations &lt; maxiter) {
      // 检查收敛
      double max_dist = 0.0;
      for (int i = 1; i &lt; 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 &lt; N + 1; ++i) {
            max_diff = std::max(max_diff, std::abs(fsim(i) - fsim(0)));
      }

      if (max_dist &lt;= xatol &amp;&amp; max_diff &lt;= 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&lt;Scalar&gt;(N) - 除以N得到平均值(质心)
      Vector xbar = sim.topRows(N).colwise().sum().transpose() / static_cast&lt;Scalar&gt;(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 &lt; 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 &lt; fxr) {
                // 扩展点更好,接受扩展
                sim.row(N) = xe.transpose();
                fsim(N) = fxe;
            } else {
                // 反射点更好,接受反射
                sim.row(N) = xr.transpose();
                fsim(N) = fxr;
            }
      } else {
            // 反射点不比最好点好
            if (fxr &lt; fsim(N - 1)) {
                // 反射点至少比次差点好,接受反射
                sim.row(N) = xr.transpose();
                fsim(N) = fxr;
            } else {
                // 执行收缩
                if (fxr &lt; 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 &lt;= 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 &lt; fsim(N)) {
                        sim.row(N) = xcc.transpose();
                        fsim(N) = fxcc;
                  } else {
                        doshrink = true;
                  }
                }

                if (doshrink) {
                  // 收缩整个单纯形
                  for (int j = 1; j &lt; 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 &gt;= 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 &gt;= maxfev) {
      result.status = 1;
      result.success = false;
      result.message = "Maximum number of function evaluations exceeded.";

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

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

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

    return result;
}


template &lt;typename Scalar&gt;
Eigen::Matrix&lt;Scalar, Eigen::Dynamic, 1&gt;
fmin(std::function&lt;Scalar(const Eigen::Matrix&lt;Scalar, Eigen::Dynamic, 1&gt;&amp;)&gt; func,
   const Eigen::Matrix&lt;Scalar, Eigen::Dynamic, 1&gt;&amp; x0,
   const Eigen::Matrix&lt;Scalar, Eigen::Dynamic, Eigen::Dynamic&gt;* initial_simplex,
   double xtol,
   double ftol,
   int maxiter,
   int maxfun,
   bool full_output,
   bool disp,
   bool retall,
   std::function&lt;bool(const NelderMeadResult&amp;)&gt; 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

</code></pre>
<p>main.cpp:</p>
<pre><code class="language-cpp">#include &lt;algorithm&gt;
#include &lt;cmath&gt;
#include &lt;functional&gt;
#include &lt;iostream&gt;
#include &lt;limits&gt;
#include &lt;vector&gt;

#include &lt;Eigen/Core&gt;

#include "opti.h"

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

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

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

    // 运行优化
    NelderMeadResult result = minimize_neldermead&lt;double&gt;(rosenbrock, x0, nullptr, options);

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

    return 0;
}
</code></pre>
<h1 id="scipy版nelder-mead算法与标准版的区别">SciPy版Nelder-Mead算法与标准版的区别</h1>
<p>(来自gpt o1,不保证正确性,用于辅助理解)</p>
<h2 id="边界约束bounds参数">边界约束(bounds参数)</h2>
<p>SciPy的Nelder-Mead实现支持简单的边界约束,而标准的Nelder-Mead算法没有原生的边界处理能力。在SciPy的<code>minimize(method="Nelder-Mead")</code>中,你可以传入边界参数,求解器会在每次迭代时将单纯形顶点裁剪到给定的边界内。实际上,这意味着如果反射或扩展的点在任何坐标上超出了范围,它会被拉回到边界(在边界处饱和)。这是一种简单的方法来保持搜索在边界内(正如SciPy开发者确认的:"这只是简单地裁剪到边界")。标准的Nelder-Mead算法(按照最初描述)不包含任何约束处理机制——任何边界处理都需要外部修改或变体算法。因此,SciPy添加的边界处理是对传统Nelder-Mead算法的显著扩展。</p>
<h2 id="自适应参数选项adaptivetrue">自适应参数选项(adaptive=True)</h2>
<p>SciPy的Nelder-Mead提供了一种"自适应"模式,可以调整算法在高维问题中的行为。当<code>adaptive=True</code>时,SciPy使用来自Gao和Han(2012)的修改参数方案。在代码中,这改变了单纯形变换系数如下:反射系数ρ = 1(保持不变),扩展系数χ = 1 + 2/N(而不是2),收缩系数ψ = 0.75 - 1/(2N)(而不是0.5),缩小因子σ = 1 - 1/N(而不是0.5),其中N是维度数量。如果<code>adaptive=False</code>(默认值),SciPy使用标准的Nelder-Mead系数:ρ = 1,χ = 2,ψ = 0.5,σ = 0.5。这些经典值对应于原始的Nelder-Mead算法。自适应模式不会在运行过程中改变系数;相反,它在开始时根据问题维度选择这些依赖于维度的值(使方法在高维中更加稳健)。总之,标准Nelder-Mead对所有问题使用固定参数,而SciPy的版本在需要时可以使用Gao-Han自适应参数集,改善高维搜索的性能(同时在<code>adaptive=False</code>时对2D或低维问题保持与标准Nelder-Mead相同)。</p>
<h2 id="收敛准则fatol和xatol">收敛准则(fatol和xatol)</h2>
<p>SciPy的实现使用两个收敛容差阈值——一个用于函数值(fatol),一个用于解的坐标(xatol)——并要求同时满足这两个准则才能收敛。在每次迭代中,SciPy检查当前最佳顶点与其他单纯形顶点之间的最大坐标距离是否&lt;= xatol,以及最佳顶点与其他顶点之间的函数值最大差异是否&lt;= fatol。只有当这两个条件都成立时,才宣布收敛并停止。这种"双重停止条件"确保单纯形在空间中充分收缩,且函数值已经趋于平稳。相比之下,标准Nelder-Mead描述通常使用单一容差(例如,检查单纯形直径或函数值范围是否低于阈值)。许多实现(包括早期的SciPy fmin)也使用了两个容差(xtol和ftol)——SciPy继续这种方法但将它们重命名为xatol和fatol(明确表示"绝对"容差)。这种双重标准更加稳健:它可以防止过早停止,例如,如果单纯形很小但函数值仍然存在差异,或者反之亦然。简而言之,SciPy的Nelder-Mead只有在参数变化和函数改进都低于各自的阈值时才停止,而简单的Nelder-Mead可能使用其中之一。(事实上,SciPy的文档指出:"ftol和xtol标准必须同时满足才能收敛。")</p>
<h2 id="终止规则maxiter和maxfun">终止规则(maxiter和maxfun)</h2>
<p>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的稳健性。</p>
<h2 id="结果存储return_all和回调机制">结果存储(return_all)和回调机制</h2>
<p>SciPy提供了方便的钩子来检查优化进度,这些钩子不是理论Nelder-Mead算法的一部分,但在实践中很有用。如果设置了<code>return_all=True</code>,SciPy的求解器会记录每次迭代中的最佳解决方案,并将其作为结果对象中的一个列表返回(result.allvecs)。这允许用户检查单纯形在搜索空间中所走的路径。此外,SciPy接受一个回调函数:如果提供了回调函数,它会在每次迭代结束时使用当前最佳点xk作为参数调用该函数。回调机制启用了用户定义的监控或日志记录(例如,打印进度,或在自定义条件下提前停止求解器)。这些特性在标准Nelder-Mead描述中没有等效项,后者只关注于找到最小值。它们纯粹是SciPy中的实现增强功能。例如,SciPy代码显示,在每次迭代时,当return_all为True时,它会将sim(当前最佳顶点)附加到allvecs,如果提供了回调函数,则调用callback(sim)。这些特性使SciPy的Nelder-Mead更加用户友好,允许在求解过程中进行自省和交互。</p>
<h2 id="结论和其他总结的准确性">结论和其他总结的准确性</h2>
<p>总之,SciPy的Nelder-Mead与"教科书"Nelder-Mead在几个方面有所不同:它通过将单纯形更新裁剪到边界内来支持有界约束变量,它提供了自适应参数变体(Gao-Han 2012)以在高维中获得更好的性能,它采用必须同时满足的双重收敛标准(xatol和fatol),它使用可配置(和默认)的迭代和函数调用限制以确保终止,它提供return_all/callback用于结果跟踪和用户干预。这些增强功能使SciPy的版本比Nelder-Mead的基本算法描述更加稳健和功能丰富。</p>
<p>如果其他人沿着这些线索总结了SciPy与标准Nelder-Mead的差异,那么他们的总结可能是准确的。上述每个点都由SciPy代码和文档支持(如所示)。然而,任何缺少这些关键差异或不正确描述它们的总结都不会完全准确。例如,必须注意SciPy的终止同时需要fatol和xatol条件(不是二选一),并且"自适应"模式只是基于问题维度使用不同的固定系数集(除了初始选择外,它不会在迭代过程中动态改变它们)。同样,声明SciPy通过"投影"或"反射回"处理边界应该明确意味着裁剪到边界,而不是更复杂的约束处理。总的来说,上述列出的点与正确的区别相符。因此,涵盖这些内容的总结——边界裁剪、自适应系数、双重容差、迭代/函数评估限制以及可选的回调/轨迹输出——是准确的。任何其他人的总结如果反映了这些概念可能是正确的,而遗漏或错误(如误解收敛检查方式或自适应如何工作)需要更正。代码分析确认了SciPy实现的细节,因此任何总结的正确性都可以根据上述事实进行判断。</p>
<h1 id="参考文献">参考文献</h1>
<p>以下是从SciPy中提取的论文引用,并附上中文翻译:</p>
<ol>
<li>
<p>Nelder, J.A. and Mead, R. (1965), "A simplex method for function<br>
minimization", The Computer Journal, 7, pp. 308-313</p>
<p>中文:Nelder, J.A. 和 Mead, R.<br>
(1965),《一种用于函数最小化的单纯形方法》,计算机杂志,第7卷,308-313页</p>
</li>
<li>
<p>Wright, M.H. (1996), "Direct Search Methods: Once Scorned, Now<br>
Respectable", in Numerical Analysis 1995, Proceedings of the 1995<br>
Dundee Biennial Conference in Numerical Analysis, D.F. Griffiths and<br>
G.A. Watson (Eds.), Addison Wesley Longman, Harlow, UK, pp. 191-208.</p>
<p>中文:Wright, M.H.<br>
(1996),《直接搜索方法:曾经被鄙视,如今受人尊敬》,载于1995年数值分析文集,1995年邓迪数值分析两年会议论文集,D.F.<br>
Griffiths和G.A. Watson(编辑),Addison Wesley Longman出版社,英国哈洛,191-208页</p>
</li>
<li>
<p>Gao, F. and Han, L. (2012), "Implementing the Nelder-Mead simplex<br>
algorithm with adaptive parameters", Computational Optimization and<br>
Applications, 51:1, pp. 259-277</p>
<p>中文:Gao, F. 和 Han, L.<br>
(2012),《使用自适应参数实现Nelder-Mead单纯形算法》,计算优化与应用,第51卷第1期,259-277页</p>
</li>
</ol><br><br>
来源:https://www.cnblogs.com/coffeepro123/p/19228703
頁: [1]
查看完整版本: 基于c++ eigen的Nelder-Mead算法(仿照scipy)