基于机器学习的地下储层岩性识别系统开发与实现:从测井曲线到岩性剖面的完整流程

发布时间:2026/10/10 1:10:23
基于机器学习的地下储层岩性识别系统开发与实现:从测井曲线到岩性剖面的完整流程
简介这是一套面向高校学生与初学者的机器学习实战项目聚焦地下储层岩性识别与分类可用于毕业设计、课程综合项目或期末考核。系统以人工智能算法为核心从复杂地质数据中提取特征并建立预测模型实现岩层类型的自动化判别代码注释详尽便于理解与二次开发。资源包共259个文件约169.38MB以175个csv数据文件为主涵盖训练集、未归一化数据及预处理完成的多地区岩层样本另含23个xlsx表格、42个zbak备份、2个ipynb笔记本、2个docx技术文档及1个py主程序配套数据集与说明文档齐全。目前已有86人学习下载。读者可获得可运行的完整源码、多来源训练数据、数据预处理与归一化对照文件以及模型训练验证的完整流程既能快速搭建岩性识别环境也能借此理解机器学习在地质勘探中的落地思路适合作为入门实践与课题研究的参考。1. 岩性识别这套系统为什么我建议你先跑通再谈调参地下储层岩性识别说白了就是拿测井曲线去判断井筒穿过的那段地层到底是砂岩、泥岩还是碳酸盐岩。传统做法靠地质人员对着 GR、RT、DEN、CNL 几条曲线人工划段一口井几百米下来眼睛都花了不同人解释结果还不一样。这套「基于机器学习方法的地下储层岩性识别系统开发与实现」的毕业设计源码干的就是把这段人工经验固化成可复现的模型流程读测井数据、做特征工程、训练分类器、输出岩性剖面再配一个能点按钮看结果的界面。它适合正在做石油地质、测井解释方向毕设的学生也适合想拿一个完整机器学习项目练手的 Python 开发者。我拿到包之后第一件事不是看代码而是先把数据跑通因为这类项目十有八九卡在数据格式上而不是模型本身。2. 系统骨架拆解从测井曲线到岩性标签的完整链路2.1 这套源码到底包含哪几块先把包里的东西按职责分清楚不然后面改代码会迷路。常见做法是分成四层数据层、特征层、模型层、界面层。数据层负责读 LAS 或 CSV 格式的测井曲线文件LAS 是测井行业的标准格式里面按深度排列每条曲线的值特征层做归一化、缺失值填充、曲线组合模型层封装 sklearn 或 TensorFlow 的分类器界面层用 PyQt5 或 Tkinter 把预测结果画出来。我一般会先看目录结构确认入口文件在哪。典型布局是这样project/ ├── data/ # 原始测井数据与标签 │ ├── well_01.las │ └── label.csv ├── src/ │ ├── data_loader.py # 读 LAS/CSV对齐深度 │ ├── features.py # 归一化、特征构造 │ ├── train.py # 训练与评估 │ └── predict.py # 单井预测 ├── ui/ │ └── main_window.py # 图形界面 ├── models/ # 保存的模型文件 └── requirements.txt看到这个结构心里就有数了训练和预测是分开的界面只调 predict。改模型不用动界面改界面不影响训练这是合格毕设该有的解耦。2.2 数据读取与深度对齐测井数据最烦人的地方是不同曲线的采样深度不一致。GR 可能每 0.125 米一个点密度曲线每 0.25 米一个点直接拼会错位。常见做法是以一条主曲线为基准把其他曲线插值到同一深度网格上。import pandas as pd import numpy as np def load_well(csv_path, label_pathNone): # 读测井曲线假设列名为 DEPTH, GR, RT, DEN, CNL df pd.read_csv(csv_path) df df.sort_values(DEPTH).reset_index(dropTrue) # 以 0.125 米为基准重采样线性插值 depth_new np.arange(df[DEPTH].min(), df[DEPTH].max(), 0.125) df df.set_index(DEPTH).reindex(depth_new).interpolate(methodlinear) df df.reset_index().rename(columns{index: DEPTH}) # 合并岩性标签标签通常是离散深度段 if label_path: labels pd.read_csv(label_path) df pd.merge_asof(df, labels.sort_values(DEPTH), onDEPTH, directionnearest) return df.dropna()这段逻辑有三个关键点。第一sort_values必须做测井文件偶尔深度是乱序的不排直接插值会出鬼。第二reindex加interpolate是深度对齐的核心基准间距 0.125 米是常见测井采样率如果你的数据是 0.1 米就改成 0.1。第三merge_asof用来把离散的岩性标签贴到连续深度上directionnearest表示取最近深度的标签标签段边界处会有半米左右的模糊这是正常代价。注意插值前一定要检查缺失值比例某条曲线缺失超过 30% 建议直接丢弃硬插出来的数据会污染训练集。2.3 特征工程别只把原始曲线丢进去很多人拿到数据直接把 GR、RT、DEN、CNL 四列塞进模型结果准确率卡在 60% 上不去。测井解释里有大量经验组合曲线比如 GR 和 SP 一起看能区分砂岩和泥岩DEN 和 CNL 交会能识别岩性。我一般会构造几组派生特征特征名计算方式地质含义GR_norm(GR - GR_min) / (GR_max - GR_min)归一化伽马消除井间差异DEN_CNLDEN - CNL密度中子交会识别流体RT_loglog10(RT 1)电阻率对数压缩量级GR_SP_ratioGR / (SP 1e-6)砂泥区分辅助def build_features(df): df[GR_norm] (df[GR] - df[GR].min()) / (df[GR].max() - df[GR].min()) df[DEN_CNL] df[DEN] - df[CNL] df[RT_log] np.log10(df[RT].clip(lower0.01) 1) df[GR_SP_ratio] df[GR] / (df[SP].abs() 1e-6) return df参数说明clip(lower0.01)是防止电阻率出现 0 或负值导致 log 报错实际测井里 RT 偶尔会有异常低值。1e-6是防止除零。这些派生特征不是拍脑袋是测井解释教材里的常规交会图思路模型学起来比原始曲线快。2.4 模型选型为什么我默认用随机森林而不是深度学习毕设场景下数据量通常不大一口井几千个采样点几口井加起来几万个样本。这种规模下随机森林和 XGBoost 往往比 LSTM 稳原因有三第一树模型对特征量纲不敏感归一化做不做差别不大第二训练快调参直观n_estimators、max_depth两个参数就能覆盖大部分情况第三可解释性好能输出特征重要性答辩时好讲。from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report def train_model(df, feature_cols, label_colLITH): X df[feature_cols].values y df[label_col].values X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42) clf RandomForestClassifier( n_estimators200, # 树的数量200 是精度和速度的平衡点 max_depth12, # 限制深度防过拟合 min_samples_leaf5, # 叶子最少样本噪声数据多时调大 class_weightbalanced, # 岩性类别不均衡时自动加权 random_state42) clf.fit(X_train, y_train) print(classification_report(y_test, clf.predict(X_test))) return clfstratifyy保证训练测试集里各岩性比例一致岩性数据往往砂岩多泥岩少不分层的话测试集可能全是砂岩。class_weightbalanced是处理类别不均衡的常规手段比手动过采样省事。如果准确率还是上不去再考虑换 XGBoost 或加一维深度特征。3. 训练与预测的工程化把脚本跑成能交付的系统3.1 训练流程的标准化封装毕设代码最容易犯的错是训练逻辑散在一堆脚本里改一个参数要翻三个文件。我一般会把训练流程收成一个类配置走字典这样换数据集只改配置不改代码。class LithologyTrainer: def __init__(self, config): self.config config self.model None self.feature_cols config[feature_cols] def run(self, df): df build_features(df) self.model train_model(df, self.feature_cols, self.config[label_col]) return self.model def save(self, path): import joblib joblib.dump({model: self.model, feature_cols: self.feature_cols}, path)joblib.dump把模型和特征列名一起存预测时不用再猜用了哪几列。这是血泪经验只存模型不存特征列表过两周自己都忘了当时喂了哪些列预测结果对不上。3.2 单井预测与结果输出预测阶段要做的事很明确读新井数据、套用训练时的特征工程、加载模型、输出每个深度的岩性。def predict_well(model_path, csv_path, out_path): import joblib bundle joblib.load(model_path) model, cols bundle[model], bundle[feature_cols] df load_well(csv_path) df build_features(df) df[PRED_LITH] model.predict(df[cols].values) df[[DEPTH, PRED_LITH]].to_csv(out_path, indexFalse) return df关键点是build_features必须和训练时完全一致少一个派生特征模型就会报维度错误。输出 CSV 只保留深度和预测岩性方便导入其他绘图软件画柱状图。3.3 界面层怎么接界面层不用花哨能选文件、点预测、显示结果就行。PyQt5 的常见接法是按钮绑定一个槽函数槽函数里调predict_well再把结果填进表格控件。注意界面里跑模型会卡主线程数据量大时用QThread把预测放到后台否则窗口会假死。这个点很多毕设演示时翻车老师一点按钮界面白了印象分直接掉。4. 避坑与排查岩性识别项目里最容易翻车的五件事4.1 准确率虚高一换井就崩现象训练集测试集准确率 95%换一口新井预测结果乱七八糟。原因同一口井的数据被随机切分训练和测试样本深度相邻模型实际上记住了这口井的局部特征。解决按井划分数据集GroupShuffleSplit按井号分组训练井和测试井完全不重叠这样评估出来的准确率才是真实泛化能力。4.2 岩性标签编码不统一现象训练时标签是「砂岩/泥岩」预测时数据里是「Sand/Mudstone」模型直接报未知类别。原因标签编码没有统一映射表。解决在配置里维护一个label_map字典读数据时统一转成数字编码训练和预测走同一套映射。4.3 缺失值填充方式不一致现象训练时用均值填充预测时忘了填模型输入全是 NaN。原因填充逻辑散落在不同脚本。解决把填充写进build_features里训练和预测共用同一个函数填充统计量均值、中位数在训练时算好存进模型包预测时直接调用。4.4 深度单位混用现象有的井深度是米有的是英尺合并后深度范围差三倍。原因LAS 文件头里STRT、STOP单位没检查。解决读数据时先看文件头单位字段统一转成米再处理转换系数 1 英尺等于 0.3048 米。4.5 模型文件与代码版本不匹配现象换了 sklearn 版本后加载旧模型报 warning 甚至报错。原因joblib 序列化对版本敏感。解决requirements.txt里锁死 sklearn 版本模型包和代码一起提交别只交代码不交模型。5. 进阶技巧用交叉验证和特征重要性把模型讲明白答辩时老师最爱问两个问题你怎么证明模型不是过拟合哪些曲线对识别贡献最大这两个问题用交叉验证和特征重要性就能答得漂亮。先说交叉验证。前面提到按井分组具体做法是用GroupKFold把井号作为分组依据每折留一口井做验证。这样跑五折得到五个准确率报均值和方差比单次划分有说服力。from sklearn.model_selection import GroupKFold, cross_val_score def cv_evaluate(df, feature_cols, label_col, group_colWELL_ID): X df[feature_cols].values y df[label_col].values groups df[group_col].values clf RandomForestClassifier(n_estimators200, max_depth12, class_weightbalanced, random_state42) scores cross_val_score(clf, X, y, groupsgroups, cvGroupKFold(n_splits5), scoringf1_macro) print(fF1均值 {scores.mean():.3f}标准差 {scores.std():.3f}) return scores这里用f1_macro而不是 accuracy因为岩性类别不均衡时 accuracy 会骗人f1_macro 对每个类别一视同仁。标准差大于 0.1 说明模型在不同井之间表现不稳定得回头查特征或数据质量。再说特征重要性。随机森林训练完直接有feature_importances_属性把它和特征名对应起来排序就能看出哪条曲线最关键。def show_importance(model, feature_cols): imp model.feature_importances_ pairs sorted(zip(feature_cols, imp), keylambda x: -x[1]) for name, val in pairs: print(f{name:15s} {val:.4f})我跑过的项目里GR_norm 和 DEN_CNL 通常排前两位这符合地质常识伽马测井区分砂泥密度中子交会区分岩性。如果跑出来某个派生特征重要性异常高比如 GR_SP_ratio 排第一那要警惕是不是数据泄漏检查这个特征是不是间接包含了标签信息。还有一个实用技巧是保存预测概率而不是硬标签。model.predict_proba输出每个深度的岩性概率分布画成概率曲线叠在测井图上过渡段能看出模型的不确定性比单纯一条岩性柱状图信息量大得多。答辩时展示这个老师会觉得你确实理解模型输出。最后说个习惯。我每次改完特征工程或模型参数都会固定跑一遍「按井交叉验证 特征重要性 混淆矩阵」三件套把结果记在一个文本文件里标注日期和改动内容。这样调了十几版之后能清楚看到哪次改动真正有用哪次是玄学波动。从那以后我每次交付模型前都强制走一遍这个流程再也没出现过「本地跑通、换数据就崩」的尴尬。希望帮到你。本文还有配套的精品资源点击获取