资讯动态

基于ConvLSTM的伦敦空气质量预测:数据处理全流程详解

发布时间:2026/10/15 5:09:35 来源:尧图企业网站定制
“基于ConvLSTM的伦敦空气质量预测”这个项目我是从一鼻子灰开始的。拿到原始监测数据的那一刻我意识到真正的战场根本不在模型搭建而在一整套磨人的数据处理流程。ConvLSTM要求的是时空网格输入但手头摆着的是十几个站点、几十万行的表结构数据中间还夹杂着设备故障带来的空洞和哨兵值。这篇先不聊模型把数据处理这条线彻底捋清楚毕竟喂给模型的东西不对后面的预测精度全是空中楼阁。先交代一下项目背景这是一个用ConvLSTM对伦敦地区小时级空气质量做时空预测的任务目标是用过去12小时的多站点观测数据预测未来6小时的关键污染物浓度。这个系列第一篇重点解决数据从“原始csv”到“可训练的四维张量”之间的所有环节包括站点网格化、缺失值修复、特征构造、归一化和滑动窗口采样。适合正在做时序预测、尤其是准备上手ConvLSTM或者STGCN这类时空模型的读者参考。1. 项目背景与模型选型逻辑1.1 为什么选空气质量预测当切入伦敦的空气质量监测体系覆盖密度较高公开可拿到的历史数据时间跨度长、指标相对齐全对做时间序列的人来讲确实是一块比较理想的试验田。但“成熟”不等于“干净”。监测设备隔三岔五要维护传感器标定偶发漂移某站点某几天数据整段消失这类问题在真实数据集里随处可见反而比教科书里那些整理得整整齐齐的数据更能暴露一个工程师处理脏数据的能力。选这个场景还有一个很实际的原因污染物在空间上是连续扩散的在时间上是累积变化的天然属于时空双重依赖的数据形态。如果只用普通LSTM模型完全没有空间的概念每个站点的序列被当成独立样本风速一吹污染物从A站点飘到B站点这种空间传播关系它根本学不到。而纯CNN又只能处理局部空间特征对时间维度上的长期依赖无能为力。两者结合的ConvLSTM正好卡在这个任务的点子上。1.2 ConvLSTM的核心机制与数据处理需求ConvLSTM是2015年提出的时空序列预测结构它的核心做法很直接将LSTM内部状态更新时用到的全连接矩阵乘法替换成卷积操作。你可以这样理解普通LSTM在记忆和遗忘信息时每个时间步用的是向量乘权重它不管输入数据的邻居是谁而ConvLSTM在做同样的记忆更新时用的是卷积核扫过一片区域既知道当前格子自己的状态也知道它周围格子的状态。这样一来模型在时间维度上保留了LSTM的长期依赖能力在空间维度上又继承了CNN的局部感受野特性。这个模型特性直接决定了数据处理的最终目标形态你要把所有监测站点和气象数据折叠成一个四维张量[batch, timesteps, height, width, channels]。height和width是空间网格的维度channels对应的是PM2.5、NO2、风速、温度这些特征。也就是说原始数据必须从“一行一个站点一个时刻”的表格结构转成“每个时刻一张空间特征图”的序列结构。这一步转不好模型根本跑不起来这也是为什么数据处理在这个项目里要单独开一篇来讲。2. 数据源摸底与质量评估2.1 数据采集与字段构成数据来自伦敦地区公开的空气质量监测网络采集方式有两种选择一种是通过平台提供的API接口按小时拉取实时数据另一种是直接下载历史数据包。考虑到项目需要三年左右的完整历史数据来做训练和验证API逐小时拉取效率太低我直接选择了历史数据包的方式一次性把全部站点数据落盘。字段大致分成两类。第一类是污染物浓度PM2.5、PM10、NO2、O3单位基本都是微克每立方米。第二类是气象因子气温、相对湿度、风速、风向、气压。气象数据不一定能从同一个数据源拿齐可能需要和天气历史数据做多源拼接。这里有个必须提前确认的细节NO2和O3的原始数据有时以ppb为单位需要按温度和气压做换算转成µg/m³换算公式是浓度(µg/m³) 浓度(ppb) × 分子量 ÷ 摩尔体积其中摩尔体积随温度气压变化。如果忽略了这一步单位不一致的特征会被模型当成两类完全不同尺度的输入预测结果直接跑偏。2.2 数据质量初步摸查拿到原始CSV后做的第一件事不是写清洗脚本而是先出数据体检报告。逐个站点、逐个字段检查四项指标缺失率、唯一值数量、数值分布范围、时间戳残差间隔。做这件事的经验是有些问题只靠简单统计是看不出来的必须画曲线。比如某站点PM2.5小时均值曲线整体形态正常但抽查原始数据时发现里面夹杂着-999这样的哨兵值这是设备维护期间系统自动写入的异常标记。如果不事先排查这个-999会被当成真实浓度参与训练模型会莫名其妙学到“某些时候浓度骤降到负值”的错误规律。再比如风向字段如果直接统计数值范围是0到360看着没毛病但这类环形变量不能直接当普通数值喂给模型后面特征工程部分细说。3. 数据处理完整实操3.1 异常值识别与缺失值分层修复这部分是整个数据流程中最耗时、最容易返工的地方。先说异常值的处理原则哨兵值和超出物理合理范围的值统一转成NaN再走缺失值流程。判断物理合理性可以设阈值比如PM2.5超过1000µg/m³、气温超过50摄氏度这类的明显越界一律认为传感器异常。缺失值修复我按三个层级从下往上处理第一层硬删。如果某个站点整体缺失率超过30%或者连续缺失窗口超过72小时直接把这个站点或这段窗口丢弃。缺失比例太高时任何填充策略都等于在造数据与其引入大量虚假信息不如损失一段样本。第二层时间插值。对单点或者不超过6小时的短窗口缺失用前后有效值做线性插值。更稳的做法是用同站点前12小时数据的加权中位数填充抗噪能力比线性插值好尤其是污染物浓度本身波动剧烈的情况下线性插值的两端是坏点时会制造尖锐毛刺。第三层空间修复。某个站点整段缺失但相邻站点正常时根据站点坐标计算距离用距离加权的邻站数据做填补。这一步需要提前维护好站点经纬度表。这里有一个必须盯死的红线不能用全时段插值。比如某站点7月1日上午缺了6小时数据做插值时只能用7月1日前后的局部数据绝不能拿到整月甚至整年数据去拟合一条曲线然后补进去——那就把未来信息焊死在历史数据里了模型在验证集上会给你一个漂亮但虚假的成绩。# 数据清洗示意代码以某个站点为例 import pandas as pd import numpy as np df pd.read_csv(station_01.csv, parse_dates[timestamp]) df df.set_index(timestamp).sort_index() # 哨兵值和物理越界值转为NaN df.replace(-999, np.nan, inplaceTrue) df[pm25] df[pm25].mask(df[pm25] 1000) # 连续缺失窗口超过72小时的整段丢弃 def drop_long_gaps(series, max_gap72): mask series.isna().astype(int) gap_length mask.groupby((mask ! mask.shift()).cumsum()).cumsum() series series.mask((mask 1) (gap_length max_gap)) return series df[pm25] drop_long_gaps(df[pm25]) # 短窗口缺失用前12小时加权中位数填充 for col in df.columns: df[col] df[col].fillna(df[col].rolling(12, min_periods1).median())3.2 时空网格构建这是ConvLSTM数据处理和普通LSTM最大的分水岭。普通LSTM只需要把每个站点的序列拼成二维矩阵但ConvLSTM要求每个时刻都有空间结构。具体做法是拿到所有站点的经纬度坐标划定一个矩形包围盒把空间切成固定大小的网格比如4乘4或者8乘8。每个站点按坐标落到最近的网格中心落在同一格子里的站点取均值。网格尺寸的选择要权衡。网格太细比如切成20乘20大部分格子是空的输入矩阵稀疏度极高模型学的空间特征大部分是无意义插值网格太粗比如2乘2空间分辨率被抹平站点间的传播关系直接丢失。我当时试下来项目里大约10个有效站点的情况4乘4是比较平衡的选择至少保证多数格子有站点落入空白格不至于太多。网格内无站点的位置有两种处理方式一种是置零配合后续Mask机制在模型里忽略另一种是用周围站点的反距离加权插值填值。我实际取的是后者因为它保留了空间连续性也让模型在卷积时不会因为大片零值产生伪特征。网格构建完成后每条时间记录就对应一张4乘4乘C的空间特征图。# 站点坐标网格化示意 import numpy as np grid_size 4 lats np.array([51.45, 51.50, 51.48, 51.52, ...]) # 虚构站点纬度 lons np.array([-0.12, -0.10, -0.08, -0.14, ...]) # 虚构站点经度 lat_edges np.linspace(lats.min(), lats.max(), grid_size 1) lon_edges np.linspace(lons.min(), lons.max(), grid_size 1) lat_idx np.digitize(lats, lat_edges) - 1 lon_idx np.digitize(lons, lon_edges) - 1 # 将多个站的时刻表数据聚合到网格 # 假设features形状为 (num_stations, num_timestamps, num_channels) grid_data np.zeros((num_timestamps, grid_size, grid_size, num_channels)) for s in range(num_stations): grid_data[:, lat_idx[s], lon_idx[s], :] features[s]3.3 特征工程与归一化原始字段不能直接全塞进模型有几个特征必须先做转换。风向是环形变量0度和360度实际是同一种状态如果直接把角度当数值输入模型会认为0和360相差巨大完全扭曲空间含义。处理方式是拆成两个新特征wind_x sin(wind_dir)和wind_y cos(wind_dir)用点在单位圆上的坐标表示方向。这样既保留了角度之间的真实距离关系也避免了环形断裂问题。时间特征同样要处理。小时在一天中是周期的凌晨0点和深夜23点距离很近直接拿小时数值喂模型会导致周期性丢失。我的做法是把“小时”拆成sin(2π * hour / 24)和cos(2π * hour / 24)星期几同理拆成周期特征。这套周期编码几乎是时序预测项目的必选项。归一化方面首选MinMaxScaler因为ConvLSTM配合ReLU类激活函数对负值输入也算友好但StandardScaler同样可用。关键是归一化参数只能在训练集上拟合再应用到验证集和测试集。很多人图省事对整个数据集统一fit这会让验证集和测试集的均值方差信息泄漏到训练流程里等于考试前偷看了答案线上部署时模型表现立刻现原形。每个特征通道要独立归一化污染物浓度和气象因子数值范围差异太大共享一套缩放参数会导致小数值特征被压扁。from sklearn.preprocessing import MinMaxScaler # 假设grid_data形状为 (T, H, W, C)重塑为二维做fit T, H, W, C grid_data.shape flat grid_data.reshape(-1, C) scaler MinMaxScaler() flat_scaled scaler.fit_transform(flat) # 仅用训练集拟合 grid_data_scaled flat_scaled.reshape(T, H, W, C)3.4 滑动窗口采样与数据集划分网格构建完成之后数据形态已经变成(时间步数, 高度, 宽度, 通道数)的四维张量但还不能直接训练。ConvLSTM的输入输出都是序列必须用滑动窗口切样本。窗口设计我用的配置是输入过去12小时预测未来6小时滑动步长1小时。切出来的样本X形状为(样本数, 12, H, W, C)Y形状为(样本数, 6, H, W, 1)其中最后的1是目标污染物通道。滑动窗口有三个细节必须注意。第一样本和样本之间高度重叠12小时输入窗口滑1小时会产生大量相似样本这是通过数据增强的方式提升训练稳定性但也意味着不能把切好的样本随机打乱后划分训练集和验证集否则相邻样本几乎同源验证集指标没有意义。第二步长可以适当调大比如滑2小时或3小时减少样本冗余度、压缩训练时间代价是样本总量下降这轮取舍视训练时长而定。第三预测目标可以选择多个污染物通道但如果一次预测太多目标会导致模型更难收敛第一个版本建议先从单目标做起。def create_sequences(grid, input_len12, pred_len6, step1): T grid.shape[0] X, Y [], [] for start in range(0, T - input_len - pred_len 1, step): x grid[start:start input_len] y grid[start input_len:start input_len pred_len, ..., 0:1] X.append(x) Y.append(y) return np.array(X), np.array(Y)数据集划分不按样本数比例来而是按时间顺序切分。比如三年数据约26000多小时切掉前70%做训练、15%做验证、15%做测试三块数据在时间上严格不重叠模拟“用过去预测未来”的真实场景。如果数据量不够可以考虑用滚动预测式的验证而不是一次性切分但样本重叠问题会让这个方案复杂不少。4. 常见问题与排查技巧实录4.1 时间戳对齐的暗坑污染物监测数据和气象数据来自不同数据源时时间戳对齐问题非常隐蔽。两者采样点看起来都是整点但一个是整点整分另一个可能是整点过15秒更麻烦的是有些数据源用半点整点交替merge时直接按时间戳硬拼接会造成一小时错位。我当时排查这个问题是靠画交叉相关图定位的把两个数据源的风速序列做滞后相关性分析发现滞后0时相关性不是最高滞后1小时才是立刻意识到某一路数据的时间基准有偏移。建议merge之前统一做时间规整把时间戳floor到小时并统一时区。4.2 数据泄露的隐形入口这个项目里数据泄露有三个常见入口全部踩过一遍之后我总结成清单归一化参数用了全数据集拟合测试集统计量进入训练流程。缺失值用了全时段插值未来信息混进历史窗口。滑动窗口样本在时间上重叠且训练集和验证集切分之前没有保证时间序列连续性。第三点特别值得展开。很多人在切完滑动窗口之后习惯性地用train_test_split随机切分。样本经过滑动窗口处理后时间维度被打散随机切意味着验证集里混入大量与训练样本时间重叠的“近亲样本”模型验证分数会异常漂亮。正确做法是先在原始时间序列上切出三段让训练集、验证集、测试集在时间上严格先后排列然后再各自做滑动窗口。4.3 站点稀疏与传感器漂移问题站点稀疏问题在真实监测网络中几乎无法回避尤其是一些新区块的站点可能只运行了一年半载历史深度和其他站点差了一大截。处理方案没银弹我的做法是给每个站点记录数据覆盖的起止时间网格聚合时对覆盖率极低的站点单独剔除避免拉低整体数据质量。网格内空格插值时也要注意插值权重需要根据邻站覆盖率动态修正。传感器漂移是个容易被忽略但影响极大的问题。某个站点在某个时间节点换了传感器数值可能整体偏移几十个百分点。这种系统性偏移在统计表里几乎不可见因为均值和方差变化不大但模型训练时会学到“同一天里污染物数值突然跳变”的错误模式。我在处理时按月份分段计算各站点均值把均值突变超过合理阈值的站点的对应时间窗直接标记为异常段再走缺失值流程处理。5. 实操总结与经验沉淀整套数据处理跑完之后我最大感受是这个项目的成败有一大半在数据准备阶段决定了。从拿到原始CSV到产出可以直接喂给ConvLSTM的npy文件前后花了两周中间还返工了两次一次是单位换算一次是网格稀疏度问题。但这两周时间花得非常值排查问题练出来的手感比调参熟练度重要得多。最后分享一个实打实的小技巧给每个处理版本打上标签比如v1_raw、v2_clean、v3_grid、v4_norm。每跑完一轮实验把模型指标和对应的数据版本记录对应起来。这样当模型效果出现意外时你能快速判断是数据问题还是模型问题而不是在几十个文件和参数之间大海捞针。后续我把ConvLSTM模型结构接上这批数据之后PM2.5预测任务上明显优于普通LSTM基线这部分模型搭建和训练的细节下一篇继续展开。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑