
简介这份资源是凸优化理论学习的心得笔记配套代码包面向正在入门或希望系统梳理凸优化知识体系的机器学习、数据分析方向学习者。内容围绕陆吾生、方述诚教授课程的学习路径展开推荐《Optimization Model》作为入门读物并细致解读Boyd《凸优化》的章节结构与核心内容同时强调线性代数与微积分基础的重要性附上MIT《线性代数》等公开课程资源帮助读者建立从数学基础到凸优化应用的完整认知。资源包共3个文件以html页面、inscode配置与gitignore为主压缩后约5KB轻量易读适合作为学习索引与资料导航使用。目前已有71人学习下载可帮助读者快速定位优质教程、理解凸优化在机器学习与信号处理中的实际价值并借鉴作者的学习节奏与心态建议。1. 凸优化学习心得从「看得懂公式」到「跑得动代码」的那道坎很多人学凸优化卡住的地方不是数学而是从数学到代码的那一步。教材上写「目标函数是凸的局部最优即全局最优」你点头可真到要写一个求解器、调一个正则项、判断某个约束能不能松弛的时候手就停了。这个标题——凸优化学习心得配上一份项目代码——讲的正是这件事把凸优化的概念、判定、建模、求解用可运行的代码串起来让你不只是「看懂」而是能自己搭一个最小可用的凸优化求解流程并知道每一步的参数为什么这么设。它适合两类人一类是刚学完线性代数和微积分、想动手做机器学习或信号处理里优化问题的学生另一类是做工程落地、被「非凸模型调不动」折磨过、想回头补凸优化这块地基的从业者。核心价值不在于背下多少定理而在于建立一套判断链路这个问题是不是凸的、能不能转成凸的、转不了该用什么替代、转成了又该怎么选求解器和参数。下面我按自己踩过的顺序把这条链路拆开讲。2. 先分清凸与非凸判定链路和三个必查条件凸优化最容易被低估的一步是「判定」。很多人拿到问题直接上求解器跑不出结果才回头怀疑模型这时候已经浪费了大量时间。我的习惯是动手写代码之前先用纸笔或符号计算把凸性判定走一遍判定不过后面全是白费。2.1 凸集、凸函数与凸优化问题的定义边界一个优化问题要称为凸优化问题需要同时满足三件事目标函数是凸函数、不等式约束函数是凸函数、等式约束函数是仿射函数。注意最后一条——等式约束必须是仿射的也就是形如 $a^T x b$ 的线性形式。如果等式约束里出现 $x^2$ 或者 $xy$ 这种项哪怕目标函数再凸整个问题也不再是凸优化问题。凸集的定义是集合内任意两点的连线仍在集合内。凸函数则是定义域为凸集、且函数图像上任意两点连线不低于函数曲线。这两个定义看起来抽象但落到代码里判定往往靠的是几个可操作的充分条件而不是硬套定义。常见的可操作判定手段有三类一是看函数是否属于已知凸函数族范数、二次型、指数、负熵、log-sum-exp 等二是用保凸运算组合非负加权和、仿射复合、逐点最大值、部分最小化三是查二阶条件海森矩阵半正定。前两类在建模阶段最实用第三类适合小规模问题做验证。2.2 用代码验证凸性的最小流程光靠眼睛看容易翻车尤其是复合函数。我一般会写一段数值验证在定义域内随机采样检查函数值是否满足凸性不等式 $f(\theta x (1-\theta) y) \le \theta f(x) (1-\theta) f(y)$。这不是严格证明但能快速筛掉明显非凸的候选。import numpy as np def check_convexity(f, x, y, n_samples1000, tol1e-6): 数值检验 f 在 x, y 连线上的凸性。 f: 目标函数输入一维或二维 ndarray x, y: 定义域内两个点 tol: 允许的数值误差 返回: True 表示未发现违反凸性的采样点 for t in np.linspace(0, 1, n_samples): z t * x (1 - t) * y lhs f(z) rhs t * f(x) (1 - t) * f(y) if lhs rhs tol: print(f凸性违反: t{t:.4f}, f(z){lhs:.6f}, 插值{rhs:.6f}) return False return True # 示例二次函数 f(x) x^T A xA 半正定 A np.array([[2.0, 0.5], [0.5, 1.0]]) f lambda v: v A v x0 np.array([1.0, 0.0]) y0 np.array([0.0, 1.0]) print(check_convexity(f, x0, y0)) # 应输出 True这段代码的逻辑很直接沿两点连线均匀采样逐点比较函数值与线性插值。参数n_samples控制采样密度太小会漏掉局部违反太大则浪费时间一般 1000 足够。tol是数值容差浮点运算下严格相等几乎不可能留一点余量避免误报。需要提醒的是这个检验只能证伪不能证明——它没报错不代表函数一定凸只代表在这条线上没发现问题。要更可靠得换多组随机点重复跑。2.3 三个必查条件与常见误判实际建模时我总结出三个必查条件按顺序过一遍能挡掉大部分错误。第一目标函数里有没有出现变量的乘积项或变量的非线性函数嵌套。比如 $x_1 x_2$ 这种双线性项单独看非凸但如果问题结构允许可以通过变量替换或松弛转成凸的。第二约束里的等式是不是仿射的。很多人在建模时顺手写了个 $x^2 y^2 1$ 的等式约束这直接把问题踢出凸优化范畴正确做法是改成不等式约束 $x^2 y^2 \le 1$再配合目标函数方向处理。第三定义域是不是凸集。如果目标函数里含 $\log$ 或 $1/x$定义域天然被限制在正半轴这个隐式约束必须显式写进模型否则求解器会在非法区域试探导致报错。常见误判里最典型的是「把非凸当凸」。比如稀疏正则里的 $L_0$ 范数很多人以为它和 $L_1$ 差不多实际上 $L_0$ 是非凸的$L_1$ 才是它的凸松弛。另一个误判是「把凸当非凸」比如某些看起来复杂的范数组合其实用保凸运算一拆就是凸的白白放弃了全局最优的保证。判定这关过不去后面选求解器、调参数都是空中楼阁。3. 从数学到代码把凸问题写成求解器能吃的形式判定通过之后下一步是建模。凸优化建模的核心思路是把问题整理成求解器认识的标准形式通常是 $\min f_0(x)$约束 $f_i(x) \le 0$$Ax b$。这一步的难点不在数学而在「翻译」——把业务语言翻译成标准形式同时保持凸性不被破坏。3.1 标准形式转换与变量替换技巧标准形式要求目标函数和不等式约束都是凸的等式约束是仿射的。很多原始问题不长这样需要做变量替换。常见的替换有几类一是把最大化问题取负号转成最小化二是把 $x 0$ 的隐式约束显式写成 $-x \le 0$三是用对数变换或平方变换把某些非凸表达式转凸。举个典型例子原始问题是 $\max \prod_{i} x_i$约束 $x_i 0$。直接看是非凸的但取对数后变成 $\max \sum_i \log x_i$$\log$ 是凹函数最大化凹函数等价于最小化凸函数 $-\sum_i \log x_i$这就转成了凸问题。这个技巧在资源分配、概率建模里非常常见。变量替换的另一个作用是改善数值条件。比如变量取值范围跨越好几个数量级时直接求解容易遇到病态矩阵这时对变量做缩放让各维度量级接近求解器收敛会稳很多。我一般会在建模阶段就检查各变量的典型量级差三个数量级以上就考虑缩放。3.2 用 CVXPY 搭一个可复现的凸优化模型CVXPY 是凸优化建模里比较顺手的工具它把「判定凸性」和「调用求解器」两件事都包了。下面用一个带 $L_2$ 正则的最小二乘问题做示例这是最经典的凸问题之一。import cvxpy as cp import numpy as np np.random.seed(0) m, n 50, 20 A np.random.randn(m, n) b np.random.randn(m) lambda_reg 0.1 # 正则化强度控制拟合与稀疏的权衡 x cp.Variable(n) # 目标最小二乘 L2 正则整体是凸的 objective cp.Minimize(cp.sum_squares(A x - b) lambda_reg * cp.sum_squares(x)) # 约束变量非负显式写出保证定义域合法 constraints [x 0] problem cp.Problem(objective, constraints) # 求解指定求解器不指定则自动选择 problem.solve(solvercp.OSQP, verboseTrue) print(最优值:, problem.value) print(最优解前五个分量:, x.value[:5]) print(求解状态:, problem.status)这段代码里cp.Variable(n)声明优化变量cp.Minimize包住目标函数constraints列表放约束。关键参数是lambda_reg它控制正则项权重调大解更平滑但拟合变差调小拟合更好但可能过拟合实际中常用交叉验证选。solvercp.OSQP指定用 OSQP 求解它适合二次规划类问题如果不指定CVXPY 会按问题类型自动挑但自动挑有时会选到较慢的通用求解器明确指定能省时间。verboseTrue会打印迭代过程排查不收敛时很有用。求解完一定要看problem.status它可能是optimal、infeasible、unbounded或solver_error。很多人只看problem.value结果拿到一个不可行解还在那调参这是典型的血泪经验。3.3 求解器选择与参数初值设置求解器选择没有万能答案但有几条经验规则。问题规模小、精度要求高用内点法类求解器收敛稳但每步开销大问题规模大、结构稀疏用一阶方法类求解器每步便宜但迭代多。二次规划优先考虑专用求解器锥规划看具体锥类型。参数初值方面凸问题理论上对初值不敏感因为局部最优即全局最优但数值实现里初值仍会影响收敛速度甚至是否收敛。我的习惯是如果问题有物理意义用物理上的合理估计做初值如果没有用零向量或小随机向量。另外求解器的容差参数不要一上来就设得极严先用默认值跑通再逐步收紧否则容易在数值噪声上卡住。4. 避坑与排查凸优化落地时最容易翻车的五个地方这一章是我自己踩过的坑按「现象 → 原因 → 解决」写每条都对应真实会遇到的场景。4.1 求解器报 infeasible 但模型看起来没问题现象模型写得好好的求解器返回infeasible检查约束也没写错。原因多半是约束之间隐含矛盾或者定义域没写全。比如变量实际要求为正但没显式写 $x \ge 0$求解器在负半轴试探时触发目标函数里的 $\log$ 或开方数值上表现为不可行。解决把所有隐式定义域约束显式写出来尤其是分母、对数、开方涉及的变量。再用一个小规模可行点代入验证约束是否真的满足。4.2 迭代不收敛残差来回震荡现象求解器跑了很多轮残差曲线上下震荡不下降。原因问题尺度差异太大或者正则化参数设得让问题接近病态。也可能是求解器类型和问题结构不匹配。解决先做变量缩放让各维度量级接近再检查正则项系数必要时用网格搜索找合理范围最后换一个求解器对比如果换了就收敛说明是求解器匹配问题。4.3 解出来了但业务上不可用现象status是optimal但解的值在业务上没意义比如出现极端值或全零。原因目标函数和约束没完整表达业务需求或者正则化过强把解压平了。解决回头检查建模阶段是否漏了约束比如上下界、整数性注意整数约束会破坏凸性需要特殊处理再调正则化系数从 0 开始逐步增大观察解的变化。4.4 数值精度导致判定凸性时误判现象数值检验凸性时偶尔报违反但理论上函数是凸的。原因浮点误差在函数值接近时被放大尤其是函数值本身很小的时候。解决放宽容差tol或者对函数做尺度归一化后再检验。如果只是个别点报违反且幅度在容差附近可以忽略。4.5 大规模问题内存爆掉现象问题规模上去后构造矩阵时内存不足。原因稠密矩阵存储和运算没有利用稀疏结构。解决用稀疏矩阵格式存储检查约束和目标里哪些项是稀疏的必要时用矩阵-free 方法只提供矩阵向量乘积而不显式构造矩阵。5. 进阶技巧用对偶和热启动把求解速度提上去凸优化的进阶用法里对偶和热启动是两个性价比很高的方向。对偶的价值在于有些原问题约束多、变量少转成对偶后变量多、约束少求解反而更快而且对偶变量本身往往有物理解释比如影子价格。热启动则是在需要反复求解相似问题时用上一次的解做初值能显著减少迭代次数。对偶的构造有固定套路拉格朗日函数、对偶函数、对偶问题三步走。以带等式约束的二次规划为例原问题 $\min \frac{1}{2}x^T Q x c^T x$约束 $Ax b$对偶问题可以写成关于对偶变量的无约束二次问题规模通常更小。代码上CVXPY 不直接暴露对偶变量但求解后可以通过constraint.dual_value拿到这对做灵敏度分析很有用。热启动在 CVXPY 里不是所有求解器都支持但 OSQP 这类一阶方法支持传入初值。做法是把上一次的x.value作为warm_start参数传进去。实测在参数缓慢变化的序列问题上迭代次数能降一半以上。不过要注意热启动对初值质量敏感如果两次问题差异太大反而可能拖慢收敛所以我的习惯是只在问题结构相同、参数小幅变化时用。验证求解结果是否可信除了看status我还会做两件事一是用对偶间隙判断原问题值和对偶问题值差距在容差内才可信二是把解代回原始约束逐条检查违反程度。这两步做完基本能排除大部分数值陷阱。最后说个我自己的习惯每次调完参数把配置和结果记一行日志包括问题规模、求解器、关键参数、迭代次数、最终残差。凸优化调参不像深度学习那样玄学但参数和结果之间的对应关系如果不记过两周就忘了回头复现等于重来。这个习惯帮我省了很多后悔药。希望帮到你。本文还有配套的精品资源点击获取