当前位置: 首页 > article >正文

基于卷积神经网络的航空发动机剩余寿命预测Matlab实现

本文利用NASA提供的涡扇发动机退化数据集,进行数据预处理,构建训练样本和测试样本,然后搭建卷积神经网络(Convolutional Neural Network,CNN),学习训练数据,最后利用测试数据,分析神经网络模型预测剩余使用寿命(Remaining useful life,RUL)的效果。

1.卷积神经网络

卷积神经网络(Convolutional Neural Networks,CNNs)是一种深度学习模型,它在图像处理、语音识别、自然语言处理等多个领域都有着广泛的应用。

CNNs的设计灵感来源于人类视觉系统的工作原理,它特别适合处理具有网格状拓扑结构的数据,比如图像,著名的卷积神经网络模型有AlexNet、InceptionNet、VGGNet和ResNet等。

值得注意是,上述的这些卷积神经网络是对图片进行卷积操作,它会利用当前位置的前后四周的数据。而在本文案例中,在进行卷积操作时,处理的对象是时间序列,如果还按照上述的卷积方式,会将未来数据带入计算,这是矛盾的,因此需要改变卷积网络的参数。

图片

该数据集为C-MAPSS模拟数据,该数据是模拟大型商用涡扇发动机的数据, 发动机简图如上图所示。该数据的代码采用了MATLAB及其Simulink模块。

2.方法具体流程

2.1 数据预处理

读取航空发动机的退化数据集,并开展数据预处理操作,整理训练样本和测试样本。

clc
close all
clear all

%数据变量的维度的含义
% data文件夹现在包含包含26列数字的文本文件,用空格分隔。
% 每一行都是在单个操作周期中获取的数据,每列表示不同的变量:
% 列 1 —  发动机单元编号
% 列 2 —  时间步
% 列 3–5 —  运行设置
% 列 6–26 —   传感器1–21
% 数据介绍页面(数据集A) https://mp.weixin.qq.com/s/RPToBmMvt5EJth42eXL2JA   

%% 数据预处理
dataFolder = "D:\我的文档\硕士\信息\副业\公众号\公众号" + ...
    "\20240518基于卷积神经网络的航空发动机剩余寿命预测方法\" + ...
    "20240518基于卷积神经网络的航空发动机剩余寿命预测方法\data";
filenameTrainPredictors = fullfile(dataFolder,"train_FD001.txt");
rawTrain = localLoadData(filenameTrainPredictors);  %训练样本 Table类型数据

head(rawTrain.X{1},8)   %展示部分数据的输入

rawTrain.Y{1}(1:8)    %展示部分数据的输出(RUL标签)

stackedplot(rawTrain.X{1},[3,5,6,7,8,15,16,24],XVariable='timeStamp')  %展示部分数据在单个样本周期的趋势

prog = prognosability(rawTrain.X,"timeStamp");% 判断单个样本全寿命周期中不随时间周期变换的特征,即无用的输入
 
idxToRemove = prog.Variables==0 | isnan(prog.Variables);   %无用的输入的索引
featToRetain = prog.Properties.VariableNames(~idxToRemove);
for i = 1:height(rawTrain)
    rawTrain.X{i} = rawTrain.X{i}{:,featToRetain};         %有用的输入
end


[~,Xmu,Xsigma] = zscore(vertcat(rawTrain.X{:}));         %归一化输入,准备进行Z标准化,
preTrain = table();
for i = 1:numel(rawTrain.X)                              %Z标准化
    preTrain.X{i} = (rawTrain.X{i} - Xmu) ./ Xsigma;
end

rulThreshold = 150;                                      %设置最大的RUL标签为150,过大的RUL没有指导意义,合适的RUL也有利于网络训练的收敛
for i = 1:numel(rawTrain.Y)                              %输出标签预处理
    preTrain.Y{i} = min(rawTrain.Y{i},rulThreshold);
end


for i = 1:size(preTrain,1)
    preTrain.X{i} = preTrain.X{i}';    %这是第一个维度为输入数据的特征维度
    preTrain.Y{i} = preTrain.Y{i}';    %输出也需要
    sequence = preTrain.X{i};
    sequenceLengths(i) = size(sequence,2); 
end

[sequenceLengths,idx] = sort(sequenceLengths,'descend');   %按照样本的时间长短进行排序,对网络训练有用
%  在后续trainingOptions中,每次epoch都不打乱样本的顺序(Shuffle='never'),这是排序是因为每次训练都是一个batch,
% 由于长度不同往往需要一个统一的长度,才能一同送入网络中,当根据样本长度差异较大,需要补0操作,占据存储空间且不利于网络的学习真正有用的特征
% 当排序后,需要补充的0少,所以相对于不排序是具有优势的
XTrain = preTrain.X(idx);
YTrain = preTrain.Y(idx); 

这展示了部分数据的输入和输出,上图为部分数据的部分输入。从图中能发现,一些变量在全寿命周期没有变换,因此剔除与不随时间变换的无用特征。因为随着时间的增长RUL是单调递减的,这些特征不发生变化,那么它们对预测RUL是没有意义的,所以需要剔除掉。

除此之外,进行了Z标准化,RUL的标签进行了规整,限制输出的RUL标签最大为150,这有利于网络的收敛,可根据具体实际进行调制,过大的RUL对工程指导意义不大(个人观点)。

对数据进行了排序,这样对网络的收敛是有利的。不进行排序,同时训练参数的shuffle改为every-epoch,会发现网络很难收敛。最终的训练集输入和输出分别保存在变量XTrain和YTrain。

2.2 搭建网络模型

搭建卷积神经网络模型,并利用训练数据训练神经网络模型。 

%% 构建神经网络
numFeatures = size(XTrain{1},1);   %输入特征的个数,

numResponses = 1;
%神经网络结构
layers = [                              
    sequenceInputLayer(numFeatures)
    convolution1dLayer(5,32,Padding="causal")  %这里采用了因果填充的方式,确保模型的输出不会违反时间上的因果关系,参考时间卷积神经网络中的因果卷积
    batchNormalizationLayer()
    reluLayer()
    convolution1dLayer(7,64,Padding="causal")
    batchNormalizationLayer
    reluLayer()
    convolution1dLayer(11,128,Padding="causal")
    batchNormalizationLayer
    reluLayer()
    convolution1dLayer(13,256,Padding="causal")
    batchNormalizationLayer
    reluLayer()
    convolution1dLayer(15,512,Padding="causal")
    batchNormalizationLayer
    reluLayer()
    fullyConnectedLayer(100)
    reluLayer()
    dropoutLayer(0.5)
    fullyConnectedLayer(numResponses)
    regressionLayer()];


maxEpochs = 40; %最大Epoch数
miniBatchSize = 16;  %最小batchsize

options = trainingOptions('adam',...
    LearnRateSchedule='piecewise',...
    MaxEpochs=maxEpochs,...
    MiniBatchSize=miniBatchSize,...
    InitialLearnRate=0.01,...
    GradientThreshold=1,...
    Shuffle='never',...
    Plots='training-progress',...
    Verbose=0);

net = trainNetwork(XTrain,YTrain,layers,options);  %训练神经网络模型
%网络结构展示
figure;
lgraph = layerGraph(net.Layers);
plot(lgraph)

卷积神经网络的训练过程: 

图片

从上图结果看,RSME有待进一步提高。多次运行代码,网络收敛的曲线较为接近,无明显差异。修改学习率、Epoch、batchsize大小,结果无明显变化,可能需要进一步进行网络结构的优化,增加新的输入层,提出更好的网络模型。

2.3 剩余寿命预测

采用已训练的神经网络模型,预测测试样本的剩余使用寿命,并采用RMSE评估网络的预测效果。

值得注意是,这个代码里面有两个函数:localLoadData(加载数据用的,同时制作RUL标签)和localLambdaPlot(画图函数)。

%% 测试模型
filenameTestPredictors = fullfile(dataFolder,'test_FD001.txt');
filenameTestResponses = fullfile(dataFolder,'RUL_FD001.txt');
dataTest = localLoadData(filenameTestPredictors,filenameTestResponses);
%测试数据进行Z标准化
for i = 1:numel(dataTest.X)
    dataTest.X{i} = dataTest.X{i}{:,featToRetain};
    dataTest.X{i} = (dataTest.X{i} - Xmu) ./ Xsigma;
    dataTest.Y{i} = min(dataTest.Y{i},rulThreshold);
end

predictions = table(Size=[height(dataTest) 2],VariableTypes=["cell","cell"],VariableNames=["Y","YPred"]);
%预测
for i=1:height(dataTest)
    unit = dataTest.X{i}';   
    predictions.Y{i} = dataTest.Y{i}';
    predictions.YPred{i} = predict(net,unit,MiniBatchSize=1);
end

%计算RMSE 评估网络
for i = 1:size(predictions,1)
    predictions.RMSE(i) = sqrt(mean((predictions.Y{i} - predictions.YPred{i}).^2));
end
%RMSE直方图
figure;
histogram(predictions.RMSE,NumBins=10);
title("RMSE ( Mean: " + round(mean(predictions.RMSE),2) + " , StDev: " + round(std(predictions.RMSE),2) + " )");
ylabel('Frequency');
xlabel('RMSE');
%% 展示预测曲线
figure;
localLambdaPlot(predictions,"random");
figure;
localLambdaPlot(predictions,"best");
figure;
localLambdaPlot(predictions,"worst");
figure;
localLambdaPlot(predictions,"average");

%% 加载数据的函数
function data = localLoadData(filenamePredictors,varargin)

if isempty(varargin)
    filenameResponses = [];
else
    filenameResponses = varargin{:};
end

rawData = readtable(filenamePredictors);
% 输入数据的名称
VarNames = {...
    'id', 'timeStamp', 'op_setting_1', 'op_setting_2', 'op_setting_3', ...
    'sensor_1', 'sensor_2', 'sensor_3', 'sensor_4', 'sensor_5', ...
    'sensor_6', 'sensor_7', 'sensor_8', 'sensor_9', 'sensor_10', ...
    'sensor_11', 'sensor_12', 'sensor_13', 'sensor_14', 'sensor_15', ...
    'sensor_16', 'sensor_17', 'sensor_18', 'sensor_19', 'sensor_20', ...
    'sensor_21'};
rawData.Properties.VariableNames = VarNames;

if ~isempty(filenameResponses)
    RULTest = readmatrix(filenameResponses);
end

IDs = rawData{:,1};
nID = unique(IDs);
numObservations = numel(nID);

% 初始化
data = table(Size=[numObservations 2],...
    VariableTypes={'cell','cell'},...
    VariableNames={'X','Y'});

for i=1:numObservations
    idx = IDs == nID(i);
    data.X{i} = rawData(idx,:);
    if isempty(filenameResponses)
        % 计算RUL
        data.Y{i} = flipud(rawData.timeStamp(idx))-1;
    else
        sequenceLength = sum(idx);
        endRUL = RULTest(i);
        data.Y{i} = [endRUL+sequenceLength-1:-1:endRUL]'; 
    end
end
end

%%
function localLambdaPlot(predictions,lambdaCase)

if isnumeric(lambdaCase)
    idx = lambdaCase;
else
    switch lambdaCase   %展示结果
        case {"Random","random","r"}
            idx = randperm(height(predictions),1);
        case {"Best","best","b"}
            idx = find(predictions.RMSE == min(predictions.RMSE)); 
        case {"Worst","worst","w"}
            idx = find(predictions.RMSE == max(predictions.RMSE)); 
        case {"Average","average","a"}
            err = abs(predictions.RMSE-mean(predictions.RMSE));
            idx = find(err==min(err),1);
    end
end
y = predictions.Y{idx};
yPred = predictions.YPred{idx};
x = 0:numel(y)-1;
plot(x,y,x,yPred)
legend("True RUL","Predicted RUL")
xlabel("Time stamp (Test data sequence)")
ylabel("RUL (Cycles)")

title("RUL for Test engine #"+idx+ " ("+lambdaCase+" case)")
end

 上图为测试样本的RMSE偏差分布的直方图,从图中能发现测试样本的RMSE偏差集中在10-15之间,有待进一步优化和提高。

下图为部分样本的RUL预测结果,分别对应了随机选择样本的RUL预测结果、RMSE偏差最小样本的RUL预测结果、RMSE偏差最大样本的预测结果和RMSE偏差适中样本的预测结果。

 随机选择样本的RUL预测结果

 RMSE偏差最小样本的RUL预测结果

图片

RMSE偏差最大样本的预测结果

图片

RMSE偏差适中样本的预测结果

从上图中能发现,虽然第二个样本的RMSE偏差最小,但预测的RUL较真实的RUL偏差较大。相反,第四个图片展示的结果,随着RUL的减小,预测的RUL不断逼近、跟踪真实的RUL,这表明了CNN网络在预测RUL方面也有一定的可行性。


http://www.kler.cn/a/399348.html

相关文章:

  • Cyberchef配合Wireshark提取并解析TCP/FTP流量数据包中的文件
  • 矩阵的对角化特征值分解
  • 怎样在软件设计中选择使用GOF设计模式
  • 爬虫——Requests库的使用
  • 杰控通过 OPCproxy 获取数据发送到服务器
  • 如何进入python交互界面
  • [每周一更]-(第123期):模拟面试|消息队列面试思路解析
  • STM32 独立看门狗(IWDG)详解
  • PHP 条件语句
  • 无线迷踪:陈欣的网络之旅
  • python之openpyxl快速读取Excel表内容
  • docker:基于Dockerfile镜像制作完整案例
  • 第 17 章 - Go语言 上下文( Context )
  • Kafka简单实践
  • SpringBoot多环境配置的实现
  • 力扣 LeetCode 15. 三数之和(Day3:哈希表)
  • Java中的 File类与Files类
  • ssm131保险业务管理系统设计与实现+jsp(论文+源码)_kaic
  • leetcode hot100【LeetCode 64.最小路径和】java实现
  • MySQL一些使用操作-持续更新
  • 前端,location.reload刷新页面
  • Go语言24小时极速学习教程(一)基础语法
  • 【网络安全】Cookie SameSite属性
  • 贪吃蛇小游戏设计
  • java八股-jvm入门-程序计数器,堆,元空间,虚拟机栈,本地方法栈,类加载器,双亲委派,类加载执行过程
  • 121、SQL Server取开始时间、截止时间