Commit Β·
f7125d8
0
Parent(s):
First release of AGTF30 Prognostics
Browse files- .gitattributes +2 -0
- A320neo_protocol.xml +133 -0
- README.md +16 -0
- agtf30_blade_model.pt +3 -0
- agtf30_scaler_params.npz +3 -0
- cmapss_model.py +115 -0
- requirements.txt +7 -0
- train_agtf30.py +102 -0
.gitattributes
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
A320neo_protocol.xml
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<!--
|
| 3 |
+
a320neo_protocol.xml
|
| 4 |
+
FlightGear Generic Output Protocol β Airbus A320neo (CFM LEAP-1A)
|
| 5 |
+
|
| 6 |
+
Place this file in:
|
| 7 |
+
<fg-root>/Protocol/a320neo_protocol.xml
|
| 8 |
+
|
| 9 |
+
Launch FlightGear with:
|
| 10 |
+
--generic=socket,out,10,127.0.0.1,5500,udp,a320neo_protocol
|
| 11 |
+
|
| 12 |
+
15 comma-separated fields per packet, ASCII, newline-terminated.
|
| 13 |
+
|
| 14 |
+
JSBSim engine property paths are identical across aircraft models β
|
| 15 |
+
only the physical operating ranges differ between B787 and A320neo.
|
| 16 |
+
|
| 17 |
+
Author: Mohammed Bello Sani
|
| 18 |
+
-->
|
| 19 |
+
<PropertyList>
|
| 20 |
+
<generic>
|
| 21 |
+
<output>
|
| 22 |
+
<binary_mode>false</binary_mode>
|
| 23 |
+
<line_separator>\n</line_separator>
|
| 24 |
+
<var_separator>,</var_separator>
|
| 25 |
+
|
| 26 |
+
<!-- [00] EGT proxy for T45 / T25 / T3 β engine exhaust gas temp degF -->
|
| 27 |
+
<chunk>
|
| 28 |
+
<node>/engines/engine[0]/egt-degf</node>
|
| 29 |
+
<type>double</type>
|
| 30 |
+
<format>%.3f</format>
|
| 31 |
+
</chunk>
|
| 32 |
+
|
| 33 |
+
<!-- [01] N2 core shaft speed percent (HPT proxy) -->
|
| 34 |
+
<chunk>
|
| 35 |
+
<node>/engines/engine[0]/n2</node>
|
| 36 |
+
<type>double</type>
|
| 37 |
+
<format>%.6f</format>
|
| 38 |
+
</chunk>
|
| 39 |
+
|
| 40 |
+
<!-- [02] EGT again β inter-turbine temperature proxy T25 -->
|
| 41 |
+
<chunk>
|
| 42 |
+
<node>/engines/engine[0]/egt-degf</node>
|
| 43 |
+
<type>double</type>
|
| 44 |
+
<format>%.3f</format>
|
| 45 |
+
</chunk>
|
| 46 |
+
|
| 47 |
+
<!-- [03] N1 fan shaft speed percent (LPT proxy) -->
|
| 48 |
+
<chunk>
|
| 49 |
+
<node>/engines/engine[0]/n1</node>
|
| 50 |
+
<type>double</type>
|
| 51 |
+
<format>%.6f</format>
|
| 52 |
+
</chunk>
|
| 53 |
+
|
| 54 |
+
<!-- [04] EGT again β compressor exit temperature proxy T3 -->
|
| 55 |
+
<chunk>
|
| 56 |
+
<node>/engines/engine[0]/egt-degf</node>
|
| 57 |
+
<type>double</type>
|
| 58 |
+
<format>%.3f</format>
|
| 59 |
+
</chunk>
|
| 60 |
+
|
| 61 |
+
<!-- [05] Thrust lbf β combustor inlet pressure proxy Pt3 -->
|
| 62 |
+
<chunk>
|
| 63 |
+
<node>/engines/engine[0]/thrust-lbs</node>
|
| 64 |
+
<type>double</type>
|
| 65 |
+
<format>%.3f</format>
|
| 66 |
+
</chunk>
|
| 67 |
+
|
| 68 |
+
<!-- [06] Thrust lbf β static pressure proxy Ps3 -->
|
| 69 |
+
<chunk>
|
| 70 |
+
<node>/engines/engine[0]/thrust-lbs</node>
|
| 71 |
+
<type>double</type>
|
| 72 |
+
<format>%.3f</format>
|
| 73 |
+
</chunk>
|
| 74 |
+
|
| 75 |
+
<!-- [07] N2 repeated for sensor slot N2 -->
|
| 76 |
+
<chunk>
|
| 77 |
+
<node>/engines/engine[0]/n2</node>
|
| 78 |
+
<type>double</type>
|
| 79 |
+
<format>%.4f</format>
|
| 80 |
+
</chunk>
|
| 81 |
+
|
| 82 |
+
<!-- [08] N1 repeated for sensor slot N1 -->
|
| 83 |
+
<chunk>
|
| 84 |
+
<node>/engines/engine[0]/n1</node>
|
| 85 |
+
<type>double</type>
|
| 86 |
+
<format>%.4f</format>
|
| 87 |
+
</chunk>
|
| 88 |
+
|
| 89 |
+
<!-- [09] N1 again as N3 proxy (2-spool: no N3 in JSBSim) -->
|
| 90 |
+
<chunk>
|
| 91 |
+
<node>/engines/engine[0]/n1</node>
|
| 92 |
+
<type>double</type>
|
| 93 |
+
<format>%.4f</format>
|
| 94 |
+
</chunk>
|
| 95 |
+
|
| 96 |
+
<!-- [10] Net thrust lbf β Fnet -->
|
| 97 |
+
<chunk>
|
| 98 |
+
<node>/engines/engine[0]/thrust-lbs</node>
|
| 99 |
+
<type>double</type>
|
| 100 |
+
<format>%.2f</format>
|
| 101 |
+
</chunk>
|
| 102 |
+
|
| 103 |
+
<!-- [11] Altitude ft β context -->
|
| 104 |
+
<chunk>
|
| 105 |
+
<node>/position/altitude-ft</node>
|
| 106 |
+
<type>double</type>
|
| 107 |
+
<format>%.1f</format>
|
| 108 |
+
</chunk>
|
| 109 |
+
|
| 110 |
+
<!-- [12] Airspeed kt β context -->
|
| 111 |
+
<chunk>
|
| 112 |
+
<node>/velocities/airspeed-kt</node>
|
| 113 |
+
<type>double</type>
|
| 114 |
+
<format>%.2f</format>
|
| 115 |
+
</chunk>
|
| 116 |
+
|
| 117 |
+
<!-- [13] Heading deg β context -->
|
| 118 |
+
<chunk>
|
| 119 |
+
<node>/orientation/heading-deg</node>
|
| 120 |
+
<type>double</type>
|
| 121 |
+
<format>%.2f</format>
|
| 122 |
+
</chunk>
|
| 123 |
+
|
| 124 |
+
<!-- [14] Sim elapsed seconds β context -->
|
| 125 |
+
<chunk>
|
| 126 |
+
<node>/sim/time/elapsed-sec</node>
|
| 127 |
+
<type>double</type>
|
| 128 |
+
<format>%.2f</format>
|
| 129 |
+
</chunk>
|
| 130 |
+
|
| 131 |
+
</output>
|
| 132 |
+
</generic>
|
| 133 |
+
</PropertyList>
|
README.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Real-Time Probabilistic Health Monitoring (AGTF30)
|
| 2 |
+
|
| 3 |
+
This repository contains the official implementation of the CNN-BiLSTM-Attention prognostic framework as described in:
|
| 4 |
+
*"Real-Time Probabilistic Health Monitoring of Turbofan Turbine Blades via CNN-BiLSTM-Attention with Physics-Informed Flight Simulator Integration"* (Ene et al., 2026).
|
| 5 |
+
|
| 6 |
+
## Model Performance (Test Set)
|
| 7 |
+
| Metric | Value |
|
| 8 |
+
| :--- | :--- |
|
| 9 |
+
| **Test RMSE** | 3.352 cycles |
|
| 10 |
+
| **RΒ² Score** | 0.993 |
|
| 11 |
+
| **NASA Score** | 346.38 |
|
| 12 |
+
|
| 13 |
+
## Getting Started
|
| 14 |
+
1. **Clone the repo:**
|
| 15 |
+
```bash
|
| 16 |
+
git clone [https://huggingface.co/SM-Bello/AGTF30-Turbofan-Prognostics](https://huggingface.co/SM-Bello/AGTF30-Turbofan-Prognostics)
|
agtf30_blade_model.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a3427f5f0511ea11a82eb2b22be1d3bb0dfbd9743611d60273c232d849961e41
|
| 3 |
+
size 3948965
|
agtf30_scaler_params.npz
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:a7f87a314b5ca1bbe5d038405a80062ce80f4dfba5942b2c20274df58c487895
|
| 3 |
+
size 2004
|
cmapss_model.py
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
cmapss_model.py
|
| 3 |
+
===============
|
| 4 |
+
CNN-BiLSTM-Attention model with MC Dropout for RUL prediction.
|
| 5 |
+
Used for both C-MAPSS and AGTF30 turbine blade datasets.
|
| 6 |
+
|
| 7 |
+
Author: Mohammed Bello Sani
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn as nn
|
| 12 |
+
import torch.nn.functional as F
|
| 13 |
+
import numpy as np
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class CNNBiLSTMAttention(nn.Module):
|
| 17 |
+
"""
|
| 18 |
+
CNN + Bidirectional LSTM + Self-Attention + MC Dropout
|
| 19 |
+
"""
|
| 20 |
+
def __init__(self, hp):
|
| 21 |
+
super().__init__()
|
| 22 |
+
self.hp = hp
|
| 23 |
+
n_features = hp['n_features']
|
| 24 |
+
cnn_filters = hp['cnn_filters']
|
| 25 |
+
kernel_size = hp.get('cnn_kernel', 3)
|
| 26 |
+
lstm_hidden = hp['lstm_hidden']
|
| 27 |
+
lstm_layers = hp['lstm_layers']
|
| 28 |
+
fc_hidden = hp.get('fc_hidden', 64)
|
| 29 |
+
dropout = hp.get('mc_dropout', 0.5)
|
| 30 |
+
|
| 31 |
+
# CNN feature extractor
|
| 32 |
+
cnn_layers = []
|
| 33 |
+
in_channels = n_features
|
| 34 |
+
for i, filters in enumerate(cnn_filters):
|
| 35 |
+
cnn_layers.append(nn.Conv1d(in_channels, filters, kernel_size, padding='same'))
|
| 36 |
+
cnn_layers.append(nn.BatchNorm1d(filters))
|
| 37 |
+
cnn_layers.append(nn.GELU())
|
| 38 |
+
cnn_layers.append(nn.Dropout(dropout * 0.5)) # lighter dropout in CNN
|
| 39 |
+
in_channels = filters
|
| 40 |
+
self.cnn = nn.Sequential(*cnn_layers)
|
| 41 |
+
self.cnn_out_dim = cnn_filters[-1]
|
| 42 |
+
|
| 43 |
+
# BiLSTM
|
| 44 |
+
self.lstm = nn.LSTM(
|
| 45 |
+
input_size=self.cnn_out_dim,
|
| 46 |
+
hidden_size=lstm_hidden,
|
| 47 |
+
num_layers=lstm_layers,
|
| 48 |
+
batch_first=True,
|
| 49 |
+
bidirectional=True,
|
| 50 |
+
dropout=hp.get('lstm_dropout', 0.3) if lstm_layers > 1 else 0
|
| 51 |
+
)
|
| 52 |
+
lstm_out_dim = lstm_hidden * 2 # bidirectional
|
| 53 |
+
|
| 54 |
+
# Self-Attention
|
| 55 |
+
self.attn_heads = hp.get('attn_heads', 4)
|
| 56 |
+
self.attn = nn.MultiheadAttention(
|
| 57 |
+
embed_dim=lstm_out_dim,
|
| 58 |
+
num_heads=self.attn_heads,
|
| 59 |
+
dropout=dropout * 0.3,
|
| 60 |
+
batch_first=True
|
| 61 |
+
)
|
| 62 |
+
self.attn_norm = nn.LayerNorm(lstm_out_dim)
|
| 63 |
+
|
| 64 |
+
# Fully connected regressor
|
| 65 |
+
self.fc = nn.Sequential(
|
| 66 |
+
nn.Linear(lstm_out_dim, fc_hidden),
|
| 67 |
+
nn.GELU(),
|
| 68 |
+
nn.Dropout(dropout),
|
| 69 |
+
nn.Linear(fc_hidden, 1)
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
# MC Dropout layers are already included; we keep them active during inference
|
| 73 |
+
self.dropout_rate = dropout
|
| 74 |
+
|
| 75 |
+
def forward(self, x):
|
| 76 |
+
# x shape: (batch, seq_len, features)
|
| 77 |
+
# Permute for CNN: (batch, features, seq_len)
|
| 78 |
+
x_cnn = x.permute(0, 2, 1)
|
| 79 |
+
x_cnn = self.cnn(x_cnn) # (batch, filters, seq_len)
|
| 80 |
+
x_cnn = x_cnn.permute(0, 2, 1) # (batch, seq_len, filters)
|
| 81 |
+
|
| 82 |
+
# BiLSTM
|
| 83 |
+
lstm_out, _ = self.lstm(x_cnn) # (batch, seq_len, lstm_hidden*2)
|
| 84 |
+
|
| 85 |
+
# Self-Attention
|
| 86 |
+
attn_out, _ = self.attn(lstm_out, lstm_out, lstm_out)
|
| 87 |
+
attn_out = self.attn_norm(attn_out + lstm_out) # residual
|
| 88 |
+
|
| 89 |
+
# Global average pooling over time dimension
|
| 90 |
+
pooled = attn_out.mean(dim=1) # (batch, lstm_out_dim)
|
| 91 |
+
|
| 92 |
+
# Final prediction
|
| 93 |
+
out = self.fc(pooled).squeeze(-1) # (batch,)
|
| 94 |
+
return out
|
| 95 |
+
|
| 96 |
+
def predict_with_uncertainty(self, x, n_samples=100):
|
| 97 |
+
"""
|
| 98 |
+
Monte Carlo Dropout inference.
|
| 99 |
+
Returns mean and standard deviation.
|
| 100 |
+
"""
|
| 101 |
+
self.train() # keep dropout active
|
| 102 |
+
preds = []
|
| 103 |
+
with torch.no_grad():
|
| 104 |
+
for _ in range(n_samples):
|
| 105 |
+
preds.append(self.forward(x).cpu().numpy())
|
| 106 |
+
preds = np.stack(preds, axis=0)
|
| 107 |
+
mean = preds.mean(axis=0)
|
| 108 |
+
std = preds.std(axis=0)
|
| 109 |
+
return mean, std
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
# Compatibility: if the old code expects the model to be loaded via torch.load,
|
| 113 |
+
# we need to ensure the class is defined in this module.
|
| 114 |
+
if __name__ == '__main__':
|
| 115 |
+
print("CNNBiLSTMAttention model definition ready.")
|
requirements.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
numpy
|
| 3 |
+
pandas
|
| 4 |
+
scikit-learn
|
| 5 |
+
matplotlib
|
| 6 |
+
scipy
|
| 7 |
+
h5py
|
train_agtf30.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Modified train_agtf30.py
|
| 3 |
+
Boosted architecture for higher tracking accuracy.
|
| 4 |
+
"""
|
| 5 |
+
import sys, os, time, warnings
|
| 6 |
+
import numpy as np
|
| 7 |
+
import pandas as pd
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
import torch.optim as optim
|
| 11 |
+
from torch.utils.data import DataLoader, TensorDataset
|
| 12 |
+
from sklearn.preprocessing import MinMaxScaler
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
|
| 15 |
+
# Replace with your actual path
|
| 16 |
+
sys.path.insert(0, r'C:\Users\User\Desktop\Steph')
|
| 17 |
+
from cmapss_model import CNNBiLSTMAttention
|
| 18 |
+
|
| 19 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
+
# CONFIG
|
| 21 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 22 |
+
DATA_FILE = r'C:\Users\User\Desktop\Steph\archive\AGTF30\AGTF30\agtf30_blade_dataset.csv'
|
| 23 |
+
MODEL_OUT = r'C:\Users\User\Desktop\Steph\archive\AGTF30\AGTF30\agtf30_blade_model.pt'
|
| 24 |
+
SCALER_OUT = r'C:\Users\User\Desktop\Steph\archive\AGTF30\AGTF30\agtf30_scaler_params.npz'
|
| 25 |
+
|
| 26 |
+
SENSOR_NAMES = ['T45','Pt45','T25','Pt25','T3','Pt3','Ps3','N2','N1','N3','Fnet']
|
| 27 |
+
HP = {
|
| 28 |
+
'n_features' : 11,
|
| 29 |
+
'cnn_filters' : [64, 128], # Increased depth
|
| 30 |
+
'cnn_kernel' : 3,
|
| 31 |
+
'cnn_dropout' : 0.25,
|
| 32 |
+
'lstm_hidden' : 128,
|
| 33 |
+
'lstm_layers' : 2,
|
| 34 |
+
'lstm_dropout': 0.3,
|
| 35 |
+
'attn_heads' : 8, # More heads for better focus
|
| 36 |
+
'fc_hidden' : 128, # Wider FC layer
|
| 37 |
+
'mc_dropout' : 0.3,
|
| 38 |
+
'window_size' : 30,
|
| 39 |
+
'batch_size' : 64, # Smaller batch for better gradients
|
| 40 |
+
'epochs' : 200,
|
| 41 |
+
'lr' : 1e-3, # Adjusted LR
|
| 42 |
+
'weight_decay': 1e-4
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
|
| 46 |
+
|
| 47 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 48 |
+
# 1. LOAD & PREP
|
| 49 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 50 |
+
df = pd.read_csv(DATA_FILE)
|
| 51 |
+
# Compute RUL
|
| 52 |
+
max_cycles = df.groupby('engine_id')['cycle'].max().reset_index()
|
| 53 |
+
max_cycles.columns = ['engine_id','max_cycle']
|
| 54 |
+
df = df.merge(max_cycles, on='engine_id')
|
| 55 |
+
df['RUL'] = (df['max_cycle'] - df['cycle']).clip(upper=125)
|
| 56 |
+
|
| 57 |
+
# Normalization
|
| 58 |
+
scaler = MinMaxScaler()
|
| 59 |
+
df[SENSOR_NAMES] = scaler.fit_transform(df[SENSOR_NAMES])
|
| 60 |
+
|
| 61 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 62 |
+
# 2. IMPROVED DATA SAMPLING
|
| 63 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 64 |
+
def create_dataset(data, window_size):
|
| 65 |
+
X, y = [], []
|
| 66 |
+
for eid, grp in data.groupby('engine_id'):
|
| 67 |
+
vals = grp[SENSOR_NAMES].values
|
| 68 |
+
rul = grp['RUL'].values
|
| 69 |
+
# Sample EVERY cycle, not just the last one
|
| 70 |
+
for i in range(len(vals) - window_size + 1):
|
| 71 |
+
X.append(vals[i:i+window_size])
|
| 72 |
+
y.append(rul[i+window_size-1])
|
| 73 |
+
return torch.tensor(np.array(X), dtype=torch.float32), torch.tensor(np.array(y), dtype=torch.float32)
|
| 74 |
+
|
| 75 |
+
engines = df['engine_id'].unique()
|
| 76 |
+
np.random.shuffle(engines)
|
| 77 |
+
split = int(len(engines)*0.85)
|
| 78 |
+
train_loader = DataLoader(TensorDataset(*create_dataset(df[df['engine_id'].isin(engines[:split])], 30)), batch_size=64, shuffle=True)
|
| 79 |
+
val_loader = DataLoader(TensorDataset(*create_dataset(df[df['engine_id'].isin(engines[split:])], 30)), batch_size=64)
|
| 80 |
+
|
| 81 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 82 |
+
# 3. TRAIN
|
| 83 |
+
# βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 84 |
+
model = CNNBiLSTMAttention(HP).to(DEVICE)
|
| 85 |
+
optimizer = optim.Adam(model.parameters(), lr=HP['lr'])
|
| 86 |
+
criterion = nn.MSELoss()
|
| 87 |
+
|
| 88 |
+
print("Training started...")
|
| 89 |
+
for epoch in range(HP['epochs']):
|
| 90 |
+
model.train()
|
| 91 |
+
for Xb, yb in train_loader:
|
| 92 |
+
optimizer.zero_grad()
|
| 93 |
+
loss = criterion(model(Xb.to(DEVICE)).squeeze(), yb.to(DEVICE))
|
| 94 |
+
loss.backward()
|
| 95 |
+
optimizer.step()
|
| 96 |
+
|
| 97 |
+
# Validation loop omitted for brevity, just verify output
|
| 98 |
+
if epoch % 10 == 0:
|
| 99 |
+
print(f"Epoch {epoch} finished.")
|
| 100 |
+
|
| 101 |
+
torch.save({'model_state': model.state_dict(), 'hp': HP}, MODEL_OUT)
|
| 102 |
+
print("Training complete and saved.")
|