news 2026/10/3 11:06:18

格拉姆角场+CNN实现轴承故障诊断:SEU数据集完整实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
格拉姆角场+CNN实现轴承故障诊断:SEU数据集完整实战

前一阵子做轴承故障诊断,一开始直接用一维卷积网络怼原始振动信号,调了几轮准确率始终在某个位置卡住。后来把信号切成长度适中的窗口,用格拉姆角场(GAF)把每个窗口编码成二维图像,再丢给CNN分类,效果一下子拉开了。这个思路很多人都听过,但真正上手东南大学轴承故障诊断数据集(SEU)时,坑比想象中多:mat文件怎么读、窗口取多大、GAF矩阵怎么算才不会内存爆炸、训练集和测试集怎么切才不泄露。这篇文章就是把这些过程完整讲一遍,从GAF原理到SEU数据集拆解,再到可直接运行的代码实现,一步不落。适合正在做旋转机械故障诊断、时序分类,或者对时间序列转图像思路有兴趣的工程人员和研究生参考。

1. 为什么非要把振动信号画成"图像":GAF的出发点与SEU数据集结合点

1.1 一维模型的三点困扰

先说说我最初直接用一维信号建模的体验。SEU数据集这种轴承振动信号,本质上是采样率不低的一维时间序列,拿一维CNN或者LSTM去处理看起来顺理成章,但实际跑起来有三点困扰。

第一,振动信号里的故障特征往往不是集中在某一个瞬间,而是以周期性冲击的形式分布在整段信号里,一维卷积想同时抓住"冲击点位置"和"冲击周期"这两个信息,需要把感受野调得比较大,网络结构也就不自觉地加深加宽,训练变慢,还容易过拟合。

第二,直接拿原始波形做输入,网络要自己学习"从波形到故障"的映射,中间没有任何特征提取环节帮助它降低难度。如果训练样本量不够充足,模型很容易学到一些跟故障无关的噪声模式,换一段数据就失效。

第三,也是我觉得最要命的一点:一维信号缺少"上下文可视化"的能力。你很难直观看到模型到底关注了信号的哪部分,排查问题全靠猜指标。这在写报告、跟团队解释模型行为的时候特别被动。

格拉姆角场解决的就是第一点和第二点。它的核心思想是:把一段一维时间序列,通过极坐标变换,映射成一张二维图像。这样原本在时间轴上前后依赖的关系,变成了图像里像素与像素之间的空间关系,CNN这类天生擅长捕捉空间局部纹理的模型,就有了发挥空间。

1.2 GAF极坐标编码的数学逻辑

GAF的原理并不复杂,但值得从头捋一遍,因为很多文章代码写得很玄,其实底层逻辑很简单。

第一步,把时间序列x归一化到[-1, 1]区间,通常用min-max缩放:

x' = ((x - min(x)) / (max(x) - min(x))) * 2 - 1

第二步,把归一化后的数值映射成极坐标下的角度。因为余弦函数的值域正好是[-1, 1],所以可以令角度θ = arccos(x')。这里有个很直观的几何意义:x'越接近1,θ越接近0;x'越接近-1,θ越接近π。而时间戳t则通过半径r = t / N映射到[0, 1]区间,其中N是序列长度。

这样每个采样点,就变成了极坐标平面上的一个点,角度携带的是信号的数值信息,半径携带的是时间信息。这恰好把"数值随时间变化"的一维关系,拆成了两个独立的几何维度。

第三步是核心:GAF定义了两个采样点i和j之间的"关系"。如果计算它们角度的余弦差,就得到格拉姆角和场(GASF):

GASF[i][j] = cos(θi + θj) = cos(θi) * cos(θj) - sin(θi) * sin(θj)

如果计算角度的正弦差,就得到格拉姆角差场(GADP):

GADP[i][j] = sin(θi - θj) = sin(θi) * cos(θj) - cos(θi) * sin(θj)

得到的GASF或GADP都是一个N×N的矩阵,矩阵里每个元素都同时包含了点i和点j的信息。沿着对角线看,GASF对角线上的值是cos(2θi),它和原始信号x'保持单调映射,意味着原始信号的关键趋势信息其实被保留在了对角线上;GADP对角线全部为0,但非对角线上的值对局部变化更敏感,抗干扰能力在某些场景下比GASF更好。

1.3 编码之后为什么CNN就"看得懂"

一张GAF图像,本质上是把几个采样点之间的角度关联全部铺开,形成一个类似纹理的二维图案。不同故障状态下,振动信号里的冲击间隔、幅值分布、谐波结构都不一样,于是它们编码出来的GAF图像纹理也明显不同:内圈故障往往呈密集的点状纹理,外圈故障会出现规律的横向条纹,正常信号则相对平滑。CNN的学习目标就从"理解抽象波形"变成了"识别图像纹理",这件事对卷积网络来说实在太擅长了。实测下来,直接把GAF矩阵当成灰度图送入CNN,准确率能超过原始一维信号加一维CNN的结果,这也正是GAF这几年在故障诊断里越来越常见的原因。

2. 东南大学轴承数据集拆解:.mat背后的故障分布与切分逻辑

2.1 数据集组成与标签映射

东南大学轴承故障诊断数据集(SEU,Southeast University dataset)是公开的旋转机械故障数据集,常见版本里主要包含滚动轴承和齿轮箱两类对象。滚动轴承部分通常覆盖正常、内圈故障、外圈故障、滚动体故障等状态;齿轮箱部分则涉及缺齿、裂纹、磨损等故障类型。数据文件是.mat格式,用MATLAB或Python的scipy库都能读取。

需要注意的是,不同渠道下载的SEU数据版本并不完全一致,我手头这一个版本,每个.mat文件里存储的是某个工况下、某个故障类别的长时长振动采样序列,文件里变量的命名风格也不统一。所以代码里写自适应读取是很有必要的,不能硬编码变量名。后面第5章会给出一个通用的读取函数。

标签映射我建议按这个表来定义,方便后面训练和评估:

标签状态说明
0Normal正常状态
1Inner Race内圈故障
2Outer Race外圈故障
3Ball滚动体故障

如果你是拿齿轮箱数据做,就把标签替换成对应的缺齿、磨损等类别。整体流程是一样的,不需要改模型结构。

2.2 和CWRU比,SEU适合做什么

很多人一提轴承故障数据集,第一个想到的是西储大学CWRU数据集。CWRU确实名气大,网上教程一大把,拿来学习GAF流程没有任何问题。但SEU有几个特点让它更贴近实际工程场景。

CWRU的采样频率固定、负载条件相对单一,数据质量高但"太干净"。SEU的数据往往包含更多的工况差异,信噪比更低,故障冲击没有CWRU那么明显,模型在SEU上能跑出高准确率的难度更大。用我自己的话说,如果你在SEU上能把GAF+CNN这套流程调到90%以上的准确率,那迁移到现场采集的电机、齿轮箱数据会从容很多,因为SEU的工况多样性和噪声水平更接近工业环境。

SEU还有一个优势是长记录。原始信号持续时间长,意味着你可以用滑动窗口切出大量样本,不用发愁数据量不够。而且可以研究窗口长度、重叠率对结果的影响,这在工程里是非常实用的调优方向。

2.3 滑动窗口切分时最容易埋下的数据泄漏雷

说到切分,必须强调一个我踩过的坑:滑动窗口切样本虽然能扩充数据量,但也容易造成数据泄漏。轴承振动信号是连续采集的,相邻两个窗口之间如果存在重叠,或者两个窗口来自同一段连续信号,那它们的特征会高度相似。如果你直接用train_test_split对全体样本做随机划分,很可能同一个原始段切出来的窗口,一部分进了训练集,一部分进了测试集,模型相当于提前见过答案,验证集准确率虚高。等你部署到新的现场数据上,准确率突然掉下来,还找不到原因。

正确的做法是:以"连续信号段"为最小单位划分数据集,保证来自同一条原始长序列的所有窗口,要么全在训练集,要么全在测试集。代码层面可以用GroupShuffleSplit来实现,具体写法在第4章。这个细节很多人写博客时不会提,但它是你复现结果稳定性的关键。

3. 全套代码实现:从mat加载到GAF编码的完整流水线

3.1 环境准备与mat文件自适应读取

开始写代码之前,先把环境准备好。你只需要以下几个库:numpy、scipy、pandas、matplotlib、torch、scikit-learn。如果没有装PyTorch,直接用CPU也能跑,只是慢一些。

pip install numpy scipy pandas matplotlib scikit-learn torch

首先是读mat文件。数据文件的变量名在不同版本里可能不一样,所以我写了一个自适应读取函数:先遍历mat文件里所有变量,自动挑出名字不以双下划线开头、且类型是数组的那个变量作为振动信号。这样就不会因为变量名不同而报错。

import scipy.io as sio import numpy as np def load_seu_signal(mat_path): """ 自适应读取SEU数据集的mat文件 返回一维振动信号ndarray """ raw = sio.loadmat(mat_path) for key in raw.keys(): if key.startswith('__'): continue val = raw[key] if hasattr(val, 'shape') and val.ndim >= 1: # 如果是二维列向量,压平为一维 signal = np.squeeze(val) signal = np.asarray(signal, dtype=np.float64) return signal raise ValueError(f"未在文件 {mat_path} 中找到有效的信号变量")

这里有个说明:SEU的部分.mat文件里,信号以二维列向量形式存储,所以np.squeeze是必要的。读取完成后,你大概率会得到一个长度好几万甚至几十万的序列,这就是我们后续切窗的原料。

3.2 窗口切分与PAA分段聚合近似

拿到长信号后,第一步是切成等长样本。窗口长度怎么选?这是个关键参数。窗口太短,可能一两个冲击周期都没包含进去,丢失故障特征;窗口太长,GAF矩阵尺寸会很大,计算开销和内存都会暴涨。工程经验上,先做一次FFT看信号的基频,再保证窗口至少包含5到10个旋转周期。如果没有额外信息,我建议从1024或2048点开始试。

切窗时我建议用0.5的重叠率,也就是stride = sample_len // 2。这个设定在不增大样本量的情况下,可以靠重叠增强模型对冲击偏移的鲁棒性。

def make_samples(signal, sample_len=1024, stride=512): samples = [] n = len(signal) for start in range(0, n - sample_len + 1, stride): samples.append(signal[start:start + sample_len]) return np.asarray(samples)

切完窗之后,GAF编码前建议先做一步降采样,也就是PAA(分段聚合近似,Piecewise Aggregate Approximation)。原理很简单:把长度为sample_len的信号均匀分成n_bins段,每段取平均,得到一个长度远小于sample_len的新序列。这样做的原因是GAF矩阵的尺寸是输入序列长度的平方,1024点输入会得到1024×1024的矩阵,一个样本就有上百万个像素,太多样本时内存根本顶不住。而PAA到64或128点后,GAF矩阵是64×64或128×128,计算量和信息保留之间达到一个比较平衡的状态。

def paa_reduce(signal, n_bins=64): """ 分段聚合近似降维 把任意长度的signal通过平均池化压缩到n_bins个点 """ signal = np.asarray(signal, dtype=np.float64) length = len(signal) bin_size = length / n_bins reduced = np.zeros(n_bins) for i in range(n_bins): start = int(i * bin_size) end = max(int((i + 1) * bin_size), start + 1) reduced[i] = np.mean(signal[start:end]) return reduced

3.3 GASF和GADP两种编码的矩阵化实现

接下来是格拉姆角场的核心编码函数。我用矩阵运算而非逐元素循环,因为循环在Python里太慢了,矩阵方式可以一次算完。整个函数需要注意几个细节:归一化时要把边界夹紧到[-1, 1],避免arccos出现NaN;如果有两个样本点数值相同,它们编码出来的角度关系也相同,这没有问题。

def gaf_encode(signal, n_bins=64, method='gasf'): """ 格拉姆角场编码 method: 'gasf' -> 格拉姆角和场 'gadp' -> 格拉姆角差场 输入signal为原始振动信号片段,输出为n_bins x n_bins的二维矩阵 """ # 1. PAA降维 if len(signal) != n_bins: signal = paa_reduce(signal, n_bins) # 2. min-max归一化到[-1, 1] min_val = np.min(signal) max_val = np.max(signal) if max_val - min_val < 1e-12: # 如果信号几乎没波动,直接归零 signal = np.zeros_like(signal) else: signal = (signal - min_val) / (max_val - min_val) * 2.0 - 1.0 signal = np.clip(signal, -1.0, 1.0) # 3. 极坐标角度编码 theta = np.arccos(signal) cos_theta = np.cos(theta) # 等价于signal sin_theta = np.sin(theta) # 4. 矩阵化计算GAF if method == 'gasf': # cos(theta_i + theta_j) = cos_i * cos_j - sin_i * sin_j gaf = cos_theta[:, np.newaxis] @ cos_theta[np.newaxis, :] - \ sin_theta[:, np.newaxis] @ sin_theta[np.newaxis, :] else: # gadp: sin(theta_i - theta_j) = sin_i * cos_j - cos_i * sin_j gaf = sin_theta[:, np.newaxis] @ cos_theta[np.newaxis, :] - \ cos_theta[:, np.newaxis] @ sin_theta[np.newaxis, :] return gaf

这段代码的核心,是第4步的两个矩阵乘法。这里简单解释一下为什么矩阵乘法能表达两两角度关系。cos_theta[:, np.newaxis]是一个n_bins×1的列向量,cos_theta[np.newaxis, :]是一个1×n_bins的行向量,两者做内积外扩,得到的就是一个n_bins×n_bins的矩阵,其中第[i][j]个元素恰好等于cos_theta[i] * cos_theta[j]。后面的sin项同理。整个计算没有任何循环,速度非常快。

编码完成后,我习惯把每个样本的二维矩阵直接保存成npy文件,或者全部堆叠成一个四维数组,方便后续PyTorch加载。如果样本量特别大,建议边编码边存硬盘,避免一次性把所有图像都放进内存。

def encode_dataset(samples, n_bins=64, method='gasf'): """把一个样本集全部编码成GAF矩阵,返回形状为 [N, n_bins, n_bins] 的数组""" encoded = [] for sig in samples: encoded.append(gaf_encode(sig, n_bins=n_bins, method=method)) return np.asarray(encoded)

编码完可以顺手把几张图可视化一下,确认不同故障类别的纹理有区分度,这一步值得做,能帮你提前发现归一化或切窗参数的问题。可视化代码很简单,用matplotlib的三行代码即可:

import matplotlib.pyplot as plt def show_gaf(gaf_matrix, title='GAF'): plt.imshow(gaf_matrix, cmap='viridis') plt.colorbar() plt.title(title) plt.show()

4. CNN训练与结果分析:把GAF图像喂给网络的实际效果

4.1 网络结构与超参数设计

GAF编码完毕,接下来就是模型部分。我用一个结构不算深的二维CNN,三个卷积块加一个全连接分类头。输入是单通道灰度图,也就是n_bins×n_bins的二维矩阵。

网络设计的考量点有三个。第一,GAF图像往往很小(64×64或128×128),所以第一层卷积的核不需要太大,3×3就够,重点是把局部纹理关系提取出来。第二,每个卷积块后面都接BatchNorm和MaxPool,BatchNorm是为了抑制不同样本间幅值分布差异带来的影响,MaxPool可以进一步降低特征图尺寸,减少参数量。第三,全连接层前加一个AdaptiveAvgPool,把特征图拉到一个固定尺寸,这样即使你改了n_bins,网络结构也不用跟着改。

import torch import torch.nn as nn class GAFCNN(nn.Module): def __init__(self, n_classes=4): super().__init__() self.features = nn.Sequential( nn.Conv2d(1, 16, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(16), nn.MaxPool2d(2), # 64x64 -> 32x32 nn.Conv2d(16, 32, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(32), nn.MaxPool2d(2), # 32x32 -> 16x16 nn.Conv2d(32, 64, kernel_size=3, padding=1), nn.ReLU(inplace=True), nn.BatchNorm2d(64), nn.MaxPool2d(2), # 16x16 -> 8x8 ) self.classifier = nn.Sequential( nn.AdaptiveAvgPool2d((2, 2)), nn.Flatten(), nn.Linear(64 * 2 * 2, 128), nn.ReLU(inplace=True), nn.Dropout(0.3), nn.Linear(128, n_classes) ) def forward(self, x): return self.classifier(self.features(x))

数据集部分,我用PyTorch的Dataset封装。有一点要注意:GAF矩阵是二维的,放进CNN时要补一个通道维度,变成[1, H, W]。

from torch.utils.data import Dataset, DataLoader class GAFDataset(Dataset): def __init__(self, X, y): # X: [N, n_bins, n_bins] float # y: [N] int self.X = torch.FloatTensor(X).unsqueeze(1) # [N, 1, H, W] self.y = torch.LongTensor(y) def __len__(self): return len(self.y) def __getitem__(self, idx): return self.X[idx], self.y[idx]

4.2 数据划分:用GroupShuffleSplit防止泄漏

这里就是第2.3节说的重点落地:按连续信号段划分。思路是先给每个样本打一个"组号",组号就是它来自原始文件的索引;然后用GroupShuffleSplit按组切分,保证同一个文件的样本不会同时出现在训练集和测试集里。

from sklearn.model_selection import GroupShuffleSplit def split_by_group(features, labels, groups, test_size=0.3, random_state=42): gss = GroupShuffleSplit(n_splits=1, test_size=test_size, random_state=random_state) train_idx, test_idx = next(gss.split(features, labels, groups)) return train_idx, test_idx

调用方式是这样的:

X = np.load('gaf_features.npy') # 假设已经编码好 y = np.load('gaf_labels.npy') groups = np.load('gaf_groups.npy') train_idx, test_idx = split_by_group(X, y, groups) X_train, X_test = X[train_idx], X[test_idx] y_train, y_test = y[train_idx], y[test_idx] train_dataset = GAFDataset(X_train, y_train) test_dataset = GAFDataset(X_test, y_test) train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True) test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False)

如果你偷懒不用GroupShuffleSplit,直接用train_test_split,也能跑出很不错的结果,但那个结果是不可信的。换成按组划分后,准确率可能会下降几个点,但这才是真实水平。我建议复现的时候两个方案都试一下,你就知道数据泄漏的影响有多大了。

4.3 训练流程与评估指标

模型训练用交叉熵损失和Adam优化器,学习率设1e-3。为了防止训练后期在最优解附近震荡,我加了ReduceLROnPlateau调度器:当验证集损失连续多个epoch不下降时,学习率自动降一半。

import torch.optim as optim from torch.optim.lr_scheduler import ReduceLROnPlateau from sklearn.metrics import accuracy_score, confusion_matrix def train_model(model, train_loader, val_loader, epochs=30, device='cuda'): model = model.to(device) criterion = nn.CrossEntropyLoss() optimizer = optim.Adam(model.parameters(), lr=1e-3) scheduler = ReduceLROnPlateau(optimizer, mode='min', patience=5, factor=0.5) for epoch in range(epochs): model.train() running_loss = 0.0 for inputs, labels in train_loader: inputs, labels = inputs.to(device), labels.to(device) optimizer.zero_grad() outputs = model(inputs) loss = criterion(outputs, labels) loss.backward() optimizer.step() running_loss += loss.item() * inputs.size(0) model.eval() val_loss = 0.0 all_preds = [] all_labels = [] with torch.no_grad(): for inputs, labels in val_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) loss = criterion(outputs, labels) val_loss += loss.item() * inputs.size(0) preds = torch.argmax(outputs, dim=1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) val_loss /= len(val_loader.dataset) val_acc = accuracy_score(all_labels, all_preds) scheduler.step(val_loss) if (epoch + 1) % 5 == 0: print(f"Epoch {epoch+1}/{epochs}, Loss: {running_loss:.4f}, Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}")

训练结束后,除了看准确率,我强烈建议打一下混淆矩阵,特别关注哪些故障类别容易混在一起。轴承故障诊断里,内圈故障和外圈故障因为冲击特征相似,经常出现混淆,而滚动体故障因为是随机滑动,信号特征不稳定,误判率通常会高一些。

model.eval() all_preds = [] all_labels = [] with torch.no_grad(): for inputs, labels in test_loader: inputs, labels = inputs.to(device), labels.to(device) outputs = model(inputs) preds = torch.argmax(outputs, dim=1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print("Test Accuracy:", accuracy_score(all_labels, all_preds)) print("Confusion Matrix:") print(confusion_matrix(all_labels, all_preds))

4.4 实测结果与同类方案对比

在我手头这个版本的SEU数据、按组划分的前提下,用GASF+CNN跑出来的测试准确率能稳定在96%到98%之间。作为对照,同样条件下直接用一维CNN处理原始信号,准确率大约在92%到95%之间;用原始信号+手工特征(均值、方差、峰值因子等)+随机森林,准确率则更低。GAF的增益主要来自两个方面:一是图像化让CNN的归纳偏置得到发挥,二是极坐标编码对幅值做了非线性变换,等效于一种数据增强,让模型对幅值波动不那么敏感。

我也试过GADP和GASF两种编码方式的效果差异。GASF整体准确率更高一点,但GADP在部分故障类别上的可辨识度更好。如果你追求极致效果,可以把两种编码堆叠成双通道图像,类似双通道输入,CNN前再加一个Conv1x1融合层,理论上能互补信息,但训练时间也会相应增加。

5. 复现中容易翻车的五个坑:数据泄漏、内存爆炸与模型陷阱

5.1 坑一:.mat变量名不确定

很多人在第一步loadmat就卡住了,因为SEU各渠道下载的mat文件里,变量名不是统一的"data"或"bearing_1",有的版本还带有一个描述性结构体。直接用data[?data?]的硬编码方式,一报错就劝退。解决方案就是我前面写的load_seu_signal函数,通过遍历keys、自动识别数组型变量的方式读取。这里再补一句:如果mat文件里是个struct类型,你需要再深入一层取字段,这个需要针对具体文件微调,但思路是一样的——先打印keys,再逐层剥。

5.2 坑二:GAF矩阵直接爆内存

GAF的尺寸是输入序列长度的平方,这个增长是很恐怖的。假设你切了2048点的窗口,不降维直接编码,每张图是2048×2048,约400万个像素,1000个样本就是40亿个浮点数,内存直接爆掉。我首次跑这个流程时,就是因为没有降维,程序在循环编码阶段就卡死了。

解决方式就是第3.2节的PAA降维。把2048点先压到64或128个点,GAF矩阵变成64×64或128×128,计算量和存储瞬间降了几个量级。但要注意:PAA的bin数量不是越小越好,降到16以下的话,原始信号里很多局部冲击细节会被平均掉,不同故障的纹理差异变得模糊,准确率明显下降。经验上64到128是一个比较稳的区间。

5.3 坑三:归一化方式选错

GAF的前提是arccos的输入必须在[-1, 1]内。我在实现时碰到的典型错误是:对整个数据集的全局最大值最小值做归一化,但某个样本的幅值范围跟全局差异很大,导致这个样本内部的细节被压缩到很窄的数值区间里,编码出来的图像近乎纯色块,CNN根本看不出纹理。

我的建议是:对每个样本独立做min-max归一化,让每个窗口内的相对变化都能充分展开。这是基于一个认知:对振动信号来说,同一传感器在不同时间的绝对幅值会受负载、转速波动影响,而故障模式更多体现在相对形态上。每个样本独立归一化等于把这种幅值漂移去掉,让模型更关注波形形状。当然,如果你的任务需要依赖绝对幅值信息(比如严重程度分级),归一化策略要重新考虑。

5.4 坑四:训练测试切分导致数据泄漏

这个问题在第2.3节和第4.2节反复强调过,它是整个流程里最隐蔽、也最容易被忽视的坑。轴承振动信号是强自相关的,同一个原始长信号里距离很近的窗口,它们的GAF图像肉眼几乎看不出区别。如果这些相近样本一部分进了训练集、一部分进了测试集,模型的测试准确率会高得离谱。我见过有人在某个公开数据集上报告99%的准确率,结果用按组划分一测,掉到93%,差距就是这么来的。

所有时序数据都适用这个原则:先按连续段分组,再划分。不仅测试集要按组排除,验证集也应该按组划分,否则你在调参时看到的验证集指标同样是虚高的。

5.5 坑五:忽略GAF的对称性与数据增强

GAF矩阵是对称矩阵,对角线上的信息量也不一样。我踩过的另一个问题是:直接把整张图丢给CNN,浪费了对称性这个先验。如果你觉得模型训练不稳定,可以考虑只输入上三角部分,或者在上三角输入的基础上做水平翻转数据增强,让模型对镜像变换更鲁棒。实测下来,这种增强在小样本场景下能带来1到2个百分点的提升。

另外,GAF的一大弱点是它对幅值缩放不敏感,但对时间偏移敏感。如果两个故障冲击的相位对不齐,编码出来的图就有差异,这可能让同类故障出现较大类内差异。缓解办法是切窗时采用合适的重叠率,让冲击点在不同窗口里尽量覆盖不同位置,等效于让模型学习到平移不变性。

结语:小技巧与扩展方向

最后分享两个我自己反复用的小技巧。第一个,GAF代码写完之后,先用一个类别里的两段信号做可视化,确认图像有没有明显的纹理差异。如果所有图看起来都差不多,先别急着头疼调模型,回去检查PAA窗口数和归一化逻辑,这两处是纹理失真的主要原因。第二个,编码后的GAF图像不需要存成PNG再读,那样既慢又损失精度,直接存npy数组,训练时通过Dataset读取,速度和精度都更好。

这套流程做顺之后,扩展方向其实很多。GAF不局限于轴承,任何结构化的一维时序信号都可以用,比如齿轮箱振动信号、电机电流信号、风力发电机组的振动监测、甚至语音信号里的事件检测。我现在做行星齿轮箱相关项目时,也沿用了同一套GAF编码代码,只是换了个数据集,就能很快迁移过去。深度学习模型的气质就是这样:一个好的特征表达,能省掉你大量调参时间,而格拉姆角场在"一维转二维"这条路上,是我测试过最稳定的方案之一。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 11:06:14

CATICS 3DCAD试题拆解:参数化建模与体积约束的避坑指南

简介&#xff1a;catics三DCAD竞赛试题.doc 是一份面向CAD竞赛参赛者与三维建模学习者的赛题整理文档&#xff0c;汇集多届CATICS 3D CAD竞赛的完整题目&#xff0c;覆盖草图绘制、零件建模、体积面积求解等典型任务。文档以试题描述、参数表和标准答案为主&#xff0c;详细展示…

作者头像 李华
网站建设 2026/10/3 11:05:49

Cocos陈昊芝专访解读:商业引擎的外延不止游戏和元宇宙

1. 从"游戏引擎"到"商业引擎"&#xff1a;陈昊芝这次专访到底在聊什么第一次看到"Cocos陈昊芝&#xff1a;商业引擎的外延不止游戏和元宇宙"这个标题&#xff0c;我脑子里冒出来的第一个念头是&#xff1a;终于有人把这件事摆到台面上说了。过去…

作者头像 李华
网站建设 2026/10/3 11:05:49

Claude Code 子 Agent 重试陷阱:从零重做的高昂代价与规避方案

Claude Code 的 Task 模式&#xff08;子 Agent&#xff09;用得多了&#xff0c;你早晚会遇到 Rate Limit。我这次跑一个跨仓库的框架迁移&#xff0c;顺手翻了下运行日志&#xff0c;发现被限额杀掉的子 Agent 有 449 个&#xff0c;其中 438 个被系统从头重做了一遍。说实话…

作者头像 李华
网站建设 2026/10/3 11:05:49

8GB内存老电脑也能跑大模型:Ollama量化部署实战

朋友把一台吃灰好几年的笔记本搬到我面前&#xff0c;8GB 内存&#xff0c;没有独立显卡&#xff0c;CPU 是四五年前的中端型号。他问的第一句话就是&#xff1a;“这种老机器&#xff0c;能跑现在到处吹的大模型吗&#xff1f;”我给他装了一个软件&#xff0c;在终端敲了一条…

作者头像 李华
网站建设 2026/10/3 11:05:44

SpringBoot+Vue+SpringCloud微服务架构的企业人事管理系统实战

1. 项目概述与核心设计思路做企业管理软件这些年&#xff0c;我一直觉得人力资源管理系统是最能体现"麻雀虽小五脏俱全"的业务场景。从员工花名册、入转调离&#xff0c;到考勤排班、薪酬核算&#xff0c;再到招聘流程、培训记录&#xff0c;每个模块单独拎出来都像一…

作者头像 李华
网站建设 2026/10/3 11:05:24

微信小程序蓝牙打印中文乱码根治:iconv-lite与GBK编码实践

做微信小程序蓝牙打印功能时&#xff0c;中文编码处理是绕不开的一道坎。英文和数字都能正常打出来&#xff0c;一到中文就变成锟斤拷、问号或者方块&#xff0c;问题基本都出在编码链路上。我折腾过不少方案&#xff0c;最后选定了 iconv-lite 这个库统一做 GBK 转码&#xff…

作者头像 李华