本节摘要:非线性规划(NLP)处理目标或约束含曲线的优化问题,核心机理是 KKT 条件——带约束版的最优性一阶判据。本节以港口集装箱配载的重量分布为战例,演示梯度法与牛顿法的差异、KKT 乘子的读法,以及非凸地形下多初始点策略的必要性。
前两件的兵器都建立在"直"上:直的目标、直的约束。但现实中曲线无处不在:风险以方差度量时目标是二次的;反应釜的产出率对温度是先升后降的曲线;结构应力的约束是非线性的物理律。这类问题统称非线性规划(NLP),一旦入场,第 2 章侦察出的两个麻烦同时出现——地形可能多坑(局部最优),顶点定理失效(最优解可能藏在可行域内部的曲面上)。
战例:港口吊机装载两个集装箱区,总载重受限,但重量的实际成本含二次的稳态损耗项。目标 min x₁² + 1.2x₂² − 6x₁ − 4x₂,约束 x₁ + x₂ ≤ 3、变量非负。目标是个碗(凸),约束是半平面(凸),所以这仍是凸 NLP——先在安全区练枪,再看非凸的变体。
无约束时的直觉:站住不动看看坡向哪边最陡(负梯度方向),朝那迈一步;重复到坡度趋平。这是梯度下降。它的毛病是走锯齿路——在狭长山谷里来回横跳。牛顿法的改进是同时利用曲率(二阶导数),一步跨到"用二次曲面近似出来的谷底",山谷里一两步到位:
import numpy as np from scipy.optimize import minimize f = lambda x: x[0]**2 + 1.2*x[1]**2 - 6*x[0] - 4*x[1] x0 = np.array([0.0, 0.0]) r_grad = minimize(f, x0, method="BFGS") r_newt = minimize(f, x0, method="Newton-CG", jac=lambda x: np.array([2*x[0]-6, 2.4*x[1]-4])) print("拟牛顿解:", r_grad.x, "迭代", r_grad.nit) print("牛顿法解:", r_newt.x, "迭代", r_newt.nit)
带约束时把约束交还求解器:
from scipy.optimize import LinearConstraint, Bounds con = LinearConstraint(np.array([[1.0, 1.0]]), -np.inf, 3.0) r = minimize(f, x0, method="SLSQP", constraints=[con], bounds=Bounds(0, np.inf)) print("约束最优:", np.round(r.x, 3), "目标值:", round(r.fun, 3)) # x 约等于 [1.791, 1.209],和约束 x1+x2=3 恰好顶格
最优解落在约束边界上(x₁+x₂=3 顶格),无约束最优 (3, 1.667) 越界被拉回。这引出本节正主。
无约束时,最优点的梯度为零;带不等式约束时呢?KKT 条件(Karush-Kuhn-Tucker)给出三合一的判据:平稳性——目标梯度可以被激活约束的梯度线性表出(表出系数就是拉格朗日乘子);原始可行——解在可行域内;互补松弛——每个不等式约束要么顶格(乘子可为正),要么松弛(乘子必须为零)。几何直觉:在最优点,目标的"下坡欲"恰好被激活约束的"围墙"顶住,两者方向共线。SLSQP 返回的乘子可以直接读:
print("约束乘子:", r.multiplier_constraint if hasattr(r, "multiplier_constraint") else "见优化结果字段")
乘子约等于 0.417,含义与 LP 的影子价格同源:总载重每放宽一单位,目标改善约 0.417。KKT 乘子是拉格朗日乘子在不等式约束世界的推广,也是第 8 章 LQR 推导里反复出现的老朋友。

把战例的目标换成带余弦起伏的形状(模拟反应率对温度的波动),同一个求解器从不同初始点出发会落到不同的谷底。工程对策有三种:多初始点重启(散点出发取最好,朴素而有效);全局近似(先用低精度扫一遍地形,再在候选谷底精修);凸化重构(把问题改写成凸形式,例如把风险度量换成凸的、把约束换成可以凸松弛的形式——第 9 章会用 CVXPY 展示这条路)。
g = lambda x: (x[0]-1.5)**2 + np.sin(4*x[0]) + (x[1]-1)**2 best = None for s in [0.0, 1.0, 2.0, 3.0, 4.0]: r = minimize(g, [s, s], method="Nelder-Mead") if best is None or r.fun < best.fun: best = r print("多初始点最优:", np.round(best.x, 3), round(best.fun, 3))
⚠️ 常见坑:向业务方汇报非线性求解结果时不提"局部"二字。正确表述是"从若干初始点出发找到的最好解,理论最优性无证书"——NP 难世界有间隙指标,非凸 NLP 连间隙都没有,诚实是唯一的选择。
既然非凸问题如此麻烦,工程界的主流对策之一是:把问题改写成凸的。这条路线叫凸化重构,例子俯拾皆是。几何规划取对数后变凸;最小化最大的几项约束可以引进辅助变量改写成"每项不超过该变量"的凸形式;绝对值与分段线性目标可用辅助变量线性化;著名的 LASSO 回归,把"最小二乘加一范数惩罚"这个表面非线性的问题,整体落在凸二次规划的世界里。一旦问题被改写为凸,局部最优的幽灵自动退场——任何出发点都通向同一个谷底,求解器返回的就是可信赖的答案。第 9 章会演示用建模工具直接声明凸问题,届时你会体会"识别凸结构"是算法工程师与建模者之间的通用语言。看到曲线别急着叹气,先问一句:换个写法能不能凸。
单阶段的兵器到此配齐。下一节处理"今天的决策改变明天的地形"——动态规划登场,贝尔曼方程把多阶段决策折叠成一张递归表。