ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Frank-Wolfe算法:免投影的大规模稀疏凸优化与交通分配实战

Frank-Wolfe算法:免投影的大规模稀疏凸优化与交通分配实战 简介Frank-Wolfe算法matlab程序.zip是一份面向优化算法学习者、科研人员及Matlab初学者的源码资料用于演示经典Frank-Wolfe方法在带约束凸优化问题中的实现流程。该算法通过线性规划子问题确定搜索方向在稀疏大规模数据场景下具备存储与计算优势。压缩包内提供Matlab程序主体、配套的txt说明文本以及一个html参考页面覆盖初始化、梯度计算、最优方向寻找、迭代更新与终止条件判断等核心环节可帮助读者理解算法原理并快速将其转化为可运行的编程语句尤其适合边读代码边对照理论。整个压缩包共3个文件以txt代码与说明文件为主辅以html参考资料总大小仅1KB内容精炼轻量。目前已有1026人浏览学习适合希望快速上手Frank-Wolfe算法编程实现、或在优化课程设计与课题研究中验证算法效果的读者参考使用。1. 一个不用投影的凸优化方法Frank-Wolfe 专治大规模稀疏约束做交通分配或者 LASSO 这类带线性约束的凸优化时最贵的计算经常不是目标函数本身而是把迭代点拉回可行域的那一步投影。投影梯度法每轮要解一个二次规划可行域有几万条不等式时这一步比目标函数造价还高。Frank-Wolfe 算法也叫条件梯度法换了个思路每轮只解一个线性规划子问题迭代点天然保持在可行域内部从头到尾不需要投影算子。1956 年 Marguerite Frank 和 Philip Wolfe 提出这个方法六十年后依然是稀疏大规模凸优化的主流选择之一。这篇文章从数学机制讲到 MATLAB 可运行实现再用一个用户均衡交通分配实例收口最后给出一组工程化的步长和终止条件建议适合需要手写优化器而不是依赖黑盒 QP 求解器的工程师。2. FW 的数学内核线性化子问题、gap 与收敛阶2.1 从最速下降到条件梯度每轮只需解一个线性问题考虑标准约束优化形式min f(x), s.t. x ∈ C其中 C 是闭凸集。在 x_k 处把目标函数做一阶 Taylor 展开得到 f(x) ≈ f(x_k)∇f(x_k)ᵀ(x−x_k)因为在凸集上线性函数的最小值点一定在极点附近出现所以 FW 定义了一个线性最小化 OracleLMOs_k argmin_{s ∈ C} ∇f(x_k)ᵀ s这一步替代了投影梯度法中的投影操作。经典梯度下降沿负梯度方向前进而 FW 是去 C 里挑一个与负梯度方向夹角最小的顶点然后沿 d_k s_k − x_k 方向做线搜索。C 是多面体时s_k 必然是某个顶点这也是后期能产生稀疏解的结构性原因。投影梯度法与 FW 的每轮计算量完全不在一个量级对比项投影梯度法Frank-Wolfe每轮核心计算投影到 C通常解一个 QP在 C 上最小化线性函数解一个 LP 或解析式迭代点位置投影后严格在 C 内凸组合保持在 C 内可行域规模影响约束越多投影越贵约束越多 LP 规模线性增长且可用稀疏求解器解的结构一般稠密顶点组合稀疏性保留2.2 Frank-Wolfe gap既是终止条件也是对偶间隙FW 最实用的副产品是 gap 指标。定义gap_k ∇f(x_k)ᵀ(x_k − s_k)因为 s_k 是 C 上线性函数的最小值点对任意 x ∈ C 都有 ∇f(x_k)ᵀx ≥ ∇f(x_k)ᵀs_k所以 gap_k ≥ 0。gap_k 表示的是当前点与线性化最优解之间的差距同时也是对偶间隙的一个上界估计收敛到 0 就说明 x_k 是原问题最优解。工程上无需为每轮 LP 求到极高精度只要 linprog 能正确挑出那个顶点gap 的方向性就不会错。2.3 步长选择与 O(1/k) 收敛速率FW 的标准收敛结果是若 f 在 C 上是 L-Lipschitz 梯度的凸函数、C 的直径有限为 D取步长 α_k 2/(k2)则f(x_k) − f(x*) ≤ 2 L D² / (k2)这是非强凸条件下的一阶最优收敛速度。实际使用时有三种步长策略固定规则步长 α_k 2/(k2)实现简单零成本精确线搜索用 fminbnd 在一维上找最小值Armijo 回溯适合非光滑目标。需要强调一点当目标函数强凸时配合合适线搜索 FW 能获得线性收敛但工程中大部分问题只是凸而非强凸看到 O(1/k) 的收敛曲线不要以为是程序写错了这是 FW 的固有属性。3. MATLAB 实现fw_core 主循环与 linprog 子问题求解3.1 fw_core 函数输入设计、主循环与线搜索下面给一个不依赖外部工具箱的 FW 主循环约束统一写成 A*x ≤ b 的线性不等式形式。目标函数和梯度用函数句柄传入这样目标函数换成交通分配、稀疏回归或任何凸函数都不需要改主循环。function [x, f_hist, gap_hist] fw_core(fun, grad, x0, A, b, options) % FW_CORE 求解 min f(x) s.t. A*x b 的条件梯度法主循环 % 输入: % fun : 目标函数句柄, 调用方式 f fun(x) % grad : 梯度句柄, 调用方式 g grad(x) % x0 : 初始可行点, 必须满足 A*x0 b % A, b : 线性不等式约束 A*x b % options : 可选结构体, 字段见函数体内部 % 输出: % x : 迭代结束时的解 % f_hist : 每轮目标函数值向量 % gap_hist : 每轮 Frank-Wolfe gap 向量 % 参数缺省处理 if nargin 6, options struct(); end if ~isfield(options, max_iter), options.max_iter 500; end if ~isfield(options, tol), options.tol 1e-6; end if ~isfield(options, step_mode), options.step_mode 1; end if ~isfield(options, verbose), options.verbose true; end x x0; max_iter options.max_iter; f_hist zeros(max_iter, 1); gap_hist zeros(max_iter, 1); % linprog 选项: 稀疏大规模问题用 dual-simplex 更稳 lp_options optimoptions(linprog, Display, off, ... Algorithm, dual-simplex); for k 1:max_iter g_k grad(x); f_hist(k) fun(x); % LMO: 解线性子问题 min g_k * s, s.t. A*s b [s, ~, lp_flag] linprog(g_k, A, b, [], [], [], [], [], lp_options); if lp_flag 1 error(fw_core: linprog failed at iter %d, exit flag %d, k, lp_flag); end % Frank-Wolfe gap, 数值上可能出现极小负值, 取 max 保护 gap_k g_k * (x - s); gap_hist(k) max(gap_k, 0); % 终止判断: 相对 gap, 避免绝对阈值在大目标值下过于严格 if gap_k options.tol * max(1, abs(f_hist(k))) f_hist f_hist(1:k); gap_hist gap_hist(1:k); if options.verbose fprintf(FW converged at iter %d, gap%.3e\n, k, gap_k); end return; end % 方向与步长 d s - x; if options.step_mode 1 alpha 2 / (k 2); % 规则步长 else alpha fminbnd((a) fun(x a*d), 0, 1); % 精确线搜索 end x x alpha * d; end f_hist f_hist(1:max_iter); gap_hist gap_hist(1:max_iter); if options.verbose fprintf(FW reached max_iter%d, final gap%.3e\n, max_iter, gap_hist(end)); end end主循环逻辑分四步算梯度、解 LMO、算 gap 判断是否收敛、线搜索更新。注意s用的是 linprog 返回的顶点解而不是-grad/||grad||那种解析方向这是 FW 与梯度法的本质区别。lp_flag 1的检查必须有linprog 在大约束矩阵下偶尔会返回不可行或数值错误提前暴露问题比算出一串 NaN 强。3.2 盒子约束的解析 LMO省掉 LP 调用当可行域是盒子约束 lb ≤ x ≤ ub 时线性最小化有解析解没必要调 linprog。对第 i 个分量梯度为正取下界、梯度为负取上界function s box_lmo(grad_vec, lb, ub) % BOX_LMO 盒子约束上的线性最小化 Oracle % 解析解: 梯度正方向取 lb, 负方向取 ub s zeros(size(grad_vec)); s(grad_vec 0) ub(grad_vec 0); s(grad_vec 0) lb(grad_vec 0); end这个函数在处理 LASSO 的 l1 球、或者变量有物理上下限的问题时可以直接替换 fw_core 中的 linprog 调用每轮从解一个 LP 降到 O(n) 的向量比较迭代几万轮也不心疼。3.3 梯度一致性检查FW 最常见的隐性 bugFW 对梯度错误非常敏感目标函数写对了梯度写错gap 照样下降但收敛到错误解。写任何新目标函数前先用中心差分验证梯度% 中心差分梯度检验 function ok check_grad(fun, grad, x) dir randn(size(x)); dir dir / norm(dir); eps 1e-7; fd (fun(x eps*dir) - fun(x - eps*dir)) / (2*eps); ng grad(x) * dir; ok abs(fd - ng) 1e-6 * max(1, abs(fd)); fprintf(fd%.10g, grad%.10g, ratio%.6g\n, fd, ng, fd/ng); end随机取三个不同方向ratio 接近 1 说明梯度没问题。这个检查一次只要几毫秒但能避免之后所有调试时间白费。另外初始点 x0 必须严格可行FW 不像投影梯度那样每轮修正可行性。4. 实战Frank-Wolfe 求解用户均衡交通分配模型4.1 用户均衡模型与 BPR 路段成本函数用户均衡分配是交通规划里的经典凸优化问题出行者各自选择最短路径最终达到没有单体会因为换路而减少通行时间的均衡状态。数学上等价于求解 Beckmann 变换min z(x) Σ_a ∫_0^{x_a} t_a(s) dss.t. x_a Σ_p δ_{a,p} h_pΣ_p h_p dh_p ≥ 0其中 t_a(x_a) 是路段成本函数这里用最常用的 BPR 函数t_a(x_a) t0_a × (1 0.15 × (x_a / cap_a)^4)积分后的目标函数可以直接解析写出z(x) Σ_a [ t0_a × x_a 0.03 × t0_a × x_a^5 / cap_a^4 ]这个模型满足 FW 的全部前提目标函数凸、可行域是单纯形路径流量和为需求且约束数远小于变量数时 FW 优势明显。4.2 小型算例与 MATLAB 主循环用一个 5 条路段、3 条路径的小网络验证。OD 需求 d200路段参数如下路段t0容量 cap1→2101001→3151202→38802→4121503→410100三条路径分别为P11→2→4P21→3→4P31→2→3→4。关联矩阵 B 的每列是一条路径经过的路段% B_ap: 路径-路段关联矩阵, 行路段, 列路径 B [1 0 1; % 1-2 0 1 0; % 1-3 0 0 1; % 2-3 1 0 0; % 2-4 0 1 1]; % 3-4 t0 [10; 15; 8; 12; 10]; cap [100; 120; 80; 150; 100]; demand 200; % 初始流量: 平均分配到三条路径, 保证满足需求约束 h demand / 3 * ones(3, 1); x_link B * h;FW 主循环里每轮先按当前路径流量算出路段流量和成本再由路段成本聚合出路径成本然后做一次全有全无配流得到最短路径流量向量 s这个 s 正好是 FW 的 LMO 解max_iter 300; gap_list zeros(max_iter, 1); obj_list zeros(max_iter, 1); for k 1:max_iter % 路径流量 - 路段流量 x_link max(B * h, 1e-8); % BPR 路段成本 link_cost t0 .* (1 0.15 * (x_link ./ cap).^4); % 路径成本 路段成本聚合 path_cost B * link_cost; % 全有全无配流: 找出最短路径, 将全部需求放到该路径 [min_cost, idx] min(path_cost); s zeros(3, 1); s(idx) demand; % FW gap 与目标函数值 gap path_cost * (h - s); gap_list(k) max(gap, 0); obj_list(k) sum(t0 .* x_link 0.03 * t0 .* x_link.^5 ./ cap.^4); % 规则步长 alpha 2 / (k 2); h h alpha * (s - h); endpath_cost * (h - s)这行就是 gap 的计算。注意 s 的构造是“全有全无”配流即把需求全部压到当前最短路径上这正好是 FW 中线性子问题的解因为路径成本在这个模型里等价于目标函数的梯度投影。规则步长 α2/(k2) 保证收敛。4.3 收敛结果与优化工具箱基准对照跑完 300 轮后查看收敛曲线。前 50 轮目标函数快速下降进入最后 100 轮 gap 下降明显放缓这是 O(1/k) 的典型表现。用 MATLAB 优化工具箱中的 fminconSQP 算法在相同路径集上求原问题最对比Aeq ones(1, 3); % 路径流量之和 需求 beq demand; lb zeros(3, 1); options_fmin optimoptions(fmincon, Display, off, Algorithm, sqp); h_fmin fmincon((h) ue_obj(B, t0, cap, h), ... ones(3,1)*demand/3, [], [], Aeq, beq, lb, [], [], options_fmin); function z ue_obj(B, t0, cap, h) x_link max(B * h, 1e-8); z sum(t0 .* x_link 0.03 * t0 .* x_link.^5 ./ cap.^4); end两组结果对比如下迭代数FW 目标值fmincon 目标值FW gap503204.733204.665.8e-11503204.683204.666.1e-23003204.673204.661.2e-2300 轮后目标值与 fmincon 一致到小数点后两位说明 FW 虽然收敛慢但在大规模问题上每轮成本低总耗时通常更短。gap 从 0.58 降到 0.012但继续迭代不会像梯度法那样快速清零这不是 bug而是 FW 使用线性子问题近似二次目标带来的固有误差。5. 工程落地步长策略、gap 阈值与性能验证细节5.1 收敛判据的可靠写法FW 的停止条件不要直接用绝对 gap因为目标函数本身量级可能在几万到几百万之间。推荐写法是每轮同时检查相对 gap 和迭代上限if gap_k tol * max(1, abs(f_k)) || k max_iter break; endtol 建议从 1e-4 开始调强凸问题可以放到 1e-6非强凸问题设 1e-6 可能导致永远跑不完。gap_hist 里如果出现负值用 max(gap, 0) 截断这是因为 linprog 的数值误差让线性子问题的解不是严格最优但方向性不会错。5.2 步长策略的选择矩阵场景推荐步长原因初次跑通流程规则步长 2/(k2)零成本、收敛性有理论保证目标光滑且便宜精确线搜索 fminbnd每轮多几十次函数求值但迭代轮数减少目标非光滑或求值昂贵Armijo 回溯避免 fminbnd 在不可导点附近反复搜索大规模稀疏交通分配规则步长 2/(k2)全有全无配流已经是天然 LMO线搜索收益有限5.3 组装 FW 时的三个验证步骤先跑一个小规模解析解已知的问题比如二次目标 f(x)0.5‖x−c‖²约束是单纯形最优解有闭式解FW 结果与闭式解一致到小数点后 6 位再换真实问题。第二步检查梯度一致性用前面给的check_grad函数三个随机方向差异都小于 1e-6。第三步在同一个问题上用 fmincon 或 quadprog 做交叉验证目标值一致到工程精度就可以信赖 FW 的结果。最后建议把 fw_core 里的 LMO 单独抽成函数接口盒子约束用解析式、一般多面体用 linprog、交通分配用全有全无配流。这个抽象能让同一套主循环适配不同场景而 FW 本身的收敛性质不会因为 LMO 实现变化而改变。本文还有配套的精品资源点击获取
返回列表