- Startseite
- Allgemeine Informationen zur Lehrveranstaltung
- Einfaches Python Setup und Wichtige Informationen
- 0. Python Einführung und Checkup
- 1. Einführung und einfache Algorithmen
- 2. Differenzieren und Integrieren
- 3. Vektoren, Matrizen und Vektorisierung in Python
- 4. Datenanalyse bzw. Datenauswertung
- 5. Grundlagen der Optimierung und Gradient Descent
- 6. Stochastische Optimierung und Genetische Algorithmen
- 7. Monte-Carlo-Methoden – Simulation und Integration
- 8. Monte-Carlo-Methoden, Teil 2 – Monte-Carlo-Integration, Teil 2 und Random Walk
- 9. Unsupervised Machine Learning: Clustering von Daten
- 10. Supervised Machine Learning: Grundlagen
- 11. Einführung in künstliche neuronale Netzwerke
Die Jupyter-Notebooks zur Lehrveranstaltung finden Sie im zugehörigen GitHub-Repository.
10 Supervised Machine Learning: Grundlagen¶
In dieser Einheit beschäftigen wir uns mit den Grundlagen des Supervised Machine Learnings. Der Begriff “supervised” bedeutet in diesem Zusammenhang, dass es zu jedem Datenpunkt einen Wert gibt, der die für das Machine Learning interessante Eigenschaft des Datenpunkts bezeichnet.
Das kann z.B. die Zuordnung zu einer Klasse (bei einem Klassifikationsproblem) sein, oder ein numerischer Wert (bei einem Regressionsproblem). Was auch immer es ist, der allgemeine Begriff dafür ist “Label”. Man spricht daher beim supervised Machine Learning auch grundsätzlich von “labeled data”, also annotierten Daten.
Im Gegensatz zum unsupervised Learning, das wir in der vorangegangenen Einheit behandelt und ausprobiert haben, gibt es hier also recht direkte Möglichkeiten, zu versuchen, dem eigenen Computerprogramm eine Vorhersagekraft für die Eigenschaften eines Datensatztes “beizubringen”. Dieser Prozess wird daher recht treffend auch als “Training” bezeichnet. Wie das genau vor sich geht, das werden wir uns noch etwas detaillierter ansehen.
Zunächst möchte ich Ihnen aber einfach einmal ein Beispiel für gelabelte Datensätze zeigen. Fangen wir gleich mit Daten für ein Klassifikationsproblem an. Zunächst aber noch die Imports für heute.
%matplotlib inline
import matplotlib.pyplot as plt # für plotting, wie gewohnt
import numpy as np # für numerische Aktionen mit Arrays, wie gewohnt
# hier die Funktionen für die verschiedenen Schritte des Supervised Learning:
from sklearn.datasets import make_moons, make_circles # zur Erzeugung von Datensets
from sklearn.model_selection import train_test_split # Aufteilen der daten in Train und Test
from sklearn.tree import DecisionTreeClassifier, plot_tree # für Decision Tree Klassifikations-Algorithmus
from sklearn.metrics import accuracy_score # zum Einschätzen der Qualität der Vorhersage
10.1 Vorbereitung der Daten und der Labels für Supervised Learning mit Hilfe von Scikit-Learn¶
Zunächst müssen wir die Daten vorbereiten. Für viele Datensätze, die man z.B. auf kaggle.com finden kann, ist das zwar schon passiert, aber manchmal muss man da selbst noch etwas nachbessern. Das gilt vor allem dann, wenn man sich die Input-Daten (auch features genannt) und die Labels selbst aussuchen möchte. Damit wir allerdings nicht allzuviel Zeit damit zubringen, gibt es dafür eine kleine Abkürzung.
Wir werden hier einen Datensatz selbst erzeugen, und zwar bereits mit der Machine-Learning Package Scikit-Learn selbst. Dort gibt es eigene Funktionalität für die Erzeugung von Test-Datensätzen, die wir hier ausprobieren werden. Obwohl das sehr einfach geht und eigentlich “blind” verwendet werden kann, sehen wir uns genau an, wie die Daten zusammengesetzt und aufgebaut sind.
Ein Datensatz besteht aus einer Liste von $2$ Teilen:
- Den Inputs/Features, als NumPy-Array, d.h. eine Matrix, in deren Zeilen die Input-Daten-Vektoren stehen
- Den Labels als Array, allerdings nur als eindimensionales, weil dort für jeden Input-Vektor nur eine Zahl (die Klassen-ID) steht.
Mit Scikit-Learn kann man verschieden “geformte” 2D-Punktwolken erzeugen und diese als Datensätze ausgeben lassen. Dazu verwenden wir Funktionen aus dem Modul _sklearn.datasets_:
# Erzeuge einen mondförmigen Datensatz mit 2 Klassen (also 2 Mond-Punktwolken)
# noise bedeutet, wie sehr die Monde "zerstreut" werden
# der random_state sorgt wieder für Reproduzierbarkeit
raw_data = make_moons(n_samples=500, noise=0.1, random_state=0)
# Der Output hat zwei Teile, der erste sind die Inputs
input_data = raw_data[0]
# der zweite sind die Labels
label_data = raw_data[1]
# Sehen wir uns das kurz an
# Die ertsen 10 Input-Daten
print("Features:\n", input_data[:10])
# Die Labels (und zwar alle 500)
print("Labels:\n", label_data)
Features: [[ 0.36203373 0.9014949 ] [-0.19235477 0.46843193] [ 0.13782021 0.10441207] [ 1.74566019 -0.12007051] [ 1.92715809 -0.23854152] [ 1.17773635 0.22713419] [ 0.26188662 -0.27885379] [ 2.0414255 0.36255569] [-0.62473358 1.11320091] [-0.36293508 1.03806201]] Labels: [0 1 1 1 1 0 1 1 0 0 0 1 1 1 0 1 1 1 0 0 0 1 0 0 0 1 0 1 0 0 1 0 0 0 1 1 1 1 1 0 1 0 1 0 0 0 0 1 1 0 1 0 1 0 0 0 1 0 0 1 0 1 1 1 1 0 0 1 1 0 0 0 1 1 0 1 1 0 1 1 1 0 1 0 1 1 1 0 1 0 0 1 1 0 0 1 0 1 0 0 0 1 0 1 0 1 0 0 1 0 1 0 0 0 1 1 1 1 0 0 1 0 0 1 1 0 0 1 1 1 1 0 1 0 1 1 1 1 1 0 0 0 0 0 1 0 1 1 0 0 0 1 0 1 0 0 0 0 1 0 1 0 0 1 0 0 1 1 1 1 1 0 1 1 1 0 1 0 1 1 1 0 0 1 0 1 1 1 1 0 1 1 0 1 1 1 1 0 0 0 0 0 0 1 0 1 0 1 0 1 1 0 0 1 0 1 0 0 0 1 0 0 0 0 1 1 0 0 0 0 1 0 1 0 1 1 1 0 0 1 0 0 1 1 1 1 1 0 0 0 1 0 0 0 0 0 0 0 1 0 0 0 1 0 1 1 1 0 0 1 1 0 1 1 0 0 0 1 1 1 0 1 0 0 0 0 1 0 0 0 0 0 0 0 1 1 0 1 1 0 1 1 0 1 1 1 0 0 0 1 1 0 0 0 0 1 0 1 0 0 1 1 1 1 1 0 1 0 1 1 1 0 1 0 1 0 1 1 0 0 1 1 1 1 1 0 1 0 1 1 0 1 0 1 0 1 0 0 1 0 0 1 1 1 0 0 0 1 0 0 0 0 1 0 0 1 1 0 1 1 1 0 1 1 0 1 0 1 0 0 1 0 1 1 0 1 0 0 0 1 0 1 0 1 1 1 1 0 0 0 1 1 1 1 1 0 0 1 0 1 1 0 1 1 1 1 0 0 1 1 1 1 0 1 0 1 1 0 1 0 0 1 0 0 0 0 0 0 0 0 1 1 1 1 1 1 0 1 1 0 0 1 1 1 1 0 0 1 0 1 1 0 1 0 0 1 1 0 1 0 0 1 1 0 1 0 0 1 0 1 0 0 1 0 1 1 0 0 0 0]
# sehen wir uns das am besten gleich mal als Plot an
fig=plt.figure()
# setzen wir das Skalenverhältnis von x und y auf 1
ax = plt.gca()
ax.set_aspect(1)
# Ein Scatterplot, wie wir ihn schon gewohnt sind, mit Farben nach Klassen
plt.scatter(*np.transpose(input_data), c=label_data)
plt.show()
Das sieht wirklich sehr schön mondförmig aus (mit dem anfänglichen Wert von noise von $0.1$. Das “Rauschen” könnten wir allerdings auch etwas aufdrehen, z.B. auf $0.9$, dann sieht das so aus:
# noise ist diesmal auf 0.9 gesetzt
raw_data = make_moons(n_samples=500, noise=0.9, random_state=0)
# Der fertige Datensatz hat zwei Teile, der erste sind die Inputs
input_data = raw_data[0]
# der zweite sind die Labels
label_data = raw_data[1]
# sehen wir uns das am besten gleich mal als Plot an
fig=plt.figure()
# Achsen-Skalen-Ratio wieder auf 1 setzen
ax = plt.gca()
ax.set_aspect(1)
# Der gleiche Scatterplot, mit Farben nach Klassen
plt.scatter(*np.transpose(input_data), c=label_data)
plt.show()
Von Monden ist hier nicht mehr so viel zu sehen, aber man kann sie noch erahnen (wenn man es weiß). Lassen wir mal für die folgenden Experimente mit den verschiedenen Classifiern den noise-Wert auf $0.9$, damit es etwas interessanter wird.
10.2 Ausgewogenheit der Daten beim Supervised Learning¶
Eine Sache ist jedoch noch wichtig zu erwähnen, bevor wir zum Training kommen. Und zwar handelt es sich dabei um die Ausgewogenheit des Datensatzes, was die Labels betrifft. Es ist grundsätzlich wichtig auf einen ausgewogenen (balanced) Datensatz zu achten, damit es nicht zu (teilweise brutalen) Artefakten bei Vorhersagen kommt.
Als extremes Beispiel stellen Sie sich kurz einen anderen Datensatz vor, bei dem es $99$ Daten der einen Klasse und nur einen einzigen Datenpunkt aus der anderen Klasse gibt. Auf diesen Daten ein gutes Modell zu trainieren, ist sehr schwierig. Aber es ist außerdem noch genauso schwierig, ein gutes Modell von einem komplett einseitigen Modell zu unterscheiden, und das kommt so:
Ein Modell, das immer nur die vorherrschende Klasse vorhersagt, liegt damit nämlich bereits zu $99$ Prozent richtig. Das ist eigentlich für jedes Machine-Learning-Problem eine beeindruckende Performance. Trotzdem ist das Modell eigentlich unbrauchbar, gerade dann, wenn es auf die seltenen Fälle (aus der wenig repräsentierten Klasse) ankommt, weil man z.B. die Ausnahmen gut vorhersagen will.
Das Fazit dieses kurzen Ausflugs: Ein ausgewogener Datensatz ist sehr wichtig für erfolgreiches Training. Schauen wir kurz nach, wie ausgewogen unser Datensatz mit den Monden ist:
# sehen wir uns grafisch an, wie die Labels in diesem Datensatz verteilt sind
fig = plt.figure()
# Erzeuge ein Histogramm der Labels
plt.hist(label_data, bins=[0, 1, 2], width=0.3)
# setze die x-Werte auf die Klassen-Indizes fest
plt.xticks([0, 1])
# Titel und Achsenbeschriftungen
plt.title("Moons Test Dataset")
plt.xlabel("Label")
plt.ylabel("Count")
# Plot anzeigen
plt.show()
Diese Verteilung ist hier also schön ausgeglichen, so wie es sein soll, wir haben hier also unsere “balanced data”. Damit haben wir jetzt was die Vorbereitung der Daten betrifft unsere Schuldigkeit getan und können damit den nächsten Schritte tun.
10.2 Die wichtigsten Schritte beim Supervised Learning im Allgemeinen¶
Das Prozedere beim Supervised Learning ist üblicherweise folgendermaßen:
- Die Daten werden zunächst vorbereitet und überprüft (das haben wir gerade getan).
- Dann werden die Daten in zwei (oder drei) Teile geteilt, nämlich in einen Trainings-Teil und einen Test-Teil (und einen Validierungsteil, damit man Hyper-Parameter tunen kann, dazu kommen wir in der nächsten Einheit).
- Der Trainingsteil sollte üblicherweise größer sein als der Rest, z.B. $80$ – $10$ – $10$ Prozent oder $60$ – $20$ – $20$. Wir werden es uns hier einfach machen und $80$ zu $20$ Prozent aufteilen und auf ein separates Validierungsset verzichten.
- Beim Aufteilen werden die Daten üblicherweise auch durchgemischt. Das führt dazu, dass nicht lauter gleiche Labels hintereinander kommen, sondern auch die Reihenfolge ausbalanciert ist (wichtig fürs Training).
- Dann bekommt der Machine-Learning-Algorithmus die Trainingsdaten, um sie zu fitten. Das geschieht je nach Algorithmus auf verschiedene, geeignete Arten.
- Anschließend wird das erhaltene Machine-Learning-Modell auf den Testdaten ausprobiert. Das bedeutet, man schickt die Testdaten durch das Modell und vergleicht die vorhergesagten Ergebnisse mit den tatsächlichen Labels der Daten.
- Beim Testen erhält man einen Score, z.B. das Verhältnis von richtig vorhergesagten Datenpunkten zu falsch vorhergesagten.
- Je besser dieser Score, desto besser. Allerdings sollte man jedenfalls besser sein als zufälliges Raten, was z.B. bei $2$ Klassen $50$ Prozent wäre.
Für alle diese Dinge verwenden wir durchgängig die Package Scikit-Learn, aus der wir ja in der vergangenen Einheit auch bereits die Algorithmen für das Unsupervised Learning importiert hatten.
10.3 Aufteilen der Daten in Trainingsdaten und Testdaten¶
So, nun konkret zu den Schritten. Teilen wir zunächst die Daten auf. Das geht ganz einfach mit einer Funktion aus Scikit-Learn, nämlich:
# üblicherweise werden Daten beim Supervised Learning als X und y bezeichnet
# Ja, das X ist wirklich groß und das y ist wirklich klein geschrieben :)
# Hier bekommen wir eine zufällige Aufteilung (shuffle bedeutet durchmischen)
# Für reproduzierbare Aufteilung den random_state auf einen Integer setzen
X_train, X_test, y_train, y_test = train_test_split(input_data, label_data, test_size=0.2,
random_state=None, shuffle=True)
# hier der Anfang von X_train
X_train[:5]
array([[ 1.20762976, -0.06936382], [-1.10272156, -0.83486623], [ 2.14888567, 0.42380907], [ 2.57691521, 1.10126134], [ 2.47763826, -0.73174052]])
# und die Labels dazu
y_train[:5]
array([1, 0, 1, 1, 1])
# Sehen wir uns die Verteilung der Punkte im Plot an
fig=plt.figure(figsize=(15,8))
# Subplot für die Trainingsdaten
ax1 = plt.subplot(1,2,1)
ax1.set_aspect(1)
# Ein Scatterplot für die Trainingsdaten, mit Farben nach Klassen
ax1.scatter(*np.transpose(X_train), c=y_train)
ax1.set_title("Train")
# Subplot für die Testdaten
ax2 = plt.subplot(1,2,2)
ax2.set_aspect(1)
# Ein Scatterplot für die Testdaten, mit Farben nach Klassen
ax2.scatter(*np.transpose(X_test), c=y_test)
ax2.set_title("Test")
plt.show()
Das Modell soll also anhand der Punkte (mit Labels) auf der linken Seite lernen und die Punkte auf der rechten Seite möglichst richtig klassifizieren können, ohne diese beim Training gesehen zu haben.
10.4 Supervised Learning: Das Training am Beispiel Decision Tree¶
Jetzt können wir unsere Daten bereits in ein Machine-Learning-Modell füttern. Als Beispiel eignen sich hier einige aus dem Supervised-Learning-Fundus von Scikit-Learn. Wir beginnen mit einem Decision Tree (Entscheidungsbaum). Dabei geht es darum, aus den Werten einzelner Inputs bzw. features Entscheidungen abzuleiten, die dann zum richtigen Ergebnis führen (im Mittel auf den Trainingsdaten). Beispiele dafür in unserem Fall wären Statements wie
- Wenn $x<-0.5$, dann ist das Klasse 0
- Wenn $x>0.5$, dann ist das Klasse 1
- Wenn $-0.5<x<0.5$ und $y>1$, dann ist das Klasse 0
- usw.
Jetzt aber zum konkreten Aufruf für das Training. In Scikit-Learn sind alle Algorithmen fix und fertig implementiert, sodass man sie im Prinzip nur starten muss. Dazu muss man meist eine Instanz einer Klasse erzeugen, die wir im Allgemeinen dann direkt als “Modell” bzw. model bezeichnen, und dann dafür einen “Fit” aufrufen, womit das Training gemeint ist.
# Aufruf der Klasseninstanz für den Decision Tree Hier könnte man auch noch
# diverse Parameter einstellen, wir schenken uns das aber für den Moment einmal
model = DecisionTreeClassifier()
# damit ist jetzt das Modell definiert, und wir können diese Instanz verwenden
# Aufruf des Fits. Danach hat die Instanz die Ergebnisse des Trainings parat
# Dieser Teil kann, je nach Komplexität der Daten und des Modells, eine Zeit lang dauern
model.fit(X_train,y_train)
DecisionTreeClassifier()
# Als nächstes rufen wir die Vorhersage der Werte auf dem
# Test-Teil des Datensatzes auf. Zur Erinnerung: Diese Daten hat
# das Modell während des Trainings nicht gesehen
y_prediction = model.predict(X_test)
# Das Ergebnis ist einfach ein Vektor mit vorhergesagten Labels
# Was sind z.B. die ersten 5 Predictions aus dem Test-Set?
y_prediction[:5]
array([1, 0, 1, 0, 0])
# Und wie vergleicht sich das mit den echten Labels auf dem Testset?
y_test[:5]
array([1, 0, 1, 0, 1])
# naja, das ist noch nicht sehr aussagekräftig.
# Plotten wir nun das Test-Set zweimal, eimmal mit den echten Klassen gefärbt,
# einmal mit den vorhergesagten
fig=plt.figure(figsize=(15,8))
# Subplot für die echten Test-Daten und Labels
ax1 = plt.subplot(1,2,1)
ax1.set_aspect(1)
# Ein Scatterplot für die Testdaten, mit Farben nach echten Klassen
ax1.scatter(*np.transpose(X_test), c=y_test)
ax1.set_title("Actual")
# Subplot für die Testdaten mit vorhergesagten Labels
ax2 = plt.subplot(1,2,2)
ax2.set_aspect(1)
# Ein Scatterplot für die Testdaten, mit Farben nach vorhergesagten Klassen
ax2.scatter(*np.transpose(X_test), c=y_prediction)
ax2.set_title("Prediction")
plt.show()
# Das ist im ersten Moment etwas schwer zu erkennen ...
# wir könnten aber auch noch die Unterschiede markieren ...
fig=plt.figure(figsize=(15,8))
# definiere Farbenliste je nach übereinstimmenden Labels (oder unterschiedlichen)
edge_colors = []
# Loop über gezippte Arrays für echte und vorhergesagte Labels
for test_point, pred_point in zip(y_test,y_prediction):
# Überprüfe, ob die Labels sich unterscheiden
if test_point != pred_point:
# Ja, unterscheiden sich, umrande den Punkt rot
edge_colors.append('r')
else:
# Nein, sind gleich, umrande den Punkt weiß (d.h. nicht)
edge_colors.append('w')
# Subplot für echte Labels
ax1 = plt.subplot(1,2,1)
ax1.set_aspect(1)
# Ein Scatterplot für die Testdaten, mit Farben nach echten Klassen und mit Rändern für Unterschiede
ax1.scatter(*np.transpose(X_test), c=y_test, edgecolors=edge_colors)
ax1.set_title("Actual")
# Subplot für vorhergesagte Labels
ax2 = plt.subplot(1,2,2)
ax2.set_aspect(1)
# Ein Scatterplot für die Testdaten, mit Farben nach vorhergesagten Klassen und mit Rändern für Unterschiede
ax2.scatter(*np.transpose(X_test), c=y_prediction, edgecolors=edge_colors)
ax2.set_title("Prediction")
# die Unterschiede sollten jetzt besser zu erkennen sein
plt.show()
10.5 Supervised Learning: Überprüfen der Genauigkeit der Vorhersagen des Machine-Learning-Modells¶
Als nächstes wollen wir nun wissen, wie gut unsere Vorhersagen quantitativ sind. Dazu vergleichen wir die Output-Labels des Modells mit den tatsächlichen Labels des Test-Sets. Dafür gibt es in Scikit-Learn mehrere sogenannte _metrics_, die wir ebenfalls einfach aufrufen können, z.B. _accuracy_, das ist im Wesentlichen der Anteil der korrekten Vorhersagen an allen Vorhersagen.
# Berechne Metrik accuracy für die Qualität der Vorhersage
accuracy_score(y_test, y_prediction)
0.7
Ist das jetzt gut oder nicht? Erinnern wir uns, dass die Hälfte der Daten Klasse $0$ ist, die andere Hälfte Klasse $1$. Das bedeutet, dass ein rein zufälliges Raten zu $50$ Prozent Genauigkeit führen müsste. Checken wir das einmal kurz ganz einfach, indem wir zufällig gewählte Klassen-Indizes mit den Test-Labels vergleichen:
# Berechne die Metrik accuracy für eine Zufallsauswahl aus 0en und 1en
accuracy_score(y_test, np.random.choice([0, 1], size=len(y_test), replace=True))
0.51
Das ist tatsächlich in der Nähe von $50$ Prozent. Die Abweichung kommt daher, dass wir hier mit $100$ Testdaten arbeiten, und das vergleichsweise wenige sind, sodass eine solche Abweichung vorkommen kann. Dagegen kann man allerdings die Qualität des Decision Trees nicht von vornherein einschätzen. Vielleicht können wir den ja noch etwas besser machen.
10.6 Verbessern eines Machine-Learning-Modells beim Supervised Learning durch Verändern der Parameter am Beispiel Decision Tree¶
In der folgenden Zelle kommt ein Aufruf, der zu einem weiteren Modell, model1, führt. Dabei werden wir die Struktur eines Decision Trees intuitiv etwas besser kennen lernen. Ein solcher Baum hat eine oder mehrere Verzweigungen, die zu weiteren Verzweigungen (in einer bestimmten Anzahl von Ebenen) oder “Blättern” führen können. Alle diese Teile heißen auf englisch nodes.
Man kann nun den Baum auf ein paar Arten einschränken, sodass z.B.:
- Eine maximale Anzahl von Blättern erlaubt ist
- Eine maximale Anzahl von Verzweigungs-Ebenen erlaubt ist
Diese beiden Parameter werden wir nun bei der Erzeugung der Instanz mit übergeben und so unseren Baum einschränken. Keine Sorge, sie werden gleich sehen, was das bedeutet und wie sich der Baum verändert, denn wir können ihn mit Hilfe einer plot_tree Funktion auch gleich visualisieren.
Grundsätzlich gilt, dass ein Baum nicht mehr Blätter haben kann, als seine Verzweigungen zulassen, die jeweils immer nur von einem node in der höheren Ebene zu zwei nodes in der darunter liegenden Ebene führen können. Bei Einer Ebene kann es also maximal zwei Blätter geben, bei zwei Ebenen vier Blätter, etc. Daher können wir die max_leaf_nodes auf eine größere Zahl setzen, z.B. auf $10$, und dann wird die maximale Anzahl der Ebenen im Wesentlichen bestimmen, wie viele Nodes und Ebenen wie verwendet werden.
Fangen wir mit einer Ebene an und gehen wir dann einfach mit dem Parameter max_depth immer höher. Diese Zellen setze ich hier einfach mehrfach untereinander
# hier also nochmal der Aufruf mit den beiden besprochenen Optionen, maximal eine Subebene
model_1 = DecisionTreeClassifier(max_depth=1, max_leaf_nodes=10)
# training des neuen Modells
model_1.fit(X_train,y_train)
# Vorhersage auf den Test-Daten
y_prediction = model_1.predict(X_test)
# und Genauigkeitsberechnung
print("Accuracy:", accuracy_score(y_test, y_prediction))
# das Plotten funktioniert genau wie sonst auch bei Figures
fig = plt.figure(figsize=(15,10))
# hier die Funktion für das Plotten des Baums
plot_tree(model_1, fontsize=15)
# und Anzeigen
plt.show()
Accuracy: 0.68
Die Relation in der ersten Zeile des Verzweigungsnodes ist die Bedingung, nach der die Datenpunkte aufgeteilt werden. Die Anzahl der aufgeteilten Punkte steht dann jeweils in den Nodes in der darunterliegenden Ebene. “gini” ist das Entscheidungskriterion, um eine optimale Bedingung zu finden. Konkret ist es ein Maß für die “Unterschiedlichkeit” in der Gruppe von Datenpunkten, sollte also idealerweise möglichst klein sein. Und im Array “value” finden sich die Anzahlen für die Klassenanteile der Daten in dieser Gruppe/diesem Node.
Weiter geht es mit einer Subebene mehr:
# hier nochmal, diesmal mit 2 Subebenen
model_1 = DecisionTreeClassifier(max_depth=2, max_leaf_nodes=10)
# training des neuen Modells
model_1.fit(X_train,y_train)
# Vorhersage auf den Test-Daten
y_prediction = model_1.predict(X_test)
# und Genauigkeitsberechnung
print("Accuracy:", accuracy_score(y_test, y_prediction))
# wieder plotten
fig = plt.figure(figsize=(15,10))
# der gleiche Baum-Plotbefehl wie vorhin
plot_tree(model_1, fontsize=15)
# und Anzeigen
plt.show()
Accuracy: 0.65
# und nochmal, diesmal mit 3 Subebenen
model_1 = DecisionTreeClassifier(max_depth=3, max_leaf_nodes=10)
# training des neuen Modells
model_1.fit(X_train,y_train)
# Vorhersage auf den Test-Daten
y_prediction = model_1.predict(X_test)
# und Genauigkeitsberechnung
print("Accuracy:", accuracy_score(y_test, y_prediction))
# wieder Plotten
fig = plt.figure(figsize=(15,10))
# der Baum
plot_tree(model_1, fontsize=15)
# und Anzeigen
plt.show()
Accuracy: 0.67
# und noch ein letztes Mal, diesmal mit 4 Subebenen, dafür reichen
# die 10 Blätter bereits nicht mehr ganz zum Ausfüllen aus
model_1 = DecisionTreeClassifier(max_depth=4, max_leaf_nodes=10)
# training des neuen Modells
model_1.fit(X_train,y_train)
# Vorhersage auf den Test-Daten
y_prediction = model_1.predict(X_test)
# und Genauigkeitsberechnung
print("Accuracy:", accuracy_score(y_test, y_prediction))
# Plotten
fig = plt.figure(figsize=(15,10))
# der Baum
plot_tree(model_1, fontsize=15)
# und Anzeigen
plt.show()
Accuracy: 0.68
Hier kann man sich sehr schön die Entwicklung der Bäume mit immer mehr Unter-Ebenen ansehen. Ebenso schön sieht man, wie der Baum versucht, die Datenpunkte möglichst gut und clever aufzuteilen, und wie manche der Blätter-Nodes sogar schon eine 0 auf einer Seite der “values” stehen haben. Was ist aber jetzt eigentlich mit der Genauigkeit insgesamt passiert? Sehen wir nach:
Bereits bei der Einschränkung auf eine Ebene hatte sich der Wert der accuracy etwas verschlechtert, und dann bei zwei Ebenen nochmal. Bei drei Ebenen und dann auch bei vier Ebenen ist der Wert der accuracy allerdings wieder besser geworden, d.h., wie der Baum aussieht, spielt hier eine Rolle. Aber Achtung: Wie gut der Baum performt, kann von verschiedenen Dingen abhängen:
- Von dem Datensatz, der untersucht wird
- Von der Ausgeglichenheit des Datensatzes
- Von der (zufälligen) Aufteilung in Training und Test
Wenn sich davon etwas ändert, z.B. die Train-Test-Aufteilung, dann kann die Performance-Kurve für unsere Daten und für die gewählten Parameter durchaus anders aussehen und auch umgekehrte Trends aufweisen.
Kommen wir aber nochmal zum Anfang zurück: Da wir dort (ohne Optionen im Aufruf) ja im Prinzip den besten Performance-Wert erhalten hatten, stellt sich die Frage:
Was haben wir denn da dann ursprünglich eigentlich für einen Baum verwendet? Hier ist er:
# nochmal Plotten
fig = plt.figure(figsize=(15,10), dpi=150)
# hier die Funktion für das Plotten des ersten Baums ohne jegliche Parameter
# die Fontsize ist hier etwas kleiner gewählt, damit man die Boxen besser im Überblick sieht
plot_tree(model, fontsize=4)
# und Anzeigen
plt.show()
Insgesamt sieht man hier, dass Supervised Learning viel mit Verständnis der Situation, der Daten, des Modells, dessen Möglichkeiten und der Hintergründe zu tun hat. Insgesamt ist hier auf jeden Fall die eigene Erfahrung wesentlich, denn nur über solche Dinge nachzulesen hilft für viele Probleme nicht weiter.
Damit Sie selbst gleich ein Bisschen Erfahrung sammeln können, kommen wir daher jetzt zur Übungsaufgabe dieser Einheit:
10.7 Übungsaufgabe: Experimentieren mit Supervised-Learning Algorithmen aus der Scikit-Learn Package¶
Nach dieser Einführung wissen Sie folgendes:
- Wie ein Datensatz beim Supervised Learning strukturiert ist
- Wie man einfache Datensätze zum Trainieren in Python mit Scikit-Learn erzeugen kann
- Wie Supervised Learning grundsätzlich abläuft
- Wie Sie einfache Modelle mit Scikit-Learn trainieren
- Wie Sie mit einem trainierten Modell Vorhersagen machen
- Wie Sie die Qualität einer Vorhersage auf einem Test-Set überprüfen
- Wie Sie Ergebnisse und andere hilfreiche Informationen visualisieren können
Diese Vorgehensweise ist immer die gleiche. In Scikit-Learn ist auch die grundsätzliche Code-Struktur immer gleich. Wenn Sie also eine Instanz eines Machine-Learning-Modells erzeugt haben, dann funktioniert das Training und die Vorhersagen immer gleich. Alles, was Sie austauschen müssen, sind die Modell-Aufrufe (und die eventuell damit verbundenen Parameter).
Gehen Sie nun in der Scikit-Learn-Übersicht für Supervised Learning auf die Suche nach weiteren interessanten Algorithmen, die Sie gerne auf unseren Datensatz loslassen würden. Oder erzeugen Sie einen eigenen Datensatz nach Ihren Wünschen und experimentieren Sie damit. Ich wünsche viel Vergnügen!
Hier schon mal ein paar Zeilen als Inspiration/Start, denn die Möglichkeiten sind sehr vielfältig und umfangreich (damit könnten wir mehrere zusätzliche Lehrveranstaltungen füllen).
from sklearn.svm import SVC # für Support-Vector Machine
from sklearn.naive_bayes import GaussianNB # für Naive Bayes Classifier
from sklearn.neighbors import NearestNeighbors # für Nearest-Neighbor