☰
深度学习油井生产动态预测:从数据清洗到时序模型实战
2026/10/2 9:54:22 网站建设 项目流程

简介:一套基于深度学习的油井生产动态预测源码,面向石油行业数据工程师与算法研究人员,解决采油生产数据时序建模与多模型效果对比问题。项目基于PyTorch与Optuna,融合CNN、RNN、LSTM、Self-Attention和Seq2Seq五种模型,覆盖特征提取、长短期依赖建模、注意力机制及序列映射,并提供超参数自动调优与可视化评估,可复用于油气生产预测、工业时序分析等场景。资源包共232个文件、9.1MB,含58个Python脚本、7个Notebook、14个模型权重、41个文本说明、6个CSV数据集及86张结果图,从数据处理、模型训练到误差对比均有完整落盘,辅助的YAML环境配置与Markdown说明也便于快速复现。已有134人学习下载,适合希望系统掌握深度时序建模并快速搭建油井预测实验的开发者,可直接基于原始生产数据展开训练与调优。

1. 为什么油井生产动态预测值得用深度学习重做一遍

油井生产动态预测不是新概念,采油厂一直用递减曲线、Arps公式和物质平衡方程在做。但现场数据越积越多,井况越来越复杂,稠油热采、注水驱、间抽制度这些场景下,递减曲线经常算不准。深度学习的价值在于能直接吃多维时序特征——油压、套压、动液面、含水率、产液量历史——把“这口井接下来30天能产多少”这个问题变成一个有监督的序列预测任务。今天聊的这套“基于深度学习的油井生产动态预测源码”,核心就是围绕这个任务组织的数据管线、模型骨架和训练调参经验。

这套方案适合谁?一类是采油厂或油服公司的数据工程师,手上有一堆历史生产报表,想用深度学习做产量预测或措施效果对比;另一类是课题组的算法同学,拿公开的油气田数据集做毕设或横向课题。无论哪一类,能跑通源码只是第一步,真正有价值的是知道数据怎么清洗、网络怎么选、预测结果怎么被现场采油工程师认可。下面从数据准备开始,逐步把整条链路拆开。

2. 井史数据先“治”再“喂”:两个关键处理与特征构造

深度学习吃数据,但油井生产数据是国内现场最“脏”的数据之一。报表录入靠人,传感器更换和校准也靠人,所以数据里既有缺失、超界值,还有“关井—开井”产生的间歇性零值。这些不处理干净,后面模型再先进也是白搭。

2.1 原始数据里最常见的三类“假信号”

第一类是产液量突然归零,原因可能是关井测压、设备检修,也可能是仪表离线,但报表上写的都是0。第二类是脉冲尖峰,比如抽油机启动瞬间的热线电流波动,或者输油泵切换导致的瞬时高值,这类值在曲线图上看着吓人,但对预测下一阶段的产量没有参考价值。第三类是时间不对齐,井口的温度、压力是小时级采集,产液量是日报,动液面可能一个月才测一次,多源数据直接拼起来会让深度学习模型学会“乱对时间戳”。

我一般处理顺序是:先按井号和时间戳排序,把明显超出物理边界的值(比如日产液量超过泵额定排量三倍)标为缺失;再用前后两天的中位数做填充,而不是均值——均值对尖峰敏感,会把一根刺磨成一个鼓包。对于关井状态,直接看生产状态字段,如果状态是“关井”或者连续三天产液量为0,就把这段标记成非生产期,不进入滑窗样本。

import pandas as pd import numpy as np df = pd.read_csv("well_history.csv", parse_dates=["date"]) df = df.sort_values(["well_id", "date"]) # 物理边界过滤:产液量不能为负,也不能超过泵额定排量的3倍 pump_max = 120 # m3/d,来自该井的泵挂参数 df.loc[df["liquid_rate"] > pump_max * 3, "liquid_rate"] = np.nan df.loc[df["liquid_rate"] < 0, "liquid_rate"] = np.nan # 连续3天为0视为关井段,整段置为NaN,避免把关井后的“恢复峰”当训练样本 zero_mask = df["liquid_rate"] == 0 zero_run = zero_mask.groupby((~zero_mask).cumsum()).transform("sum") df.loc[(zero_run >= 3) & zero_mask, "liquid_rate"] = np.nan # 中位数填充缺失 df["liquid_rate"] = df.groupby("well_id")["liquid_rate"].transform( lambda s: s.fillna(s.median()) )

这段代码里最关键的是“连续3天为0”的判断逻辑。分组用的技巧是(~zero_mask).cumsum(),它会把连续的 True 切成不同的组,然后transform("sum")统计每组连续零值天数,只有真正达到3天才被当作关井段处理。如果只把单日0值置为NaN,会把正常生产中的偶发日停误伤,导致模型学到一个错误的“今天零产量明天就恢复”的规律。

2.2 构造滑窗样本:预测目标怎么定才符合现场需求

油井生产动态预测的落地场景一般是三条:一是月度配产计划的制定,需要预测未来30天累积产液量;二是措施井效果评价,比如压裂或酸化后30天产量对比;三是间抽制度寻优,需要看不同开关井周期下的产量趋势。因此源码里推荐默认预测窗口是30天,输入窗口是60天或90天。输入太长,模型学到的是长期递减趋势;输入太短,则很难捕捉到压力恢复周期等中短期波动。

构造样本时要注意步长。如果每口井用滑窗步长1天切样本,一口1000天的井能切出900多条样本,相邻样本重叠度太高,训练集和验证集之间会产生严重的相似性泄漏。我一般对日产数据用步长7天切,既能保证样本量,又能降低重叠带来的伪相关。对月报数据的井,窗口和步长都按“月”为单位重新定义。

def make_samples(df, input_len=60, pred_len=30, stride=7): samples, targets = [], [] for well_id, grp in df.groupby("well_id"): grp = grp.sort_values("date").reset_index(drop=True) values = grp[["liquid_rate", "oil_rate", "water_cut", "tubing_pressure", "casing_pressure"]].values for i in range(0, len(values) - input_len - pred_len + 1, stride): x = values[i : i + input_len] y = values[i + input_len : i + input_len + pred_len, 1] # 预测 oil_rate samples.append(x) targets.append(y) return np.array(samples), np.array(targets)

这里的y取的是oil_rate这一列,也就是未来30天的产油量序列。实际业务里,产油量比产液量对经济评价更有意义,但也更难预测,因为它受含水率变化影响。你也可以把liquid_rate也放进预测目标里,做成多目标输出,但后续损失函数和评估指标都要跟着调整。参数stride=7直接影响样本数量:1000天数据、60天输入、30天输出,按步长7切能得到约130条样本,这在小数据集上是合理数量。

2.3 归一化的坑:MinMax 还是 RobustScaler

油井数据经常有长尾尖峰,比如压裂井初期产量是正常水平的5到10倍,之后迅速回落。如果直接对全序列做 MinMax,尖峰会被拉到接近1,大部分正常生产值被压到0到0.3之间,模型梯度在归一化空间里非常难走。常见做法有两种:一是对所有特征用 RobustScaler,基于中位数和四分位距缩放,抗尖峰能力强;二是对油压、套压这类本身变化幅度不大的特征单独用 MinMax,但把上界设成物理上限而不是数据最大值。

from sklearn.preprocessing import RobustScaler scaler = RobustScaler(quantile_range=(10.0, 90.0)) x_scaled = scaler.fit_transform(grp[["liquid_rate", "oil_rate", "water_cut", "tubing_pressure", "casing_pressure"]])

注意一个顺序问题:RobustScaler的fit应该在训练集上完成,验证集和测试集只用transform。如果在全量数据上先缩放再切分,等于让模型在训练时已经“见过”测试集的分布统计量,这是典型的数据泄漏。另一个坑是不同井的液量基数差异很大,有的井日产5吨,有的井日产80吨。如果所有井一起缩放再训练,低产井的特征会被压缩到很小范围,模型天然偏向高产井。我通常先按井分组,每组单独 fit scaler,再拼接样本。

3. 模型选型与源码骨架:从 LSTM 到 TCN 的实测对比

模型选型不该拍脑袋。油井生产动态预测本质是单变量或多变量的时序回归,最常见的选择是 LSTM、GRU、TCN 和 Transformer。对这个特定场景,样本量是关键约束——一口井的历史数据往往只有几百到两千天,并且生产制度一旦调整(提液、转抽、措施),序列的统计规律会突变。这种数据量下,Transformer 容易过拟合,LSTM 是平衡性能和可解释性的基线选择。

3.1 为什么把 LSTM 当成源码默认基线

LSTM 的门控机制适合捕捉油井生产中的两类典型动态:一类是长期递减趋势,这是由地层能量下降决定的,跨度在数月以上;另一类是短期波动,比如泵工况变化引起的产量抖动。标准的 LSTM 细胞状态可以在长时间跨度上保留递减趋势信息,同时遗忘掉无意义的抖动,这种双重特性正好匹配产量序列。

层数上,现场实测下来两层 LSTM 基本就是上限。三层以上在小样本时序数据上几乎必然过拟合,而且训练时间翻倍。隐藏单元数建议从32到128之间调,输入特征多可以适当加到128,特征少用64。每层后面接 Dropout,丢弃率0.2到0.3,防止对训练井的模式记忆过深。

import tensorflow as tf from tensorflow.keras import layers, Model def build_lstm_model(input_shape=(60, 5), hidden_dim=64, pred_len=30): inputs = layers.Input(shape=input_shape) x = layers.LSTM(hidden_dim, return_sequences=True)(inputs) x = layers.Dropout(0.2)(x) x = layers.LSTM(hidden_dim // 2)(x) x = layers.Dropout(0.2)(x) x = layers.Dense(64, activation="relu")(x) outputs = layers.Dense(pred_len)(x) model = Model(inputs, outputs) model.compile(optimizer="adam", loss="huber", metrics=["mae"]) return model

这个网络结构输出的是30个时间步的产油量预测,也就是一个Dense(pred_len)直接把 LSTM 最后一步的隐状态映射成30个数值。相比用TimeDistributed逐步输出,这种直接映射更适合产量预测,因为我们要的不是“逐步滚动预测”,而是一次性给出未来30天的趋势曲线。损失函数用huber而不是mse,是因为产量序列里有尖峰,Huber 损失对异常值的梯度约束更平滑,不会让模型为了少数几天的高产尖峰扭曲整体预测。

3.2 候选模型:GRU、TCN 和注意力机制各自的定位

GRU 是 LSTM 的简化版,参数量大约少四分之一,训练更快,在小数据集上往往比 LSTM 更稳。我在几口注水井的数据上对比过,GRU 的验证集 MAPE 比 LSTM 低1到2个百分点,原因是 GRU 少一个遗忘门,对噪声更钝感。如果你的数据只有一两年,优先试 GRU,它能省不少调参时间。

TCN 即时间卷积网络,优势是训练速度远超 LSTM,因果卷积可以并行计算,而且感受野可以靠膨胀系数灵活控制。油井产量序列的周期性和趋势性都能被卷积捕获,但 TCN 对输入序列的长度要求更高,短序列上表现不如 LSTM。另外 TCN 的输出对输入中的近期波动更敏感,如果现场数据中的偶发尖峰没有清洗干净,TCN 的预测曲线会明显比 LSTM “毛躁”。

注意力机制在这类任务里适合加在 LSTM 输出和全连接层之间,让模型自己决定历史哪几个时间点的状态对预测最关键。对油井生产动态预测来说,注意力加权后的隐状态通常更关注最近7天到14天的趋势,这符合现场经验——短期内压力恢复或措施见效的迹象,比三个月前的递减规律更能决定未来30天产量。

3.3 训练流程里容易被忽略的三个环节

第一是验证集切分不能随机。油井时序数据的随机切分会让训练集里混入验证集时间之后的样本,造成时间穿越。正确做法是按时间顺序切分,比如前70%的日期范围做训练,中间15%做验证,最后15%做测试。第二是早停的监控指标,不能只看验证集损失,还要结合业务指标。我见过模型验证损失降得很好,但预测曲线整体滞后一天,原因是损失函数对“整体抬高但形状吻合”和“形状滞后一天”给出的惩罚差异不够大。第三是多井训练时的样本权重,高产井样本波动大、低产井样本波动小,如果不做任何处理,损失会被高产井主导。简单做法是按井的产量中位数做样本权重归一化,实际使用中能显著提升低产井的预测精度。

4. 训练、评估与调参:这套源码的核心参数表

源码能跑通和能上现场是两码事。真正决定模型好坏的是那几个看起来不起眼的参数——滑窗长度、步长、损失函数、批大小、学习率。这章直接给出参数表和一套命令行训练流程,便于直接复现。

4.1 必调参数清单:从数据到训练的一站式建议

下表是这套代码里的核心参数,取值基于多口井的评测结果给出推荐范围和理由。

参数推荐值区间影响说明
input_window60 ~ 90天太短捕捉不到递减趋势,太长引入过多历史噪声
pred_window30天默认值,对齐月度配产计划周期
stride7天降低相邻样本重叠,抑制数据泄漏
网络隐藏单元64 / 128特征5个以内用64,超过8个用128
损失函数huber / log_cosh不容易被生产尖峰带偏
学习率0.0005 ~ 0.001太高发散,太低训练慢且容易过拟合
batch_size32 ~ 64小批量在少量样本上泛化略好
early_stopping_patience15 ~ 20轮防止在验证集上反复震荡

滑窗长度对结果的影响最直接。60天输入能覆盖两个月左右的趋势,这对递减率稳定的井足够;90天输入适合含水率上升较快或注水见效滞后的井。我建议先跑60天基线,再跑90天做对比,如果验证集指标提升超过2%才保留长窗口,否则短窗口优先,因为短窗口意味着推理时需要的输入更短,部署时更灵活。

4.2 训练脚本与早停、模型保存的工程细节

from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau callbacks = [ EarlyStopping(monitor="val_loss", patience=20, restore_best_weights=True), ModelCheckpoint("best_oil_model.keras", monitor="val_loss", save_best_only=True, verbose=1), ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=5, min_lr=1e-5), ] model.fit( x_train, y_train, validation_data=(x_val, y_val), epochs=200, batch_size=32, callbacks=callbacks, verbose=1, )

这里ModelCheckpoint只保存验证集损失最小的模型,restore_best_weights=True则保证训练结束后模型权重回滚到最优状态而不是最后一步。ReduceLROnPlateau的作用是当验证损失连续5轮不降时把学习率减半,这对 LSTM 类模型很有效,能避免后期在局部最优附近反复震荡。模型保存格式用.keras而不是.h5,前者是 Keras 3 的推荐格式,保存了完整的优化器状态和自定义损失,后续做推理恢复更省事。

4.3 评估指标不能只看 RMSE:采油业务里的“合格率”

深度学习通用的评估指标是 RMSE 和 MAE,但油工程审核人员更认“单月预测合格率”——预测产油量与真实产油量的相对误差在正负15%以内记作合格。这个指标对模型输出的解释更直观:一张月度配产计划表上,领导关心的是“有百分之几的井预测基本靠谱”,而不是抽象的均方根误差。

def qualified_rate(y_true, y_pred, tol=0.15): # 相对误差百分比,排除真实值接近0的样本 mask = np.abs(y_true) > 1e-6 rel_err = np.abs((y_true[mask] - y_pred[mask]) / y_true[mask]) return np.mean(rel_err <= tol)

引入合格率之后,你会发现模型调参的方向变了。RMSE 会被少数极端井的预测误差拉高,导致你拼命去拟合那些难井;但从合格率角度看,真正值得优化的是把中等难度井的误差从18%压到14%,而不是把最难的井从50%压到40%。这也是为什么现场落地时,模型的损失函数可以不改,但评估报告里一定要同时给出 RMSE、MAPE 和合格率三个指标。

5. 避坑、踩坑与排查:油井时序预测里最常见的四个翻车现场

深度学习在工业数据上的失败,大部分不是模型问题,而是数据处理和评估设计的问题。油井生产动态预测的翻车现场,我按出现频率排了四个,前两个几乎必然遇到,后两个在交叉验证和部署阶段容易爆发。

5.1 时间穿越:随机切分让模型“偷看”了未来

现象:训练集损失降得很好,验证集损失也很低,但模型在一口新井上的预测完全不对。原因:代码里用了train_test_split(X, y, random_state=42),随机切分把同一时间段的一部分样本分到训练集、一部分分到验证集。油井产量序列有强自相关性,验证集里的某一天其实和训练集里前两天高度相似,模型实际在“背答案”。解决:改成按日期排序的连续切分,训练集日期范围在验证集之前,验证集在测试集之前。

5.2 关井段的“零值陷阱”

现象:验证集损失不大,但画出来的预测曲线在真实产油量归零的时段还维持在高位,或者提前几天就往下掉。原因:关井段的零值没有被清洗,模型用大量零值样本训练,学到“遇到相似压力特征时产量应该趋近于零”,但真实场景中关井是人为决策,和油层产能没有直接关系。解决:按2.1节的逻辑把连续3天及以上的零值段剔除,不送入训练;推理时如果当前井实际处于关井状态,直接先做人工标记,再决定是否启用模型预测。

5.3 滑窗重叠造成的样本依赖

现象:把验证集切出来后,模型在验证集上的表现异常好,但到了下一口井就崩。原因:步长设成了1,相邻两个样本只有一天之差,训练集和验证集边界附近的样本几乎重合,验证损失被严重低估。解决:步长至少设到预测长度的四分之一,推荐7天;更严格的做法是在多井数据上按“井级”切分,训练集和验证集各含完全不同批次的井,这样评估的是模型跨井泛化能力,不是对同一口井的记忆能力。

5.4 归一化统计量泄漏

现象:同一套代码换了数据集,训练正常,验证集损失却莫名其妙比训练还低。原因:数据预处理时先在全量数据上计算 MinMax 或 RobustScaler 的参数,再进行切分,验证集和测试集的分布统计已经参与了训练数据的缩放。解决:严格把预处理拆成两步,先在训练集上 fit,再对验证集和测试集只做 transform;如果按井分组归一化,每口井的 scaler 参数要单独序列化保存,推理阶段加载同一口井的 scaler 反变换输出。

6. 从离线预测到现场可用:部署形态与模型迭代的验证技巧

模型训练完成只是开始。油井生产动态预测的源码价值,最终体现在“预测结果能不能直接进配产会、能不能指导间抽制度调整”。这里讲三个落地习惯:模型导出、输出反变换、再训练节奏。

训练好的 Keras 模型可以导出为SavedModel格式,方便用 TensorFlow Serving 或 ONNX Runtime 部署。导出前记得把 scaler 一起打包。由于训练时每组输入特征都经过了归一化,推理接口接收的应该是归一化后的张量,而输出需要反变换回真实产量单位,否则工程师看到的是一堆0到1之间的小数,根本没法判断预测值是否合理。

model.save("oil_prod_model_saved", save_format="tf") np.save("scaler_params.npy", {"medians": scaler.center_, "scales": scaler.scale_})

再训练节奏上,我的习惯是现场每月月底用截至当天的数据增量训练一次,而不是模型上线后就不管了。油井的含水率上升、地层压力下降都会让输入分布发生漂移,静态模型的有效期往往只有两到三个自然月。增量训练时保留上一版模型作为对照,如果新模型在验证集上的合格率低于旧模型,直接回滚,不发布。

最后一个验证技巧:拿历史“措施事件”做回溯测试。选3到5口做过压裂、酸化或提液的井,用模型在措施前的输入窗口做预测,看措施后30天的实际曲线和预测曲线差多少。理想结果是在措施生效前模型预测偏差可控,这说明模型学的是趋势性的生产动态规律,而非对具体事件的记忆。我把这个测试写进了每次模型迭代的检查清单里,每次发布新版模型前必须跑一遍,经历过一次在措施井上预测失真而现场照搬的情况后,这个步骤再也没省过。

希望这套从数据清洗、特征构造到模型部署的完整思路,能帮你少走一些弯路。

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

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

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

立即咨询