Restlebenszeit-Vorhersage / Remaining Useful Lifetime (RUL)¶
Dieses Notebook steht auch direkt zum Download auf unserem GitHub bereit!Beispiel Notebook aus dem ITS.ML Workshop "Inside Out: The Essentials of Predictive Maintenance"¶
Dieses Beispiel beschreibt exemplarisch das Vorgehen, um eine ML-Pipeline zu erzeugen, das in der Lage ist die Restlebenszeit eines Systems vorherzusagen.
1. Daten¶
In diesem Beispiel nutzen wir die Turbofan Engine Daten des Nasa Repositories (6. Turbofan Engine Degradation Simulation Data Set). Die darin enthaltenen Datensätze bestehen aus künstlich erzeugten Daten und beschreiben das Verhalten eines Flugzeugtriebwerks, indem unterschiedliche Betriebs- und Fehlerbedingungen erfasst werden. Ziel ist es ein Modell zu entwickeln, das vorhersagt, wann ein Fehler in der Hochdruckkompressionseinheit (HPC) des Flugzeugtriebwerks auftritt. Die Daten sind in 4 Datensätze unterteilt und entsprechend nummeriert, die jeweils mit unterschiedlichen Umgebungsbedingungen simulieren. Jeder Datensatz ist wiederum aufgeteilt in 3 Dateien (wobei "X" durch die Nummer des Datensatzes ersetzt werden muss):
- train_FD00X.txt: Trainingsdatensatz bei dem der letzte Timestep als Fehlerzeitpunkt zu interpretieren ist
- test_FD00X.txt: Testdatensatz bei dem jede Instanz zu einem unbestimmten Zeitpunkt endet. Für diesen soll die Restlebenszeit prädiziert werden
- rul_FD00X.txt: Beinhaltet die wahren RUL Werte, die zur Evaluation genutzt werden
Sowohl die Trainingsdaten, als auch die Testdaten, sind im Komma-separierten CSV-Format gespeichert. Im Folgenden sind die Spalten erläutert:
- Spalte 1: Instanz ID
Alle Instanzen sind nummeriert von 1 - 100, dementsprechend beschreibt die erste Spalte um welche Instanz es sich handelt. - Spalte 2: Timestep
Jede Instanz enthält Aufzeichnungen von Sensoren über einen längeren Zeitraum. Die Zeitpunkte, zu denen es Werte gibt, sind nummeriert und in der zweiten Spalte zu finden. - Spalten 3 - 5: Umgebungsbedingungen
Zu den jeweiligen Zeitpunkten wurden Informationen zu den Umgebungsbedigungen gesammelt, welche in diesen Spalten notiert sind. - Spalten 6 - 26: Sensor Werte
Insgesamt wurden 21 Sensoren aufgezeichnet, welche in diesen Spalten notiert sind
Sowohl die Umgebungsbedingungen, als auch die Sensor Werte, sind Zeitserien, welche von einem Triebwerk desselben Types stammen.
1.1 Daten lesen¶
Zu Beginn müssen die Daten eingelesen werden. Da der Datensatz in ein Pandas Dataframe geladen wird, ist es möglich die Spalten zu benennen. Dementsprechend wird zuerst die Spaltenbezeichnung erstellt um anschließend die Daten zu laden.
import warnings
warnings.filterwarnings("ignore") # Unterdrücke matplotlib Warnungen für sauberen Output
import pandas as pd
# Erstellen der Spaltennamen für das Dataframe, da der verwendeter Datensatz keine Spaltenbeschreibung besitzt
operational_setting_columns_names=["Operation Setting " + str(i) for i in range(1,4)]
sensor_data_columns_names=["Sensor " + str(i) for i in range(1,22)]
column_names = ["Instance", "Timestep"] + operational_setting_columns_names + sensor_data_columns_names
# Laden des Trainingdatensatzes FD001 mit den zuvor erstellten Spaltennamen
X_train = pd.read_csv("./CMAPSSData/train_FD001.txt", names=column_names, delim_whitespace=True, dtype=float)
print('Anzahl der Instanzen: ' + str(len(X_train["Instance"].unique())))
X_train.head()
Anzahl der Instanzen: 100
Instance | Timestep | Operation Setting 1 | Operation Setting 2 | Operation Setting 3 | Sensor 1 | Sensor 2 | Sensor 3 | Sensor 4 | Sensor 5 | ... | Sensor 12 | Sensor 13 | Sensor 14 | Sensor 15 | Sensor 16 | Sensor 17 | Sensor 18 | Sensor 19 | Sensor 20 | Sensor 21 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1.0 | 1.0 | -0.0007 | -0.0004 | 100.0 | 518.67 | 641.82 | 1589.70 | 1400.60 | 14.62 | ... | 521.66 | 2388.02 | 8138.62 | 8.4195 | 0.03 | 392.0 | 2388.0 | 100.0 | 39.06 | 23.4190 |
1 | 1.0 | 2.0 | 0.0019 | -0.0003 | 100.0 | 518.67 | 642.15 | 1591.82 | 1403.14 | 14.62 | ... | 522.28 | 2388.07 | 8131.49 | 8.4318 | 0.03 | 392.0 | 2388.0 | 100.0 | 39.00 | 23.4236 |
2 | 1.0 | 3.0 | -0.0043 | 0.0003 | 100.0 | 518.67 | 642.35 | 1587.99 | 1404.20 | 14.62 | ... | 522.42 | 2388.03 | 8133.23 | 8.4178 | 0.03 | 390.0 | 2388.0 | 100.0 | 38.95 | 23.3442 |
3 | 1.0 | 4.0 | 0.0007 | 0.0000 | 100.0 | 518.67 | 642.35 | 1582.79 | 1401.87 | 14.62 | ... | 522.86 | 2388.08 | 8133.83 | 8.3682 | 0.03 | 392.0 | 2388.0 | 100.0 | 38.88 | 23.3739 |
4 | 1.0 | 5.0 | -0.0019 | -0.0002 | 100.0 | 518.67 | 642.37 | 1582.85 | 1406.22 | 14.62 | ... | 522.19 | 2388.04 | 8133.80 | 8.4294 | 0.03 | 393.0 | 2388.0 | 100.0 | 38.90 | 23.4044 |
5 rows × 26 columns
1.2 Daten visualisieren¶
Im Folgenden werden einige Eigenschaften der verschiedenen Sensoren visualisiert. Bei einigen Sensoren ist zu erkennen, dass der minimale Wert dem maximalen Wert gleicht (z.B. Sensor 1, 5, 6, ...). Dies bedeutet, dass in allen Instanzen für diesen Sensor für jeden Timestep der gleiche Wert aufgezeichnet wurde. Dies spricht dafür, dass diese Sensoren keine Aussagekraft haben für eine spätere Prädiktion.
- mean: Mittelwert
- std: Standardabweichung vom Mittelwert
- min: Minimaler Wert
- 50%: Median
- max: Maximaler Wert
X_train.describe(percentiles=[]).transpose()[1:]
count | mean | std | min | 50% | max | |
---|---|---|---|---|---|---|
Timestep | 20631.0 | 108.807862 | 6.888099e+01 | 1.0000 | 104.0000 | 362.0000 |
Operation Setting 1 | 20631.0 | -0.000009 | 2.187313e-03 | -0.0087 | 0.0000 | 0.0087 |
Operation Setting 2 | 20631.0 | 0.000002 | 2.930621e-04 | -0.0006 | 0.0000 | 0.0006 |
Operation Setting 3 | 20631.0 | 100.000000 | 0.000000e+00 | 100.0000 | 100.0000 | 100.0000 |
Sensor 1 | 20631.0 | 518.670000 | 0.000000e+00 | 518.6700 | 518.6700 | 518.6700 |
Sensor 2 | 20631.0 | 642.680934 | 5.000533e-01 | 641.2100 | 642.6400 | 644.5300 |
Sensor 3 | 20631.0 | 1590.523119 | 6.131150e+00 | 1571.0400 | 1590.1000 | 1616.9100 |
Sensor 4 | 20631.0 | 1408.933782 | 9.000605e+00 | 1382.2500 | 1408.0400 | 1441.4900 |
Sensor 5 | 20631.0 | 14.620000 | 1.776400e-15 | 14.6200 | 14.6200 | 14.6200 |
Sensor 6 | 20631.0 | 21.609803 | 1.388985e-03 | 21.6000 | 21.6100 | 21.6100 |
Sensor 7 | 20631.0 | 553.367711 | 8.850923e-01 | 549.8500 | 553.4400 | 556.0600 |
Sensor 8 | 20631.0 | 2388.096652 | 7.098548e-02 | 2387.9000 | 2388.0900 | 2388.5600 |
Sensor 9 | 20631.0 | 9065.242941 | 2.208288e+01 | 9021.7300 | 9060.6600 | 9244.5900 |
Sensor 10 | 20631.0 | 1.300000 | 0.000000e+00 | 1.3000 | 1.3000 | 1.3000 |
Sensor 11 | 20631.0 | 47.541168 | 2.670874e-01 | 46.8500 | 47.5100 | 48.5300 |
Sensor 12 | 20631.0 | 521.413470 | 7.375534e-01 | 518.6900 | 521.4800 | 523.3800 |
Sensor 13 | 20631.0 | 2388.096152 | 7.191892e-02 | 2387.8800 | 2388.0900 | 2388.5600 |
Sensor 14 | 20631.0 | 8143.752722 | 1.907618e+01 | 8099.9400 | 8140.5400 | 8293.7200 |
Sensor 15 | 20631.0 | 8.442146 | 3.750504e-02 | 8.3249 | 8.4389 | 8.5848 |
Sensor 16 | 20631.0 | 0.030000 | 1.387812e-17 | 0.0300 | 0.0300 | 0.0300 |
Sensor 17 | 20631.0 | 393.210654 | 1.548763e+00 | 388.0000 | 393.0000 | 400.0000 |
Sensor 18 | 20631.0 | 2388.000000 | 0.000000e+00 | 2388.0000 | 2388.0000 | 2388.0000 |
Sensor 19 | 20631.0 | 100.000000 | 0.000000e+00 | 100.0000 | 100.0000 | 100.0000 |
Sensor 20 | 20631.0 | 38.816271 | 1.807464e-01 | 38.1400 | 38.8300 | 39.4300 |
Sensor 21 | 20631.0 | 23.289705 | 1.082509e-01 | 22.8942 | 23.2979 | 23.6184 |
Nun werden die Längen der Instanzen betrachtet. Im Folgenden ist ein Diagramm zu sehen, das die Verteilung der Instanzlängen der Trainingsdaten wiedergibt.
import matplotlib.pyplot as plt
def plot_label_distribution(data, title=None):
"""
Erstellt ein Balkendiagramm, das die Lebensdauer der Instanzen visualisiert
"""
overall_instance_lengths = []
number_of_instances = len(data["Instance"].unique())
for instance_id in range(number_of_instances): # Über alle Instanzen
instance = data[data["Instance"] == instance_id + 1]
overall_instance_lengths.append(instance.shape[0])
fig, ax = plt.subplots()
ax.hist(x=overall_instance_lengths, bins=number_of_instances);
ax.set(xlabel='Timesteps', ylabel='Amount', title=title)
fig.show()
plot_label_distribution(X_train)
Als nächstes wird eine Instanz näher betrachtet. Je Umgebungsbedingung/Sensor wird der Verlauf der jeweiligen Zeitserie dargestellt. Die X-Achse beschreibt dabei jeweils den zeitlichen Verlauf, wobei die Y-Achse den tatsächlichen Sensor Wert abbildet. Es wird die Instanz dargestellt, dessen ID in der ersten Zeile des folgenden Codes beschrieben ist.
instance_id = 1
# Darstellung des zeitlichen Verlaufs der Umgebungsbedingungen/Sensoren einer Instanz
number_of_plots = len(operational_setting_columns_names) + len(sensor_data_columns_names)
plt.figure(figsize=(15, 3*number_of_plots))
history_dataframe_id = X_train[X_train["Instance"] == instance_id]
for i, v in enumerate(operational_setting_columns_names):
diagram = plt.subplot(number_of_plots, 1, 1 + i)
diagram.plot(history_dataframe_id.index.values,history_dataframe_id.iloc[:, 2 + i].values)
diagram.title.set_text(v)
diagram.set_xlabel("Timestep")
diagram.set_ylabel("Value")
plt.tight_layout()
for i, v in enumerate(sensor_data_columns_names):
diagram = plt.subplot(number_of_plots, 1, 4 + i)
diagram.plot(history_dataframe_id.index.values,history_dataframe_id.iloc[:, 5 + i].values)
diagram.title.set_text(v)
diagram.set_xlabel("Timestep")
diagram.set_ylabel("Value")
plt.tight_layout()
plt.show()