ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

保凸运算与复合函数凹凸性判断:优化问题中的实用工具箱

保凸运算与复合函数凹凸性判断:优化问题中的实用工具箱 最近在处理一个优化问题的时候我又一次体会到判断一个目标函数是不是凸的这件事很多时候比求解本身更让人头疼。函数一旦复杂起来直接从定义去验证 Jensen 不等式几乎不可能而 Hessian 半正定这种二阶判断又要求函数二阶可导导数越算越脏。后来我把“保凸运算”这套工具当成日常习惯使用思路反而清晰了很多。这篇文章就聊聊我一直用的保凸运算清单以及复合函数凹凸性判断的完整规则。内容不挑方向只要你经常和目标函数打交道无论是做机器学习、运筹优化还是数学建模这套东西都能直接拿来用。在机器学习里凸性几乎决定了训练过程的稳定性。线性回归的损失是凸的逻辑回归的负对数似然是凸的SVM 的目标函数也是凸的。正因为这些模型凸梯度下降才能稳定收敛不需要反复换初始化碰运气。你如果在一个非凸问题上硬套梯度下降最后收敛到哪里很大程度上取决于初始点多跑几次结果都不一样这在工程上是很难接受的。所以我面对一个新函数时第一个问题永远是它是凸的吗如果是后面求解这条路就顺了。1. 从实际问题到凸性判断先搞清楚“保凸”为什么值得学1.1 凸性到底意味着什么不只是教科书定义凸函数最直观的定义其实一句话就能说清楚定义域是凸集并且对任意 x、y 和 θ ∈ [0,1]都有 f(θx (1-θ)y) ≤ θf(x) (1-θ)f(y)。用大白话讲函数图像上任意两点的连线永远落在函数图像的上方。这个性质听起来像一道证明题实际价值却非常大对凸函数做最小化任意一个局部最优解都是全局最优解而且梯度下降、牛顿法、近端梯度这类算法都有严格的收敛性保证。换句话说一旦你确认目标函数是凸的求解就变成“按流程执行算法”的事不需要担心卡在坏的局部极小值里。生活里也有类似的例子。想象你站在一个标准的碗里无论从哪个方向走最终都会滑到碗底而且碗底只有一个但如果站在鸡蛋盒上你可能卡在某个凸起的脊上以为自己到了最低点其实旁边还有更深的坑。凸函数就是那个碗非凸函数就是鸡蛋盒。这个直觉在优化算法设计里直接决定了你敢不敢用简单的迭代法。1.2 为什么不能每次都从定义出发验证我知道有些朋友会问想验证凸性用定义硬推不行吗确实可以但对稍微复杂的函数就不现实。比如一个嵌套了两层指数、带矩阵运算的函数你要把 Jensen 不等式展开验证可能写满两页纸而用 Hessian 半正定判断得先算二阶导表达式常常是几百项的和不仅费时间还特别容易在某一步计算错误。更麻烦的是实际建模中函数基本不是从天而降的而是由若干个基本函数组合出来的。比如“损失 正则项 业务约束的惩罚项”这种结构每一项都有明确来源整体凹凸性其实可以由每个部件的性质推导出来。保凸运算提供的正是这样一套“组合规则”它告诉你哪些操作会把凸函数仍然变成凸函数。这就像搭积木只要你手头有几种公认的“凸积木”比如范数、仿射函数、指数函数再用允许的“连接件”把它们拼起来整体的凸性就自然有保证。这也是工程实践中判断凸性最高效的路径。2. 五种常用保凸运算从直觉到结论2.1 非负加权和搭建目标函数的“加法基础”先说最常用的一种如果 f1、f2 都是凸函数那么对任意的非负系数 α1、α2函数 α1 f1 α2 f2 仍然是凸函数。这个性质的证明很直接直接把加权和套进 Jensen 不等式就行。直觉上也好理解两个碗叠在一起不管每个碗多深叠加之后口子还是朝上的但系数必须是非负的如果拿一个正碗减去一个太大的碗叠加结果可能就不再是碗了。这个运算在建模中几乎是默认使用的。比如最小二乘加上正则项f(x) ||Ax - b||_2^2 λ||x||_1只要 ||Ax - b||_2^2 是凸的、||x||_1 也是凸的λ ≥ 0那么整体就一定凸。我计算损失函数时经常遇到“业务损失 惩罚项”的组合第一件事就是逐项确认凸性然后再放心地加在一起。另外提醒一下如果系数是负数那就反过来了凸函数减去凸函数既可能凸也可能凹也可能不凸不凹不能直接下结论。2.2 与仿射函数复合对定义域做拉伸和平移第二种保凸运算是仿射复合如果 f 是凸函数A 是矩阵b 是向量那么 g(x) f(Ax b) 仍然是凸函数。这本质上是对变量做线性变换或者说是对定义域做旋转、伸缩、平移。一个碗经过这些操作之后还是碗只是位置和朝向变了。这里不要求 A 是方阵也不要求 A 可逆哪怕把高维变量压缩到低维再映射凸性依然保持。这个性质在建模里太常见了。线性回归里的 f(x) ||Ax - b||_2^2其实可以看成 f(z) ||z||_2^2 作用于 z Ax - bz 是 x 的仿射函数于是由仿射复合可知它凸再如逻辑回归里的线性得分 w^T x b 也是仿射外层套上任意凸函数整体都是凸的。我在写推导时会刻意把“线性层”单独拆出来确认它就是一个仿射函数这样外层函数只要凸整条表达式就总是凸的。2.3 逐点最大值和上确界制造“尖角”的利器第三个常用保凸运算是逐点最大值如果 f1 和 f2 都是凸的那么 f(x) max(f1(x), f2(x)) 也是凸的。推广到无穷多个函数f(x) sup_{y ∈ A} g(x, y)只要对每个固定的 yg(·, y) 是凸的那么上确界函数也是凸的。直觉上多个碗取上包络得到的形状依然像一个碗只是底部会出现一些棱和尖角但整体还是“碗形”。这个运算的现实价值很大。SVM 里常用的 hinge loss也就是 max(0, 1 - y(w^T x b))就是 0 和仿射函数逐点取最大所以天然凸。分段线性函数、最大值池化这类操作也都可以归入这一类。还有一个非常有力的应用是支撑函数对于任意集合 C函数 σ_C(x) sup_{y ∈ C} x^T y 是凸的因为 x^T y 对每个 y 都是 x 的仿射函数取上确界仍然凸。很多复杂的对偶问题里靠这个性质能快速判断一大堆表达式的凸性。2.4 透视函数与部分极小化两个进阶但实用的工具这两个运算相对进阶但用到的场景非常多。先看透视函数如果 f 是凸函数那么 g(x, t) t f(x / t) 在 t 0 上也是凸函数。第一次看到这个定义可能有点绕它的用途主要体现在一些矩阵表达式中。比如二次型 x^T X^{-1} x其中 X 是正定矩阵这个函数关于 (x, X) 联合凸正是由 f(z) z^T z 的透视函数得到的。在估计问题、最优控制里这个结论经常出现如果没有透视函数直接证明联合凸性会很痛苦。部分极小化指的是如果 F(x, y) 关于 (x, y) 联合凸C 是非空凸集那么 g(x) inf_{y ∈ C} F(x, y) 仍然凸。这个性质有点反直觉把变量 y 优化掉剩下的函数居然还保持凸。一个标准例子是点到凸集的距离函数dist(x, C) inf_{y ∈ C} ||x - y||其中 ||x - y|| 关于 (x, y) 凸所以距离函数对 x 凸。做概率推断时把隐变量优化掉或者积分掉的操作也很常见只要原始联合函数凸边缘化的结果在较宽条件下还是凸的。这就给了建模一个很大的自由度你可以放心地加入辅助变量反正后面消掉也不破坏凸性。2.5 复合函数规则标量版从“嵌套”看凸性最后要重点说的是复合函数规则这也是判断“复合函数凹凸性”最核心的工具。设 f(x) h(g(x))其中 h 是外层函数g 是内层函数。那么判断 f 的凹凸性不是简单看 h 和 g 各自凸不凸而是要看 h 的单调性才能确定。这里我把规则先放在一张表里后面第 3 节会详细展开。内层 g外层 h复合 f h(g(x))凸凸且非递减凸凹凸且非递增凸凹凹且非递减凹凸凹且非递增凹初看会有点奇怪外层函数明明是凸的为什么需要内层配合“单调方向”才对得上原因在于函数复合会把内层的取值映射到外层函数的不同区间。用一个反例说明h(t) t^2 是凸的g(x) x^2 - 1 也是凸的但复合得到 f(x) (x^2 - 1)^2这个函数图像是 W 形的在 x 0 附近二阶导为负根本不是凸函数。问题就出在 g 的值域同时覆盖了 h 的递减区间 t 0 和递增区间 t 0而 h 在全局不单调。反过来如果 h(t) exp(t)它是凸且递增的无论 g 是凸还是凹只要复合有意义exp(g(x)) 在 g 凸时一定凸。这个“单调方向”就是复合规则里的关键。3. 复合函数的凹凸性判断一张检查表走天下3.1 标量复合完整规则与实例标量复合的规则可以延展成更系统的检查表。除了上一节列出的四条基本规则实际应用中还经常遇到 h 在部分区间单调的情况。一个常见的做法是先把 h 的定义域和单调区间理清楚再检查 g 的值域是否落在 h 的单调区间内最后套用四条规则中的某一条。举几个我经常用到的组合你以后可以直接对照f(x) exp(g(x))h(t) exp(t) 凸且非递减。如果 g 凸则 f 凸。这个结论我经常用来处理指数型损失。f(x) log(g(x))h(t) log(t) 在 t 0 上凹且非递减。如果 g 凹且恒正则 f 凹如果 g 凸log(g) 的凸性无从保证需要单独验证。f(x) 1 / g(x)h(t) 1/t 在 t 0 凸且非递增。如果 g 凹且恒正则 f 凸如果 g 凸1/g 不一定是凸比如 1/(1x^2) 就不是凸的。f(x) sqrt(g(x))h(t) sqrt(t) 在 t ≥ 0 上凹且非递减。如果 g 凹且非负则 f 凹。这类判断的关键是“外层函数的单调性 凸性 内层函数的值域”三者同时确认。我在写代码前会在草稿纸上先把外层函数 h 的单调区间画一遍比直接硬推要快得多。3.2 向量复合规则多输入情况下怎么判当外层函数有多个输入时规则会变得更丰富。设 f(x) h(g1(x), g2(x), ..., gk(x))核心规则是如果 h 是凸函数并且 h 关于每个参数 gi 都是非递减的而每个 gi 都是凸函数那么 f 凸反过来如果 h 关于每个参数 gi 都是非递增的而每个 gi 都是凹函数那么 f 也凸。还有一种混合情况h 对某些参数递增、对另一些参数递减只要对应方向的 gi 分别是凸或凹结论依然成立。此时需要逐个参数单独检查不能只看整体。最有名的例子是 log-sum-exp 函数f(x) log(∑ exp(xi))。它对每个 xi 的偏导数等于 exp(xi) / ∑ exp(xj)恒大于 0所以 LSE 对每个参数都是递增的。又因为 LSE 本身是凸函数所以只要 g1 到 gk 都是凸函数整个复合表达式 log(∑ exp(gi(x))) 就是凸函数。这个结论在机器学习里应用极广很多带指数结构的损失函数都可以直接套上去。再举一个例子softmax 交叉熵损失对于固定特征提取器的最后一层线性权重其实是凸的。因为里层是仿射函数 w_i^T h b_i仿射函数既是凸也是凹而外层 LSE 对每个参数递增所以复合后对 w 凸再加上交叉熵中的线性项整体还是一个凸问题。我说“固定特征提取器”是因为一旦底层网络也参与优化复合结构发生变化凸性就失效了这也是深度学习中全网络训练几乎没人谈凸性的原因。3.3 实操判断四步法我自己的判断流程已经固定成四步这里完整写出来。第一步把目标函数拆成基本函数组合标出内层和外层。比如 f(x) log(∑ exp(a_i^T x b_i))先拆成 z_i a_i^T x b_i这是仿射函数再拆成 outer log-sum-exp。第二步从最内层开始逐个确认每个子函数的凸性和值域。仿射函数 z_i 对 x 是线性的既是凸也是凹值域是整个实轴LSE 的定义域是整个实轴值域是实数。第三步确认外层函数对每个参数的单调性方向。LSE 对每个参数都是严格递增的因为偏导数恒为正。这里要注意判断单调性用的是扩展值意义上的单调也就是在凸函数的定义域内讨论。第四步把内层结果代入复合规则得出整体结论。LSE 凸且坐标递增内层仿射凸于是整体凸。整个流程不到一分钟比从定义出发省太多力气。我在实际工作中经常重复这套流程而且会把每一步的结论写在代码注释里。因为几个月后再回来看一段算法代码谁还记得当初为什么这个式子能保证是凸的有了注释下次加减项、换损失函数时就能快速判断是否还保持凸性。4. 实操记录用保凸运算验证几个典型目标函数4.1 最小二乘 L1 正则化组合拳判断第一个例子是机器学习里最经典的组合f(x) ||Ax - b||_2^2 λ||x||_1。我拿到这种目标第一反应不是去求导而是先搭组合树。先看第一项||z||_2 是凸函数z Ax - b 是 x 的仿射函数根据仿射复合保凸||Ax - b||_2 凸再对范数取平方也就是外层 h(t) t^2 作用于 t ||Ax - b||_2。这里 t ≥ 0h(t) t^2 在 t ≥ 0 上凸且递增所以平方后依然凸。第二项 ||x||_1 是凸函数范数都是凸函数乘以非负系数 λ 后保凸。最后两项相加由非负加权和规则整体凸。结论可以用近端梯度法、ADMM 等成熟算法求解而且全局最优有保证。这个判断过程看起来平平无奇但每一步都有保凸运算在撑腰换成更复杂的正则项也同样适用。4.2 log-sum-exp 与交叉熵分类问题中的凸性第二个例子是逻辑回归的负对数似然。单个样本的损失可以写成 log(1 exp(-y w^T x))其中 y ∈ {1, -1}t -y w^T x 是 w 的仿射函数。外层 h(s) log(1 e^s) 是凸函数并且 h(s) e^s/(1 e^s) 恒在 0 和 1 之间所以 h 是递增的。按照标量复合规则h 凸且递增内层 t 仿射凸所以整个损失对 w 凸。对多个样本求和非负加权和保凸于是逻辑回归的目标函数是凸的。这就是为什么常见的机器学习库能直接用各种凸优化求解器稳定收敛。同样的思路可以推广到多分类 softmax 交叉熵对 w 而言每个 logits z_i w_i^T h b_i 是仿射交叉熵中 LSE 部分对 z 凸且坐标递增所以整体对 w 凸。我在做模型推导时只要看到这种结构第一反应就是先把线性层单独拿出来剩下的事情交给保凸运算。4.3 一个常见的坑凸函数嵌套不一定是凸函数这里必须专门强调一个最常见的误区两个凸函数复合在一起结果不一定是凸的。前面已经提过 h(t) t^2、g(x) x^2 - 1 的例子这里再给一个更“日常”的反例f(x) 1 / (x^2 1)。内层 g(x) x^2 1 是凸的且恒正外层 h(t) 1/t 在 t 0 上是凸的但 h 在 t 0 上是递减的因为 h(t) -1/t^2 0。按照规则h 凸且递减配上内层 g 凸并不能推出复合凸。事实上 f(x) 1/(1x^2) 的图像是中间凸起、两侧平坦下降它的二阶导数在 |x| 稍微大一点的地方变负整体不是凸函数。这类函数如果在优化问题里出现往往就是隐藏的非凸来源。我在设计新的损失函数或正则项时会在纸上把复合规则表过一遍确认单调方向匹配否则宁可换一种表达方式。凸性不是玄学每一处失效基本都能在单调方向或定义域上找到原因。4.4 扩展到矩阵变量核范数与迹的逆保凸运算不只适用于向量变量在矩阵空间同样有效。比如 f(X) tr(X^{-1}) 在正定矩阵锥上是凸函数这个结论在协方差估计、最优实验设计里很常用g(X) log det X 则是凹函数核范数 ||X||_* sum(σi(X)) 是凸函数因为它本质上是奇异值组成的向量的 L1 范数。后面这几点虽然不能完全靠前面五条基础运算推出来但它们作为“已知凸函数库”的一部分同样可以参与复合。这对建模的意义是如果目标函数里出现“矩阵求逆 线性复合”的结构可以放心按凸问题来处理。比如要最小化 tr((AXB)^{-1}) 之类的表达式先确认内层 AXB 是 X 的仿射函数然后外层 tr(·^{-1}) 在正定域上凸再加上复合规则整体凸性就明确了。我印象很深的一次是设计带协方差约束的分配问题当时就是靠这套判断快速认定目标是凸的省掉了大量数值实验试错。5. 常见问题与排查技巧实录5.1 快速自检清单判断前先过一遍我平时拿到一个新函数在动手写代码前会先过一遍下面的清单这里整理成一张表方便对照。检查项常见错误正确动作定义域是否为凸集忽略 log、sqrt、1/t 的定义域限制先标出定义域确认每个子函数在定义域内有意义内层函数的凸凹性把仿射函数当成“没有凸性”仿射函数既是凸也是凹可以直接参与规则外层函数的单调方向只查凸性不查单调性对外层函数求导看符号确认在值域上单调复合方向把 h 和 g 的角色搞反先认准谁是外层谁是内层非负系数加了负权重还当凸系数必须非负否则规则失效这条清单看起来简单实际踩坑率非常高。尤其是定义域这一项log 和 sqrt 出现的频率很高只要取值范围一越界整个复合规则就不能直接用。5.2 用数值方法快速排查凸性有时候表达式太复杂纸上判断太慢我会先用数值方法快速排查一下。一个可靠的手段是“方向函数法”对目标 f 随机选一个起始点 x0 和方向 d定义一元函数 φ(t) f(x0 t d)然后在 t 的区间上采样检查 φ 是否满足凸函数的 Jensen 不等式。如果发现 φ 在某一段明显非凸那么 f 一定非凸如果多个方向测下来都像凸的再去从理论上找证明。这个方法的计算成本低而且能快速暴露反例。如果函数二阶可导还可以用数值 Hessian 检查。用 scipy 的 approx_fprime 或者自己写有限差分都可以代码如下import numpy as np from scipy.optimize import approx_fprime def numerical_hessian(f, x, eps1e-5): n len(x) H np.zeros((n, n)) for i in range(n): def df_dxi(z): return approx_fprime(z, f, 1e-6)[i] H[:, i] approx_fprime(x, df_dxi, eps) return (H H.T) / 2 # 用法示例检查 f(x) log(1 exp(x1 2*x2)) 在某个点的 Hessian f lambda x: np.log(1 np.exp(x[0] 2*x[1])) x0 np.array([1.0, -1.0]) H numerical_hessian(f, x0) print(特征值:, np.linalg.eigvalsh(H))注意数值结果只能作为辅助证据不能替代理论证明。它最大的价值是帮你快速判断“这个函数看起来不是凸的”或者“这个区域可能有问题”从而节省在错误方向上花的时间。5.3 踩坑记录关于定义域、单调方向与复合方向最后分享几个我踩过的具体坑。第一个坑是定义域。比如 log(det X)X 必须正定如果在推导过程中忽略了 X 的正定性直接说 log det 是凹函数看起来没什么区别但实际优化时算法很容易跑到非正定区域去数值上直接 NaN。所以每次应用复合规则之前我都会把内层函数的取值范围是否落在外层函数定义域里确认一遍。第二个坑是单调方向搞反。外层 h(t) exp(t) 是递增的内层 g 凸能推出 exp(g) 凸但换成外层 h(t) exp(-t)它变成递减的了这时需要 h 凸递减与内层 g 凹才能保证复合凸。比如 g(x) -x^2 是凹的exp(-(-x^2)) 其实就是 exp(x^2)这个函数不凸也不凹而如果把内层换成凹函数再乘上正确的系数才能得到凸性。这说明不能只凭“外层是指数函数应该保凸”这种粗糙印象要看指数自变量的符号。第三个坑是把“非凸”等同于“凹”。非凸函数可能是一部分凸一部分凹也可能是既非凸也非凹。在优化实践中一个函数如果不是凸的不要简单按凹函数的思路去处理而是要重新审视局部结构。通常非凸来源出现在外层函数的单调区间跨越了内层函数值域的位置找到这个位置很多设计问题就能避免。我在做算法选型时一旦确认目标非凸就会考虑重新设计表达式尽量把非凸部分隔离到少量变量上或者用凸松弛近似而不是硬着头皮用启发式搜索。最后再分享一点个人习惯我把保凸运算的规则表打印出来贴在工位的显示器边框上。每次设计目标函数时先看一眼心里默念一遍“非负加权、仿射复合、逐点最大、透视、部分极小化、复合规则”然后开始搭组合树。这套方法帮我避开了无数次无效的数值实验。如果你也经常和优化目标函数打交道建议把它变成肌肉记忆遇到新公式先拆再判比埋头求导快得多也踏实得多。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进