ARTICLE DETAIL

资讯详情

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

TCN时间卷积网络实现航空发动机剩余寿命预测:从因果卷积到PyTorch实战

TCN时间卷积网络实现航空发动机剩余寿命预测:从因果卷积到PyTorch实战 简介面向航空发动机健康管理PHM研究人员与Python开发者这份实战资源演示了如何利用时间卷积网络TCN预测发动机剩余使用寿命RUL。资源包共5个文件核心Python脚本实现从数据清洗、标准化、特征提取到TCN建模、训练与评估的完整流程配合训练集、测试集文本和Excel数据表格压缩包仅5.58MB方便下载复现。已有581人学习下载是入门时序预测与故障预测的不错参考。代码采用多层因果卷积与残差连接捕获序列长期依赖相比LSTM具备更快的并行训练速度同时提供可视化模块观察损失变化与预测效果支持模型保存与加载通过调整卷积层数、滤波器尺寸等超参数可进一步优化精度。该项目不仅适用于航空发动机稍作修改即可迁移至电力系统、交通流量等时序预测任务实用价值与学习价值兼备。1. 为什么用TCN预测航空发动机剩余寿命航空发动机的传感器信号是典型的多变量时间序列CMAPSS数据集里每台发动机从健康状态退化到失效记录了几十个通道的周期采样值。传统做法用LSTM、GRU这类循环网络建模时间依赖但训练慢、梯度传播路径长而且难以真正利用多核GPU的并行能力。TCNTemporal Convolutional Network用因果卷积和膨胀卷积组合出足够大的感受野既保留了序列建模能力又能像CNN一样并行训练。实际跑FD001时在相近精度下TCN的训练耗时常能比LSTM缩短一半以上。剩余寿命预测本质上是一个回归问题给出一段历史观测窗口预测从当前时刻到故障时刻的剩余循环数。TCN对这类输入的天然优势在于它不需要像LSTM那样按时间步逐个迭代而是一次性卷积整个窗口并且通过膨胀系数指数增长来覆盖长时间依赖。适合有一定深度学习基础、想在工业预测性维护里快速上手的工程师。下面就从模型结构拆起逐步给出可运行的Python代码。2. TCN模型结构与航空发动机剩余寿命预测的匹配点2.1 因果卷积与膨胀卷积如何覆盖时间窗口TCN的核心约束是因果性输出t时刻的值时只能使用t时刻及之前的信息不能“偷看”未来。普通卷积如果padding不当就会让输出位置混入未来数据所以TCN在卷积前对左侧进行显式padding使卷积核只看到过去的点。膨胀卷积在卷积核元素之间插入空洞用很小的卷积核覆盖很宽的输入范围。当膨胀系数d1时就是普通卷积d2时卷积核覆盖间隔为1的点d4时覆盖间隔为3的点。膨胀系数从1开始每层乘以2感受野随层数指数增长。感受野大小计算方式为[ \text{receptive field} 1 \sum_{i1}^{L} (\text{kernel_size} - 1) \times \text{dilation}_i ]其中L是卷积层数dilation_i是第i层的膨胀系数。如果kernel_size3膨胀系数依次为1、2、4则两层后感受野为12×(12)7三层后为12×(124)15。这个公式在设定网络深度前用来估算需要多少层才能覆盖滑窗长度。2.2 残差块与感受野计算TCN的每个残差块由两组膨胀卷积、权重归一化、ReLU和Dropout组成输入和输出通过1×1卷积对齐通道数。残差连接让梯度可以绕过卷积层直接回传解决深层网络退化问题。在RUL预测里传感器退化趋势是缓慢变化的残差块能让模型学习到“当前时刻数据 变化量”而不是从头学原始信号。2.2.1 一个残差块的前向流程输入经过第一个膨胀因果卷积再经过ReLU和Dropout然后经过第二个膨胀卷积再次ReLU和Dropout。最后与输入相加若两者通道数不同输入侧加一个1×1卷积做投影。每一步都保持时间长度不变因为因果padding已经补齐了卷积造成的长度缩减。2.3 TCN参数与序列长度的关系滑窗长度决定了每个样本包含多少历史循环数。比如窗口设为60那么模型看到过去60个周期预测的就是第61个周期距离故障还有多久。感受野必须大于等于窗口长度否则最早的信息无法到达最后一层的输出位置。一般做法是先算好感受野再反过来确定输入长度。def compute_receptive_field(kernel_size: int, dilations: list) - int: rf 1 for d in dilations: rf (kernel_size - 1) * d return rf # kernel_size3, 膨胀系数 [1, 2, 4, 8] 时 rf compute_receptive_field(3, [1, 2, 4, 8]) print(f感受野大小: {rf}) # 输出: 31这段代码用来在建模前验证配置是否覆盖滑窗长度。如果窗口设为60而感受野只有31就必须增加膨胀层数或调大kernel_size。建议把滑窗长度设为感受野的0.8~1倍留一点冗余同时避免浪费计算量。3. 数据预处理从CMAPSS原始信号到滑动窗口样本3.1 CMAPSS数据集字段与RUL标签生成CMAPSS包含四个子集FD001、FD002、FD003、FD004区别在于工况和故障模式数量。每个子集包含训练集和测试集测试集每个序列的末尾就是失效前最后一次观测需要预测的是从该时刻到实际故障的剩余循环数。原始CSV共有26列第一列是发动机编号第二列是循环序号第三到第五列是操作设定值第六到第二十六列是21个传感器测量值。训练集可以直接用“总循环数 - 当前循环数”作为RUL标签。但直接线性标签会高估早期健康状态的影响实践中常用分段线性标签超过某个阈值比如125的RUL全部置为125让模型更关注接近失效的阶段。下表是FD001的常见设置参数项常见取值说明最大RUL125超过125的样本标签截断滑窗长度60 ~ 100覆盖一个典型退化周期传感器通道14个剔除常值传感器标准化方式z-score按训练集均值和标准差3.2 传感器选择与标准化CMAPSS中有些传感器如T2、T3等数值始终保持不变对预测没有贡献。我一般先删除方差接近0的列再保留剩余通道。标准化必须在训练集上计算均值和标准差然后应用到测试集避免信息泄漏。import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler # 读取FD001训练集和测试集注意CSV以空格分隔 train_df pd.read_csv(train_FD001.txt, sep , headerNone) test_df pd.read_csv(test_FD001.txt, sep , headerNone) # 去掉末尾NaN列并指定列名 train_df train_df.dropna(axis1) test_df test_df.dropna(axis1) train_df.columns [id, cycle] [fop{i} for i in range(1,4)] [fs{i} for i in range(1,22)] test_df.columns train_df.columns # 选择传感器列s1到s21 sensor_cols [fs{i} for i in range(1,22)] # 删除方差为零的传感器保留有效列 valid_sensors [] for col in sensor_cols: if train_df[col].std() 1e-5: valid_sensors.append(col) # 标准化只fit训练集 scaler StandardScaler() train_scaled scaler.fit_transform(train_df[valid_sensors]) test_scaled scaler.transform(test_df[valid_sensors])代码先按空格解析文本文件去掉整列NaN的空列然后筛选方差大于阈值的传感器。之后用StandardScaler对训练集学习均值和标准差再对测试集做同样的变换。要注意测试集不能单独fit否则训练时模型从未见过这种分布会导致预测偏差。3.3 滑动窗口构建训练和测试样本标准化后的数据是逐循环的需要滑窗截成固定长度的样本。窗口内形状为(batch, seq_len, num_features)每个样本的标签为该窗口最后一个循环的RUL。步长通常设为1保证每个时间点都产生一个样本。def make_samples(data, labels, seq_len): X, y [], [] for i in range(len(data) - seq_len 1): X.append(data[i:i seq_len]) y.append(labels[i seq_len - 1]) return np.array(X), np.array(y) # 计算训练集RUL标签分段线性阈值125 rul_train train_df.groupby(id)[cycle].max() - train_df[cycle] rul_train rul_train.clip(upper125).values # 按发动机分组分别做滑窗这里要注意按发动机ID分别滑窗不能跨发动机拼接。否则窗口可能会把前一台发动机的尾部和后一台发动机的头部拼在一起制造出永远不存在的退化轨迹。分组滑窗后每一组内部生成样本再合并成一个大数组。4. 用PyTorch实现TCN模型并训练4.1 TCN模型类定义PyTorch中实现TCN核心就是残差块和网络骨架。残差块内部包含两个Conv1dpadding方式设置为左侧paddingdilation参数从外部传入。为了支持不同的输入输出通道需要判断通道数是否一致不一致时用1x1卷积做投影。import torch import torch.nn as nn import torch.nn.functional as F class TCNBlock(nn.Module): def __init__(self, in_channels, out_channels, kernel_size, dilation, dropout0.2): super().__init__() self.padding (kernel_size - 1) * dilation self.conv1 nn.Conv1d(in_channels, out_channels, kernel_size, dilationdilation, paddingself.padding) self.conv2 nn.Conv1d(out_channels, out_channels, kernel_size, dilationdilation, paddingself.padding) self.relu nn.ReLU() self.dropout nn.Dropout(dropout) self.downsample None if in_channels ! out_channels: self.downsample nn.Conv1d(in_channels, out_channels, 1) def forward(self, x): # 输入x形状: (batch, features, seq_len) residual x out self.conv1(x) out out[:, :, :x.size(2)] # 截断右侧多出的padding out self.relu(out) out self.dropout(out) out self.conv2(out) out out[:, :, :x.size(2)] out self.relu(out) if self.downsample is not None: residual self.downsample(residual) return self.dropout(out residual)左侧padding通过把padding全部加在左侧实现但PyTorch的Conv1d默认padding是左右均匀的。因此这里先设置padding为(kernel_size-1)*dilation卷积后把右侧多出来的部分截掉剩下就是左侧因果对齐。downsample分支让输入输出通道不一致时也能相加。4.2 完整TCN网络与训练循环TCN网络把多个残差块串联起来每层的膨胀系数按指数增长。最后一层输出接入一个线性层把特征映射成1维RUL预测值。输入输出通道按实际传感器数量设置例如上面筛选后是14个传感器。class TCNRegression(nn.Module): def __init__(self, input_size, num_channels, kernel_size3, dropout0.2): super().__init__() layers [] n_levels len(num_channels) prev input_size for i, ch in enumerate(num_channels): dilation 2 ** i layers.append(TCNBlock(prev, ch, kernel_size, dilation, dropout)) prev ch self.tcn nn.Sequential(*layers) self.linear nn.Linear(prev, 1) def forward(self, x): # x形状: (batch, seq_len, features) x x.transpose(1, 2) # 转成 (batch, features, seq_len) out self.tcn(x) out out.mean(dim2) # 全局平均池化或取最后时间步均可 return self.linear(out).squeeze(-1)训练时用MSE损失优化器用Adam学习率可以设0.001批量大小64。早期训练要看验证损失是否震荡如果震荡就把batch调大或降低学习率。下面是一段精简的训练循环。def train_model(model, train_loader, val_loader, epochs30): optimizer torch.optim.Adam(model.parameters(), lr0.001) criterion nn.MSELoss() for epoch in range(epochs): model.train() total_loss 0 for x, y in train_loader: optimizer.zero_grad() pred model(x) loss criterion(pred, y) loss.backward() optimizer.step() total_loss loss.item() if (epoch 1) % 5 0: val_loss evaluate(model, val_loader, criterion) print(fEpoch {epoch1} train_loss{total_loss/len(train_loader):.4f}, val_loss{val_loss:.4f})注意evaluate函数要写一个不计算梯度的验证循环和训练循环分开。当验证损失开始逆势上升时保存当前模型参数避免过拟合。4.3 环境配置与常见运行错误跑这套代码前要保证Python版本3.8以上PyTorch 1.10以上scikit-learn和pandas可用。如果GPU内存不足把批大小调到32或者减少num_channels里的通道数。一个常见错误是输入通道数不匹配滑窗特征数必须和模型input_size一致。pip install pandas numpy scikit-learn torch另一个高频问题是数据维度错误。滑窗产生的numpy数组形状是(samples, seq_len, features)而TCN的卷积层期望(batch, features, seq_len)所以代码里一定要有transpose操作否则会报RuntimeError。遇到这类错误先在forward里打印x.shape定位到具体维度不一致的那一层。5. 模型验证与调优从RMSE到寿命曲线的实践技巧5.1 评估指标RMSE与NASA评分函数剩余寿命预测常用两个指标RMSE和评分函数。RMSE看重所有样本的整体误差而NASA评分函数对晚期低估预测寿命小于真实值惩罚远大于高估因为漏报故障的代价比提前维修更大。评分函数公式是[ S \sum \left(e^{-\frac{h_i}{13}} - 1\right) \quad (h_i 0) ] [ S \sum \left(e^{\frac{h_i}{10}} - 1\right) \quad (h_i \ge 0) ]其中h_i是预测RUL减真实RUL。调优时优先看这个评分而不是只看RMSE因为两个模型RMSE接近时评分可能差很多。画出测试集每个序列的真实寿命曲线和预测寿命曲线的对比图能快速发现哪段退化区间拟合不好。5.2 超参数调优膨胀层数、核大小与dropout第一个要调的参数是膨胀层数。如果滑窗长度是60kernel_size3膨胀系数1、2、4、8、16、32时感受野是12×63127足以覆盖窗口。层数过多会带来更多参数同时容易在训练后期过拟合。第二个是dropout0.2是安全起点如果验证集波动大提高到0.4。超参数推荐范围调试方向kernel_size2 ~ 5核越大感受野越大但计算量线性增加dilation层数4 ~ 8按感受野公式推算num_channels32 ~ 128通道越多特征表达能力越强dropout0.1 ~ 0.4过拟合时增大欠拟合时减小学习率0.0001 ~ 0.01初始0.001损失波动大就降调参时可以每次只改一个变量先固定滑窗和通道数观察评分函数变化。数据集较小时通道从64降到32往往能防止在训练集上拟合极快但验证集上不涨。5.3 用测试集传感器信号可视化预测曲线验证阶段不可只看数字需要把预测曲线叠在原图上。测试集每个发动机编号有不同样本数量取最后一个滑窗的预测值当作最终结果与实际RUL比较。将同一编号下所有滑窗的预测结果按时间顺序画线能直观看到模型在早期是否倾向高估后期是否出现突变。import matplotlib.pyplot as plt def plot_one_engine(model, X_test, engine_id, true_rul): model.eval() with torch.no_grad(): preds model(torch.FloatTensor(X_test[engine_id])) preds preds.numpy().flatten() plt.plot(preds, labelpred RUL) plt.axhline(true_rul, linestyle--, colorred, labeltrue RUL) plt.legend() plt.xlabel(sample index) plt.ylabel(RUL) plt.show()在预测后期如果曲线出现剧烈的锯齿抖动说明模型对高退化状态的敏感度过高可以在TCN块后加一个全局平均池化层抑制最后时间步的过强激活。若曲线整体水平偏移则重新检查RUL标签是否做了正确的截断或归一化。最后保存模型参数时记得连同scaler对象一起用pickle存后续部署预测阶段才能保证输入分布一致。本文还有配套的精品资源点击获取
返回列表