ARTICLE DETAIL

资讯详情

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

机器学习驱动测井岩性识别:从数据预处理到模型实战

机器学习驱动测井岩性识别:从数据预处理到模型实战 简介面向石油工程从业者、数据科学家以及机器学习与深度学习领域的研究者资源聚焦测井曲线预测、岩性识别、自动化测井、测井解释与储层评价等关键任务适用于油气勘探、储层品质评估及时间序列分析等实际场景。压缩包共218个文件以175个csv数据集和23个xlsx表格为主辅以2个ipynb笔记本、py脚本及文档说明整体约168.89MBcsv/xlsx承载测井原始数据与特征表ipynb/py覆盖从数据预处理、特征工程、模型训练到测井解释的完整流程docx、md、txt文档可辅助理解数据处理思路与实验背景png图示便于直观查看曲线结构目录模块清晰。已有197人学习下载。资料包含测井曲线标准化、数据增强、时间序列分析等实用代码示例并附开源数据集与模型训练脚本可直接复用于油气田数据分析和深度学习实验也可作为教学与科研的基础素材帮助读者从数据处理到储层预测建立完整技术链路。1. 一口井的人工解释要一两天模型十分钟自动化测井岩性识别到底在做什么测井曲线是油气勘探里最便宜也最硬核的地层信息源——一口探井打完井下仪器拉上来一串随深度变化的物理曲线自然伽马、电阻率、密度、中子孔隙度、声波时差。地质工程师对着这些曲线把地层一段段划成砂岩、泥岩、粉砂岩、灰岩再和录井岩屑互相印证这就是岩性识别。传统做法靠人眼和经验一口井解释下来大半天算快的遇到复杂岩性要反复比对邻井和成像资料拖一两天很正常。机器学习/深度学习做的事情就是把这一过程自动化数据预处理整净值特征工程把地层上下文的形态信息暴露给模型模型训练让分类器学会“曲线长这样大概率是砂岩”。配上测井曲线预测比如用已有曲线回归缺失的孔隙度曲线整条管线就同时覆盖岩性识别、测井解释和储层评价的输入准备。这篇笔记写给两类人手里有LAS井库但靠人工划线想提效的测井工程师以及想进入这个方向的算法工程师——按我的经验入门最大的坎不是模型而是把测井数据的物理意义和时序特性想明白。2. 先把数据洗干净开源数据集选型、LAS读取与测井曲线标准化2.1 从LAS文件开始用lasio把一口井拆成一张深度-曲线表LAS是测井行业的通用交换格式结构上分版本信息、曲线定义和ASCII数据三块。Python里读LAS最省事的是lasio库一条命令就能把一口井的曲线读进来转成DataFrame后深度变成索引列名是曲线助记名。开源的测井数据渠道不少常见的有几个方向地质调查局公开的LAS井库比如Kansas Geological Survey的老井数据、勘探地球物理学会在机器学习竞赛里放出的测井数据集、以及Volve油田公开数据集。选数据先确认两件事——有没有岩性标签、曲线是否齐全。很多公开LAS只有曲线没有标签没有标签就只能做曲线预测回归做不了岩性识别分类。import lasio import pandas as pd las lasio.read(well_A.las) df las.df().rename_axis(DEPTH).reset_index() # 深度列统一命名为 DEPTH df.columns [c.upper() for c in df.columns] # GR/RT/DEN/CNL/AC 全转大写 print(df.head())las.df()返回的index是深度rename_axis(DEPTH)把深度索引命名成显式列reset_index()后得到“深度各曲线”的宽表。列名统一大写是为了后面做井间合并时不被大小写坑到——实际井库里同一类曲线可能有GR、GRC、GR_RT三种写法这一步只对齐了一小步。具体到工区我一般会再准备一个别名映射表ALIAS { GR: [GR, GRC, GR_COM, SGR], RT: [RT, ILD, LLD, RDEEP], DEN: [DEN, RHOB, ZDEN], CNL: [CNL, NPHI, CNC], AC: [AC, DT, SONIC], } for col in list(df.columns): for standard, variants in ALIAS.items(): if col in variants and standard not in df.columns: df[standard] df[col]这段别名逻辑放在读入之后做能消化大部分历史井的命名混乱。注意先判断目标列是否已存在避免把已有标准列覆盖掉。实际项目里这一小步能省掉后面大量的数据对齐时间。2.2 深度对齐与曲线补齐自动化测井的第一道数据门槛不同仪器组合的采样率不一样有的曲线0.1米一个点有的0.1524米一个点合并到同一口井的时候各曲线深度网格天然不重合。深度学习模型要求输入是等间距的向量所以必须先做深度重采样。常见做法是取统一网格通常用0.125米或0.1524米然后用线性插值把每条曲线搬上去。为什么不用更高采样率因为测井仪器的垂向分辨率就是分米级别插值不会带来新信息只会把插值噪声喂给模型。import numpy as np def resample_to_grid(df, step0.125): depth_min np.floor(df[DEPTH].min() / step) * step depth_max np.ceil(df[DEPTH].max() / step) * step grid np.arange(depth_min, depth_max step, step) df_rs df.set_index(DEPTH).reindex(grid) df_rs df_rs.interpolate(methodlinear).reset_index() df_rs.columns [DEPTH] list(df.columns) return df_rs.dropna()reindex之后所有缺失位置变成NaNinterpolate做线性填充最后dropna把顶底两端的无效区切掉。这一步是测井数据预处理最核心的“网格统一”。曲线补齐要克制如果某口井缺失密度可以先尝试用同井其他曲线回归但缺失段太长或整口井缺一项直接剔除该井训练数据不要硬造。老井缺曲线是常态硬补出来的曲线在储层评价里会变成系统性误差后面模型再强也救不回来。2.3 测井曲线标准化井间可比的底线不是可选项标准化是岩性识别从“单井能看”到“工区能用”的分水岭。同一套泥岩层A井GR测出来105 APIB井却只有85 API原因可能是仪器刻度不同、井眼环境不同、测井年代不同。模型如果直接吃原始读数会把刻度差异当成岩性差异学到权重里。这是多井建模最容易被忽略的细节也是很多人换了邻井就翻车的根子。缓解的办法从轻到重分三档按井做z-score、工区标准层均值匹配、分位数匹配。方法适用场景局限按井z-score单井建模、快速基线抹掉绝对幅度跨井可比性一般标准层均值匹配工区有稳定标志层标准层选取依赖地质经验分位数匹配多井、无明确标准层对分布形态敏感离散曲线会有台阶def zscore_by_well(df, cols): out df.copy() for c in cols: out[c] (out[c] - out[c].mean()) / (out[c].std() 1e-6) return out def quantile_match(source, reference, n_quantiles1000): 把source曲线的分布对齐到reference曲线的分布上 q np.linspace(0, 1, n_quantiles) src_q np.quantile(source, q) ref_q np.quantile(reference, q) return np.interp(source, src_q, ref_q)注意z-score只适合单井建模或模型只吃相对趋势的场景工区级别建模优先选标准层分位数匹配。选标准层时找工区内分布稳定的泥岩或膏岩层把每口井该层位的GR/电阻率均值对齐到标准井再对整个井段做分位数映射。分位数匹配的思路是把source曲线的每个分位数值替换成reference曲线相同分位数的值效果是两条曲线分布形态相似、绝对数值可比。对岩性识别这种分类任务分布可比通常够用。n_quantiles取1000已经覆盖大部分曲线形态如果曲线离散化严重比如某些老井只记整数建议先对数据加极小噪声或把分位数量调小否则np.interp容易在平直段产生台阶状映射。3. 特征工程与数据增强把测井曲线当成时间序列来构造样本3.1 滑窗切样本岩性识别模型要看上下两米而不是单点测井曲线上单点几乎不能说明岩性——密度2.3和2.6之间本来就模糊真正给解释工程师信心的是曲线的组合形态砂岩段GR低平、电阻率相对高、密度中子有交会泥岩段GR高、形态平稳岩性变化位置往往伴随明显的曲线突变和过渡带。深度学习模型要学的是这种“上下文”所以输入不能是逐点特征而是按深度滑窗。窗口大小怎么定我的经验是取上下各1到2米也就是窗口总长3到5米。太小模型看不到沉积旋回太大把上下多套岩性全混进来学习信号被稀释。如果采样间隔0.125米一个5米窗口是41个点一个3米窗口是25个点这个尺寸对一维卷积网络是合适的输入长度。def make_windows(features, labels, window_size41, stride5): X, y [], [] n len(features) for i in range(0, n - window_size 1, stride): X.append(features[i:i window_size]) y.append(labels[i window_size // 2]) return np.array(X), np.array(y)stride大于1时样本数量骤减适合井段长、标注稀疏的情况stride1时每个深度点都成为中心样本数据量最大但相邻样本严重重叠训练变慢且验证指标虚高。我一般先按stride5跑基线模型能收敛再把stride降到1做最终版本。注意井段首尾不足一个窗口的样本要直接剔除不要用零填充。零填充会引入虚假的曲线突变模型学到的是“边界岩性变化”而不是真实地层响应。3.2 测井数据的数据增强加噪、幅值扰动与缺测模拟测井数据的数据增强不能照搬图像那一套不能翻转、裁剪、旋转。深度方向翻转等于把地层序列倒过来地质上就是错的随机裁剪会破坏岩性组合的上下文丢掉增强的意义。合理的增强围绕仪器噪声和井眼环境影响做def augment_curve(curve, noise_std0.02, scale_std0.05): noise np.random.normal(0, noise_std, curve.shape) scale np.random.normal(1.0, scale_std) return curve * scale noise加噪是模拟仪器随机抖动幅值扰动是模拟同一套地层在不同井的刻度残留差异此外还可以随机把某一段曲线置为NaN再插值模拟缺测井段的恢复效果。这三个操作都不会改变地层的上下叠置关系属于测井这个时序场景里安全的增强方式。增强的幅度要控制noise_std超过0.05之后模型在训练集上的loss下不去反而把细小的岩性边界磨平。数据增强在这里的作用不是凭空扩量而是提升模型对仪器噪声和缺测的鲁棒性。3.3 手工特征的增量差分、滑动统计与多尺度聚合传统机器学习模型XGBoost、随机森林吃不了滑窗三维输入要么把窗口展平成window_size * feature的高维向量要么用滚动统计构成二维特征表。展平方式在高窗口尺寸下会引入大量冗余维度我习惯先用手工特征跑基线模型收敛快、可解释性也强。def add_handcrafted_features(df, cols, windows(5, 15, 31)): out df.copy() for c in cols: out[f{c}_diff] out[c].diff() for w in windows: out[f{c}_ma_{w}] out[c].rolling(w, centerTrue).mean() out[f{c}_std_{w}] out[c].rolling(w, centerTrue).std() return out.fillna(methodbfill).fillna(methodffill)窗口5/15/31在0.125米采样下分别对应0.6米、1.9米、3.9米的垂向尺度。diff描述曲线变化率ma描述背景趋势std描述局部复杂程度三个统计量组合起来模型能区分“低GR平稳的厚层砂岩”和“低GR但快速抖动的薄互层砂岩”。这个特征组合在XGBoost上的提升往往比换模型更大。手工特征和深度学习也不是对立关系——把diff、ma、std作为额外输入通道和原始曲线一起送进CNN在测井通道数本来就少通常五六个的情况下通常还能再涨两三个点F1。4. 模型训练用XGBoost先打基线再用一维卷积做深度学习路线4.1 XGBoost基线小数据集上先把F1拉到及格线测井岩性数据集按井来看规模并不大一口井几千到几万个深度点折算成互不重叠的有效样本只有一两千条。这个量级上深度学习没有优势XGBoost/LightGBM这类表格模型反而更稳。先把基线跑出来后面深度学习模型的好坏才有比较基准。import xgboost as xgb from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_val, y_train, y_val train_test_split(X, y, test_size0.2, stratifyy) model xgb.XGBClassifier( max_depth6, learning_rate0.05, n_estimators500, subsample0.8, colsample_bytree0.8, eval_metricmlogloss, ) model.fit(X_train, y_train, eval_set[(X_val, y_val)], early_stopping_rounds20, verboseFalse) print(classification_report(y_val, model.predict(X_val)))参数说明max_depth6在十几维特征下足够拟合非线性关系再深就是过拟合learning_rate0.05配合500棵树在几千样本上正好收敛subsample和colsample_bytree都取0.8防止树模型记住单井的特殊刻度。评估用classification_report而不是accuracy原因在第5章讲。测井曲线预测里的回归任务比如用已有曲线预测缺失的孔隙度曲线把XGBClassifier换成XGBRegressor、eval_metric换成rmse即可和岩性识别共用同一套特征表。4.2 一维卷积路线让模型在深度方向上滑着学深度学习路线上我首选一维卷积而不是LSTM。测井曲线的序列依赖是局部的——判断当前深度是砂岩还是泥岩主要看上下几米的形态不需要记住整口井几百米的长期状态。一维卷积用卷积核沿深度方向滑动天然匹配这种局部上下文训练比LSTM稳定不容易遇到梯度消失。RNN和Transformer不是不能用而是对这个任务来说用重武器打轻目标。import torch import torch.nn as nn class CNN1D(nn.Module): def __init__(self, n_features, n_classes): super().__init__() self.conv nn.Sequential( nn.Conv1d(n_features, 64, kernel_size7, padding3), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(64, 128, kernel_size5, padding2), nn.ReLU(), nn.AdaptiveAvgPool1d(1), ) self.fc nn.Linear(128, n_classes) def forward(self, x): # 输入形状 (batch, window, feature)Conv1d 期望 (batch, feature, window) x x.permute(0, 2, 1) x self.conv(x).flatten(1) return self.fc(x)AdaptiveAvgPool1d是这里的关键它把第二个卷积输出的任意长度压成1个点避免全连接层输入维度随窗口大小变化。kernel_size7和5分别对应0.9米和0.6米的视野两层叠加后感受野大约1.5米和“看上下1米”的地质直觉一致。训练循环用标准的交叉熵加Adamoptimizer torch.optim.Adam(model.parameters(), lr1e-3) loss_fn nn.CrossEntropyLoss(weightclass_weight) scheduler torch.optim.lr_scheduler.StepLR(optimizer, step_size10, gamma0.5) for epoch in range(40): for xb, yb in dataloader: out model(xb) loss loss_fn(out, yb) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step()每个epoch结束在验证集上计算宏平均F1连续5轮不涨就提前停止。学习率从1e-3开始StepLR在epoch 10和20各衰减一半比恒定学习率更稳。如果发现验证集F1上不去先别调网络结构回头检查第2章的标准化和深度对齐是否做透——这个领域里数据管线的锅远大于模型结构的锅。4.3 训练参数细节类别不均衡、批次尺寸与过拟合判断岩性数据里类别不均衡是常态一口井泥岩占60%到80%砂岩、粉砂岩、灰岩加起来才有话语权。如果直接拿原始分布训练模型倾向于把所有点预测成泥岩因为这样损失最小。处理办法是用类别权重让少数类样本的梯度贡献放大。sklearn提供现成工具from sklearn.utils.class_weight import compute_class_weight import numpy as np class_weight compute_class_weight(balanced, classesnp.unique(y_train), yy_train) class_weight torch.tensor(class_weight, dtypetorch.float32)如果类别特别多且少数类样本太少可以在采样阶段按深度段限制每类的样本量再做增强。batch_size我一般取64窗口长度41时每batch数据量不大显存不是瓶颈。过拟合主要通过验证集F1和训练集loss的差距判断——测井样本重叠严重训练loss低到0.01不能说明任何问题只有验证集在按井划分的情况下还能保持稳定模型才算真正学到地质信号。5. 测井岩性识别训练避坑记录四个最容易翻车的坑5.1 井间验证失败标准化没做透模型只记住了单井刻度现象在训练井上测试F1宏平均0.85模型切到邻井直接掉到0.5错分样本集中表现为GR刻度偏移。原因训练井和测试井的仪器刻度、井眼环境不同模型学到的不是“泥岩段GR高”而是“GR大于100就算泥岩”这种和具体井绑定的规则。解决先做第2章的标准化标准层加分位数匹配划分数据集时按井分组确保同一口井的数据不会同时出现在训练集和验证集——随机切分会让模型见过同一口井的上下邻段验证分数虚高换井就现原形。遇到换井掉点先把训练井和邻井同一曲线的直方图叠在一起看均值偏移明显就说明标准化没做到位先别急着调模型。5.2 标签噪声录井解释标签不是真实分布现象模型在某一类上的F1特别低把预测结果画到深度剖面上肉眼觉得模型分得合理但和录井剖面对不上。原因标签是录井岩屑定名和人工曲线解释的结果岩屑上返有滞后人工解释带主观性深度上相差一两米很常见——标签本身就不是精确答案是带着噪声的近似答案。解决训练前做标签清洗把岩性变化点两侧0.5米过渡带的样本从训练集剔除只保留主体井段预测时输出概率而不是硬分类让解释工程师优先复核概率在0.4到0.7之间的井段训练损失用标签平滑给硬标签留一点容错空间。这个领域的标签噪声是常态不是异常。5.3 类别不平衡泥岩占大头准确率会骗人现象训练日志里accuracy 92%混淆矩阵打开一看砂岩类召回率只有0.2几乎所有样本都被判成泥岩。原因泥岩占比太高模型发现全猜泥岩就能拿低损失。1000个点里泥岩占800个全猜泥岩准确率80%但砂岩、灰岩的F1全是0——这个准确率在甲方汇报里没有意义。解决用compute_class_weight或Focal Loss评估指标改成各类别的precision/recall/F1和宏平均F1不接受只看accuracy的报告如果类别多且少数类样本太少先按深度段限制每类样本量再做增强。岩性识别里砂岩、粉砂岩、灰岩三类往往都不到20%占比类别权重在多数工区都是必调参数。5.4 深度不对齐电缆深度和钻具深度差几米一切白搭现象预测剖面轮廓跟录井剖面很像但整体错位1到3米错位量还不稳定。原因测井记录的是电缆深度录井记录的是钻具深度或返屑时间换算深度两者系统性偏差是行业常态多矿物仪器组合里不同传感器物理位置不同也会造成曲线间亚米级错位。解决训练前做深度匹配。选择GR曲线固定一口井的标志层把另一口井的GR前后平移0到10个采样点计算相关系数取最大相关系数对应的偏移量做整体搬移也可以选多个标志层分井段校正。这一步不做后面标准化、特征工程全在错位数据上做模型能力再强也是白搭。我见过有人在这上面调了两周模型没效果最后发现是两条曲线的深度基准差了2米。6. 把模型用到新井盲井测试协议与预测后的储层评价落点工区里真正让甲方信服的验证方式只有一种盲井测试。选一口从来没参与过训练和验证的井走完标准化、深度对齐、特征构建的完整流程让模型输出整口井的岩性剖面再拿录井解释剖面做逐段对比。这一步同时检验了数据管线、特征工程和模型泛化能力比在训练井里报交叉验证分数有说服力得多。X_blind, y_blind build_features(well_C) # 复用同一套预处理管线 y_prob model.predict_proba(X_blind) # 输出概率不要只输出硬分类 y_pred np.argmax(y_prob, axis1) print(classification_report(y_blind, y_pred, target_namesrock_classes))推理环节有个小技巧预测新井时不要只喂目标深度段的局部窗口把整口井的曲线按步长1滑一遍再取每个深度的平均概率。这个操作等价于一个轻量级的深度上下文集成——窗口中心同一个深度点会出现多次每次看到的上下文略有不同平均概率之后边界段的预测会稳定很多。画储层评价成果图时把预测岩性剖面和预测的孔隙度曲线叠在一张图上砂体厚度、有效孔隙度范围、夹层分布一眼就能读出来这就是测井解释和储层评价的直接落点。我第一次做这个方向时犯过最典型的错误拿ResNet当图像分类来做模型结构堆得很重结果数据量撑不起来一口井都解释不动。后来回头把测井曲线标准化和深度对齐做扎实换回XGBoost效果反而先追平了人工解释的八成水平。这个领域真正的门槛不在网络结构而在于数据管线的纪律性——标准化、对齐、分层验证每一步都老老实实做模型自然会回报你。希望帮到你。本文还有配套的精品资源点击获取
返回列表