基于MATLAB的电力负荷数据异常检测与窃电分析
1. 项目概述与整体思路1.1 为什么电力负荷数据要做异常检测电力负荷数据是电网运行状态最直接的“体温计”。正常用户的用电曲线往往具有周期性强、变化平滑的特点而故障、计量装置异常、窃电行为都会在负荷曲线上留下“影子”。比如某商业用户平时夜间负荷稳定在20kW左右突然连续一周夜间负荷掉到接近0这种异常单靠肉眼从几百万条记录里找出来基本是不可能的。所以需要用算法把人工排查的工作量降下来把可疑样本挑出来交给稽查人员核实。这个项目的核心目标不是做学术研究而是解决实际工程问题给出一套基于MATLAB的、能落地的电力负荷数据异常检测与窃电检测方案。它既能帮电力公司的用检人员快速定位高损台区里的“重点嫌疑户”也能为工业用户的能源管理部门提供设备异常预警的参考。从技术层面看这类任务通常分两条线走一条是通用异常检测目标是找出负荷曲线中的离群点、突变段和统计异常另一条是窃电检测需要在通用异常的基础上叠加业务规则比如电量同比环比突降、线损率异常抬升、三相电流不平衡等。两条线最终汇合到“异常评分—风险排序—人工复核”的处理闭环中。1.2 方案选型为什么选MATLAB而不是Python现在做数据挖掘很多人默认首选Python。但在这个场景里MATLAB有它的独特优势第一数据清洗和可视化非常顺手。readtable、timetable、isoutlier这些内置函数几行代码就能完成数据读入、时间对齐和初步筛选调试效率比用pandas做时间索引处理还要直观。第二信号处理和统计工具箱成熟。电力负荷数据本质上是一维时间序列MATLAB的findpeaks、movmean、fillmissing、robustcov等函数在处理尖峰、缺失、突变检测时比手写Python循环稳定得多。第三部署和交互友好。电网企业里大量系统基于MATLAB/Simulink构建用MATLAB写出的检测脚本能直接嵌入现有的数据接口甚至能编译成独立程序给班组用。当然如果项目数据量到了海量流式处理的级别或者要上线到大规模分布式集群MATLAB不是最优选择。但对省级以下的用检部门、园区能源管理、高校课题验证这些典型场景MATLAB完全够用而且开发周期短、调试成本低。注意方案选型从来不是“哪个语言更牛”而是“哪个工具能最短路径解决问题”。我见过不少团队用Python堆模型最后卡在环境依赖和部署兼容上也见过直接用MATLAB脚本跑完整个检测流程的一天就出了首轮嫌疑清单。1.3 检测流程的总体设计整体流程分五步数据准备、特征构建、算法检测、业务规则融合、结果输出。每一步都是下一层的输入结构上清晰后期换算法或者调参数也不会牵一发动全身。数据准备阶段解决的是“原料能不能吃”的问题包括缺失值填补、重复记录去重、时间戳对齐。特征构建阶段从原始负荷序列里提取日用电量、峰谷差、负荷率、电流三相不平衡度等指标。算法检测阶段分别用统计方法和机器学习方法做无监督异常打分。业务规则融合阶段把算法结果与窃电特征规则交叉验证生成嫌疑指数。结果输出阶段生成带可视化图表的Excel报告直接交给稽查人员。这个设计和单纯跑一个孤立森林模型的最大区别是它把“数据规律发现”和“业务先验知识”两条腿都用上了。只用算法容易出误报只用规则容易漏报两者结合才能在实际数据上达到可用水平。2. 数据分析与预处理决定成败的第一关2.1 原始数据长什么样先说数据来源。最常见的采集渠道是用电信息采集系统一般以15分钟或1小时为间隔保存负荷曲线数据。字段通常包括用户编号、计量点编号、采集时间、有功功率、无功功率、A/B/C三相电流、三相电压等。表格结构一般是这样的字段名示例值说明CONS_IDU10002345用户编号METER_IDM8800123计量点编号DATA_TIME2024-05-11 00:00:00采集时间P_ACTIVE35.28有功功率kWP_REACTIVE12.06无功功率kvarI_A / I_B / I_C58.2 / 57.9 / 58.5三相电流AU_A / U_B / U_C220.3 / 221.1 / 219.8三相电压V在实际项目中数据量级通常是几百个用户的日曲线乘以96个采集点存下来就是几十万到几百万行。这样的数据量在MATLAB里用timetable完全放得下不需要上数据库。2.2 数据清洗的几个关键动作第一步是时间戳对齐。采集系统有时会漏点、重复点或者时间偏移比如某天00:15的采集记录被打到00:00上。我一般先把DATA_TIME统一转成datetime类型然后用retime按固定间隔对齐缺失的位置留空待补。% 读取数据 data readtable(load_data.csv); data.DATA_TIME datetime(data.DATA_TIME, InputFormat, yyyy-MM-dd HH:mm:ss); % 转成 timetable按15分钟对齐 tt table2timetable(data, RowTimes, DATA_TIME); tt retime(tt, regular, mean, TimeStep, minutes(15));第二步是缺失值处理。负荷数据缺失的原因很多可能是采集终端离线、通信信道拥塞也可能是电能表本身故障。处理方式要分场景连续缺失1~2个点用前后均值或线性插值填充对检测结果影响很小。连续缺失超过半天不建议盲目插值因为插出来的曲线是假的会造成误判。直接标记为“数据不完整”在检测时把这天剔除。全天空缺单独列表登记可能是表计停走或通信故障需要现场排查。第三步是处理零值。这里有个工程上特别重要的坑不要把所有零值都当异常。农业排灌用户在非灌溉季负荷就是0夜间小型商铺负荷也可能长时间为0。正确的做法是先算日用电量把“日用电量为0的天数占比”作为一个特征而不是把所有零值点直接替换掉。% 先统计每个用户每天零值点数 daily_stats groupsummary(tt, DATA_TIME, day, sum, P_ACTIVE);2.3 特征工程的构建方法异常检测算法真正吃的不是原始曲线而是从曲线里提炼出来的特征。我从实际项目中总结了一组比较有效的特征集日用电量一天96个点有功功率的平均值乘以时间间隔转换成kWh。日最大负荷与最小负荷反映曲线的上下边界。峰谷差与峰谷比正常用户峰谷差相对稳定窃电用户可能出现峰谷比异常下跌。日负荷率平均负荷除以最大负荷正常在0.3~0.8之间窃电用户往往偏低。夜间用电占比22:00~次日06:00的用电量除以全天电量。同比变化率当天用电量与上周同天或去年同天的比值。环比变化率当天用电量与前一天同时段的比值。三相电流不平衡度max(|IA-IB|,|IB-IC|,|IC-IA|) / max(IA,IB,IC)。这些特征里前五个用来做通用异常检测后三个对窃电检测尤其重要。比如欠压窃电通常表现为电压曲线异常偏低欠流窃电表现为电流曲线异常偏低但电压正常移相窃电表现为功率因数异常。经验特征不是越多越好。我见过有人构建了五十多个特征结果模型性能没提升多少调试解释成本却翻了好几倍。实际项目中先用业务理解挑选10个左右强特征比盲目堆特征更实用。3. 常用异常检测算法的MATLAB实现3.1 统计方法箱线图与3σ原则统计方法是异常检测的“入门刀”适合快速筛查和作为基准。箱线图方法基于四分位数不要求数据服从正态分布对偏态分布更稳健。用MATLAB自带的isoutlier函数就能实现% 每日用电量向量 daily_energy daily_stats.Sum_P_ACTIVE; % 箱线图法检测异常点 [tf_low, tf_up] isoutlier(daily_energy, quartiles);quartiles方法默认把低于Q1-1.5×IQR或高于Q31.5×IQR的点标记为异常。在负荷数据里我一般更关注偏低方向的异常因为窃电和表计故障大多数表现为用电量下降高值异常通常对应大负荷设备启动不一定有业务价值。3σ原则假设数据服从正态分布以均值±3倍标准差为阈值。这个方法实现最简单但电力负荷数据往往有重尾和周期性直接用3σ会把正常峰谷当成异常。我的建议是先做季节分解或差分处理再应用3σ。比如对“用电量环比变化率”做3σ检测比直接对原始电量做更合理。3.2 聚类方法DBSCANDBSCAN是一种基于密度的聚类算法优点是能自动识别任意形状的簇并给出离群点标签而且不需要像K-means那样预先指定类别数。在MATLAB里调用dbscan函数非常方便% feat_matrix: N×M 的特征矩阵N为样本数M为特征数 [idx, corepts] dbscan(feat_matrix, epsilon, minpts); % idx 中值为 -1 的样本即为离群点epsilon邻域半径和minpts最小邻域点数是两个需要调的关键参数。我的经验是先用knnsearch计算每个样本到第k个近邻的距离画出k距离曲线取曲线拐点附近的距离值作为epsilon。minpts一般取特征维数的2倍左右。在窃电检测场景中DBSCAN特别适合用来发现“形状相同但位置偏移”的用户。比如正常居民用户的负荷曲线呈现“早晚双峰”模式窃电用户双峰明显变矮特征空间里它们就会形成独立的小簇或者直接被判成离群点。提示DBSCAN对特征缩放敏感。使用前一定要做标准化处理。MATLAB里可以用zscore或normalize函数。3.3 机器学习方法孤立森林孤立森林是异常检测领域非常经典的集成学习方法核心思想是异常样本更容易被随机划分“孤立”出来。它的计算效率很高适合处理高维特征。MATLAB从R2021a开始在Statistics and Machine Learning Toolbox里加入了iforest函数用法如下% 训练孤立森林模型 Mdl iforest(feat_matrix, NumLearners, 200, NumVariablesToSample, 5); % 预测异常分数 [score, tf] isanomaly(Mdl, feat_matrix);score越接近1表示该样本越可能是异常点。实际使用时我一般不会直接用tf的默认阈值而是按分数降序排列取前5%作为初筛名单再由业务规则进一步过滤。孤立森林在负荷异常检测中表现不错的原因在于正常用户负荷特征集中分布在几个“密集区”居民、商业、工业各有模式而异常用户往往偏离这些密集区正好符合孤立森林的算法假设。不过也有局限如果正常数据本身就包含多种模式比如同一用户春夏秋冬负荷差异很大孤立森林容易把换季变化误判为异常。解决办法是把季节特征比如月份加入特征矩阵或者分季节分别建模。3.4 时间序列专有方法滑动窗口与残差分析前三类方法把一个用户不同时间的样本看作独立点忽略了时间上的连续性。但在实际负荷数据里异常往往是一个持续段而不是孤立点。比如某天上午10点开始有功功率持续偏低用逐点检测很难判断必须引入时间窗口。滑动窗口残差方法的核心是用历史正常数据训练一个预测模型用模型预测当前窗口的值计算实际值与预测值的残差残差超过阈值就判定异常。这个思路能捕捉传统方法漏掉的“渐进式异常”。% 对每个用户用前14天数据建模预测当天 window_size 14; for i (window_size1):days train_data daily_energy(i-window_size:i-1, :); test_data daily_energy(i, :); % 可以用简单的ARIMA模型 Mdl arima(2,0,0); EstMdl estimate(Mdl, train_data); [forecast, ~] forecast(EstMdl, 1, Y0, train_data); residual(i) (test_data - forecast) / std(train_data); end这个方法计算量大但在识别慢性窃电每天多偷几度电、持续几个月时效果非常好。因为慢性窃电每天的异常幅度不大逐日统计看不出问题但残差会持续为正或负累积信号很强。4. 窃电检测的核心方法业务规则与模型融合4.1 窃电手法的电气特征分析窃电检测不能只看“数据异常”还要理解窃电导致的电气量变化特征。我梳理了几种最常见的窃电手段及其在数据上的表现窃电手法数据特征检测切入点欠压法断开电压线电压曲线异常偏低电流正常或偏高电压值超过±20%波动范围欠流法短接电流线电流异常偏低电压正常三相电流不平衡度超标移相法改变接线极性功率因数异常无功电量异常增大功率因数长期低于0.5扩差法改动表内误差负荷曲线形状正常但整体按比例缩小与同期台区线损比对遥控窃电利用通讯模块负荷曲线出现规律性缺口周期性零值段检测知道了这些特征特征工程就有方向了。比如针对欠流法重点构造三相电流不平衡度特征针对欠压法重点构造电压跌落频次和幅度特征针对遥控窃电重点检测固定时间段内的重复零值模式。4.2 线损比对法的应用线损比对是我个人非常推荐的一个方向。原理特别简单一个台区一台变压器供区的总表计量电量应该等于所有分户表计量电量之和加上固定损耗。如果某个台区线损率突然从5%涨到15%说明大概率有用户窃电或表计故障。用MATLAB实现台区线损异常检测% 台区总表用电量 total_energy ...; % 分户表用电量求和 sum_user_energy ...; % 线损率 loss_rate (total_energy - sum_user_energy) / total_energy * 100; % 滑动窗口统计线损率变化 loss_smooth movmean(loss_rate, [6 0]); % 向前7天平均线损率异常是一个强信号虽然不能直接定位到户但能把范围缩小到一个台区。后续再在异常台区内部做用户级检测效率会高很多。我在实际项目里见过一个案例某个台区线损率连续三周从6%爬升到22%逐户排查后发现某用户用欠压法窃电电压线被断开导致该户电压只有110V左右但该户的用电量只下降了不到40%单纯看用户自身负荷曲线很容易漏掉。4.3 用户级检测多模型融合打分用户级窃电检测我会同时跑三个模型再把分数融合第一统计模型。计算电压、电流、功率因数的周对比变化率用3σ方法判异常输出异常分S1。第二孤立森林模型。基于负荷特征矩阵计算异常分S2。第三规则模型。检查是否存在深夜固定时段零值、电量同比骤降超过50%等情况命中一条得10分累计得S3。最后融合成嫌疑指数% 归一化三个分数到[0,1] S1_norm (S1 - min(S1)) / (max(S1) - min(S1)); S2_norm (S2 - min(S2)) / (max(S2) - min(S2)); S3_norm S3 / 30; % 规则最多命中3条每条10分 % 加权融合权重根据实际稽查命中率调整 suspect_score 0.3 * S1_norm 0.4 * S2_norm 0.3 * S3_norm;权重的设定需要结合本地历史稽查数据。如果发现某个台区主要是欠压窃电就适当调高电压相关特征的权重如果主要是遥控窃电就要加强零值周期规则的权重。这种融合方式比单一模型稳健得多因为它兼顾了数据规律和业务经验。注意任何算法都只能输出“嫌疑”不能直接给用户定性。最终确认环节一定要人工现场检查避免误伤正常用户。5. 完整实战流程从数据到报告5.1 环境准备与数据导入我用的测试环境是MATLAB R2023a需要的工具箱包括Statistics and Machine Learning Toolbox用到孤立森林、DBSCAN、Econometrics Toolbox用到ARIMA、Signal Processing Toolbox用到平滑滤波。数据导入时推荐用readtable配合detectImportOptions处理带中文字段的CSV文件不然容易出编码问题。如果你遇到MATLAB读CSV中文乱码可以在读入前先用fopen设置编码fid fopen(load_data.csv, r, n, UTF-8); data textscan(fid, %s %s %f %f %f %f, Delimiter, ,, HeaderLines, 1); fclose(fid);5.2 数据清洗与特征计算伪代码完整的代码结构如下%% 1. 数据读取与预处理 rawData readtable(sample_load_data.csv); rawData.DATA_TIME datetime(rawData.DATA_TIME); tt table2timetable(rawData, RowTimes, DATA_TIME); tt sortrows(tt); % 填补缺失值最多连续2点 tt fillmissing(tt, linear, MaxGap, minutes(30)); %% 2. 按日聚合特征 daily retime(tt, daily, custom_features); % 自定义函数计算日用电量、峰谷差、负荷率等 %% 3. 用户级特征矩阵 feat [daily.DAILY_ENERGY, daily.PEAK, daily.VALLEY, ... daily.LOAD_RATE, daily.NIGHT_RATIO, daily.CURRENT_UNBALANCE]; %% 4. 标准化 feat_norm normalize(feat); %% 5. 孤立森林检测 Mdl iforest(feat_norm, NumLearners, 200); [score, ~] isanomaly(Mdl, feat_norm); %% 6. 箱线图基线检测 [~, tf_low] isoutlier(daily.DAILY_ENERGY, quartiles); %% 7. 业务规则检测 rule_flag (daily.DAILY_ENERGY 0.5 * movmedian(daily.DAILY_ENERGY, 7)) | ... (daily.CURRENT_UNBALANCE 30); %% 8. 融合评分与排序 suspect_score 0.3 * score 0.3 * tf_low_score 0.4 * rule_score; [sorted_score, idx] sort(suspect_score, descend);这段代码是核心骨架实际项目中再根据数据特点补充细节。重点是custom_features这个自定义聚合函数它是一个把原始96点曲线转换成特征向量的封装能让你在不同数据集间快速复用。5.3 阈值怎么定不再是玄学阈值设置是异常检测里争议最大的话题。我的经验是分三步走第一步用分类结果反推阈值。有历史稽查数据哪些用户确认窃电时画出ROC曲线取约登指数最大的点作为阈值。没有历史数据时取异常分数分布的95%分位数。第二步阈值不是固定的。每月初根据上个月数据分布动态调整用分位数法而不是固定绝对值这样能适应季节变化和负荷增长。第三步给阈值留“灰区”。不要把数据生硬地分成“正常”和“异常”而是分成高、中、低三个风险等级。高风险直接核查中风险累积观察低风险归档。这个灰区设计能大幅减少误报带来的稽查资源浪费。5.4 结果可视化的三个必做图检测结果不能只给一个Excel表那样业务人员根本不想看。我每期报告至少附三张图第一张是嫌疑用户负荷曲线总览图用subplot把多个用户同一天曲线叠在一起正常用户和嫌疑用户一眼就能区分出来。第二张是特征分布散点图横轴日用电量纵轴负荷率颜色深浅表示异常分数。这张图能直观看到嫌疑用户分布在正常群体的哪个方向。第三张是时间轴异常热力图横轴是日期纵轴是用户颜色表示当天的异常分数。稽查人员能快速看到某个用户是不是“近期连续异常”对判断慢性窃电特别有用。% 热力图示例 figure; imagesc(score_matrix); colorbar; xlabel(日期); ylabel(用户编号); set(gca, YTick, 1:num_users, YTickLabel, user_ids);6. 常见问题排查与避坑指南6.1 误报率过高的7个原因问题现象可能原因解决方案春季大面积报异常春节工厂停工负荷骤降加入节假日日历字典夏季高温日全部报警空调负荷激增曲线剧烈波动用同比而不是环比农业用户全年高异常分季节性强非灌溉期负荷近零分行业建模型同一用户反复报告模型对个体模式过拟合增加滑动窗口参考期新投运用户误报历史数据不足模型无基准设置最短观测期如30天分布式光伏用户误报发电与用电叠加曲线反向单独构建净负荷特征数据缺失导致跳变采集终端离线未标记修复采集链路识别后剔除这些坑我几乎都踩过。最典型的是春节误报刚开始跑模型时2月所有工业用户异常分都爆表就是因为工厂停工导致日用电量环比下降80%以上。后来在特征里加了“是否节假日”的布尔变量还用了“同比变化率”对比去年春节前后误报率立刻降下来了。6.2 数据质量问题如何兜底数据质量是这类项目最大的隐性风险。我有几条硬性规则第一训练集和检测集要分离。用上个月数据训练模型检测本月数据不能拿同一批数据又训练又检测否则异常分数会被压缩区分度变差。第二用户档案信息要清洗。行业分类、合同容量、电价类别这些字段经常有空值和错值直接影响分行业建模效果。处理办法是如果不能100%确定宁可先不用这个字段分组而不是用错分组污染模型。第三连续数据缺失超过48小时的记录在做最终统计时要剔除。我在一个项目里遇到过这样的情况一个用户表计故障停走19天恢复后数据显示电量只有正常值的5%算法判为重大窃电嫌疑现场核查结果是表计坏了。如果在预处理阶段把这个用户标记为“数据不完整”就不会浪费稽查人力。6.3 模型效果评估怎么落地异常检测没有标签效果评估是个老大难。我的思路是用“收敛指标”和“反馈回环”两个手段收敛指标指的是稽查核实率。每月输出嫌疑清单后跟踪稽查结果计算“嫌疑命中率查实用户数/稽查用户数”这个数字应该稳定在30%以上才算模型可交付。如果长期低于30%说明阈值定得不好或者特征选偏了。反馈回环是指把稽查结果重新喂给模型。查实为窃电的用户标记为正样本查实正常的标记为负样本积累两个月后可以用这些标签微调阈值或权重。这个闭环做起来后模型会越用越准。6.4 部署上线时的几个细节如果要把脚本交付给非研发人员使用有几个细节相当关键第一写成带GUI的批处理程序。用MATLAB的uigetfile选择数据文件warndlg提示异常读结果用writetable输出。别让现场人员去改代码。第二设计自动定时任务。在Windows下用任务计划程序调用MATLAB编译好的exe每天凌晨自动跑一次早上上班时出报告。第三异常报告里必须附带“判断依据说明”。不能只写“该用户异常”要写明是“日用电量环比下降63%电压低于额定值20%持续48小时”这样现场人员才知道该查什么。7. 写在最后做了这么久的负荷数据异常检测项目我个人最大的体会是算法只是整个链条里的一环真正决定项目成败的是特征工程对业务场景的理解深度和数据质量的把控能力。同样一套孤立森林代码如果不懂窃电手法的电气特征输出就是一串没有业务含义的分数但当你把欠压、欠流、移相这些场景转化为电压跌落检测、三相不平衡度、功率因数窗口等特征后模型输出立马变得有解释性、能落地。这个方法经过多个中低压台区实测检测出的嫌疑用户与现场稽查结果的吻合度在同类方案里已经算比较理想。后续还可以往两个方向扩展一是把检测频率从“日”提到“小时级”用于识别瞬时窃电或间歇性窃电二是把深度学习里的Transformer时序模型引入进来做基于序列的异常检测不过那对数据量和训练成本的要求会明显上一个台阶得根据团队实际情况衡量。从工程角度说先把手里的统计方法、机器学习方法和业务规则用透比急着追新模型更实在。希望这篇实操文章能帮你在自己的负荷数据上少踩几个坑把异常分数真正转化成查窃成果。