LeakDB实战指南:从数据预处理到CNN泄漏检测模型落地

发布时间:2026/10/7 2:55:26
LeakDB实战指南:从数据预处理到CNN泄漏检测模型落地 简介LeakDB是面向配水网络泄漏检测研究的真实基准数据集基于现实管网结构模拟多种泄漏场景从微小渗漏到显著破裂均有覆盖同时考虑管道材质、直径、流速及环境因素为验证和比较泄漏定位与量化算法提供标准测试环境。该压缩包共61个文件约74MB以30个csv数据表、8个mat矩阵文件、7个m脚本和4个py脚本为主体另含inp管网模型、pdf文档及基准测试说明完整呈现数据集生成、算法实现和评分对比的链路目录按基准、检测算法、数据集生成等模块组织便于按需查阅。已有644人学习适合高校研究人员、算法工程师与水务信息化从业者。借助内置MATLAB评分函数读者可系统评估精度、召回率、F1分数等指标理解不同泄漏检测方法的适用条件并可基于可扩展源码进行二次开发加速算法迭代。1. LeakDB给配水网络泄漏检测算法准备的“标准试卷”配水网络的泄漏诊断最缺的不是算法而是带标签的数据。真实管网里泄漏位置和泄漏时间往往要等开挖之后才能确认且记录分散在工单系统里研究者很难拿到一份“在哪漏、漏多大、何时开始”都齐全的标注数据。LeakDB正是为解决这个困境而生的泄漏诊断基准数据集它基于EPANET水力仿真在多个真实配水管网拓扑上注入不同位置和强度的泄漏输出带完整场景标签的压力与流量时间序列。对做泄漏检测、泄漏定位、管网状态估计的从业者来说LeakDB是目前最适合快速验证算法思路的公开数据源之一。这篇文章会按我实际使用这套数据的路径从数据结构、预处理、模型训练一路讲到踩坑记录与迁移思路。2. 先吃透LeakDB的数据结构泄漏场景、传感器和标签怎么串起来2.1 目录组织、文件格式与单位确认拿到LeakDB之后别急着跑模型。我第一次上手时直接去找CSV文件结果发现不同网络的目录层级并不一致有的网络下方直接平铺放文件有的则套了子目录手翻半天才找到数据位置。一般数据集的根目录下会有管网模型文件常见的是EPANET的.inp格式里面定义了管网拓扑、管道参数、节点基准需水量和用水模式传感器读数文件则按场景分文件夹存放每个文件对应一个网络场景。建议第一步先把整体文件清单打出来形成索引免得每次换场景都重新翻目录import os data_root LeakDB for dirpath, dirnames, filenames in os.walk(data_root): if filenames: print(f{dirpath}: {len(filenames)} 个文件) print( 样例:, filenames[:5])这段代码用os.walk递归遍历目录把每个文件夹下的文件数和前几个文件名打印出来。这样做的好处是能快速看出哪些是传感器读数、哪些是场景映射、哪些是配置文件。拿到CSV后的头等大事是确认压力单位。LeakDB中压力单位可能是米水头而EPANET在部分导出场景下会直接给出帕斯卡二者相差约9800倍。如果你不做换算就把数据喂给模型训练loss会直接爆掉且排查起来非常痛苦。我的做法是先从.inp文件的[OPTIONS]段查看单位声明再结合某几个节点的基准压力值做交叉验证。比如一个正常节点压力是28米水头如果读出来的数值接近28而不是274400那就是米水头否则就是帕斯卡。确认单位后在预处理函数入口加一道数据范围校验超出正常区间直接中断并告警。2.2 泄漏注入机制发射器系数与场景标签搞清楚泄漏是怎么被制造出来的才能正确解释标签。LeakDB中泄漏的仿真方式通常是在某个节点上挂EPANET发射器emitter泄漏流量与节点压力的关系近似满足Q C * P^γ其中C是发射器系数γ取0.5时对应孔口出流公式。C越大泄漏越严重节点压降越明显传感器上能看到的信号也就越清晰。这种注入方式直接决定了标签不是单纯的0/1而是带有三个维度泄漏节点编号、泄漏强度由C决定、以及泄漏是否持续整个采样周期。做监督学习前我会把场景编号翻译成结构化表格import pandas as pd scenarios pd.read_csv(LeakDB/Net1/scenarios.csv) scenarios[is_leak] (scenarios[emitter_coeff] 0).astype(int) scenarios[severity] pd.cut( scenarios[emitter_coeff], bins[-0.001, 0.005, 0.02, 0.1], labels[no_leak, small, medium, large] ) print(scenarios.groupby(severity).size())这里的pd.cut按发射器系数把泄漏强度离散成四个档位。离散化不是为了丢信息而是为了在后评估时能按泄漏强度分层看指标。一套算法如果只在“large”档位上拿到95%的检测率参考价值不大小泄漏才最考验算法。另一个需要重点处理的是泄漏节点编号它会做为定位任务的标签。因为不同网络节点命名方式不同我会统一转换成整数编码避免模型把节点ID当成连续数值去理解。场景中泄漏发生的时间和持续时间也要仔细核对。有些场景的泄漏从仿真开始就注入有些则在中途某一时间步才打开后者的检测难度更高因为需要算法在时间维度上识别突变点。这两种场景在标签上如果混在一起模型很容易产生误判。2.3 传感器布局与采样参数的选择逻辑LeakDB各网络的预设传感器位置和数量差异很大。传感器太少泄漏信号可能够不到任何一个传感器传感器太多特征维度上升训练开销变大且相邻传感器高度相关。我建议先沿用数据集官方默认配置跑通基线再尝试增删传感器观察指标变化这样能直观感受“传感器数量对检测率的影响”。# 读取传感器位置信息常见为CSV格式 sensors pd.read_csv(LeakDB/Net1/sensors.csv) print(sensors[[sensor_id, node_id, x, y]].head())x和y是传感器所在节点的坐标在做定位结果可视化和距离误差计算时会用到。传感器的空间覆盖密度直接影响定位精度上限传感器越靠近泄漏点压力梯度的空间分辨力越强。采样间隔也是影响模型上限的重要一环。常见间隔有1分钟和5分钟两种。1分钟数据能保留更多瞬态信息让算法更早察觉泄漏5分钟数据更平滑噪声小但检测延迟更大。第一次上手建议先用5分钟间隔跑通全流程因为高采样率会让数据量成倍增长等你还在排查代码bug时训练时间已经拉得很长了会严重影响调试效率。采样间隔还会影响滑窗的物理含义同样是120个时间步的窗口1分钟数据对应2小时5分钟数据对应10小时。不同的窗口长度对模型能捕捉到的水力动态范围有很大影响后面选窗口大小时要结合采样间隔一起考量。3. 用LeakDB做检测从原始波形到训练样本的预处理全流程3.1 去噪、滑窗与三维样本构造预处理这一步直接决定模型能吃到多少有效信息。虽然LeakDB是仿真生成的但传感器读数依然带有压力波动主要来自用水模式的昼夜变化以及EPANET求解器在瞬态切换时产生的数值振荡。我采用的标准流程是滑动平均去噪再滑窗切出固定长度样本。import numpy as np import pandas as pd from scipy.ndimage import uniform_filter1d def build_windowed_samples(df, window_size120, stride30): 从压力时间序列构造滑窗样本。 - df: 传感器读数shape(T, n_sensors) - window_size: 每个样本包含的时间步数 - stride: 相邻样本的滑动步长 返回: X (n_samples, window_size, n_sensors), timestamps smooth df.apply(lambda col: uniform_filter1d(col, size5)) X, ts [], [] for i in range(0, len(smooth) - window_size, stride): X.append(smooth.iloc[i:i window_size].values) ts.append(smooth.index[i window_size]) return np.asarray(X), np.asarray(ts)这里uniform_filter1d做对称滑动平均size5意味着每个点取前后各2个相邻点求均值。之所以不用普通FIR低通滤波器是因为对称平均不会引入相位偏移压力波形的波峰波谷位置不会在时间轴上被推后。如果你用了带相位延迟的滤波器泄漏发生时压力下降的斜率会被抹平直接影响模型对泄漏起点的判断。window_size和stride是关键参数。当采样间隔为5分钟时window_size取120对应10小时历史窗口长度足以覆盖从正常到泄漏后压力重新稳定的大部分动态过程stride取30意味着相邻样本有90步重叠样本量充足但重叠会导致样本间不独立。滑窗之后要记录每个样本对应的场景ID和起始时间后面划分数据集时严格按场景分否则会产生数据泄露。3.2 基线与残差特征消除用水模式干扰的常用做法绝对压力值在配水管网里受用水模式影响极大早晚高峰压力低夜间压力高。如果直接用原始压力当特征模型学到的可能大部分是“当前是几点钟”而不是“有没有泄漏”。我几乎必做的一步就是构造残差特征观测值减去无泄漏条件下同时刻的期望压力。base_df[hour] pd.to_datetime(base_df[timestamp]).dt.hour baseline_profile base_df.groupby(hour)[[P1, P2, P3]].mean() leak_df[hour] pd.to_datetime(leak_df[timestamp]).dt.hour residual leak_df[[P1, P2, P3]] - leak_df[hour].map(baseline_profile)关键点在于按小时对齐而不是按绝对时间对齐。配水网络用水模式是日周期性的同一个小时间窗内基线均值才可比。不按小时对齐直接相减残差里会混入昼夜波动模型学到的全是伪特征。残差特征有时会携带泄漏的间接影响泄漏导致上游压力下降下游节点的用水量也会被动变化这些级联效应在残差中留下的痕迹反而有助于定位。实际测试中残差特征加上原始压力拼接成的多通道输入比单独使用任何一种特征效果都好。做法是在构造样本时把原始压力、残差、以及压力变化率一阶差分三个通道拼在一起让模型自己决定用哪部分信息。3.3 训练/验证/测试划分怎么才算“不越界”数据切分是LeakDB使用中最容易埋雷的环节。场景是按节点和强度排列的如果随机切分样本同一个泄漏场景的前半段进了训练集、后半段进了测试集模型实际上见过了这个泄漏位置测试指标自然虚高。正确做法是按场景ID分组切分同一个场景的所有样本完整落在一侧。from sklearn.model_selection import GroupShuffleSplit gss GroupShuffleSplit(n_splits1, test_size0.2, random_state42) train_idx, test_idx next(gss.split(samples_df, groupssamples_df[scene_id]))GroupShuffleSplit的核心参数就是groups在这里传入scene_id后sklearn会保证同一个组的样本不会被拆散。我还会在训练集内部用同样的方法再切出验证集而不是随机抽样。如果训练集和验证集之间有场景重叠你在调参时看到的验证集成绩会偏乐观最终测试集结果会打回原形。报告中我建议把测试集再拆成两个子集一个是“已见节点但强度不同”的场景另一个是“完全未见过的泄漏节点”。LeakDB上的unseen nodes成绩更接近真实部署预期——实际管网中你永远不知道下一个漏点在哪里。能在这两个子集上同时表现良好的模型才是真正值得带出实验室的候选方案。4. 在LeakDB上跑通泄漏定位CNN方案的参数、训练与评估4.1 为什么选CNN而不是阈值法阈值法在LeakDB单网络上能跑出不错的检测率因为仿真环境比真实管网“干净”压力基线是确定的。但阈值法有个致命短板阈值需要在每个网络调一次Net1上调好的压力阈值的值搬到Net2上可能完全失效。更现实的问题是阈值法只能报“有泄漏”很难报出“泄漏在哪个节点”。CNN不需要手工设阈值它直接从波形中学习压力模式输出层既可以是二元分类也可以是多节点分类直接输出定位结果。工程层面还有一个选CNN的理由LeakDB样本是三维结构(batch, window_size, n_sensors)天然适配卷积网络。一维卷积在时间维度上扫每个传感器相当于一个独立通道结构简洁、参数少、不容易在中等规模数据上过拟合二维卷积把时间-传感器矩阵当图像处理能建模传感器之间的空间关联但需要更多训练数据和调参技巧。我的通常做法是先用一维卷积跑通流程如果检测率不够再升级到二维卷积做空间建模。4.2 CNN模型结构、超参数与训练代码下面是我实际跑通的一维CNN泄漏分类模型输入一段滑窗压力序列输出是否存在泄漏的概率。import torch import torch.nn as nn class LeakCNN1D(nn.Module): def __init__(self, n_sensors, seq_len120): super().__init__() self.conv1 nn.Conv1d(n_sensors, 32, kernel_size7, padding3) self.conv2 nn.Conv1d(32, 64, kernel_size5, padding2) self.pool nn.MaxPool1d(2) self.fc nn.Linear(64 * (seq_len // 2), 1) def forward(self, x): # x shape: (B, n_sensors, seq_len) x torch.relu(self.conv1(x)) x self.pool(torch.relu(self.conv2(x))) x x.flatten(1) return self.fc(x).squeeze(-1)这段模型的输入维度需要特别注意。build_windowed_samples返回的X形状是(n_samples, seq_len, n_sensors)而nn.Conv1d期望的输入是(batch, channels, length)所以训练前必须做一个transpose把传感器维度挪到通道位。第一层卷积的输入通道数是n_sensors每个传感器波形独立过卷积核第二层卷积把多传感器信息混合。kernel_size7让卷积核一次覆盖7个时间步能捕捉短时压力突降。池化层把序列长度减半全连接层映射到单个输出节点最后通过sigmoid得到泄漏概率。训练参数也是调出来的几组关键配置值见下表参数取值说明优化器AdamW权重衰减设1e-4收敛比Adam稳学习率3e-4配合CosineAnnealingLR做衰减batch_size64小数据集上64比128更稳定训练轮数30LeakDB单网络数据量不大30轮足够损失函数BCEWithLogits数值稳定不需要额外sigmoid类别权重正类权重1.5无泄漏样本远多于泄漏样本时训练循环本身不复杂但我建议每个epoch结束后在验证集上评估AUC保存验证集AUC最高的权重作为最终模型而不是沿用最后那个epoch的权重。我在早期直接拿最后一个epoch的权重做测试结果因为训练末尾学习率震荡测试指标波动了2到3个百分点这种代价完全可以避免。4.3 评价指标检测率、误报率与定位精度LeakDB研究里检测率通常定义为正确检出的泄漏场景数除以总泄漏场景数误报率则是在无泄漏时间窗中出现误报警的比率。这两个指标本质上是权衡关系你放低报警阈值检测率上去了误报率也跟着涨。我习惯用F1分数做综合判断同时把二者分开报告因为水司运营场景中每一次误报都对应一次真实的现场核查成本误报率控制不住再高的检测率也没法落地。定位任务的评估不应只看“预测节点是否等于真实节点”。LeakDB上传感器数量有限精确命中节点的概率很低更合理的做法是计算预测节点与真实节点之间的管网图距离import networkx as nx def graph_distance(G, pred_node, gt_node): try: return nx.shortest_path_length(G, pred_node, gt_node) except nx.NetworkXNoPath: return float(inf)这里G是把EPANET的.inp文件节点与管道解析后建立的图结构。随后统计“距离小于500米的预测占比”作为定位成功率或者绘制距离误差的累积分布曲线。一个定位模型如果能在500米范围内达到70%以上的命中率在真实管网里已经具备实际巡检参考价值。5. LeakDB实战避坑5个让检测结果不再是玄学的关键教训5.1 现象同一份数据不同论文的检测率能差30个百分点原因多出在“泄漏样本”的判定口径不一致。有的论文把泄漏注入后的全部时间步都当作正样本有的只取泄漏达到稳态后的时段。前者把泄漏初期的瞬态波动也算进了正样本模型可以轻松学到压力突降的特征指标自然偏高后者剔除了瞬态样本更难指标偏低但更真实。解决方式是做实验前明确定义正样本的起止规则如果是“泄漏发生后第30个时间步开始”就按这个标准来复现别人结果时先读透他们的样本定义再对齐自己的口径。5.2 现象模型在测试集上AUC很高换一个网络性能崩盘最大嫌疑是数据泄露。滑窗重叠导致的样本相似性是头号原因——当stride小于window_size时相邻样本大部分重叠如果这些重叠样本一部分进了训练集、一部分进了测试集模型等于在“背题”。解决方式就一条按scene_id分组划分数据并确保测试集里的场景ID在训练集中完全不存在。我后来还会再补一层防护——在每个场景内把最后20%时间段单独划走作为跨时间测试集因为真实管网的压力模式会随时间漂移如果模型连“同场景延后时段”都预测不准上线也不会稳。5.3 现象小泄漏检测率几乎为0模型只会报大泄漏小泄漏的定义是发射器系数C很小产生的压降可能不足0.5米水头这个量级的信号完全淹没在正常用水波动里。模型做整体loss优化时小泄漏样本数量占比低、梯度贡献小自然被忽略。我验证过两个有效的补救措施一是在损失函数里按泄漏强度给样本加权系数越小的泄漏权重越大二是评估时按severity分组报告指标不要只看平均值。在LeakDB上small档位的检测率能做到50%以上已经是相当强的算法了。5.4 现象重复训练同一模型结果忽高忽低问题基本出在随机性没有控制住。PyTorch训练中随机初始化、数据shuffle顺序、dropout都会引入波动。解决方式是固定全局随机种子并注意在评估前把模型切回eval模式否则dropout层在推断时也在起作用输出就会抖。报告最终指标时最好用三个不同随机种子独立训练输出均值加减标准差这个习惯能大幅提升结果可信度。def set_seed(seed42): np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True固定cuDNN的deterministic会让训练略变慢但LeakDB数据量小开销完全可以接受。三个种子跑完也就多花十几分钟换来的是可以在论文或汇报里理直气壮地写“均值±标准差”这项投入很划算。5.5 现象压力单位混用loss在训练初期就发散这个坑很初级但非常常见。如果你从两个不同来源拼数据一部分压力值是以米水头为单位另一部分是以帕斯卡为单位量纲差约9800倍模型根本学不进去。解决方式是在预处理入口加一道单位校验护栏压力值落在0到100这个区间视为米水头落在10万附近则视为帕斯卡按比例换算。代码里加一个断言超范围直接报错比靠注释提醒自己可靠得多。LeakDB内部单位一致性较好但你要拼接自己的现场数据时这个护栏能拦下很多潜在问题。6. 把LeakDB上验证过的模型迁移到真实管网差距与补偿思路LeakDB上跑得再好最终还是要面对真实管网。从我自己的项目经验来看LeakDB上训练好的模型迁移到真实压力数据时检测率通常会掉10到20个百分点。原因集中在三方面真实压力噪声更大、真实泄漏的物理过程比仿真更复杂、现场传感器存在时序漂移和数据缺失。所以我的习惯是把LeakDB当“算法筛选器”而不是最终考试场地。在LeakDB上快速淘汰不适合的模型族和超参范围选出最有希望的选手再拿到真实数据上二次验证这样试错成本最低。迁移时有几个补偿手段我实际验证过有效。第一收集现场无泄漏时段的压力数据重新计算残差基线替换LeakDB训练时用的基线均值这一步能显著消除管网间用水模式差异带来的特征偏移。第二在LeakDB样本上叠加现场采集到的噪声形态用高斯噪声或用水模式随机扰动做数据增强让模型提前见过噪声不至于上线后被各种毛刺吓出误报。第三把深度模型的输出概率与传统的压力阈值判断做融合策略两个信号同时报警时再派单核查能有效把误报率压制到水司能接受的范围。还有一个小技巧更换新管网后先在无泄漏数据上跑一遍模型的误报率曲线确认误报率达标后再开启预警模式。这一步相当于给模型写体检报告。没有这一步就直接上线出了事故你会被一线同事盯着很久。数据集的规范使用和工程习惯是两码事LeakDB帮我练出来的恰恰是后者。希望这套从数据到落地的路径能帮你在泄漏诊断方向上少走几步弯路。本文还有配套的精品资源点击获取