› 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.
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.
Ü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.
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: |
---|---|
|
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
- Feedforward-Netze, bei denen die Weiterleitung nur in eine Richtung von der Eingabe- zur Ausgabeschicht erfolgt,
- Rekurrente Netze, die Verbindungen von Neuronen einer Schicht zu Neuronen derselben oder einer vorangegangenen Schicht haben. Durch die rückgekoppelten Verbindungen erhält das Netz ein Gedächtnis.
- Long Short Term Memory-Netze (LSTM), eine Verbesserung rekurrenter Netze, bei denen die Neuronen einen komplexeren Aufbau haben und einen konfigurierbaren Anteil der Ausgaben auch wieder vergessen können.
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 |
---|---|
|
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 |
---|---|
|
|
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.
- Zeile 2: Die Datum-Spalte wird als Index des DataFrames festgelegt.
- Zeile 4: Fehlende Werte, die im DataFrame als NaN (Not Available) angezeigt werden, ersetzen wir mit Hilfe der Funktion fillna durch den jeweils vorigen Wert der Zeitreihe.
- Zeile 6: Die Zahlenwerte werden auf zwei Nachkommastellen gerundet.
- Zeile 8: Ausgabe des DataFrames mit Hilfe der Funktion display_dataframe()
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 |
---|---|
|
|
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 |
---|---|
|
|
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 |
---|---|
|
|
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 |
---|---|
|
|
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 |
---|---|
|
|
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 |
---|---|
|
|
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 |
---|---|
|
|
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.
- Zeile 1-4: Die Konfigurationsparameter für die Erstellung des Modells werden festgelegt.
- Zeile 5: Erstelle neues Sequential-Modell und lege seinen Namen fest.
- Zeile 7-12: Füge dem Modell in einer for-Schleife N_LAYER versteckte LSTM-Schichten hinzu. Für jede LSTM-Schicht wird die Anzahl der Ausgangsknoten festgelegt (UNITS) sowie das Format der Eingabedaten (input_shape). Weitere wichtige Parameter sind die Aktivierungsfunktion (activation), die für LSTM-Ausgabe defaultmäßig auf die sigmoid-Funktion festgelegt ist, und der dropout-Parameter, der festlegt, welcher Anteil an Ausgangsknoten der Schicht beim Trainieren "vergessen" werden soll.
- Zeile 9: Jede Zelle einer LSTM-Schicht erwartet eine dreidimensionale Eingabe. Eine LSTM-Schicht, die eine Eingabesequenz von Zeitreihendaten verarbeitet, wird jedoch einen einzelnen Wert als 2D-Array zurückliefern. Um mehrere LSTM-Schichten nacheinander schalten zu können, müssen wir daher die inneren LSTM-Schichten so konfigurieren, dass sie ein dreidimensionales Array ausgeben, welches als Eingabe vom nächsten LSTM-Layer verwendet werden kann. Die geschieht mit Hilfe des Parameters return_sequences = True
- Zeile 13: Als letzte Schicht fügen wir eine Dense-Schicht hinzu. Eine Dense-Schicht ist die Implementierung einer Standardschicht eines neuronalen Netzwerkes. Standardmäßig wird keine Aktivierungsfunktion verwendet, d.h. die Daten werden unverändert weitergegeben.
- Zeile 15: Die Methode compile() konfiguriert das Modell, d.h. bereitet es mit Hilfe der angegebenen Konfigurationsparameter für die Trainingsphase vor. Insbesondere kann hier eingestellt werden, welcher Optimierer und welche Metriken verwendet werden sollen. Mit Hilfe des Parameters loss wird konfiguriert, welche Performance-Metrik während des Trainings minimiert werden soll. Hier wird die mittlere quadratische Abweichung (mse) verwendet.
- Zeile 17: Die Zusammenfassung des Modells (vgl. Ausgabe 1) wird ausgegeben.
- Zeile 18: Visualisierung des Modells mit Hilfe der Funktion plot_model(), vgl. Ausgabe 2.
# 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:
- epochs: Anzahl der Epochen, d.h. Trainingsdurchläufen des Algorithmus.
- batch_size: Größe eines Batchs. Ein Batch ist die Anzahl der Trainings-Beobachtungen (Zeilen), die der Algorithmus auf einmal bearbeitet. Bei einer Anzahl von 1000 Trainingsbeobachtungen und N_BATCH = 100 wird das Netzwerk zunächst mit den ersten 100 Datensätzen trainiert, danach mit den nächsten 100 etc.
- validation_split: Anteil der Trainingsdaten, die für die Validierung verwendet werden. Falls validation_split = 0.1 gesetzt wird, verwendet der Algorithmus die letzten 10% der Trainingsdaten für die Validierung.
- verbose: ein Parameter, der steuert, ob und wie Zwischenergebnisse des Trainings ausgegeben werden.
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.
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.
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
- Zeile 2-5: Die Testdaten X_Test und Y_Test erhalten eine zusätzliche Dimension, um als Eingabeparameter der Funktionen predict() bzw. inverse_transform() akzeptiert zu werden. D.h. X_Test wird ein dreidimensionales Array und Y_Test ein zweidimensionales Array.
- Zeile 6: Die predict-Funktion liefert die geschätzten Werte der Zielvariablen y_Pred zurück.
- Zeile 7-11: Die tatsächlichen und geschätzten Werte der Zielvariablen werden mit Hilfe der Funktion inverse_transform in den ursprünglichen Wertebereich reskaliert.
- Zeile 12: Ausgabe des DataFrames der geschätzten Werte.
Die geschätzten Werte liegen in dem erwarteten Wertebereich, soweit sieht alles gut aus.
Python-Code | Ausgabe |
---|---|
|
|
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 |
---|---|
|
|
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 |
---|---|
|
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.
- Zeile 2: Erstelle Figur mit zwei Diagrammen, die nebeneinander angeordnet sein sollen, und gebe die Größe der Figur an.
- Zeile 3-9: Zeichne die erste und zweite Spalte des DataFrames pred auf das erste Diagramm.
- Zeile 11-15: Zeichne den relativen Fehler auf das zweite Diagramm.
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.
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 |
---|---|
|
|
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.
-- die Original-Veröffentlichung zu LSTM - McKinney, Wes (2019): Datenanalyse mit Python. Auswertung von Daten mit Pandas, NumPy und IPython. 2. Auflage. Heidelberg: O'Reilly.
- Pouhela, Franc. (2020). Bachelorarbeit: Datenanalyse und Vorhersage mit Python und Keras für Open Power System-Daten.
-- das verwendete LSTM-Netzwerk wurde im Rahmen dieser Bachelorarbeit erstellt. - Russell, Stuart J., Norvig, Peter (2014) Artificial intelligence. A modern approach.
- Python-Tutorial: https://www.elab2go.de/demo-py1/
- Jupyter Notebooks verwenden: https://www.elab2go.de/demo-py1/jupyter-notebooks.php
- Jupyter Notebook Widgets erstellen: https://www.elab2go.de/demo-py1/jupyter-notebook-widgets.php
- Interaktive Datenvisualisierung in Pythonhttps://www.elab2go.de/demo-py2/