ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

非侵入式负荷监测入门:用NILMTK和REDD数据集快速跑通负荷分解

非侵入式负荷监测入门:用NILMTK和REDD数据集快速跑通负荷分解 这问题我朋友问过我好几回家里电表只有一个想分开看空调、热水器、冰箱各耗多少电有没有办法答案就是非侵入式负荷监测NILM而想快速上手这个方向社区里最常用的工具链就是NILMTK配上MIT发布的REDD数据集就能把“从总功率里拆出单个电器功率”这件事真正跑起来。我最近又把这套流程从头到尾走了一遍发现只要环境处理得当5分钟跑通第一个分解模型不是夸张说法。这篇就把完整步骤、代码和那几个没人提醒的坑一次写清楚。1. 负荷分解在解决什么问题一个总表拆出全家电器的用电画像1.1 非侵入式负荷监测到底是什么传统想知道“哪个电器费电”最直接的办法是给每个电器装智能插座或独立电表。这叫侵入式监测数据干净但成本高、布线麻烦家里十几个电器不可能全装。非侵入式负荷监测的思路则聪明得多只在入户总表处装一个采集点有的直接用智能电表数据通过分析总功率曲线的变化模式把每个电器的功率贡献分离出来。我习惯用混音来类比。录音棚里每个乐器都有独立音轨但最终混音输出的只是一条合成都轨道如果你只有这条合成都轨道能不能还原出吉他和鼓各自的声音NILM做的就是类似的事只不过输入信号是总功率时间序列输出是冰箱的开关状态、空调的运行功率、洗衣机的不同工作阶段。这个方向在智能家居、用电安全、需求响应、碳足迹统计里都有实际用途。比如用户只看得到月度总电量但想参加峰谷电价优化就必须知道哪个电器在高峰时段用电最多又比如电力公司想在不入户的前提下估计某小区空调负荷占比NILM也是一条现成路径。1.2 为什么选NILMTK和REDD这套组合NILMTKNon-Intrusive Load Monitoring Toolkit是伦敦帝国理工等机构主导的开源工具包专门为负荷分解研究设计。它把数据读取、预处理、算法实现、评估指标都封装起来了你不用从零开始写HDF5解析、不用自己造评估函数直接拿来就能跑基线实验。REDD数据集则是这个领域的“入门必修课”。它是2011年MIT发布的Reference Energy Disaggregation Dataset记录了美国6个家庭的用电数据既有高频的电压电流采样也有低频的总功率和各个房间/电器回路采样。REDD公开得早、格式稳定所以大量NILM论文都在它上面做基准你拿它跑出的结果很容易和别人的工作对比。把NILMTK和REDD放在一起相当于给你一套标准的实验台和一块标准的测试材料。对于第一次接触负荷分解的新人来说用这套组合跑通一个端到端流程比直接啃论文复现要快得多。2. 安装NILMTK能一次跑通的环境核心是把依赖版本按住2.1 为什么直接pip install会翻车网上很多教程一句话带过“pip install nilmtk”但真到执行时不少人会卡在编译环节或者导入环节。原因主要有两个层面。第一NILMTK的稳定版本依赖的pandas、numpy、scikit-learn版本都偏老某些新版numpy把旧API删掉之后NILMTK的源码一运行就报错。第二NILMTK里的部分算法包含Cython扩展编译时需要系统有可用的C编译器。你要是直接在一个干净的Python 3.11环境里pip install大概率会遇到numpy编译崩溃或者装完之后import nilmtk直接抛出 AttributeError。我的建议是不要硬刚最新环境老老实实用conda建一个干净的旧版本环境。这个环境不一定追求最老但至少要让pandas和numpy的API保持在NILMTK还能识别的范围内。实际跑下来下面这组锁定版本在多数机器上都能顺利安装成功conda create -n nilmtk python3.7 -y conda activate nilmtk pip install numpy1.19.5 pandas1.1.5 scikit-learn0.22.2.post1 pip install nilmtk如果你已经安装了较新的pandasNILMTK里的DataFrame操作很可能直接报找不到方法。所以安装顺序是先锁基础库再装nilmtk让pip基于你已经固定的版本库去解析剩余依赖。2.2 Windows和macOS下的额外注意事项Windows用户最容易踩的坑是Cython编译。打开开始菜单里的“Developer Command Prompt for VS”或者在安装Python时勾上C工具链否则pip过程中会在构建numpy扩展时卡住甚至报错。macOS用户则要留意如果系统里是用Homebrew装的Python编译器路径偶尔会找不到解决办法是装一下Command Line Toolsxcode-select --install装完之后可以用一个更保守的小测试来确认环境是不是真的可用。不要只import一遍就完事我通常会新建一个临时目录初始化一个空的HDF5文件并在里面写一段DataFrame模拟NILMTK的数据存取流程。import nilmtk import pandas as pd import h5py print(nilmtk import ok) df pd.DataFrame({power: [1.0, 2.0, 3.0]}) with h5py.File(test.h5, w) as f: f.create_dataset(test, datadf.values) print(hdf5 write ok)如果这两步都正常环境基本稳了。这里多说一句别偷懒跳过锁版本的步骤我见过太多人直接pip install然后卡在import环节浪费半小时最后回到老版本环境一分钟就解决。2.3 跑在Jupyter里的环境要单独确认如果你想在Jupyter Notebook里复现后面的流程记得确认notebook用的是当前conda环境的kernel。可以在容器里执行conda activate nilmtk ipython kernel install --user --namenilmtk然后从notebook右上角kernel菜单切到nilmtk。否则你明明在conda环境里装了NILMTKJupyter打开后却提示ModuleNotFoundError这个坑特别隐蔽。3. 数据准备把REDD原始文件转成NILMTK认识的HDF5格式3.1 REDD数据集长什么样REDD压缩包解压之后核心目录是low_freq和high_freq。对于入门来说基本只用low_freq目录因为它的采样频率低、文件小跑起来快信息量对大多数分解任务也足够。low_freq目录下按house_1到house_6分文件夹每个house文件夹下有一堆channel_*.dat文件外加一个labels.dat记录每个channel对应的电器类型。你不需要手工去解析这些.dat文件。NILMTK自带一个转换器专门把REDD原始格式转成统一的HDF5格式。这个环节能帮你省很多事但前提是数据目录结构不能被改动。如果你从网盘或别人那里传了一份REDD先检查一下low_freq下是否直接就是house_1、house_2这样的文件夹。如果外面还多了一层外壳目录转换器会找不到数据。3.2 执行转换一行代码从原始数据到redd.h5进入conda环境后在Python里执行from nilmtk.dataset_converters import redd_to_hdf5 redd_to_hdf5( data/REDD/low_freq, data/redd.h5, formatREDD )第一个参数是原始数据路径第二个参数是输出HDF5文件路径。format参数在部分版本里可省略但显式写出来更稳。整个过程会遍历每个house的每个channel把时间戳统一成UTC再把功率数据按采样周期存储。不同版本的NILMTK对这个函数的命名空间略有差异。如果你导入redd_to_hdf5时找不到符号可以去源码里翻一下nilmtk/dataset_converters/目录看看有没有redd.py或redd_convert.py之类的文件确认函数实际位置。我自己在某个版本里就遇到过需要写成from nilmtk.dataset_converters import redd的情况。转换时间取决于你机器速度和REDD文件大小低频数据一般几十秒到一两分钟。转换完成后你会看到一个redd.h5体积通常在几百MB到几个GB之间。3.3 转换后先检查再训练不要急着直接训练先加载一下数据看看NILMTK到底识别到了哪些建筑物和电器。这一遍检查能帮你提前发现数据缺失或电器名映射问题。from nilmtk import DataSet ds DataSet(data/redd.h5) print(ds.buildings)NILMTK会把每个house当作一个building所以输出通常是一个字典键为1到6。接着可以进一步查看house 1里有哪些测量通道elec ds.buildings[1].elec for meter in elec.meters: print(meter)这一步不是必须的但能让你明确知道冰箱、微波炉这些电器的通道编号因为后面的build_meter_group参数要参考这个信息。注意不同版本的NILMTK对building、meter、appliance的命名不完全一致。如果你的环境里没有buildings这个属性试试用ds.buildings改成ds.metadata或者直接打印dir(ds)看有什么可用API。重点不是死记API名而是理解这个工具包是把“数据集-建筑-电表-电器”这一层结构抽象出来了。4. 5分钟跑通第一个模型Mean基线法的完整代码4.1 为什么第一个模型选Mean而不是深度学习很多新人一上来就想跑LSTM、Transformer但我觉得入门阶段最该先跑的是Mean这种基线算法。所谓Mean法逻辑非常朴素在训练阶段记录每个目标电器的平均功率在分解阶段把总功率按比例或按启停时间分配给各个电器。它几乎不用训练几秒钟就能跑完但它的结果是一个非常重要的参照系。你不会第一眼就觉得Mean法分解得很准但它给了一个“最低底线”。后面你换成FHMM、CO或者其他更复杂的模型如果效果连Mean法都没超过那说明不是模型问题而是数据或者特征处理有bug。这是我在实际项目里反复得到的一个经验。4.2 训练集和测试集的划分逻辑做负荷分解实验时序数据的划分不能随便随机打乱应该用时间段切分。常见做法是取前半段时间做训练后半段做测试。我以house 1为例取2011年5月之前的数据训练5月之后的数据测试from nilmtk import DataSet from nilmtk.disaggregate import Mean train_ds DataSet(data/redd.h5) test_ds DataSet(data/redd.h5) train_ds.set_window(end2011-05-15) test_ds.set_window(start2011-05-15, end2011-05-30)这里有个细节NILMTK读取HDF5时用的是懒加载机制你设置了窗口后它只读取对应时间段的数据。先切窗口再取电器能避免把整年数据全部读进内存速度会快很多。4.3 构建主表和目标电器的测量组接下来要告诉NILMTK两件事模型从哪个总表读总功率以及要分解出哪些电器。以REDD house 1为例主表通常是channel 1/2所在的mains冰箱是某个具体的回路。不同版本的NILMTK里build_meter_group的签名略有不同但基本思路是train_elec train_ds.build_meter_group( mains[1], appliances[(refrigerator, 5)] ) test_elec test_ds.build_meter_group( mains[1], appliances[(refrigerator, 5)] )这里的数字5是我在house 1里记忆中的冰箱通道号真实环境可能会不同。所以前面第3.3节里查看meter列表的那一步就派上用场了你需要先把labels.dat里的电器名和通道号对应关系确认好。电器名有时候是“refrigerator”“microwave”“washer_dryer”这类标准名但不同house可能命名不完全一致。如果你的NILMTK版本不接受appliances参数也可以退一步用整栋楼的全部电器来做分解train_elec train_ds.build_meter_group(mains[1]) test_elec test_ds.build_meter_group(mains[1])这样虽然不能只关注冰箱但作为第一次跑通流程目标更简单让模型对每个可用回路都给出分解结果。4.4 训练和分解完整可复制的5分钟脚本下面这一段是核心代码。我建议你把它存成run_mean.py直接在终端里跑比在Jupyter里一步步执行更省心from nilmtk import DataSet from nilmtk.disaggregate import Mean import time start time.time() train_ds DataSet(data/redd.h5) test_ds DataSet(data/redd.h5) train_ds.set_window(end2011-05-15) test_ds.set_window(start2011-05-15, end2011-05-30) train_elec train_ds.build_meter_group(mains[1], appliances[(refrigerator, 5)]) test_elec test_ds.build_meter_group(mains[1], appliances[(refrigerator, 5)]) model Mean() model.train(train_elec) pred model.disaggregate(test_elec, sample_period6) print(disaggregate done, time cost:, time.time() - start)解释一下sample_period6这个参数它表示重采样到6秒间隔。REDD的原始低频数据本身就是秒级或分钟级但各个通道采样时刻不一定对齐NILMTK的disaggregate会先把数据重采样到统一的采样周期再做分解。选择6秒是因为它能在保留足够多启停变化特征的同时计算量不会太大。跑完这段代码pred就是一个包含分解结果的生成器或列表具体形式取决于NILMTK版本。输出结构一般会按meter分组每个组里是一个DataFrame包含总功率和该电器的分解功率。可以用一个简单的循环把它收集起来for item in pred: if hasattr(item, __iter__): meter_key, df item print(meter_key, df.shape)有的版本里disaggregate会返回字典有的版本返回生成器所以这个循环同时兼容多种情况。你自己跑的时候打印出来看一下就行。4.5 多电器怎么处理如果不想只盯冰箱想同时分解冰箱、微波炉和洗衣机appliances列表里多写几项即可train_elec train_ds.build_meter_group( mains[1], appliances[ (refrigerator, 5), (microwave, 7), (washer_dryer, 20) ] )通道号要根据你手头REDD版本的labels.dat来定。这里要特别留意你传入的每个电器必须确实存在于该house的数据里否则NILMTK在构建MeterGroup时可能会报KeyError或者直接忽略这个电器。很多奇怪的问题最后追下来都是通道号写错了。5. 评估指标不能只看分解曲线“像不像”5.1 先可视化但别被可视化骗了第一次跑出结果大多数人第一反应是画图看看。可以用matplotlib把测试集某一天的分解功率和真实功率画在一起import matplotlib.pyplot as plt # 假设 df 是某个meter在测试窗口的分解结果 # df[predicted] 为分解值df[real] 为真实值 df[2011-05-20:2011-05-21].plot() plt.show()可视化有价值它能让你快速发现时间轴对不对、量纲对不对、有没有严重的相位偏移。但可视化也是最容易产生“看起来不错”错觉的环节。两条曲线重叠度不能完全说明模型好因为负荷分解本质是一个时间序列拆解问题你需要用更客观的统计指标。5.2 事件级指标F1 ScoreNILMTK里自带f1_score专门评估电器开关事件的识别效果。它会根据真实功率和预测功率判断出“设备应该开但没开”“设备开了但预测没开”等情况最后综合成精准率和召回率。把预测结果转成事件标签之后评估代码类似from nilmtk.metrics import f1_score # 需要保证 pred_df 和 ground_truth_df 索引对齐 result f1_score(pred_df, ground_truth_df) print(result)在NILMTK的标准流程里f1_score可以直接作用于disaggregate的输出。但它对输入格式非常挑剔索引时间必须完全一致采样周期必须相同。如果你发现返回值是NaN先检查是不是时间索引有偏移。这个坑我踩过很多次后来养成了统一重采样再评估的习惯。5.3 估算误差平均绝对误差和相对误差除了事件级指标还需要数值级指标。平均绝对误差MAE可以衡量分解功率和真实功率在每个采样点的差距from nilmtk.metrics import mean_absolute_error mae mean_absolute_error(pred_df, ground_truth_df) print(mae)不过MAE有个问题它把零功率时段也纳入了计算。很多电器的真实功率大部分时间是0模型只要学会“全部输出0”MAE也不会太差但你并没有真正抓到设备启动。所以我会额外关注通电时长内的误差也就是只统计真实功率大于某个阈值的点。这个阈值取多少取决于电器。冰箱的启动功率可能几百瓦待机可能几瓦取个30W通常能把待机滤掉。你可以做成一个可调参数多看几组结果。5.4 不同电器适合不同指标冰箱和空调这类温控设备有明显的启停周期F1 Score很能说明问题。微波炉、电热水壶这类大功率短时设备事件识别更重要因为它的用电量大部分集中在几分钟内一旦没捕捉到启动MAE就会飙升。我以前在做一个洗衣机的项目时发现F1 Score看起来不差但实际能耗误差在30%以上。原因就是洗衣机程序里有好几个阶段每个阶段功率不同模型虽然抓到了启动时间却低估了主洗阶段的持续功率。所以现在我做评估时通常至少看三个维度事件级F1、能耗误差、单阶段时长误差。没有哪一个单指标能覆盖所有质量问题。6. 从Mean基线到FHMM换模型的成本和真实项目里的提醒6.1 NILMTK里还有哪些现成算法NILMTK自带了一批经典算法最常被提的是Mean、CO组合优化和FHMM。它们都在nilmtk.disaggregate模块下。换算法几乎不需要改流程只要改导入和实例化即可from nilmtk.disaggregate import FHMM model FHMM()FHMM的适用场景更广它能建模电器的多状态工作模式比如洗衣机有洗涤、甩干、暂停等多个状态每个状态对应不同的功率均值。从这个角度看FHMM比Mean真实很多代价是训练时间更长超参数也更多。6.2 三张图表帮你选第一个进阶算法算法核心思想适合的电器训练速度Mean平均功率分配全天候稳定负荷冰箱秒级CO组合优化功率特征明显的设备中等FHMM因子隐马尔可夫多状态非线性设备洗衣机、洗碗机较慢对于REDD house 1我建议你把Mean跑通并记录指标后直接切换到FHMM跑一遍对比两者的F1和MAE。这个对比能帮你建立“算法复杂度与实际收益”的直觉有些数据集上FHMM提升幅度很小但训练时间增加了十倍有些场景里FHMM能把冰箱的能耗误差从30%压到15%以内。差异很大。6.3 我在这套工具链上踩过的三个真实坑第一个坑是数据处理不干净就训练。红得很漂亮的数据里面如果有重复时间戳NILMTK的MeterGroup在训练时会神秘报错错误信息还不直观。后来我每次加载数据前都会对时间索引做去重、排序、插值这个问题基本绝迹。第二个坑是采样周期不一致导致评估结果失真。某些REDD通道是1秒采样有些是3秒或4秒如果直接评估不重采样pred_df和ground_truth_df的行数不一样指标会莫名其妙差很多。统一用sample_period6重采样后问题消失。第三个坑是对HDF5版本的过度纠结。NILMTK早期依赖h5py而h5py在较新版本里有文件格式锁定机制的调整导致旧HDF5文件无法读取。解决办法很简单直接用第2节里锁好的环境把REDD重新转换一次不要试图用其他工具打开redd.h5去“修复”文件。6.4 真实项目里的扩展方向如果你已经跑通REDD和NILMTK下一步可以考虑把同样的流程迁移到其他公开数据集比如UK-DALE、REFIT、iAWE。NILMTK对它们基本都提供了类似的转换接口。数据一变你会发现模型表现立刻变样因为不同国家的电器类型、用电习惯、电压标准都不一样。我个人的真实体会是工具链只是敲门砖这套流程给你留下的最宝贵资产是“知道怎么评估一个分解模型”。无论以后你用什么深度学习框架数据怎么清洗、训练集怎么切、F1怎么算、误差怎么统计这些核心习惯在NILMTK阶段就已经养成了。至于模型本身Mean法跑出来的那些数字会是你日后所有模型最诚实的参照线。
返回列表