重剑无锋:超大规模QP快速优化器以及其他solver

求解大规模QPP问题,几乎是是每一种基于核方法的机器学习算法最终要面对的问题,求解器的发展也有及其长的历史,本文记录笔者学习过程中遇到的几种代表性,重要的方法。

接着,本文将继续推导其他不限于QPP的经典solver的实现细节。包括内点法等经典方法,以及现代神经网络训练使用到的adam,Nadam等主流优化器。书山有路勤为径,学海无涯苦作舟,机器学习理论与方法正在不断快速迭代,知识也应当是不断更新的,笔者也会持续更新此文。

此外,优化算法是个无底洞,对数学要求很高,除非专门做优化算法本身的学者,不建议过多时间投入到此处。建议在学习完一些经典的solver后,直接使用现成的库即可。比如scikit-learn,libsvm,torch,cvxopt,osqp等。

SMO

1996年,John Platt 发布一个称为 SMO(Sequential Minimal Optimization,序列最小优化)的强大算法。(SVM)中,我们最终要解决的优化问题是一个二次规划问题(QP)

$$ \max_{\boldsymbol\alpha} \quad W(\boldsymbol\alpha) = \sum_{i=1}^n \alpha_i - \frac{1}{2} \sum_{i,j} \alpha_i \alpha_j y_i y_j K(\mathbf{x}_i, \mathbf{x}_j) $$

$$ \text{s.t. } \sum_{i=1}^n \alpha_i y_i = 0,\quad 0 \le \alpha_i \le C $$

这是一个约束优化问题,变量是 α1, …, αn,它们的个数和样本数量一样。求解上述问题需要专用的二次规划器(如内点法、拉格朗日对偶算法)。当样本数 n 较大(比如上万),存储和运算开销会非常高。所以我们需要一种更高效、结构化的优化方法

SMO 的核心理念是:

“每次只优化两个变量(两个 α),将高维约束问题分解成一系列二元子问题,这些子问题可以解析求解。”

为什么是两个?因为我们有一个等式约束:

$$ \sum_{i=1}^n \alpha_i y_i = 0 $$

优化两个变量时,可以直接通过代数法消去其中一个,从而将约束转化为一维问题。

第一步:固定其他变量,优化两个变量

目标函数(对偶):

$$ W(\alpha) = \sum_{i=1}^n \alpha_i - \frac{1}{2} \sum_{i,j=1}^n \alpha_i \alpha_j y_i y_j K(x_i, x_j) $$

固定除 αi, αj 以外的变量后,这变成了一个关于 αi, αj 的二元二次函数。

由于 αiyi + αjyj = −∑k ≠ i, jαkyk = 常数αi, αj更新前后,应当满足 αioldyi + αjoldyj = αiyi + αjyjs = yiyj,可以将 αi 表示为:

αi = αiold + s(αjold − αj)

第二步:构造二次目标函数

此时的目标函数事实上变成了关于αi,αj的二元二次函数。 $$ W(\alpha) = \text{常数} + \alpha_i + \alpha_j - \frac{1}{2} \left[ \alpha_i^2 K_{ii} + \alpha_j^2 K_{jj} + 2 \alpha_i \alpha_j y_i y_j K_{ij} \right] - \text{其他项} $$

代入:

αi = αiold + s(αjold − αj)

将上述整理,写为标准一维二次函数:

$$ W(a_j) = -\frac{1}{2} \eta a_j^2 + A a_j + \text{常数} $$

其中:

  • η = Kii + Kjj − 2Kij
  • A = yj(Ei − Ej)
  • *Ei = f(xi) − yiEj = f(xj) − yj:预测误差

$$ \frac{dW}{d\alpha_j} = y_j (E_i - E_j) - \eta \cdot (\alpha_j - \alpha_j^{\text{old}}) \Rightarrow $$

令导数为 0,得到解析更新公式:

$$ \alpha_j^{\text{new, unclip}} = \alpha_j^{\text{old}} + \frac{y_j (E_i - E_j)}{\eta} \tag{2} $$

第三步:边界裁剪(clip)

20250505163359

为满足盒约束,根据 yi, yj 的关系,推导出合法区间 [L, H]。:

  • yi ≠ yj

    L = max (0, αjold − αiold),  H = min (C, C + αjold − αiold)

  • yi = yj

    L = max (0, αiold + αjold − C),  H = min (C, αiold + αjold)

然后进行裁剪:

αjnew = clip(αjnew, unclipped, L, H)

第四步:更新另一个变量 αi

用约束表达式反求另一个变量:

αinew = αiold + s(αjold − αjnew)

第五步:更新阈值 b

b的更新是一种启发式方法,不做过多赘述,有两种情况下可选的更新策略:

b1 = bold − Ei − yi(αinew − αiold)Kii − yj(αjnew − αjold)Kij

b2 = bold − Ej − yi(αinew − αiold)Kij − yj(αjnew − αjold)Kjj

最终的更新方式:

  • 如果 0 < αi < C:设 b = b1
  • 否则如果 0 < αj < C:设 b = b2
  • 否则取均值 b = (b1 + b2)/2

SOR(逐次超松弛)

因为解大规模QPP等同于解大规模线性方程组,因此解方程组的方法也可以用于解QPP。

我们从最基本的开始讲解,循序渐进地理解从雅可比(Jacobi)迭代法发展到 SOR(Successive Over-Relaxation,逐次超松弛法)的来龙去脉。

一、目标:求解线性方程组

我们的问题是: 给定一个线性方程组:

Ax = b

其中:

  • A ∈ ℝn × n:系数矩阵
  • x ∈ ℝn:待求解向量
  • b ∈ ℝn:常数向量

A 较大或稀疏时,直接解法(如高斯消元、LU 分解)代价太高,所以我们使用迭代法

二、雅可比迭代法(Jacobi Method)

在数值线性代数中,我们希望解线性方程组:

Ax = b

当矩阵 A 维度较大或稀疏时,直接求逆代价过高,因此考虑构造一个迭代过程,令解 x 逐步逼近。

这里我们将矩阵 A 分解为三部分:

A = D + L + U

  • D:对角线上的元素(对角矩阵)
  • L:下三角部分(不含对角)
  • U:上三角部分(不含对角)

Ax = b 改写为:

Dx = b − (L + U)x

两边左乘 D−1,得到不动点形式:

x = D−1(b − (L + U)x)

自然导出 Jacobi 迭代公式:

$$ \boxed{ \mathbf{x}^{(k+1)} = D^{-1} \left( \mathbf{b} - (L + U)\mathbf{x}^{(k)} \right) } $$

同理可以使用分量形式表达

对于方程组第 i 行:

ai1x1 + ⋯ + aiixi + ⋯ + ainxn = bi

解出 x_i

$$ x_i = \frac{1}{a_{ii}} \left( b_i - \sum_{j \ne i} a_{ij}x_j \right) $$

引入迭代思想,即将未知分量 x_j 全部替换为上一步的近似值 x_j(k),得到:

$$ x_i^{(k+1)} = \frac{1}{a_{ii}} \left( b_i - \sum_{j \ne i} a_{ij}x_j^{(k)} \right) $$

Jacobi 的主要问题是收敛慢。在一次迭代中,更新 xi(k + 1) 时,不利用已经更新的 x1(k + 1), …, xi − 1(k + 1),导致信息利用不充分。

其收敛性取决于迭代矩阵 M = D−1(L + U) 的谱半径 ρ(M)

  • ρ(M) < 1,则收敛
  • ρ(M) > 1,则发散

例如:

$$ A = \begin{bmatrix} 1 & 2 \\ 3 & 4 \end{bmatrix} $$

Jacobi 的迭代矩阵谱半径约为 1.08 > 1,因此不收敛。

✅ 小结

Jacobi 方法从线性方程组中构造出一个显式的不动点形式,每一步使用上次的全部值进行更新,适合并行计算。但由于更新中未充分利用最新值,收敛性较差,通常作为后续 Gauss-Seidel 或 SOR 方法的引子。

高斯-赛德尔(Gauss-Seidel)迭代法

Jacobi 有个问题:每次迭代都等到所有 xj(k) 用完才算完,不充分利用已更新的结果。

Gauss-Seidel 改进:一边算一边更新

更新公式:

$$ x_i^{(k+1)} = \frac{1}{a_{ii}} \left( b_i - \sum_{j=1}^{i-1} a_{ij} x_j^{(k+1)} - \sum_{j=i+1}^{n} a_{ij} x_j^{(k)} \right) $$

也就是:前面的 xj 用新值,后面的用旧值。

优点:通常比 Jacobi 收敛快 缺点:仍可能收敛慢,特别是矩阵条件数不好时

逐次超松弛法(SOR)

Gauss-Seidel 其实是 Jacobi 的“小步走”,SOR 就是加速这个过程的“大步走”。

在 Gauss-Seidel 的基础上,SOR 加入一个松弛因子 ω ∈ (0, 2),做“加权平均”:

$$ x_i^{(k+1)} = (1 - \omega) x_i^{(k)} + \frac{\omega}{a_{ii}} \left( b_i - \sum_{j=1}^{i-1} a_{ij} x_j^{(k+1)} - \sum_{j=i+1}^{n} a_{ij} x_j^{(k)} \right) $$

  • ω = 1,就退化为 Gauss-Seidel;
  • ω ∈ (1, 2),为超松弛(加速)
  • ω ∈ (0, 1),为欠松弛(更稳定)

通过松弛因子ω引入惯性项,动态调整步长:

xi(k + 1) = (1 − ω)xi(k) + ω(Gauss − Seidel)

ω > 1:放大修正步长(超松弛),加速逼近解。ω < 1:缩减不稳定更新(低松弛),抑制震荡。

DCDM对偶坐标下降方法

源自ICML一篇论文(https://www.csie.ntu.edu.tw/~cjlin/papers/cddual.pdf)[https://www.csie.ntu.edu.tw/~cjlin/papers/cddual.pdf]

对偶坐标下降方法(DCDM)的数学推导全流程

原始SVM问题: $$ \min_w \frac{1}{2} w^T w + C \sum_{i=1}^l \xi(w; x_i, y_i) $$ 其中损失函数ξ为L1或L2损失: - L1-SVM:ξ(w; xi, yi) = max (1 − yiwTxi, 0)

  • L2-SVM:ξ(w; xi, yi) = max (1 − yiwTxi, 0)2

对偶问题推导:引入拉格朗日乘子αi,构造对偶函数。通过KKT条件,原始问题的对偶形式为

$$ \min_\alpha \frac{1}{2} \alpha^T \bar{Q} \alpha - e^T \alpha \quad \text{s.t.} \quad 0 \leq \alpha_i \leq U $$ 其中: -  = Q + DQij = yiyjxiTxj

  • L1-SVM:Dii = 0U = C

  • L2-SVM:Dii = 1/(2C)U = ∞

为了方便叙述,先定义这样一组向量 αk, i = [α1k + 1, …, αi − 1k + 1, αik, …, αlk]T 此时的α表示正在进行第K+1轮迭代,按顺序已经迭代完了前i-1个分量,正准备迭代其中第i个分量。

DCDM的子问题定义: 固定除αi外的所有变量,优化单变量αimindf(α + dei)  s.t.  0 ≤ αi + d ≤ U 其中$f(\alpha) = \frac{1}{2} \alpha^T \bar{Q} \alpha - e^T \alpha$

目标函数泰勒展开后(二阶近似)是一个关于d的二次函数: $$ f(\alpha + d e_i) = \frac{1}{2} \bar{Q}_{ii} d^2 + \nabla_i f(\alpha) d + \text{常数项} $$ 其中梯度分量:if(α) = (α)i − 1 = yiwTxi − 1 + Diiαiw = ∑jyjαjxj

闭式解推导: 1. 二次函数极小值:最优d为无约束解: $$ d^* = -\frac{\nabla_i f(\alpha)}{\bar{Q}_{ii}} $$ 2. 投影到可行域:

$$\alpha_i^{k,i+1}=\min\left(\max\left(\alpha_i^{k,i}-\frac{\nabla_if(\boldsymbol{\alpha}^{k,i})}{\bar{Q}_{ii}},0\right),U\right)$$

(即使用clip函数限制在(0,U)区间上)

此时问题变成了如何高效求解if(α),自然地想法是直接求解$\nabla_if(\boldsymbol{\alpha})=(\bar{Q}\boldsymbol{\alpha})_i-1=\sum_{j=1}^l\bar{Q}_{ij}\alpha_j-1$,其中ij = xixj,时间复杂度是O(l),是平均非零特征数,这样的计算开销是巨大的。(可能会疑惑为何不预先计算出整个矩阵Q?事实上是因为存储成本太高!若样本数l = 105, 那么Q存储1010个浮点数,粗略记每个单精度浮点数4字节,此时内存开销已经来到将近40GB,完全不可行)

因此我们计算分量式子 if(α) = yiwTxi − 1 + Diiαi 当然,如果按照定义式子$\boldsymbol{w}=\sum_{j=1}^ly_j\alpha_j\boldsymbol{x}_j$求解w,复杂度就回到了O(l),为此作者也对w在每一轮进行迭代 w ← w + (αi − ᾱi)yixi

20250505215415

根据当前 αi 的位置,PG的计算分为以下三种情况:

αi 的位置 PG的计算公式 物理意义
0 < αi < U(内部) PG = G 变量未触及边界,直接使用原始梯度 G 的方向更新。
αi = 0(下边界) PG = min (G, 0) 仅允许梯度方向为负(G < 0)时更新,避免 αi < 0
αi = U(上边界) PG = max (G, 0) 仅允许梯度方向为正(G > 0)时更新,避免 αi > U

OSQP(https://osqp.org/)

原始论文地址(https://web.stanford.edu/~boyd/papers/pdf/osqp.pdf)

介绍OSQP之前,需要先介绍ADMM(Alternating Direction Method of Multipliers)方法。ADMM是一个非常经典的优化算法,OSQP就是基于ADMM的一个变种。

ADMM

Boyd等人的推导:(https://stanford.edu/~boyd/papers/pdf/admm_talk.pdf)

ADMM 将优化问题分解为两个交替更新的子问题: $$ \begin{aligned} \min_{x,z} \quad & f(x) + g(z) \\ \text{s.t.} \quad & A x + B z = c \end{aligned} $$ 写出增广拉格朗日函数 $$\begin{aligned}L_\rho(\mathbf{x},\mathbf{z},\boldsymbol{\lambda})&=Q_\rho(\mathbf{x},\mathbf{z})+\boldsymbol{\lambda}^\top(\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{z}-\mathbf{c})\\&=f(\mathbf{x})+g(\mathbf{z})+\frac{\rho}{2}\|\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{z}-\mathbf{c}\|_2^2+\boldsymbol{\lambda}^\top(\mathbf{A}\mathbf{x}+\mathbf{B}\mathbf{z}-\mathbf{c})\end{aligned}$$ 迭代公式: $$ \begin{aligned} x^{k+1} &= \arg\min_x \left( f(x) + \frac{\rho}{2} \| A x + B z^k - c + \lambda^k \|^2 \right) \\ z^{k+1} &= \arg\min_z \left( g(z) + \frac{\rho}{2} \| A x^{k+1} + B z - c + \lambda^k \|^2 \right) \\ \lambda^{k+1} &= \lambda^k + \rho (A x^{k+1} + B z^{k+1} - c) \end{aligned} $$

OSQP演变

从ADMM到OSQP的数学推导

考虑凸二次规划问题: $$ \begin{aligned} \min_{x} \quad & \frac{1}{2}x^T P x + q^T x \\ \text{s.t.} \quad & l \leq A x \leq u \end{aligned} $$ 其中 P ≽ 0A ∈ ℝm × n。引入辅助变量 z,将原问题改写为: $$ \begin{aligned} \min_{x,z} \quad & \frac{1}{2}x^T P x + q^T x + I_{[l, u]}(z) \\ \text{s.t.} \quad & A x = z \end{aligned} $$ 其中 I[l, u](z) 是指示函数: $$ I_{[l, u]}(z) = \begin{cases} 0 & \text{if } l \leq z \leq u \\ +\infty & \text{otherwise} \end{cases} $$

构造增广拉格朗日函数: $$ \mathcal{L}_\rho(x, z, y) = \frac{1}{2}x^T P x + q^T x + I_{[l, u]}(z) + y^T (A x - z) + \frac{\rho}{2} \|A x - z\|^2 $$ 其中 y ∈ ℝm 为对偶变量,ρ > 0 为惩罚参数。采用ADMM迭代步骤交替更新 xzy

(1) x-子问题 固定 zk, yk,求解: $$ x^{k+1} = \arg\min_x \left( \frac{1}{2}x^T P x + q^T x + y^{kT} (A x - z^k) + \frac{\rho}{2} \|A x - z^k\|^2 \right) $$ 展开并整理目标函数: $$ x^{k+1} = \arg\min_x \frac{1}{2}x^T (P + \rho A^T A) x + \left( q + A^T ( \rho z^k - y^k ) \right)^T x $$ 其解析解为: xk + 1 = −(P + ρATA)−1(q + AT(ρzk − yk))

(2) z-子问题 固定 xk + 1, yk,求解: $$ z^{k+1} = \arg\min_z \left( I_{[l, u]}(z) + \frac{\rho}{2} \| z - (A x^{k+1} + \frac{y^k}{\rho}) \|^2 \right) $$ 等价于投影到区间 ([l, u]): $$ z^{k+1} = \Pi_{[l, u]} \left( A x^{k+1} + \frac{y^k}{\rho} \right) $$

(3) 对偶变量更新 yk + 1 = yk + ρ(Axk + 1 − zk + 1)

完整算法流程 - 输入:初始 x0, z0, y0,参数 ρ > 0,容差 ϵ
迭代直到收敛: - 1. 更新 xk + 1 = −(P + ρATA)−1(q + AT(ρzk − yk)) - 2. 更新 $z^{k+1} = \Pi_{[l, u]} \left( A x^{k+1} + \frac{y^k}{\rho} \right)$ - 3. 更新 yk + 1 = yk + ρ(Axk + 1 − zk + 1) - 4. 检查终止条件 rk∥ ≤ ϵ, sk∥ ≤ ϵ


重剑无锋:超大规模QP快速优化器以及其他solver
http://example.com/2025/05/05/文献/QPP快速求解器/
作者
bradin
发布于
2025年5月5日
许可协议