MCMC马尔科夫-蒙特卡洛抽样:MATLAB仿真与收敛诊断
2026/9/16 10:56:00 网站建设 项目流程

简介:基于MCMC马尔科夫-蒙特卡洛抽样的MATLAB仿真资源,面向本硕博及教研人员,适用于学习Metropolis-Hastings抽样、贝叶斯推断与概率分布采样等典型应用场景。资源包共5个文件,包含3个MATLAB脚本、1个说明文档和1个avi操作录像,压缩包大小仅753KB,轻量易部署。脚本由主程序与两个子函数构成,主程序与子函数分离,便于逐段理解马尔科夫链状态转移、接受拒绝准则与积分估计的实现流程;配套txt说明与操作录像则演示了在MATLAB2021a及以上版本中的正确启动方式——需将当前文件夹切换至工程路径并运行主文件,而非直接运行子函数,可帮助使用者避开常见运行错误。目前已有2381人学习下载,适合作为MCMC算法课程设计、论文仿真或自学入门的参考资料,借助实操视频可快速复现抽样过程并验证算法效果。

1. 基于MCMC马尔科夫-蒙特卡洛抽样matlab仿真,到底在解什么问题

做贝叶斯推断的人迟早会撞上一堵墙:后验分布写得出公式,却算不出数值。抛硬币的二项分布还能手推共轭先验,一旦模型变成层次结构、混合分布或者带隐变量的状态空间,后验的归一化常数就是一个高维积分,解析解不存在,数值积分在维数超过四五个时直接失效。MCMC就是把“算积分”换成“抽样本”——让马尔可夫链的稳态分布恰好等于目标后验,链走出来的样本就是对后验的近似描述。用MATLAB做这件事,优势在于矩阵化计算、内置概率分布对象、以及调试可视化的一体化环境。这篇博客的落地路径是:先讲清抽样原理和链的收敛条件,再给出在MATLAB里可复现的Metropolis-Hastings和Gibbs采样实现,接着解决“仿真发散”和“链不收敛”的排查方法,最后用MATLAB把采样过程导出成操作视频,方便汇报和教学。

2. 马尔科夫-蒙特卡洛的核心:蒙特卡洛积分、细致平衡与转移核

2.1 蒙特卡洛采样为什么能替代解析积分

蒙特卡洛的核心思想是用样本均值替代积分期望。假设想计算函数g(x)关于分布p(x)的期望,解析做法是求∫g(x)p(x)dx,但高维情况下这个积分无法计算。蒙特卡洛的做法是从p(x)中抽取N个独立样本,用(1/N)Σg(x_i)近似期望。大数定律保证N足够大时近似收敛,中心极限定理则给出了误差的收敛速度——大概是N的平方根分之一。这意味着精度每提高一个数量级,样本量要增加一百倍,所以单纯增加抽样次数不是好办法,真正要做的是让每一个样本都来自目标分布。

MCMC与普通蒙特卡洛的区别在于样本并非独立。它构造一条马尔可夫链,当前状态只依赖上一个状态,通过精心设计的转移概率让链在足够长的时间后收敛到目标分布。链在平稳状态下的样本虽然相关,但依然可以用来做期望估计。这就是为什么MCMC适用于高维、非标准、无法直接采样的分布,而普通蒙特卡洛只能用于已知简单分布。

2.2 细致平衡条件与转移核的设计约束

要让一条马尔可夫链收敛到目标分布p(x),必须满足细致平衡条件:对于任意两个状态x和x',有p(x)T(x→x') = p(x')T(x'→x)。T表示转移核,即从当前状态跳转到另一个状态的概率密度。满足细致平衡能保证p(x)是链的不变分布,但不保证链一定收敛,还要求链具有不可约性和非周期性。不可约指从任意状态都能到达其他状态,非周期指不会陷入固定步长的循环振荡。

用MATLAB理解这些概念,最直观的方式是先不看MCMC,而是手动构造一个离散状态的马尔可夫链。

% 定义一个3状态马尔可夫链的转移矩阵 P = [0.7 0.2 0.1; 0.3 0.4 0.3; 0.1 0.3 0.6]; % 初始状态分布,从状态1开始 pi0 = [1 0 0]; pi_current = pi0; chain_states = zeros(2000, 1); for step = 1:2000 % 按当前状态所在行进行随机跳转 chain_states(step) = find(mnrnd(1, P(pi_current == 1, :))); pi_current = zeros(1, 3); pi_current(chain_states(step)) = 1; end % 计算各状态出现频率,对比理论平稳分布 empirical_pi = histcounts(chain_states, 1:4) / 2000; [eigen_vec, ~] = eig(P'); stationary = eigen_vec(:,1) / sum(eigen_vec(:,1)); disp('经验频率:'); disp(empirical_pi); disp('理论平稳分布:'); disp(real(stationary)');

这段代码从状态1出发,按转移矩阵P反复跳转2000步。mnrnd是MATLAB内置的多项分布随机数生成函数,第一个参数1表示每次抽一个样本,第二个参数是当前状态对应的转移概率行。迭代结束后统计每个状态出现的频次,并与P矩阵的特征向量对比——平稳分布就是特征值为1对应的左特征向量归一化结果。这个最小示例用来验证直观感受:不管初值如何,链跑足够长后状态访问频率总会稳定在固定比例上。MCMC的原理与此一样,区别只在转移矩阵是连续状态下的核函数,且目标分布不是固定矩阵的特征向量,而是待抽样的后验。

2.3 随机游走转移核与接受-拒绝框架

连续状态MCMC中最常见的构造方式是随机游走Metropolis-Hastings算法,简称MH。其做法是:当前状态记作θ,从提议分布q(θ'|θ)中生成候选状态θ',然后计算接受概率α = min(1, p(θ')q(θ|θ') / (p(θ)q(θ'|θ))),以概率α接受候选状态作为链的下一个状态,否则保持原状态。

提议分布的选择直接影响采样效率。高斯分布是应用最广的提议分布:θ' = θ + σ·ε,其中ε服从标准正态分布。对称提议分布下q(θ|θ') = q(θ'|θ),接受率退化为后验密度比值。σ步长太小,接受率高但链移动缓慢,样本强相关,有效样本量低;σ步长太大,候选点经常落到后验的尾巴区域,被拒绝的频率变高,链长时间停留原地。经验法则是调节σ让接受率落在0.2到0.5之间,高斯目标变量维度较低时0.44附近比较合适。

3. 在MATLAB中实现Metropolis-Hastings抽样:目标分布、候选分布与参数整定

3.1 用MH采样双峰高斯混合分布的完整代码

真实建模中后验经常是多峰的,这里用一个双峰高斯混合作为目标分布,展示MCMC如何从多峰分布中正确抽样。

% 目标分布的双峰高斯混合 mu = [-3; 3]; sigma = [1.0; 1.2]; weight = [0.4; 0.6]; log_target = @(x) log(weight(1)*normpdf(x, mu(1), sigma(1)) + ... weight(2)*normpdf(x, mu(2), sigma(2))); % MH参数 n_samples = 20000; % 总采样数 burn_in = 5000; % 燃烧期样本数 sigma_prop = 2.0; % 提议分布标准差 theta0 = -5; % 初始值 theta = zeros(n_samples, 1); theta(1) = theta0; n_accept = 0; % 随机游走MH主循环 for t = 2:n_samples % 从高斯提议分布生成候选点 theta_cand = theta(t-1) + sigma_prop * randn(); % 计算对数接受率,用log避免数值下溢 log_alpha = log_target(theta_cand) - log_target(theta(t-1)); log_alpha = min(0, log_alpha); % log(min(1, ratio)) % 接受-拒绝判定 if log(rand()) < log_alpha theta(t) = theta_cand; n_accept = n_accept + 1; else theta(t) = theta(t-1); end end % 输出接受率并绘图 accept_rate = n_accept / (n_samples - 1); fprintf('接受率: %.3f\n', accept_rate); samples = theta(burn_in + 1:end); figure; histogram(samples, 200, 'Normalization', 'pdf'); hold on; x_range = linspace(-8, 8, 500); p_true = weight(1)*normpdf(x_range, mu(1), sigma(1)) + ... weight(2)*normpdf(x_range, mu(2), sigma(2)); plot(x_range, p_true, 'r-', 'LineWidth', 2); legend('MCMC样本直方图', '真实目标分布');

代码里的关键点是全程使用对数密度。目标分布是混合高斯时,密度值本身可能极小,直接计算比值会长期等于0或inf,采样被迫中断。对数化之后比值变为减法,log(rand())与log_alpha的比较在数值上稳定得多。建议参数含义如下:sigma_prop是提议分布标准差,决定候选状态的平均跳跃距离;burn_in是燃烧期样本,这段样本尚未收敛到稳态,直接丢弃不参与统计;n_accept用于输出真实接受率,与预设经验值对照。运行后两个峰值都会被采出来,但链条在两个峰之间切换的频率取决于sigma_prop——步长太小会长期困在单一峰内。

3.2 对数概率比的计算为什么比直接除法可靠

直接计算接受率α = min(1, p(θ')/p(θ))在概念上简单,实际执行时却受限于浮点数精度。后验密度通常涉及连乘的似然项,比如观测数据有1000个点,每个点的似然在0.1到0.9之间,连乘之后数值直接下溢为0。0除以0没有意义。取对数之后,连乘变成求和,密度值的大小范围从10的负几百次方缩放到几百以内,完全落入双精度浮点的表示区间。

% 直接计算与对数计算的对比示例 x = 1:1000; likelihood_vec = 0.5 * exp(-0.5 * (x - 500).^2 / 100^2); p_direct = prod(likelihood_vec); % 下溢为0 p_log = sum(log(likelihood_vec)); % 对数域正常 fprintf('直接乘积: %g\n', p_direct); fprintf('对数求和: %.2f\n', p_log);

这段代码模拟了1000个观测点的似然连乘。prod运算结果在MATLAB中显示为0,因为中间步骤已经低于最小浮点数。而sum(log(...))得到一个负几百的数值,完全可用。MH算法里只需要后验密度的比值,对数空间下log(p(θ'))与log(p(θ))相减即可得到log_alpha,不需要恢复原始尺度。如果模型包含先验分布,把先验的对数密度加上似然的对数密度,整个计算过程始终保持在对数域。

3.3 提议步长sigma的整定策略与接受率对照表

sigma_prop取值接受率区间链的行为表现适用场景
0.1 ~ 0.30.7~0.9几乎每次接受,但相邻样本高度相关,有效样本量极低仅适合低维且目标分布方差很小的情况
0.5 ~ 1.50.4~0.6移动效率较高,自相关适中,最常用区间单峰且尺度接近标准正态的后验
2.0 ~ 4.00.2~0.35频繁拒绝但跳跃幅度大,链混合速度尚可双峰分布或多峰时适应能力较强
5.0 以上低于0.1链长时间停在原处,极少数大跳跃导致严重偏态不推荐,出现时需缩小步长

接受率并不是越高越好。接受率接近1说明提议分布的标准差远小于目标分布的标准差,链在局部范围内转圈,遍历整个后验需要极长的链长。接受率过低说明提议的跳跃经常落在后验支撑集外面,浪费了计算资源。一个实际做法是先用短链做预实验,以1000步为一次迭代调sigma,观察接受率落在0.2~0.5区间后,再正式跑长链。对高维问题,单一随机游走步长会失效,此时应当换用自适应MH算法,或者按维度分组采样。

4. 仿真发散与链不收敛:MATLAB下的诊断方法和处理策略

4.1 仿真发散的具体表现:轨迹图、直方图与自相关

仿真发散在MCMC语境里指链没有收敛到目标后验,或者在两个模态之间切换不足,统计结果失真。肉眼可见的标志是轨迹图出现长平台段——链长期停在同一数值附近不动,然后突然跳到另一片区域。另一个标志是直方图与目标分布明显偏离,比如双峰分布只采到一个峰。自相关图衰减缓慢则说明相邻样本之间信息冗余严重,有效样本量不足。在MATLAB里写一个简单的诊断脚本可以让这些问题当场暴露。

samples_good = theta(burn_in + 1:end); % 轨迹图:观察链是否遍历整个支撑区间 figure; subplot(2,1,1); plot(samples_good, 'LineWidth', 0.3); title('轨迹图'); xlabel('迭代次数'); ylabel('采样值'); % 自相关图:观察链的记忆长度 subplot(2,1,2); [autocorr_lags, autocorr_vals] = autocorr(samples_good, 'NumLags', 100); stem(autocorr_lags, autocorr_vals, 'MarkerSize', 3); title(sprintf('自相关函数,滞后期=100')); xlabel('lag'); ylabel('自相关系数');

轨迹图如果像毛毛虫那样围绕一条水平线均匀抖动,说明链平稳。如果出现明显断层或者长时间上升/下降趋势,说明链还处于燃烧期,需要丢弃更多前期样本或增大burn_in。自相关函数应该在若干滞后之后迅速跌向0,如果滞后50之后仍然高于0.2,说明相邻样本高度相关,应该考虑增大sigma_prop或对样本做稀疏化存储,每隔几步记录一个样本。

4.2 39个潜在尺度缩减因子R_hat:用多链判断是否收敛

单链轨迹图可能产生误判——链在一个局部模态停留足够久也会呈现“看似平稳”的画面。业界通行做法是同时跑多条链,比较链间方差与链内方差,这就是Gelman-Rubin诊断,也叫R_hat。收敛标准是R_hat小于1.1,越接近1越好。MATLAB中虽然没有直接封装R_hat计算,但用统计工具箱可以快速手写。

n_chains = 4; n_iter = 15000; burn_in = 5000; chain_samples = zeros(n_iter, n_chains); % 并行跑4条链,初始值分散在不同位置 start_points = [-6; -1; 2; 6]; parfor c = 1:n_chains chain_samples(:, c) = run_mh_sampler(start_points(c), n_iter); end % 计算R_hat samples_post = chain_samples(burn_in + 1:end, :); n = size(samples_post, 1); chain_means = mean(samples_post, 1); grand_mean = mean(chain_means, 2); B = n / (n_chains - 1) * sum((chain_means - grand_mean).^2); W = mean(var(samples_post, 0, 1), 2); var_est = (n - 1) / n * W + B / n; R_hat = sqrt(var_est / W); fprintf('R_hat = %.3f\n', R_hat);

multiple chains的核心意义在于暴露局部模态问题。如果所有链初始值分散在参数空间不同区域,最终都收敛到几乎相同的均值与方差,那可以确信链真正遍历了后验。R_hat大于1.1时不能信任当前采样结果,常见解决办法是延长燃烧期、增大链总数,或改用自适应提议分布。

4.3 处理多峰分布的收敛陷阱:模拟退火与并行回火

多峰后验是MCMC仿真发散的重灾区。普通MH从某个峰附近出发后,要跨越两个峰之间的低概率区域才能到达另一个峰,而低概率区域的接受概率极低,链几乎不可能完成跨越。解决办法分两类:一类是把目标分布取幂,p_α(θ) ∝ p(θ)^(1/T),温度T越高分布越平坦,峰谷之间的差异被抹平,链可以自由巡游整个空间;随后逐渐降低T恢复到原始分布。这就是模拟退火。另一类是并行回火,同时维护多条不同温度的链,每隔一定步数按Metropolis准则交换两条链的状态,高温链帮低温链跳出局部陷阱。

MATLAB实现并行回火的关键代码片段:

function swapped_samples = parallel_temper_mh(log_target, n_chains, n_iter, swap_interval, temps) n_samples = n_iter * n_chains; chain_states = randn(n_chains, 1) * 5; swapped_samples = zeros(n_samples, 1); idx = 1; for iter = 1:n_iter for c = 1:n_chains proposal = chain_states(c) + 1.5 * randn(); log_alpha = (log_target(proposal) - log_target(chain_states(c))) / temps(c); if log(rand()) < min(0, log_alpha) chain_states(c) = proposal; end end % 相邻链状态交换 if mod(iter, swap_interval) == 0 for c = 1:(n_chains - 1) log_swap = (log_target(chain_states(c + 1)) - log_target(chain_states(c))) * ... (1 / temps(c) - 1 / temps(c + 1)); if log(rand()) < min(0, log_swap) temp_state = chain_states(c); chain_states(c) = chain_states(c + 1); chain_states(c + 1) = temp_state; end end end for c = 1:n_chains swapped_samples(idx) = chain_states(c); idx = idx + 1; end end end

温度序列一般按几何级数排布,最低温为1,最高温让链能自由跨越两个峰。链间交换间隔不宜太频繁,否则状态交换开销过大;也不宜太稀疏,否则高温链无法帮助低温链。实际运行时先试swap_interval = 50,观察低温链的轨迹图是否出现模态切换,再逐步调整。

5. 把MCMC采样过程做成操作视频:MATLAB动画录制与结果导出

5.1 用animatedline动态绘制轨迹和高斯核密度估计

汇报和教学场景里,静态直方图不足以展示MCMC的抽样过程。动态呈现采样点一波一波落入直方图,观众才能直观理解马尔可夫链逐步逼近目标分布的过程。MATLAB的animatedline函数可以在循环中不断追加数据点并实时刷新图形,搭配drawnow即可形成流畅动画。

% 动态绘制MCMC采样过程动画 figure; h_hist = histogram('BinEdges', -8:0.2:8, 'BinCounts', zeros(1, 80), ... 'FaceColor', [0.3 0.6 0.9], 'EdgeColor', 'none'); hold on; % 目标分布参考曲线 x_line = linspace(-8, 8, 300); p_line = weight(1)*normpdf(x_line, mu(1), sigma(1)) + ... weight(2)*normpdf(x_line, mu(2), sigma(2)); plot(x_line, p_line, 'r-', 'LineWidth', 2); ylim([0 0.3]); xlabel('θ'); ylabel('概率密度'); % 动态更新直方图 bin_edges = -8:0.2:8; hist_counts = zeros(1, length(bin_edges) - 1); total_draws = 0; for t = 1:2000 % theta_precomputed为事先跑好的MH链 idx = find(theta_precomputed(t) > bin_edges(1:end-1) & ... theta_precomputed(t) <= bin_edges(2:end), 1); if ~isempty(idx) hist_counts(idx) = hist_counts(idx) + 1; end total_draws = total_draws + 1; if mod(t, 5) == 0 set(h_hist, 'BinCounts', hist_counts / total_draws / 0.2); title(sprintf('MCMC采样过程 | 第 %d 次迭代', t)); drawnow; end end

这个动画展示的是归一化直方图逐步逼近目标分布曲线的过程。每5步刷新一次可以降低渲染开销,2000次迭代的动画大约5秒内完成。运行前需要预先用MH算法生成theta_precomputed序列,动画只是将采样过程重放一遍,避免动画播放期间的回放与真实采样相互干扰。

5.2 输出为GIF或MP4:录制操作视频与分享格式选择

MATLAB导出动画有两种推荐方式。一是输出为GIF,适合插入PPT或网页展示;二是输出为MP4,适合完整操作视频录制。转换逻辑都是先把动画每一帧捕获下来,再编码成目标格式。

% 方案一:导出GIF figure; gif_filename = 'mcmc_sampling.gif'; for t = 1:500 % 更新图形的代码,与5.1节相同 % ... frame = getframe(gcf); [A, map] = rgb2ind(frame.cdata, 256); if t == 1 imwrite(A, map, gif_filename, 'gif', 'LoopCount', Inf, 'DelayTime', 0.05); else imwrite(A, map, gif_filename, 'gif', 'WriteMode', 'append', 'DelayTime', 0.05); end end % 方案二:导出MP4视频 video_file = VideoWriter('mcmc_sampling.mp4', 'MPEG-4'); video_file.FrameRate = 20; open(video_file); for t = 1:500 % 更新图形的代码 % ... frame = getframe(gcf); writeVideo(video_file, frame); end close(video_file);

GIF的优势是到处能播放,缺点是有256色限制,直方图色彩过渡会出现轻微色带。MP4色彩保真度更高,文件体积也更小,但需要播放器支持。DelayTime参数控制每帧停留时间,0.05秒对应20帧每秒的输出速度,符合人眼视觉暂留特性。导出视频之前建议把figure窗口置于屏幕最前并固定尺寸,getframe捕获的画面大小与figure窗口显示尺寸严格一致,窗口大小影响视频分辨率。

5.3 操作视频中的关键标注:高斯核密度叠加与接受率显示

单纯动画还不够,操作视频里最好把每次迭代是“接受”还是“拒绝”用颜色标出来,接受的点用深色落进直方图,拒绝时当前帧高亮显示并保留原状态。另一种有效标注是动态显示累计接受率,让观看者看到链的调整过程。

% 在绘图区域左上角动态显示接受率文本 text_handle = annotation('textbox', [0.15 0.8 0.3 0.1], ... 'String', '', 'EdgeColor', 'none', ... 'FontSize', 12, 'FontWeight', 'bold'); for t = nanot~:2000 % ...绘图更新代码... current_accept_rate = n_accept / t; set(text_handle, 'String', sprintf('累计接受率: %.2f%%', current_accept_rate * 100)); drawnow; end

这段代码用annotation在图形坐标系的相对位置绘制文本,不受坐标轴范围变化影响。接受率文本随着迭代进行逐渐稳定到目标区间,观看者能看到参数整定的动态过程。最后补充一句:做操作视频时,把燃烧期用红色竖线在轨迹图上标出来,观众一眼就能分辨哪些样本进入统计、哪些被丢弃。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询