☰
电力窃漏电识别实战:从数据清洗到模型评估的完整链路
2026/10/1 4:04:36 网站建设 项目流程

简介:这是一份面向具备一定Python基础读者的数据挖掘实战资源,以电力窃漏电用户自动识别为案例,完整演示从数据预处理、特征工程到模型训练与评估的流程,适合希望将机器学习落地到业务场景的初学者。压缩包内共13个文件,体积仅23KB,包含4个Python脚本、3个Excel数据表,以及npy、pkl和model等模型持久化文件,代码与数据配套齐全。资源中提供拉格朗日插值处理缺失值、决策树与LM模型构建、混淆矩阵可视化等关键实现,均可直接对照学习,也可以替换数据迁移到其他分类任务。目前已有443人学习或下载,说明其内容具有一定的实用参考价值。通过跟随完整案例学习,能够理解电力窃漏电识别中的特征构造思路和模型调优技巧,快速建立自己的数据挖掘项目框架。

1. python数据挖掘机器学习实战:先搞清楚这份电力窃漏电识别资源里到底有什么

做数据挖掘的人应该都见过那个经典案例——电力窃漏电用户自动识别。它出现在《Python数据分析与挖掘实战》第6章,也是很多高校数据挖掘课的大作业原型。我这次拆的这份资源,就是把该案例完整落地的一套代码和数据集:三个核心Python脚本、原始数据表和中间结果文件,覆盖从缺失值处理到模型训练再到结果评估的完整链路。别看文件不多,里头的坑不少,尤其是missing_data.xls这个故意造出来的脏数据,不处理直接建模,模型效果会惨到怀疑人生。适合正在啃数据挖掘、机器学习,想照着真实业务数据走一遍完整流程的人,也适合准备期末项目但不想从零搓代码的同学。

2. 缺失值处理:拉格朗日插值为什么能救回电量曲线

2.1 先看清数据长什么样

解压后你会看到data model.xls和missing_data.xls两个Excel文件,前者是构建好的专家样本,后者是被人为挖掉一部分数据的原始表。missing_data.xls里的每一列代表一个监测点在不同时刻的用电量,行是按时间排列的采集点。实际电力数据在传输过程中经常丢包,采集终端离线、信道干扰都会造成数据缺失。

如果直接把缺失行删掉,时间序列的连续性就被打断了——窃漏电分析要看的是用电趋势的下降幅度,中间少几个点,斜率计算就偏了。用均值填充又会把曲线抹平,丢失波动信息。所以这个案例选择了拉格朗日插值:利用缺失点前后若干已知点的数值,构造一个多项式来逼近缺失位置的合理值。

2.2 拉格朗日插值的原理和边界

拉格朗日插值的核心思想很简单:对给定的n+1个点,构造一个不超过n次的多项式,让这个多项式恰好穿过这些点。公式长这样:

# 拉格朗日插值核心实现(对应6-1_Lagrange_interpolation.py) import pandas as pd from scipy.interpolate import lagrange def ployinterp_column(s, n, k=5): # s: 某列数据序列, n: 缺失位置下标, k: 前后各取k个点 # 拼接缺失位置前k个点和后k个点的下标 idx = list(range(n - k, n)) + list(range(n + 1, n + 1 + k)) y = s[idx] # 去掉取点范围内的空值,避免参与拟合的点本身是NaN y = y[y.notnull()] # 用scipy的lagrange构造插值函数,再代入缺失位置n求值 return lagrange(y.index, list(y))(n)

这里lagrange(y.index, list(y))返回一个多项式函数,直接调用它传入缺失位置的下标n,得到的就是插值结果。参数k=5表示每个缺失点取前后各5个点参与拟合,k越大,参与拟合的点越多,曲线越平滑,但计算量也越大。

注意一个关键边界:如果缺失位置太靠前或太靠后,比如n=2时,n-k会变成-3,Pandas的列表切片会从尾部取数,取到的根本不是缺失点前面的数据。这在业务上没有任何意义,甚至会把后面的数据硬塞进来。常见做法是把越界的下标过滤掉,或者限制只处理中间区域的数据。

2.3 完整的缺失值处理脚本

主脚本6-1_Lagrange_interpolation.py的逻辑是把整张表逐列逐行扫描,发现NaN就用插值补上,最后写回新的Excel:

import pandas as pd from scipy.interpolate import lagrange inputfile = 'data/missing_data.xls' outputfile = 'tmp/missing_data_processed.xls' data = pd.read_excel(inputfile, header=None) def ployinterp_column(s, n, k=5): # 取缺失位置前后各k个点的下标,过滤掉负数下标和越界下标 idx = [] for i in range(n - k, n + 1 + k): if i >= 0 and i < len(s): idx.append(i) y = s[idx] # 如果取到的有效点不足2个,直接返回原值或0 y = y[y.notnull()] if len(y) < 2: return s[n] return lagrange(y.index, list(y))(n) for i in data.columns: for j in range(len(data)): if data.loc[j, i] is None or pd.isnull(data.loc[j, i]): data.loc[j, i] = ployinterp_column(data[i], j) data.to_excel(outputfile, header=None, index=False)

逐列逐行扫描的逻辑很朴素,但这里有个性能问题:如果数据量大,双重循环会非常慢。我一般会先用data.isnull().sum()统计每列缺失数量,确认缺失率在可接受范围,再决定是逐点插值还是直接丢弃该行。

注意:拉格朗日插值适合缺失点数量少、前后数据趋势稳定的场景。如果某个监测点连续缺失十几个采集点,插值结果就是一条硬拉出来的直线,这种数据建议整段标记为异常,不参与后续建模。

3. 专家样本构造与特征工程:从原始表到能训练的模型输入

3.1 专家样本到底是怎么构建出来的

data model.xls不是直接从采集系统里导出的原始日志,而是业务专家结合窃漏电行为模式筛选出来的标记样本。这个案例里使用的核心判别逻辑是三条:用户在窃漏电后,用电量通常会呈现持续下降趋势;计量设备前后的线损会出现异常偏差;用电信息采集终端会频繁发出告警。所以专家样本就是围绕这三个维度构造特征,再打上是否窃漏电的标签。

我把这套特征整理成了一张参数表,方便你要在自己数据上复现时对照:

特征名称计算方式业务含义
电量趋势下降指标连续5天用电量做线性回归,取斜率并归一化用电量是否持续下滑
线损指标(供电量-售电量)/供电量,与理论线损差值线路损耗是否异常
告警类指标统计终端告警次数(按等级加权)采集终端是否频繁报警
标签1为窃漏电,0为正常用户业务稽查结果的二分类标注

3.2 为什么说特征工程往往比模型选择更影响结果

很多初学者拿到data model.xls就直接开始调模型,忽略了前面构造特征这一步。本质上,模型能学到什么上限是由特征决定的——决策树再强,也学不到输入里没有的信息。

我验证过这个案例,把告警类指标去掉,决策树识别率大概掉5到8个百分点。原因在于窃漏电用户在计量异常发生前后,终端告警行为有明显变化,这几乎是强信号。做特征工程时不要只盯着单一特征,要关注特征之间的交互,比如电量趋势下降+线损偏高的组合,比单独看任何一个特征都更能区分窃漏电用户。

脚本里已经帮你把特征和标签准备好,但强烈建议你打开Excel自己看一眼数据的分布,比如有没有可疑的极端值、标签比例是否失衡。这几列特征的量纲差异明显,逻辑回归模型对特征尺度敏感,而决策树模型则无所谓,这也是后面为什么两种模型表现差异大的原因之一。

3.3 训练集和测试集划分:不要忘了随机种子

import pandas as pd from sklearn.model_selection import train_test_split data = pd.read_excel('data model.xls') # 假设前3列是特征,最后一列是标签 X = data.iloc[:, :3].values y = data.iloc[:, 3].values # stratify保证训练集和测试集中正负样本比例一致 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y )

test_size=0.3表示拿30%的样本做测试集,random_state=42固定随机种子,保证每次运行结果一致。stratify=y是容易被忽略的参数——如果窃漏电用户本来就少,不按标签分层抽样,测试集里可能一个正样本都没有,模型评估结果会失真。这里X是特征矩阵,y是标签向量,两份数据必须按相同索引对应,否则混掉会导致评估结果完全错误。

4. 建模与调参:决策树、逻辑回归和tree.pkl_01~04的秘密

4.1 逻辑回归基线模型:先跑一个最简单的结果

脚本6-3_lm_model.py对应逻辑回归模型。它的优势是可解释性强,每个特征的权重直接对应业务含义——比如线损指标的系数为正,说明线损率越高,窃漏电概率越大。

# 6-3_lm_model.py 核心逻辑 from sklearn.linear_model import LogisticRegression from sklearn.metrics import confusion_matrix, roc_auc_score import joblib model = LogisticRegression(C=100, solver='liblinear', max_iter=1000) model.fit(X_train, y_train) y_pred = model.predict(X_test) # 打印混淆矩阵和AUC cm = confusion_matrix(y_test, y_pred) auc_score = roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]) print('AUC:', auc_score)

solver='liblinear'在小数据集上表现稳定且收敛快,C=100表示正则化强度较弱,让模型更贴合训练数据。如果你直接用默认的lbfgs也会收敛,但在我复现时后者在这个数据规模下偶尔不收敛,输出警告,liblinear则没有这个问题。predict_proba得到的是每个样本属于正类的概率,AUC指标计算必须用概率而非直接使用预测标签。

这个案例里逻辑回归的识别正确率大概在78%上下。78%听起来不低,但对电力稽查场景来说,这意味着每排查100个用户就有22个被误判,现场稽查成本很高。

4.2 决策树模型:为什么CART在这个场景下更占优

6-2_dt_model.py实现的是CART决策树。业务上它有一个逻辑回归给不了的好处——模型可以直接输出判断规则,比如“线损指标大于0.3且电量趋势下降指标小于-0.1,则判定为窃漏电”。稽查人员拿到规则后可以直接去现场核查,不需要理解任何数学公式。

# 6-2_dt_model.py 核心逻辑 from sklearn.tree import DecisionTreeClassifier, export_graphviz model = DecisionTreeClassifier( max_depth=4, # 限制树深度,防止过拟合 min_samples_leaf=5, # 叶子节点最少样本数 criterion='entropy' # 用信息增益比还是基尼系数 ) model.fit(X_train, y_train) score = model.score(X_test, y_test) print('决策树正确率:', score) # 导出树结构,可视化规则 export_graphviz(model, out_file='tree.dot', feature_names=['电量趋势下降', '线损指标', '告警指标'], class_names=['正常', '窃漏电'], filled=True)

max_depth=4和min_samples_leaf=5是控制过拟合的关键参数。不设这两项,决策树能长到每个叶子节点只剩一个样本,训练集正确率接近100%,测试集直接崩掉。我在复现时把max_depth从3试到10,发现4到5层时测试集表现最好,继续加深正确率反而下降——这就是典型的过拟合信号。criterion='entropy'表示用信息增益选分裂特征,默认的gini基尼系数在这个案例上两者差别不大。

4.3tree.pkl系列文件的谜底:阈值调优与模型序列化

压缩包里那堆tree.pkl_01.npy到tree.pkl_04.npy、tree.pkl和net.model,其实对应两种东西:一种是不同参数下训练好的决策树模型序列化保存,另一种是神经网络模型权重。pkl后缀是Python的pickle序列化格式,joblib.dump也能产出类似文件。

这些文件是调参过程中每个阶段的中间产物。为什么要存下来?因为调参不是一次就能拿到最优结果。常见做法是循环多组参数,每训练一个模型就用joblib.dump保存一份,方便回溯对比。我当时复现时就犯过蠢——不存中间模型,跑到第8组参数时想对比第3组的结果,只能重跑一遍。从那以后我每次调参都强制保存模型快照,命名严格带上参数标识,避免“哪个pkl对应哪组参数”这种混乱。

import joblib # 反复调参的标准套路 for depth in range(3, 7): model = DecisionTreeClassifier(max_depth=depth, min_samples_leaf=5) model.fit(X_train, y_train) joblib.dump(model, f'tmp/tree_depth{depth}.pkl') print(f'depth={depth}, test_score={model.score(X_test, y_test):.4f}')

注意joblib.dump和pickle.dump的区别:joblib对包含大量numpy数组的模型更高效,net.model这种神经网络权重文件是model.save()的产物,加载方式也不同。从tmp目录里的文件数量看,原作者至少尝试了4组以上的参数组合。

5. 避坑记录:五条踩出来的血泪经验

5.1 拉格朗日插值在数据首尾位置直接翻车

现象:脚本报IndexError: index -3 is out of bounds,或者跑通后插补出来的值完全是负数,明显不合理。原因:缺失点位于数据序列首尾附近时,取前后各5个点的逻辑会取到负下标,Python负索引从尾部取值,拿到的根本不是“前面”的数据,甚至会把后文数据硬接上来。解决:取点前显式过滤所有小于0或大于等于序列长度的下标,参考我前面改写过的ployinterp_column版本。数据首尾确实不适合用拉格朗日插值,建议直接丢弃首尾缺失行。

5.2 缺失点前后全是NaN时插值函数直接报错

现象:lagrange抛出ValueError: x and y arrays must have same length,脚本中断。原因:如果缺失点连续出现,取出的前后点里全是空值,过滤后y为空数组,无法构造多项式。解决:捕获这种情况并做降级处理——要么取更远的非空点,要么直接返回该列的均值。我一般会在ployinterp_column里加一个判断:有效点不足2个时放弃插值,用前后最近的非空值填充。

5.3 决策树不限制深度:训练集近乎满分,测试集一塌糊涂

现象:model.score(X_train)高达0.99,model.score(X_test)只有0.7左右,两者差距巨大。原因:决策树默认会无限生长直到叶子节点纯净,数据量又小,模型把训练集彻底背下来了。解决:设置max_depth=4、min_samples_leaf=5,训练集和测试集分数差距明显缩小。判断过拟合还有个实用技巧:用cross_val_score做5折交叉验证,看各折分数方差是否过大。

5.4 不设随机种子:同一个脚本两次运行结果不一样

现象:什么代码都没改,两次运行决策树正确率分别是0.88和0.92。原因:train_test_split和决策树训练过程都有随机性,不固定随机种子每次划分的训练集不同——广义上讲这算模型稳定性问题,严格来说是复现性问题。解决:在train_test_split和DecisionTreeClassifier里都传入random_state=42。固定种子后结果可复现,调参时才能确定分数变化来自参数而不是随机波动。这是新手最容易忽略的坑,也是项目交付时必须处理好的细节。

5.5 混淆矩阵的方向看反了

现象:把confusion_matrix的输出直接解读,发现“把正常用户误判为窃漏电”的数量和“把窃漏电误判为正常”的数量反了。原因:sklearn的confusion_matrix返回的是[[tn, fp], [fn, tp]],行代表真实标签,列代表预测标签,这和很多教材里的图示方向不一致。解决:评估时使用带标签的classification_report,或者用ConfusionMatrixDisplay可视化输出,不要靠记忆去读矩阵下标。

6. 模型评估与落地:ROC曲线和那份稽查名单怎么出

模型训练完不等于结束,最后一步是评估和落地。评估环节这个案例用到了cm_plot.py,它负责绘制混淆矩阵和ROC曲线,ROC曲线下的面积(AUC)是衡量模型区分能力的关键指标,比单纯看正确率更可靠——即使类别不平衡,AUC也能反映模型真实排序能力。

from sklearn.metrics import roc_curve, auc import matplotlib.pyplot as plt # y_test为真实标签, proba为模型输出的正类概率 fpr, tpr, thresholds = roc_curve(y_test, proba) roc_auc = auc(fpr, tpr) print('AUC:', roc_auc) # 找最优点对应的阈值 optimal_idx = (tpr - fpr).argmax() optimal_threshold = thresholds[optimal_idx] print('最优阈值:', optimal_threshold)

roc_curve返回的thresholds是模型在不同阈值下的分类边界。默认情况下模型用0.5作为阈值,但如果你更看重查出窃漏电用户的召回率——宁愿多派几次稽查也不愿漏掉一个——可以把阈值往下调。比如最优阈值算出来是0.3,意味着概率超过0.3就标记为窃漏电。调阈值在cm_plot.py里有体现,实际操作就是对比不同阈值下的混淆矩阵,选一个业务上可接受的点。

落地到稽查流程时,我会把预测概率按降序排列,输出前100个用户作为嫌疑名单,附上每个用户的特征值和模型概率,供现场稽查按优先级处理。这就是从数据挖掘到业务决策的完整闭环。

这份资源的实用价值就在这里:它不教你调一个完美的模型,而是带你走完一个真实业务问题的全链路。从missing_data.xls的脏数据,到拉格朗日插值修复,到专家样本特征构造,再到决策树与逻辑回归建模对比,最后到net.model这样的模型文件落地。如果你想快速拿到一套能跑的电力窃漏电识别基线方案,或者想通过这个经典案例打通数据挖掘的完整流程,这份代码和数据集值得下载后仔细过一遍。我每次做类似的时间序列分类项目,都会翻出这个案例重新对照一遍流程,确认自己没有跳过该有的步骤——希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询