ARTICLE DETAIL

建站实战干货

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

基于XGBoost和LSTM的污染物浓度预测实战方案

2026/10/2 18:11:28 拓冰建站 浏览量
基于XGBoost和LSTM的污染物浓度预测实战方案 简介面向空气质量预测与时空数据建模的Python学习者这套完整项目结合XGBoost与LSTM模型针对北京35个站点未来两天24*2小时的PM2.5、PM10、O3浓度进行预测。压缩包共7个文件含3个Jupyter Notebook覆盖数据预处理、XGBoost建模、LSTM建模、1个Python脚本与3个CSV观测数据集整体约9.95MB。项目结构清晰从气象与空气质量站点数据到模型评估均有可运行代码适合作为污染物浓度预测的入门到进阶实战参考。目前已有734人学习下载读者可直接复现实验并迁移到其他城市或污染物预测任务中完整呈现了数据清洗、特征构造、模型训练与预测流程是理解树模型与循环神经网络在时间序列预测中应用的实用素材。1. 污染物浓度预测为什么同时需要XGBoost和LSTM先看懂数据再选模型污染物浓度预测很少靠一个模型就能稳定落地。做过空气质量预报的人都有这种体会PM2.5浓度受风速、湿度、边界层高度和昨天同一时刻的浓度共同影响白天和夜间还可能呈现完全不同的扩散规律这种“强非线性 时间依赖”的结构恰好分别踩在XGBoost和LSTM各自最擅长的区域。XGBoost擅长从表格特征里挖掘非线性关系LSTM能通过循环结构捕捉序列里的长期依赖。标题里的“基于XGBoost和LSTM模型来预测污染物浓度”是一套完整的Python实现方案数据、特征工程、模型训练、预测和评估都包含在内适合正在做环境数据建模的从业者也适合拿真实监测数据做课题的学生。下面按数据准备、两个模型、踩坑排查、模型融合的顺序把这个方案完整讲一遍代码都是可直接复现的最小实现。2. 监测数据与特征工程把小时级记录变成可训练样本2.1 数据字段与时间对齐污染浓度和气象数据先合并污染物浓度数据一般来自环境监测站的六项常规参数PM2.5、PM10、SO2、NO2、CO、O3时间分辨率通常是小时。气象数据来自气象站或区域气象观测网包含温度、相对湿度、风速、风向、气压。这两套数据来源不同合并时最常见也最容易被忽视的问题是时间对齐污染物数据多是整点记录气象数据有时是半点或者十分钟间隔直接把两张表按行拼接会产生大量错位和NaN。我一般先把两套数据的time字段统一成datetime类型再各自按小时做重采样取均值最后以时间为键做inner join。只保留两套数据都完整的小时后续缺测问题会少很多。合并之后要立刻检查缺失值比例超过5%就需要考虑是不是站点停电、仪器校准而不是盲目填充。import pandas as pd pollution pd.read_csv(pollution_hourly.csv, parse_dates[time]) weather pd.read_csv(weather_hourly.csv, parse_dates[time]) # 统一重采样到小时取小时均值避免半点错位 pollution pollution.set_index(time).resample(h).mean() weather weather.set_index(time).resample(h).mean() df pd.merge(pollution, weather, left_indexTrue, right_indexTrue, howinner) # 查询缺失率判断数据质量 missing_rate df.isnull().mean() print(missing_rate[missing_rate 0])resample(h)会把非整点记录聚合到整点10分钟粒度数据也能自动归并。howinner保证后续训练不会因为大量空值翻车代价是丢弃部分时段样本。如果两个站点的时间戳存在时区偏差务必先统一时区再做重采样这一步错了后面全部白做。2.2 缺失值填充与滞后特征让模型看到“昨天”的浓度污染物数据里的缺失通常是一两个小时的小洞前向填充加线性插值就够了如果一段连续缺失超过6小时说明当天仪器基本没工作我建议直接删掉这段而不是用插值硬补。原因很简单6小时以上的浓度变化受局地排放和气象影响太大插值出来的值会变成模型里的假规律。滞后特征是这类预测里权重最高的特征。污染物浓度有很强的自相关性今天的PM2.5和昨天同一时刻的浓度高度相关把历史时刻的浓度作为特征喂给模型等于给模型提供了一条最直接的先验路径。这里的目标变量是“未来24小时后的PM2.5”所以要用shift(-24)把未来浓度搬到当前行。target_horizon 24 # 预测未来24小时的浓度 df[pm25_target] df[pm25].shift(-target_horizon) # 构造历史滞后特征 for lag in [1, 3, 6, 24, 48]: df[fpm25_lag_{lag}] df[pm25].shift(lag) # 滑动窗口统计量捕捉“最近几小时的变化趋势” df[pm25_rolling_6h_mean] df[pm25].rolling(6).mean() df[pm25_rolling_24h_mean] df[pm25].rolling(24).mean() df[pm25_rolling_24h_std] df[pm25].rolling(24).std() df df.dropna().reset_index(dropTrue)逻辑上要分清两组shift的方向shift(-24)是往前看24小时把未来目标搬到今天这一行shift(lag)是往后看把过去第lag小时的浓度搬到当前行。前者会产生末尾24行NaN后者会产生开头lag行NaNdropna()会同时丢这两段这是正常现象。滞后阶数选24和48是因为污染物存在明显日周期只看最近一两个小时会让模型过度依赖短期惯性。2.3 按时间切分数据集训练集、验证集、测试集的正确姿势随机切分在时序任务里是错误做法。污染物数据是时间序列随机打乱会把未来的数据混进训练集验证分数虚高等模型真正面对未来数据时误差立刻反弹。正确做法是按时间顺序取前80%训练、后20%验证验证期必须连续这样才接近真实预测场景。split_idx int(len(df) * 0.8) train_df df.iloc[:split_idx] val_df df.iloc[split_idx:] print(ftrain: {len(train_df)} hours, val: {len(val_df)} hours)切分比例不是死的。如果总数据只有几千小时验证集可以缩到15%把更多数据留给模型。另外要特别注意不要把验证集夹在训练集中间比如用1月到5月做训练、3月做验证这种交叉方式时序交叉验证在某些场景可以用但在污染物预测里验证集必须晚于训练集结束时间否则等于让模型“看到”了它不该看到的近期趋势。后面的XGBoost和LSTM训练都基于这个切分。3. 用XGBoost训练污染物预测模型核心参数与早停3.1 为什么表格特征优先选XGBoost污染物预测的特征天然是表格结构滞后浓度、气象要素、时间属性特征数量不多但相互之间关系复杂。XGBoost对这类数据有两个天然优势。第一树模型基于分裂规则自动处理非线性交互。比如“湿度高且风速低时PM2.5更容易累积”这种组合关系XGBoost不需要你手动设置交互项就能学到。第二XGBoost对特征尺度不敏感温度、风速、浓度的量纲差异大也不需要归一化省掉了时序模型里最容易出错的那一步。LSTM在这个环节反而不占优势。当样本量只有几千到几万小时序列网络容易欠拟合而XGBoost在同样数据量下可以很快收敛出一个可靠的基线。所以我的习惯是先跑XGBoost拿到一个基础分数再让LSTM在序列建模的位置上补充它学不到的长时间依赖信息最后融合。这个顺序不要颠倒。3.2 XGBRegressor训练代码与早停最小可复现实现XGBoost回归任务用XGBRegressor。几个直接影响预测效果的参数n_estimators树的数量配合早停使用不用一开始定死。learning_rate学习率0.05到0.1之间常见。调低之后要增加树的数量否则欠拟合。max_depth树深4到8。特征数量不大时太深容易记住训练集噪声。subsample和colsample_bytree每轮迭代的样本采样和特征采样比例0.7到0.9之间是防过拟合的主力。reg_lambdaL2正则数据噪声大时调高效果明显。训练时必须用早停每训练一轮就在验证集上算RMSE连续多轮不下降就停避免无意义地追加树。import xgboost as xgb feature_cols [c for c in df.columns if c not in [time, pm25_target]] X df[feature_cols].values y df[pm25_target].values X_train, X_val X[:split_idx], X[split_idx:] y_train, y_val y[:split_idx], y[split_idx:] model xgb.XGBRegressor( n_estimators1000, learning_rate0.05, max_depth6, min_child_weight3, subsample0.8, colsample_bytree0.8, reg_lambda2.0, objectivereg:squarederror, random_state42 ) model.fit( X_train, y_train, eval_set[(X_val, y_val)], eval_metricrmse, early_stopping_rounds50, verbose100 )早停生效后model.best_iteration能拿到最优迭代轮数模型内部保留的就是这一轮的参数不需要再手动重训。注意verbose100不是必需但建议打开能直观看到验证RMSE每100轮的变化趋势用来判断学习率是否过低。3.3 特征重要性排序哪些特征对预测贡献最大XGBoost训练完第一步不是急着评估而是看特征重要性。用model.feature_importances_配合特征名排序能快速验证特征工程方向是否正确。importance pd.Series(model.feature_importances_, indexfeature_cols) importance.sort_values(ascendingFalse).head(10)常见的排序结果里pm25_lag_24、pm25_lag_1、wind_speed、humidity通常排在前列。原因是污染物浓度有强日周期同一时刻的历史浓度是最强先验气象特征起修正作用。如果滞后特征完全没进前五大概率是滞后阶数选错了比如目标设成未来24小时滞后特征却只做到2小时模型根本找不到有效的历史参照。如果wind_speed排在末尾检查风向是不是只用了数值没有做环形编码风速风向这类角度型特征直接丢给树模型会损失信息。4. 用LSTM训练时序预测模型窗口、归一化与训练流程4.1 LSTM在污染物预测里的定位和XGBoost互补LSTM的价值在于直接建模连续时间步之间的状态变化。污染物浓度是典型自相关序列前一天的扩散过程会通过逐小时的状态传递到今天这种状态传递用固定滞后特征表达不清楚。滞后特征只给了模型几个离散时间点的“快照”而LSTM拿到的是完整的一段历史走势。所以当XGBoost的滞后特征已经很强时LSTM的增量在于学习“近一天浓度走势 气象序列组合”的深层模式。常见做法是把过去24小时的特征序列作为输入预测未来24小时的目标浓度。这里的关键是构造三维输入样本数、时间步数、特征数。4.2 构造滑动窗口样本24小时窗口怎么建LSTM要求的输入形状是(samples, time_steps, features)。把一张宽表格重排成三维数组是LSTM建模里最容易出错的一步。窗口长度我用过12、24、48三种24是大多数项目的起点因为污染物日周期是24小时窗口太短模型看不到完整周期太长则训练样本减少、计算量增加。构造窗口时有两个细节必须注意。第一窗口不能跨切分点要在训练集内部构造序列不能把验证集数据混进训练窗口。第二目标索引要对齐特征窗口取X[i:iseq_len]目标取y[iseq_len]保证是用过去24小时预测当前目标时刻而不是把目标算进特征里。import numpy as np from sklearn.preprocessing import MinMaxScaler # 只对训练集做归一化拟合验证集用同一套参数变换 scaler MinMaxScaler() X_train_scaled scaler.fit_transform(X_train) X_val_scaled scaler.transform(X_val) seq_len 24 def make_sequences(X, y, seq_len): Xs, ys [], [] for i in range(len(X) - seq_len): Xs.append(X[i:i seq_len]) ys.append(y[i seq_len]) return np.array(Xs), np.array(ys) X_train_seq, y_train_seq make_sequences(X_train_scaled, y_train, seq_len) X_val_seq, y_val_seq make_sequences(X_val_scaled, y_val, seq_len) print(X_train_seq.shape, y_train_seq.shape)这里有一个容易踩的坑y[i seq_len]的索引范围。当i取到len(X) - seq_len - 1时y的索引是len(X) - 1刚好不越界。如果特征和目标长度不一致这里会报错所以构造前务必确认len(X) len(y)。序列窗口的步长默认是1也就是相邻窗口重叠了23个小时这是正常设计不是Bug。4.3 归一化与模型训练Keras最小实现LSTM内部用梯度下降训练特征值范围差异太大会让某些维度的梯度主导更新。用MinMaxScaler把所有特征压到[0,1]之后训练会稳定很多。对于目标值y我一般保持原始量纲这样预测结果可以直接和实测值对比。如果发现训练不收敛再把y也做一次缩放预测后inverse_transform回来效果等价但多一道手续。模型结构建议从简开始一层LSTM加Dropout加全连接。堆两层LSTM有利有弊第一层设置return_sequencesTrue把序列继续传给第二层两层都加Dropout。units从32或64起步污染物预测不是超大规模序列任务128起步很容易过拟合。优化器用Adam学习率默认1e-3不收敛时再降到1e-4。from tensorflow.keras.models import Sequential from tensorflow.keras.layers import LSTM, Dense, Dropout from tensorflow.keras.callbacks import EarlyStopping model Sequential([ LSTM(64, return_sequencesTrue, input_shape(seq_len, X_train_seq.shape[2])), Dropout(0.2), LSTM(32), Dropout(0.2), Dense(1) ]) model.compile(optimizeradam, lossmse) model.summary() early_stop EarlyStopping( monitorval_loss, patience10, restore_best_weightsTrue ) history model.fit( X_train_seq, y_train_seq, epochs100, batch_size64, validation_data(X_val_seq, y_val_seq), callbacks[early_stop], verbose1 )EarlyStopping的restore_best_weightsTrue是关键参数没有它训练结束后模型权重会停留在最后一次epoch而不是验证误差最低的那一次。model.predict(X_val_seq)返回的形状是(样本数, 1)和y_val_seq直接比较时注意维度。训练完成后可以用history.history[loss]画一条loss曲线验证集loss如果训练中途反弹说明学习率偏高或Dropout不够。5. 污染物预测的5个常见坑数据泄漏和时序错位的元凶5.1 先fit全量数据再做归一化低误差是假象现象LSTM验证集RMSE明显好于XGBoost但把同一套代码放到新数据上一预测误差直接翻倍。原因整个数据处理流程里如果先把全量X一起交给MinMaxScaler().fit_transform()scaler在求最大值和最小值时就已经偷看了验证集的数据范围等于提前泄露了未来的浓度区间。污染物浓度有季节变化夏季低、冬季高全量fit后验证集里的高浓度信息进入了特征缩放验证分数自然好看。解决先按时间切分再做两步缩放。scaler.fit_transform(X_train)只拟合训练集scaler.transform(X_val)用训练集的参数变换验证集。这个原则在时间序列项目里是铁律不能变通。5.2 train_test_split默认shuffle打乱时序现象XGBoost用train_test_split切分后验证RMSE很漂亮按时间顺序重新切分后误差明显变大两个结果对不上。原因train_test_split默认shuffleTrue会把样本随机打乱。污染物相邻小时的样本高度相似打乱后一部分相似样本进了训练集、另一部分进了验证集模型很容易“猜对”。但实际预测面对的是未来数据和训练数据的分布存在时间错位分数自然会下跌。解决时间序列任务里要么用shuffleFalse要么干脆按索引切分。我在这个项目里用的就是df.iloc[:split_idx]切片逻辑直接不容易被误改。5.3 目标时间与特征时间重叠模型学会了抄答案现象模型在验证集上误差接近0但把预测结果画出来发现曲线比真实曲线滞后了大约一个预测周期。原因滞后特征构造错了。有些实现把当前小时的浓度同时放进特征和目标模型根本不需要学气象关系直接输出特征里的当前浓度就是最优解。看起来精度高实则是抄了特征中的历史答案。解决构造特征时严格区分特征时刻与目标时刻。目标用shift(-horizon)把未来浓度搬到当前行滞后特征全部取过去时刻。检查方法很简单把特征里的pm25原始列删掉再看误差是否显著上升如果上升明显说明模型正在抄答案。5.4 LSTM输出水平线MSE对偏斜分布不敏感现象LSTM训练过程loss正常下降但预测结果是一条近似水平线只在均值附近波动峰谷完全丢失。原因污染物浓度分布严重右偏大部分时间浓度较低偶尔有高浓度事件。MSE对高浓度样本的误差惩罚很大模型为了整体loss最小会把预测逼近在浓度中位数附近放弃对极值的拟合。解决先看目标浓度的直方图。若偏斜明显对目标y做log1p变换压缩高浓度段的数值范围预测后再expm1还原。或者改用加权MSE给高浓度样本加大权重。评估时不要只看整体RMSE要分段算低浓度段和高浓度段的误差分开看才能暴露这个问题。5.5 季节漂移导致模型退化现象模型6月训练完8月还好12月验证RMSE突然高得离谱。原因污染物浓度分布随季节明显漂移北方采暖季PM2.5基准浓度可能翻倍模型训练时没见过冬季样本的取值范围外推能力有限。解决滚动重训练。每用最近三个月的数据训练一次预测下一周每周更新一次模型。这个策略比试图训练一个“全年通吃”的模型可靠得多。另外可以把月份、是否采暖期作为显式特征加入训练让树模型至少见过季节标记减少突发漂移。6. 模型融合与效果验证把两个模型的优势叠加起来6.1 加权融合与Stacking两个模型的组合方式XGBoost对表格特征的拟合稳LSTM对序列状态的捕捉比树模型强两者预测结果的误差结构不一样最简单的融合就是加权平均。权重不必拍脑袋在验证集上按0.1到0.9步长搜索即可。from sklearn.metrics import mean_squared_error best_w, best_rmse 0.5, float(inf) for w in np.arange(0.1, 1.0, 0.1): pred_fusion w * pred_xgb (1 - w) * pred_lstm rmse np.sqrt(mean_squared_error(y_val, pred_fusion)) if rmse best_rmse: best_w, best_rmse w, rmse print(fbest weight: {best_w:.1f}, rmse: {best_rmse:.2f})如果不想手动搜权重可以把两个模型的预测结果当作新特征再训练一个XGBoost做stacking。元模型会学到何时更信任XGBoost、何时更信任LSTM在这个任务上通常比固定权重再提升一点。6.2 验证指标与退化检查RMSE不是唯一标准单一指标不够。我会同时打印RMSE、MAE和R2并按浓度分段计算误差。低浓度段RMSE小但百分比误差可能很大高浓度段MAE大但百分比误差可能更合理。评估的重心要放在“预测曲线是否跟得上峰谷变化”而不只是数字好看。检查退化的实用技巧把验证集的预测值和实测值按时间画在一起出现三种情况分别排查——曲线明显滞后查目标时间对齐曲线整体偏低查训练数据季节覆盖范围曲线在峰处扁平查目标分布偏斜问题。我自己的习惯是XGBoost和LSTM单独训练完先各自评估得到基线融合只在验证集上明显优于两个基线时才上线每次重训都重新搜索融合权重因为权重本身也会随季节漂移。污染物预测没有一劳永逸的配置数据分布一变上周的参数这周就可能成为翻车来源。回归模型在这种任务里的血泪经验就是模型结构只决定上限数据切分、目标对齐和重训练频率才决定你实际能拿到多少分。希望这套方案能帮你在自己的污染物浓度预测项目里少走一段弯路。本文还有配套的精品资源点击获取