›  Demo-PY5: Machine Learning-Modellierung mit Keras und Tensorflow

Dieser Demonstrator des elab2go zeigt die Erstellung und Verwendung eines Künstlichen Neuronalen Netzwerks mit Hilfe der Python-Bibliotheken Keras und Tensorflow. Nachdem in Demo-PY1: Python-Tutorial der erste Einstieg in die Python-Syntax erfolgt ist, und in Demo-PY2: Datenverwaltung mit Pandas die Datenverwaltung und - visualisierung mit Hilfe der Python-Bibliothek Pandas am Beispiel eines Stromverbrauch-Datensatzes aus der Open-Power-System-Data (OPSD)-Plattform durchgeführt wurde, zeigt Demo-PY5 im nächsten Schritt, wie ein Künstliches Neuronales Netz mit mehreren Long Short-Term Memory (LSTM)-Schichten trainiert, validiert und damit eine Prognose über die zukünftige Entwicklung der Stromverbrauchsdaten erstellt wird. Die Daten werden in der interaktiven, webbasierten Anwendungsumgebung Jupyter Notebook analysiert.

info-icon Motivation

Machine Learning-Algorithmen haben im weitesten Sinne das Ziel, aus Input-Daten sinnvolle Zusammenhänge zu erkennen und daraus Regeln abzuleiten. Z.B. Vorhersagen treffen, Trends erkennen, Daten nach bestimmten Kriterien gruppieren. Maschinelles Lernen hat seit seinen Anfängen um 1950 mehrere Auf-und-ab-Phasen erlebt. Klassisches datenzentrisches Maschinelles Lernen ist seit 1990 eine Disziplin der Datenanalysten und Statistiker. Seit ca. 2010 haben Künstliche Neuronale Netze und insbesondere Deep Learning die Forschung beflügelt und zu einem neuen Hype geführt.

Warum Künstliche Neuronale Netzwerke?

Künstliche Neuronale Netzwerke sind besonders gut geeignet, um komplexe und nichtlineare Zusammenhänge in Daten zu lernen. Sie sind gut skalierbar und können für besonders große Datensätze eingesetzt werden. Zur Prognose einer Zeitreihe legt man ein gleitendes Zeitfenster mit n Werten der Vergangenheit über die Zeitreihe. Die Trainingsaufgabe besteht darin, aus den bekannten Werten deren Zukunft zu schätzen, d.h. aus n Werten in der Eingabe-Schicht auf den nächsten Wert zu schließen.

... mit Keras und Tensorflow?

Die aktuell meistgenutzten Frameworks für die Entwicklung von Künstlichen Neuronalen Netzwerken und Deep-Learning-Modellen mit Python sind Tensorflow und Theano. Tensorflow ist ein von Google entwickeltes Framework für maschinelles Lernen und künstliche Intelligenz, das unter Open-Source-Lizenz verfügbar ist. Theano ist wie Tensorflow ein Open-Source-Projekt für die Erstellung künstlicher neuronaler Netzwerke, und wurde an der Universität von Montreal (Kanada) entwickelt. Keras ist eine Open-Source-Python- Bibliothek zum Entwickeln und Bewerten von Machine Learning-Modellen, die als benutzerfreundliche Schnittstelle zu den Frameworks Tensorflow und Theano verwendet wird.

... und Jupyter Notebook?

Jupyter-Notebook ist eine webbasierte Umgebung, die das Erstellen, Dokumentieren und Teilen von Demonstratoren unterstützt, und zwar insbesondere im Umfeld der Datenanalyse. In einem Jupyter Notebook kann man Code schreiben und ausführen, Daten visualisieren, und diesen Code auch mit anderen teilen. Das Besondere an Jupyter Notebook ist, dass der Code und die Beschreibung des Codes in unabhängige Zellen geschrieben werden, so dass einzelne Codeblöcke individuell ausgeführt werden können.

... und Matplotlib (und Seaborn)?

Matplotlib ist eine Python-Programmbibliothek für die graphische Darstellung mathematischer Funktionen und vom Funktionsumfang her vergleichbar mit MATLABs Plotting-Funktionen.

Seaborn ist eine Matplotlib-Erweiterung, die speziell auf Pandas Dataframes abgestimmt ist. Mit Hilfe der seaborn-Funktionen können graphische Darstellungen verschönert und professioneller gestaltet werden, z.B. kann man verschiedene Farbschemen auswählen.

info-iconÜbersicht

Demo-PY5 ist in zehn Abschnitte gegliedert. Zunächst wird der OPSD-Datensatz beschrieben und die Fragestellung erläutert, die wir mit unserer Datenanalyse beantworten wollen. Im dritten Abschnitt werden grundlegende Konzepte Künstlicher Neuronaler Netzwerke vorgestellt. Danach wird die Erstellung des Jupyter Notebooks und Verwendung der benötigten Bibliotheken erläutert. Anschließend erfolgt die Vorbereitung der Daten (Skalierung der Daten, Aufteilung in Trainings- und Testdaten, Transformation der Zeitreihe in das vom überwachten Lernen erforderte Datenformat), die Erstellung des Neuronalen Netzwerks, das Trainieren und Validieren des Neuronalen Netzwerks.

  1. Der OPSD-Datensatz

  2. Die Fragestellung

  3. Was ist ein Neuronales Netz?

  4. Jupyter Notebook erstellen

  5. Bibliotheken importieren

  6. Datenvorbereitung

  7. Modell erstellen und trainieren

  8. Modell validieren

  9. Prognose für 2018

  10. Zusammenfassung und Ausblick

Der OPSD-Datensatz

Open Power System Data ist eine offene Plattform für Energieforscher, die europaweit gesammelte Energiedaten in Form von csv-Dateien und sqlite-Datenbanken zur Verfügung stellt. Die Daten können kostenlos heruntergeladen und genutzt werden. Die Plattform wird von einem Konsortium der Europa-Universität Flensburg, TU Berlin, DIW Berlin und Neon Neue Energieökonomik betrieben, mit dem Ziel, die Energieforschung zu unterstützen. Die Stromverbrauchs-Rohdaten werden auf der OPSD-Plattform unter dem Reiter Time Series zum Download angeboten, in unterschiedlichen Formaten und Auflösungen: time_series.xlsx, time_series_15min_singleindex.csv, time_series_30min_singleindex.csv, time_series_60min_singleindex.csv.

Für Demo-PY5 verwenden wir als Datenbasis einen aggregierten OPSD-Zeitreihen-Datensatz zum Stromverbrauch in Deutschland, der den Datenzeitraum: 01.01.2014 - 31.12.2017 mit einer täglichen Auflösung abbildet. Wie in Demo-PY2: Aufbereitung des OPSD-Datensatzes beschrieben, wird die Datei mit den Rohdaten time_series_60min.csv in ein DataFrame eingelesen, die relevanten Spalten werden extrahiert, und die Zeitskala wird durch ein Resampling geändert, so dass Daten mit täglicher Auflösung erhalten und in eine neue CSV-Datei mit dem Namen opsd_2014-2017.csv exportiert werden.


Die Fragestellung

Ziel ist, auf Basis der Vergangenheitsdaten für die Jahre 2014 bis 2017 den Stromverbrauch für das Jahr 2018 vorherzusagen. Dafür wird zunächst als Vorhersagemodell ein Künstlichen Neuronalen Netz erstellt. Entsprechend der Vorhergehensweise des überwachten Lernens wird der Datensatz in Trainings- und Testdaten aufgeteilt, das Modell wird mit Hilfe der Trainingsdaten trainiert und mit Hilfe der Testdaten validiert. Als Performancemaß für die Güte der Prognose verwenden wir die mittlere Fehlerquadratsumme (engl. Mean Square Error, MSE) bzw. die Wurzel der mittleren Fehlerquadratsumme (engl. Root Mean Square Error, RMSE).

Was ist ein Neuronales Netz?

Künstliche Neuronale Netze sind Machine Learning-Algorithmen, die den neuronalen Prozessen des Gehirns nachempfunden sind und für Klassifikations-, Prognose- und Optimierungsaufgaben verwendet werden. Ein Künstliches Neuronales Netz besteht aus Neuronen, d.h. Knoten bzw. Verarbeitungseinheiten, die durch gerichtete und gewichtete Kanten verbunden und in Schichten angeordnet sind (Eingabeschicht, versteckte Schichten und Ausgabeschicht). Eine Kante von Neuron i zu Neuron j hat das Gewicht wi,j, die die Stärke der Verbindung zwischen den Neuronen angibt. Die Eingabedaten werden innerhalb des Netzes von den Neuronen über Aktivierungsfunktionen verarbeitet und das Ergebnis wird bei Überschreiten eines Schwellwertes an die Neuronen der nächsten Schicht weitergegeben.

Neuron: Funktionsweise:

Struktur eines Neurons
Modell eines Neurons

Das vereinfachte Modell eines künstlichen Neurons nach McCulloch und Pitts (1943) besteht aus einer Verarbeitungseinheit mit gewichteten Eingaben und einer Ausgabe, die durch das Zusammenschalten zweier Funktionen (Übertragungsfunktion und Aktivierungsfunktion) berechnet wird. Ein Neuron j berechnet mit Hilfe der Übertragungsfunktion zunächst die gewichtete Summe seiner Eingaben und wendet darauf im nächsten Schritt die Aktivierungsfunktion an, um seine Ausgabe zu berechnen.

Falls die Ausgabe einen festgelegten Schwellwert überschreitet, wird sie an die Neuronen der nächsten Schicht propagiert.

Die Aktivierungsfunktion ist meist eine monoton steigende und differenzierbare Funktion, die (stückweise) linear oder nichtlinear sein kann. Nichtlineare Aktivierungsfunktionen haben den Vorteil, dass mit ihrer Hilfe nichtlineare Abhängigkeiten zwischen den Ein- und Ausgaben des Neuronalen Netzwerks erlernt werden können. Häufig verwendete Aktivierungsfunktionen sind die sigmoid-, die tanh- und die relu- Funktion. Wegen dem Problem des verschwindenen Gradienten können sigmoid und tanh für Netzwerke mit vielen Schichten schlecht eingesetzt werden. In diesem Fall sind stückweise lineare Aktivierungsfunktionen wie die relu-Funktion das Mittel der Wahl.

Die Trainingsphase eines neuronalen Netzwerkes besteht darin, die Gewichte zu erlernen, die zu den vorgegebenen Trainingsdaten passen. Der Trainings-Algorithmus wird auf die Lösung eines Optimierungsproblems zurückgeführt, bei dem das Minimum der Kostenfunktion (des Fehlers der Ausgabeschicht) bestimmt werden muss. Da die einzelnen Aktivierungsfunktionen, die in die Bildung der Kostenfunktion eingehen, differenzierbar sind, kann das Optimierungsproblem mit Gradientenabstiegsverfahren gelöst werden.

Abhängig von der Topologie der Neuronalen Netze (d.h. Anordnung der Schichten) unterscheidet man

Jupyter Notebook erstellen

Wir verwenden die Programmiersprache Python, sowie die Entwicklungs- und Paketverwaltungsplattformen Anaconda, Spyder und Jupyter Notebook, die umfangreiche Funktionalität für Paketverwaltung, Softwareentwicklung und Präsentation bereitstellen. Die Details der Verwendung von Anaconda und Jupyter Notebook sind in Demo-PY1 Installation von Python und Anaconda und Jupyter Notebook verwenden beschrieben.

Für die Datenvorbereitung und Erstellung des Prognosemodells wird ein Jupyter Notebook erstellt. In Anaconda wird zunächst eine neue Umgebung (engl. Environment) mit dem Namen KNN-Demos angelegt, in die die benötigten Programmpakete installiert werden: keras, tensorflow, scikit-learn, matplotlib, seaborn, graphviz, pydot.
Die Installation erfolgt über die Befehlskonsole (in Anaconda > KNN-Demos: Menüpunkt "Open Terminal") mit Hilfe der Paketverwaltungsprogramme pip oder conda: mit Hilfe des Befehls pip install keras wird z.B. das Programmpaket keras installiert, mit Hilfe des Befehls pip install tensorflow das Paket tensorflow, etc.

In dem angewählten Environment KNN-Demos wird dann die Jupyter Notebook-Anwendung geöffnet und mit Hilfe des Menüpunkts "New" ein neues Python3-Notizbuch mit dem Namen elab2go-Demo-PY5 angelegt.

Bibliotheken importieren

In Python kann man mit Hilfe der import-Anweisung entweder eine komplette Programmbibliothek importieren, oder nur einzelne Funktionen der Programmbibliothek (from-import-Anweisung). Beim Import werden für die jeweiligen Bibliotheken oder Funktionen Alias-Namen vergeben: für numpy vergeben wird den Alias np, für pandas vergeben wir den Alias pd.

Die Bibliotheken müssen vor Verwendung installiert sein. Falls Sie Anaconda verwenden, können Sie die erfolgreiche Installation überprüfen, indem Sie in der Liste der installierten Pakete (engl. packages) nach keras, tensorflow etc. suchen.

Python-Code Wirkung
import numpy as np
import pandas as pd
from datetime import datetime
import math
import matplotlib.pyplot as plt
import seaborn as sns
# sklearn für Überwachtes Lernen
from sklearn.preprocessing import MinMaxScaler
from sklearn.metrics import mean_squared_error
from sklearn.metrics import max_error
# keras für Neuronale Netze
from keras.models import Sequential 
from keras.layers import Dense, LSTM, Dropout 
from keras.utils import plot_model 

In der ersten Code des Jupyter Notebooks importieren wir die benötigten Programmbibliotheken: numpy, pandas, sklearn, keras und matplotlib, sowie Funktionen und Klassen aus diesen Bibliotheken. numpy wird für die Speicherung der Daten in Arrays und nützliche Funktionen benötigt, pandas für die Datenaufbereitung, keras für die Erzeugung und Verwendung der Neuronalen Netzwerke. Das sklearn-Packet metrics enthält Funktionen, mit denen man die Güte eines Vorhersagemodells bewerten kann.

Datenvorbereitung

1. Daten einlesen und visualisieren

Der OPSD-Datensatz, der in Form einer komma-getrennten csv-Datei opsd_train2014-2017.csv vorliegt, wird zunächst mit Hilfe der Pandas-Datenstrukturen und Funktionen eingelesen, aufbereitet und tabellarisch ausgegeben und visualisiert.

Für die Visualisierung von Pandas-DataFrames definieren wir eine Hilfsfunktion mit dem Namen display_dataframe(), die ein DataFrame mit der angegebenen maximalen Anzahl an Zeilen und Spalten formatiert ausgibt. Wir testen die Funktion, indem wir ein DataFrame df mit zwei Spalten und acht Zeilen erstellen und davon jedoch nur 4 Zeilen ausgeben. Die Ausgabe ist wie abgebildet: die ersten und letzten beiden Zeilen werden ausgegeben, die anderen durch Punkte angedeutet.

Python-Code Ausgabe
def display_dataframe( df, rows=6, cols=None):
    with pd.option_context( 'display.max_rows', rows,
                      'display.max_columns', cols):
        display(df);
# Teste die Funktion
d = {'s1': [1, 2, 3, 4, 5, 6, 7, 8],
     's2': [9, 8, 7, 6, 5, 4, 3, 2]}
df = pd.DataFrame (data=d)
display_dataframe (df, 4)


Danach werden die Daten mit Hilfe der Funktion read_csv() in einen DataFrame mit dem Namen opsd_df eingelesen und die Spalte Verbrauch (unsere Rohdaten für die Zeitreihe) wird in einen DataFrame series extrahiert.

Die Ausgabe der ersten und letzten 3 Zeilen des DataFrames sieht wie rechts abgebildet aus. Wir definieren in diesem Codeblock noch zwei Variablen für die spätere Verwendung: series bezeichnet die Spalte / Zeitreihe (engl. series), für die wir die Prognose erstellen wollen. anzZ bezeichnet die Anzahl Zeilen des DataFrames, die wir mit Hilfe der Funktion shape herausfinden.

Python-Code Ausgabe
opsd_df = pd.read_csv( 'opsd_2014-2017.csv')
opsd_df.set_index('Datum', inplace = True)

opsd_df.loc[:,:].fillna( method='pad', inplace = True)

opsd_df = opsd_df.astype(float).round(2)

display_dataframe(opsd_df, 6)

series = opsd_df['Verbrauch'] # Verbrauch
anzZ = opsd_df.shape[0] # Anzahl Zeilen


Die Visualisierung der Daten der Verbrauchsspalte zeigt, dass die Daten über die Jahre 2014 bis 2017 saisonale Peaks um Jahresmitte und Jahreswende und einen geringen Trend aufweisen. Wir erzeugen die grafische Darstellung hier mit Hilfe der Grafik-Funktionen der pandas-Bibliothek.

Python-Code Ausgabe
sns.set(rc={'figure.figsize':(12, 4)})
sns.set_color_codes('bright')

ax = series.plot(lineWidth=1, color='b')
ax.set_title('Täglicher Stromverbrauch')
ax.set_xlabel('Datum');
ax.set_ylabel('Verbrauch');


2. Stationarität untersuchen

In diesem Vorbereitungsschritt wird die Zeitreihe stichprobenartig auf Stationarität untersucht. Eine stationäre Zeitreihe hat zu allen Zeitpunkten den gleichen Erwartungswert und die gleiche Varianz. Stationarität ist eine wünschenswerte Eigenschaft, um ein funktionierendes Prognosemodell erstellen zu können. Falls eine Zeitreihe z.B. einen starken Trend oder Saisonalitäten hat und komplett unterschiedliches Verhalten über die Zeit hinweg hat, ist die Erstellung einer Prognose schwieriger. Zeitreihen könnnen durch diverse mathematische Transformationen stationär gemacht werden.

Stichprobenartige Auswertungen des Erwartungswertes und der Varianz für die Verbrauchsdaten ergeben ähnliche Werte (Mittelwert: ca. 1382, Standardabweichung: ca. 160) für verschiedene Bereiche der Zeitreihe.

Python-Code Ausgabe
mitte = int(anzZ/2)
opsd_1 = series.iloc[0:mitte]
opsd_2 = series.iloc[mitte+1:anzZ]
mean1 = opsd_1.mean(); std1 = opsd_1.std();
mean2 = opsd_2.mean(); std2 = opsd_2.std();
print("Erste Hälfte:")
print("Mittelw. = %.2f, Std. = %.1f" % (mean1, std1))
print("Zweite Hälfte:")
print("Mittelw. = %.2f, Std. = %.1f" % (mean2, std2))


Eine Zeitreihe, die Trends und Saisonalitäten aufweist, kann durch mathematische Umformungen in eine gleichwertige stationäre Zeitreihe umgewandelt werden. Falls z.B. ein jährlicher Peak um die Jahreswende auffällt, kann durch Differenzenbildung (von jedem Tageswert eines Jahres wird der Wert des Vorjahres abgezogen) eine neue Zeitreihe erstellt werden, die diese Saisonalität nicht aufweist. Die Prognose würde dann für die stationäre Zeitreihe durchgeführt werden. Da diese mathematischen Transformationen jedoch zusätzliche Komplexität einführen und unser Fokus hier die Erstellung des Neuronalen Netzwerkes ist, erstellen wir unser Prognosemodell auf der vorhandenen Zeitreihe und verzichten zunächst auf eine Transformation.

Die weitere Datenvorbereitung besteht darin, die aus der CSV-Datei importierten Rohdaten in eine Form zu bringen, die die Keras-Funktionen für die Erstellung eines Neuronalen Netzes als Eingabedaten akzeptieren. Da die Prognose nur für die Stromverbrauchsdaten (d.h. erste Spalte des Datensatzes) erfolgen soll, sind das unsere Rohdaten. Diese müssen auf den Wertebereich (0, 1) skaliert, in Trainings- und Testdaten aufgeteilt und in ein Überwachtes-Lernen-Problem überführt werden.

3. Skalierung der Daten

Die Eingabedaten eines Neuronalen Netzwerkes müssen vor der Trainingsphase skaliert werden, um zur Größenordnung der Gewichte zu passen und und somit einen stabil verlaufenden Trainingsprozess zu ermöglichen. Beim Trainieren eines Neuronalen Netzes mit Keras werden die Eingabedaten mit Hilfe der Methode fit_transform() der Klasse MinMaxScaler in den Wertebereich (0, 1) skaliert. Da die Methode fit_transform() ein zweidimensionales Numpy-Array als Eingabe erwartet, müssen wir aus unserem series-Objekt zunächst die Werte mit .values als NumPy-Array extrahieren, und dann mit Hilfe der reshape-Funktion eine zusätzliche Dimension hinzufügen.

Zur Kontrolle geben wir die ersten drei Zeilen des Arrays verbrauch vor und nach der Skalierung aus.

Python-Code Ausgabe
# Verbrauch als 2D numpy-Array
verbrauch = series.values.reshape(anzZ, 1)
# Skaliere die Daten auf den Wertebereich (0, 1)
scaler = MinMaxScaler(feature_range = (0,1))
print("Verbrauch (unskaliert)")
print(verbrauch[0:3])
verbrauch = scaler.fit_transform(verbrauch)
print("Verbrauch (skaliert)")
print(verbrauch[0:3])


4. Aufteilung in Trainings- und Testdaten

Die im Schritt 3 skalierten Verbrauchsdaten werden nun entsprechend dem Modells des Überwachten Lernens in Trainings- und Testdaten aufgeteilt. Mit Hilfe der Trainingsdaten train wird das neuronale Netzwerk erstellt und mit Hilfe der Testdaten test wird es validiert. Der Anteil der Trainingsdaten, die für Validierung verwendet werden, wird durch den Konfigurationsparameter VALIDATION_SPLIT gesteuert.

Python-Code Ausgabe
# Anteil der Trainingsdaten für Validierung 
VALIDATION_SPLIT = 0.1
# Letzter Index der Trainingsdaten
train_size = int( anzZ*(1-VALIDATION_SPLIT)) 
# Erster Index der Testdaten
test_size = anzZ - train_size
train = verbrauch[0:train_size, :]
test = verbrauch[train_size:anzZ, :]
print("Trainingsdaten:"); print(train.shape[0])
print("Testdaten:"); print(test.shape[0])


5. Zeitreihe in ein Überwachtes-Lernen-Problem überführen

Für die Prognose von Zeitreihen-Daten muss das Neuronale Netzwerk als ein Modell des überwachten Lernens formuliert werden. Beim überwachten Lernen liegt für jeden Datensatz der Input-Daten (X) eine bekannte Bewertung (Y) vor, d.h. die Daten sind schon in Kategorien unterteilt. Die Datenpunkte der Zeitreihe müssen aufgeteilt werden in Input-Daten (Merkmale, mit X bezeichnet) und eine Zielvariable (Y), die die Bewertung der Merkmale enthält.

Wir erreichen dies mit Hilfe der Funktion erzeuge_bewertung(data, timesteps), die für eine gegebene Zeitreihe data und eine Anzahl von Schritten timesteps zwei Numpy-Arrays X und Y erstellt, die die Merkmale und Zielvariable enthalten. Konkret werden aus der Zeitreihe stets timesteps = 7 Werte als Merkmale festgehalten, und der achte Wert ist die Zielvariable, für die die Bewertung schon bekannt ist.

Um die Funktion zu testen, werden die ersten 14 Zeilen der Spalte Verbrauch in ein DataFrame data extrahiert. Für das Array data werden anschließend mit Hilfe der Funktion erzeuge_bewertung() die Merkmale X und die Zielvariable Y extrahiert. Die Ausgabe zeigt zwei numpy-Arrays: ein Array X mit 7 Zeilen und 7 Spalten und ein Array Y mit 7 Zeilen. Die erste rot markierte Zeile von X stellt eine Gruppe von 7 Merkmalswerten dar, die durch den ersten Wert in Y bewertet wird.

Python-Code Ausgabe
 def erzeuge_bewertung(data, timesteps = 7):
   X, Y = [], []
   for i in range(timesteps, len(data)):
        X.append(data[i-timesteps:i, 0])
        Y.append(data[i, 0])
   return np.array(X), np.array(Y)

# Teste die Funktion erzeuge_bewertung
data = series.iloc[0:14]
print("data:");print(data);
data = data.values.reshape(14, 1)
X, Y = erzeuge_bewertung(data, 7)
print("X:"); print(X);
print("Y:");print(Y);


Nachdem die Funktionsweise der Funktion erzeuge_bewertung() getestet wurde, wird sie auf die Trainings- und Testdaten angewendet. Die Funktion liefert als Rückgabewert aus den Trainingsdaten die Numpy-Arrays X_Train und Y_Train und aus den Testdaten die Numpy-Arrays X_Test und Y_Test. In Zeilen 5 bis 10 werden die Dimensionen der erzeugten Arrays zur Kontrolle ausgegeben.

Ausgabe Ausgabe
# X = Merkmale, Y = Zielvariable
X_Train, Y_Train = erzeuge_bewertung(train, TIMESTEPS)
X_Test, Y_Test = erzeuge_bewertung(test, TIMESTEPS)

print("Trainingsdaten:")
print("X_Train: Zeilen {}, Spalten {}".format(*X_Train.shape)) 
print("Y_Train: Zeilen {}".format(*Y_Train.shape)) 
print("Testdaten:")
print("X_Test: Zeilen {}, Spalten {}".format(*X_Test.shape)) 
print("Y_Test: Zeilen {}".format(*Y_Test.shape)) 


Modell erstellen und trainieren

Die Keras-Bibliothek bietet zum Erstellen eines neuronalen Netzwerks zwei Klassen: Sequential und Functional, die beide die Erstellung mehrschichtiger Netzwerke unterstützen. Die Sequential-Klasse ermöglicht das sequentielle Zusammenzufügen von Schichten (engl. layer), während mit Hilfe der Functional-Klasse komplexere Anordnungen von Schichten erstellt werden können.

Die Schichten eines Künstlichen Neuronalen Netzwerks sind in Keras durch die Klassen der Layer-API realisiert, insbesondere die Klassen Dense und LSTM. Jede Layer-Klasse hat eine Gewichtsmatrix, eine Größenangabe für die Anzahl verwendeter Neuronen (units), eine Formatbeschreibung der Eingabedaten (input_shape), eine Aktivierungsfunktion (activation), und eine Reihe weiterer Parameter, die die Gestaltung der Schicht steuern.

Die üblichen Schritte beim Erstellen eines Neuronalen Netzwerks (Konfigurieren der Topologie, Trainieren des Netzes, Vorhersagen) werden in Keras mit Hilfe der Funktionen compile(), fit() und predict() durchgeführt.

1. Modell erstellen

Als Prognosemodell wird ein neuronales Netzwerk mit einer Eingabeschicht, einer Ausgabeschicht und fünf versteckten LSTM-Schichten erstellt.

# Konfigurationsparameter
TIMESTEPS = 7 # Länge eines Prognosezeitraums (Anzahl Tage)
UNITS = 10 # Anzahl von Ausgängen bei einer LSTM-Schicht
N_LAYER = 4 # Anzahl LSTM-Schichten
model = Sequential(name='sequential')  # Erstelle ein sequentielles Modell
# Füge Schichten hinzu
for i in range(N_LAYER):
    lstm_layer = LSTM(units = UNITS, input_shape=(TIMESTEPS,1),
                      return_sequences=True, 
                      name = 'lstm_' + str(i+1))
    model.add(lstm_layer)
model.add(LSTM(units = UNITS, input_shape=(TIMESTEPS,1), name = 'lstm_' + str(N_LAYER+1)))
model.add(Dense(units = 1, name='dense_1'))
# Konfiguriere das Modell für die Trainingsphase
model.compile(optimizer = "adam", loss = "mse", metrics=['mean_squared_error'])
# Zusammenfassung und Visualisierung des Modells
model.summary()
plot_model(model, show_shapes=True, show_layer_names=True)

Ausgabe 1 Ausgabe 2


2. Modell trainieren

Das Trainieren des Modells geschieht mit Hilfe der Funktion fit(). Die Funktion erhält als Parameter die Merkmale und Zielvariable des Trainingsdatensatzes (X_Train und Y_Train) und trainiert daran in einer festgelegten Anzahl von Iterationen ("Epochen") das Neuronale Netzwerk, d.h. erlernt seine Gewichte. Der Rückgabewert der Funktion ist ein history-Objekt, das die beim Training ermittelten Performancemetriken speichert. Wichtige Konfigurationsparameter der Funktion sind:

Das trainierte Modell kann in verschiedenen Formaten (JSON oder HDF5) für die spätere Verwendung gespeichert werden.

# X_Train erhält eine zusätzliche Dimension
X_Train = np.reshape(X_Train, 
                     (X_Train.shape[0], X_Train.shape[1], 1))
# Trainiere das Modell mit Hilfe der Funktion fit()
history = model.fit(X_Train, Y_Train, 
                    epochs = 100, batch_size = 64, validation_split = 0.1, verbose = 2)
# Speichere das Modell im Format HDF5
model.save("model_adam.h5")
print("History");print(history.history.keys());

Falls der Parameter verbose den Wert 2 erhält, sieht eine Ausgabe des Training-Prozesses ähnlich wie abgebildet aus. Für jede Epoche wird eine Zeile mit der benötigten Zeit und den Werten der Performance-Metriken angezeigt, die beim Training verwendet werden. Die Performance-Metriken 'loss' und 'mean_squared_error' beziehen sich auf die Trainingsdaten, die Performance-Metriken 'val_loss' und 'val_mean_squared_error' hingegen auf die Validierungsdaten. Bei einem gut verlaufenden Training sollte die loss-Funktion der Trainingsdaten in ein Plateau führen, während die loss-Funktion der Validierungsdaten bis zu einem bestimmten Punkt fällt, dann aber wieder ansteigt.

Visualisierung der Trainingsphase
Entwicklung des MSE-Fehlers über die ersten 19 der insgesamt 100 Epochen

3. Trainingsphase visualisieren

Die bei der Durchführungs des Trainings verwendeten Metriken können dem history-Objekt entnommen und grafisch dargestellt werden. Durch history.history['mean_squared_error'] greift man auf die ermittelte mittlere quadratische Abweichung zurück, die bei der Vorhersage auf den Trainingsdaten ermittelt wird, und durch history.history['val_mean_squared_error'] auf den MSE-Fehler, der bei den Testdaten entsteht.

plt.plot(history.history['mean_squared_error'], label='MSE (Trainingsdaten)')
plt.plot(history.history['val_mean_squared_error'], label='MSE (Testdaten)')

plt.title('Training: Entwicklung des Fehlers')
plt.ylabel('MSE-Fehler')
plt.xlabel('Epochen')
plt.legend()

Die Entwicklung des MSE-Fehlers über die Epochen zeigt, dass er am Anfang stark fällt und mit wachsender Zahl der Epochen ein stabiles Niveau erreicht, d.h. eine größere Anzahl an Epochen würde zu keiner Verbesserung des Modells führen. Der Fehler in den Testdaten liegt minimal über dem Fehler der Trainingsdaten. Da die Abweichung sehr gering ist, ist unser Modell ggf. überangepasst.

Visualisierung der Trainingsphase
Entwicklung des MSE-Fehlers über 100 Epochen

Modell validieren

Mit Hilfe des Testdatensatzes wird das Modell validiert, d.h. es wird eine Prognose erstellt und für diese werden Performancemetriken ausgewertet. Dafür erstellen wir zunächst eine Prognose für die skalierten Testdaten mit Hilfe der Funktion predict(). Die Funktion predict() erhält als Parameter den skalierten Testdatensatz X_Test und liefert die Prognose-Werte Y_Pred zurück. Die bekannten und geschätzten Werte der Zielvariablen (Y_Test und Y_Pred) werden anschließend in ihren ursprünglichen Wertebereich reskaliert.

Prognose erstellen und Daten reskalieren

Die geschätzten Werte liegen in dem erwarteten Wertebereich, soweit sieht alles gut aus.

Python-Code Ausgabe
# Vorhersage für skalierte Testdaten
X_Test = np.reshape(X_Test, 
        (X_Test.shape[0], X_Test.shape[1], 1))
Y_Test = np.reshape(Y_Test, 
        (Y_Test.shape[0],  1))
Y_Pred = model.predict(X_Test)
# Reskaliere die Daten
y_Test = pd.DataFrame(
    scaler.inverse_transform(Y_Test))
y_Pred = pd.DataFrame(
    scaler.inverse_transform(Y_Pred))
display_dataframe(y_Test)


Performance-Metriken auswerten

Nachdem die Vorhersagewerte für die Testdaten ermitteln wurden, berechnen wir noch die maximale Abweichung zwischen tatsächlichen und geschätzten Testdaten und visualisieren diese. Dafür wird ein neuer DataFrame pred durch Zusammenfügen der DataFrames y_Test, y_Pred erstellt, dem wir noch zwei weitere Spalten hinzufügen, die den absoluten und den relativen Fehler enthalten.

Um den Code übersichtlicher zu halten, schreiben wir eine Hilfsfunktion rel_error(df, col1, col2), die für ein DataFrame df und zwei namentlich gegebene Spalten col1 und col2 den relativen Fehler der Spalten berechnet und in einer neuen Spalte mit dem Namen Error (%) speichert.

def rel_error(df, col1, col2):
    df['Error (%)'] = (df[col1] - df[col2]) / df[col1]  * 100
    df['Error (%)'] = df['Error (%)'].abs()
    return df['Error (%)']

Der folgende Codeblock fügt die DataFrames y_Test und y_Pred mit Hilfe der Funktion concat() zu einem neuen DataFrame mit dem Namen pred zusammen. Das Zusammenfügen geschieht entlang der Spalten, was durch die Angabe axis=1 festgelegt wird. Der neue DataFrame erhält das Datum als Index-Spalte sowie zwei neuen Spalten, die den absoluten und den relativen Fehler der ersten beiden Spalten enthalten. Durch die Berechnung des relativen Fehlers wird sichtbar, dass die Prognose für die Testdaten akzeptable Werte liefert: der relative Fehler ist im Durchschnitt kleiner als 20%.

Python-Code Ausgabe
cols = [y_Test, y_Pred]
headers = ['y_Test', 'y_Pred']
# Erzeuge DataFrame pred aus y_Test und y_Pred
pred = pd. concat(cols, axis=1, keys=headers) 
datum = opsd_df.index[anzZ-Y_Test.shape[0]:anzZ]
pred.set_index(datum, inplace=True)
# Füge Fehler-Spalten hinzu
pred['Error'] = np.abs(pred['y_Test'] - pred['y_Pred'])
pred['Error (%)'] = rel_error(pred, 'y_Test', 'y_Pred')
pred = pred.astype(float).round(1)
# Gebe pred aus
display_dataframe(pred,6)


Bei der Validierung der Prognose-Ergebnisse sind die Mittlere Quadratische Abweichung (MSE), deren Wurzel (RMSE), und der Maximale Absolute Fehler geeignete Performance-Maße, um die Güte einer Prognose zu bewerten. Informell: MSE bedeutet, dass die Auswirkung einzelner Abweichungen gemittelt wird. RMSE "bestraft" große Abweichungen. Die Funktion perf_prediction(pred) enthält die Berechnung der Performancemetriken für die reskalierten Daten.

Python-Code Ausgabe
def perf_prediction(pred):
    y_Test = pred['y_Test']
    y_Pred = pred['y_Pred']
    print("MSE-Fehler:")
    mse = mean_squared_error(y_Test, y_Pred)
    mse = np.round(mse)
    print(mse)
    rmse = np.round(np.sqrt(mse))
    print("RMSE-Fehler:")
    print(rmse)
    # Fehler: Maximum
    print("Max-Fehler:")
    maxe = max_error(y_Test,y_Pred)
    maxe = np.round(maxe)
    print(maxe)
    # Berechne Maximum jeder Zeile
    df_max = pd.DataFrame(pred.max())
    df_max = df_max.transpose()
    display_dataframe(df_max)
# Funktionsaufruf:   
perf_prediction(pred) 
Fehler: MSE und MAX
Fehler: MSE und MAX

Prognose visualisieren

Die Prognose für die Testdaten, wie sie im DataFrame pred enthalten ist, wird nun visualisiert. Die Funktion plot_prediction(pred) enthält den Visualisierungscode, genauer: eine Figur mit zwei Diagrammen, die in einer Zeile nebeneinander plaziert werden.

def plot_prediction(pred):
    fig, (ax1, ax2) = plt.subplots(ncols=2, figsize=(20, 5))
    plt.subplot(1,2,1)
    ax1 = pred.iloc[:,0].plot(label='Verbrauch', lineWidth=1, color='b');
    ax1 = pred.iloc[:,1].plot(label='Verbrauch (geschätzt)', 
                             lineWidth=1, color='g', linestyle='dashed', marker='o');
    ax1.set_title('Stromverbrauch: Tatsächliche vs. geschätzte Daten')
    ax1.set_ylabel('Verbrauch');
    ax1.legend();

    plt.subplot(1,2,2)
    ax2 = pred.iloc[:,3].plot(label='Fehler (%)', lineWidth=1, color='b');
    ax2.set_title('Relativer Prognosefehler')
    ax2.set_xlabel('Datum');
    ax2.set_ylabel('Fehler (%)');
# Funktionsaufruf
plot_prediction(pred)

Das Diagramm links zeigt den tatsächlichen vs. den geschätzten Verbrauch, das Diagramm rechts zeigt den relativen Fehler. Die Vorhersage liegt in einigen Bereichen (speziell Jahreswende) um bis zu 35% falsch, was auf die nicht behandelte Saisonalität zurückzuführen ist.

Validierung
Visualisierung der Prognose für den Testdatensatz

Die Validierung, die in diesem Abschnitt durchgeführt wurde, bezieht sich auf einen Anteil des Trainingsdatensatzes und liefert damit bessere Ergebnisse, als man es mit einem unabhängigen Datensatz erreichen würde. Daher erstellen wir im letzten Schritt Prognosen mit unabhängigen Datensätzen und evaluieren auch für diese die Performance-Metriken.

Prognose für 2018

In diesem Schritt wird das zuvor erstellte Neuronale Netzwerk eingesetzt, um eine Prognose für das Jahr 2018 zu erstellen. Für die Prognose mit neuen Testdatensätzen kann der Code aus Abschnitt "Modell validieren" wiederverwendet werden. Es bietet sich an, aus den Codefragmenten für die Vorhersage eine wiederverwendbare Funktionen zu extrahieren: Die Funktion opsd_prediction(df, colname) erstellt eine Prognose für die Spalte colname des DataFrames df und liefert einen DataFrame pred zurück, der tatsächliche und geschätzte Werte sowie zwei Fehlerspalten enthält.

# Funktion erstellt eine Prognose für die Spalte colname des DataFrames df
# df: DataFrame
# colname: Spalte des DataFrames, für die die Prognose erstellt werden soll.
# pred: Rückgabewert: ein DataFrame mit 4 Spalten: y_Test, y_Pred, Error, Error %
def opsd_prediction(df, colname):    
    anzZ = df.shape[0] # Anzahl Zeilen
    series = df[colname] # Spalte
    # Skaliere auf (0, 1)
    test = scaler.fit_transform(series.values.reshape(anzZ, 1))
    # Erzeuge Bewertung
    X_Test, Y_Test = erzeuge_bewertung(test, TIMESTEPS)
    # Zusätzliche Dimensionen
    X_Test = np.reshape(X_Test,  (X_Test.shape[0], X_Test.shape[1], 1))
    Y_Test = np.reshape(Y_Test, (Y_Test.shape[0],  1))
    # Erstelle Prognose
    Y_Pred = model.predict(X_Test)
    # Reskaliere die Daten
    y_Test = pd.DataFrame(scaler.inverse_transform(Y_Test))
    y_Pred = pd.DataFrame(scaler.inverse_transform(Y_Pred))
    # Tabelle für Ausgabe
    cols = [y_Test, y_Pred]
    headers = ['y_Test', 'y_Pred']
    # Erzeuge DataFrame pred aus y_Test und y_Pred
    pred = pd. concat(cols, axis=1, keys=headers) 
    datum = df.index[anzZ-Y_Test.shape[0]:anzZ]
    pred.set_index(datum, inplace=True)
    # Füge Fehler-Spalten hinzu
    pred['Error'] = np.abs(pred['y_Test'] - pred['y_Pred'])
    pred['Error (%)'] = rel_error(pred, 'y_Test', 'y_Pred')
    pred = pred.astype(float).round(1)   
    return pred

Die Prognose erfolgt nun in fünf Schritten: Einlesen der Testdaten in einen DataFrame opsd_test, Erstellen der Prognose mit Hilfe eines Funktionsaufrufs der Funktion opsd_prediction(), Ausgabe des DataFrames, Berechnung der Performance-Metriken und visuelle Darstellung der geschätzen Werte und des Fehlers.

Python-Code Ausgabe
## Prognose für 2018: 
## Performance-Metriken und Visualisierung

# (1) Testdaten einlesen
opsd_test = pd.read_csv( 'opsd_2018.csv')
opsd_test.set_index('Datum', inplace = True)
opsd_test.loc[:,:].fillna( method='pad', inplace = True)
opsd_test = opsd_test.astype(float).round(2)

# (2) Erstelle Vorhersage mit Funktion opsd_prediction
pred_test = opsd_prediction(opsd_test, 'Verbrauch')

# (3) Gebe pred_test aus
display_dataframe(pred_test, 6)

# (4) Berechne Performance-Metriken
perf_prediction(pred_test) 

# (5) Visualisiere Prognose für 2018
plot_prediction(pred_test)
Fehler: MSE und MAX
DataFrame pred_test

Fehler: MSE und MAX
Fehler: MSE und MAX

Validierung
Visualisierung der Prognose für das Jahr 2018

Performance-Metriken und Visualisierung zeigen eine akzeptable Prognose, mit einem RMSE-Fehler von 94 der wie erwartet größer ist als der in den Validierungsdaten.

Zusammenfassung und Ausblick

Demo-PY5 zeigt, wie ein "tiefes" Neuronales Netzwerk mit mehreren LSTM-Schichten für die Zeitreihenprognose mit Hilfe der Python-Bibliotheken Keras und Tensorflow erstellt wird. Die Trainingsaufgabe besteht darin, in einem rollenden Zeitfenster aus sieben bekannten Werten einen Zukunftswert zu schätzen. Für die Bewertung des Modells verwenden wir die mittlere quadratische Abweichung als Performance-Maß. Die Güte des Modells beruht auf der richtigen Konfiguration des Netzes (Anzahl an LSTM-Schichten und Neuronen) und der Einstellung der Konfigurationsparameter für die Trainingsphase (batch_size).

Die nächsten Schritte zu einem optimierten Modell sind die Behandlung der Trend- und Saisonalitätskomponenten in den Trainingsdaten, das Testen des Dropout-Parameters, um Überanpassung an die Trainingsdaten zu vermeiden, und die Vorhersage für komplett neue Daten.

YouTube-Video

Die Verwendung des erstellten Jupyter Notebook wird durch ein Video (Screencast mit zusätzlichen Erläuterungen) veranschaulicht.

Autoren, Tools und Quellen

Autor:
 Prof. Dr. Eva Maria Kiss
Mit Beiträgen von:
 B. Sc. Franc Willy Pouhela
 M. Sc. Anke Welz
Tools:
Keras, Pandas, Tensorflow, Python, Jupyter Notebook
Quellen und weiterführende Links
  Hochreiter, Sepp & Schmidhuber, Jürgen. (1997). Long Short-term Memory. Neural computation. 9. 1735-80.
  McKinney, Wes (2019): Datenanalyse mit Python. Auswertung von Daten mit Pandas, NumPy und IPython.
  Pouhela, Franc. (2020). Bachelorarbeit: Datenanalyse und Vorhersage mit Python und Keras für Open Power System-Daten.
  Russell, Stuart J., Norvig, Peter (2014) Artificial intelligence. A modern approach.