
简介在工业预测性维护和故障诊断领域振动信号分析是核心技术之一。其原理是通过传感器采集设备运行时的振动数据利用信号处理与机器学习方法识别故障特征。这项技术的核心价值在于能实现设备状态的实时监测与早期预警大幅降低非计划停机风险广泛应用于风电、轨道交通、智能制造等关键行业。数据预处理是其中至关重要的一环直接决定了后续模型性能。本文聚焦于该领域的经典基准——凯斯西储大学轴承数据集针对其原始.mat文件格式复杂、标签信息隐晦、样本未划分等痛点提供了一套完整的Python自动化处理方案。该方案通过数据加载解析、元数据映射、样本切割与标准化等模块将原始长序列振动信号转化为可直接用于CNN、LSTM等模型训练的结构化样本并详细探讨了样本长度、重叠率等关键参数的选择逻辑以及如何避免数据泄露、处理类别不平衡等工程实践问题为研究者与工程师提供了一个高效、可靠的‘开箱即用’工具包。1. 项目概述与核心价值如果你正在研究机械设备的故障诊断、预测性维护或者想入门机器学习在工业领域的应用那么“凯斯西储大学轴承数据集”这个名字你一定不陌生。它几乎是这个领域的“MNIST”一个被无数论文、教程和开源项目反复引用的经典基准数据集。我最初接触它是为了验证一个轴承振动信号的特征提取算法结果发现网上能找到的资料要么是零散的要么就是只给个原始数据链接下载下来一堆看不懂的.mat文件还得自己写脚本去解析、归一化、划分数据集光是数据预处理就能劝退一大半初学者。这个项目就是把我踩过的坑、写过的代码和整理好的数据打包在一起。它不仅仅是一个数据集的搬运更是一个“开箱即用”的工具包。核心包含三部分整理好的数据文件、一套完整的Python数据处理程序、以及一份详细的使用说明。目标是让你在5分钟内就能把数据加载到Python环境里直接开始你的特征工程或模型训练而不是花5个小时去研究数据格式和写预处理脚本。对于学生和研究者它能帮你快速复现论文结果验证算法性能对于工程师它提供了一个标准的、干净的工业数据样本可以用来搭建故障诊断的原型系统。无论你是用传统的信号处理方法比如FFT、小波变换还是用深度学习模型CNN、LSTM这个整理好的数据集都能作为你可靠的起点。2. 数据集深度解析从原始文件到可用样本2.1 数据来源与实验背景凯斯西储大学CWRU轴承数据中心的这个数据集来源于一个精心设计的轴承故障模拟实验台。实验台的核心是一个2马力的电机通过扭矩传感器/编码器连接到一个测功机。轴承被安装在电机的驱动端。研究人员通过电火花加工技术在轴承的内圈、外圈和滚动体上分别制造了单点故障故障直径包括0.007英寸、0.014英寸和0.021英寸三种尺寸模拟了从早期轻微损伤到严重故障的不同阶段。数据采集时振动加速度传感器被分别放置在电机驱动端和风扇端的轴承座上。采集系统以12 kHz或48 kHz的采样频率记录了轴承在四种负载条件0、1、2、3马力下正常状态和各种故障状态下的振动信号。最终原始数据以MATLAB的.mat文件格式发布每个文件对应一个特定的实验工况。注意原始数据文件通常以复杂的命名规则存储如97.mat、105.mat等这些数字编码了故障位置、尺寸、负载和采样频率等信息但对外部用户极不友好需要对照文档手动解码这是使用原始数据的第一道门槛。2.2 数据整理的挑战与核心工作直接使用原始.mat文件存在几个显著问题格式不统一文件内部数据结构不一致有些数据在X键下有些在X097_DE_time这样的键下加载代码需要写很多条件判断。信息隐晦故障标签、负载信息等元数据没有与振动信号直接关联需要用户自行根据文件名或额外文档进行映射容易出错。未划分样本每个.mat文件通常包含一个长时间序列比如10秒12kHz采样率就是120,000个点。对于机器学习任务我们需要将其切割成多个固定长度如1024点的短样本并给每个样本打上正确的标签。未做归一化不同工况下的信号幅值差异很大直接输入模型会影响训练稳定性和性能。本项目的核心工作就是通过Python程序自动化地解决了以上所有问题。程序的主要流程包括自动解析与加载遍历所有原始.mat文件统一数据加载接口提取出振动信号时间序列。元数据关联根据文件命名规则或内置映射表自动为每个文件对应的数据生成明确的标签如Normal、Ball_007、IR_014、OR_021分别代表正常、滚动体0.007英寸故障、内圈0.014英寸故障、外圈0.021英寸故障以及负载信息。样本切割与打标将长序列按指定的样本长度和重叠率进行滑动窗口切割生成成千上万个短样本每个样本都携带准确的故障类型和负载标签。数据归一化对切割后的样本进行标准化处理如Z-Score标准化消除量纲影响。结构化存储将处理好的样本数据、标签和元数据以标准格式如NumPy数组或HDF5保存方便后续一键加载。2.3 整理后数据结构一览经过处理你得到的是一个清晰、立即可用的数据包。通常它会被组织成以下结构cwru_processed_data/ ├── README.md # 数据说明文档 ├── metadata.csv # 样本元数据表索引、故障类型、故障尺寸、负载、原始文件 ├── X_train.npy # 训练集样本形状[n_samples, sample_length] ├── y_train.npy # 训练集标签整数编码或one-hot ├── X_test.npy # 测试集样本 ├── y_test.npy # 测试集标签 └── label_encoder.pkl # 标签编码器用于反向映射metadata.csv是这个数据包的精髓它可能包含如下内容sample_idfault_locationfault_sizeload_hporiginal_filesplit0Normal0097.mattrain1Ball0.0070105.mattrain2InnerRace0.0141118.mattest3OuterRace0.0213130.mattest这样的结构使得数据探索和模型训练变得异常简单。你可以轻松地筛选出“所有在1马力负载下的内圈故障数据”进行针对性分析。3. Python程序核心模块详解配套的Python程序是数据整理工作的引擎它由几个功能明确的模块组成。理解这些模块你不仅能使用这个工具包还能根据自己项目的需求进行定制。3.1 数据加载与解析模块 (data_loader.py)这个模块负责与原始的.mat文件打交道。核心函数是load_cwru_mat_file(filepath)。它的内部逻辑需要处理CWRU数据集的“历史遗留问题”。import scipy.io as sio import numpy as np def load_cwru_mat_file(filepath): 加载单个CWRU的.mat文件并提取驱动端(DE)振动信号。 参数: filepath: .mat文件的路径。 返回: data: 一维振动信号数组。 fs: 采样频率。 try: mat_data sio.loadmat(filepath) # 情况1: 数据在X键下如97.mat, 98.mat... if X in mat_data and mat_data[X].size 0: # 数据形状可能是(1, N)或(N, 1)需要展平为一维 data mat_data[X].flatten() # 情况2: 数据在X097_DE_time这类键下如105.mat, 118.mat... else: # 遍历找到包含DE_time的键 for key in mat_data.keys(): if DE_time in key and not key.startswith(__): data mat_data[key].flatten() break else: # 如果都没找到抛出异常 raise KeyError(f在文件 {filepath} 中未找到振动数据键。) # 提取采样频率它可能存储在fs或sr键中 fs mat_data.get(fs, mat_data.get(sr, 12000))[0,0] # 默认12kHz return data, int(fs) except Exception as e: print(f加载文件 {filepath} 时出错: {e}) return None, None实操心得原始数据中驱动端(DE)信号最常用于故障诊断。风扇端(FE)信号有时也会提供但本工具包默认聚焦于DE信号因为它对大多数类型的故障更敏感。在scipy.io.loadmat加载时可能会遇到警告关于不标准的MAT文件格式通常可以忽略但务必检查返回的数据维度是否正确。3.2 元数据映射与标签生成模块 (label_mapper.py)这是将晦涩的文件名转换为清晰标签的核心。我采用了一个基于字典的映射方法比写一堆if-else更清晰。# 文件名到故障信息的映射字典 # 键: 文件名不含扩展名 值: (故障位置, 故障尺寸(英寸), 负载(马力)) FAULT_MAP { 97: (Normal, 0.0, 0), 98: (Normal, 0.0, 1), 99: (Normal, 0.0, 2), 100: (Normal, 0.0, 3), 105: (Ball, 0.007, 0), 106: (Ball, 0.007, 1), # ... 此处省略大量映射条目 130: (OuterRace, 0.021, 3), # 外圈故障0.021英寸3马力负载 } def get_fault_info_from_filename(filename): 从文件名提取故障信息。 参数: filename: 如105.mat或105 返回: fault_location: 故障位置如Ball, InnerRace, OuterRace, Normal fault_size: 故障尺寸英寸正常时为0 load: 负载马力 # 去除路径和扩展名获取纯数字部分 base_name filename.split(/)[-1].replace(.mat, ) return FAULT_MAP.get(base_name, (None, None, None))这个映射关系是我根据CWRU官网的文档和大量社区资料核对后整理的。使用这个模块程序在读取105.mat文件时能自动知道它对应的是“0马力负载下滚动体有0.007英寸损伤”的数据。3.3 样本切割与数据集构建模块 (dataset_builder.py)这是数据预处理中最关键的一步将连续信号转化为机器学习模型可消化的离散样本。def create_samples_from_signal(signal, label, sample_length1024, overlap_ratio0.5): 将一维振动信号切割成多个重叠的样本。 参数: signal: 一维振动信号数组。 label: 该信号对应的标签可以是整数或元组。 sample_length: 每个样本的长度点数如1024。 overlap_ratio: 重叠率0表示不重叠0.5表示50%重叠。 返回: samples: 样本数组形状为[n_samples, sample_length]。 labels: 标签数组长度为n_samples。 if len(signal) sample_length: print(f信号长度({len(signal)})小于样本长度({sample_length})无法切割。) return np.array([]), np.array([]) step int(sample_length * (1 - overlap_ratio)) n_samples (len(signal) - sample_length) // step 1 samples np.zeros((n_samples, sample_length)) labels np.full(n_samples, label) # 假设label已编码为整数 for i in range(n_samples): start i * step end start sample_length samples[i] signal[start:end] return samples, labels参数选择背后的逻辑样本长度 (sample_length)通常选择1024、2048或4096点。1024点在12kHz下约85毫秒是一个常用起点它能捕获轴承故障特征频率的几个周期同时保持样本尺寸较小适合快速训练和测试。对于更复杂的故障或想捕获更多信息可以增加长度。重叠率 (overlap_ratio)设置为0.5即50%重叠是常见做法。这能在不显著增加样本间相关性的前提下最大化利用有限的数据特别是对于故障数据本身就不多的情况。如果计算资源充足且希望样本完全独立可以设为0。3.4 数据标准化与存储模块 (normalize_and_save.py)切割后的样本需要标准化以确保模型训练的稳定性。我们通常使用Z-Score标准化即对每个特征维度这里是每个时间点位置不是对整个样本的所有点进行减去均值、除以标准差的操作。但注意对于振动信号我们通常对每个样本独立进行标准化而不是在整个数据集上计算全局的均值和标准差。这是因为不同工况下的信号绝对幅值差异很大全局标准化会扭曲单个样本内的特征。def normalize_samples(samples, methodzscore_per_sample): 标准化样本。 参数: samples: 样本数组形状[n_samples, sample_length] method: 标准化方法zscore_per_sample或minmax 返回: normalized_samples: 标准化后的样本。 normalized_samples np.zeros_like(samples, dtypenp.float32) if method zscore_per_sample: for i in range(samples.shape[0]): sample samples[i] eps 1e-8 # 防止除零 normalized_samples[i] (sample - np.mean(sample)) / (np.std(sample) eps) elif method minmax: for i in range(samples.shape[0]): sample samples[i] min_val, max_val np.min(sample), np.max(sample) if max_val - min_val eps: normalized_samples[i] (sample - min_val) / (max_val - min_val) else: normalized_samples[i] sample * 0 return normalized_samples处理完成后使用np.save或h5py库将数据保存为.npy或.h5文件。h5py格式对于超大型数据集更友好支持分块读取。4. 完整使用流程从零到训练数据集假设你已经下载了本项目包结构如下your_project/ ├── cwru_data_processor/ # 我们的Python工具包 │ ├── data_loader.py │ ├── label_mapper.py │ ├── dataset_builder.py │ ├── normalize_and_save.py │ └── config.py # 配置文件存放样本长度、重叠率等参数 ├── raw_data/ # 你需要自己从CWRU官网下载的原始.mat文件 │ ├── 97.mat │ ├── 98.mat │ ├── 105.mat │ └── ... └── main.py # 主运行脚本4.1 环境配置与依赖安装首先确保你的Python环境建议3.8以上已安装必要的科学计算库。# 使用pip安装核心依赖 pip install numpy scipy scikit-learn h5py matplotlibscipy用于读取.mat文件scikit-learn用于数据划分和标签编码h5py是可选的用于保存HDF5格式matplotlib用于可视化查看信号。4.2 运行主脚本一键生成数据集在main.py中我们将上述模块串联起来import os import numpy as np from sklearn.model_selection import train_test_split from sklearn.preprocessing import LabelEncoder import pickle # 导入自定义模块 from cwru_data_processor.data_loader import load_cwru_mat_file from cwru_data_processor.label_mapper import get_fault_info_from_filename from cwru_data_processor.dataset_builder import create_samples_from_signal from cwru_data_processor.normalize_and_save import normalize_samples def main(): raw_data_dir ./raw_data output_dir ./processed_data os.makedirs(output_dir, exist_okTrue) sample_len 1024 overlap 0.5 all_samples [] all_labels [] metadata_records [] # 1. 遍历原始数据文件 for filename in os.listdir(raw_data_dir): if filename.endswith(.mat): filepath os.path.join(raw_data_dir, filename) print(f正在处理: {filename}) # 2. 加载信号 signal, fs load_cwru_mat_file(filepath) if signal is None: continue # 3. 获取标签信息 fault_loc, fault_size, load get_fault_info_from_filename(filename) if fault_loc is None: print(f 警告: 无法识别文件 {filename} 的标签已跳过。) continue # 4. 创建复合标签例如Ball_007_0hp # 也可以分开存储这里示例用复合标签 composite_label f{fault_loc}_{fault_size:03d}_{load}hp # 5. 切割样本 samples, labels create_samples_from_signal( signal, composite_label, sample_lengthsample_len, overlap_ratiooverlap ) if len(samples) 0: all_samples.append(samples) # 为每个样本记录元数据 for i in range(len(samples)): metadata_records.append({ sample_id: len(all_labels) i, filename: filename, fault_location: fault_loc, fault_size_inch: fault_size, load_hp: load, composite_label: composite_label }) all_labels.extend(labels) # 6. 合并所有样本和标签 X np.vstack(all_samples) if all_samples else np.array([]) y np.array(all_labels) # 7. 将字符串标签编码为整数 label_encoder LabelEncoder() y_encoded label_encoder.fit_transform(y) # 形状: (n_samples,) # 8. 标准化 X_normalized normalize_samples(X, methodzscore_per_sample) # 9. 划分训练集和测试集按文件或随机划分 # 这里按80/20随机划分更严谨的做法是按文件划分以避免数据泄露 X_train, X_test, y_train, y_test, idx_train, idx_test train_test_split( X_normalized, y_encoded, range(len(y_encoded)), test_size0.2, random_state42, stratifyy_encoded ) # 10. 保存数据 np.save(os.path.join(output_dir, X_train.npy), X_train) np.save(os.path.join(output_dir, y_train.npy), y_train) np.save(os.path.join(output_dir, X_test.npy), X_test) np.save(os.path.join(output_dir, y_test.npy), y_test) # 保存元数据并根据划分标记 import pandas as pd df_meta pd.DataFrame(metadata_records) df_meta[split] train df_meta.loc[idx_test, split] test df_meta.to_csv(os.path.join(output_dir, metadata.csv), indexFalse) # 保存标签编码器 with open(os.path.join(output_dir, label_encoder.pkl), wb) as f: pickle.dump(label_encoder, f) print(数据处理完成) print(f训练集样本数: {X_train.shape[0]}) print(f测试集样本数: {X_test.shape[0]}) print(f样本维度: {X_train.shape[1]}) print(f故障类别数: {len(label_encoder.classes_)}) if __name__ __main__: main()运行python main.py程序会自动读取raw_data文件夹下的所有.mat文件进行解析、切割、标注、标准化和划分最终在processed_data文件夹下生成一系列.npy文件和一个metadata.csv。4.3 加载并使用整理好的数据集进行模型训练数据生成后在另一个模型训练脚本中加载变得极其简单import numpy as np import pandas as pd import pickle from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import classification_report # 加载数据 X_train np.load(./processed_data/X_train.npy) y_train np.load(./processed_data/y_train.npy) X_test np.load(./processed_data/X_test.npy) y_test np.load(./processed_data/y_test.npy) # 加载标签编码器用于理解预测结果 with open(./processed_data/label_encoder.pkl, rb) as f: le pickle.load(f) print(f类别: {le.classes_}) # 使用一个简单的随机森林进行快速测试 clf RandomForestClassifier(n_estimators100, random_state42, n_jobs-1) clf.fit(X_train, y_train) y_pred clf.predict(X_test) print(classification_report(y_test, y_pred, target_namesle.classes_))至此你已经绕过了所有数据预处理的坑直接进入了模型构建和评估的核心环节。5. 常见问题、避坑指南与高级技巧5.1 数据下载与文件缺失问题问题CWRU官网的原始数据文件众多且分散在不同链接。新手往往不知道需要下载哪些文件。解决本工具包通常会提供一个raw_data文件夹的预期文件列表如required_files.txt。对于最常见的10类分类任务正常、3种故障位置×3种尺寸你至少需要下载包含以下基础编号的文件97-100正常105, 106, 107, 108滚动体0.007英寸118, 119, 120, 121内圈0.014英寸130, 131, 132, 133外圈0.021英寸等。务必从CWRU轴承数据中心官网下载确保数据完整。5.2 样本切割导致的“数据泄露”问题在随机划分训练集和测试集时如果来自同一个长信号文件的样本被同时分到了训练集和测试集由于滑动窗口的重叠这些样本高度相似会导致模型在测试集上得到虚高的、不真实的性能评估。解决更严谨的划分策略是按文件划分。例如将所有来自105.mat文件的样本全部归入训练集所有来自106.mat文件的样本全部归入测试集。这样可以确保训练集和测试集的数据来自完全独立的实验运行过程评估结果更可靠。你可以在main.py的划分步骤前先根据metadata.csv中的filename字段进行分组。5.3 类别不平衡处理问题不同故障类型和负载下的数据量可能不完全相同。例如正常状态的数据可能多于严重故障的数据。解决首先在metadata.csv中检查各类别的样本数量。如果存在不平衡可以在模型训练时采取以下策略使用class_weight参数大多数机器学习模型如RandomForestClassifier,SVC和深度学习框架都支持设置类别权重让模型更关注少数类。from sklearn.utils import compute_class_weight classes np.unique(y_train) weights compute_class_weight(balanced, classesclasses, yy_train) class_weight_dict dict(zip(classes, weights)) clf RandomForestClassifier(class_weightclass_weight_dict)过采样或欠采样使用imbalanced-learn库中的SMOTE等方法对少数类进行过采样或对多数类进行欠采样。数据增强对振动信号进行轻微的时间拉伸、添加高斯噪声等人工增加少数类样本的多样性需谨慎避免改变故障特征。5.4 从时域信号到特征工程直接使用长达1024点的原始时域信号作为输入对于像随机森林这样的模型来说维度可能过高且包含大量冗余信息。通常我们需要进行特征工程。常用特征提取方法时域特征均值、均方根、峰值、峭度、偏度、波形因子、脉冲因子等。这些特征计算简单对某些故障敏感。频域特征对信号进行快速傅里叶变换提取频谱的均值、重心频率、均方频率等。这对于识别与故障特征频率相关的周期性冲击非常有效。时频域特征使用小波变换提取不同尺度下的能量特征能同时捕捉故障在时间和频率上的信息。你可以修改dataset_builder.py在create_samples_from_signal函数切割样本后立即对每个样本计算一组特征最终生成的是一个特征矩阵[n_samples, n_features]而不是原始信号矩阵[n_samples, sample_length]。这能大幅降低数据维度提升模型训练速度有时还能提高精度。5.5 用于深度学习CNN/1D-CNN的输入准备如果你计划使用卷积神经网络原始信号或简单的归一化信号可以直接作为输入。但通常需要调整一下维度以适应CNN的输入要求。# 对于1D-CNN输入形状通常是 [样本数, 序列长度, 通道数] # 我们的X_train形状是 [n_samples, 1024]需要增加一个通道维度 X_train_cnn X_train[:, :, np.newaxis] # 新形状: [n_samples, 1024, 1] X_test_cnn X_test[:, :, np.newaxis] # 对于简单的1D-CNN模型 from tensorflow import keras from tensorflow.keras import layers model keras.Sequential([ layers.Input(shape(1024, 1)), layers.Conv1D(filters64, kernel_size3, activationrelu), layers.MaxPooling1D(pool_size2), layers.Conv1D(filters128, kernel_size3, activationrelu), layers.GlobalAveragePooling1D(), layers.Dense(len(le.classes_), activationsoftmax) ]) model.compile(optimizeradam, losssparse_categorical_crossentropy, metrics[accuracy]) model.fit(X_train_cnn, y_train, validation_data(X_test_cnn, y_test), epochs20, batch_size32)5.6 扩展引入风扇端信号与多传感器融合本工具包默认处理驱动端信号。但原始数据中也包含风扇端信号。一个更高级的用法是进行多传感器数据融合。你可以同时加载驱动端和风扇端信号并将它们作为两个通道输入到2D-CNN需要将两个信号堆叠或设计一个双分支网络分别提取特征后再融合。这有可能捕捉到更全面的故障信息提升诊断鲁棒性。这需要对data_loader.py进行扩展使其能同时返回DE和FE信号。处理完CWRU数据集你会对时间序列数据的预处理、故障诊断的流程有一个非常扎实的理解。这套代码和思路稍加修改就可以应用到其他类似的旋转机械振动数据集上。数据预处理是枯燥的但一份干净、可靠的数据是任何成功分析或模型的基础。希望这个整合好的数据包和代码能为你节省大量时间让你更专注于算法和模型本身。如果在使用中遇到任何问题或者有了新的改进想法随时可以在此基础上进行迭代。本文还有配套的精品资源点击获取