1.2. Grundwasservorhersage mittels eines Decision Trees, Sachsen#
author: Karl Wagemann und Adnessa Nguyen
area: Sachsen
date: 26.08.2024
# Load libraries
import os, sys
import wget
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy.io import netcdf
import matplotlib.pyplot as plt
import netCDF4, h5netcdf
import xarray as xr
import glob
from sklearn import tree
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split
import math
from math import sqrt
from sklearn.tree import DecisionTreeClassifier
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import accuracy_score, mean_squared_error, mean_absolute_error, r2_score
import os
os.chdir('..')
os.chdir('C:/Users/karl-/UNI/Neuer_versuch')
print(os.getcwd())
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 4
1 # Load libraries
3 import os, sys
----> 4 import wget
5 import pandas as pd
6 import numpy as np
ModuleNotFoundError: No module named 'wget'
1.2.1. Ziel#
Das Ziel dieser Hausarbeit ist es, zu untersuchen, ob und wie gut sogenannte Entscheidungsbäume zur Vorhersage des Grundwasserspiegels in Sachsen verwendet werden können. Der Grundwasserspiegel, also die Tiefe, in der sich unter der Erdoberfläche Wasser befindet, ist für viele Bereiche, wie die Landwirtschaft und Wasserversorgung, von großer Bedeutung. In dieser Arbeit soll geprüft werden, ob die wöchentlichen Messungen von Temperatur (tas) und Niederschlag (pr) in Sachsen ausreichen, um mit Hilfe von Entscheidungsbäumen den Grundwasserspiegel (GWL) für acht verschiedene Messstationen in Sachsen vorherzusagen. Dabei wird auch untersucht, wie genau diese Vorhersagen sind und ob das Modell zuverlässig arbeitet, indem es auf verschiedenen Daten getestet wird.
Als Grundlage unserer Forschungsarbeit diente uns das Paper von Wunsch et al. (2022). Ziel seiner Studie war es, den Klimawandel anhand des Grundwasserspiegels vorherzusagen und zunehmende Trends von Trocknheit in Deutschland zu belegen.
1.2.2. Daten#
Für die Vorhersage des Grundwasserspiegels verwendeten wir Daten von acht verschiedenen Messstellen in Sachsen. Diese Daten umfassten Rasterdaten der wöchentlichen Durchschnittswerte der Tagestemperaturen und des Niederschlags (5x5km). Diese Daten sind frei zugänglich und stammen vom Climate Data Center des Deutschen Wetterdienstes (DWD), einer offiziellen Stelle, die solche Informationen sammelt und zur Verfügung stellt (Link: https://opendata.dwd.de/climate_environment/CDC/grids_germany/).
Um unsere Klimadaten auf unser Untersuchungsgebiet anwenden zu können, verwendeten wir einen ESRI-Shapefile aus Sachsen, den wir in das räumlich benötigte Koordinatensystem einspeisten (EPSG 25833). Anschließend extrahierten wir die Zeitreihen aus den netCDF-Datein und stapelten sie übereinander, sodass wir für jede der acht Messstellen eine eigene .csv-Datei mit den benötigten Daten für Niederschlag, Temperatur und Grundwasserspiegel erhielten. Der Zeitraum, der in dieser Untersuchung betrachtet wird, erstreckt sich von Januar 1990 bis Dezember 2005, also über 16 Jahre. Insgesamt gibt es für jede .csv-Datei 835 Messwerte, die als Grundlage für die Vorhersage des Grundwasserspiegels dienen.
1.2.3. Was ist ein Entscheidungsbaum?#
Der Random Forest ist eine Machine-Learning-Methode, die mehrere Entscheidungsbäume kombiniert, um eine konsolidierte Vorhersage zu treffen. Jeder Baum im Wald wird auf einer zufällig ausgewählten Teilmenge der Trainingsdaten sowie einer zufälligen Auswahl von Merkmalen trainiert. Dieser Ansatz sorgt für Diversität unter den Bäumen, was die Genauigkeit und Robustheit der Vorhersagen erhöht. Bei Klassifikationsaufgaben erfolgt die finale Entscheidung durch Mehrheitsvotum der Bäume, während bei Regressionsaufgaben die Durchschnittswerte aller Bäume gebildet werden. In unserem Random Forest Model bilden wir die Durchschnittswerte aller Bäume, um letztendlich einen größmöglichen Prädiktor für unseren Grundwasserspiegel zu erhalten.
1.2.4. Ergebnisse#
Bevor wir mit den Ergebnissen anfangen, möchten müssen wir zuerst notwendige Pakete runterladen, die wir für unser Model und den Code notwendig sind. Anschließend werden wir mit folgender Struktur forfahren.
Jede CSV-Datei einzeln reinladen und die Daten zu einem einzigen DataFrame für das Modelltraining zusammenführen.
Den Random Forest Regressor auf dem kombinierten Datensatz trainieren.
Das Modell evaluieren.
# 1. Wir lesen nun unsere vorgefertigten Datensätze ein...
csv_files = glob.glob('data/HYRAS_GWData_csv/*.csv')
# ... bereiten diese Daten in einer Liste für die verarbeiteten Datensätze zusammen...
processed_samples = pd.DataFrame()
for file in csv_files:
df = pd.read_csv(file, skipinitialspace=True).drop(['Unnamed: 0'], axis=1)
# ... und kombinieren alle Datensätze der einzelnen Messstationen zu einem gemeinsamen Datensatz
processed_samples = pd.concat([processed_samples,df], axis=0)
# Anschließend prüfen wir den Datensatz nach fehlenden Werten...
processed_samples.isnull().sum()
Date 0
pr 0
tas 0
GWL 0
dtype: int64
# ...und schmeißen alle fehlenden Werte aus dem Datensatz raus, da dies sonst Fehler mitsich bringen könnte
processed_samples.dropna(axis=0, inplace=True)
processed_samples
Date | pr | tas | GWL | |
---|---|---|---|---|
0 | 1990-01-01 | 0.600000 | -2.157143 | 140.690 |
1 | 1990-01-08 | 2.100000 | 1.885714 | 140.670 |
2 | 1990-01-15 | 1.200000 | 5.328571 | 140.620 |
3 | 1990-01-22 | 9.000000 | 6.085714 | 140.670 |
4 | 1990-01-29 | 4.000000 | 6.042857 | 140.660 |
... | ... | ... | ... | ... |
830 | 2005-11-28 | 5.700000 | 0.300000 | 322.910 |
831 | 2005-12-05 | 2.000000 | 1.242857 | 322.880 |
832 | 2005-12-12 | 25.600000 | 1.271429 | 322.850 |
833 | 2005-12-19 | 22.400000 | 1.542857 | 322.880 |
834 | 2005-12-26 | 13.099999 | -3.550000 | 322.985 |
6680 rows × 4 columns
# wir teilen unsere Daten in abhängige und unabhängige Variablen. 'pr' und 'tas' sollen 'GWL' als abhängige Variable bestimmen
feature_cols = ['pr', 'tas']
X = df[feature_cols] # Features
y = df.GWL # Target variable
#X = df.iloc[:, :-1].values
#y = df.iloc[:, -1].values
# Nun teilen wir unseren Datensatz in ein Trainings- und Testdatensatz. Als Richtwert nehmen wir 70% Trainingsdaten und 30% Testdaten.
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)
# 2. Nun legen wir den Initialwert (seed) fest, sowie die Anzahl der Entscheidungsbäume (n_estimators) fest.
## Der Initialwert stellt sicher, dass die Zufallsprozesse bei jedem Lauf des Modells gleich bleiben
### Die Anzahl der Entscheidungsbäume gibt an, wie viele Bäume im Modell enthalten sind. Je höher die Zahl, desto höher die Genauigkeit des Modells
seed = 196
n_estimators = 200
# 2. Wir erstellen nun einen RandomForest, definieren X und y und trainieren unser Modell
model = RandomForestRegressor(n_estimators = n_estimators, random_state = seed, max_features = 1.0,
min_samples_split = 2, min_samples_leaf = 1, max_depth = None)
model.fit(X_train, y_train)
RandomForestRegressor(n_estimators=200, random_state=196)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestRegressor(n_estimators=200, random_state=196)
# 3. Mit dieser Funktion lassen wir uns den Mittelwert der quadrierten Residuen sowie die erklärte Varianz ausgeben. Diese Werte werden in der Diskussion nochmals ausführlich besprochen.
# Allerdings ist ein Wert von 0,83 bzw. 83% sehr gut.
print(f"Mean of squared residuals: {model.score(X_train, y_train)}")
print(f"% Var explained: {model.score(X_train, y_train) * 100}")
Mean of squared residuals: 0.8383661439335206
% Var explained: 83.83661439335206
# In dem unten stehenden Code wird eine Kreuzvalidierung durchgeführt und gibt die einzelnen Scores für die Leistung des Modells zurück.
# Je niedriger der Wert, desto besser die Performance.
from sklearn.model_selection import cross_val_score
scores = - cross_val_score(estimator=model, X=X_train, y=y_train, cv=10, scoring='neg_mean_squared_error', n_jobs=1)
print(scores)
print(np.mean(scores), np.std(scores))
[0.2641384 0.35096317 0.38723234 0.37628229 0.31605197 0.29167542
0.22262363 0.38456727 0.28407709 0.24205715]
0.31196687262097245 0.05740758746431723
# Um die Tiefe der Bäume zu variieren, trainieren wir ein weiteres Modell mit einer Tiefe von 7.
seed = 196
n_estimators = 200
# 2. Wir erstellen nun einen RandomForest, definieren X und y und trainieren unser Modell
model2 = RandomForestRegressor(n_estimators = n_estimators, random_state = seed, max_features = 1.0,
min_samples_split = 2, min_samples_leaf = 1, max_depth = 7)
model2.fit(X_train, y_train)
RandomForestRegressor(max_depth=7, n_estimators=200, random_state=196)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestRegressor(max_depth=7, n_estimators=200, random_state=196)
# Auch hier lassen wir uns den Mittelwert der quadrierten Residuen ausgeben. Wir sehen, dass das Model mit einer Tiefe von 7 schlechter abschneiedet, als wenn die Tiefe frei gewählt wird.
print(f"Mean of squared residuals: {model2.score(X_train, y_train)}")
print(f"% Var explained: {model2.score(X_train, y_train) * 100}")
Mean of squared residuals: 0.42244101795001376
% Var explained: 42.24410179500138
1.2.5. Hyperparameter Tuning:#
1.2.5.1. Beim Hyperparameter Tuning schauen wir uns an, welche Parameter unser Model am Besten vorhersagt. Dabei variieren wir zum einen in der Anzahl der Entscheidungsbäume (n_estimators) sowie in der Anzahl der Tiefe.#
from sklearn.model_selection import GridSearchCV
rfc = RandomForestRegressor(random_state=42)
param_grid = {
'n_estimators': [100,200,300, 400, 500, 600, 700, 800, 900, 1000],
# 'max_features': ['auto', 'sqrt', 'log2'],
'max_depth' : [4,5,6,7,8],
# 'criterion' :['gini', 'entropy']
}
CV_rfc = GridSearchCV(estimator=rfc, param_grid=param_grid, cv= 5)
CV_rfc.fit(X_train, y_train)
GridSearchCV(cv=5, estimator=RandomForestRegressor(random_state=42), param_grid={'max_depth': [4, 5, 6, 7, 8], 'n_estimators': [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]})In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
GridSearchCV(cv=5, estimator=RandomForestRegressor(random_state=42), param_grid={'max_depth': [4, 5, 6, 7, 8], 'n_estimators': [100, 200, 300, 400, 500, 600, 700, 800, 900, 1000]})
RandomForestRegressor(random_state=42)
RandomForestRegressor(random_state=42)
# Die Besten Parameter für unser Model ist eine Tiefe von 4 sowie eine Anzahl von ca. 200 Entscheidungsbäumen
best_params = CV_rfc.best_params_
best_params
{'max_depth': 4, 'n_estimators': 200}
# Initialwert und Anzahl der Bäume für den Random Forest festlegen
seed = 196
# RandomForest erstellen, X und y definieren und Modell trainieren
model3 = RandomForestRegressor(n_estimators = best_params["n_estimators"],
random_state = seed,
max_features = 1.0,
min_samples_split = 2,
min_samples_leaf = 1,
max_depth = best_params["max_depth"])
model3.fit(X_train, y_train)
RandomForestRegressor(max_depth=4, n_estimators=200, random_state=196)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
RandomForestRegressor(max_depth=4, n_estimators=200, random_state=196)
print(f"Mean of squared residuals: {model3.score(X_train, y_train)}")
print(f"% Var explained: {model3.score(X_train, y_train) * 100}")
Mean of squared residuals: 0.19331573231045152
% Var explained: 19.33157323104515
# Wir haben nun unser Modell mit einer beliebigen Tiefe
from sklearn.tree import DecisionTreeRegressor
from sklearn.metrics import mean_squared_error
# Liste zur Speicherung der Ergebnisse initialisieren
ergebnisse = []
# Schleife über verschiedene Tiefen des Entscheidungsbaums
for tiefe in range(1, 11): # Beispiel: Baumtiefen von 1 bis 10
# Erstellen des Regressormodells mit der aktuellen Baumtiefe
regressor = DecisionTreeRegressor(max_depth=tiefe, random_state=42)
# Modell trainieren
regressor.fit(X_train, y_train)
# Vorhersagen auf den Trainingsdaten
y_train_pred = regressor.predict(X_train)
# Berechnung des Mean Squared Error (MSE) für die Trainingsdaten
mse_train = mean_squared_error(y_train, y_train_pred)
# Vorhersagen auf den Testdaten
y_test_pred = regressor.predict(X_test)
# Berechnung des MSE für die Testdaten
mse_test = mean_squared_error(y_test, y_test_pred)
# Ergebnisse speichern
ergebnisse.append((tiefe, mse_train, mse_test))
# Ergebnisse ausgeben
print("Baumtiefen | MSE auf Trainingsdaten | MSE auf Testdaten")
for tiefe, mse_train, mse_test in ergebnisse:
print(f"{tiefe:<10} | {mse_train:.4f} | {mse_test:.4f}")
# Beste Tiefe auswählen (z.B. die mit dem niedrigsten MSE auf den Testdaten)
beste_tiefe = min(ergebnisse, key=lambda x: x[2])[0]
bester_regressor = DecisionTreeRegressor(max_depth=beste_tiefe, random_state=42)
bester_regressor.fit(X_train, y_train)
# Vorhersagen auf den Testdaten mit dem besten Modell
y_test_pred = bester_regressor.predict(X_test)
# Berechnung und Ausgabe des MSE für das beste Modell auf den Testdaten
mse_best_test = mean_squared_error(y_test, y_test_pred)
print(f"\nBeste Baumtiefe: {beste_tiefe}")
print(f"MSE auf Testdaten mit dem besten Modell: {mse_best_test:.4f}")
Baumtiefen | MSE auf Trainingsdaten | MSE auf Testdaten
1 | 0.2658 | 0.3036
2 | 0.2569 | 0.3192
3 | 0.2432 | 0.3336
4 | 0.2324 | 0.3450
5 | 0.2240 | 0.3525
6 | 0.2105 | 0.3843
7 | 0.1944 | 0.4018
8 | 0.1761 | 0.4285
9 | 0.1618 | 0.4236
10 | 0.1433 | 0.4536
Beste Baumtiefe: 1
MSE auf Testdaten mit dem besten Modell: 0.3036
1.2.6. Diskussion der Ergebnisse#
In dieser Untersuchung wurde der Random Forest Regressor eingesetzt, um den Grundwasserspiegel (GWL) auf Basis der Einflussfaktoren Niederschlag (pr) und Durchschnittstemperatur (tas) vorherzusagen. Zu Beginn wurde ein Random Forest Modell ohne Einschränkung der Baumtiefe trainiert. Das Modell erreichte eine erklärte Varianz von etwa 83.84% auf den Trainingsdaten, was darauf hinweist, dass es die Beziehungen zwischen den Prädiktoren und dem GWL sehr gut erfassen konnte. Diese hohe erklärte Varianz deutet auf eine starke Anpassung an die Trainingsdaten hin. Die Kreuzvalidierung ergab eine durchschnittliche MSE von 0.312 mit einer Standardabweichung von 0.057. Diese Werte bestätigen, dass das Modell auf den Trainingsdaten zuverlässig ist, jedoch zeigt die Varianz in den MSE-Werten, dass es nicht immer konsistent auf verschiedenen Test-Splits abschneidet, was auf mögliche Unsicherheiten hindeutet.
Um die Modellkomplexität besser zu kontrollieren, wurde ein Random Forest Modell mit einer festgelegten Baumtiefe von 7 getestet. Die Ergebnisse zeigten eine reduzierte erklärte Varianz von 42.24% und eine erhöhte MSE von 0.422 auf den Trainingsdaten. Diese Verschlechterung weist auf eine Unteranpassung hin, die auftritt, wenn die Modellkomplexität zu niedrig ist, um die zugrunde liegenden Muster in den Daten angemessen zu erfassen. Hier zeigt sich, dass eine Baumtiefe von 7 das Modell zu sehr einschränkt und wichtige Informationen im Datensatz möglicherweise nicht berücksichtigt werden.
Das Hyperparameter-Tuning mittels GridSearchCV ermöglichte es, die besten Modellparameter zu identifizieren. Die besten Parameter waren eine Baumtiefe von 4 und 200 Entscheidungsbäume. Trotz dieser Optimierung war die erklärte Varianz des Modells auf den Trainingsdaten nur 19.33%, was auf signifikantes Overfitting hindeutet. Overfitting tritt auf, wenn ein Modell so komplex wird, dass es die Trainingsdaten zu gut anpasst und dadurch in der Lage ist, Rauschen oder spezifische Details zu lernen, die nicht verallgemeinerbar sind. Dies zeigt sich in der erhöhten MSE auf den Testdaten, die anzeigt, dass das Modell Schwierigkeiten hat, auf neuen, unbekannten Daten gut abzuschneiden.
Zusätzlich wurden Entscheidungsbaum-Modelle mit verschiedenen Tiefen getestet, um die Auswirkungen der Baumtiefe auf die Modellleistung zu untersuchen. Interessanterweise zeigte ein Baum mit einer Tiefe von 1 die beste Leistung auf den Testdaten. Dies deutet darauf hin, dass tiefere Bäume in diesem Fall möglicherweise zu komplex wurden und zusätzliche, nicht generalisierbare Muster lernten, die auf den Testdaten zu einer höheren MSE führten. Ein einfaches Modell mit geringer Tiefe kann oft die zugrunde liegenden Trends besser einfangen, ohne sich in den Details der Trainingsdaten zu verlieren.
Zusammenfassend lässt sich sagen, dass der Random Forest Regressor eine leistungsfähige Methode zur Modellierung des GWL darstellt, indem er die Robustheit und Genauigkeit durch die Aggregation mehrerer Entscheidungsbäume verbessert. Die Untersuchung zeigt jedoch, dass sowohl Überanpassung als auch Unteranpassung potenzielle Herausforderungen darstellen können. Während das Modell ohne Einschränkung der Baumtiefe eine hohe erklärte Varianz aufwies, führte die Begrenzung der Baumtiefe zu Unteranpassung, und das Hyperparameter-Tuning zeigte, dass Overfitting ein Problem darstellt. Insgesamt lässt sich festhalten, dass die Variablen Temperatur sowie Niederschlag den Grundwasserspiegel der Messstellen in Sachsen gut vorhersagen können.
1.2.7. Literatur#
Wunsch, A., et al. (2022). Deep learning shows declining groundwater levels in Germany until 2100 due to climate change. Nature Communications. 13:1221. doi: https://doi.org/10.1038/s41467-022-28770-2