☰
Python测井岩性识别与曲线回归:从数据预处理到模型部署实战
2026/10/2 3:13:12 网站建设 项目流程

简介:这份资源面向计算机、人工智能、自动化、电子信息等专业的高校学生与科研人员,围绕人工智能在石油测井领域的应用,提供Python岩性识别与测井曲线回归的完整课程设计项目。压缩包共246个文件,约174.51MB,以175个csv测井数据文件、28个ipynb实验笔记、23个xlsx表格为主,另含md说明、docx设计文档、py脚本及html页面,覆盖数据、代码与报告全流程。项目源码经严格测试,功能完善且稳定运行,易复现,可直接作为课程设计、毕业设计、作业或项目初期立项演示,也适合基础尚可者在此基础上修改扩展。已有56人学习下载。读者可获得完整的岩性识别与曲线回归实现思路、数据组织方式、实验记录与设计报告,便于快速理解测井数据处理与建模流程,并对照复现与二次开发。

1. 从一份课程设计压缩包说起:Python 岩性识别与测井曲线回归到底在做什么

如果你手上正好有一份「人工智能在石油测井应用之 Python 岩性识别与测井曲线回归」的课程设计资料包,打开后大概率会看到三类东西:一份 Word 报告、一堆测井曲线数据(LAS、CSV 或 Excel),以及若干 Python 脚本。很多人第一反应是「这不就是个交作业的压缩包吗」,但真正做过测井解释的人会告诉你,这套东西的内核其实是一条完整的工业级小流水线:把深度域上的多条测井曲线喂进模型,输出两类结果——离散的岩性标签和连续的储层参数曲线。

它解决的问题很具体。传统测井解释靠交会图版和人工经验,一口井几百米到几千米,逐层看曲线、划岩性、读孔隙度,效率低且不同解释员结论不一致。用 Python 把这件事半自动化,本质是用机器学习做两件事:分类(岩性识别,输出砂岩、泥岩、碳酸盐岩等标签)和回归(用测井响应反演孔隙度、渗透率、含水饱和度等连续曲线)。适合谁?石油工程、地质资源、勘查技术专业的学生做课程设计或大作业,也适合刚转行进石油 AI 方向的算法工程师拿它当第一个能跑通的实战项目。下面我按「数据怎么准备 → 分类怎么做 → 回归怎么做 → 坑在哪 → 怎么验证」的顺序,把这条线讲透。

2. 测井数据怎么读进来、对齐、清洗:LAS 与 CSV 的预处理流水线

2.1 先搞清楚测井曲线的物理含义再动手

测井曲线不是普通时间序列,它的横轴是深度(单位通常是米),纵轴是各种物理响应。常见的有自然伽马 GR(反映泥质含量,泥岩高、砂岩低)、深/浅电阻率 RT/RXO(反映流体和岩性)、密度 RHOB、中子孔隙度 NPHI、声波时差 DT。岩性识别的本质,就是这些曲线在不同岩性上的响应组合不同。比如砂岩通常 GR 低、DT 中等;泥岩 GR 高;碳酸盐岩密度大、中子低。

动手前必须做的一件事是深度对齐。不同测井仪器的采样间隔不一样,GR 可能是 0.125 米一个点,密度是 0.1 米,电阻率是 0.2 米。如果直接按行拼接,深度就错位了,模型学到的全是噪声。常见做法是以一条主曲线(一般选采样最密的)为基准深度,用插值把其他曲线重采样到同一深度网格上。

2.2 用 Python 读 LAS 并重采样到统一深度

LAS 是测井行业的标准格式,用lasio库读最省事。下面这段是我一般会用的预处理骨架:

import lasio import pandas as pd import numpy as np # 读取 LAS 文件,lasio 会自动解析曲线名和深度 las = lasio.read("well_A.las") df = las.df() # 转成 DataFrame,index 是深度 df = df.reset_index().rename(columns={"DEPT": "depth"}) # 只保留建模需要的曲线,缺失的曲线名按实际文件调整 cols = ["depth", "GR", "RT", "RHOB", "NPHI", "DT"] df = df[[c for c in cols if c in df.columns]] # 以 0.1 米为统一深度网格重采样 target_depth = np.arange(df["depth"].min(), df["depth"].max(), 0.1) df = df.set_index("depth").reindex(target_depth) df = df.interpolate(method="linear", limit=5) # 最多连续插 5 个点 df = df.dropna() # 插不上的整段丢掉 df.to_csv("well_A_clean.csv", index_label="depth")

逻辑说明:las.df()把曲线转成以深度为索引的表格;reindex到统一网格后,原本没有采样的深度会变成 NaN;interpolate做线性插值,limit=5是关键参数——它限制连续插值的最大点数,防止在仪器故障导致的大段缺失上凭空造数据。参数怎么改:如果曲线质量好、缺失少,limit可以放到 10;如果数据本身噪声大,建议降到 3 甚至直接dropna。

2.3 异常值处理与归一化:别让一条坏曲线毁掉整个模型

测井曲线里常见的异常是井径突变、仪器刻度错误导致的尖峰。处理方式不是简单删掉,而是先看分布。我一般会画箱线图,把超过 3 倍四分位距的点标记出来,再决定是截断还是置 NaN。

def clip_outliers(series, k=3): q1, q3 = series.quantile([0.25, 0.75]) iqr = q3 - q1 lower, upper = q1 - k * iqr, q3 + k * iqr return series.clip(lower, upper) for col in ["GR", "RT", "RHOB", "NPHI", "DT"]: df[col] = clip_outliers(df[col])

归一化方面,GR、DT 这类量纲差异大的曲线必须做标准化。注意:归一化参数只能用训练集统计,验证集和测试集要用训练集的均值和方差来变换,否则就是数据泄漏。这是很多人翻车的地方,报告里模型指标好看,实际换一口井就崩。

3. 岩性识别:用随机森林和 XGBoost 做分类,标签怎么来

3.1 岩性标签从哪来:岩心标定与专家解释

分类任务最大的门槛不是模型,是标签。测井曲线本身没有岩性标签,标签来自岩心描述或专家解释成果。课程设计里通常会给一份「解释结论表」,里面按深度段标注了岩性,比如 2000-2005 米是砂岩,2005-2012 米是泥岩。你要做的是把这种段状标签展开成逐深度的点标签。

def expand_labels(df, label_table): # label_table 每行: top, bottom, lith df["lith"] = "unknown" for _, row in label_table.iterrows(): mask = (df["depth"] >= row["top"]) & (df["depth"] < row["bottom"]) df.loc[mask, "lith"] = row["lith"] return df[df["lith"] != "unknown"]

逻辑说明:按深度区间把标签打到每个采样点上。参数注意:区间边界用左闭右开,避免相邻段重叠;如果标签表里岩性名称不统一(比如「细砂岩」和「砂岩」),要先做一次映射归并,否则类别数会虚高。

3.2 特征工程:光有原始曲线不够,加比值和交会特征

原始曲线直接喂模型也能跑,但加几个衍生特征通常能涨几个点。常用的是曲线比值和差值,比如 GR 与 DT 的比值能区分砂泥岩,RHOB 与 NPHI 的差值能指示流体性质。

df["GR_DT"] = df["GR"] / (df["DT"] + 1e-6) df["RHOB_NPHI"] = df["RHOB"] - df["NPHI"] df["RT_GR"] = df["RT"] / (df["GR"] + 1e-6)

加 1e-6 是防止除零。这些特征不是越多越好,我一般控制在原始曲线数量的 1.5 倍以内,太多会引入噪声和过拟合。

3.3 训练分类模型并看混淆矩阵

from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report, confusion_matrix features = ["GR", "RT", "RHOB", "NPHI", "DT", "GR_DT", "RHOB_NPHI", "RT_GR"] X = df[features] y = df["lith"] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, stratify=y, random_state=42 ) clf = RandomForestClassifier( n_estimators=300, max_depth=12, min_samples_leaf=5, random_state=42 ) clf.fit(X_train, y_train) y_pred = clf.predict(X_test) print(confusion_matrix(y_test, y_pred)) print(classification_report(y_test, y_pred))

参数说明:n_estimators=300是树的数量,测井数据量通常几千到几万点,300 够用;max_depth=12控制树深,太深会记住噪声;min_samples_leaf=5保证每个叶子至少 5 个样本,防止对薄层过拟合。stratify=y很重要,岩性类别往往不均衡(泥岩可能占 60%),分层抽样保证训练测试集类别比例一致。

看结果时重点看混淆矩阵里砂岩和泥岩有没有互相混。如果混得厉害,多半是 GR 特征区分度不够,或者标签本身在过渡带就是模糊的。这时候别急着换模型,先回去检查标签质量。

4. 测井曲线回归:用孔隙度做例子,把连续值预测讲清楚

4.1 回归目标和分类的本质区别

岩性识别输出离散标签,回归输出连续曲线,比如孔隙度 POR、渗透率 PERM、含水饱和度 SW。回归的难点在于:目标值本身也是从测井或岩心实验得到的,存在测量误差;而且连续值对异常点非常敏感,一个错误的岩心标定值能把整条回归线拉偏。

我一般先用孔隙度练手,因为它和密度、中子、声波的关系相对明确,物理上有经验公式支撑,模型结果好验证。

4.2 用梯度提升回归树建模

from sklearn.ensemble import GradientBoostingRegressor from sklearn.metrics import mean_squared_error, r2_score target = "POR" X = df[features] y = df[target] X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42 ) reg = GradientBoostingRegressor( n_estimators=400, learning_rate=0.05, max_depth=4, subsample=0.8, random_state=42 ) reg.fit(X_train, y_train) y_pred = reg.predict(X_test) rmse = mean_squared_error(y_test, y_pred, squared=False) print(f"RMSE: {rmse:.4f}, R2: {r2_score(y_test, y_pred):.4f}")

参数说明:learning_rate=0.05配合n_estimators=400是典型的慢学习率多树组合,比学习率 0.1 配 200 棵树更稳;max_depth=4对回归来说已经够深,再深容易过拟合;subsample=0.8表示每棵树用 80% 样本训练,引入随机性提升泛化。RMSE 的单位和孔隙度一致,如果孔隙度是小数(0-0.3),RMSE 在 0.02 以内算不错;如果是百分数(0-30),RMSE 在 2 以内可以接受。

4.3 回归结果怎么验证:交会图和深度道对比

光看 RMSE 不够,一定要画两张图。第一张是预测值 vs 实测值的交会图,点应该沿 45 度线分布;第二张是按深度把预测曲线和实测曲线叠在一起,看趋势是否一致。

import matplotlib.pyplot as plt fig, axes = plt.subplots(1, 2, figsize=(12, 5)) axes[0].scatter(y_test, y_pred, s=5, alpha=0.5) axes[0].plot([y.min(), y.max()], [y.min(), y.max()], "r--") axes[0].set_xlabel("Measured POR") axes[0].set_ylabel("Predicted POR") axes[1].plot(y_test.values, df.loc[y_test.index, "depth"], label="Measured") axes[1].plot(y_pred, df.loc[y_test.index, "depth"], label="Predicted") axes[1].invert_yaxis() axes[1].legend() plt.tight_layout() plt.show()

如果交会图在高值区发散,说明模型对大孔隙度预测偏保守,常见原因是高值样本少。解决办法不是调模型,而是检查是否需要按岩性分组建模——砂岩和碳酸盐岩的孔隙度响应机制不同,混在一起学必然互相干扰。

5. 避坑与排查:做这个课程设计最容易翻车的 5 个地方

5.1 深度错位导致模型学到假相关

现象:模型在训练集上准确率 95%,换一口井掉到 60%。原因:不同曲线深度没对齐,或者重采样时用了错误的基准。解决:预处理后一定画一张多曲线深度道图,肉眼确认 GR 的峰值和 RT 的峰值在同一个深度上。这一步花五分钟,能省后面几小时的排查。

5.2 标签泄漏:用测试集统计量做归一化

现象:报告里 R2 高达 0.95,实际应用完全不能用。原因:归一化时用了全量数据的均值和方差,测试集信息泄漏到训练过程。解决:把所有预处理(归一化、异常值截断)封装成 sklearn 的 Pipeline,只在训练集上 fit,再 transform 测试集。

5.3 类别不均衡导致薄层岩性全被吞掉

现象:混淆矩阵里薄层砂岩全被预测成泥岩。原因:泥岩样本占 70% 以上,模型倾向于预测多数类。解决:用class_weight="balanced"让模型对少数类加权,或者对少数类做 SMOTE 过采样。但要注意,测井数据是深度序列,过采样不能简单随机插值,最好按深度段整体复制。

5.4 用随机划分做时序数据的交叉验证

现象:交叉验证分数很高,但实际按深度顺序预测时效果差。原因:测井数据有深度自相关性,相邻深度点高度相似,随机划分会让训练集和测试集共享相邻点,造成乐观偏差。解决:用GroupKFold按井或按深度段分组,保证同一口井或同一段不跨训练测试集。

5.5 忽略曲线单位导致量纲混乱

现象:模型训练不收敛或预测值离谱。原因:不同 LAS 文件的 GR 单位可能是 API 也可能是计数率,RT 可能是欧姆米也可能是对数刻度。解决:读入后先打印每条曲线的统计量(min、max、mean),和行业常识对照。GR 正常范围 0-200 API,如果看到 0-5000,多半是单位不对,需要先做刻度转换。

6. 把模型用起来:单井预测、批量跑井与结果落盘

6.1 封装成可复用的预测函数

训练完模型只是第一步,课程设计报告里通常要求对未知井做预测。我一般会把预处理和预测封装成一个函数,输入 LAS 路径,输出带预测岩性和孔隙度的 CSV。

def predict_well(las_path, clf, reg, feature_cols): las = lasio.read(las_path) df = las.df().reset_index().rename(columns={"DEPT": "depth"}) df = df[[c for c in ["depth"] + feature_cols if c in df.columns]] df = df.set_index("depth").reindex( np.arange(df["depth"].min(), df["depth"].max(), 0.1) ).interpolate(limit=5).dropna() df["GR_DT"] = df["GR"] / (df["DT"] + 1e-6) df["RHOB_NPHI"] = df["RHOB"] - df["NPHI"] df["RT_GR"] = df["RT"] / (df["GR"] + 1e-6) X = df[feature_cols] df["pred_lith"] = clf.predict(X) df["pred_POR"] = reg.predict(X) return df

逻辑说明:这个函数把第 2 章的预处理和第 3、4 章的模型串起来,输入输出都是文件,方便批量调用。参数注意:feature_cols必须和训练时完全一致,顺序都不能变,否则模型会报错或给出错误结果。

6.2 批量跑多口井并汇总

import glob results = [] for path in glob.glob("wells/*.las"): df = predict_well(path, clf, reg, features) df["well"] = path.split("/")[-1].replace(".las", "") results.append(df) all_pred = pd.concat(results) all_pred.to_csv("all_wells_prediction.csv", index_label="depth")

这样一份包含所有井预测结果的 CSV 就出来了,可以直接导入 Petrel 或其他解释软件做可视化。报告里可以放一张多井连井剖面图,展示岩性预测的横向连续性,这是加分项。

6.3 一个我踩过的坑:模型版本和特征顺序

最后说一个血泪经验。我曾经把训练好的模型用 joblib 存下来,过了一个月换台机器加载,预测结果全乱。排查半天发现是 sklearn 版本不一致,导致模型内部结构解析出错。后来我养成的习惯是:模型文件、特征列表、归一化参数、sklearn 版本号,四样东西必须一起存、一起加载。特征顺序也一样,训练时是["GR", "RT", "RHOB"],预测时写成["RT", "GR", "RHOB"],模型不会报错,但结果会悄悄变差,这种玄学问题最难查。

如果你正准备做这个课程设计,我的建议是先把第 2 章的预处理跑通,画图确认深度对齐,再往下走。模型选随机森林还是 XGBoost 差别不大,数据质量才是决定成败的那一环。希望帮到你。

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

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

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

立即咨询