ARTICLE DETAIL

资讯详情

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

基于概率神经网络的变压器DGA故障诊断:小样本多分类实战

基于概率神经网络的变压器DGA故障诊断:小样本多分类实战 简介发表于《工业控制计算机》2017年第30卷第10期的学术论文系统研究了基于概率神经网络PNN的变压器故障诊断方法适合电力系统运维人员、电气工程及自动化专业学生以及关注机器学习和数据建模的算法工程师阅读。论文以油中溶解气体分析DGA为核心选取绝缘油中H2、CH4、C2H6、C2H4、C2H2五种特征气体作为输入构建了可识别高能放电、低能放电、过热和正常状态的PNN诊断模型并对径向基函数扩展系数Spread进行寻优实测中当Spread取70时诊断正确率达到100%。资源包仅含1个PDF文档文件大小1.44MB内容紧凑、适合直接阅读与引用已有148人学习下载。通过文中完整的模型结构说明、贝叶斯决策原理推导及MATLAB仿真分析读者可掌握PNN在变压器故障诊断中的建模流程与鲁棒性验证方法为绝缘油监测数据的智能分析和在线预警提供了可复现的思路。1. 基于概率神经网络的变压器故障诊断小样本、多类别场景下值得优先试的方案油化试验室台账里躺着的油中溶解气体DGA数据往往是几百条样本分属六七种故障类型其中“正常”和“高温过热”占了七八成局部放电、低能放电这类样本可能只有十几条。拿这样的数据去训BP神经网络结果通常很尴尬训练集精度99%换到新数据就崩调参调一个月也压不住过拟合。这也是概率神经网络PNNProbabilistic Neural Network近几年在变压器故障诊断领域被反复提起的原因——它没有反向传播、没有权重迭代一次前向计算就能完成训练和分类天然适合小样本、多类别、特征维度不高的场景。这篇笔记就围绕“基于概率神经网络的变压器故障诊断”这个方向把从DGA数据整理到模型落地、再到避坑验证的完整路径讲清楚。适合正在做设备状态评估的工程师也适合想快速跑通一个可复现故障诊断代码、准备发论文的同学。2. 从DGA气体数据到PNN输入特征工程与样本构建2.1 为什么DGA是变压器故障诊断的第一手证据变压器内部绝缘油和固体绝缘材料在电、热作用下会分解产生特征气体局部放电主要产氢H2和少量甲烷CH4过热故障按温度不同中低温过热以CH4和C2H6为主高温过热以C2H4为主电弧放电高能放电会产生大量乙炔C2H2和氢气。这就是油中溶解气体分析成为变压器故障诊断“第一手证据”的原因——不需要停电取一次油样做气相色谱就能拿到故障类型的间接信号。传统做法是IEC三比值法用C2H2/C2H4、CH4/H2、C2H4/C2H6三组比值查编码表。它的短板很明显编码区间是离散的比值落在边界附近时编码跳变诊断结论跟着跳而且对早期微弱故障不敏感局部放电和低能放电经常混在一起。把原始气体浓度和派生比值直接喂给概率神经网络等于让模型自己学这些边界而不是靠人工硬切。常见做法是取H2、CH4、C2H6、C2H4、C2H2、CO、CO2这七种气体浓度作为基础特征再叠加上总烃、C2H2/C2H4、CH4/H2、C2H4/C2H6等比值特征。特征不是越多越好——PNN的判别依赖样本间距离维度太多会让距离度量失效特征总数控制在10到15维比较合适。2.2 构造训练数据集气体浓度归一化与派生特征DGA数据的第一个特点是量纲跨度大正常变压器氢气浓度可能只有几十ppm而故障瞬间乙炔浓度能上千。直接拿原始浓度算欧氏距离数值大的气体完全主导距离小浓度气体的信息直接被淹没。归一化是必须的但min-max归一化在这里不好使——数据分布严重右偏个别极端高值会把正常样本全部压缩到0到0.1的区间里。我一般用StandardScaler做标准化或者先取对数再标准化。下面这段代码演示了从原始DGA表构造PNN输入特征的完整过程import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler # 原始DGA台账列为七种气体浓度ppm和故障类型标签 df pd.read_csv(dga_samples.csv) # 第一步剔除全零样本未溶解气体或采样异常的记录 gas_cols [H2, CH4, C2H6, C2H4, C2H2, CO, CO2] df df[df[gas_cols].sum(axis1) 0].copy() # 第二步派生比值特征注意分母加极小值防止除零 df[total_hydrocarbon] df[[CH4, C2H6, C2H4, C2H2]].sum(axis1) df[c2h2_c2h4] df[C2H2] / (df[C2H4] 1e-6) df[ch4_h2] df[CH4] / (df[H2] 1e-6) df[c2h4_c2h6] df[C2H4] / (df[C2H6] 1e-6) feature_cols gas_cols [total_hydrocarbon, c2h2_c2h4, ch4_h2, c2h4_c2h6] X_raw df[feature_cols].values # 第三步对数变换压制右偏再标准化 X_log np.log1p(X_raw) # log1p 避免 log(0) scaler StandardScaler() X scaler.fit_transform(X_log) y df[fault_type].values # 故障类型标签如 normal,pd,d1,d2,t1,t2,t3代码逻辑说明np.log1p先对所有浓度做对数压缩把几百到几千的数值拉到同一个数量级再交给StandardScaler做零均值单位方差标准化。这样处理之后H2和C2H2的数值范围才具有可比性PNN计算欧氏距离时才不会偏向单一气体。1e-6是分母保护避免C2H4或C2H6浓度为0时出现除零。参数说明特征列的顺序要和之后训练、预测时完全一致包括派生特征的计算方式——训练时用哪个公式预测新样本时就必须用同一个公式。很多人在这里翻车训练集用log1p预测时忘了对新样本做同样的变换导致模型输入分布不一致诊断结果直接失真。建议把scaler和特征构造函数一起保存后面会讲到。2.3 数据划分最容易踩的泄漏问题DGA样本不是独立同分布的。同一台变压器在不同年份取了好几次油样这些样本高度相关。如果直接随机划分训练集和测试集同一台设备的历史样本很可能同时出现在两边模型“记住”了这台设备的个体特征测试精度虚高换到新变压器上立刻现出原形。正确做法是按变压器ID分组划分保证同一台设备的所有样本只出现在训练集或只出现在测试集from sklearn.model_selection import GroupShuffleSplit # df 中必须有 transformer_id 列标识每行样本来自哪台变压器 gss GroupShuffleSplit(n_splits1, test_size0.3, random_state42) train_idx, test_idx next(gss.split(X, y, groupsdf[transformer_id])) X_train, X_test X[train_idx], X[test_idx] y_train, y_test y[train_idx], y[test_idx]GroupShuffleSplit的groups参数接收每个样本所属的组别这里就是变压器ID切分时保证同一个组的样本全部进入同一侧。如果原始台账里没有变压器ID至少要按“同一变电站同一型号时间相近”合并出一个伪ID。这一步直接决定了后面所有精度指标的可信度——审稿人或者领导问起“这个精度怎么来的”你答“按设备分组划分的”和答“随机划分的”分量完全不同。3. 用Python实现一个可复现的概率神经网络故障诊断模型3.1 PNN的结构模式层、求和层、决策层各干什么概率神经网络的核心思想是把每个训练样本直接变成一个“模式神经元”。预测时新样本与每个训练样本计算高斯径向基距离距离越近激活值越大然后把同一类别的激活值累加哪一类累加值最大就判为哪一类。结构上分三层。模式层每个训练样本一个神经元激活函数是高斯核exp(-||x - x_i||² / (2σ²))σ就是平滑因子。求和层按故障类别把模式层的输出求和每个类别一个求和神经元。决策层比较各类别求和值的大小输出最大值对应的类别也可以把求和值归一化后当作各类别的概率估计。整个网络没有权重需要迭代更新训练过程就是“记住”训练样本预测过程就是算距离、激活、求和、比大小。相比BP神经网络PNN的优势在三个方面一是训练瞬时完成不需要调学习率、动量、隐藏层节点数二是面对小样本时不容易过拟合因为每个样本只影响局部区域三是可以天然输出每个类别的概率而不仅是硬分类标签——这对故障诊断很重要现场工程师需要看置信度来决定是否停电检修。3.2 核心代码在NumPy里实现PNN前向传播PNN的实现非常简洁不依赖深度学习框架NumPy就能完整跑通。下面是一份可以直接复制的完整实现import numpy as np from scipy.spatial.distance import cdist class PNN: 概率神经网络分类器支持多类别输出概率 def __init__(self, sigma0.3): self.sigma sigma self.x_train None self.y_train None self.classes None def fit(self, X, y): 训练保存训练样本和标签PNN不需要迭代优化 self.x_train np.asarray(X, dtypenp.float64) self.y_train np.asarray(y).ravel() self.classes np.unique(self.y_train) # 记录每个类别的样本数用于概率归一化时做补偿 self.class_counts {c: np.sum(self.y_train c) for c in self.classes} def predict_proba(self, X): 输出每个类别的概率行和为1 X np.atleast_2d(X) n_test X.shape[0] proba np.zeros((n_test, len(self.classes))) for i in range(n_test): # 计算当前测试样本与所有训练样本的欧氏距离 distances cdist(X[i:i1], self.x_train).ravel() # 高斯核激活sigma越小曲线越尖越容易过拟合 activations np.exp(-(distances ** 2) / (2 * self.sigma ** 2)) for j, cls in enumerate(self.classes): mask self.y_train cls # 求和层该类所有样本的激活值求和后再取平均 # 取平均是为了避免样本多的类别天然占优势 proba[i, j] np.mean(activations[mask]) if np.any(mask) else 0.0 # 归一化成概率 total proba[i].sum() if total 0: proba[i] proba[i] / total return proba def predict(self, X): 返回硬标签和概率矩阵 proba self.predict_proba(X) idx np.argmax(proba, axis1) return np.array([self.classes[k] for k in idx]), proba逻辑说明fit方法不做任何数学优化只是把训练样本存下来这就是PNN和BP最大的区别——没有损失函数、没有反向传播。predict_proba是核心cdist一次性算出测试样本与所有训练样本的欧氏距离矩阵高斯激活把距离转成相似度距离为0时激活为1距离越远衰减越快求和层对每个类别分别累加激活值。参数说明求和层这里用的是np.mean而不是np.sum。如果不取平均样本量最大的故障类型通常是“正常”类会天然获得更大的累加值小样本的放电类故障几乎永远分不出来。取平均等于对类别数量做了补偿这是一个很小的细节但对不平衡数据影响非常大。sigma的默认值0.3是基于标准化后的特征空间设定的经验值标准化后特征方差为10.3到0.5是一个比较合理的起始搜索范围后面会详述如何自动搜索。3.3 平滑因子搜索决定诊断精度的关键参数PNN只有一个超参数——平滑因子σ。σ太小高斯核又尖又窄每个训练样本只影响极小范围模型退化成“最近邻分类器”对噪声极度敏感σ太大高斯核过于平滑所有类别被抹成一片决策边界失去分辨力。这个参数直接决定诊断精度不能拍脑袋定。常见做法是在一个范围内做网格搜索配合交叉验证来选。下面是用5折分层交叉验证搜索σ的代码from sklearn.model_selection import StratifiedKFold from sklearn.metrics import accuracy_score def search_sigma(X, y, sigma_rangeNone): 在sigma_range范围内搜索最优平滑因子返回最优值和对应精度 if sigma_range is None: sigma_range np.arange(0.05, 2.0, 0.05) cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) best_sigma, best_score sigma_range[0], 0.0 for sigma in sigma_range: fold_scores [] for train_idx, val_idx in cv.split(X, y): model PNN(sigmasigma) model.fit(X[train_idx], y[train_idx]) pred, _ model.predict(X[val_idx]) fold_scores.append(accuracy_score(y[val_idx], pred)) mean_score np.mean(fold_scores) if mean_score best_score: best_score mean_score best_sigma sigma # 打印曲线方便观察sigma是否已经进入平缓区 # print(fsigma{sigma:.2f}, acc{mean_score:.4f}) return best_sigma, best_score # 用法在标准化后的特征上搜索 best_sigma, cv_acc search_sigma(X_train, y_train) print(f最优sigma: {best_sigma:.2f}, 交叉验证精度: {cv_acc:.4f})逻辑说明StratifiedKFold保证每一折里各类别比例和全量数据一致防止某些小类别在某一折里恰好全消失。对每个候选σ训练5个模型、验证5次取平均精度作为该σ的得分最终选出得分最高的σ。参数说明sigma_range取0.05到2.0、步长0.05这是标准化特征空间下的经验范围。如果特征没有标准化这个范围完全不适用——原始浓度下的距离可能是几百上千σ取0.05会导致所有激活值全部为0。这也是为什么前面第2章要把特征工程做扎实归一化不只是为了精度更让超参数搜索有了一个稳定可迁移的尺度。搜索结果里如果最优σ落在搜索区间的边界比如正好是0.05或2.0说明区间设置不合理要扩大范围重新搜索。提示网格搜索步长建议先粗后细。先用0.1步长扫一遍定位最优区间再在最优区间内用0.01步长精搜能省下不少时间。4. 把模型封装成能用的诊断工具输入输出设计与批量测试4.1 单条样本诊断与置信度输出模型训练好之后不能每次调用都翻出训练代码重新跑。工程上应该把整个流程封装成一个诊断函数输入一条新的DGA气体数据输出故障类型和各类别概率。下面这段代码把特征构造、标准化、PNN预测串在一起import joblib # 训练完成后保存预处理器和模型 joblib.dump(scaler, dga_scaler.pkl) class TransformerDiagnoser: 变压器DGA故障诊断封装输入气体浓度输出诊断结论 def __init__(self, model, scaler, feature_cols): self.model model self.scaler scaler self.feature_cols feature_cols def _build_features(self, gas_dict): 从原始气体浓度构建特征向量顺序必须与训练一致 h2 gas_dict[H2] ch4 gas_dict[CH4] c2h6 gas_dict[C2H6] c2h4 gas_dict[C2H4] c2h2 gas_dict[C2H2] co gas_dict[CO] co2 gas_dict[CO2] total_hydrocarbon ch4 c2h6 c2h4 c2h2 c2h2_c2h4 c2h2 / (c2h4 1e-6) ch4_h2 ch4 / (h2 1e-6) c2h4_c2h6 c2h4 / (c2h6 1e-6) raw np.array([[h2, ch4, c2h6, c2h4, c2h2, co, co2, total_hydrocarbon, c2h2_c2h4, ch4_h2, c2h4_c2h6]]) return self.scaler.transform(np.log1p(raw)) def diagnose(self, gas_dict, top_k3): 返回诊断结论、概率分布和置信度排序 x self._build_features(gas_dict) proba self.model.predict_proba(x)[0] labels self.model.classes # 按概率从高到低排序 order np.argsort(proba)[::-1] result [] for k in order[:top_k]: result.append({ fault_type: labels[k], probability: round(float(proba[k]), 4) }) return result # 用法示例 diagnoser TransformerDiagnoser(modelbest_model, scalerscaler, feature_colsfeature_cols) gas_data {H2: 180, CH4: 95, C2H6: 30, C2H4: 210, C2H2: 6, CO: 120, CO2: 900} result diagnoser.diagnose(gas_data) print(result)逻辑说明_build_features严格复现了第2章的特征构造过程——先算派生特征再做log1p最后用保存好的scaler做标准化。这里有一个关键点scaler只能做transform不能重新fit。很多人在预测阶段拿全部数据含新样本重新拟合了标准化器等于把新样本的信息泄漏进了特征变换输出的概率分布会整体偏移。参数说明top_k3控制返回几个候选故障类型。PNN输出的是各类别概率分布现场诊断时看Top 3比只看硬标签更有价值——如果“低温过热”和“中温过热”概率接近说明故障可能在温度边界提醒运行人员注意负荷变化。4.2 批量测试代码混淆矩阵与每个故障类型的召回率单条诊断只能验证模型能不能跑评估模型必须做批量测试。故障诊断这个领域整体精度高没有用——如果“正常”类占80%“高能放电”占2%一个把所有样本都判为“正常”的模型也能拿到80%精度但这种模型毫无价值。必须逐类看召回率和混淆矩阵from sklearn.metrics import confusion_matrix, classification_report import seaborn as sns import matplotlib.pyplot as plt # 在测试集上批量预测 y_pred, proba best_model.predict(X_test) # 逐类精度、召回率、F1 print(classification_report(y_test, y_pred, digits3)) # 混淆矩阵可视化观察哪些类别容易互相混淆 cm confusion_matrix(y_test, y_pred, labelsbest_model.classes) plt.figure(figsize(8, 6)) sns.heatmap(cm, annotTrue, fmtd, cmapBlues, xticklabelsbest_model.classes, yticklabelsbest_model.classes) plt.xlabel(Predicted) plt.ylabel(True) plt.tight_layout() plt.savefig(confusion_matrix.png, dpi150)逻辑说明classification_report输出每个故障类型的精确率、召回率、F1和样本数比只看整体精度诚实得多。混淆矩阵则直接暴露“哪两类容易被混淆”——这是故障诊断里最有价值的信息如果你发现局部放电PD样本经常被错判为低能放电D1说明这两类的气体特征本身确实很接近模型在逼近这个领域的天花板而不是代码有bug。4.3 工程输出置信度阈值与“未知类别”处理模型封装好之后还有一个工程细节必须处理当所有类别的概率都差不多、最大概率只有0.2时模型实际上“不知道”这是什么但硬分类仍然会给出一个标签。现场诊断里这种输出是有害的——它会让运行人员对模型的判断产生过度信任。常见做法是设置一个置信度阈值低于阈值时输出“未知故障建议人工分析”而不是硬给一个类别。在4.1的diagnose方法里加一行判断如果Top 1概率低于0.6就把fault_type置为unknown。这个0.6不是拍脑袋——需要看训练集交叉验证里所有正确样本的最低置信度取比它略低一点的值。这样既保证了模型在熟悉区域正常工作又能在面对训练集里没出现过的气体组合时主动“认怂”。5. 概率神经网络故障诊断常见问题与避坑记录5.1 现象训练集精度100%测试集精度只有60%这是我在帮别人review代码时见过最多的问题。用第2章的GroupShuffleSplit按设备分组后精度掉一截是正常的但如果掉得太多先检查是不是特征泄漏。常见原因有两个一是数据划分前做了标准化scaler.fit使用了全量数据包括测试集导致特征分布信息泄漏二是把同一台变压器的重复采样同时分进了训练集和测试集。解决方法是把scaler.fit严格限制在训练集上并且按设备ID分组切分。如果还不行检查特征里有没有混入标签相关的列——比如有些人会把“是否故障”这类与诊断目标高度相关的统计量当特征喂进去。5.2 现象局部放电和低能放电永远分不开这不是bug是这两类故障的DGA特征在物理上就很接近。局部放电初期会产氢和少量甲烷低能放电也以氢和乙炔为主当放电能量不大时两种故障的气体组成高度重叠。遇到这种情况先别急着调σ去看看混淆矩阵里这两类的样本量——如果每类只有5条样本PNN学不到足够的区分信息。解决方向有两条一是增加这两类的样本数哪怕只有十几条也能提升二是引入新的特征维度比如增加油中含水量、微水色谱数据或者把气体产气速率单位时间浓度变化率作为特征——放电类故障的产气速率通常比过热类高一个数量级。把产气速率加进去后这两类的区分度会明显改善。5.3 现象sigma搜索范围不同结果差异巨大有一种情况是σ0.3时精度85%σ0.35时精度掉到70%精度曲线剧烈波动。这通常说明σ太小模型进入了过拟合区间——高斯核太尖决策边界被个别噪声样本牵着走。检查方法是画出σ-精度曲线如果曲线在最优值附近像锯齿一样抖动说明σ搜索的步长太粗或者数据里存在异常样本。解决方法是先把σ范围整体放大比如从0.3到3.0扫一遍看曲线是否变平滑如果还是抖动逐条检查训练样本——很可能某条样本的气体浓度被错误记录了一个数量级比如C2H2本来是5录成了500。PNN对离群点敏感一条错误样本就能在局部区域形成一个假“聚类”。5.4 现象气体比值分母为零直接报错DGA数据里浓度为0的情况非常常见——正常变压器乙炔浓度就是0早期故障甲烷浓度也可能为0。如果在特征构造时直接做除法C2H4为0时C2H2 / C2H4会得到infPNN计算距离时inf会把一切距离变成inf整个模型直接失效。解决方法是分母加一个极小值常量比如1e-6。但这里有个隐藏坑加了1e-6之后比值的量纲和原始浓度量纲不同标准化后分布会比较怪异。更稳妥的做法是使用np.log1p直接对原始浓度做变换然后构造气体间的“对数差”特征比如log(C2H21) - log(C2H41)这样既避免了除零又天然压缩了动态范围。5.5 现象少数类样本只有十几条交叉验证结果忽高忽低5折交叉验证要求每一折里每个类别至少有1条样本如果某类只有5条分到某一折里可能全折都没有这个类别模型根本没见过它预测时这一类的概率永远为0。这就是为什么前面用StratifiedKFold而不是普通KFold。但即使分层样本量太少时交叉验证的方差也会非常大——同一组数据换一个随机种子精度从70%跳到95%。这不是模型的问题是样本量不够的统计必然。解决方法是接受现实把这类少数类的评估从“精度”改成“是否被漏报”也就是只看召回率同时考虑用SMOTE之类的过采样方法人工合成少数类样本——但注意必须在按设备分组划分之后再过采样否则还是泄漏。6. 验证模型不是自说自话统计检验与工程落地的下一步6.1 用多次重复交叉验证代替单次留出法来汇报精度单次留出法的结果受随机种子影响巨大放在故障诊断领域同样的数据、同样的模型换一个random_state精度可能差出10个百分点。要拿这个结果去写论文——哪怕是投故障诊断领域二区和三区的SCI期刊审稿人第一个问题就是“你这个精度是单次运行的结果还是多次重复的平均值”。常见做法是把外层再包一层循环用StratifiedKFold重复10次每次用不同的随机种子记录精度、召回率、F1的均值和标准差最终汇报为“平均精度±标准差”。标准差小于3%说明模型稳定大于5%说明样本量不足或类别不平衡严重需要在论文里如实讨论。工程报告也一样——给领导汇报诊断准确率时不带标准差的数据没有说服力领导随便换一批数据一测就露馅。6.2 从PNN到可交付把模型嵌入状态检修流程的三个动作模型验证通过之后距离真正可用还有三步。第一步是给每个诊断结论配上DGA原始数据和Top 3概率输出让运行人员能回溯判断而不是只看到“高温过热”四个字。第二步是设置置信度阈值并明确“未知类”的处理流程概率低于阈值时触发人工复核而不是盲目信任模型。第三步是定期重训——变压器故障类型在不同电压等级、不同厂家设备上的气体特征有差异模型部署到新地区后前几个月的预测结果要人工核对积累足够多的本地样本后重训一次σ也要重新搜索。这三个动作做完PNN才从一段“故障诊断代码”变成真正能在状态检修里起作用的东西。说一个自己的血泪经验有一年我把一整年的DGA数据拿来训PNN用了随机切分测试精度95.8%当场觉得可以上线了。后来按变压器编号分组重做直接掉到78%。那一刻才意识到之前的高精度里面有相当一部分是“记住了设备个体”而非“学会了故障模式”。从那以后凡是拿到新数据第一件事就是查样本来自哪些设备、时间跨度多长先分组再谈建模。如果你也正在做变压器故障诊断方向建议从DGA特征构造和按设备分组这两件事入手先把数据侧做扎实再跑第3章的PNN模型——你会发现模型本身的代码半小时就能写完数据坑才是真正花时间的地方。希望帮到你。本文还有配套的精品资源点击获取
返回列表