简介:这是一份面向压缩感知研究的稀疏贝叶斯算法代码包,包含SBL、TSBL与TMSBL三种主流实现,适合需要从少量测量值中重构稀疏信号的科研人员和工程师,可应用于图像重建、医学成像、无线通信信道估计等任务。作者已亲自测试,代码可顺利运行,减少了调试成本。
资源共15个文件,主体为11个MATLAB脚本,实现了SBL、TSBL、TMSBL及MSBL、MFOCUSS等算法,并附带演示示例;另有2份PDF和1份Word快速上手指南,讲解算法使用要点,txt文件说明整体流程。压缩包仅479KB,获取方便、部署简单。
目前已有929人学习该资源。通过阅读源码和文档,读者不仅能复现时间变化、不同信噪比等实验场景,还能深入对比SBL与TSBL、TMSBL在时间相关性和多尺度建模上的差异,掌握贝叶斯稀疏重构的核心思路,便于后续改造和拓展应用,是一份高价值的压缩感知工具资源。
1. 压缩感知稀疏贝叶斯:SBL 家族为什么值得亲测一遍
压缩感知稀疏贝叶斯学习(SBL)这个词,做过信号重构的人多半不陌生。和 L1 范数类算法相比,SBL 的迭代往往显得慢,但在多快拍场景下,它的支撑集恢复精度和噪声鲁棒性通常会反超。这份资源把 SBL、TSBL、TMSBL 三种稀疏贝叶斯算法的 MATLAB 实现打包在一起,我实测过 demo.m、demo_time_varying.m 等脚本,能直接跑通出图。
适合正在做 DOA 估计、MIMO 信道估计、脑电或心电信号重构的工程师,也适合刚接触多测量向量(MMV)压缩感知的学生拿去做基线对比。下文先说清楚三种算法的差别和选型边界,再给出完整复现步骤、参数说明和踩坑记录。
2. 三种算法的原理与选型:从稀疏先验到时间相关性建模
2.1 SBL 的核心思想:把稀疏约束放进超参数
SBL 和 L1 正则化的本质差别在于:L1 把稀疏约束写成目标函数里的惩罚项,而 SBL 把稀疏建模到参数的概率分布里。以包里的 MSBL.m 为例,观测模型是 Y = A X + N,X 是 N×T 的多快拍信号,T 是时间快拍数。核心假设是行稀疏:所有时间快拍共享同一个支撑集,也就是 X 的非零行都落在同样的行索引上。这个假设在 DOA 估计里很自然——同一个声源在连续多个快拍里都位于相同方位,只是幅度在变。
实现上,算法给每一行 X(i,:) 赋予一个方差超参数 gamma_i,然后做 Type-II 最大似然估计。迭代过程中大部分 gamma_i 会被压到极小值,对应行被自动剪掉,剩下的大 gamma 行就是恢复出的支撑集。相比 L1 需要手调正则化系数,SBL 顺手把噪声方差 sigma 也当成可学习参数,这是它鲁棒性好的一个直接原因。我对这个机制的理解是:L1 是在"罚"稀疏,SBL 是在"学"稀疏,后者对噪声水平的适应能力强得多。
核心迭代可以写成下面的示意代码,理解它对后续调参加大有帮助:
% SBL 类算法的核心迭代(示意,对应 MSBL.m 的内部逻辑) % Sigma = inv(diag(gamma) + A'*A / sigma^2) % 每轮迭代: % Sigma_old = inv(diag(gamma) + A'*A / sigma^2); % mu = Sigma_old * A' * Y; % 后验均值 % gamma_new = mean(mu.^2, 2) + diag(Sigma_old) / T; % sigma_new = norm(Y - A*mu, 'fro')^2 / (M*T) ... % + sum(diag(Sigma_old) .* sum(A.^2, 1)') / (M*T);逻辑说明:mu 是后验均值,Sigma_old 是后验协方差。gamma_new 的更新公式里,第一项 mean(mu.^2, 2) 衡量该行在数据拟合里的能量,第二项 diag(Sigma_old)/T 是超参数自身的"惯性"惩罚,正是这一项让无关行的 gamma 迅速衰减。sigma_new 的更新拆成残差项和协方差迹项,避免噪声估计被过拟合带偏。
参数说明:gamma 是 N×1 列向量,对应 X 每一行的方差;sigma^2 是标量噪声方差;T 是快拍数。gamma 初始化通常全设为 1,sigma 初始化可以先用观测残差估一个粗值,后面算法会自己迭代修正。需要注意,这段代码是理解用,不是让你替换包内实现——MSBL.m 里还有数值稳定性处理,直接改容易翻车。
2.2 TSBL:把时间连续性写进先验
TSBL(Temporal Sparse Bayesian Learning)解决的是"支撑集随时间不变、但幅度随时间变化"的信号。典型场景:同一个 DOA 源在连续多个快拍里一直存在,只是幅度有起伏;或者生理信号里某个频点在整段记录中都活跃。这种时间结构如果不用,相当于浪费了宝贵的先验信息。
TSBL 和 MSBL 的差别在于:MSBL 假设不同时间快拍之间独立,TSBL 则显式估计时间相关矩阵 B,把 X 每一行在时间轴上的相关性建模进去。这样在快拍数不充裕时,TSBL 能靠时间结构把支撑集估得更稳。包里的 demo_time_varying.m 就是这种场景的复现脚本,信号支撑集固定、幅度随时间平滑变化。
% TSBL 调用(以包内 TSBL.m 实际签名为准) % 常见签名: [X, gamma, sigma] = TSBL(A, Y, option); option.prune_gamma = 1e-4; % gamma 剪枝阈值 option.maxit = 500; % 最大迭代次数 option.learn_b = 1; % 是否学习时间相关矩阵 B [X, gamma, sigma] = TSBL(A, Y, option);逻辑说明:learn_b 置 1 时,算法会在每轮迭代里用当前残差估计时间相关矩阵,再把它代回先验更新;置 0 时退化成时间独立的版本。对平滑变化的信号,learn_b=1 能显著提高支撑集恢复率;但对时间上本来就不相关的信号,learn_b=1 反而是负资产。
参数说明:prune_gamma 是剪枝阈值,gamma 低于该值的行会在迭代中被直接剔除,既省时间也防止数值震荡。maxit 是最大迭代次数,实际多数场景在 100~300 轮内收敛,设 500 是为了防止算法在边界条件下没跑完就退出,真跑满 500 轮还没收敛,问题通常不在迭代次数上。
2.3 TMSBL:多尺度分块解决了什么问题
TMSBL 是在 TSBL 基础上的进一步扩展,引入了分块结构:按先验知识把 N 行分成若干组,每一组内部共享同一个方差超参数。这就是"Multiple-Scale"的含义——不同块可以用不同的稀疏力度去约束,而不是所有行一刀切。这在信号天然有分组结构时特别有用,比如一组传感器对应一组行、一组频点对应一组行。
当信号的行天然存在分组结构时,TMSBL 能把块稀疏先验用上,样本效率更高。代价是必须提供合理的 group 划分,group 给错了效果反而比 MSBL 差。包里的 TMSBL.m 和 TMSBL_code 目录就是干这个的,配套的 "Master the Usage of TMSBL in 3 Minutes.pdf" 快速上手文档写得很清楚,建议跑代码之前先花三分钟过一遍。
% TMSBL 需要额外传入 group(分块索引) % 常见签名: [X, gamma, sigma] = TMSBL(A, Y, group, option); N = 100; % 行数 block_size = 10; group = cell(1, N / block_size); for g = 1:(N / block_size) group{g} = (g-1)*block_size + 1 : g*block_size; end option.prune_gamma = 1e-4; [X, gamma, sigma] = TMSBL(A, Y, group, option);逻辑说明:group 是元胞数组,每个元胞是一组行索引,算法会为每个块学习一个独立的超参数,同时对块内的时间相关性做联合建模。分块越符合信号的真实结构,收敛越快、支撑集恢复越准。
参数说明:block_size 不是算法参数,是信号本身的先验。如果你并不知道真实块大小,宁可把块设小一点(比如 5~10 行一组),也不要设成整块一块,后者会让 TMSBL 失去区分不同尺度的能力,退化成近似 TSBL。
2.4 选型边界:什么时候该换算法
选型判断我总结成三条,剩下的交给实验说话:
- 只有一个观测向量(T=1),用 SBL 类最朴素的版本就行,TSBL/TMSBL 的时间建模没有用武之地。
- 多快拍且支撑集固定,信号在时间上平滑变化,直接用 TMSBL;时间相关性一般不明显的,TSBL 足够。
- 完全不确定信号结构,先用 MSBL 跑一遍当基线,再拿 TSBL/TMSBL 对比。如果信号本身没有时间相关性,时间建模会帮倒忙,结果会比 MSBL 更差,这是正常现象,不是代码坏了。
我自己的习惯是永远先跑 MSBL 基线。没有基线就上 TMSBL,出了问题你分不清是参数没调好、group 给错了,还是算法本身不适合这个信号。有了基线,三行对比就能定位问题。
3. 跑通代码包:目录结构、demo 与核心函数
3.1 压缩包里的文件怎么组织
解压后能看到 TMSBL_code 目录、MSBL.m、TSBL.m、TMSBL.m、MFOCUSS.m 以及一堆 demo 脚本。ReadMe.txt 是总的说明,两个 PDF/DOCX 是 TSBL 和 TMSBL 的快速上手文档。我建议按这个顺序看:
- ReadMe.txt 先读,确认 MATLAB 版本要求,以及函数依赖关系。
- 跑 demo.m,看 MSBL 的基本重构流程。
- 跑 demo_time_varying.m 和 demo_identicalVector.m,对比 TSBL/TMSBL 对不同时间结构的处理。
- 需要复现论文图的再看 demo_fig3.m、demo_fig6_SNR10.m、demo_fig8.m。
perfSupp.m 是支撑集恢复性能的计算函数,后面验证指标会用到。MFOCUSS.m 是 FOCUSS 的 MMV 版本,通常被当作对比基线,跑对比实验时拿它和三兄弟放一起,表格会很好看。
3.2 demo.m 拆解:从生成信号到调用 MSBL
demo.m 是最短的完整流程,我把它的关键步骤拆出来:
% demo.m 的核心流程(示例) clear; clc; close all; rng(42); % 固定随机种子,保证可复现 N = 100; % 原子数 / 信号维度 M = 50; % 观测维度 T = 10; % 时间快拍数 K = 6; % 非零行数(稀疏度) A = randn(M, N); % 感知矩阵 for col = 1:N A(:, col) = A(:, col) / norm(A(:, col)); % 列归一化 end X_true = zeros(N, T); supp = randperm(N, K); X_true(supp, :) = randn(K, T); Y = A * X_true + 0.01 * randn(M, T); % 观测加噪 [X, gamma, sigma] = MSBL(A, Y); % 调用 MSBL support_est = find(gamma > 1e-3); hit = length(intersect(support_est, supp)) / K; fprintf('support recovery: %.2f\n', hit);逻辑说明:先固定随机种子,保证每次跑出来的支撑集、噪声都一样,这是复现和对比的前提。感知矩阵逐列归一化,这是 SBL 类算法的隐藏前置条件,后面避坑章节会专门讲。调用 MSBL 后,算法返回后验均值 X、超参数 gamma 和估计的噪声方差 sigma。
参数说明:N、M、T、K 四个数决定问题规模。压缩比 M/N 越低,重构难度越大;T 太小(比如小于 3),TSBL/TMSBL 的时间协方差估计就不稳。K 是真实稀疏度,对算法是未知的,只能从 gamma 的分布间接观察,所以 demo 里算 support recovery 前先用 gamma 阈值提取估计支撑集。
3.3 TSBL/TMSBL 的调用方式与 group 参数
TSBL 和 TMSBL 的调用和 MSBL 基本一致,区别在选项和时间建模:
% TSBL 与 TMSBL 的调用对照 % TSBL: [X, gamma, sigma] = TSBL(A, Y, option); % TMSBL: [X, gamma, sigma] = TMSBL(A, Y, group, option); common_opt.prune_gamma = 1e-4; common_opt.maxit = 500; % 用同一份观测同时跑三个算法 [X_msbl, g_msbl, s_msbl] = MSBL(A, Y); [X_tsbl, g_tsbl, s_tsbl] = TSBL(A, Y, common_opt); [X_tmsbl, g_tmsbl, s_tmsbl] = TMSBL(A, Y, group, common_opt); % 对比三者的支撑集 support_msbl = find(g_msbl > 1e-3); support_tsbl = find(g_tsbl > 1e-3); support_tmsbl = find(g_tmsbl > 1e-3);逻辑说明:把三个算法放在同一份观测上跑,是判断算法是否适合当前信号的最快方式。支撑集用 gamma 阈值提取,阈值取 1e-3 还是 1e-4 要看 gamma 的量级,量级偏小时取 1e-4 更安全。注意三个算法返回的 gamma 量级可能不同,统一用一个阈值时,要先各自看一眼分布再定。
参数说明:common_opt 在两个算法间复用,保证对比公平。注意 TMSBL 多一个 group 参数,如果这里传了错误的 group,TMSBL 的结果会明显劣于 TSBL,不能据此判断"TMSBL 不行",只能说明 group 给错了。这里也是新手最容易误判的地方。
4. 参数设置与结果验证:怎么判断恢复得好不好
4.1 关键参数表
把三个算法的参数汇总成一张表,方便对照:
| 参数 | 所属算法 | 建议取值 | 作用 |
|---|---|---|---|
| gamma 初始值 | SBL/TSBL/TMSBL | 全 1 | 每行方差超参数的起点 |
| prune_gamma | SBL/TSBL/TMSBL | 1e-3 ~ 1e-4 | 剪枝阈值,低于此值的行被剔除 |
| maxit | SBL/TSBL/TMSBL | 300 ~ 500 | 最大迭代次数 |
| sigma 初始值 | SBL/TSBL/TMSBL | 残差粗估计 | 噪声方差起点,迭代中更新 |
| learn_b | TSBL/TMSBL | 1 或 0 | 是否学习时间相关矩阵 |
| group | TMSBL | 按先验分块 | 块稀疏结构定义 |
需要多说一句的是 sigma 初始值。很多人直接取 0.01 或 0.1,这在 demo 里能跑,换数据就崩。我一般先用最小二乘残差估一个初值,量级对了算法自己会迭代修正,量级错了就是灾难。prune_gamma 也别拍脑袋,可以先跑一轮不剪枝,看 gamma 的直方图分布,选择两个量级之间的位置作为阈值。
4.2 验证指标:支持集恢复率与 NMSE
跑通不是终点,支撑集恢复率和归一化均方误差(NMSE)才是判断算法效果的两个硬指标:
% 支撑集恢复率 support_true = supp; support_est = find(gamma > 1e-3); hit = length(intersect(support_true, support_est)) / length(support_true); % 归一化均方误差 nmse = norm(X_est - X_true, 'fro')^2 / norm(X_true, 'fro')^2; fprintf('hit = %.2f, NMSE = %.4f\n', hit, nmse);逻辑说明:支撑集恢复率算的是"真实非零行有多少被找到",这个指标比 NMSE 对稀疏重构更敏感——NMSE 好看但支撑集找错位置,对后续定位问题毫无意义。两个指标搭配看,一个管位置准不准,一个管幅度像不像,缺一不可。
参数说明:gamma 阈值取 1e-3 时略激进,可能把微弱行也剪掉;取 1e-4 相对保守。对比不同算法时阈值必须一致,否则对比结论是无效的。demo_fig6_SNR10.m 和 demo_fig8.m 分别对应不同 SNR 下的实验,跑完用 perfSupp.m 汇总,就能看到支撑集恢复率随信噪比的曲线。
4.3 收敛判断:看 gamma 的变化轨迹
SBL 类算法没有显式的残差下降曲线可看,我一般直接观察 gamma 的迭代轨迹。最实用的做法是记录每一轮 gamma 的最大值和非零个数,画出来看是否进入平稳区。如果 gamma 最大值连续 20 轮变化小于 1%,基本可以认为收敛了;如果 gamma 一直在震荡,优先怀疑 sigma 初始化过小或感知矩阵条件数太差。
另外两个容易被忽略的点:一是 prune_gamma 设得过大(比如 1e-2)会让算法过早剪枝,把微弱但真实的非零行误杀,恢复率会掉得很明显;二是 demo_identicalVector.m 这个脚本,信号各时间快拍完全相同时,算法收敛极快,别拿它的收敛轮数去预估自己数据的收敛速度,二者没有可比性。
提示:对比算法必须用同一个随机种子、同一份观测 Y。换了观测谈对比,等于把变量搞混,结论不可信。
5. 避坑记录:SBL/TSBL/TMSBL 实战中我踩过的五个坑
5.1 换了自己的数据后输出全是 NaN
现象:demo 跑得挺好,把代码里的随机感知矩阵换成自己的测量矩阵,MSBL 直接输出 NaN 或者全零矩阵。
原因:感知矩阵没做列归一化。SBL 类算法的更新公式里隐式假设 A 的列能量一致,列范数差异过大时,gamma 的迭代会数值崩溃。demo 里的 randn 矩阵列范数天然接近,所以问题不容易暴露,换真实数据才会翻车。
解决:在调用任何算法之前,先对 A 做逐列归一化,并同步检查 Y 的尺度。这个前置处理我放在封装函数的第一行,比让每个调用者自己记得要靠谱得多。
5.2 TSBL/TMSBL 报维度不匹配
现象:报错 "Subscripted assignment dimension mismatch" 或 "Index exceeds array bounds"。
原因:group 分块没有覆盖全部 N 行。比如 N=100,group 只包了 90 行,或者块之间有重叠、有空洞。
解决:用一行代码校验 group 覆盖:
assert(isequal(sort([group{:}]), 1:N), 'group must cover 1:N exactly');这个断言我每次都会写进脚本里,比看报错猜原因快得多。TMSBL 对 group 的完整性要求极高,缺一行都会导致索引错乱。
5.3 gamma 迟迟不收敛,支撑集来回跳
现象:算法迭代到 maxit 也没停,最后输出的支撑集和真实支撑集偏差很大,而且换一次随机种子结果就变。
原因:sigma 初始化太小。噪声方差起点过低时,算法会把噪声当成信号去拟合,gamma 更新被残差主导,收敛不到正确位置。
解决:先用最小二乘残差估一个粗噪声方差作为初始值:
sigma_init = norm(Y - A * (A \ Y), 'fro')^2 / (M * T);这样一个粗估计通常就在正确量级附近,算法迭代几轮就能收敛到稳定值。从那以后我每次跑新数据,第一件事就是算这个值,不再手动猜噪声水平。
5.4 T=1 时 TMSBL 反而比 MSBL 差
现象:只有一个观测向量的时候,TMSBL 的时间协方差估计完全不 work,结果比 MSBL 差一截。
原因:T=1 时,时间相关矩阵 B 根本没有足够的样本去估计,TMSBL 等于多了一个无效约束,自然拖后腿。这属于模型假设和应用场景不匹配。
解决:T 小于 5 时直接回退到 MSBL,不要硬上 TSBL/TMSBL。这个规律在文档里没有明说,但代码跑出来的趋势非常明显。我遇到单快拍任务时,就直接跳过 TSBL/TMSBL 的对比环节了。
5.5 时间不相关的信号用 TSBL 精度下降
现象:信号在时间上其实是独立的,或者每快拍支撑集都在变,用 TSBL 去重构,支撑集恢复率反而低于 MSBL。
原因:TSBL 的先验假设是支撑集固定、幅度相关。信号不满足这个假设时,时间相关矩阵 B 学到的是虚假相关性,等于给模型强加了错误结构,属于先验和事实拧着来。
解决:先用 demo_time_varying.m 和 demo_identicalVector.m 两个脚本对照,判断自己的信号属于哪种形态,再决定选型。支撑集随时间变化的信号,本质上不是 TSBL/TMSBL 的目标场景,这时应该回到 MSBL 或者换用别的动态稀疏算法,别在不合适的算法上浪费时间调参。
6. 进阶:把 TMSBL 封装成自己的重构入口
跑通 demo 之后,最值得做的一件事是把这三个算法封装成一个统一的入口函数,这样以后换数据只需要改参数,不需要重读文档:
function [X, gamma, info] = my_sbl_recovery(A, Y, alg, group, opts) % alg: 'msbl' / 'tsbl' / 'tmsbl' % group: 仅 tmsbl 需要,其余传 [] if nargin < 5, opts = struct(); end if ~isfield(opts, 'prune_gamma'), opts.prune_gamma = 1e-4; end if ~isfield(opts, 'maxit'), opts.maxit = 500; end switch lower(alg) case 'msbl' [X, gamma, sigma] = MSBL(A, Y); case 'tsbl' [X, gamma, sigma] = TSBL(A, Y, opts); case 'tmsbl' assert(~isempty(group), 'TMSBL requires group'); assert(isequal(sort([group{:}]), 1:size(A,2)), 'group mismatch'); [X, gamma, sigma] = TMSBL(A, Y, group, opts); otherwise error('unknown algorithm: %s', alg); end info.gamma = gamma; info.sigma = sigma; info.support = find(gamma > opts.prune_gamma); end逻辑说明:封装的核心是统一输出接口,把 gamma、sigma、支撑集一次性返回。info 结构体里带上支撑集提取结果,后面无论是画图还是接入下游定位流程,都不用再重复写 find(gamma > thr) 这类代码。A 的列归一化也可以放进这个函数里,一步到位。
参数说明:opts 用 isfield 判断,缺省值自动补上,这样新来的同事只传 A、Y、alg 也能跑,不至于被参数项吓退。三个算法的返回值结构一致,switch 只是分派调用,真正的差异被挡在函数内部。
封装完之后,我建议你把 demo_fig3.m、demo_fig6_SNR10.m、demo_fig8.m 三张图分别在不同 SNR 下重跑一遍,用 perfSupp.m 统计支撑集恢复率,形成一张"算法×SNR"的对比表。这张表就是你以后跟别人讨论压缩感知稀疏贝叶斯算法时的硬证据,比口头说"效果不错"有用得多。从那以后,我每次拿到新的测量数据,都会强制先跑一遍 MSBL 基线和两个时间序列 demo 的对照,确认信号形态属于哪一类,再决定上 TSBL 还是 TMSBL。这个习惯帮我少走了很多弯路,希望帮到你。
本文还有配套的精品资源,点击获取