CVX变量声明与凸优化建模:MATLAB中cvx.variable的使用与DCP规则详解

发布时间:2026/9/16 23:03:14
CVX变量声明与凸优化建模:MATLAB中cvx.variable的使用与DCP规则详解 简介面向MATLAB用户与凸优化初学者的一份CVX演示代码围绕cvx_variable变量定义与凸问题建模展开重点演示了决策变量的声明、维度设置、上下界约束添加以及cvx_end触发求解的完整流程。压缩包体积仅1KB包含1个m脚本麻雀虽小五脏俱全脚本注释清晰展示了从变量创建到目标函数构造、约束条件写入再到求解器调用的闭环代码并暗含对变量凸性要求、上下界写法、类型转换等易错点的处理示范便于在真实环境中边运行边学习。除了基础建模演示该DEMO还提供了一套简洁可复用的代码骨架读者稍加修改即可迁移到自身的最小二乘、范数优化或线性规划等凸优化问题中省去从零搭建的麻烦。目前已有644人学习下载适合希望通过最小化示例快速上手CVX建模的MATLAB开发者与科研人员。1. CVX 与 cvx.variable为什么 MATLAB 里做凸优化绕不开它先说一个反直觉的点CVX 并不是一个求解器它本身不负责计算最优解。CVX 在 MATLAB 里做的是两件事把人类语言写的凸优化问题翻译成求解器能读的标准形式同时在翻译前用 DCPDisciplined Convex Programming规则检查你的问题到底是不是凸的。真正算数的是底层的 SDPT3、SeDuMi、Gurobi、MOSEK 这些求解器。这个 DEMO 的价值不在于问题本身有多难而在于它把 cvx.variable 的声明、目标函数拼装、约束追加、cvx_end 触发求解这一整条链路走了一遍。适合刚开始用 CVX 的人对照学也适合已经写了不少手算矩阵代码的工程师反过来用这个建模方式替代容易出错的 A 矩阵和 b 向量拼装。2. cvx.variable 的类型体系与声明方式2.1 官方语法是 variable不是 cvx_varDEMO 的文件名和项目标题里出现了cvx_variable_CVX_cvxmatlab_cvx.Variable_cvx优化_DEMO网上不少旧笔记还会写成cvx_var x(3)或cvx_constraint看起来像 CVX 提供的函数。实际 CVX 官方语法里声明变量统一用variable关键字约束直接写在subject to之后没有cvx_前缀的约束函数。这些前缀多半是早期示例作者自己包装的 m 文件不是 CVX 内置 API。在这个 DEMO 里cvx_variable.m更有可能是一个演示脚本而不是某个被调用的函数。% demo_variable.m n 5; cvx_begin variable x(n) % 默认实数值列向量维度 n minimize( sum_square(x) ) cvx_end这里variable x(n)就是生成一个 n 维实数列向量x它在cvx_begin和cvx_end之间是一个 CVX 特殊对象。你可以对它做四则运算、转置、取范数但要把它塞进if判断或者传给普通 MATLAB 函数必须先经过cvx_end或者用cvx_optval等结果量。sum_square(x)是 CVX 内置的凸函数等价于x*x但用前者时 DCP 检查更容易通过因为 CVX 能直接识别幂锥结构。2.2 维度、复数和结构性变量cvx.variable 不只是标量和列向量它支持矩阵、复数、对称、半正定、非负、整数、二值等属性。声明时把附加关键字放在变量名后面顺序不影响解析。cvx_begin variable x(3) nonnegative % 每个分量 0 variable y(2,2) semidefinite % 2x2 半正定矩阵 variable z(4) complex % 复数向量 variable p(4) integer % 整数变量 variable q(3) binary % 0/1 变量 cvx_end参数说明nonnegative相当于隐式添加x 0但它比手动写约束更高效因为 CVX 会把它压缩进锥定义而不是生成一条独立的线性约束。semidefinite只方阵并且会强制矩阵对称所以声明variable y(2,2) semidefinite后你不需要再写y y。integer和binary一旦声明CVX 会切换到混合整数求解模式此时求解器选择受限不能用 SDPT3必须切换成 Gurobi 或 MOSEK 这类支持 MIP 的求解器。2.3 中间表达式与 variable 的关系很多人在循环里反复声明variable导致报错因为同一个cvx_begin区域内变量名不能重复声明。如果只是想构造临时计算值应该用expression而不是variable。cvx_begin variable x(10) expression obj obj 0; for i 1:10 obj obj (x(i) - i)^2; end minimize(obj) cvx_end这里的expression obj不产生新的优化变量它只是组合复杂表达式的容器。variable x(10)是真正的决策变量。区别在于expression本身不能被约束也不能作为minimize/maximize 的唯一对象它需要在最后赋值给目标函数或约束。这种写法适合目标函数有复杂循环结构时避免写出超长的一行表达式。3. 从 cvx_begin 到 cvx_endCVX 优化 DEMO 的完整拆解3.1 问题定义带约束的最小二乘重构这里把 DEMO 扩展成一个可实际运行的完整例子。假设我们有一个被噪声污染的稀疏信号b要用一组基矩阵A去重构原信号x同时加上非负约束。目标是最小化||Ax - b||_2^2加上一个小额 L1 正则项lambda * ||x||_1这样能体现 CVX 处理混合范数的能力。% cvx_reduce_demo.m rng(42); m 20; n 50; A randn(m, n); x_true zeros(n, 1); x_true(1:5) randn(5, 1); % 稀疏真实信号 b A * x_true 0.05 * randn(m, 1); % 加噪声 lambda 0.1; cvx_begin variable x_hat(n) minimize( sum_square(A * x_hat - b) lambda * norm(x_hat, 1) ) subject to x_hat 0 cvx_end fprintf(重建误差: %.4f\n, norm(x_hat - x_true));sum_square(A * x_hat - b)是凸性质最好的二次型norm(x_hat, 1)是 L1 范数两者相加仍是凸函数。subject to下面的x_hat 0直接作用在整个向量上等价于逐分量约束。CVX 会自动把这个问题转化为二阶锥规划再交给求解器。3.2 目标函数与约束的书写边界CVX 要求目标函数和约束必须满足 DCP 规则。规则的核心是凸函数只能出现在minimize目标的不等式左侧凹函数只能出现在右侧。比如下面的写法会报错% 错误的例子DCP 规则不允许 cvx_begin variable x(5) maximize( norm(x, 2) ) % norm 是凸函数不能用于 maximize cvx_end这是因为maximize要求目标是凹的而norm是凸函数。反过来minimize(norm(x,2))合法。另一个常见误用是把等式约束写成norm(x) 1这是非凸约束CVX 会明确提示Disciplined convex programming error。如果确实需要单位范数通常的做法是改成norm(x) 1或者用辅助变量做双不等式逼近。3.3 结果读取cvx_optval 与 cvx_statuscvx_end 执行后解从变量对象里取值x_hat已经变成普通 MATLAB 数值矩阵。同时 CVX 会设置几个全局状态量最常用的是cvx_status和cvx_optval。switch cvx_status case Solved fprintf(求解成功最优值: %.4f\n, cvx_optval); case Infeasible error(问题不可行); case Unbounded error(问题无界); otherwise warning(求解器提前终止状态: %s, cvx_status); endcvx_optval是最优目标值但要注意它只在cvx_status为Solved时有效。对于不可行或无界问题cvx_optval可能是Inf、-Inf或空值。实际工程中我习惯先查cvx_status再取x_hat避免把求解器失败时的部分解当结果用。另外cvx_status还有Inaccurate/Solved等字样说明结果是通过终止容差得到的不是严格最优这时候需要回看求解器报告。4. 求解器选型、参数调优与常见坑位4.1 从 cvx_solver 到求解器能力对比CVX 默认使用内置的 SDPT3 或 SeDuMi但对于大规模问题、整数变量或特定结构切换求解器能带来数量级差异。切换方式很简单cvx_solver(Gurobi) cvx_precision(high)cvx_solver接受字符串参数可选项包括Gurobi,MOSEK,SDPT3,SeDuMi,SCS等。cvx_precision控制求解精度取值有low,default,high,best。精度越高迭代越多。注意cvx_precision要在cvx_begin之前设置否则不会对当前求解生效。下表是我常用的选型经验问题类型推荐求解器理由小规模 SDP 1000 变量SDPT3默认安装内点法可靠大规模稀疏 LP/QPGurobi并行能力强内存控制好二阶锥规划 SOCPMOSEK对锥问题预处理更精细混合整数凸规划Gurobi / MOSEK支持分支定界超大规模但精度要求一般SCS一阶算法内存占用低4.2 输出控制与求解日志CVX 默认会打印一堆求解器日志调试时候看但批量跑实验时最好关掉。用cvx_quiet(true)关掉详细输出cvx_quiet(false)打开。这行代码也要放在cvx_begin之前。cvx_quiet(true); cvx_solver(MOSEK); cvx_begin variable X(10, 10) semidefinite minimize( trace(X) - log_det(X) ) subject to X * ones(10,1) 1 cvx_end这里log_det(X)是凹函数所以它在minimize中被减去整个目标是凸的。trace(X)是线性函数。这种组合很常见但容易犯的错是把log_det(X)单独放在目标里minimize(-log_det(X))然后抱怨报错其实负号会自动翻转凸凹性CVX 能识别。关键是整个表达式在minimize里必须整体凸。4.3 坑位清单与排查方向报错信息实际原因处理方式Disciplined convex programming error约束或目标不满足 DCP检查是否有凸函数在右侧或凹函数在左侧Illegal operation: {convex} ./ {convex}两个凸函数相除改用辅助变量或变换成锥形式Dimensions must agree变量维度与表达式维度不一致检查variable x(n)的 n 和运算符另一侧矩阵大小Solver failed求解器数值问题尝试cvx_precision(high)或换求解器Mixed integer problems require a solver that supports integer variables声明了integer/binary但求解器不支持先执行cvx_solver(Gurobi)排错时先用cvx_quiet(false)打开完整日志看求解器给出的退出码。比如 MOSEK 返回MSK_RES_TRM_MAX_ITER说明达到最大迭代步数可考虑放宽精度或用expression重构问题。另一个经常被忽略的是数值尺度如果 A 矩阵元素量级在 1e6 到 1e-8 之间混杂直接求解容易病态。先把 A 列归一化到[0,1]求解后再把解变换回去比调求解器参数更有效。5. 进阶复优化、正则化组合与调试技巧5.1 用 cvx.variable 处理复变量CVX 的variable z(n) complex可以构建复平面上的凸优化问题比如最小化复残差的二范数。一个典型应用是波束成形或系统辨识。cvx_begin variable z(4) complex minimize( norm(A_c * z - b_c, 2) ) subject to abs(z) 1 cvx_endabs(z)在复数变量上逐元素取模它是凸的。注意z是复数约束abs(z) 1是一个二阶锥约束的压缩写法。如果你需要分离实部和虚部可以直接访问real(z)和imag(z)这两个操作在 CVX 表达式里是线性且 DCP 友好的。5.2 正则化项中的变量切片与复用处理图像或时间序列时常需要对变量的不同块施加不同罚项。用variable X(m,n)声明矩阵后可以用 MATLAB 索引生成子表达式但不要直接在索引处覆盖X(:,1) ...那样会破坏 CVX 表达式树。正确做法是把子表达式赋给expression中间量。cvx_begin variable X(10, 5) minimize( sum_square(X(:,1)) 0.5 * norm_nuc(X(1:6,:)) ) subject to X(end-1:end, :) 0 cvx_endnorm_nuc表示核范数对矩阵变量是凸函数常用来做低秩逼近。上面的例子把目标函数拆成两部分第一列的二范数平方和以及前 6 行的核范数。X(1:6,:)这种切片在 CVX 中是合法的但前提是切片结果仍保持 convex 表达式可辨识。如果切片方式过于复杂比如用X(:)重新排布最好先用reshape转换不过reshape在 CVX 里支持有限我一般直接用变量索引化简问题结构。5.3 一个调试技巧用小规模问题验证 DCP遇到 DCP 报错时最有效的调试不是读文档而是把问题规模缩小到 2~3 个变量手工计算目标函数和约束的曲率再对照 CVX 规则逐个排除。比如怀疑log(x(1)) log(x(2))是不是凸直接想log是凹函数凹函数与凹函数相加仍然是凹函数因此它不能用于minimize。要最小化包含 log 的目标通常需要改写成-sum(log(x))这样才能把凹的log变成凸的-log。CVX 报错信息已经足够准确问题在于大多数人没耐心读。下一行代码是我想给你的最后一个工具cvx_begin variable x(2) maximize( sum(log(x)) ) subject to sum(x) 1 x 1e-6 cvx_end这是一个最大熵问题sum(log(x))是凹函数所以放在maximize里合法。约束x 1e-6避免迭代时变量落到零附近导致log数值异常。cvx_end后检查x和cvx_optval你会看到经典结论均匀分布让熵最大。这个例子虽然简单但能同时验证变量声明、凹目标、不等式约束和数值下界四个环节作为 CVX 的冒烟测试很合适。本文还有配套的精品资源点击获取