Zum Hauptinhalt springen

Machine Learning mit Kaggle: Feature Engineering

Lerne, wie dir Feature Engineering beim Erstellen von Machine-Learning-Modellen auf Kaggle einen echten Vorsprung verschafft: neue Spalten erstellen, Variablen transformieren und mehr!
· 14 Min. lesen

Entdecke KI

In den zwei vorherigen Kaggle-Tutorials hast du gelernt, wie du deine Daten in die richtige Form bringst, um dein erstes Machine-Learning-Modell zu bauen – mithilfe von Exploratory Data Analysis und Baseline-Modellen. Anschließend hast du erfolgreich dein erstes Machine-Learning-Modell erstellt, einen Decision-Tree-Classifier. Du hast all diese Modelle bei Kaggle eingereicht und ihre Accuracy interpretiert.

In diesem dritten Tutorial lernst du mehr über Feature Engineering – also den Prozess, bei dem du Domänenwissen über deine Daten nutzt, um zusätzliche relevante Merkmale zu erstellen, die die Vorhersagekraft des Lernalgorithmus erhöhen und deine Machine-Learning-Modelle noch besser performen lassen!

Im Detail:

  • Du startest mit allen nötigen Imports und lädst die Daten in deinen Workspace;
  • Dann siehst du, warum sich Feature Engineering lohnt, und legst los, eigene neue Features für deinen Datensatz zu entwickeln! Du erstellst neue Spalten, wandelst Variablen in numerische um, gehst mit fehlenden Werten um und vieles mehr.
  • Zum Schluss baust du ein neues Machine-Learning-Modell mit deinem erweiterten Datensatz und reichst es bei Kaggle ein.

Los geht's!

Bevor du startest, führst du wie im letzten Tutorial alle Imports aus, nutzt etwas IPython-Magie, damit Grafiken inline im Jupyter Notebook dargestellt werden, und setzt den Visualisierungsstil. Danach importierst du die Daten und sorgst dafür, dass du die Zielvariable der Trainingsdaten sicher ablegst. Anschließend führst du Train- und Testdaten zusammen (mit Ausnahme der Spalte 'Survived' aus df_train) und speicherst das Ergebnis in data.

Merke: Das machst du, damit jede Vorverarbeitung, die du auf die Daten anwendest, sowohl im Train- als auch im Test-Set abgebildet wird!

Zum Schluss nutzt du die Methode .info(), um dir deine Daten anzuschauen:

# Imports
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import re
import numpy as np
from sklearn import tree
from sklearn.model_selection import GridSearchCV

# Figures inline and set visualization style
%matplotlib inline
sns.set()

# Import data
df_train = pd.read_csv('data/train.csv')
df_test = pd.read_csv('data/test.csv')

# Store target variable of training data in a safe place
survived_train = df_train.Survived

# Concatenate training and test sets
data = pd.concat([df_train.drop(['Survived'], axis=1), df_test])

# View head
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1309 entries, 0 to 417
Data columns (total 11 columns):
PassengerId    1309 non-null int64
Pclass         1309 non-null int64
Name           1309 non-null object
Sex            1309 non-null object
Age            1046 non-null float64
SibSp          1309 non-null int64
Parch          1309 non-null int64
Ticket         1309 non-null object
Fare           1308 non-null float64
Cabin          295 non-null object
Embarked       1307 non-null object
dtypes: float64(2), int64(4), object(5)
memory usage: 122.7+ KB

Warum überhaupt Feature Engineering?

Mit Feature Engineering holst du mehr Information aus deinen Daten heraus – und holst beim Modellieren mehr aus deinen Modellen.

Titel der Titanic-Passagiere

Schauen wir uns das an einem Beispiel an. Sieh dir die Spalte 'Name' mit der Methode .tail() an, die dir die letzten fünf Zeilen deiner Daten zeigt:

# View head of 'Name' column
data.Name.tail()
413              Spector, Mr. Woolf
414    Oliva y Ocana, Dona. Fermina
415    Saether, Mr. Simon Sivertsen
416             Ware, Mr. Frederick
417        Peter, Master. Michael J
Name: Name, dtype: object

Plötzlich tauchen unterschiedliche Anreden auf! Diese Spalte enthält also Zeichenketten bzw. Text mit Titeln wie "Mr", "Master" oder "Dona".

Diese Titel liefern Hinweise auf sozialen Status, Beruf etc. – und könnten damit etwas über das Überleben aussagen.

Auf den ersten Blick scheint es schwierig, Namen und Titel zu trennen – keine Panik! Mit regulären Ausdrücken kannst du den Titel extrahieren und in einer neuen Spalte 'Title' speichern:

# Extract Title from Name, store in column and plot barplot
data['Title'] = data.Name.apply(lambda x: re.search(' ([A-Z][a-z]+)\.', x).group(1))
sns.countplot(x='Title', data=data);
plt.xticks(rotation=45);

bar chart

Hinweis: Diese neue Spalte 'Title' ist ein neu entwickeltes Feature für deinen Datensatz!

Tipp: Wenn du mehr zu regulären Ausdrücken lernen willst, lies meinen Beitrag zu unserem letzten FB Live Code-Along oder schau dir DataCamps Python Regular Expressions Tutorial an.

Im Plot oben gibt es mehrere Titel, einige kommen selten vor. Es ergibt Sinn, sie in weniger Klassen zusammenzufassen.

Zum Beispiel kannst du 'Mlle' und 'Ms' durch 'Miss' ersetzen und 'Mme' durch 'Mrs', da es französische Titel sind – idealerweise ist alles in einer Sprache. Außerdem bündelst du einige schwer zuzuordnende Titel in einer Kategorie 'Special'.

Tipp: Probiere aus, wie sich diese Gruppierung auf die Performance deines Algorithmus auswirkt!

Anschließend visualisierst du das Ergebnis erneut mit .countplot():

data['Title'] = data['Title'].replace({'Mlle':'Miss', 'Mme':'Mrs', 'Ms':'Miss'})
data['Title'] = data['Title'].replace(['Don', 'Dona', 'Rev', 'Dr',
                                            'Major', 'Lady', 'Sir', 'Col', 'Capt', 'Countess', 'Jonkheer'],'Special')
sns.countplot(x='Title', data=data);
plt.xticks(rotation=45);

bar chart

So sieht dein neu entwickeltes Feature 'Title' jetzt aus!

Überprüfe nun, dass du eine Spalte 'Title' hast, und wirf mit .tail() erneut einen Blick auf die Daten:

# View head of data
data.tail()
 PassengerIdPclassNameSexAgeSibSpParchTicketFareCabinEmbarkedTitle
41313053Spector, Mr. WoolfmaleNaN00A.5. 32368.0500NaNSMr
41413061Oliva y Ocana, Dona. Ferminafemale39.000PC 17758108.9000C105CSpecial
41513073Saether, Mr. Simon Sivertsenmale38.500SOTON/O.Q. 31012627.2500NaNSMr
41613083Ware, Mr. FrederickmaleNaN003593098.0500NaNSMr
41713093Peter, Master. Michael JmaleNaN11266822.3583NaNCMaster

Kabinen der Passagiere

Beim Laden und Prüfen der Daten hast du gesehen, dass es in der Spalte 'Cabin' mehrere NaN-Werte bzw. fehlende Werte gibt.

Es ist plausibel anzunehmen, dass diese NaN-Werte bedeuten, dass die Personen keine Kabine hatten – was wiederum etwas über 'Survival' aussagen könnte. Erstelle daher eine neue Spalte 'Has_Cabin', die kodiert, ob eine Person eine Kabine hatte oder nicht.

Hinweis: In dem folgenden Code verwendest du .isnull(). Das ergibt True, wenn die Person keine Kabine hat, und False, wenn doch. Da du das Ergebnis aber in 'Has_Cabin' speichern möchtest, drehst du das Ergebnis mit der Tilde ~ um: Du willst True zurückgeben, wenn eine Kabine vorhanden ist.

# Did they have a Cabin?
data['Has_Cabin'] = ~data.Cabin.isnull()

# View head of data
data.head()
 PassengerIdPclassNameSexAgeSibSpParchTicketFareCabinEmbarkedTitleHas_Cabin
013Braund, Mr. Owen Harrismale22.010A/5 211717.2500NaNSMrFalse
121Cumings, Mrs. John Bradley (Florence Briggs Th...female38.010PC 1759971.2833C85CMrsTrue
233Heikkinen, Miss. Lainafemale26.000STON/O2. 31012827.9250NaNSMissFalse
341Futrelle, Mrs. Jacques Heath (Lily May Peel)female35.01011380353.1000C123SMrsTrue
453Allen, Mr. William Henrymale35.0003734508.0500NaNSMrFalse

Als Nächstes wirfst du einige Spalten heraus, die keinen weiteren Mehrwert liefern (oder bei denen unklar ist, wie man sie nutzt). Hier betrifft das ['Cabin', 'Name', 'PassengerId', 'Ticket'], denn:

  • Du hast bereits in 'Has_Cabin' kodiert, ob eine Person eine Kabine hatte;
  • Die Titel hast du bereits aus der Spalte 'Name' extrahiert;
  • 'PassengerId' und 'Ticket' liefern vermutlich keinen Erkenntnisgewinn für das Überleben der Titanic-Passagiere.

Tipp: In 'Cabin' könnte noch mehr Information stecken, aber für dieses Tutorial nehmen wir an, dass das nicht der Fall ist.

Um diese Spalten im DataFrame data zu löschen, nutze das Argument inplace in .drop() und setze es auf True:

# Drop columns and view head
data.drop(['Cabin', 'Name', 'PassengerId', 'Ticket'], axis=1, inplace=True)
data.head()
 PclassSexAgeSibSpParchFareEmbarkedTitleHas_Cabin
03male22.0107.2500SMrFalse
11female38.01071.2833CMrsTrue
23female26.0007.9250SMissFalse
31female35.01053.1000SMrsTrue
43male35.0008.0500SMrFalse

Glückwunsch! Du hast erfolgreich neue Features wie 'Title' und 'Has_Cabin' entwickelt und Spalten entfernt, die deinem Modell keinen zusätzlichen Nutzen bringen.

Als Nächstes gehst du fehlende Werte an, bildest Bins für numerische Daten und wandelst anschließend alle Features mit .get_dummies() in numerische Variablen um. Zum Schluss baust du das finale Modell für dieses Tutorial. Wie das geht, siehst du in den nächsten Abschnitten!

Umgang mit fehlenden Werten

Nach all den Änderungen am ursprünglichen DataFrame data ist es sinnvoll, mit .info() zu prüfen, ob noch fehlende Werte übrig sind:

data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1309 entries, 0 to 417
Data columns (total 9 columns):
Pclass       1309 non-null int64
Sex          1309 non-null object
Age          1046 non-null float64
SibSp        1309 non-null int64
Parch        1309 non-null int64
Fare         1308 non-null float64
Embarked     1307 non-null object
Title        1309 non-null object
Has_Cabin    1309 non-null bool
dtypes: bool(1), float64(2), int64(3), object(3)
memory usage: 133.3+ KB

Das Ergebnis zeigt fehlende Werte in 'Age', 'Fare' und 'Embarked'.

Merke: Du erkennst das, indem du zuerst auf die Gesamtanzahl der Einträge (1309) schaust und dann die Anzahl der Non-Null-Werte in den jeweiligen Spalten prüfst. 'Age' hat 1046 Non-Null-Werte, also fehlen 263. 'Fare' fehlt ein Wert, 'Embarked' fehlen zwei.

Wie im vorherigen Tutorial imputest du die fehlenden Werte mit .fillna():

Hinweis: Für 'Age' und 'Fare' nutzt du wieder den Median, da er gut mit Ausreißern umgeht. Alternativen wären der Mittelwert (Summe aller Werte geteilt durch die Anzahl) oder der Modus (der am häufigsten vorkommende Wert).

Die zwei fehlenden Werte in 'Embarked' füllst du mit 'S' (Southampton), da dieser Wert in der Spalte am häufigsten vorkommt.

Tipp: Das kannst du mit etwas Exploratory Data Analysis leicht nachprüfen!

# Impute missing values for Age, Fare, Embarked
data['Age'] = data.Age.fillna(data.Age.median())
data['Fare'] = data.Fare.fillna(data.Fare.median())
data['Embarked'] = data['Embarked'].fillna('S')
data.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 1309 entries, 0 to 417
Data columns (total 9 columns):
Pclass       1309 non-null int64
Sex          1309 non-null object
Age          1309 non-null float64
SibSp        1309 non-null int64
Parch        1309 non-null int64
Fare         1309 non-null float64
Embarked     1309 non-null object
Title        1309 non-null object
Has_Cabin    1309 non-null bool
dtypes: bool(1), float64(2), int64(3), object(3)
memory usage: 133.3+ KB
data.head()
 PclassSexAgeSibSpParchFareEmbarkedTitleHas_Cabin
03male22.0107.2500SMrFalse
11female38.01071.2833CMrsTrue
23female26.0007.9250SMissFalse
31female35.01053.1000SMrsTrue
43male35.0008.0500SMrFalse

Numerische Daten bündeln (Binning)

Als Nächstes bündelst du die numerischen Daten, da es Bereiche bei Alter und Fahrpreis gibt. In diesen Bereichen können Schwankungen stecken, die keine Muster widerspiegeln, sondern Rauschen sind. Deshalb legst du Personen innerhalb bestimmter Alters- oder Fahrpreisbereiche in die gleichen Bins. Das geht mit der pandas-Funktion qcut():

# Binning numerical columns
data['CatAge'] = pd.qcut(data.Age, q=4, labels=False )
data['CatFare']= pd.qcut(data.Fare, q=4, labels=False)
data.head()
 PclassSexAgeSibSpParchFareEmbarkedTitleHas_CabinCatAgeCatFare
03male22.0107.2500SMrFalse00
11female38.01071.2833CMrsTrue33
23female26.0007.9250SMissFalse11
31female35.01053.1000SMrsTrue23
43male35.0008.0500SMrFalse21

Hinweis: Du übergibst die Daten als Series (data.Age und data.Fare), setzt die Anzahl der Quantile mit q=4 und stellst labels=False ein, damit die Bins als Zahlen kodiert werden.

Da nun alles gebinnt ist, kannst du die Spalten 'Age' und 'Fare' entfernen. Vergiss nicht, dir die ersten fünf Zeilen anzusehen!

data = data.drop(['Age', 'Fare'], axis=1)
data.head()
 PclassSexSibSpParchEmbarkedTitleHas_CabinCatAgeCatFare
03male10SMrFalse00
11female10CMrsTrue33
23female00SMissFalse11
31female10SMrsTrue23
43male00SMrFalse21

Anzahl der Familienmitglieder an Bord

Als Nächstes könntest du eine neue Spalte erstellen, die die Anzahl der Familienmitglieder an Bord enthält. In diesem Tutorial lassen wir das außen vor und schauen, wie das Modell ohne diese Spalte performt. Wenn du testen willst, wie sich die zusätzliche Spalte auswirkt, führe folgende Zeile aus:

# Create column of number of Family members onboard
data['Fam_Size'] = data.Parch + data.SibSp

Wirf für den Moment die Spalten 'SibSp' und 'Parch' aus deinem DataFrame:

# Drop columns
data = data.drop(['SibSp','Parch'], axis=1)
data.head()
 PclassSexEmbarkedTitleHas_CabinCatAgeCatFare
03maleSMrFalse00
11femaleCMrsTrue33
23femaleSMissFalse11
31femaleSMrsTrue23
43maleSMrFalse21

Variablen in numerische Variablen umwandeln

Jetzt, da du weitere Features wie 'Title' und 'Has_Cabin' entwickelt, fehlende Werte behandelt und numerische Daten gebinnt hast, ist es Zeit, alle Variablen in numerische zu überführen. Das ist nötig, weil Machine-Learning-Modelle in der Regel numerische Eingaben erwarten.

Wie zuvor nutzt du dazu .get_dummies():

# Transform into binary variables
data_dum = pd.get_dummies(data, drop_first=True)
data_dum.head()
 PclassHas_CabinCatAgeCatFareSex_maleEmbarked_QEmbarked_STitle_MissTitle_MrTitle_MrsTitle_Special
03False001010100
11True330000010
23False110011000
31True230010010
43False211010100

Damit ist alles vorbereitet – Zeit für dein finales Modell!

Modelle mit deinem neuen Datensatz bauen

Wie zuvor teilst du data wieder in Trainings- und Testdaten auf und wandelst sie in Arrays um:

# Split into test.train
data_train = data_dum.iloc[:891]
data_test = data_dum.iloc[891:]

# Transform into arrays for scikit-learn
X = data_train.values
test = data_test.values
y = survived_train.values

Jetzt baust du einen Decision Tree auf deinem neu feature-entwickelten Datensatz. Um den Hyperparameter max_depth zu wählen, nutzt du eine Variante des Train-Test-Splits namens „Cross-Validation“.

cross validation

Du teilst den Datensatz zunächst in 5 Gruppen oder Folds. Dann hältst du den ersten Fold als Testmenge zurück, trainierst dein Modell auf den verbleibenden vier Folds, sagst auf dem Testfold voraus und berechnest die Metrik. Danach hältst du den zweiten Fold zurück, trainierst auf den restlichen Daten, sagst voraus und berechnest die Metrik – und so weiter für den dritten, vierten und fünften.

Am Ende hast du fünf Accuracy-Werte, aus denen du interessante Statistiken berechnen kannst, z. B. Median, Mittelwert und 95%-Konfidenzintervalle.

Das machst du für jede Kombination der zu tunenden Hyperparameter und wählst das Set, das am besten abschneidet. Das nennt man Grid Search.

Genug Theorie – los geht's!

Im Folgenden nutzt du Cross-Validation und Grid Search, um das beste max_depth für deinen neuen Datensatz zu wählen:

# Setup the hyperparameter grid
dep = np.arange(1,9)
param_grid = {'max_depth' : dep}

# Instantiate a decision tree classifier: clf
clf = tree.DecisionTreeClassifier()

# Instantiate the GridSearchCV object: clf_cv
clf_cv = GridSearchCV(clf, param_grid=param_grid, cv=5)

# Fit it to the data
clf_cv.fit(X, y)

# Print the tuned parameter and score
print("Tuned Decision Tree Parameters: {}".format(clf_cv.best_params_))
print("Best score is {}".format(clf_cv.best_score_))
Tuned Decision Tree Parameters: {'max_depth': 3}
Best score is 0.8103254769921436

Jetzt kannst du Vorhersagen für dein Test-Set machen, eine neue Spalte 'Survived' anlegen und deine Predictions darin speichern. Vergiss nicht, die Spalten 'PassengerId' und 'Survived' aus df_test als .csv zu speichern und bei Kaggle einzureichen!

Y_pred = clf_cv.predict(test)
df_test['Survived'] = Y_pred
df_test[['PassengerId', 'Survived']].to_csv('data/predictions/dec_tree_feat_eng.csv', index=False)

kaggle

Die Accuracy deiner Einreichung beträgt 78,9.

Nächste Schritte

Schau, ob du noch mehr Feature Engineering betreiben und neue Modelle ausprobieren kannst, um diesen Score zu verbessern. Dieses Notebook – zusammen mit den beiden vorherigen – findest du auf GitHub. Es wäre großartig zu sehen, wie ihr die Modelle noch weiter verbessert.

Es gibt noch viel mehr Preprocessing zu lernen, etwa das Skalieren von Daten. Außerdem sind scikit-learn-Pipelines extrem hilfreich. Sieh dir unseren Kurs Supervised Learning with scikit-learn und die scikit-learn-Dokumentation an – für all das und mehr.

Themen

Ähnlich

Tutorial

Python-Arrays

Tutorial

Python Datenstrukturen Tutorial