简介:这套基于高斯混合马尔科夫-蒙特卡洛算法(GM-MCMC)的线性地震反演Matlab仿真包,面向本硕博及科研人员,用于贝叶斯框架下的地震反演算法教学与编程实践。资源共13个文件,含9个m脚本/函数、2个mat数据文件、1个txt说明与1个avi操作录像,压缩包仅1.9MB,轻量易用,目录结构简洁便于快速定位。核心代码覆盖弹性正演模型、马尔科夫链模拟、GM-MCMC采样、协方差矩阵计算及后验概率估计等关键环节,主程序Runme.m可一键串联整个反演流程。此外,配套的操作录像演示了从环境配置到运行出图的全过程,可有效降低上手门槛。目前已有500人学习使用,对希望系统掌握蒙特卡洛反演思想并快速迁移到自身实验与科研场景的读者而言,是一份兼具完整性与可操作性的参考资料。
1. 安排一次 GM-MCMC 线性地震反演,从一条合成道开始
把一条 30 Hz 雷克子波和反射系数褶积成合成地震道,再用梯度类算法反演波阻抗,十次有七次会得到一个光滑得像低通滤波器的答案;如果目标是薄储层,这七次里可能还有三次直接把储层抹没了。问题出在不适定性上:正演是低通,高频信息在观测数据里根本没有,解空间天然是一大片等价的模型。GM-MCMC 的思路是把地下描述成一个加权的高斯混合先验——围岩、储层、致密层各占一个高斯簇,然后用马尔可夫链蒙特卡洛从后验里抽取大量样本,输出不是单一模型,而是整条后验分布。这套做法适合做叠后地震反演、测井约束波阻抗估计,以及需要给出 P10/P90 不确定性范围的储层评价任务。
2. 贝叶斯反演框架:把 GM-MCMC 拆成三个可计算的块
2.1 线性反演的正演算子:褶积模型与对数波阻抗近似
线性地震反演里的“线性”主要指正演路径。反射系数和波阻抗的关系可以写成
[ r_i \approx \frac{1}{2}\left(\ln Z_{i+1} - \ln Z_i\right) ]
即对数波阻抗的差分乘 0.5。观测地震道再由反射系数与子波褶积得到
[ d = w * r + n ]
把这两个式子合起来,正演就变成矩阵乘法:
[ d = W D m + n = G m + n ]
其中 (m) 是对数波阻抗,(D) 是差分矩阵,(W) 是褶积矩阵。合成数据里这个 (G) 显示写出后是稀疏的,matlab 里一次正演只需要一次稀疏矩阵乘法和一次卷积,这是 GM-MCMC 能跑起来的前提——MCMC 一轮要评估很多次似然,正演算子必须便宜。
注意这里“线性”不是说问题简单。观测噪声是加性高斯,似然函数 (p(d|m)) 确实是高斯的,但先验如果是多峰混合,后验仍然多峰。后面你会看到,MCMC 在“线性问题”里照样有不可替代的位置。
2.2 高斯混合先验:用几类高斯描述地下岩性
高斯混合模型把先验写成
[ p(m) = \sum_{k=1}^{K} \pi_k , \mathcal{N}(m; \mu_k, \Sigma_k) ]
每一个分量代表一种“岩性-阻抗”组合。比如围岩的对数波阻抗均值 (\mu_1=9.0)、标准差 0.05,储层 (\mu_2=8.2)、标准差 0.10,致密层 (\mu_3=9.4)、标准差 0.08,权重 (\pi_k) 对应各岩性的体积比例。这样先验天然就有多个峰:储层和围岩的阻抗差异体现为两个峰的位置差,层内波动体现为各自的方差。
和单高斯先验相比,GMM 对薄储层更友好。单高斯会把后验拉向全局平滑,把 8.2 的低阻抗层抹成 8.6;GMM 允许先验密度在两个峰值之间出现凹谷,只要数据有一点储层响应,后验就有可能偏向低阻抗峰。代价是 GMM 本身不包含空间相关性——每个深度点的类别是独立抽的,层与层的连续性要靠后续 MCMC 的 proposal 设计和平滑约束补上。
2.3 为什么“线性 + 高斯”仍然要 MCMC:后验不是单一高斯
如果先验是单高斯、噪声是高斯、正演是线性的,后验可以直接用卡尔曼滤波或最小二乘解析求解,根本不需要采样。但把先验换成 GMM 后,后验是两个高斯峰分别乘以同一个似然再叠加,仍然多峰。这种情况下最大后验估计高度依赖初值,可能落在错误的峰上,而且给不出任何不确定度信息。
MCMC 的核心价值是让样本自身携带不确定性。样本直方图直接显示双峰结构:哪些深度点储层概率高,哪些点围岩概率高,一清二楚。四种常见实现路径对比如下:
| 方法 | 优势 | 代价 | 实现难度 |
|---|---|---|---|
| MH 随机游走 | 每次只需一次正演,内存小 | 样本自相关高,多峰切换慢 | 低 |
| Gibbs 采样 | 条件分布可解析时收敛快 | 需要推导条件分布,GMM 下并不直接 | 中 |
| HMC | 高维连续后验效率高 | 需要梯度,GMM 的 log 密度梯度好算 | 中高 |
| 变分推断 | 计算快,适合大规模 | 只能给近似分布,容易低估方差 | 中 |
对 GMM 先验 + 线性算子 + 几百维参数的问题,我的默认选择是 MH 随机游走加块更新。维度超过 1000 或链始终跨不过峰间势垒时,再考虑把 HMC 的 leapfrog 集成进来。
3. 在 matlab 中实现 GM-MCMC:从合成道到后验样本
3.1 构造合成地震道与线性正演算子
先用一段可复现的脚本生成带薄储层的对数波阻抗模型,并合成观测地震道。
rng(42); nz = 120; % 深度采样点数 true_lp = 9.0 * ones(nz,1); % 背景对数波阻抗 true_lp(40:60) = 8.2; % 储层段:低阻抗 true_lp(70:85) = 9.4; % 致密段:高阻抗 % 反射系数,近似为对数阻抗差分的一半 r = zeros(nz,1); r(2:nz) = 0.5 * (true_lp(2:nz) - true_lp(1:nz-1)); % 30 Hz 雷克子波 nt = 32; dt = 1; f0 = 30; t = (0:nt-1)*dt - (nt-1)/2*dt; w = (1 - 2*pi^2*f0^2*t.^2) .* exp(-pi^2*f0^2*t.^2); w = w / max(abs(w)); % 褶积 + 观测噪声 d = conv(r, w, 'same'); d = d + 0.03 * randn(nz,1);这段脚本里true_lp的三段式结构对应 GMM 的三个分量。conv(r,w,'same')保持了和深度道一样的长度,正演算子在这里是隐式的。实际反演时可以把conv包成一个匿名函数,比如G = @(m) conv(0.5*diff([m(1);m]), w, 'same'),这样 MCMC 主循环里调用最方便。噪声标准差 0.03 是相对对数波阻抗的量级,实际数据用残差估计。
3.2 建立高斯混合先验:fitgmdist 与手动高斯簇
先验有两种来源。有测井解释标签时,直接把每类的均值、方差、权重喂给gmdistribution:
mu = [9.0; 8.2; 9.4]; sig = [0.05; 0.10; 0.08]; pik = [0.6; 0.25; 0.15]; gm = gmdistribution(mu, sig, pik);没有标签时,用一段测井的对数波阻抗做无监督聚类:
options = statset('MaxIter', 1000, 'Display', 'off'); gm = fitgmdist(lp_well, 3, ... 'CovarianceType', 'diagonal', ... 'RegularizationValue', 1e-6, ... 'Options', options);fitgmdist在 matlab 的统计与机器学习工具箱里,注意如果环境中没有这个工具箱,手写 EM 也能完成——反正是估计 (\mu_k)、(\Sigma_k)、(\pi_k) 三个参数组。RegularizationValue设一个小的正数可以防止某个类别的样本太少导致协方差奇异;这个参数在真实测井数据上几乎是必设的。
3.3 块更新 M-H 采样器:接受概率的对数实现
MCMC 主循环我一般不用全向量扰动。120 维全向量独立高斯扰动的接受率会迅速掉到 1% 以下,因为任何一点扰动让某一道不拟合就能把似然拉下来。改为每次随机抽取 8 个深度点同时扰动,接受率可以回到 20% 到 40% 之间。
sigma_e = 0.03; % 观测噪声标准差 smooth_lambda = 1.0; % 平滑约束权重 step = 0.06; % proposal 扰动幅度(对数阻抗域) block_size = 8; % 每次更新的深度点数 niter = 30000; burnin = 8000; cur = 9.0 + 0.2 * randn(nz,1); samples = zeros(niter, nz); accept = 0; % 先验:GMM 的 log 密度 + 相邻点平滑惩罚 log_prior_full = @(m) sum(log(pdf(gm, m))) ... - smooth_lambda * sum(diff(m).^2); for iter = 1:niter prop = cur; idx = randperm(nz, block_size); prop(idx) = prop(idx) + step * randn(block_size, 1); % 两次正演:当前模型和提议模型 rcur = 0.5 * diff([cur(1); cur]); rprop = 0.5 * diff([prop(1); prop]); res_cur = d - conv(rcur, w, 'same'); res_prop = d - conv(rprop, w, 'same'); ll_cur = -0.5 * sum(res_cur.^2) / sigma_e^2; ll_prop = -0.5 * sum(res_prop.^2) / sigma_e^2; % 接受概率用对数形式,避免 exp 上溢 log_alpha = (ll_prop - ll_cur) ... + (log_prior_full(prop) - log_prior_full(cur)); if log(rand()) < log_alpha cur = prop; accept = accept + 1; end samples(iter, :) = cur; end accept_rate = accept / niter;randperm(nz, block_size)保证每个深度点被抽中的概率均匀,而step控制单次扰动量。diff([m(1);m])相当于在浅端边界用零反射系数延拓,减少端点效应。log_prior_full里的smooth_lambda * sum(diff(m).^2)等价于对相邻波阻抗差加一个零均值高斯先验,这比单纯依赖 GMM 的独立采样更贴近真实地层连续性。
3.4 收敛后处理:干链样本、后验直方图与 P10/P90
采样完成后,先舍弃 burnin,再做 thinning 降低样本自相关:
thin = 10; post = samples(burnin+1:thin:end, :); % 逐深度后验均值与分位数 mean_model = mean(post, 1); p10 = prctile(post, 10, 1); p90 = prctile(post, 90, 1); % 画三条曲线 depth = (1:nz) * 5; % 假设每点 5 米 plot(mean_model, depth, 'r', p10, depth, 'b--', p90, depth, 'b--'); set(gca, 'YDir', 'reverse');thinning 取 10 意味着 30000 次迭代最终保留约 2200 个样本,足够画出平滑的直方图。P10 和 P90 是逐深度统计的,不能理解成某一条链的整体包络。后续做储量区间估计时,这两个向量比 MAP 曲线有用得多。
4. 参数怎么调:GM-MCMC 必设参数与三条收敛判据
4.1 必设参数的取值范围与调参信号
GM-MCMC 里真正决定成败的不是 GMM 的精度,而是下面五个参数的配合:
| 参数 | 含义 | 常见范围 | 超范围时的表现 |
|---|---|---|---|
step | proposal 扰动幅度 | 0.03 ~ 0.15(ln 阻抗域) | 接受率低于 15% 或高于 40% |
block_size | 每次扰动点数 | 4 ~ 12 | 块太大接受率陡降,块太小全局移动慢 |
burnin | 预烧期长度 | 5000 ~ 20000 | 统计量随初值明显漂移 |
niter | 采样迭代总数 | 20000 ~ 50000 | 后验分位数仍在抖动 |
smooth_lambda | 平滑约束权重 | 0.5 ~ 5.0 | 模型过度光滑,储层细节被压平 |
step的调法不看绝对值,看接受率。块更新的理论参考是 20% 到 40%,低于 15% 就减小step或减小block_size,高于 40% 说明每次动得太小,链虽然到处接受但整体移动缓慢。smooth_lambda与 GMM 先验是竞争关系:太高会让两类阻抗差异变糊,太低会让单点随机起伏变大,实际数据上可以分别跑一次对比储层顶底反射的锐度。
提示:如果手里有操作视频或别人给的脚本,建议先在这组参数下跑通合成数据,再替换真实道集。真实数据的主频、噪声和子波相位会和合成道差很多,直接套参数很容易看到链完全不动。
4.2 用三条判据确认链已进入后验收敛
第一条是接受率窗口。MH 随机游走的长期最优接受率大约在 23% 到 50% 之间,块更新时落在 20% 到 40% 算健康。
第二条是 trace 平稳性。选 3 个代表性深度点(浅部背景、储层中心、致密层)画迭代轨迹,burnin 之后不应该有明显的线性漂移。储层中心的 trace 应该在 8.2 附近来回跳,偶尔跑到 8.5,这是多峰后验的正常表现。
第三条是多链 Gelman-Rubin 统计量。两条独立链各跑同样迭代数,取后半段:
n1 = size(s1,1); % 每条链样本数 m_all = cat(3, s1, s2); % 两条链叠成三维数组 mean_chain = mean(m_all, 1); B = n1 * var(squeeze(mean_chain), 0, 1); % 链间方差 W = mean(var(m_all, 0, 1), 1); % 链内方差 Rhat = sqrt((1 - 1/n1) + B / (n1 * W));任何深度点的Rhat大于 1.1 都说明链还没探索到完整后验,加长niter或缩小 proposal 步长。严格一点应该用 split-Rhat,但作为日常排查这个简化版够用。
4.3 四个常见的失败模式与修正
链卡住是 GM-MCMC 最典型的失败。表现为储层深度的 trace 始终停留在 8.2 而不访问 9.0,或反过来。原因是 GMM 两个分量之间的势垒太高,随机游走跨不过去。改进办法有两个:轻量方案是把高斯 proposal 换成长尾的 t 分布,偶尔大步长跳跃;重量方案是做温度采样,在高温度链上交换状态。
接受率接近 1 但模型几乎不变,是step太小。此时链每一步都被接受,但每一步只移动 0.001,30000 步也不够走出初始区域。调大step,观察 trace 是否出现更大幅度的随机摆动。
边界端点震荡是另一个常见现象:浅端和深端附近没有地震约束,后验几乎完全由先验支配,样本方差明显偏大。处理方式是在两端施加更紧的先验均值,比如把端点固定在由测井曲线外推得到的值上。smooth_lambda能减轻但无法根除端点效应。
如果后验样本显示储层位置漂移不定,比如 40 到 60 号采样点的均值被拉平成一条缓坡,通常是smooth_lambda设得过大,导致平滑项抵消了 GMM 的峰间分离。把smooth_lambda调到 1 以下,并把 GMM 的峰间距重新核对一遍,确认测井统计的先验和地震反演目标一致。
5. 从后验样本直接输出储层概率与厚度不确定度
反演完成后,最有价值的输出不是均值曲线,而是每个深度点落在某个 GMM 分量的后验概率。计算方式很简单:对每条链每个样本,比较它在各高斯分量下的概率密度,取最大者作为该样本在该深度点的类别标签,再对样本方向求平均。
% post: thinning 后的样本矩阵,大小 nsample x nz logpdf_all = zeros(size(post,1), nz, 3); for k = 1:3 logpdf_all(:,:,k) = log(pik(k)) + ... log(normpdf(post, mu(k), sig(k))); end [~, class_label] = max(logpdf_all, [], 3); prob_reservoir = mean(class_label == 2, 1);这个prob_reservoir就是深度域储层概率曲线。如果想刻画“至少 5 米储层存在”的概率,把连续 5 个采样点同时标记为储层的事件做一次游程统计,比单独看每个深度点的概率更有工程价值。这个结果可以直接导入油藏数值模拟:从 thinning 后的样本里等间隔抽取 30 个模型,分别做流动模拟,比用单一 MAP 模型得到一个“假确定”的产量预测要有意义得多。最后检查样本残差是否为白噪声:把每个后验样本的模拟道与观测道相减,若残差存在系统波形,大概率是子波估计不准或 GMM 的峰位偏移,而不是噪声问题。
本文还有配套的精品资源,点击获取