ARTICLE DETAIL

资讯详情

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

Matlab实现OMP正交匹配追踪:从原理到代码详解

Matlab实现OMP正交匹配追踪:从原理到代码详解 简介面向需要掌握稀疏表示与压缩感知的MATLAB学习者与研究人员代码包完整实现了正交匹配追踪OMP算法。资源共4个文件其中3个.m脚本分别承担算法主函数、调用示例与辅助演示另有1张流程图或效果图辅助理解迭代选原子与残差更新过程压缩包仅5KB轻量紧凑方便直接阅读和调试。目前已有272人学习/下载特别适合信号去噪、图像恢复、特征选择等场景的初学者快速上手。通过运行示例代码读者可以清晰看到字典构造、内积匹配、正交投影和停止判断等关键步骤并据此扩展至MRI重建、音频处理等实际问题。整体逻辑清晰注释简明便于学习者根据自身需求进行改造与二次开发。1. 自己在matlab里实现omp为什么比下载现成工具包更稳压缩感知里Orthogonal Matching PursuitOMP经常被当作第一个上手的稀疏恢复算法。很多人的做法是从代码分享平台下载一个 omp_matlab 函数然后直接调用 [x,supp] omp(A,y,K)。这个流程在无噪声、信号严格 K 稀疏时很容易跑通可一旦把测量矩阵换成不相干性较差的过完备字典或者观测向量带了噪声不同版本的实现给出的结果就开始分叉。有的版本把 K 当作真实稀疏度有的把它当作最大迭代次数有的会对残差阈值自动处理有的不会。自己维护一个最小 omp_matlab 实现虽然只是二十行循环但每个参数的行为都可控而且把算法改写成 OLS、CoSaMP或者接入字典学习流程时也更顺手。这篇文章适合正在做压缩感知、稀疏信道估计、图像稀疏表示或者想在 matlab 里快速验证贪婪类算法的工程人员。2. 从y Ax到支撑集OMP的迭代逻辑与停止条件2.1 稀疏恢复模型与测量矩阵构造处理的线性观测模型是 y A x n其中 A 是 m×n 测量矩阵实际场景里 m 经常明显小于 nx 的非零位置只有 K 个这就是 K 稀疏的含义。问题是当 m n 时A x y 存在无穷多个解不引入稀疏约束就无法确定地求解。压缩感知理论解决的是“在什么样的 A 和多大的 K 下这个解可以被稳定恢复”而 OMP 是在满足类似条件时用贪心策略去逼近这个解。在 matlab 里构造合成数据第一步建议把测量矩阵列归一化。原因是 OMP 每轮比较的是“哪个原子方向与当前残差更接近”如果某列的模比其他列大内积自然偏大选择结果就会偏向大范数列。列归一化的代码很简洁m 128; n 256; K 10; A randn(m, n); A A ./ sqrt(sum(A.^2, 1)); % 每列向量的2-范数归一为1 x_true zeros(n, 1); x_true(randperm(n, K)) randn(K, 1); y A * x_true;randn 生成的是高斯随机测量矩阵在压缩感知验证里很常用因为它满足有限等距性质的概率高sum(A.^2,1)对每一列求平方和再开方得到 1×n 的列范数向量逐列广播后就把每列归一成单位列向量。randperm(n,K)随机指定 K 个非零位置恢复算法的任务就是把这些位置找回来。2.2 迭代循环原子选择、最小二乘更新与残差正交化第 t 次迭代开始前支撑集是 S_t-1残差是 r_t-1。算法先在字典所有列上做一次内积用 matlab 写就是两行corr A * r; [~, idx] max(abs(corr));A * r 得到 n 个内积值绝对值大小代表每一列对残差的解释能力。选最大值对应的索引 idx把 A 的第 idx 列加入支撑集。加入新列之后需要重新计算整个支撑集上的系数而不是只更新新加的那一个系数。因为新增一列之后先前的最小二乘解已经不是整个支撑集上的最优解。这一步在 matlab 里用反斜杠运算符实现x_LS A(:, supp) \ y; r y - A(:, supp) * x_LS;反斜杠在 m S 且列满秩时给出的是最小二乘解等价于对 y 向支撑集张成的子空间做正交投影。残差 r 满足 A_supp * r 0也就是说已选原子与当前残差的内积严格为零。这就是“正交匹配追踪”里“正交”二字的来源下一次迭代再做 max(abs(A*r)) 时支撑集里的索引不会再以明显优势被选中算法不会反复选同一列。对比基本匹配追踪MPMP 只做原子方向的减法而不做正交化同一原子可能被重复选中收敛路径明显曲折。2.3 停止条件在matlab代码里的落地方式OMP 有三种常见的停止方式实际工程里经常是组合使用停止条件matlab写法适合场景容易踩的坑固定迭代 K 次for 循环到 K 即停已知信号严格 K 稀疏噪声场景下 K 给大噪声被当信号选进来绝对残差阈值norm(r) eps无噪合成实验eps 给太小等于没设给太大又提前停相对残差阈值norm(r) tol*norm(y)绝大多数工程场景tol 需随信噪比调整不可一套参数通吃下文给出的最小实现采用“K 上限 相对残差”双条件。原因是实际工作中很难提前知道精确稀疏度所以 K 先给一个偏大的上界让相对阈值在支撑集进入噪声区间时提前截断。tol 不要给得太小否则阈值条件形同虚设循环一定会跑满 K 次。3. 手写一个最小可运行的 omp_matlab 核心循环3.1 完整函数代码以下是我在项目里惯用的最小实现不依赖任何工具箱复制到 .m 文件即可运行function [x_hat, supp] omp_matlab(A, y, K, tol) % OMP 正交匹配追踪最小实现 % 输入: % A : m*n 测量矩阵建议先做列归一化 % y : m*1 观测向量 % K : 最大迭代次数 / 稀疏度估计上界 % tol : 相对残差停机阈值默认 1e-6 % 输出: % x_hat : n*1 稀疏恢复信号 % supp : 被选中原子的索引列向量 if nargin 4 || isempty(tol) tol 1e-6; end [m, n] size(A); r y; % 残差初始化为观测向量 supp []; % 支撑集索引初始为空 x_hat zeros(n, 1); % 预置输出向量 for iter 1 : K corr A * r; % 当前残差在字典每个原子上的投影 [~, idx] max(abs(corr)); % 取绝对值最大对应的原子索引 if any(supp idx) % 已选列再次被选中说明残差已正交化 break; end supp [supp; idx]; % 扩展支撑集 x_LS A(:, supp) \ y; % 支撑集上的最小二乘系数反斜杠自动走QR路径 r y - A(:, supp) * x_LS; % 更新残差使残差与所有已选列正交 if norm(r) tol * norm(y) % 相对残差达到阈值则提前退出 break; end end x_hat(supp) x_LS; % 只在支撑集位置写回系数 end这段代码的关键点有三个。第一初始残差直接取 y第一轮选中的必然是观测向量在字典上投影最大的原子第二每轮支撑集扩张后都要完整重算最小二乘而不是只更新新增原子方向的系数第三用相对残差 norm(r)/norm(y) 作为停机判断避免了 y 本身幅值变化对阈值的影响。代码里加的重复索引检查是为了应对原子高度相干时的边界情况理论上如果残差严格正交同一列不会被再次选中但浮点误差可能带来极小的非零投影。3.2 输入参数与常见取值边界参数维数常见取值说明Am×n doublem ≈ 4K~8K列必须归一化否则内积选择会被大范数列带偏ym×1 double均值先归零含直流分量时先做去均值避免第一轮选错原子K正整数真实稀疏度的 1.5~2 倍上界给大一点配合 tol 截断比给小更稳妥toldouble 标量无噪声 1e-10有噪声 1e-2~1e-3噪声越大 tol 越大过小会把噪声当成信号实际调参时最常犯的错误是把 K 精确设成已知的稀疏度。在无噪声理想条件下这没问题但工程数据很少严格稀疏K 稍大一点反而给了残差阈值发挥作用的空间。与之相对tol 也不能设成 1e-10 想当然含噪场景下这个阈值几乎不可能被满足循环会一直跑到 K 次把噪声原子也收进支撑集。3.3 用模拟数据验证恢复正确性验证代码可以这样写rng(42); m 128; n 256; K 10; A randn(m, n); A A ./ sqrt(sum(A.^2, 1)); % 列归一化 x_true zeros(n, 1); pos randperm(n, K); x_true(pos) 3 * randn(K, 1); % 放大系数幅值便于观察恢复误差 y A * x_true; [x_hat, supp] omp_matlab(A, y, K 5, 1e-10); rel_err norm(x_hat - x_true) / norm(x_true); recall length(intersect(supp, pos)) / K; fprintf(相对误差: %e, 支撑集重合率: %.2f\n, rel_err, recall);调用时把 K 上限设置为 K5是为了展示“上界 阈值”双条件的作用多出来的 5 次迭代允许 OMP 继续判断是否有新原子可加但 1e-10 的相对阈值保证了残差已经足够小时不会继续无聊迭代。正常情况下输出支撑集重合率 1.00相对误差在 1e-12 量级差别取决于反斜杠求解的数值精度。如果重合率低于 1.00优先检查 A 是否列归一化以及 y 是否带了未预料的直流分量。4. 噪声环境下调优 omp_matlab阈值、Gram 预计算与原子相干性4.1 噪声下停止阈值按噪声方差估计带噪声的观测模型是 y A x n其中 n 的元素服从 N(0, σ²)。当支撑集选得正确时残差里主要剩下的就是测量噪声因此残差范数理论值在 σ·sqrt(m) 附近。如果 tol 设得比这个水平小很多OMP 就会把噪声方向的投影当成“值得选择”的原子继续扩张支撑集最终在真实支撑之外多选几个错误位置。反过来tol 设得太大又会在真实原子还未全部进入支撑集时提前收手。一个可靠的阈值估计是sigma_n 0.05; % 观测噪声标准差可从系统前端估计 y_noisy y sigma_n * randn(m, 1); tol_noise sqrt(m) * sigma_n / norm(y_noisy); [x_hat_noisy, supp_noisy] omp_matlab(A, y_noisy, 2*K, tol_noise);sqrt(m) * sigma_n 是残差能量的量级估计除以 norm(y_noisy) 是为了和函数内部的相对残差判断对齐。如果 σ_n 无法直接获得可以用一个工程上常用的稳健近似sigma_hat median(abs(A * y)) / 0.6745;0.6745 来自正态分布的 median 与标准差的比例关系A * y 中那些与真实支撑集无关的投影分量近似服从零均值高斯分布所以 median 能剥离掉大系数原子的影响。这个估计比手工试 tol 稳定得多。4.2 用预计算 Gram 矩阵缩短 matlab 循环耗时在 m、n 都较大的仿真里OMP 每次迭代都要算一次 A * r这是一次 m×n 的矩阵乘向量的运算总体耗时大约等于 n·m·K 次浮点运算。如果字典 A 固定不变可以预计算 Gram 矩阵 G A * A并用迭代关系跳过 A * rfunction [x_hat, supp] omp_matlab_gram(A, y, K, tol) [m, n] size(A); Aty A * y; G A * A; % 预计算 Gram 矩阵n 较大时注意内存占用 r y; supp []; for iter 1:K if iter 1 corr Aty; % 第一轮直接用 A*y else % r y - A(:,supp)*x_LS所以 A*r Aty - G(:,supp)*x_LS corr Aty - G(:, supp) * x_LS; end [~, idx] max(abs(corr)); supp [supp; idx]; x_LS A(:, supp) \ y; r y - A(:, supp) * x_LS; if norm(r) tol * norm(y) break; end end x_hat zeros(n, 1); x_hat(supp) x_LS; end推导逻辑很简单残差 r 始终等于 y - A(:,supp)·x_LS因此 A·r A·y - A·A(:,supp)·x_LS Aty - G(:,supp)·x_LS。每一轮只需一次 n×|S| 的矩阵乘而 |S| 远小于 m所以在 K 远小于 m 时收益明显。G 的存储代价是 O(n²)n4096 时约 134MBn8192 时超过 500MB内存不足时不要硬上这也是我把这个版本独立成函数而不是直接改写核心函数的原因之一。4.3 原子相干性与支撑集误判的排查OMP 是贪心算法它的失败模式非常集中字典里两个原子高度相关时算法选了其中一个残差投影会让另一个也表现得很强很容易把错误位置提前收入支撑集。字典相干系数定义为所有互异列内积绝对值中的最大值在 matlab 里一行代码就能检查G0 abs(A * A); G0(1:n1:end) 0; % 对角元是原子自身的内积置零排除 mu max(G0(:)); fprintf(字典相干系数 mu %.3f\n, mu);当 mu 超过 0.9 时OMP 选错原子的概率显著上升尤其在信号非零系数幅值有差异的场景。实际例子是图像稀疏表示里常用的过完备 DCT 字典列数比维度大几倍相邻频带的原子相干性很容易接近 1OMP 会把支撑集选在相邻的冗余基上恢复结果看着误差大但每次都稳定得诡异。matlab 图像处理的这类任务里我一般会先用 OMP 跑一版看支撑集索引是否频繁跳动如果跳动明显就改走 OLS 或者换用稀疏贝叶斯类算法。matlab 优化工具箱里的 lsqlin 也能作为子问题求解器替换反斜杠但收敛性质并不会因此变好真正要解决的是字典设计不是求解器。5. 用部分傅里叶矩阵验证omp恢复从观测到支撑集重合率5.1 构造部分傅里叶观测矩阵部分傅里叶矩阵是压缩感知里非常典型的一类测量矩阵常用于 MRI 重建和雷达回波稀疏恢复。它从 n 点 DFT 矩阵中随机抽取 m 行对应物理含义是“只观测部分频谱”。构造代码rng(7); n 1024; x_true zeros(n, 1); x_true([120 300 450]) [1.5 -2.0 1.0]; % 三个稀疏脉冲 sel sort(randperm(n, 256)); % 随机选 256 个频点作为观测 if exist(dftmtx, file) % Signal Processing Toolbox F dftmtx(n) / sqrt(n); else F exp(-2j*pi*(0:n-1)*(0:n-1)/n) / sqrt(n); end A F(sel, :); % 部分傅里叶测量矩阵 y A * x_true;这里 A 的行数只有 256列数是 1024属于明确的欠定系统。DFT 矩阵除以 sqrt(n) 后每列都是单位范数所以不需要再做列归一化。sel 排序的意义在于让观测矩阵的行顺序不影响 matlab 的稀疏运算缓存不排序也可以。5.2 用 omp_matlab 做恢复并计算支撑集重合率恢复与评估代码K_true length(find(abs(x_true) 1e-6)); % 真实支撑集大小 [x_hat, supp] omp_matlab(A, y, K_true, 1e-10); true_pos find(abs(x_true) 1e-6); hit length(intersect(supp, true_pos)) / K_true; snr_rec 20 * log10(norm(x_true) / norm(x_hat - x_true)); fprintf(支撑集重合率: %.0f%%, 恢复信噪比: %.2f dB\n, hit*100, snr_rec);在无噪声条件下OMP 能在这类部分傅里叶矩阵上精确恢复三个脉冲支撑集重合率应为 100%。如果出现漏选先确认观测频点数量是否过低m 明显小于 4K·log(n/K) 时恢复失败属于正常情况不是代码错误。5.3 含噪场景下用残差能量检验支撑集可信度最后一个实用技巧在含噪环境里拿到支撑集之后不要只看恢复信噪比要单独检验支撑集是否可信。做法是保留前文估计出的 σ_n然后比较最终残差能量与噪声理论能量sigma_n 0.02; y_noisy y sigma_n * randn(size(y)); [x_hat, supp] omp_matlab(A, y_noisy, K_true 5, 1e-8); x_check zeros(n, 1); x_check(supp) A(:, supp) \ y_noisy; r_final y_noisy - A(:, supp) * x_check(supp); res_energy norm(r_final)^2; noise_energy sigma_n^2 * length(y_noisy); if res_energy 2 * noise_energy disp(残差偏大支撑集可能有漏选需要放开K或放宽tol); elseif res_energy 0.1 * noise_energy disp(残差异常小存在过拟合支撑集里混入了噪声原子); else disp(残差在噪声范围内支撑集可信); end这个判据有效的前提是支撑集大小远小于观测维度 m。如果支撑集本身就接近 m最小二乘会精确拟合出零残差这时把残差和噪声能量对比没有意义。把这段检查逻辑加在你的 omp_matlab 主循环之后比对单纯的信噪比参数要可靠得多尤其适合批处理仿真里需要自动丢弃失败结果的情况。本文还有配套的精品资源点击获取
返回列表