
简介这份资源面向计算机、人工智能、自动化、电子信息等专业的高校学生与科研人员围绕人工智能在石油测井领域的应用提供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() # 转成 DataFrameindex 是深度 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(methodlinear, limit5) # 最多连续插 5 个点 df df.dropna() # 插不上的整段丢掉 df.to_csv(well_A_clean.csv, index_labeldepth)逻辑说明las.df()把曲线转成以深度为索引的表格reindex到统一网格后原本没有采样的深度会变成 NaNinterpolate做线性插值limit5是关键参数——它限制连续插值的最大点数防止在仪器故障导致的大段缺失上凭空造数据。参数怎么改如果曲线质量好、缺失少limit可以放到 10如果数据本身噪声大建议降到 3 甚至直接dropna。2.3 异常值处理与归一化别让一条坏曲线毁掉整个模型测井曲线里常见的异常是井径突变、仪器刻度错误导致的尖峰。处理方式不是简单删掉而是先看分布。我一般会画箱线图把超过 3 倍四分位距的点标记出来再决定是截断还是置 NaN。def clip_outliers(series, k3): 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_size0.2, stratifyy, random_state42 ) clf RandomForestClassifier( n_estimators300, max_depth12, min_samples_leaf5, random_state42 ) 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_estimators300是树的数量测井数据量通常几千到几万点300 够用max_depth12控制树深太深会记住噪声min_samples_leaf5保证每个叶子至少 5 个样本防止对薄层过拟合。stratifyy很重要岩性类别往往不均衡泥岩可能占 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_size0.2, random_state42 ) reg GradientBoostingRegressor( n_estimators400, learning_rate0.05, max_depth4, subsample0.8, random_state42 ) reg.fit(X_train, y_train) y_pred reg.predict(X_test) rmse mean_squared_error(y_test, y_pred, squaredFalse) print(fRMSE: {rmse:.4f}, R2: {r2_score(y_test, y_pred):.4f})参数说明learning_rate0.05配合n_estimators400是典型的慢学习率多树组合比学习率 0.1 配 200 棵树更稳max_depth4对回归来说已经够深再深容易过拟合subsample0.8表示每棵树用 80% 样本训练引入随机性提升泛化。RMSE 的单位和孔隙度一致如果孔隙度是小数0-0.3RMSE 在 0.02 以内算不错如果是百分数0-30RMSE 在 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, s5, alpha0.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], labelMeasured) axes[1].plot(y_pred, df.loc[y_test.index, depth], labelPredicted) 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_weightbalanced让模型对少数类加权或者对少数类做 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(limit5).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_labeldepth)这样一份包含所有井预测结果的 CSV 就出来了可以直接导入 Petrel 或其他解释软件做可视化。报告里可以放一张多井连井剖面图展示岩性预测的横向连续性这是加分项。6.3 一个我踩过的坑模型版本和特征顺序最后说一个血泪经验。我曾经把训练好的模型用 joblib 存下来过了一个月换台机器加载预测结果全乱。排查半天发现是 sklearn 版本不一致导致模型内部结构解析出错。后来我养成的习惯是模型文件、特征列表、归一化参数、sklearn 版本号四样东西必须一起存、一起加载。特征顺序也一样训练时是[GR, RT, RHOB]预测时写成[RT, GR, RHOB]模型不会报错但结果会悄悄变差这种玄学问题最难查。如果你正准备做这个课程设计我的建议是先把第 2 章的预处理跑通画图确认深度对齐再往下走。模型选随机森林还是 XGBoost 差别不大数据质量才是决定成败的那一环。希望帮到你。本文还有配套的精品资源点击获取