☰
基于特征融合的一维CNN电能质量扰动分类实战
2026/10/5 4:28:10 网站建设 项目流程

简介:这份PDF面向电力系统保护与控制、电能质量分析方向的研究生与工程技术人员,聚焦电能质量扰动分类中传统特征选取算法鲁棒性差、抗噪能力弱的问题。文中提出基于特征融合的一维卷积神经网络模型,先用三个CNN子模型分别提取扰动信号特征向量,再融合为新特征向量,最后经BP神经网络完成分类,并与改进前一维CNN及现有算法进行对比验证。资源包共1个PDF文件,大小约2.26MB,内容为完整期刊论文,含摘要、引言、算法原理、仿真实验与结论等章节,便于系统研读模型结构与实验设计。目前已有136人学习,适合深度学习、机器学习与数据建模方向的读者参考,可从中获取特征融合思路、一维CNN建模方法及电能质量扰动分类的完整研究方案。

1. 电能质量扰动分类为什么值得用一维 CNN 重做一遍

做过电能质量监测的人大概都有过这种体验:现场采回来的电压波形,肉眼一看就知道是暂降还是谐波,但要让算法自动分出来,识别率死活卡在 95% 上下,噪声一大就崩。传统路子是先做特征提取——傅里叶、小波、S 变换轮着上——再喂给 SVM 或 BP 网络分类。问题在于,特征选多了冗余,选少了丢信息,换个台区或换种扰动组合,模型就得重新调。这份来自西华大学和国网四川电科院的论文,提出了一种基于特征融合的一维卷积神经网络(FCNN)方案,核心思路是用三个不同深度的 CNN 子模型各自提取特征向量,拼成一个 768 维的融合特征,再交给全连接层分类。它解决的就是“不想手工选特征、又要在强噪声下稳住识别率”这个具体问题。适合正在做电能质量扰动识别、手里有波形数据、想用深度学习替换传统特征工程路线的工程师,也适合想搞明白一维 CNN 在序列信号上到底怎么落地的人。

2. FCNN 模型拆解:三个子模型怎么搭、特征怎么融

2.1 为什么选一维 CNN 而不是二维图像转换

电能质量扰动信号本质是一维时间序列,采样频率 3200 Hz、单样本 1000 个点、基波频率 50 Hz。有些方案会先把一维信号转成二维灰度图像再上二维 CNN,但转换过程本身就引入额外计算,而且暂降和中断的灰度图特征差异不明显,识别率反而掉。一维 CNN 直接对原始序列做卷积,卷积核沿时间轴滑动,计算量小、速度快,对这类序列信号是更自然的选择。论文里也引了心电图分类、电机故障诊断等场景用一维 CNN 的案例,说明这条路子在工程上已经跑通了。

2.2 三个子模型的结构差异与选型逻辑

FCNN 不是随便堆三个网络。它选了 LeNet-5、VGGNet、ResNet 三个经典结构做改造,分别得到 Net_6、Net_18、Net_21。选这三个是因为它们代表了不同的深度和感受野:Net_6 在 LeNet-5 基础上加层,偏浅,抓局部细节快;Net_18 借鉴 VGGNet 反复叠 3×1 小卷积核,中等深度,特征抽象能力更强;Net_21 参考 ResNet 优化层数和超参数,最深,能捕捉更长范围的模式。三个子模型第一个卷积层的卷积核长度都设为 32,目的是减少训练时间——第一层核太长,后面计算量会炸。每个子模型最后一个池化层输出 256 维向量,三个拼起来就是 768 维。

这里有个关键设计:子模型不是端到端一起训练的。论文的训练流程分三步走。第一步,用训练集 1 分别训练三个子模型,让它们各自收敛。第二步,把训练集 2 输入训练好的子模型,取最后一个池化层的输出作为特征向量,丢弃后面的全连接部分。第三步,把三个 256 维向量首尾拼接成 768 维,用这个融合特征去训练最后的分类层。这种分阶段训练的好处是每个子模型可以独立调参,不会因为某个子模型拖后腿导致整体梯度混乱。

2.3 融合层、Dropout 与分类层的参数设置

Merge 层做的是拼接(concatenate),不是相加或平均。拼接保留了每个子模型特征的完整信息,维度从 256 变成 768。拼接后接一个 Dropout 层,Dropout 率设 0.5,这是防止过拟合的常规操作——融合特征维度高了,参数量上去,不加 Dropout 很容易在训练集上过拟合。最后全连接层的输出节点数设为 8,对应八类扰动:暂升 C0、暂降 C1、中断 C2、谐波 C3、闪变 C4、振荡 C5、暂升+谐波 C6、暂降+谐波 C7。输出层用 Softmax 激活,损失函数用交叉熵。

用 Keras 搭这个模型的话,子模型的定义大概长这样:

import keras from keras.models import Model from keras.layers import Input, Conv1D, BatchNormalization, Activation, MaxPooling1D, Dropout, Dense, concatenate def build_sub_model(input_len=1000, num_classes=8, depth='shallow'): inputs = Input(shape=(input_len, 1)) # 第一层卷积核长度固定 32,减少训练时间 x = Conv1D(filters=32, kernel_size=32, strides=1, padding='same')(inputs) x = BatchNormalization()(x) x = Activation('relu')(x) x = MaxPooling1D(pool_size=2)(x) if depth == 'shallow': # Net_6 风格 x = Conv1D(64, 16, padding='same')(x) x = BatchNormalization()(x) x = Activation('relu')(x) x = MaxPooling1D(2)(x) elif depth == 'medium': # Net_18 风格,反复叠 3x1 卷积 for _ in range(3): x = Conv1D(64, 3, padding='same')(x) x = BatchNormalization()(x) x = Activation('relu')(x) x = MaxPooling1D(2)(x) else: # Net_21 风格,带残差思路 for _ in range(4): x = Conv1D(128, 3, padding='same')(x) x = BatchNormalization()(x) x = Activation('relu')(x) x = MaxPooling1D(2)(x) # 取最后一个池化层输出作为特征向量,此处输出 256 维 feature = MaxPooling1D(pool_size=2)(x) return Model(inputs, feature) # 三个子模型分别构建 net_6 = build_sub_model(depth='shallow') net_18 = build_sub_model(depth='medium') net_21 = build_sub_model(depth='deep') # 融合层:拼接三个 256 维特征 merged = concatenate([net_6.output, net_18.output, net_21.output]) # 768 维 x = Dropout(0.5)(merged) outputs = Dense(num_classes, activation='softmax')(x) fcnn = Model(inputs=[net_6.input, net_18.input, net_21.input], outputs=outputs) fcnn.compile(optimizer=keras.optimizers.SGD(lr=0.005), loss='categorical_crossentropy', metrics=['accuracy'])

这段代码里几个参数值得注意。kernel_size=32只用在第一层,后面层用 3 或 16,这是论文明确说的“减少训练时间”的做法。BatchNormalization加在激活函数之前,论文里 BN 层的作用是让每层输入分布一致,可以用更大学习率、加快收敛。SGD(lr=0.005)是论文里用的随机梯度下降,学习率 0.005,Mini-Batch 大小 30。实际复现时如果发现收敛太慢,可以换成 Adam 先跑通,再切回 SGD 调参。

2.4 训练集划分与数据准备

论文的数据设置是:每类扰动 1000 个样本,随机选 600 个做训练集,剩下 400 个做测试集。采样频率 3200 Hz,单样本 1000 点,基波 50 Hz。信噪比测试在 25 dB 和 15 dB 两档下做。如果你手头有自己的波形数据,建议按同样比例划分,并且确保训练集和测试集的噪声水平一致——否则模型在干净数据上训练、在噪声数据上测试,识别率会虚低。数据预处理阶段,常见做法是对幅值做归一化,把电压标幺值缩放到 [0,1] 或 [-1,1],这样卷积核的初始权重不用反复调。

3. 从训练到验证:复现 FCNN 的完整操作链路

3.1 分阶段训练的具体步骤

复现 FCNN 最容易翻车的地方是训练顺序。论文的流程是先用训练集 1 训练三个子模型,再用训练集 2 提取特征,最后用融合特征训练分类层。如果你直接把三个子模型和分类层拼在一起端到端训练,梯度会同时回传到三个子模型,导致它们互相干扰,收敛慢且识别率不稳定。正确做法是分三步:

第一步,单独训练每个子模型。每个子模型后面临时接一个全连接层输出 8 类,用训练集 1 训练到收敛。论文里 Net_6、Net_18、Net_21 在 25 dB 下分别训练 12、30、40 代后识别率趋于稳定,在 15 dB 下分别需要 17、40、50 代。这个代数是参考值,实际训练时看验证集 loss 不再下降就可以停。

第二步,冻结子模型权重,把训练集 2 输入进去,取最后一个池化层的输出。这一步只做前向传播,不更新权重。三个子模型各输出 256 维,拼成 768 维特征向量。训练集 2 可以和训练集 1 相同,也可以重新采样,论文里没有强制要求不同。

第三步,用 768 维特征训练最后的分类层。分类层就是一个 Dropout(0.5) 加一个 8 节点的 Dense 层,训练到收敛即可。这一步训练量很小,因为特征已经提取好了。

# 第一步:单独训练子模型 for sub_model, name in zip([net_6, net_18, net_21], ['Net_6', 'Net_18', 'Net_21']): x = sub_model.output x = Dense(8, activation='softmax')(x) temp_model = Model(inputs=sub_model.input, outputs=x) temp_model.compile(optimizer=SGD(lr=0.005), loss='categorical_crossentropy', metrics=['accuracy']) temp_model.fit(X_train1, y_train1, batch_size=30, epochs=50, validation_split=0.2) # 保存子模型权重 sub_model.save_weights(f'{name}_weights.h5') # 第二步:冻结子模型,提取特征 for sub_model in [net_6, net_18, net_21]: for layer in sub_model.layers: layer.trainable = False features_6 = net_6.predict(X_train2) features_18 = net_18.predict(X_train2) features_21 = net_21.predict(X_train2) X_train3 = np.concatenate([features_6, features_18, features_21], axis=1) # (N, 768) # 第三步:训练分类层 classifier = Sequential([ Dropout(0.5, input_shape=(768,)), Dense(8, activation='softmax') ]) classifier.compile(optimizer=SGD(lr=0.005), loss='categorical_crossentropy', metrics=['accuracy']) classifier.fit(X_train3, y_train2, batch_size=30, epochs=30)

这段代码里layer.trainable = False是关键,冻结后子模型权重不再更新,只做特征提取。np.concatenate沿 axis=1 拼接,得到 768 维。分类层用Sequential单独搭,输入维度 768,输出 8。

3.2 用 t-SNE 验证特征融合是否真的有效

论文用 t-SNE 把子模型特征和融合特征降到二维做可视化,结论是融合后不同类别的距离加大、重合减少。这个验证方法很实用,建议复现时也跑一遍。具体做法是:取测试集的一部分样本,分别用单个子模型和融合特征提取向量,然后用 sklearn 的 TSNE 降到二维,画散点图,不同类别用不同颜色。如果融合后的散点图里各类别分得更开,说明融合确实增加了区分度。这一步不需要写进生产代码,但作为调试手段很有价值——如果融合后反而更混,说明子模型选得不对或者特征拼接方式有问题。

from sklearn.manifold import TSNE import matplotlib.pyplot as plt # 提取融合特征 feat_6 = net_6.predict(X_test) feat_18 = net_18.predict(X_test) feat_21 = net_21.predict(X_test) feat_fused = np.concatenate([feat_6, feat_18, feat_21], axis=1) # t-SNE 降维 tsne = TSNE(n_components=2, random_state=42) feat_2d = tsne.fit_transform(feat_fused) plt.figure(figsize=(8, 6)) for cls in range(8): idx = y_test.argmax(axis=1) == cls plt.scatter(feat_2d[idx, 0], feat_2d[idx, 1], label=f'C{cls}', s=5) plt.legend() plt.title('FCNN fused feature t-SNE') plt.show()

TSNE(n_components=2)把 768 维降到 2 维,random_state=42固定随机种子保证可复现。散点大小设 5 避免重叠太严重。如果某两类始终混在一起,比如暂降 C1 和中断 C2,说明这两个类在特征空间里确实难分,可以考虑增加子模型数量或换更深的网络。

3.3 不同信噪比下的识别率对比与参数敏感度

论文给出的结果:25 dB 下 FCNN 平均识别率 99.47%,15 dB 下 99.31%。单类看,暂降 C1 在 25 dB 下 96.83%、15 dB 下 97.50%,中断 C2 在 25 dB 下 98.96%、15 dB 下 97.00%,其余六类基本都是 100%。暂降和中断识别率偏低的原因是噪声环境下两者的空间特征相似——都是幅值下降,只是下降程度不同。对比传统方法,FCNN 在 30 dB 下识别率 99.53%,而基于生长-修剪优化 RBF 神经网络的方案只有 95.63%,基于拉格朗日延迟器优化小波基的方案只有 92.00%。无噪声下 FCNN 99.88%,也高于这两个对比方案。

参数敏感度方面,学习率 0.005 是论文用的值,实际复现时如果 loss 震荡厉害,可以降到 0.001;如果收敛太慢,可以升到 0.01 但别超过,否则容易跳过最优解。Mini-Batch 大小 30 对 1000 点长度的序列来说偏小,显存够的话可以加到 64 或 128,训练更稳。Dropout 率 0.5 是常规值,如果发现训练集识别率远高于测试集,可以加到 0.6;如果两者都低,降到 0.3 试试。

4. 避坑与排查:复现 FCNN 时最容易翻车的五个地方

4.1 子模型端到端训练导致识别率上不去

现象:把三个子模型和分类层拼在一起直接训练,训练集识别率能到 99%,但测试集只有 95% 左右,曲线波动大。

原因:端到端训练时梯度同时回传到三个子模型,它们的参数更新方向互相干扰,导致提取的特征不稳定。论文里 LeNet-5 单模型测试识别率只有 96.11% 且曲线波动大,就是这个问题的体现。

解决:严格按论文的三阶段训练流程走。先单独训练子模型到收敛,冻结权重后再提取特征,最后只训练分类层。如果非要端到端微调,学习率要设得很小(比如 0.0001),并且先冻结子模型训练几轮分类层,再解冻微调。

4.2 融合特征维度对不上

现象:拼接三个子模型输出时报错,维度不匹配。

原因:子模型最后一个池化层的输出维度取决于输入长度和池化层数。论文里输入 1000 点、输出 256 维,但如果你改了网络层数或池化窗口大小,输出维度会变。三个子模型如果结构不同,输出维度可能不一致,没法直接拼接。

解决:在每个子模型最后加一个GlobalAveragePooling1D或Flatten再接Dense(256),强制统一到 256 维。或者打印每个子模型output_shape确认维度后再拼接。

4.3 噪声水平不匹配导致测试识别率虚低

现象:训练时用干净数据,测试时加 15 dB 噪声,识别率从 99% 掉到 80% 多。

原因:模型在干净数据上学到的特征对噪声敏感,噪声一加,特征分布偏移,分类层就懵了。

解决:训练集里就要混入不同信噪比的样本。论文在 25 dB 和 15 dB 下分别测试,说明训练时应该覆盖目标噪声范围。常见做法是训练集按信噪比分层采样,比如 30 dB、25 dB、20 dB、15 dB 各占一部分,让模型学会在噪声下提取稳定特征。

4.4 BN 层加错位置导致收敛慢

现象:训练 loss 下降很慢,几十轮还在高位震荡。

原因:BatchNormalization 加在激活函数之后,或者加在池化层之后,效果会打折扣。论文里 BN 层是加在卷积层和激活层之间的。

解决:按Conv1D -> BatchNormalization -> Activation -> MaxPooling1D的顺序搭。BN 层的作用是让卷积输出分布标准化,加在激活前才能让激活函数工作在敏感区间。

4.5 学习率设太大导致 loss 爆炸

现象:训练几个 batch 后 loss 变成 NaN。

原因:SGD 学习率 0.005 对某些初始化权重来说偏大,梯度爆炸。

解决:先降学习率到 0.001 跑通,确认模型能收敛后再逐步升到 0.005。或者加梯度裁剪clipnorm=1.0。另外检查输入数据是否归一化,没归一化的数据幅值大,卷积输出也大,更容易爆。

5. 进阶技巧:用特征融合思路迁移到其他序列分类任务

FCNN 这套“多子模型提特征 + 拼接融合 + 分类层”的架构,不只能用在电能质量扰动分类上。任何一维序列分类任务——比如轴承故障诊断、心电图异常检测、电机振动信号识别——都可以套这个框架。关键是把子模型的差异拉开:不要用三个结构一样的网络,那样融合没意义。可以一个浅层抓局部、一个中层抓纹理、一个深层抓全局模式,或者用不同卷积核长度(比如 3、7、15)让感受野不同。

迁移时有个参数要重新调:融合后的特征维度。论文里是 256×3=768,如果你的子模型输出不是 256 维,分类层的输入维度要跟着改。另外 Dropout 率也要根据融合维度调整,维度越高,Dropout 率可以适当加大。我一般会先跑一个子模型看单模型识别率,如果单模型已经 98% 以上,融合提升空间不大;如果单模型只有 95% 左右且波动大,融合通常能拉到 99% 且曲线更稳。

验证融合是否有效,除了看识别率,一定要跑 t-SNE。我见过融合后识别率涨了但 t-SNE 图里类别反而更混的情况,那是子模型提取的特征冗余,融合只是增加了维度没增加信息量。这时候要换子模型结构,而不是继续加网络深度。

从那以后我每次做序列分类,只要单模型识别率卡在 95% 上下波动,就强制走一遍“三个不同深度子模型 + 特征拼接 + t-SNE 验证”的流程,比调学习率和加数据都管用。希望帮到你。

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

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

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

立即咨询