ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

深度学习驱动的油井生产动态预测:从滑窗构造到LSTM对比分析

2026/9/28 2:10:39 拓冰建站 浏览量
深度学习驱动的油井生产动态预测:从滑窗构造到LSTM对比分析 简介面向石油工程师与深度学习研究者的油井生产动态预测源码包融合CNN、RNN、LSTM、Self-Attention及Seq2Seq等模型并结合ARIMA等基线在统一数据集上完成效果对比为油田生产优化提供可复用建模方案。资源共232个文件压缩包约12.38MB目录结构清晰包含58个Python源码、7个Jupyter交互分析脚本、6个CSV历史生产数据、14个训练好的模型权重以及大量特征图、损失曲线等可视化图片与文本配置覆盖数据预处理、模型训练到误差对比的完整流程。已有253人学习浏览。借助这套源码可掌握多模型在时序油井数据上的调用方式与调参技巧理解不同网络结构对产量、压力预测的精度差异结合现成的可视化结果与误差输出快速评估模型优劣便于迁移至实际工况分析适合高校课题研究及工业落地验证。1. 油井生产动态预测为什么深度学习能比递减曲线看得更远一口新投产井的日产油曲线前三个月从每天 20 吨滑到 8 吨。按传统递减曲线外推下个月该产 6 吨结果措施作业后产量又反弹回 15 吨——规律在拐点处失效了。油井生产动态预测要解决的正是在这类数据上预测未来 30 天、90 天的产量变化。基于深度学习模型的思路是把产量序列切成滑窗样本用 LSTM、GRU 这类时序模型学习历史模式再和 Arps 递减等传统方法放在同一套指标下做对比分析。适合油田开发工程师、算法方向的研究生和做毕业设计的同学价值不在模型结构多新而在数据管道、训练评估、对比设计这一整条链路能完整复现。2. 把井史数据变成训练样本滑窗构造与特征工程的可复现代码2.1 原始井史数据长什么样先盘点字段再谈建模油井生产动态预测的输入最基础的是时间序列生产日期、日产油量、日产液量、含水率条件允许再加油压、套压、开井时数。这些字段来自采油厂的日报或月报数据脱敏后通常长这样字段类型说明well_idstr井号prod_datedatetime生产日期oil_ratefloat日产油t/dliq_ratefloat日产液t/dwater_cutfloat含水率%oil_pressurefloat油压MPa常有缺失casing_pressurefloat套压MPa常有缺失在真实数据上第一道工序是去脏数据停产检修段的日产油突变为 0 再恢复、计量异常点突然翻倍再回落、含水率超过 100% 的记录。常见做法是对oil_rate做差分把diff().abs()超过单井均值 3 倍的点标记出来人工确认。这些脏数据对深度学习的影响比线性模型大得多因为滑窗会把异常值直接喂进 LSTM 的隐状态影响会向后蔓延好几个窗口。源码工程在组织上一般按四个模块拆data_process.py负责清洗和滑窗models.py放模型结构train.py管训练循环evaluate.py做指标和画图。训练参数和文件路径放到一个config.py里换数据集或换模型时只改配置不动数据处理逻辑。2.2 滑窗切分与标签构造一个函数把时序转监督样本把时间序列转成监督样本的做法是滑窗给定历史窗口长度lookback预测未来horizon天。下面是通用切分函数import pandas as pd import numpy as np def make_samples(df, lookback60, horizon30, step5): df: 按日期升序排列的单井数据含 oil_rate 等列 返回: X (样本数, lookback, 特征数), y (样本数, horizon, 1) features [oil_rate, liq_rate, water_cut] n len(df) X, y [], [] for i in range(lookback, n - horizon 1, step): x_win df[features].iloc[i - lookback:i].values y_win df[oil_rate].iloc[i:i horizon].values X.append(x_win) y.append(y_win) return np.array(X, dtypenp.float32), np.array(y, dtypenp.float32) # df 为单井按日期排序后的数据 X, y make_samples(df, lookback60, horizon30, step5) print(X.shape, y.shape) # 例如 (样本数, 60, 3) 与 (样本数, 30, 1)滑窗要注意三点。第一y_win取的是原始日产油序列不是累产量如果要预测累产油把目标列换成oil_rate.cumsum()再切。第二step控制样本密度步长为 1 时相邻样本重叠严重模型容易过拟合一般取 515 起步样本量不够再往小调。第三循环里做了边界裁剪保证窗口末尾不越过未来标签避免无意中把未来信息带到特征里。含水率缺失多的井建议先按列删除或线性插值。油压、套压这类辅助特征经常缺 20% 以上最简单的处理是把该列整体去掉不要用 0 填充否则模型会学到“压力为 0 对应停产”的假规律。2.3 归一化与切分顺序错了模型就白训归一化这条线是最容易被忽视的。正确的切分顺序是先把数据按时间切出训练集、验证集、测试集再在训练集上fit归一化参数最后transform三段数据。如果先整体归一化再切分测试集的均值方差被模型提前看到误差会被系统性低估这是典型的时序数据泄露。from sklearn.preprocessing import StandardScaler # 先按时间切分再归一化 cut1 int(len(X) * 0.7) cut2 int(len(X) * 0.85) X_train, X_val, X_test X[:cut1], X[cut1:cut2], X[cut2:] y_train, y_val, y_test y[:cut1], y[cut1:cut2], y[cut2:] scaler_x StandardScaler() # fit 只在训练集上三维输入要先 reshape n_train, lb, n_feat X_train.shape scaler_x.fit(X_train.reshape(-1, n_feat)) X_train scaler_x.transform(X_train.reshape(-1, n_feat)).reshape(n_train, lb, n_feat) X_val scaler_x.transform(X_val.reshape(-1, n_feat)).reshape(-1, lb, n_feat) X_test scaler_x.transform(X_test.reshape(-1, n_feat)).reshape(-1, lb, n_feat) # y 有独立的 scaler预测完要 inverse_transform 才能回到原量纲 scaler_y StandardScaler() scaler_y.fit(y_train.reshape(-1, 1)) y_train scaler_y.transform(y_train.reshape(-1, 1)).reshape(y_train.shape) y_val scaler_y.transform(y_val.reshape(-1, 1)).reshape(y_val.shape) y_test scaler_y.transform(y_test.reshape(-1, 1)).reshape(y_test.shape)这里强调一点油井产量序列是强非平稳的不同井的量纲差异极大。一口日产 50 吨的高产井和一口日产 3 吨的低产井放进同一个全局 StandardScaler低产井的数值会被压到接近噪声。做多井联合建模时要先按井分别缩放或让模型输入里带一列单井历史均值作为尺度标识否则模型的注意力会被高产井完全带走。3. 模型选型与训练LSTM、GRU、TCN 在产量预测上的取舍3.1 先回答一个问题为什么不能继续用 Arps 外推传统产量递减分析的核心假设是产量按某种固定递减率下降指数递减、双曲递减、调和递减三件套在现场用了几十年。公式简洁、外推方便但它有三个结构性缺陷一是对措施后产量反弹、注水见效这类“非递减阶段”无能为力递减率本身就不再是常数二是它对含水率、油压、套压这些信息完全无视油气水三相关系只能靠工程师脑补三是递减率估计对拟合起点特别敏感起点选在递减段的不同位置外推结果能差出一倍。深度学习模型不预先指定趋势形态它从大量井的历史里学的是“含水率上升、油压下降之后产量通常会怎样”这样的非线性映射。这也是基于深度学习的油井生产动态预测和通用时间序列预测之间的关键区别通用场景靠周期性和趋势性油田场景靠的多是措施事件和递减规律的组合序列形态更尖锐、更不平稳。3.2 最小可训练的 LSTM 模型PyTorch 训练脚本工程上最稳妥的起点是 LSTM对长序列的记忆能力够用参数直观训练技巧成熟。GRU 参数更少、训练更快但精度和 LSTM 基本持平TCN 在长窗口上更快但对井史这种强非平稳数据没有明显优势通常作为第二版对比模型再加进来。先跑通一个最小可训练的 LSTMimport torch import torch.nn as nn from torch.utils.data import TensorDataset, DataLoader class OilLSTM(nn.Module): def __init__(self, n_features, hidden_size64, num_layers2, horizon30): super().__init__() self.lstm nn.LSTM( input_sizen_features, hidden_sizehidden_size, num_layersnum_layers, batch_firstTrue, dropout0.2 if num_layers 1 else 0.0 ) self.fc nn.Linear(hidden_size, horizon) def forward(self, x): # x: (batch, lookback, features) out, _ self.lstm(x) last out[:, -1, :] # 最后一个时间步的隐状态 return self.fc(last) # DataLoader 避免大数据量一次进内存 dataset TensorDataset(torch.tensor(X_train), torch.tensor(y_train)) loader DataLoader(dataset, batch_size64, shuffleTrue) model OilLSTM(n_featuresX_train.shape[2], horizon30) optimizer torch.optim.Adam(model.parameters(), lr1e-3) loss_fn nn.MSELoss() for epoch in range(50): model.train() total_loss 0.0 for xb, yb in loader: optimizer.zero_grad() pred model(xb) loss loss_fn(pred, yb) loss.backward() torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0) optimizer.step() total_loss loss.item() * len(xb) print(fepoch {epoch}: {total_loss / len(dataset):.4f})代码逻辑说明模型是两层 LSTM 加一个线性输出层batch_firstTrue让输入维度为(batch, lookback, features)和数据构造时的张量一致。输出层直接映射到horizon个未来时间点这叫多步直接预测比“一步步递归预测”稳定得多后面讲预测退化时会再对比一次。参数注意点clip_grad_norm_建议一定带上产量序列里偶尔会有极端值梯度裁剪能避免 loss 突然变 NaN。MSELoss 对连续回归自然但如果你的项目更关心低产井的相对误差可以换 HuberLoss异常值的干扰会更小。训练轮数别死磕 50配合 3.3 的早停验证集不降就停。3.3 超参数怎么定一张能直接抄的参数表很多新手把深度学习入门教程跑完回来处理油井数据还是措手不及主要原因是参数和环境这两关没过。下面是常见参数表按“先用默认值跑通再根据现象调参”的顺序使用。参数建议值/范围说明lookback30120太短学不到递减趋势太长噪声多且训练慢horizon30 或 90预测目标业务上常用 30 天、90 天hidden_size32128井数少时取 32 或 64别盲目加大num_layers12两层够用加深对产量数据收益很小dropout00.3井史数据容易过拟合0.2 起步learning_rate1e-3 到 5e-4Adam 配 1e-3 正常loss 震荡就降一半batch_size32128看显存和样本量样本少用 32early_stop_patience510验证集 loss 连续 N 轮不降就停止常踩的“玄学”问题集中在两处hidden_size 翻倍不一定提升精度反而让模型记住单井噪声学习率大于 1e-3 时loss 常常在几轮后直接飞到 NaN。训练脚本里建议每个 epoch 都把验证集 loss 打印出来画成曲线看是否平滑下降——如果上下跳得厉害先降学习率再谈改网络结构。提示几百口井、每口几千行产量数据的规模CPU 上跑几十轮也就几十分钟不一定非要 GPU。做深度学习环境配置时先把 CUDA、PyTorch 版本对齐比中途重装环境省时间。4. 对比分析怎么设计才公平基准模型、评估指标与可视化4.1 基准模型选谁Arps 递减、历史均值、线性回归三件套“对比分析”做不好就变成自说自话。至少要放两类基准一类是传统递减模型 Arps另一类是更简单的数据驱动基准比如历史均值或线性回归。Arps 双曲递减的公式是q(t) qi / (1 b * Di * t)^(1/b)其中qi是初始产量Di是初始递减率b是递减指数。拟合用 scipy 的非线性最小二乘即可from scipy.optimize import curve_fit def arps_double(t, qi, di, b, t0): # 相对时间 t-t0 的产量外推 return qi / (1 b * di * (t - t0)) ** (1 / b) # t_hist 取拟合段相对时间oil_hist 为对应日产油 popt, _ curve_fit( lambda t, qi, di, b: arps_double(t, qi, di, b, t0fit_start_time), t_hist, oil_hist, p0[oil_hist[-1], 0.1, 0.5], maxfev100000 )逻辑说明curve_fit对初值敏感qi初值取拟合段最后一个产量点收敛快很多。拟合区间只取递减阶段不要包含措施后反弹段否则 Arps 基线会给出离谱的外推结果。基线模型的意义在于如果 LSTM 连 Arps 都打不过问题大概率出在数据清洗或训练设置而不是模型不够深。历史均值基准则更简单直接用测试段前 30 天的产量均值当作未来预测。它没有趋势但能测出“无脑平滑”的下限。源码工程里建议把每个实验的模型名、随机种子、指标配置都写进 config保证每次对比是同一起跑线。4.2 评估指标单个 RMSE 不够还要看 bias 和分层油井生产预测常用三个误差指标RMSE、MAE、MAPE。另一个容易被忽视的指标是平均偏差bias mean(pred - true)。产量预测用于配产决策时系统性高估比随机误差危险得多——高估会让人误判井的剩余产能制定出不切实际的配产方案。指标计算方式适用场景RMSEsqrt(mean((pred-true)^2))对大误差敏感适合产量波动大的井MAEmean(abs(pred-true))直观反映平均误差MAPEmean(abs((pred-true)/true))适合低产井占比高的场景产量接近 0 时会爆biasmean(pred-true)判断系统高估还是低估评估的正确顺序是先把所有模型在同一测试集上算出上表指标再按井的类型分层看。分层通常按产量水平分高产井、中产井、低产井因为误差分布通常不均。如果只在全体样本上比一个 RMSE结论容易被高产井的绝对误差主导掩盖低产井上的系统性偏差。4.3 可视化单井拟合曲线和误差分布比数字更说服人一张预测对比图比指标表格更直观。通常画两张图一张是随机抽三口井的真实产量、LSTM 预测、Arps 外推曲线另一张是所有井预测误差的分布直方图或箱线图。import matplotlib.pyplot as plt fig, ax plt.subplots(figsize(10, 4)) ax.plot(history_dates, oil_history, labelhistory, colorgray) ax.plot(future_dates, true_future, labeltrue, colorblack) ax.plot(future_dates, lstm_future, labellstm, colortab:blue) ax.plot(future_dates, arps_future, labelarps, colortab:red) ax.set_xlabel(date) ax.set_ylabel(oil rate (t/d)) ax.legend() plt.tight_layout() plt.show()出图后要检查两点LSTM 在产量拐点处的滞后是否比 Arps 更严重在低产井上 LSTM 是否把波动抹平成了一条直线。这两点是判断模型是否退化的直接线索也是对比分析里最有说服力的证据。如果 LSTM 在所有井上都把波动抹平那它不是在做预测只是在做平滑。5. 油井动态预测最常见的几个坑现象、原因与解法5.1 数据泄露测试集误差低得像在骗自己现象模型在测试集上 MAPE 只有 2%换一个随机种子重新训练结果大幅变差甚至 Arps 基线比 LSTM 还好。原因最典型的是在划分训练测试前就对整个数据集做了归一化测试集的均值方差被模型提前看到另一种是滑窗切分时没有按时间严格切分训练样本和测试样本在时间上有重叠模型实际上见过测试段附近的序列。解决先按时间切分再归一化这一步写进数据管道的最前面。测试集从最后 20% 的时间段开始取划分时严禁打乱时序再切。检查代码时着重看scaler.fit发生在切分之前还是之后这个顺序错了后面所有结论都不可信。5.2 多步预测退化预测曲线慢慢变成一条直线现象预测未来 30 天前 5 天贴得还不错后 20 天输出几乎等于历史尾部的均值曲线被“熨平”。原因模型训练时用的是独立多步输出但真实预测时误差会累积如果用了递归预测——把上一步输出当下一步输入——误差越滚越大模型学到的不是趋势而是“输出期望值最安全”。MSELoss 对极端值敏感也会强化这种保守输出。解决训练时改多步直接预测一次输出全部 horizon不要逐点递归。如果项目非要递归框架可以考虑给中间时刻加辅助损失让模型每一步都有监督信号。多步直接预测对油井这种中长程预测最省心先试它。5.3 含水率异常点污染滑窗现象训练 loss 一直降不下去画单井曲线发现预测在个别日期突然跳变跳变位置恰好对应含水率异常段。原因含水率序列非平稳现场报表里偶尔会录成 100% 甚至 105%一个异常点进入滑窗后LSTM 的隐状态会一路受影响影响范围远大于异常点本身所在的单个样本。解决在数据清洗阶段对含水率做 0100% 上下限截断再用差分突变过滤掉单日跳变超过 10 个百分点的记录。宁可把这一段几十天删掉也不要让异常点进入滑窗污染相邻样本。5.4 多井训练时井间分布不一致导致模型偏心现象把 20 口井一起训练高产井预测得很好低产井的预测误差全部偏大整体指标被高产井牵走。原因全局归一化让低产井的产量数值被压到极小甚至接近噪声模型看到的有效信号主要来自大井学出的形态自然偏向它们。解决每口井先用各自的产量峰值或历史均值做缩放再进入模型或者按产量水平分组各组单独训练。工程上更稳的做法是按日产量峰值 5t、5~20t、20t分三组建模每组一个模型。别用一口井一个模型历史短的井样本量不够学不出递减规律。6. 进阶多井联合建模与滚动验证让预测结果真正可落地6.1 分组建模比全局模型和单井模型都稳全局模型数据多但难处理井间差异单井模型拟合能力强但样本量撑不起网络容量。常见做法是按区块或产量水平分 34 组每组训练一个模型。分组之后单井样本量变少验证集波动会变大建议用 K 折交叉验证看稳定性不要只看一次划分的结果。模型结构可以保持同一套各组只共享预处理参数换组时改一下配置就能重训。6.2 滚动验证模拟真实投产节奏而不是一次赌博固定切分只测了一次结果不稳定。更接近生产的是 walk-forward 滚动验证先训练前 70% 数据预测后 30 天再扩展到 80% 预测下 30 天逐步滚到数据末尾。def walk_forward_validate(model_fn, X, y, expand_ratio0.1, horizon30): n len(X) start int(n * 0.5) results [] while start horizon n: # 训练集取 [0, start)测试集取 [start, starthorizon) model model_fn() train_model(model, X[:start], y[:start]) pred model.predict(X[start:start horizon]) true y[start:start horizon] results.append(calc_metrics(pred, true)) start int(n * expand_ratio) return results滚动验证能直接看到误差随预测步长增长的斜率。如果误差在 30 天预测末段急剧放大说明多步稳定性差优先回去调训练策略而不是加网络层数。我自己糊过几版模型起初总想直接上 Transformer结果在几十口井的中小样本数据上Transformer 反而不如 LSTM原因不是结构不够新而是数据量和非平稳性撑不起自注意力训练。后面总结出来的教训是油井生产动态预测这类中小样本、强非均衡问题先把数据清洗、滑窗构造、对比分析三件事做扎实比换模型更有性价比源码工程里把数据、模型、训练、评估四个模块拆开后续换模型只改配置不重写管道。希望帮到你。本文还有配套的精品资源点击获取