Grenzen des Maschinellen Lernens

Das Maschinelle Lernen stößt an bestimmten Stellen an seine Grenzen. Für die meisten Business-Cases wird mithilfe Maschinellen Lernens nicht-lineare Zusammenhänge in strukturierten Datensätzen gesucht. Diese Zusammenhänge liegen in der Praxis aber gar nicht zwangsläufig vor. Dies hält aber manche Praktiker nicht davon ab, die Auswertung der eigenen Daten immer wieder zu verändert, bis auf einmal das Modell zu funktionieren scheint. Dies wird P-Hacking genannt (der Begriff stammt eigentlich aus der Statistik und hatte ursprünglich eine leicht andere Bedeutung). Der Begriff bedeutet, dass das lang erwünschte Ergebnis leider rein zufällig zustande kam. Dies tritt vor allem bei kleinen Datensätzen auf.

Um besonders gute Genauigkeitswerte für Modell des Maschinellen Lernens zu erhalten, können z. B. Scheinkorrelationen (d.h. lineare zufällige Zusammenhänge) oder andere nicht-lineare zufällige Zusammenhänge „ausgenutzt“ werden. Wenn genügend Attribute gleichzeitig betrachtet werden, gibt es mit hoher Wahrscheinlichkeit ein Attribut, welches einen Zusammenhang mit der Zielvariable aufweist. Deswegen sollte immer sehr kritisch betrachtet werden, aus welchen Daten welches Ergebnis angeblich vorhersagbar sein soll.

So ein Verhalten ist ein Problem für alle Bereiche: In der Wissenschaft werden unter Umständen falsche Annahmen und Vorhersagemodelle verwendet. In der Praxis bedeutet es, dass ggf. defekte Vorhersagemodelle in den Betrieb aufgenommen werden. Sobald sich jemand auf die Vorhersagen des falschen Modells verlässt, kann dies zu ernsthaften Schäden an Menschen oder Umwelt führen. Ebenso sind finanzielle Schäden nicht ausgeschlossen.

[1]:
import random
import pandas as pd
import sklearn.tree
/tmp/ipykernel_2188/3939139406.py:2: DeprecationWarning:
Pyarrow will become a required dependency of pandas in the next major release of pandas (pandas 3.0),
(to allow more performant data types, such as the Arrow string type, and better interoperability with other libraries)
but was not found to be installed on your system.
If this would cause problems for you,
please provide us feedback at https://github.com/pandas-dev/pandas/issues/54466

  import pandas as pd

Es wird zunächst ein Datensatz mit zufälligen Attribute erzeugt. Dies könnte z. B. die Zahlenrepräsentation von nominalskalierten Attributen sein.

[2]:
# Fixiere Zufallsgenerator für random.choice
random.seed(0)

number_rows = 50

df = pd.DataFrame({
    "Outlook": [random.choice(["Sunny", "Overcast", "Rain"]) for _ in range(number_rows)],
    "Temperature": [random.choice(["Hot", "Mild", "Cool"]) for _ in range(number_rows)],
    "Humidity": [random.choice(["High", "Normal"]) for _ in range(number_rows)],
    "Wind": [random.choice(["Weak", "Strong"]) for _ in range(number_rows)],
    "Play Tennis?": [random.choice(["Yes", "No"]) for _ in range(number_rows)],
})

df
[2]:
Outlook Temperature Humidity Wind Play Tennis?
0 Overcast Cool Normal Weak No
1 Overcast Cool Normal Strong No
2 Sunny Hot High Weak No
3 Overcast Cool High Weak No
4 Rain Mild High Weak Yes
5 Overcast Mild High Strong No
6 Overcast Hot High Weak No
7 Overcast Cool High Strong No
8 Overcast Mild Normal Weak Yes
9 Overcast Cool Normal Weak Yes
10 Rain Hot High Weak Yes
11 Sunny Hot High Strong No
12 Rain Cool Normal Strong Yes
13 Sunny Hot Normal Strong Yes
14 Overcast Hot Normal Weak Yes
15 Sunny Hot Normal Weak No
16 Sunny Cool Normal Strong No
17 Rain Mild High Weak No
18 Overcast Hot Normal Weak Yes
19 Rain Hot High Strong No
20 Rain Mild Normal Weak No
21 Rain Cool Normal Strong Yes
22 Sunny Mild High Strong Yes
23 Overcast Hot High Strong No
24 Sunny Mild High Weak Yes
25 Rain Cool Normal Weak No
26 Sunny Mild High Weak Yes
27 Rain Cool High Weak No
28 Overcast Hot Normal Weak No
29 Overcast Cool High Strong Yes
30 Rain Mild Normal Strong Yes
31 Sunny Cool Normal Weak No
32 Overcast Hot High Strong No
33 Overcast Cool High Weak No
34 Overcast Cool High Weak No
35 Rain Cool High Strong Yes
36 Rain Mild High Strong No
37 Sunny Mild High Strong Yes
38 Rain Hot High Strong Yes
39 Overcast Cool High Strong Yes
40 Overcast Mild High Strong Yes
41 Rain Mild High Weak No
42 Overcast Cool Normal Weak No
43 Sunny Hot High Strong No
44 Rain Mild Normal Weak Yes
45 Sunny Hot High Strong Yes
46 Sunny Hot High Weak Yes
47 Rain Hot High Strong Yes
48 Overcast Hot High Weak Yes
49 Rain Cool High Weak Yes
[3]:
df_cat = df.assign(**{
    col: df[col].astype('category').cat.codes for col in ["Humidity", "Wind", "Play Tennis?"]
})
df_cat = df_cat.assign(
    Outlook=df["Outlook"].astype(
        pd.CategoricalDtype(categories=["Rain", "Overcast", "Sunny"], ordered=True)).cat.codes,
    Temperature=df["Temperature"].astype(
        pd.CategoricalDtype(categories=["Cool", "Mild", "Hot"], ordered=True)).cat.codes
)
df_cat
[3]:
Outlook Temperature Humidity Wind Play Tennis?
0 1 0 1 1 0
1 1 0 1 0 0
2 2 2 0 1 0
3 1 0 0 1 0
4 0 1 0 1 1
5 1 1 0 0 0
6 1 2 0 1 0
7 1 0 0 0 0
8 1 1 1 1 1
9 1 0 1 1 1
10 0 2 0 1 1
11 2 2 0 0 0
12 0 0 1 0 1
13 2 2 1 0 1
14 1 2 1 1 1
15 2 2 1 1 0
16 2 0 1 0 0
17 0 1 0 1 0
18 1 2 1 1 1
19 0 2 0 0 0
20 0 1 1 1 0
21 0 0 1 0 1
22 2 1 0 0 1
23 1 2 0 0 0
24 2 1 0 1 1
25 0 0 1 1 0
26 2 1 0 1 1
27 0 0 0 1 0
28 1 2 1 1 0
29 1 0 0 0 1
30 0 1 1 0 1
31 2 0 1 1 0
32 1 2 0 0 0
33 1 0 0 1 0
34 1 0 0 1 0
35 0 0 0 0 1
36 0 1 0 0 0
37 2 1 0 0 1
38 0 2 0 0 1
39 1 0 0 0 1
40 1 1 0 0 1
41 0 1 0 1 0
42 1 0 1 1 0
43 2 2 0 0 0
44 0 1 1 1 1
45 2 2 0 0 1
46 2 2 0 1 1
47 0 2 0 0 1
48 1 2 0 1 1
49 0 0 0 1 1

Nun lass uns betrachten, ob der Entscheidungsbaum einen zufälligen Zusammenhang findet, und das Kreuzvalidierungsergebnis dennoch gut aussieht.

[4]:
dt = sklearn.tree.DecisionTreeClassifier(random_state=0)
eingabe = df_cat[list(set(df_cat.columns) - set(["Play Tennis?"]))]

ziel = df["Play Tennis?"]
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(eingabe, ziel, random_state=42)
dt.fit(X_train, y_train)
dt.score(X_test, y_test)
[4]:
0.46153846153846156

Aufgabe 1

Interpretieren Sie den Zahlenwert. Liegt hier ein zufälliger nicht-linearer Zusammenhang vor? Ist dieses Ergebnis plausibel? Warum (nicht)?

Antwort: …

Feature Engineering falsch gemacht

Normalerweise hilft Feature Engineering dabei, zielführende Zahlenwerte zu generieren. So ist es z. B. sinnvoll, statt einem Start- und einem Endzeitpunkt lieber gleich die Zeitspanne zu generieren, oder statt einer Länge und einer Breite gleich die Fläche zu berechnen. Es gibt aber auch Fälle, in denen Projektverantwortliche beliebige Attribute addiert, multipliziert etc. haben, ohne dass es dafür eine inhaltliche Rechtfertigung gab. Genau das werden wir jetzt auf die Spitze treiben.

Nun werden wir so lange neue Attribute erstellen, bis wir (mindestens) eines finden, mit dem der Entscheidungsbaum zu einem guten Ergebnis kommt.

[5]:
new_columns = {}

for column_A in list(set(df_cat.columns) - set(["Play Tennis?"])):
    for column_B in list(set(df_cat.columns) - set(["Play Tennis?"])):
        if column_A == column_B:
            continue

        addition = df_cat[column_A] + df_cat[column_B]
        addition.name = f"{column_A} + {column_B}"
        new_columns.update({addition.name: addition})

        subtraktion = df_cat[column_A] - df_cat[column_B]
        subtraktion.name = f"{column_A} - {column_B}"
        new_columns.update({subtraktion.name: subtraktion})

        multiplication = df_cat[column_A] * df_cat[column_B]
        multiplication.name = f"{column_A} * {column_B}"
        new_columns.update({multiplication.name: multiplication})

Diese kreierten Attribute fügen wir nun dem DataFrame hinzu.

[6]:
df_extended = df_cat.assign(**new_columns)
df_extended
[6]:
Outlook Temperature Humidity Wind Play Tennis? Outlook + Wind Outlook - Wind Outlook * Wind Outlook + Humidity Outlook - Humidity ... Humidity * Temperature Temperature + Outlook Temperature - Outlook Temperature * Outlook Temperature + Wind Temperature - Wind Temperature * Wind Temperature + Humidity Temperature - Humidity Temperature * Humidity
0 1 0 1 1 0 2 0 1 2 0 ... 0 1 -1 0 1 -1 0 1 -1 0
1 1 0 1 0 0 1 1 0 2 0 ... 0 1 -1 0 0 0 0 1 -1 0
2 2 2 0 1 0 3 1 2 2 2 ... 0 4 0 4 3 1 2 2 2 0
3 1 0 0 1 0 2 0 1 1 1 ... 0 1 -1 0 1 -1 0 0 0 0
4 0 1 0 1 1 1 -1 0 0 0 ... 0 1 1 0 2 0 1 1 1 0
5 1 1 0 0 0 1 1 0 1 1 ... 0 2 0 1 1 1 0 1 1 0
6 1 2 0 1 0 2 0 1 1 1 ... 0 3 1 2 3 1 2 2 2 0
7 1 0 0 0 0 1 1 0 1 1 ... 0 1 -1 0 0 0 0 0 0 0
8 1 1 1 1 1 2 0 1 2 0 ... 1 2 0 1 2 0 1 2 0 1
9 1 0 1 1 1 2 0 1 2 0 ... 0 1 -1 0 1 -1 0 1 -1 0
10 0 2 0 1 1 1 -1 0 0 0 ... 0 2 2 0 3 1 2 2 2 0
11 2 2 0 0 0 2 2 0 2 2 ... 0 4 0 4 2 2 0 2 2 0
12 0 0 1 0 1 0 0 0 1 -1 ... 0 0 0 0 0 0 0 1 -1 0
13 2 2 1 0 1 2 2 0 3 1 ... 2 4 0 4 2 2 0 3 1 2
14 1 2 1 1 1 2 0 1 2 0 ... 2 3 1 2 3 1 2 3 1 2
15 2 2 1 1 0 3 1 2 3 1 ... 2 4 0 4 3 1 2 3 1 2
16 2 0 1 0 0 2 2 0 3 1 ... 0 2 -2 0 0 0 0 1 -1 0
17 0 1 0 1 0 1 -1 0 0 0 ... 0 1 1 0 2 0 1 1 1 0
18 1 2 1 1 1 2 0 1 2 0 ... 2 3 1 2 3 1 2 3 1 2
19 0 2 0 0 0 0 0 0 0 0 ... 0 2 2 0 2 2 0 2 2 0
20 0 1 1 1 0 1 -1 0 1 -1 ... 1 1 1 0 2 0 1 2 0 1
21 0 0 1 0 1 0 0 0 1 -1 ... 0 0 0 0 0 0 0 1 -1 0
22 2 1 0 0 1 2 2 0 2 2 ... 0 3 -1 2 1 1 0 1 1 0
23 1 2 0 0 0 1 1 0 1 1 ... 0 3 1 2 2 2 0 2 2 0
24 2 1 0 1 1 3 1 2 2 2 ... 0 3 -1 2 2 0 1 1 1 0
25 0 0 1 1 0 1 -1 0 1 -1 ... 0 0 0 0 1 -1 0 1 -1 0
26 2 1 0 1 1 3 1 2 2 2 ... 0 3 -1 2 2 0 1 1 1 0
27 0 0 0 1 0 1 -1 0 0 0 ... 0 0 0 0 1 -1 0 0 0 0
28 1 2 1 1 0 2 0 1 2 0 ... 2 3 1 2 3 1 2 3 1 2
29 1 0 0 0 1 1 1 0 1 1 ... 0 1 -1 0 0 0 0 0 0 0
30 0 1 1 0 1 0 0 0 1 -1 ... 1 1 1 0 1 1 0 2 0 1
31 2 0 1 1 0 3 1 2 3 1 ... 0 2 -2 0 1 -1 0 1 -1 0
32 1 2 0 0 0 1 1 0 1 1 ... 0 3 1 2 2 2 0 2 2 0
33 1 0 0 1 0 2 0 1 1 1 ... 0 1 -1 0 1 -1 0 0 0 0
34 1 0 0 1 0 2 0 1 1 1 ... 0 1 -1 0 1 -1 0 0 0 0
35 0 0 0 0 1 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
36 0 1 0 0 0 0 0 0 0 0 ... 0 1 1 0 1 1 0 1 1 0
37 2 1 0 0 1 2 2 0 2 2 ... 0 3 -1 2 1 1 0 1 1 0
38 0 2 0 0 1 0 0 0 0 0 ... 0 2 2 0 2 2 0 2 2 0
39 1 0 0 0 1 1 1 0 1 1 ... 0 1 -1 0 0 0 0 0 0 0
40 1 1 0 0 1 1 1 0 1 1 ... 0 2 0 1 1 1 0 1 1 0
41 0 1 0 1 0 1 -1 0 0 0 ... 0 1 1 0 2 0 1 1 1 0
42 1 0 1 1 0 2 0 1 2 0 ... 0 1 -1 0 1 -1 0 1 -1 0
43 2 2 0 0 0 2 2 0 2 2 ... 0 4 0 4 2 2 0 2 2 0
44 0 1 1 1 1 1 -1 0 1 -1 ... 1 1 1 0 2 0 1 2 0 1
45 2 2 0 0 1 2 2 0 2 2 ... 0 4 0 4 2 2 0 2 2 0
46 2 2 0 1 1 3 1 2 2 2 ... 0 4 0 4 3 1 2 2 2 0
47 0 2 0 0 1 0 0 0 0 0 ... 0 2 2 0 2 2 0 2 2 0
48 1 2 0 1 1 2 0 1 1 1 ... 0 3 1 2 3 1 2 2 2 0
49 0 0 0 1 1 1 -1 0 0 0 ... 0 0 0 0 1 -1 0 0 0 0

50 rows × 41 columns

Nun lass uns sehen, ob der Entscheidungsbaum mit diesen generierten Features besser arbeiten kann:

[7]:
ziel = df_extended["Play Tennis?"]
eingabe = df_extended[list(sorted(list(set(df_extended.columns) - set(["Play Tennis?"]))))]

X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(eingabe, ziel, random_state=74)
dt = sklearn.tree.DecisionTreeClassifier(random_state=5)
dt.fit(X_train, y_train)
s = dt.score(X_test, y_test)
s
[7]:
0.9230769230769231

Aufgabe 2

Interpretieren Sie den Zahlenwert. Liegt hier ein zufälliger nicht-linearer Zusammenhang vor? Ist dieses Ergebnis plausibel? Warum (nicht)?

Antwort: …

Aufgabe 3

Stellen Sie sich vor, Sie haben mit einem Dienstleister einen Vertrag abgeschlossen. Dort heißt es, es soll mindestens eine Genauigkeit von 80% erreicht werden (wie oben der Fall). Wie können Sie sich davor schützen, dass der Dienstleister Ihnen ein nicht praxistaugliches Produkt verkauft? Gehen Sie davon aus, dass Sie den Source Code nicht einsehen können.

Antwort: …

Creative Commons Lizenzvertrag     Dieses Werk von Marvin Kastner ist lizenziert unter einer Creative Commons Namensnennung 4.0 International Lizenz.