恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
电力系统多传感器融合入侵检测:Python实现与核心要点
首页
资讯中心
/
电力系统多传感器融合入侵检测:Python实现与核心要点
电力系统多传感器融合入侵检测:Python实现与核心要点
发布时间:2026/10/11 15:53:01
简介面向电力系统网络安全防护多传感器融合资源以Python代码实现为核心演示如何将传感器状态、网络流量、运行日志等多源异构数据进行统一融合处理并用于网络入侵的精准检测。内容覆盖数据预处理、特征提取、融合算法设计与入侵检测模型构建等关键环节能够提升系统对潜在攻击的早期预警能力和响应效率。资源适合具备Python与机器学习基础、关注电力系统安全或工业控制安全的科研人员和研究生学习使用。资源包内含1个docx文档整体大小约77KB结构紧凑便于查阅和携带。文档结合代码逐模块解析融合逻辑与模型设计细节并给出实际应用场景与调优建议读者可直接复现算法流程也可在此基础上改进融合策略用于关键基础设施安全监控、物联网安全等相关研究。目前已有31人学习该资源是快速了解多传感器融合网络入侵检测技术思路的实用材料。1. 多传感器融合电力系统网络入侵检测为什么不能只靠单一数据源电力系统的网络安全检测和普通IT入侵检测有个本质区别一次破坏动作会同时体现在网络报文异常、SCADA遥测跳变和审计日志异动上单一数据源只能看到局部。这套Python实现的价值就是把三类异构数据通过时间对齐、特征提取和融合决策串成完整流水线输出入侵检测结果。我拆包后最深的印象是代码组织适合科研复现——预处理、融合、模型三层边界清晰改一个模块不影响另外两个。适合已经能用pandas和scikit-learn跑通基础流程、想做电力系统安全实验的研究生和工程师。它直接回答多源数据怎么融合、融合后比单源强在哪、指标怎么算才不算自欺欺人。2. 电力场景的三类感知数据SCADA遥测、网络流量与日志特征怎么对齐2.1 三类数据源各自的检测盲区多传感器融合的「多」不是凑数而是因为每一类传感器都有明确盲区。这套实现里输入数据在层面被分成三类正好对应电力系统信息物理融合的三个层次网络层、物理层、操作层。它们在采样节奏、字段含义、噪声特性上完全不同融合的第一步不是建模而是先搞清楚这三类数据各自能看见什么、看不见什么。数据源典型字段采样特点单源检测盲区网络流特征报文数、包长均值/方差、协议种类数、连接标志位毫秒级量大看不到电气量异常对虚假数据注入这类物理攻击不敏感SCADA遥测电压、电流、有功/无功、频率、开关状态秒级到分钟级对不改变电气量的网络侦察行为反应滞后审计日志登录事件、权限变更、指令序列、失败计数事件驱动日志可被篡改覆盖不到物理层单源模型单独看AUC往往都不差可能都有0.9以上但这是假象。真正有价值的是误报的位置网络流模型误报的样本和SCADA模型误报的样本通常来自不同原因一个源被噪声干扰时另一个源还是正常的融合之后错误被互相抵消。这就是多传感器融合在这个场景里最值得做的事也是论文里「融合优于单源」结论的来源。我拆这包的时候第一件事就是分别跑三个单源基线确认这个问题数据上真的存在再做融合。2.2 时间戳对齐与窗口聚合不同采样率的处理三类数据的采样节奏完全不一样直接按行拼接是错的。常见做法是以秒为最小时间粒度做窗口对齐网络流按1秒窗口聚合出统计量SCADA在同一秒内取最新值或均值审计日志按事件计数落入对应秒窗口。import pandas as pd # 网络流数据按秒窗口聚合出统计特征 net_agg (net.groupby(pd.Grouper(keytimestamp, freq1s)) .agg(pkt_count(pkt_len, count), pkt_len_mean(pkt_len, mean), pkt_len_std(pkt_len, std), proto_nunique(protocol, nunique)) .reset_index()) # SCADA遥测5秒一个采样点先floor到秒再保留每个秒内的最后一条 scada[timestamp] pd.to_datetime(scada[timestamp]).dt.floor(s) scada scada.drop_duplicates(timestamp, keeplast)Grouper的freq参数是这里的关键。窗口开太大比如60秒短时攻击会被正常流量稀释特征对攻击不敏感开太小比如100毫秒大量窗口里根本没有SCADA采样值merge之后样本全是空。1秒是大多数电力场景的折中。SCADA采样率低导致的缺失用前向填充而不是直接删行同时加一个缺失指示列让模型知道「这个窗口没有遥测」本身也是信息fused fused.sort_values(timestamp).ffill() fused[scada_missing] fused[voltage].isna().astype(int)提示两个数据源做merge之前先打印各自的时间范围确认重叠区间足够大。很多「融合后样本骤减」的问题都是时间范围不重叠造成的。2.3 归一化放在切分之后数据泄漏的第一道防线三类特征量纲差异极大电压可能是110kV量级报文数是每秒几千的量级登录失败次数是个位数。逻辑回归这类模型不归一化根本没法收敛随机森林虽然不要求归一化但如果你要跑SVM之类的对照模型归一化就绕不开。这一步最容易被新手做错的地方是先对全量数据归一化再切分。我见过不少复现代码这么写结果模型准确率虚高好几个点。原因是scaler在fit时看到了测试集的统计量相当于考试前拿到了答案。正确顺序是先切分再只对训练集fitfrom sklearn.model_selection import train_test_split from sklearn.preprocessing import RobustScaler X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) scaler RobustScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) # 只transform绝不fit归一化器我一般选RobustScaler而不是MinMaxScaler。SCADA遥测里经常有传感器瞬时毛刺MinMax会被这些离群点把正常区间压得很扁而RobustScaler用中位数和四分位距对尖峰更抗造。另外做对比实验时务必所有模型共用同一个切分和同一个scaler否则你比较的就不是算法是运气。3. 融合策略选型特征级拼接与决策级投票的对比和参数设置3.1 特征级融合拼接之后为什么还要降维融合算法是整个资源最核心的部分。特征级融合的做法是把三个源的特征向量拼成一个长向量交给一个分类器它最大的优点是能自动利用源之间的交叉信息——比如「网络流量激增且同时段电压波动」这种组合模式只有特征级融合才能学到。import numpy as np # 三个源各抽出一组特征水平拼接成融合向量 X_net net_feat.values # 形状 (n_samples, 14) X_scada scada_feat.values # 形状 (n_samples, 8) X_log log_feat.values # 形状 (n_samples, 6) X_fused np.hstack([X_net, X_scada, X_log]) print(X_fused.shape) # (n_samples, 28)拼接之后不是直接用我一般会加一步降维。原因有两个第一如果某个源的特征数远多于其他源它会主导树模型的split选择融合退化成单源第二SVM和逻辑回归对高维冗余特征非常敏感。常见做法是用PCA把28维压到能保留95%累积解释方差的维度通常15到20维或者用SelectKBest按F值筛特征。随机森林本身可以不做降维但降维能让你在与其他模型对比时处在同一条件。3.2 决策级融合软投票与置信度加权的实现决策级融合是另一种路线每个源单独训练一个分类器各自输出预测概率再合并成最终决策。好处是模型可以单独调参、单独替换某一个源数据质量变差时不会拖累整个系统代价是丢失了源间交叉特征而且训练和调参工作量翻三倍。from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier, VotingClassifier from sklearn.linear_model import LogisticRegression models [ (net, RandomForestClassifier(n_estimators200, max_depth10, random_state42)), (scada, GradientBoostingClassifier(n_estimators150, max_depth5, random_state42)), (log, LogisticRegression(max_iter1000, C0.1)) ] voting VotingClassifier(estimatorsmodels, votingsoft) voting.fit(X_train_scaled, y_train)soft voting取的是三个模型的平均概率比hard voting少数服从多数更稳。原因是不均衡数据下某个源的召回率可能很低硬投票会让这个「瘸腿」源一票否决掉正确的预测。如果想进一步优化可以按每个源在验证集上的F1作为权重做加权平均效果通常比等权平均好一点# 按单源验证F1加权合并概率 p_final 0.40 * proba_net 0.35 * proba_scada 0.25 * proba_log y_pred (p_final 0.5).astype(int)三个权重分别来自单源模型的F1分数权重的绝对值没那么重要但比例要反映真实性能。我习惯把权重写成一个可配置的列表放在代码顶部方便后面实验做多组对比。3.3 分类器配置随机森林与对照模型的参数边界模型选型上这套实现的主模型是随机森林对照模型一般是梯度提升和逻辑回归。随机森林适合这个场景的原因很直接特征尺度差异大、包含统计类特征、训练数据可能有噪声树模型对这些都不敏感且能输出概率用于后续阈值调整。模型关键参数参考取值注意事项RandomForestn_estimators / max_depth / min_samples_leaf300 / 12 / 4树超过300棵收益递减推理变慢GradientBoostingn_estimators / max_depth / learning_rate150 / 5 / 0.05容易过拟合配合早停LogisticRegressionC / penalty0.1 / l2必须归一化特征相关高时调小C三个参数值得单独说。n_estimators到300以后随机森林的OOB误差基本走平再加树只是浪费时间max_depth限制在12左右是为了防止树在融合特征上学到过于细碎的split反而降低泛化min_samples_leaf设4是为了让叶节点至少有4个样本在类别不均衡数据上减少纯粹靠样本量堆出来的叶子。梯度提升的learning_rate和n_estimators要一起调learning_rate越小需要的树越多这里0.05配150棵是一个保守但稳定的组合。注意所有对比实验固定同一个random_state。随机森林和梯度提升都有随机性不固定种子的话你看到的差异可能是随机噪声而不是算法差异。4. 核心代码拆解从三个CSV到入侵检测模型的完整流水线4.1 环境依赖与目录结构先交代运行环境。这套代码依赖pandas、numpy、scikit-learn、matplotlib类别不均衡处理用到imbalanced-learn。安装命令pip install pandas numpy scikit-learn matplotlib imbalanced-learnPython版本建议3.8以上scikit-learn用1.0以上版本太老的版本里VotingClassifier的接口行为有差异。这套资源的目录组织我拆开看时大致是下面这样三个数据文件加三个处理模块. ├── data/ │ ├── netflow_features.csv # 网络流统计特征 │ ├── scada_telemetry.csv # SCADA遥测量 │ └── audit_log_features.csv # 审计日志特征 ├── src/ │ ├── preprocess.py # 时间对齐、缺失处理、归一化 │ ├── fusion.py # 特征级/决策级融合 │ ├── model.py # 分类器定义与训练 │ └── evaluate.py # 指标、混淆矩阵、PR曲线 └── main.py # 串联全流程拆分模块的价值在于替换数据时不用动算法。你拿到自己厂站的数据后只需要改preprocess.py里的字段映射把CSV列名对应到自己的字段上后面fusion和model完全不用碰。我建议第一次跑的时候不要改任何参数原样跑通拿一个基线然后再动一个模块验证一个模块出了问题也好定位。4.2 数据加载与预处理代码主流程的第一步是加载三个数据源并做时间对齐import pandas as pd # 三个传感源各自带timestamp列 net pd.read_csv(data/netflow_features.csv) scada pd.read_csv(data/scada_telemetry.csv) audit pd.read_csv(data/audit_log_features.csv) # 统一时间精度全部floor到秒 for df in (net, scada, audit): df[timestamp] pd.to_datetime(df[timestamp]).dt.floor(s) # 网络流按秒窗口聚合 net_agg (net.groupby(pd.Grouper(keytimestamp, freq1s)) .agg(pkt_count(pkt_len, count), pkt_len_mean(pkt_len, mean), pkt_len_std(pkt_len, std), proto_nunique(protocol, nunique)) .reset_index())这段做了两件事把三个源的timestamp统一成秒级精度再把网络流压成每秒一条的统计记录。pd.to_datetime负责把字符串解析成时间对象dt.floor(s)把毫秒级时间戳归位到所在秒这样三个源才有共同的join键。groupby里用pd.Grouper指定freq1s相当于按秒切桶agg里前三个字段是包长统计量proto_nunique是每秒出现的协议种类数——网络侦察类攻击通常表现为协议种类数的异常跳变。4.3 融合建模与训练代码预处理之后进入融合建模。三个源都对齐到秒级后做inner merge再前向填充SCADA的空缺然后切分、归一化、训练from sklearn.model_selection import train_test_split from sklearn.preprocessing import RobustScaler from sklearn.ensemble import RandomForestClassifier # 按时间戳合并三个源形成特征级融合矩阵 fused net_agg.merge(scada, ontimestamp, howinner) fused fused.merge(audit, ontimestamp, howinner) fused fused.sort_values(timestamp).ffill() fused[scada_missing] fused[voltage].isna().astype(int) feature_cols [c for c in fused.columns if c not in (timestamp, label)] X fused[feature_cols] y fused[label] X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state42 ) scaler RobustScaler() X_train_scaled scaler.fit_transform(X_train) X_test_scaled scaler.transform(X_test) model RandomForestClassifier( n_estimators300, max_depth12, min_samples_leaf4, class_weightbalanced_subsample, n_jobs-1, random_state42 ) model.fit(X_train_scaled, y_train)class_weightbalanced_subsample是根据少数类占比自动调整每棵树的样本权重相当于内建了一层不均衡处理比事后加SMOTE更省事树模型通常建议优先试这个。n_jobs-1让所有CPU核心并行训练数据量不大时几百棵树几秒钟就能跑完。如果类别不均衡严重到balanced权重不够用再叠加SMOTE注意SMOTE只fit在训练集上from imblearn.over_sampling import SMOTE smote SMOTE(random_state42) X_train_res, y_train_res smote.fit_resample(X_train_scaled, y_train)4.4 评估产出分类报告与曲线训练完直接看sklearn的分类报告和混淆矩阵from sklearn.metrics import classification_report, confusion_matrix y_pred model.predict(X_test_scaled) print(classification_report(y_test, y_pred, digits3)) print(confusion_matrix(y_test, y_pred)) # 顺便拿到正类概率后面选阈值用 proba_positive model.predict_proba(X_test_scaled)[:, 1]分类报告里重点看attack类的precision、recall、f1-score三列不要盯着accuracy。在网络入侵检测场景里漏报一个攻击可能引发连锁故障所以recall的优先级通常高于precision。混淆矩阵用来定位错误类型把攻击判成正常是漏报是最不能接受的错误把正常判成攻击是误报代价相对较低。后面第6章会讲怎么用PR曲线把这个权衡精确化。5. 避坑指南多传感器融合模型的五个典型翻车现场5.1 训练前全局归一化准确率虚高的元凶现象对全量数据fit了scaler再切分测试集准确率98%但换一组随机种子后分数剧烈波动交叉验证各折差异超过5个点。原因scaler在fit时看到了测试集的分布信息这是数据泄漏。测试集的均值和方差被「预先知道」后测试分数会系统性偏高而且切分方式一变泄漏程度就变分数自然不稳定。解决严格在交叉验证内部做预处理训练集fit、测试集transform用Pipeline一劳永逸from sklearn.pipeline import Pipeline pipe Pipeline([ (scaler, RobustScaler()), (clf, RandomForestClassifier(n_estimators300, random_state42)) ]) pipe.fit(X_train, y_train)5.2 类别不均衡accuracy很高但攻击样本一条没抓到现象分类报告里accuracy 0.97往下翻到attack类recall是0.00所有攻击样本都被判成了normal。原因入侵数据集中攻击样本通常只占2%到5%模型只要全部预测为多数类就能拿到95%以上的accuracy默认阈值0.5对少数类的概率分布完全不适用。解决先用class_weightbalanced_subsample不行再上SMOTE最后用PR曲线重新选阈值把决策边界往少数类方向偏移。具体代码见第4.3节和第6节。5.3 时间戳精度不一致merge之后样本量骤减现象三个源单独看都有一万多行merge之后只剩两千多行或者出现大量NaN。原因时间精度不统一。一个源的时间戳精确到毫秒另一个只到秒floor处理之前两者的join键根本对不上更隐蔽的情况是时区不一致一个存UTC一个存本地时间差8个小时。解决merge之前先打印每个源的时间范围和前几行时间戳样本肉眼确认精度和时区。统一用pd.to_datetime解析后floor(s)再做merge。5.4 特征直接拼接融合后F1反而不如单源现象特征级融合的F1比最好的单源模型还低加了融合反而退化。原因某个源的特征维度远高于其他源或者噪声特征过多拼接后这些特征主导了树模型的split选择模型实际退化成单源同时还被噪声源拖累。解决先分别跑三个单源基线记下各自的F1。融合后检查特征重要性把重要性为0的特征删掉或者上一步降维后再对比。判断融合有没有效唯一标准是和最好的单源基线比不是和默认参数比。5.5 训练集分布和现场不一致测试指标好看上线就废现象随机切分测试F1有0.9部署到另一个厂站或者换了一个时间段的数据F1掉到0.6以下。原因随机切分默认样本独立同分布但电力系统数据是时间序列攻击模式、协议版本、季节负荷都会随时间漂移。用随机切分评估等于提前让模型「偷看」了未来的数据。解决按时间顺序切分用前70%时间段训练、后30%时间段测试这才是模拟真实上线场景的做法。如果时间是连续的还要注意切分点附近不要有跨边界的窗口特征。6. 进阶从离线模型到在线预警的阈值校准与增量更新6.1 用PR曲线选阈值代替默认0.5离线模型指标好看不等于上线好用第一步就是把阈值从0.5改成数据说了算。PR曲线直接刻画precision和recall的权衡对类别不均衡数据比ROC更诚实from sklearn.metrics import precision_recall_curve import numpy as np precision, recall, thresholds precision_recall_curve(y_test, proba_positive) f1 2 * precision * recall / (precision recall 1e-9) best_idx np.argmax(f1) best_threshold thresholds[best_idx] print(f最优阈值: {best_threshold:.3f}, F1: {f1[best_idx]:.3f})电力系统网络入侵检测里漏报的代价远高于误报所以我不一定选F1最大的点而是会在F1下降不超过0.02的前提下尽量往左移阈值提高recall。这个选择本质是业务决策不是纯数学决策但PR曲线至少给了你一个可视化的权衡依据不用拍脑袋定阈值。6.2 滑动窗口与模型热更新在线预警的常见做法是滑动窗口推理加周期性重训。窗口特征每秒滚动更新模型每5秒或10秒推理一次每天凌晨用前一天新增的标注数据做一次增量训练把新出现的攻击模式吸收进来对抗分布漂移。这一步不需要改融合逻辑只需要在推理层加一个缓存队列把最近N秒的特征拼好再送进模型。6.3 置信度分级把输出变成可操作的告警模型输出不要直接接自动阻断更稳的做法是按融合概率分三档概率区间处置动作 0.3仅记录不告警0.3 ~ 0.7告警人工复核 0.7高优先级告警联动处置从那以后我每跑一个多传感器融合实验都强制走一遍流程先单源基线再融合对比最后PR曲线选阈值三个结果放同一张表里才敢下结论。这套资源里的Python代码把前三步都铺好了下载解压后建议先原样跑一遍拿基线再按这个习惯一步步验证。希望帮到你。本文还有配套的精品资源点击获取