简介:面向石油工程从业者、数据科学家以及机器学习与深度学习领域的研究者,资源聚焦测井曲线预测、岩性识别、自动化测井、测井解释与储层评价等关键任务,适用于油气勘探、储层品质评估及时间序列分析等实际场景。压缩包共218个文件,以175个csv数据集和23个xlsx表格为主,辅以2个ipynb笔记本、py脚本及文档说明,整体约168.89MB,csv/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, step=0.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(method="linear").reset_index() df_rs.columns = ["DEPTH"] + list(df.columns) return df_rs.dropna()reindex之后所有缺失位置变成NaN,interpolate做线性填充,最后dropna把顶底两端的无效区切掉。这一步是测井数据预处理最核心的“网格统一”。曲线补齐要克制:如果某口井缺失密度,可以先尝试用同井其他曲线回归,但缺失段太长或整口井缺一项,直接剔除该井训练数据,不要硬造。老井缺曲线是常态,硬补出来的曲线在储层评价里会变成系统性误差,后面模型再强也救不回来。
2.3 测井曲线标准化:井间可比的底线,不是可选项
标准化是岩性识别从“单井能看”到“工区能用”的分水岭。同一套泥岩层,A井GR测出来105 API,B井却只有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_quantiles=1000): """把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_size=41, stride=5): 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时样本数量骤减,适合井段长、标注稀疏的情况;stride=1时每个深度点都成为中心样本,数据量最大但相邻样本严重重叠,训练变慢且验证指标虚高。我一般先按stride=5跑基线,模型能收敛再把stride降到1做最终版本。
注意:井段首尾不足一个窗口的样本要直接剔除,不要用零填充。零填充会引入虚假的曲线突变,模型学到的是“边界=岩性变化”,而不是真实地层响应。
3.2 测井数据的数据增强:加噪、幅值扰动与缺测模拟
测井数据的数据增强不能照搬图像那一套,不能翻转、裁剪、旋转。深度方向翻转等于把地层序列倒过来,地质上就是错的;随机裁剪会破坏岩性组合的上下文,丢掉增强的意义。合理的增强围绕仪器噪声和井眼环境影响做:
def augment_curve(curve, noise_std=0.02, scale_std=0.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, center=True).mean() out[f"{c}_std_{w}"] = out[c].rolling(w, center=True).std() return out.fillna(method="bfill").fillna(method="ffill")窗口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_size=0.2, stratify=y) model = xgb.XGBClassifier( max_depth=6, learning_rate=0.05, n_estimators=500, subsample=0.8, colsample_bytree=0.8, eval_metric="mlogloss", ) model.fit(X_train, y_train, eval_set=[(X_val, y_val)], early_stopping_rounds=20, verbose=False) print(classification_report(y_val, model.predict(X_val)))参数说明:max_depth=6在十几维特征下足够拟合非线性关系,再深就是过拟合;learning_rate=0.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_size=7, padding=3), nn.ReLU(), nn.MaxPool1d(2), nn.Conv1d(64, 128, kernel_size=5, padding=2), 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_size=7和5分别对应0.9米和0.6米的视野,两层叠加后感受野大约1.5米,和“看上下1米”的地质直觉一致。训练循环用标准的交叉熵加Adam:
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3) loss_fn = nn.CrossEntropyLoss(weight=class_weight) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.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", classes=np.unique(y_train), y=y_train) class_weight = torch.tensor(class_weight, dtype=torch.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, axis=1) print(classification_report(y_blind, y_pred, target_names=rock_classes))推理环节有个小技巧:预测新井时不要只喂目标深度段的局部窗口,把整口井的曲线按步长1滑一遍再取每个深度的平均概率。这个操作等价于一个轻量级的深度上下文集成——窗口中心同一个深度点会出现多次,每次看到的上下文略有不同,平均概率之后边界段的预测会稳定很多。画储层评价成果图时,把预测岩性剖面和预测的孔隙度曲线叠在一张图上,砂体厚度、有效孔隙度范围、夹层分布一眼就能读出来,这就是测井解释和储层评价的直接落点。
我第一次做这个方向时,犯过最典型的错误:拿ResNet当图像分类来做,模型结构堆得很重,结果数据量撑不起来,一口井都解释不动。后来回头把测井曲线标准化和深度对齐做扎实,换回XGBoost,效果反而先追平了人工解释的八成水平。这个领域真正的门槛不在网络结构,而在于数据管线的纪律性——标准化、对齐、分层验证,每一步都老老实实做,模型自然会回报你。希望帮到你。
本文还有配套的精品资源,点击获取