Vorhersage von Ausfällen von Brandschutzsystemen basierend auf Sensordaten¶
Business Understanding¶
Mit Hilfe des Inveron-Gefahrenmanagementsystems können bereits mit Sensoren ausgestattete Anlagen überwacht und kontrolliert werden, zum Beispiel von einer Kontrollstation aus. Inveron ist eine Software, die alle Sensordaten aus den Brandschutzsystemen bündelt und visualisiert. Darüber hinaus können mit Hilfe von Inveron Testalarme ausgelöst oder Alarme zurückgesetzt werden. Das Inveron-Gefahrenmanagementsystem verarbeitet bereits eine Vielzahl von Sensordaten, die in der lokalen Systemumgebung, zum Beispiel im Sicherheitszentrum eines Industrieunternehmens, verwendet werden können. Spezifische Wartungsintervalle geben Minimax-Servicetechnikern an, wann ein System gewartet werden sollte. Neben der Überwachung der eigenen Sensordaten kann Inveron auch Daten von Drittanbietern überwachen. Zum Beispiel können angeschlossene Videokameras zur Branddetektion verwendet werden. Auch Einbruchmeldeanlagen, Systeme zur Zaunüberwachung oder Torsteuerungssysteme können genutzt werden. Offene Schnittstellen (OPC, Modbus, Profibus, BAC-net) ermöglichen eine langfristige Nutzbarkeit bei Austausch einzelner Komponenten.
2.1. Import von relevanten Modulen¶
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Setting seed for reproducability
np.random.seed(9876)
PYTHONHASHSEED = 0
from sklearn import preprocessing
from sklearn.metrics import confusion_matrix, recall_score, precision_score
from keras.models import Sequential
from keras.layers import Dense, Dropout, LSTM, Activation
%matplotlib inline
2.2. Daten laden¶
#load training data
train_df = pd.read_csv('https://storage.googleapis.com/ml-service-repository-datastorage/Prediction_of_IOT_system_failures_based_on_sensor_data_PM_train.txt', sep=" ", header=None)
train_df.drop(train_df.columns[[26, 27]], axis=1, inplace=True)
train_df.columns = ['id', 'zyklus', 'setting1', 'setting2', 'setting3', 's1', 's2', 's3',
's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', 's12', 's13', 's14',
's15', 's16', 's17', 's18', 's19', 's20', 's21']
# load testdata
test_df = pd.read_csv('https://storage.googleapis.com/ml-service-repository-datastorage/Prediction_of_IOT_system_failures_based_on_sensor_data_PM_test.txt', sep=" ", header=None)
test_df.drop(test_df.columns[[26, 27]], axis=1, inplace=True)
test_df.columns = ['id', 'zyklus', 'setting1', 'setting2', 'setting3', 's1', 's2', 's3',
's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11', 's12', 's13', 's14',
's15', 's16', 's17', 's18', 's19', 's20', 's21']
# load truth data
truth_df = pd.read_csv('https://storage.googleapis.com/ml-service-repository-datastorage/Prediction_of_IOT_system_failures_based_on_sensor_data_PM_truth.txt', sep=" ", header=None)
truth_df.drop(truth_df.columns[[1]], axis=1, inplace=True)
2.4. Deskriptive Analyse¶
def attribute_description(data):
longestColumnName = len(max(np.array(data.columns), key=len))
print("| Feature | Data Type|")
print("|-----|------|")
for col in data.columns:
description = ''
col_dropna = data[col].dropna()
example = col_dropna.sample(1).values[0]
if type(example) == str:
description = 'str '
if len(col_dropna.unique()) < 10:
description += '{'
description += '; '.join([ f'"{name}"' for name in col_dropna.unique()])
description += '}'
else:
description += '[ example: "'+ example + '" ]'
elif (type(example) == np.int32) and (len(col_dropna.unique()) < 10) :
description += 'dummy int32 {'
description += '; '.join([ f'{name}' for name in sorted(col_dropna.unique())])
description += '}'
else:
try:
description = example.dtype
except:
description = type(example)
print("|" + col.ljust(longestColumnName)+ f'| {description} |')
attribute_description(train_df)
| Feature | Data Type| |-----|------| |id | int64 | |zyklus | int64 | |setting1| float64 | |setting2| float64 | |setting3| float64 | |s1 | float64 | |s2 | float64 | |s3 | float64 | |s4 | float64 | |s5 | float64 | |s6 | float64 | |s7 | float64 | |s8 | float64 | |s9 | float64 | |s10 | float64 | |s11 | float64 | |s12 | float64 | |s13 | float64 | |s14 | float64 | |s15 | float64 | |s16 | float64 | |s17 | int64 | |s18 | int64 | |s19 | float64 | |s20 | float64 | |s21 | float64 |
train_df = train_df.sort_values(['id','zyklus'])
train_df.head()
id | zyklus | setting1 | setting2 | setting3 | s1 | s2 | s3 | s4 | s5 | ... | s12 | s13 | s14 | s15 | s16 | s17 | s18 | s19 | s20 | s21 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | -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 | 2388 | 100.0 | 39.06 | 23.4190 |
1 | 1 | 2 | 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 | 2388 | 100.0 | 39.00 | 23.4236 |
2 | 1 | 3 | -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 | 2388 | 100.0 | 38.95 | 23.3442 |
3 | 1 | 4 | 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 | 2388 | 100.0 | 38.88 | 23.3739 |
4 | 1 | 5 | -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 | 2388 | 100.0 | 38.90 | 23.4044 |
5 rows × 26 columns
print(train_df.isna().sum())
id 0 zyklus 0 setting1 0 setting2 0 setting3 0 s1 0 s2 0 s3 0 s4 0 s5 0 s6 0 s7 0 s8 0 s9 0 s10 0 s11 0 s12 0 s13 0 s14 0 s15 0 s16 0 s17 0 s18 0 s19 0 s20 0 s21 0 dtype: int64
print(test_df.isna().sum())
id 0 zyklus 0 setting1 0 setting2 0 setting3 0 s1 0 s2 0 s3 0 s4 0 s5 0 s6 0 s7 0 s8 0 s9 0 s10 0 s11 0 s12 0 s13 0 s14 0 s15 0 s16 0 s17 0 s18 0 s19 0 s20 0 s21 0 dtype: int64
There are no Null values
there is no missing data
2.1 Berechnung der verbleibenden Tage bis zum Ausfall¶
Im ersten Schritt haben wir nun die Daten geladen und werden dem Trainingsdatensatz eine weitere Spalte hinzufügen, die die verbleibenden Tage bis zum Austausch des Feuermelders oder Bauteils angibt. Im Testdatensatz wird eine weitere Spalte für die binäre Unterscheidung oder Klassifikation erstellt. Es soll festgestellt werden, ob ein bestimmter Feuermelder (ID) innerhalb von w1-Zyklen ausfällt, weshalb hier ein fiktiver Wert von 1 für w1 gewählt wird (1 Tag).
remaining_days_until_F = pd.DataFrame(train_df.groupby('id')['zyklus'].max()).reset_index()
remaining_days_until_F.columns = ['id', 'max']
train_df = train_df.merge(remaining_days_until_F, on=['id'], how='left')
train_df['remaining_days_until_Failure'] = train_df['max'] - train_df['zyklus']
train_df.drop('max', axis=1, inplace=True)
train_df.head()
id | zyklus | setting1 | setting2 | setting3 | s1 | s2 | s3 | s4 | s5 | ... | s13 | s14 | s15 | s16 | s17 | s18 | s19 | s20 | s21 | remaining_days_until_Failure | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | -0.0007 | -0.0004 | 100.0 | 518.67 | 641.82 | 1589.70 | 1400.60 | 14.62 | ... | 2388.02 | 8138.62 | 8.4195 | 0.03 | 392 | 2388 | 100.0 | 39.06 | 23.4190 | 191 |
1 | 1 | 2 | 0.0019 | -0.0003 | 100.0 | 518.67 | 642.15 | 1591.82 | 1403.14 | 14.62 | ... | 2388.07 | 8131.49 | 8.4318 | 0.03 | 392 | 2388 | 100.0 | 39.00 | 23.4236 | 190 |
2 | 1 | 3 | -0.0043 | 0.0003 | 100.0 | 518.67 | 642.35 | 1587.99 | 1404.20 | 14.62 | ... | 2388.03 | 8133.23 | 8.4178 | 0.03 | 390 | 2388 | 100.0 | 38.95 | 23.3442 | 189 |
3 | 1 | 4 | 0.0007 | 0.0000 | 100.0 | 518.67 | 642.35 | 1582.79 | 1401.87 | 14.62 | ... | 2388.08 | 8133.83 | 8.3682 | 0.03 | 392 | 2388 | 100.0 | 38.88 | 23.3739 | 188 |
4 | 1 | 5 | -0.0019 | -0.0002 | 100.0 | 518.67 | 642.37 | 1582.85 | 1406.22 | 14.62 | ... | 2388.04 | 8133.80 | 8.4294 | 0.03 | 393 | 2388 | 100.0 | 38.90 | 23.4044 | 187 |
5 rows × 27 columns
w1 = 20
w0 = 10
train_df['label1'] = np.where(train_df['remaining_days_until_Failure'] <= w1, 1, 0 )
train_df['label2'] = train_df['label1']
train_df.loc[train_df['remaining_days_until_Failure'] <= w0, 'label2'] = 2
train_df.head()
id | zyklus | setting1 | setting2 | setting3 | s1 | s2 | s3 | s4 | s5 | ... | s15 | s16 | s17 | s18 | s19 | s20 | s21 | remaining_days_until_Failure | label1 | label2 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | -0.0007 | -0.0004 | 100.0 | 518.67 | 641.82 | 1589.70 | 1400.60 | 14.62 | ... | 8.4195 | 0.03 | 392 | 2388 | 100.0 | 39.06 | 23.4190 | 191 | 0 | 0 |
1 | 1 | 2 | 0.0019 | -0.0003 | 100.0 | 518.67 | 642.15 | 1591.82 | 1403.14 | 14.62 | ... | 8.4318 | 0.03 | 392 | 2388 | 100.0 | 39.00 | 23.4236 | 190 | 0 | 0 |
2 | 1 | 3 | -0.0043 | 0.0003 | 100.0 | 518.67 | 642.35 | 1587.99 | 1404.20 | 14.62 | ... | 8.4178 | 0.03 | 390 | 2388 | 100.0 | 38.95 | 23.3442 | 189 | 0 | 0 |
3 | 1 | 4 | 0.0007 | 0.0000 | 100.0 | 518.67 | 642.35 | 1582.79 | 1401.87 | 14.62 | ... | 8.3682 | 0.03 | 392 | 2388 | 100.0 | 38.88 | 23.3739 | 188 | 0 | 0 |
4 | 1 | 5 | -0.0019 | -0.0002 | 100.0 | 518.67 | 642.37 | 1582.85 | 1406.22 | 14.62 | ... | 8.4294 | 0.03 | 393 | 2388 | 100.0 | 38.90 | 23.4044 | 187 | 0 | 0 |
5 rows × 29 columns
Die Spalte "cycle" soll mittels einer Min-Max-Normalisierung normalisiert werden, weshalb eine Spalte mit den normalisierten Werten erstellt wird.
train_df['Zyklus normalisiert'] = train_df['zyklus']
cols_normalize = train_df.columns.difference(['id','zyklus','remaining_days_until_Failure','label1','label2'])
minmax = preprocessing.MinMaxScaler()
norm_train_df = pd.DataFrame(minmax.fit_transform(train_df[cols_normalize]),
columns=cols_normalize,
index=train_df.index)
join_df = train_df[train_df.columns.difference(cols_normalize)].join(norm_train_df)
train_df = join_df.reindex(columns = train_df.columns)
train_df.head()
id | zyklus | setting1 | setting2 | setting3 | s1 | s2 | s3 | s4 | s5 | ... | s16 | s17 | s18 | s19 | s20 | s21 | remaining_days_until_Failure | label1 | label2 | Zyklus normalisiert | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 0.459770 | 0.166667 | 0.0 | 0.0 | 0.183735 | 0.406802 | 0.309757 | 0.0 | ... | 0.0 | 0.333333 | 0.0 | 0.0 | 0.713178 | 0.724662 | 191 | 0 | 0 | 0.00000 |
1 | 1 | 2 | 0.609195 | 0.250000 | 0.0 | 0.0 | 0.283133 | 0.453019 | 0.352633 | 0.0 | ... | 0.0 | 0.333333 | 0.0 | 0.0 | 0.666667 | 0.731014 | 190 | 0 | 0 | 0.00277 |
2 | 1 | 3 | 0.252874 | 0.750000 | 0.0 | 0.0 | 0.343373 | 0.369523 | 0.370527 | 0.0 | ... | 0.0 | 0.166667 | 0.0 | 0.0 | 0.627907 | 0.621375 | 189 | 0 | 0 | 0.00554 |
3 | 1 | 4 | 0.540230 | 0.500000 | 0.0 | 0.0 | 0.343373 | 0.256159 | 0.331195 | 0.0 | ... | 0.0 | 0.333333 | 0.0 | 0.0 | 0.573643 | 0.662386 | 188 | 0 | 0 | 0.00831 |
4 | 1 | 5 | 0.390805 | 0.333333 | 0.0 | 0.0 | 0.349398 | 0.257467 | 0.404625 | 0.0 | ... | 0.0 | 0.416667 | 0.0 | 0.0 | 0.589147 | 0.704502 | 187 | 0 | 0 | 0.01108 |
5 rows × 30 columns
Da nun eine Normalisierung der Trainingsdaten stattgefunden hat, wird für die Testdaten ebenfalls eine Normalisierung der Zyklen durchgeführt.
test_df['Zyklus normalisiert'] = test_df['zyklus']
norm_test_df = pd.DataFrame(minmax.transform(test_df[cols_normalize]),
columns=cols_normalize,
index=test_df.index)
test_join_df = test_df[test_df.columns.difference(cols_normalize)].join(norm_test_df)
test_df = test_join_df.reindex(columns = test_df.columns)
test_df = test_df.reset_index(drop=True)
test_df.head()
id | zyklus | setting1 | setting2 | setting3 | s1 | s2 | s3 | s4 | s5 | ... | s13 | s14 | s15 | s16 | s17 | s18 | s19 | s20 | s21 | Zyklus normalisiert | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 0.632184 | 0.750000 | 0.0 | 0.0 | 0.545181 | 0.310661 | 0.269413 | 0.0 | ... | 0.220588 | 0.132160 | 0.308965 | 0.0 | 0.333333 | 0.0 | 0.0 | 0.558140 | 0.661834 | 0.00000 |
1 | 1 | 2 | 0.344828 | 0.250000 | 0.0 | 0.0 | 0.150602 | 0.379551 | 0.222316 | 0.0 | ... | 0.264706 | 0.204768 | 0.213159 | 0.0 | 0.416667 | 0.0 | 0.0 | 0.682171 | 0.686827 | 0.00277 |
2 | 1 | 3 | 0.517241 | 0.583333 | 0.0 | 0.0 | 0.376506 | 0.346632 | 0.322248 | 0.0 | ... | 0.220588 | 0.155640 | 0.458638 | 0.0 | 0.416667 | 0.0 | 0.0 | 0.728682 | 0.721348 | 0.00554 |
3 | 1 | 4 | 0.741379 | 0.500000 | 0.0 | 0.0 | 0.370482 | 0.285154 | 0.408001 | 0.0 | ... | 0.250000 | 0.170090 | 0.257022 | 0.0 | 0.250000 | 0.0 | 0.0 | 0.666667 | 0.662110 | 0.00831 |
4 | 1 | 5 | 0.580460 | 0.500000 | 0.0 | 0.0 | 0.391566 | 0.352082 | 0.332039 | 0.0 | ... | 0.220588 | 0.152751 | 0.300885 | 0.0 | 0.166667 | 0.0 | 0.0 | 0.658915 | 0.716377 | 0.01108 |
5 rows × 27 columns
Als nächstes verwenden wir die Wahrheitsdaten, um die Labels für die Testdaten zu generieren.
verbleibende_zyklen = pd.DataFrame(test_df.groupby('id')['zyklus'].max()).reset_index()
verbleibende_zyklen.columns = ['id', 'max']
truth_df.columns = ['more']
truth_df['id'] = truth_df.index + 1
truth_df['max'] = verbleibende_zyklen['max'] + truth_df['more']
truth_df.drop('more', axis=1, inplace=True)
test_df = test_df.merge(truth_df, on=['id'], how='left')
test_df['remaining_days_until_Failure'] = test_df['max'] - test_df['zyklus']
test_df.drop('max', axis=1, inplace=True)
test_df.head()
id | zyklus | setting1 | setting2 | setting3 | s1 | s2 | s3 | s4 | s5 | ... | s14 | s15 | s16 | s17 | s18 | s19 | s20 | s21 | Zyklus normalisiert | remaining_days_until_Failure | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 0.632184 | 0.750000 | 0.0 | 0.0 | 0.545181 | 0.310661 | 0.269413 | 0.0 | ... | 0.132160 | 0.308965 | 0.0 | 0.333333 | 0.0 | 0.0 | 0.558140 | 0.661834 | 0.00000 | 142 |
1 | 1 | 2 | 0.344828 | 0.250000 | 0.0 | 0.0 | 0.150602 | 0.379551 | 0.222316 | 0.0 | ... | 0.204768 | 0.213159 | 0.0 | 0.416667 | 0.0 | 0.0 | 0.682171 | 0.686827 | 0.00277 | 141 |
2 | 1 | 3 | 0.517241 | 0.583333 | 0.0 | 0.0 | 0.376506 | 0.346632 | 0.322248 | 0.0 | ... | 0.155640 | 0.458638 | 0.0 | 0.416667 | 0.0 | 0.0 | 0.728682 | 0.721348 | 0.00554 | 140 |
3 | 1 | 4 | 0.741379 | 0.500000 | 0.0 | 0.0 | 0.370482 | 0.285154 | 0.408001 | 0.0 | ... | 0.170090 | 0.257022 | 0.0 | 0.250000 | 0.0 | 0.0 | 0.666667 | 0.662110 | 0.00831 | 139 |
4 | 1 | 5 | 0.580460 | 0.500000 | 0.0 | 0.0 | 0.391566 | 0.352082 | 0.332039 | 0.0 | ... | 0.152751 | 0.300885 | 0.0 | 0.166667 | 0.0 | 0.0 | 0.658915 | 0.716377 | 0.01108 | 138 |
5 rows × 28 columns
Als nächstes verwenden wir die Wahrheitsdaten, um die Labels für die Testdaten zu generieren.
test_df['label1'] = np.where(test_df['remaining_days_until_Failure'] <= w1, 1, 0 )
test_df['label2'] = test_df['label1']
test_df.loc[test_df['remaining_days_until_Failure'] <= w0, 'label2'] = 2
test_df.head()
id | zyklus | setting1 | setting2 | setting3 | s1 | s2 | s3 | s4 | s5 | ... | s16 | s17 | s18 | s19 | s20 | s21 | Zyklus normalisiert | remaining_days_until_Failure | label1 | label2 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 1 | 0.632184 | 0.750000 | 0.0 | 0.0 | 0.545181 | 0.310661 | 0.269413 | 0.0 | ... | 0.0 | 0.333333 | 0.0 | 0.0 | 0.558140 | 0.661834 | 0.00000 | 142 | 0 | 0 |
1 | 1 | 2 | 0.344828 | 0.250000 | 0.0 | 0.0 | 0.150602 | 0.379551 | 0.222316 | 0.0 | ... | 0.0 | 0.416667 | 0.0 | 0.0 | 0.682171 | 0.686827 | 0.00277 | 141 | 0 | 0 |
2 | 1 | 3 | 0.517241 | 0.583333 | 0.0 | 0.0 | 0.376506 | 0.346632 | 0.322248 | 0.0 | ... | 0.0 | 0.416667 | 0.0 | 0.0 | 0.728682 | 0.721348 | 0.00554 | 140 | 0 | 0 |
3 | 1 | 4 | 0.741379 | 0.500000 | 0.0 | 0.0 | 0.370482 | 0.285154 | 0.408001 | 0.0 | ... | 0.0 | 0.250000 | 0.0 | 0.0 | 0.666667 | 0.662110 | 0.00831 | 139 | 0 | 0 |
4 | 1 | 5 | 0.580460 | 0.500000 | 0.0 | 0.0 | 0.391566 | 0.352082 | 0.332039 | 0.0 | ... | 0.0 | 0.166667 | 0.0 | 0.0 | 0.658915 | 0.716377 | 0.01108 | 138 | 0 | 0 |
5 rows × 30 columns
Jetzt schauen wir uns die Datenstruktur an.
train_df.info()
<class 'pandas.core.frame.DataFrame'> Int64Index: 20631 entries, 0 to 20630 Data columns (total 30 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 20631 non-null int64 1 zyklus 20631 non-null int64 2 setting1 20631 non-null float64 3 setting2 20631 non-null float64 4 setting3 20631 non-null float64 5 s1 20631 non-null float64 6 s2 20631 non-null float64 7 s3 20631 non-null float64 8 s4 20631 non-null float64 9 s5 20631 non-null float64 10 s6 20631 non-null float64 11 s7 20631 non-null float64 12 s8 20631 non-null float64 13 s9 20631 non-null float64 14 s10 20631 non-null float64 15 s11 20631 non-null float64 16 s12 20631 non-null float64 17 s13 20631 non-null float64 18 s14 20631 non-null float64 19 s15 20631 non-null float64 20 s16 20631 non-null float64 21 s17 20631 non-null float64 22 s18 20631 non-null float64 23 s19 20631 non-null float64 24 s20 20631 non-null float64 25 s21 20631 non-null float64 26 remaining_days_until_Failure 20631 non-null int64 27 label1 20631 non-null int32 28 label2 20631 non-null int32 29 Zyklus normalisiert 20631 non-null float64 dtypes: float64(25), int32(2), int64(3) memory usage: 5.2 MB
Es ist zu sehen, dass alle Daten entweder den Datentyp float oder int haben, es gibt keine kategorialen Variablen im Datensatz. Nun zeigen wir die einzelnen Variablen in einem Histogramm an, um festzustellen, ob alle Variablen sinnvoll für das Modell verwendet werden können. Die gerade erstellten Spalten sowie "id" werden nicht berücksichtigt, da diese für die Auswertung verwendet werden und es keinen Sinn macht, sie hier zu überprüfen.
3.3 Analyse der Histogramme aller Merkmale¶
def plot_hist(variable):
print("min {} : {} ".format(variable, min(train_df[variable])))
print("max {} : {}".format(variable, max(train_df[variable])))
plt.figure(figsize=(9,3))
plt.hist(train_df[variable], color="orange", ec="orange", edgecolor='green')
plt.xlabel(variable)
plt.ylabel("Frequenz")
plt.title("distribution of the {} variable ".format(variable))
plt.show()
numericVar = ["zyklus", "setting1", "setting2","setting3", "s1", "s2", "s3",
"s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "s12",
"s13", "s14", "s15", "s16", "s17", "s18", "s19", "s20", "s21"]
for n in numericVar:
plot_hist(n)
min zyklus : 1 max zyklus : 362
min setting1 : 0.0 max setting1 : 1.0
min setting2 : 0.0 max setting2 : 1.0
min setting3 : 0.0 max setting3 : 0.0
min s1 : 0.0 max s1 : 0.0
min s2 : 0.0 max s2 : 1.0
min s3 : 0.0 max s3 : 1.0
min s4 : 0.0 max s4 : 1.0
min s5 : 0.0 max s5 : 0.0
min s6 : 0.0 max s6 : 1.0
min s7 : 0.0 max s7 : 1.0
min s8 : 0.0 max s8 : 1.0
min s9 : 0.0 max s9 : 1.0
min s10 : 0.0 max s10 : 0.0
min s11 : 0.0 max s11 : 1.0
min s12 : 0.0 max s12 : 1.0
min s13 : 0.0 max s13 : 1.0
min s14 : 0.0 max s14 : 1.0
min s15 : 0.0 max s15 : 1.0
min s16 : 0.0 max s16 : 0.0
min s17 : 0.0 max s17 : 1.0
min s18 : 0.0 max s18 : 0.0
min s19 : 0.0 max s19 : 0.0
min s20 : 0.0 max s20 : 1.0
min s21 : 0.0 max s21 : 1.0
Wir sehen, dass einige Variablen einfach konstante Werte haben. Diese können gelöscht werden, da sie keine nützlichen Informationen über den Zustand der Anlage enthalten. Die folgenden Spalten werden gelöscht: 'setting3', 's1', 's5', 's10', 's16', 's18', 's19'.
train_df.drop(columns=['setting3', 's1', 's5', 's10', 's16', 's18', 's19'],inplace=True)
test_df.drop(columns=['setting3', 's1', 's5', 's10', 's16', 's18', 's19'],inplace=True)
train_df2 = train_df
test_df2 = test_df
4. Modellierung und Evaluation¶
Nachdem die Daten vorbereitet wurden, kann die Modellierung beginnen. Wir werden ein Long Short-Term Memory (LSTM) Layer Recurrent Neural Network (RNN) mit der Keras-Bibliothek aufbauen. Keras erfordert ein dreidimensionales numpy-Array (Eingaben: Ein 3D-Tensor mit der Form [Batch, Zeitpunkte, Merkmale]), siehe auch: https://keras.io/api/layers/recurrent_layers/lstm/ ). Daher werden in einem nächsten Schritt unsere Merkmale oder Variablen in diese dreidimensionale Form gebracht.
Gleichzeitig wird die Fenstergröße definiert, da LSTMs den Vorteil haben, Dinge aus langen Sequenzen ohne direkte Abstraktion zu merken.
window_size = 25
def generate_sequenze(dataframe_id, laenge_sequenz, spalten_sequenz):
array = dataframe_id[spalten_sequenz].values
anzahl_elemente = array.shape[0]
for start, stop in zip(range(0, anzahl_elemente-laenge_sequenz), range(laenge_sequenz, anzahl_elemente)):
yield array[start:stop, :]
spalten_sensoren = ['s2', 's3','s4', 's6', 's7', 's8', 's9', 's11', 's12', 's13', 's14', 's15', 's17', 's20', 's21',]
spalten_sequenz = ['setting1', 'setting2', 'Zyklus normalisiert']
spalten_sequenz.extend(spalten_sensoren)
generierung_sequenz = (list(generate_sequenze(train_df2[train_df2['id']==id], window_size, spalten_sequenz))
for id in train_df2['id'].unique())
array_sequenz = np.concatenate(list(generierung_sequenz)).astype(np.float32)
array_sequenz.shape
(18131, 25, 18)
def ueberschrift_generieren(dataframe_id, laenge_sequenz, ueberschrift):
array_daten = dataframe_id[ueberschrift].values
elemente_numerisch = array_daten.shape[0]
return array_daten[laenge_sequenz:elemente_numerisch, :]
ueberschriften_generieren = [ueberschrift_generieren(train_df[train_df['id']==id], window_size, ['label1'])
for id in train_df2['id'].unique()]
ueb_array = np.concatenate(ueberschriften_generieren).astype(np.float32)
ueb_array.shape
(18131, 1)
Im nächsten Schritt wird ein LSTM-Netzwerk erstellt, da die Daten jetzt in dreidimensionaler Form vorliegen. Dafür wird das Netzwerk mit 100 Einheiten im ersten Schritt und einem zweiten mit 50 Einheiten erstellt. Dropout wird verwendet, um Overfitting zu vermeiden. Schließlich wird eine einzelne Schicht, unsere Ausgabeschicht mit einer einzigen Einheit und der Sigma-Aktivierung, generiert, da hier ein binäres Klassifikationsproblem vorliegt.
spalten = array_sequenz.shape[2]
ausgabe = ueb_array.shape[1]
model = Sequential()
model.add(LSTM(
input_shape=(window_size, spalten),
units=200,
return_sequences=True))
model.add(Dropout(0.2))
model.add(LSTM(
units=100,
return_sequences=False))
model.add(Dropout(0.2))
model.add(Dense(units=ausgabe, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
print(model.summary())
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= lstm (LSTM) (None, 25, 200) 175200 _________________________________________________________________ dropout (Dropout) (None, 25, 200) 0 _________________________________________________________________ lstm_1 (LSTM) (None, 100) 120400 _________________________________________________________________ dropout_1 (Dropout) (None, 100) 0 _________________________________________________________________ dense (Dense) (None, 1) 101 ================================================================= Total params: 295,701 Trainable params: 295,701 Non-trainable params: 0 _________________________________________________________________ None
model.fit(array_sequenz, ueb_array, epochs=10, batch_size=200, validation_split=0.05, verbose=1)
Epoch 1/10 87/87 [==============================] - 17s 151ms/step - loss: 0.2726 - accuracy: 0.8735 - val_loss: 0.1108 - val_accuracy: 0.9592 Epoch 2/10 87/87 [==============================] - 12s 136ms/step - loss: 0.0972 - accuracy: 0.9615 - val_loss: 0.0681 - val_accuracy: 0.9625 Epoch 3/10 87/87 [==============================] - 12s 144ms/step - loss: 0.0629 - accuracy: 0.9746 - val_loss: 0.1099 - val_accuracy: 0.9570 Epoch 4/10 87/87 [==============================] - 12s 132ms/step - loss: 0.0610 - accuracy: 0.9746 - val_loss: 0.0787 - val_accuracy: 0.9647 Epoch 5/10 87/87 [==============================] - 11s 130ms/step - loss: 0.0585 - accuracy: 0.9763 - val_loss: 0.0731 - val_accuracy: 0.9614 Epoch 6/10 87/87 [==============================] - 11s 130ms/step - loss: 0.0539 - accuracy: 0.9774 - val_loss: 0.0571 - val_accuracy: 0.9757 Epoch 7/10 87/87 [==============================] - 11s 126ms/step - loss: 0.0547 - accuracy: 0.9774 - val_loss: 0.0520 - val_accuracy: 0.9757 Epoch 8/10 87/87 [==============================] - 11s 122ms/step - loss: 0.0498 - accuracy: 0.9791 - val_loss: 0.0562 - val_accuracy: 0.9735 Epoch 9/10 87/87 [==============================] - 10s 118ms/step - loss: 0.0462 - accuracy: 0.9813 - val_loss: 0.0450 - val_accuracy: 0.9802 Epoch 10/10 87/87 [==============================] - 10s 114ms/step - loss: 0.0452 - accuracy: 0.9811 - val_loss: 0.0322 - val_accuracy: 0.9879
<tensorflow.python.keras.callbacks.History at 0x24033da29d0>
4.2. Evaluation¶
scores = model.evaluate(array_sequenz, ueb_array, verbose=1, batch_size=200)
print('accuracy: {}'.format(scores[1]))
91/91 [==============================] - 3s 35ms/step - loss: 0.0519 - accuracy: 0.9782 accuracy: 0.9782140851020813
y_pred = model.predict(array_sequenz,verbose=1, batch_size=200)
y_pred = np.round(y_pred).astype(int)
y_true = ueb_array
print('confusion Matrix')
konfusionsmatrix = confusion_matrix(y_true, y_pred)
konfusionsmatrix
91/91 [==============================] - 3s 30ms/step confusion Matrix
array([[15698, 333], [ 62, 2038]], dtype=int64)
accuracy = precision_score(y_true, y_pred)
recall = recall_score(y_true, y_pred)
print( 'accuracy = ', accuracy, '\n', 'recall = ', recall)
accuracy = 0.859552931252636 recall = 0.9704761904761905
Im nächsten Schritt werden wir die Testdaten vergleichen. Dafür haben wir den letzten funktionierenden Zustand des Zyklus für jeden Feuermelder in den Testdaten gespeichert. Um die Ergebnisse vergleichen zu können, verwenden wir die letzte Sequenz für jeden Feuermelder in den Testdaten.
testdaten_vergleich = [test_df[test_df['id']==id][spalten_sequenz].values[ - window_size:]
for id in test_df['id'].unique() if len(test_df[test_df['id']==id]) >= window_size]
testdaten_vergleich = np.asarray(testdaten_vergleich).astype(np.float32)
testdaten_vergleich.shape
(100, 25, 18)
y2 = [len(test_df[test_df['id']==id]) >= window_size for id in test_df['id'].unique()]
ueb_array_test_last = test_df.groupby('id')['label1'].nth(-1)[y2].values
ueb_array_test_last = ueb_array_test_last.reshape(ueb_array_test_last.shape[0],1).astype(np.float32)
ueb_array_test_last.shape
(100, 1)
print(testdaten_vergleich.shape)
print(ueb_array_test_last.shape)
(100, 25, 18) (100, 1)
werte_testdaten = model.evaluate(testdaten_vergleich, ueb_array_test_last, verbose=2)
print('accuracy: {}'.format(werte_testdaten[1]))
4/4 - 0s - loss: 0.1298 - accuracy: 0.9400 accuracy: 0.9399999976158142
testdaten_y = model.predict(testdaten_vergleich)
testdaten_y = np.round(testdaten_y).astype(int)
wahrheit_y = ueb_array_test_last
print('confusion Matrix')
konfusionsmatrix2 = confusion_matrix(wahrheit_y, testdaten_y)
konfusionsmatrix2
confusion Matrix
array([[80, 4], [ 2, 14]], dtype=int64)
testdaten_genauigkeit = precision_score(wahrheit_y, testdaten_y)
recall_test = recall_score(wahrheit_y, testdaten_y)
f1_test = 2 * (testdaten_genauigkeit * recall_test) / (testdaten_genauigkeit + recall_test)
print( 'Precision: ', testdaten_genauigkeit, '\n', 'recall: ', recall_test,'\n', 'F1-score:', f1_test )
Precision: 0.7777777777777778 recall: 0.875 F1-score: 0.823529411764706
results_df = pd.DataFrame([[werte_testdaten[1],testdaten_genauigkeit,recall_test,f1_test]],
columns = ['Accuracy', 'Precision', 'Recall', 'F1-score'],
index = ['LSTM'])
results_df
Accuracy | Precision | Recall | F1-score | |
---|---|---|---|---|
LSTM | 0.94 | 0.777778 | 0.875 | 0.823529 |