先说我自己的一个感受:很多做工程、做数据的人,一提到“非线性规划”第一反应是课本里的拉格朗日乘子法,觉得它偏理论、离业务很远。但真实世界的优化问题,十有八九都是非线性的——成本曲线是弯的,反应速率是带指数的,组合风险是二次的,供应商报价还有阶梯折扣。线性规划在学校里太好用了,以至于很多人误以为所有问题都能线性化,结果一碰真实约束就发现根本解不动。这篇想系统聊聊非线性规划从建模到求解的完整链路:标准形式是什么、主流算法在干嘛、KKT条件为什么是核心、有哪些典型应用,最后给一个可以直接复现的Python案例和一堆踩坑经验。无论你是运筹优化方向的初学者,还是要用优化器解决业务问题的工程师,这一篇应该够你入门到动手了。
1. 非线性规划从哪来:当“线性”这个假设先坏了
1.1 线性规划的边界,其实比想象中窄得多
线性规划的标准形是min c^T x,约束长成Ax ≤ b这种。它要求目标函数和约束条件里,所有变量的关系都是线性的:投入一份原料,产出固定倍数的产品;单位运输成本不随运量变化;收益是可加可拆的。这些假设在入门题目里天经地义,但在实际项目里往往撑不过第一个真实参数。
我举三个最常见的反例。第一是采购折扣:采购量超过某个阈值,供应商单价直接降一档,成本函数变成阶梯状,根本不可能用一条直线近似;第二是化学反应过程:反应速率与温度之间是阿伦尼乌斯型的指数关系,温度每升高10度,反应速率可能翻倍,这种关系任何线性化都只能在一个极窄区间勉强成立;第三是风险管理:投资组合的风险不是权重的一次函数,而是权重向量二次型,类似w^T Σ w,协方差矩阵把资产之间的交互项全带进来了。
工程上很多人为了继续用线性规划,会硬把上述函数分段线性化。这不失为一种妥协,但代价很大:分段越多,变量和约束膨胀得越厉害;而且分段线性化会在拐点引入整数变量,把问题变成混合整数规划,求解难度和工程复杂度都上了一个台阶。与其强行线性化,不如承认问题的非线性本质,正面用非线性规划求解。
1.2 非线性规划的标准形式与分类
非线性规划的标准形式比线性规划只多了两个字,但内涵完全不同:
- 决策变量:x = (x₁, x₂, ..., xₙ)
- 目标函数:min f(x)
- 等式约束:h_j(x) = 0(可选)
- 不等式约束:g_i(x) ≤ 0(可选)
只要 f、h、g 中至少有一个不是 x 的线性函数,这就是非线性规划。注意这里没有任何“必须光滑”“必须连续”的要求,只是通常求解器和算法都会假设函数足够光滑,至少是可微的。
从求解难度看,非线性规划可以粗略分三类。
第一类是凸优化:目标函数是凸函数,可行域是凸集。这类问题最重要,因为凸问题的局部最优解就是全局最优解,KKT条件既是必要条件也是充分条件。教科书里的“拉格朗日乘子法”能直接派上用场,而且有非常成熟的求解器。
第二类是非凸但光滑的问题:目标或约束里有多个峰值、鞍点。比如神经网络训练、分子构型优化、某些供应链网络设计。这类问题没有“全局最优”的保证,算法容易陷在局部最优出不来回。
第三类是非光滑问题:目标或约束里有绝对值、max、L1范数这类不可导项。很多现实中的惩罚函数、软约束都长这样,普通梯度方法在不可导点会剧烈震荡,需要特殊的次梯度方法或光滑化技巧。
同一个问题,凸不凸,直接决定了你能用哪一档算法。这也是为什么我在后面讲到KKT条件时,一定要反复强调“这是必要条件”而不是“充要条件”。
1.3 非凸和非光滑,才是非线性规划真正的难点
线性规划之所以好解,是因为可行域是凸多面体,最优点一定在某个顶点上,单纯形法本质上是沿着顶点找路。非线性规划一旦跨出凸集,局面立刻变复杂。
举个例子。一个简单的一维问题min {x² + a·sin(5x)},当 a 比较小时,最小值唯一;当 a 变大时,函数会出现多个波谷,梯度下降从哪个初始点出发,就可能停在哪个波谷,最后得到的解完全取决于你的初始猜测。这就是局部最优。更麻烦的是,有些函数表面平静,实际上存在若干个几乎等高的峰谷,稍微动一点参数,最优解就跳跃到另一个山谷。
对于非光滑问题,典型代表是带绝对值的目标函数,比如min |x - 1| + 0.5(x + 2)²。在 x = 1 这个点,导数不存在,数值梯度会给出一个不稳定甚至错误的方向。工程上常用的办法是给绝对值一个光滑近似,比如sqrt(ε² + (x-1)²),ε 取一个很小的正数,让函数在拐点附近变得圆滑,然后再交给常规优化器。
我后来的实战经验是:判断一个非线性规划好不好解,先画一下目标函数的等值线或者做几次随机采样;如果100个随机初始点跑出来的最优值差别很大,那就要有心理准备,这不是优化器的问题,是问题本身非凸的宿命。
2. 无约束问题的算法主线:梯度、二阶信息与拟牛顿法
2.1 梯度下降:成也步长,败也步长
非线性规划最简单的解法是梯度下降,迭代公式是x_{k+1} = x_k - α_k ∇f(x_k)。这里 α_k 是步长,虽然看起来只是个参数,但实际工程里它决定一切。
固定步长最容易踩坑。步长设大了,目标函数在下降方向上来回震荡,甚至发散;设小了,算法像蜗牛爬,迭代几百步还停留在原地。我自己跑过一个二维二次函数测试,固定步长取0.1时,50步就收敛;取0.4时,迭代轨迹直接画成螺旋线;取0.9时误差指数增长到溢出。这种对参数的极端敏感,正是工程中不建议手写固定步长梯度下降的原因。
可靠的做法是引入线搜索。简单说,每一步先确定一个下降方向,然后在这个方向上尝试不同的步长,直到满足某个条件。最常用的是Armijo条件,它的思想很朴素:移动之后的目标函数应该比原地下降至少“一个可观的量”,一般是f(x_k + α p) ≤ f(x_k) + c₁ α ∇f(x_k)^T p,其中c₁通常取1e-4。别小看这个不等式,它保证了每次迭代都不是无效的抖动,同时又允许步长在一个合理区间内自适应。
还有一个经常被忽视的问题是条件数。如果目标函数的等值线是又长又扁的椭圆,梯度方向并不会直接指向圆心,而是在山谷两侧来回折返,收敛速度极慢。这种问题光调步长没用,本质是需要二阶信息来修正方向。
2.2 牛顿法与拟牛顿法:为什么二阶信息这么值钱
牛顿法把目标函数在当前点做二阶泰勒展开,然后求解二次模型的最小值,迭代公式是x_{k+1} = x_k - [H(x_k)]⁻¹ ∇f(x_k)。它最大的优点是收敛速度快,在最优解附近可以达到二次收敛,也就是误差每一步都按平方级缩减。
代价也很明显:你要算Hessian矩阵,也就是二阶偏导数矩阵。对于小规模问题,解析Hessian还能手推;一旦变量维度上百,Hessian的计算量和存储量都是灾难。更要命的是,在非凸区域 Hessian 可能不正定,导致牛顿方向上并不是下降方向,算法直接跑飞。
于是拟牛顿法出场了。它的思路是用一阶梯度的差值去近似Hessian,经典代表是BFGS。BFGS不需要解析二阶导数,只需要每一步的梯度和变量增量,就能逐步构造出一个正定的近似Hessian逆矩阵。正定这一点非常重要,它保证搜索方向一定下降。后来的L-BFGS是BFGS的省内存版本,不存完整近似矩阵,只存最近几步的梯度差,适合大维度问题。
我在实际项目里几乎不手写这些算法,而是直接用现成优化器。但理解背后的机理仍然必要,因为你至少要能读懂报错信息。比如L-BFGS报 “ConvergenceError: line search failed”,十有八九是目标函数在某个点附近不光滑或者有NaN,而不是算法本身坏了。
三种方法的对比大致是这样:
| 算法 | 需要信息 | 收敛速度 | 每步开销 | 适用规模 | 典型坑 |
|---|---|---|---|---|---|
| 梯度下降 | 一阶梯度 | 线性收敛 | 低 | 超大 | 步长敏感、山谷震荡 |
| 牛顿法 | 二阶Hessian | 二次收敛 | 高 | 小 | Hessian可能不正定 |
| 拟牛顿(BFGS) | 一阶梯度 | 超线性收敛 | 中 | 中 | 内存随维度平方增长 |
| L-BFGS | 一阶梯度 | 超线性收敛 | 低 | 大 | 对非光滑问题易崩 |
2.3 无约束问题里容易被忽略的两件事
第一件事是停机准则。很多初学优化的人会把 “迭代步数达到上限” 当作停机条件,这是最后的兜底,不是正常结束方式。工程上应该看梯度范数‖∇f‖是否足够小,以及连续几步的目标函数下降量是否低于阈值。如果梯度已经接近零,说明你已站在平稳点附近,再迭代也不会有什么收益,果断停。至于深度学习中用的SGD为什么不讲收敛,是因为在庞大非凸问题上我们根本不追求高精度最优,只要在有限时间内找到可用的参数就行,这跟经典非线性规划的目标不一样。
第二件事是数值梯度。当你懒得手写解析导数时,优化器会用有限差分近似梯度,比如(f(x+h) - f(x-h)) / (2h)。h太大近似误差大,h太小浮点误差又冒出来。尤其在目标函数数值本身就在1e-3量级时,数值梯度的噪声可能完全盖过真实信号。所以,凡是目标函数能写出解析导数的,我强烈建议用autograd这类的自动微分工具算出来,或者至少手写一阶导数传给优化器。这一步能省掉你后面大半的debug时间。
3. 约束下的非线性规划:从拉格朗日到KKT再到SQP
3.1 拉格朗日乘子法的一种几何直觉
很多人学拉格朗日乘子法时只记住了结果:对等式约束h(x) = 0,拉格朗日函数L(x, λ) = f(x) + λ h(x),最优解处对x和λ求偏导都为零。但为什么能这么做,几何直觉其实更简单。
在无约束问题中,最优解处梯度为零;在有等式约束的问题中,你不能随便移动,只能在约束面h(x) = 0上移动。如果某个点是约束问题的最优点,那目标函数的等值面在这一点应该与约束面相切,否则沿着约束面稍微移动还能继续降低目标值。切在几何上意味着∇f与∇h方向平行,也就是存在一个系数 λ,使得∇f + λ∇h = 0。这个 λ 就是拉格朗日乘子。
一个非常经典的验证例子:设计一个圆柱形储罐,容积固定为 V,求最小表面积。设半径 r、高 h,约束πr²h = V,目标2πr² + 2πrh。用拉格朗日乘子法很快能得到r = (V/(2π))^{1/3},h = 2r,也就是高恰好等于直径时最省材料。我建议每位读者都亲手推一遍这道题,它对理解“为什么梯度共线”特别有用。
3.2 KKT条件:不等式约束下的“升级版必要条件”
引入不等式约束g(x) ≤ 0后,拉格朗日乘子法就扩展为KKT条件。它的内涵是几个条件同时成立:
- 平稳性:拉格朗日函数对x的梯度为零,即
∇f + Σλᵢ∇gᵢ = 0; - 原问题可行性:所有不等式约束和等式约束都满足;
- 对偶可行性:所有不等式约束对应的拉格朗日乘子 λᵢ ≥ 0;
- 互补松弛:λᵢ·gᵢ(x) = 0,也就是说如果某个不等式约束是松弛的,乘子必为零;如果乘子非零,约束必然紧。
互补松弛条件是最容易被忽略也最关键的一条。它的直觉是:只有当约束真的构成了“限制”,也就是当前解正在这个约束边界上时,约束才值得拥有一个非零的影子价格;如果一个约束离你十万八千里,它对当前解没有任何影响力,影子价格当然为零。
但必须死死记住一个前提:KKT条件是非线性规划最优解的必要条件,不是充分条件。只有在问题满足约束规格并且是凸优化时,KKT才同时是充分条件。换句话说,对于非凸问题,即便你构造了一个满足所有KKT条件的点,它也可能只是局部最优点甚至鞍点,离全局最优还差得远。这点我见过太多人搞混,以为KKT一满足就万事大吉。
3.3 罚函数、内点法与SQP:实际求解器在背后的工作
理解了KKT,再看工程求解器就一目了然。现在主流的非线性规划算法基本是三类:罚函数法、内点法、序列二次规划(SQP)。
罚函数法的思路最暴力:把约束“塞进”目标函数,比如f(x) + ρ·max(0, g(x))²,然后解一个无约束问题。ρ特别大时,违反约束的代价极高,解自然会被“推”到可行域附近。缺点是ρ太小约束不满足,ρ太大目标函数变得病态,数值上很难调。它是很多初学者自己写代码的首选方案,但真实求解器已经很少单独用它了。
内点法从另一个方向切入:给不等式约束加一个“障碍函数”,在接近边界时让目标值暴增,保证迭代点永远在可行域内部。常用的是对数障碍项-μ Σ log(-gᵢ(x)),然后随着迭代把障碍系数 μ 一点点缩小,让解逐渐逼近真实边界。内点法对大规模问题的适用性很强,现代很多开源求解器都基于这个思路。
SQP则把原始问题在当前点做近似:对目标函数做二次近似,约束做线性近似,于是每一步都变成一个二次规划子问题,然后再用逐步迭代逼近原问题。它每轮都在更新KKT乘子的估计,收敛性相当好,所以很多通用优化器选择它作为默认方法,比如我下面实战要用的SLSQP就属于这一类。
4. 非线性规划真正值钱的地方:几个典型应用建模
4.1 投资组合优化:收益是一次函数,风险可不是
Markowitz的均值-方差模型,目标是最小化组合波动率的同时让预期收益达到某个下限。假如有 n 种资产,权重向量 w,收益均值向量 μ,协方差矩阵 Σ,目标函数就是w^T Σ w,约束包括Σ wᵢ = 1和μ^T w ≥ r_min。
这个模型的“非线性”身份很明显:目标函数是权重向量的二次型,协方差矩阵里的交互项把资产之间的联动关系全部编码进去。如果把收益率目标改成动态的,或者加入“交易成本与换手量指数相关”这种条件,目标函数还会跑出w^1.5甚至更复杂的项,那时候二次规划工具就不够用了,只能交给通用非线性规划求解器。这部分我会在第5章写一个完整案例。
4.2 工程设计:参数约束往往是非线性的
工程设计是非线性规划最古老的温床之一。比如简支梁的矩形截面设计:决策变量是宽度 b 和高度 h,目标是最小化材料用量,也就是min b·h。
看起来目标函数只是两个变量相乘,够简单了吧?可一旦加上强度约束,事情立刻变非线性。最大弯曲应力公式σ = 6FL / (b h²),这已经带了一个 h² 项;最大挠度公式里因为有惯性矩I = b h³ / 12,挠度约束直接变成δ = 4FL³ / (E b h³) ≤ δ_allow,这是 b 乘 h³ 的分式约束。你在图纸上改变截面高度,挠度和应力不会线性地变化,而是按二次、三次的关系狂变。
工程上处理这类问题的难点还在于量纲统一。长度用毫米、力用牛顿、应力用兆帕,算出来的数值动不动是1e9上下,目标函数却是1e3量级,如果不对变量做归一化处理,数值优化的精度会非常差。这类“量级失配”问题恰好也是第6章要讲的坑。
4.3 路径规划与机器人控制:连续空间里的动态约束
机器人手臂从一个姿态运动到另一个姿态,如何做到能耗最低、时间最短,同时又避开障碍物、不超过各关节力矩极限?这本质上是一个带微分方程约束的非线性规划。
把轨迹按时间离散成很多个节点后,每个节点上的位置、速度、加速度都是决策变量,关节动力学方程变成一系列等式约束,障碍物距离变成一系列不等式约束,目标函数可以是终点时间最小,也可以是控制能量积分最小。变量规模轻松上千,约束也是上千条。这种问题直接用简单梯度法几乎不可能收敛,通常需要内点法或SQP,并且要对变量做良好的缩放。无人机航线规划、机械臂轨迹规划,底层逻辑都是这套。
4.4 机器学习:非凸损失函数与优化算法的相遇
很多人可能没意识到,深度网络训练其实是“大规模非凸非线性规划”的一种极限形态。损失函数对权重参数是非凸的、极度非光滑的,参数量从百万到千亿不等,梯度只能用随机批次近似。所以训练工程里的“学习率调度”“动量”“Adam”本质上就是为适应这种病态条件设计的优化策略。
经典非线性规划里追求的高精度收敛,在深度学习里不适用;你只要在有限预算内得到足够低的损失就行。但两者很多东西是相通的,比如对梯度消失/爆炸的察觉、对初始值的敏感性、对目标函数曲率变化的理解。理解非线性规划的核心算法,再去看深度优化器的设计,会有一种看透了底牌的感觉。
5. 实战:把非线性规划跑起来
5.1 工具选型:先判断你的问题是凸的还是非凸的
动手之前先选工具。以Python生态为例,最常用的是scipy.optimize.minimize,它内部封装了多种算法,包括BFGS、L-BFGS-B、SLSQP、trust-constr等。
如果问题能确认是凸问题,尤其是二次规划,我建议直接上凸优化框架cvxpy,它建模非常优雅,求解速度也快,还能给出对偶变量这些有用信息。如果问题是非凸、非光滑或约束复杂,就只能用通用非线性规划求解器,scipy.optimize.minimize里的SLSQP和trust-constr是我的首选;工业级的大规模问题还可以考虑基于内点法的开源求解器IPOPT这类工具。
选型逻辑大致这样:
| 问题类型 | 推荐工具 | 原因 |
|---|---|---|
| 线性规划 | scipy / 专业LP求解器 | 成熟、快、有单纯形法 |
| 二次规划 | cvxpy | 凸二次优化极稳 |
| 一般凸非线性 | cvxpy 或 scipy trust-constr | 支持多种约束 |
| 小规模非凸非线性 | scipy SLSQP | 配置简单、够用 |
| 大规模约束优化 | IPOPT类内点法 | 可扩展到上万变量 |
| 大规模非凸 | 多起点+SQP/内点 | 局部最优不可避,多试几次 |
5.2 一个可复现的投资组合优化案例
我用一个四资产组合的例子,把完整建模和代码流程走一遍。假设四种资产预期收益为[8%, 12%, 15%, 10%],协方差矩阵取一个对称正定矩阵:
import numpy as np from scipy.optimize import minimize mu = np.array([0.08, 0.12, 0.15, 0.10]) Sigma = np.array([ [0.16, 0.05, 0.02, 0.00], [0.05, 0.25, 0.03, -0.01], [0.02, 0.03, 0.40, 0.02], [0.00, -0.01, 0.02, 0.09] ]) def objective(w): risk = w @ Sigma @ w penalty = 0.02 * np.sum(w ** 1.5) return risk + penalty def budget_constraint(w): return np.sum(w) - 1.0 def return_constraint(w): return mu @ w - 0.10 constraints = [ {'type': 'eq', 'fun': budget_constraint}, {'type': 'ineq', 'fun': return_constraint}, ] bounds = [(0.05, 0.6)] * 4 x0 = np.array([0.25, 0.25, 0.25, 0.25]) res = minimize(objective, x0, method='SLSQP', bounds=bounds, constraints=constraints) print(res.x) print("组合波动率(方差):", res.fun) print("组合收益:", mu @ res.x)这个例子里有几个关键设计。
目标函数里的0.02·Σ w_i^1.5是一个非二次的非线性惩罚项,它鼓励权重不要过度集中、更均匀地分散。之所以不用常见的二次惩罚,是因为二次惩罚的思路某种程度上已经被协方差矩阵的二次型覆盖了,指数1.5带来的非二次曲率能更真实地反映“小权重持仓成本高、大权重边际成本上升更快”的实务逻辑。
约束写法上要特别注意:scipy中eq类型的约束要求函数返回0,ineq类型要求函数返回大于等于0。所以收益约束我写成mu @ w - 0.10 >= 0,含义是组合预期收益至少是10%。这是最容易写反的地方,返回值的正负号搞错,求解器会直接告诉你找不到可行解。
在实际运行中,比较可能的结果是:低风险且收益尚可的第四类资产很快触达60%的上限,剩余权重主要在预期收益较高的资产上,同时收益约束大概率是活跃的——也就是正好卡在10%这个目标线上。原因是你的收益目标偏低时,优化器没必要冒着更大风险去追求更高收益。
5.3 调参和敏感性分析:参数扫描比单次求解更有用
单一参数求出来的最优权重只是起点,你在实际业务里更想知道“如果收益目标变成8%或12%,组合会怎么变化”“如果惩罚系数改成0.05,结果会怎样”。这类敏感性分析,本质上就是循环调用优化器。
targets = [0.08, 0.09, 0.10, 0.11, 0.12] for t in targets: def return_constraint(w, t=t): return mu @ w - t res = minimize(objective, x0, method='SLSQP', bounds=bounds, constraints=[{'type': 'eq', 'fun': budget_constraint}, {'type': 'ineq', 'fun': return_constraint}]) print(f"target={t:.2f}, 收益={mu @ res.x:.4f}, 风险={res.fun:.4f}, 权重={np.round(res.x, 3)}")跑这种扫描图时,你很容易看到几个规律:目标收益提高后,低风险资产权重下降,高风险高收益资产权重上升;当目标收益接近理论上限时,优化器开始报找不到可行解,这其实是在告诉你,当前的资产池和上限约束已经支撑不了这么高的收益预期了。
这种“扫描式求解”在业务决策中价值极高。它比单独给一个最优权重更不容易被挑战,因为你既展示了约束变化如何影响方案,也为团队讨论“收益目标应该定多少”提供了决策依据。
6. 实战中躲不开的坑:局部最优、数值病态与不可导
6.1 初始值:一个点出发的算法,天然是近视眼
非线性规划算法大多是局部算法,从初始点出发,沿着下降方向一直走到某个平稳点。如果问题非凸,初始点选得不同,终点可能完全不同。
举个简单的例子,min x² - 2cos(3x)在区间 [0, 8] 上有好几个局部极小值。从 x=0 出发,梯度下降大概率停在约0.7的浅谷;从 x=4 出发,才可能走向约3.7的深谷。如果在业务模型里只跑一次,你甚至不知道自己拿到的是全局最优还是局部最优。
对策很粗暴但有效:多起点。随机生成几十个满足边界约束的初始点,分别跑优化器,把每次的目标值记录下来,最后保留最好的那个解。这个思路不需要任何复杂的全局优化算法,就能在相当程度上对冲局部最优问题。对低维度问题尤其管用。
best = None best_val = np.inf for _ in range(20): x0 = np.random.uniform(0.05, 0.6, size=4) res = minimize(objective, x0, method='SLSQP', bounds=bounds, constraints=constraints) if res.fun < best_val: best_val = res.fun best = res.x print("best:", best)6.2 数值病态与量纲失配:最容易被忽视的大坑
我见过最多“优化器报错”的场景,不是算法选了哪个,而是变量量纲差了好几个数量级。比如工程问题上,长度用毫米、弹性模量用千兆帕、力用牛,约束函数的值动不动就是1e9,目标函数却是1e3量级。求解器在数值差分、步长选择时面对这种量级悬殊的数,很容易认为某些方向“变化不大”而提前停机,或者产生严重的数值噪声。
解决思路说起来非常简单:做变量缩放。把所有决策变量归一化到 [0, 1] 或 [0, 10] 这类区间,目标函数也用基准值做归一。对于投资组合案例,权重本来就在0到1之间,量纲没问题;但如果你要同时优化价格、库存和运输批次,这三个变量的量级可能差万倍,这时候不缩放就是自找麻烦。
6.3 数值梯度的脆弱性与自动微分
使用scipy.optimize.minimize时,如果你不传jac参数,求解器就用有限差分法近似梯度。多数情况下它能凑合跑,但一旦碰到目标函数内部有复杂计算链,比如多次矩阵求逆、对数求和、条件分支,数值梯度的误差会沿着计算链逐层放大,导致线搜索失败,报出类似“Positive directional derivative for linesearch”的信息。
我的习惯是:能算解析梯度就尽量算;算不出来折中方案是使用自动微分框架,像JAX这类库对vmap和grad支持得很好,把目标函数用可微原语重写一遍后,可以免费拿到精确梯度。自动微分与数值差分的结果可以互相校验,告诉你的模型排错省下大量时间。
6.4 多起点之外的兜底策略
如果多起点跑了很多次,结果依然差强人意,可以考虑更高级的手段。
一种是从“小问题”开始热身:先把约束放宽一点、惩罚系数调小一点,求出一个粗略解,再把这个解作为紧约束问题的初始点。这种“逐渐收紧”的思路在工程上叫 continuation,效果往往比直接硬啃原问题好得多。
另一种是混合整数非线性规划的思路:如果某些变量本质上是离散的,不要急着用连续非线性规划近似,先想想能不能把问题分解成“离散枚举 + 连续优化”两层。比如先枚举供应商折扣档位,再固定折扣档位做连续优化,这样每一层的子问题都简单得多。
最后,永远保留“接受平凡解”的心态。工程问题不是数学竞赛,很多情况下找到“可行、够好、稳健”的方案,比执着于“数学意义的最优”要重要得多。非线性规划的价值恰恰在于帮你在可行域里快速找到一个满足约束的好解,而不是保证你能翻越每一座山峰。
做优化这几年,我最大的体会是:一个模型的质量,三分在求解器,七分在建模。你看懂目标函数里每一项的物理含义,理解约束条件哪些是真正起作用的硬约束,远比纠结某一种算法的收敛常数有意义。先把问题简化跑通,再逐步加复杂约束,任何优化项目都应该从这条路开始。