简介:这份资源面向信号处理与机器学习方向的学习者和研究者,聚焦稀疏表示这一连接信息理论与优化理论的核心概念,帮助读者理解如何用尽可能少的非零元素刻画信号的内在结构,并掌握其在降噪、压缩感知与分类等任务中的实现思路。压缩包共6个文件,全部为m脚本,整体约4KB,涵盖主流程入口、PCA降维、L1正则求解、样本读取、精度计算与预处理等环节,构成一条从数据准备到稀疏编码验证的完整链路。资源已有6568人学习下载,说明其在相关课程与自学场景中具备一定认可度。代码围绕LASSO与BPDN等经典稀疏编码方法展开,并涉及字典学习与K-SVD的迭代思想,读者可据此对照马毅等人在PAMI上的研究工作,理解字典构建与优化求解的衔接方式,进而把稀疏性思路迁移到图像去噪、压缩感知重建等实际问题上,形成可复用的算法骨架与调试参考。
1. 稀疏表示:为什么“用更少的原子拼出信号”反而更稳
先给一个反直觉的结论:在稀疏表示里,把信号用一整套冗余字典去表示,解出来的系数往往比用一组正交基更“干净”、更抗噪。直觉上冗余意味着更多自由度、更容易过拟合,但实际恰恰相反——只要加上“系数尽量稀疏”这个约束,冗余字典反而让表示更鲁棒。这套机制在压缩感知、图像去噪、超分辨、字典学习里反复出现,而 MATLAB 因为矩阵运算天然贴合线性代数表达,成了很多人第一次把稀疏表示跑通的地方。搞懂它,你就明白为什么 L1 正则能把一堆近乎为零的系数“压”成真正的零,也明白为什么同一份代码换个字典就翻车。这份带完整 MATLAB 代码的资源,价值就在于把“字典怎么建、稀疏怎么求、重构怎么验”这条链路摊开给你看,而不是停在公式上。
2. 稀疏表示的核心机制:从欠定方程到 L1 的魔法
2.1 欠定方程为什么能有唯一解
设信号 $y \in \mathbb{R}^m$,字典 $D \in \mathbb{R}^{m \times n}$,且 $n > m$(原子数多于维数,字典冗余)。表示问题写成 $y = Dx$,未知数比方程多,线性代数告诉我们解有无穷多个。普通最小二乘会给出最小范数解,也就是能量最分散的那个,几乎每个系数都不为零——这不是我们想要的。
稀疏表示换了个目标:在所有满足 $y = Dx$ 的解里,挑非零元素最少的那个,即 $\min |x|_0$ 约束 $y = Dx$。$\ell_0$ 范数数的是非零个数,这个优化是 NP 难的,组合爆炸。关键结论是:当解足够稀疏、字典满足一定 incoherence 条件时,$\ell_0$ 问题等价于它的凸松弛 $\ell_1$ 问题 $\min |x|_1$ 约束 $y = Dx$。$\ell_1$ 是凸的,能转成线性规划或迭代软阈值求解。这就是整套机制的支点——用一个可解的凸问题,去逼近一个不可解的组合问题。
常见的实现思路是:不直接解约束形式,而是解它的拉格朗日形式 $\min_x \tfrac{1}{2}|y - Dx|_2^2 + \lambda |x|_1$,$\lambda$ 控制稀疏度和重构误差的权衡。$\lambda$ 越大,解越稀疏,但重构残差越大;$\lambda$ 趋近 0,退化成最小二乘。这个参数是整个流程里最需要手调的东西,后面避坑章节会专门讲。
2.2 软阈值与 ISTA:为什么“收缩”能产生精确零
求解 $\ell_1$ 正则问题最直观的算法是迭代软阈值(ISTA)。它的更新分两步:先沿梯度方向走一步,再对每个系数做软阈值收缩。软阈值算子定义为 $S_\tau(x) = \mathrm{sign}(x)\max(|x| - \tau, 0)$,$\tau$ 通常取 $\lambda / L$,$L$ 是 $D^T D$ 的最大特征值(Lipschitz 常数)。
function x = ista(y, D, lambda, iters) % ISTA 求解 min 0.5*||y-Dx||^2 + lambda*||x||_1 % y: 观测信号 (m x 1) % D: 字典 (m x n) % lambda: 正则化强度 % iters: 迭代次数 L = norm(D, 2)^2; % Lipschitz 常数,用谱范数平方近似 tau = lambda / L; % 软阈值门限 x = zeros(size(D, 2), 1); % 从零系数起步 for k = 1:iters grad = D' * (D * x - y); % 梯度:D^T(Dx - y) x = x - grad / L; % 梯度下降一步 x = sign(x) .* max(abs(x) - tau, 0); % 软阈值收缩 end end这段代码里,L = norm(D,2)^2取的是字典谱范数的平方,保证梯度步长不发散;tau是每次收缩的幅度,直接由 $\lambda$ 和 $L$ 决定。软阈值那行是精髓:绝对值小于tau的系数被直接压成 0,大于的则整体向零平移tau。正是这个“平移再截断”的操作,让解产生精确零而不是一堆小数值,稀疏性才真正落地。迭代次数iters不够会欠收敛,系数还没收缩到位;太大则浪费时间,实际中常配合收敛判据提前退出。
2.3 字典的两种来源:固定基 vs 学习得到
字典 $D$ 怎么来,决定了稀疏表示的上限。固定字典包括离散余弦变换(DCT)基、小波基、过完备的 Gabor 字典,构造简单、无需训练,但对特定信号的适配性有限。学习字典(如 K-SVD、在线字典学习)则从数据里迭代更新原子,让字典更贴合信号结构,代价是训练开销和过拟合风险。
K-SVD 的思路值得说清楚:它交替做两件事——固定字典求稀疏系数(就是上面的稀疏编码),固定系数逐个更新字典原子。更新第 $k$ 个原子时,只挑出用到该原子的样本,对这些样本的残差做奇异值分解,用最大奇异向量替换原子、对应奇异值更新系数。这样每次原子更新都直接降低重构误差,而不是盲目微调。常见的实现思路是:初始化字典用随机样本或 DCT 基,外层循环若干轮,内层稀疏编码用 OMP 或 ISTA,原子更新时注意跳过未被使用的原子,否则会除零。
3. 正交匹配追踪:贪心为什么也能逼近 L0 最优
3.1 OMP 的贪心逻辑与残差更新
除了凸松弛,另一条路是贪心算法,代表就是正交匹配追踪(OMP)。它不去解 $\ell_1$,而是直接逼近 $\ell_0$:每一轮从字典里挑一个和当前残差最相关的原子,把它加入支撑集,然后在这个支撑集上做最小二乘重算系数,更新残差,继续下一轮,直到残差足够小或达到稀疏度上限 $K$。
function [x, support] = omp(y, D, K) % 正交匹配追踪,求 K 稀疏表示 % y: 观测信号, D: 字典, K: 目标稀疏度 residual = y; support = []; x = zeros(size(D, 2), 1); for k = 1:K corr = D' * residual; % 各原子与残差的相关性 [~, idx] = max(abs(corr)); % 选最相关原子 support = [support, idx]; % 加入支撑集 Ds = D(:, support); % 取出支撑集对应的子字典 xs = pinv(Ds) * y; % 子字典上最小二乘 residual = y - Ds * xs; % 更新残差 end x(support) = xs; % 回填系数 endcorr = D' * residual是内积,衡量每个原子和当前残差的方向一致性;max(abs(corr))选绝对值最大的,因为系数可正可负。pinv(Ds) * y在支撑集上做最小二乘,保证已选原子的系数是最优的,这一步是“正交”二字的来源——残差与已选原子张成的空间正交。K是稀疏度上限,选大了会引入噪声原子,选小了重构不全,通常按信号先验或交叉验证定。
3.2 OMP 与 ISTA 的取舍
OMP 快、直观,稀疏度 $K$ 有明确物理含义,适合 $K$ 已知且字典相干性不高的场景。但它是贪心的,一旦某轮选错原子,后面很难纠正,原子间相关性高时容易翻车。ISTA 是全局凸优化,理论上更稳,但收敛慢,且 $\lambda$ 的调节不如 $K$ 直观。实践中常见做法是:先用 OMP 快速验证字典和信号是否匹配,再用 ISTA 或 FISTA(加速版)做精细重构。FISTA 在 ISTA 基础上加了 Nesterov 动量,收敛速度从 $O(1/k)$ 提升到 $O(1/k^2)$,代码改动很小,就是在梯度步里用前两步的组合,值得在资源代码里对照看。
4. 避坑与排查:稀疏表示跑不通时先看这几处
4.1 现象:重构全是噪声,系数几乎不稀疏
原因通常是 $\lambda$ 太小或iters太少,软阈值门限tau压不住系数,解退化成最小二乘。解决:先把 $\lambda$ 调大一个数量级观察系数稀疏度变化,画一张“$\lambda$ 对重构误差和稀疏度”的曲线找拐点;同时确认L = norm(D,2)^2没写错,写成norm(D)会导致步长过大直接发散。
4.2 现象:OMP 选来选去总是同一个原子
字典原子间相干性太高,或者信号本身和某个原子几乎共线。解决:检查字典的 Gram 矩阵 $D^T D$,非对角元接近 1 说明原子太像,需要换字典或做去相关;也可以改用 OMP 的变体,比如每轮选多个原子(CoSaMP 思路),降低单次误选的影响。
4.3 现象:字典学习训练集上很好,测试集崩
典型的过拟合。K-SVD 外层轮数太多、字典原子数远超样本数,都会让字典记住训练噪声。解决:控制字典规模(原子数一般取维数的 2 到 4 倍),留验证集监控重构误差,误差回升就停;原子更新时对奇异值做阈值截断,避免小奇异值放大噪声。
4.4 现象:矩阵求逆报奇异或结果数值爆炸
pinv在子字典列线性相关时会给出病态解,或者字典某列全零导致除零。解决:选原子时排除已选过的索引,更新前检查rank(Ds),必要时加小的对角正则(Tikhonov)再求逆;数值上把信号和字典都做归一化,避免量纲差异放大条件数。
提示:调参时先固定字典、只动 $\lambda$ 或 $K$,观察稀疏度和残差的单调关系,再动字典。两个变量一起调,出了问题根本分不清是谁的锅。
5. 设计取舍:冗余、稀疏与计算量之间的三角平衡
稀疏表示这套机制的本质,是在三个互相拉扯的量之间找平衡:字典冗余度、表示稀疏度、计算代价。冗余度高,原子选择多,表示可以更稀疏、更抗噪,但字典相干性上升,OMP 容易误选、ISTA 收敛变慢;稀疏度高,重构干净,但 $\lambda$ 或 $K$ 调过头就丢信息;计算代价则随字典规模和迭代次数线性甚至超线性增长。没有一组参数通吃所有信号,这正是它不像“调个库函数”那么省心的原因。
我一般会这么理解这三者的关系:字典是“词汇表”,稀疏度是“用几个词说清楚一件事”,计算量是“查词典的时间”。词汇表越全,越能用少数几个词精准表达,但查起来越慢、近义词越多越容易选错。所以工程上的常见做法是分层——先用小字典快速粗筛,再用大字典精修;或者对不同类型的信号用不同子字典,而不是硬堆一个超大字典。
验证一套稀疏表示流程是否真的 work,我习惯走三步:第一步,用合成信号(已知稀疏支撑集)测重构,看支撑集恢复率,这是最干净的验证;第二步,加不同信噪比的高斯噪声,画重构误差随 SNR 变化的曲线,看拐点是否合理;第三步,换真实信号,和 DCT 基、小波基的固定字典做对比,确认学习字典带来的增益是真实的而不是过拟合。这三步里任何一步异常,都说明字典、稀疏求解器或参数至少有一处不对,别急着上应用。
资源里的 MATLAB 代码把稀疏编码、OMP、字典学习几条线都摆在一起,最大的价值是让你能逐行对照公式和实现,改一个参数立刻看到系数和重构的变化。这种“可动手验证”的闭环,比读十篇推导都管用。我早年调一个去噪流程,就是因为没做合成信号验证,直接在真实图上调 $\lambda$,结果在训练图上看着挺好,换一张图就满屏伪影,血泪经验就是:先让合成实验跑通,再碰真实数据。从那以后我每次搭稀疏表示流程,都强制先走一遍已知支撑集的重构测试,确认求解器没写错再往下走。希望帮到你。
本文还有配套的精品资源,点击获取