Aufteilungen der Daten

Datensets in Lern- und Testsets trennen

Splitting into train and test sets

Sie haben Ihre Daten bereit und möchten den Klassifikator trainieren? Aber seien Sie vorsichtig: Wenn Ihr Klassifikator fertig ist, benötigen Sie einige Testdaten, um Ihren Klassifikator zu bewerten. Wenn Sie Ihren Klassifikator mit den zum Lernen verwendeten Daten bewerten, sehen Sie möglicherweise überraschend gute Ergebnisse. Was wir tatsächlich testen möchten, ist die Leistung der Klassifizierung auf unbekannten Daten.

Zu diesem Zweck müssen wir unsere Daten in zwei Teile aufteilen:

  1. Einen Trainingssatz, mit dem der Lernalgorithmus das Modell anpasst oder lernt
  2. Ein Testset zur Bewertung der Generalisierungsleistung des Modells

Wenn man bedenkt, wie das maschinelle Lernen normalerweise funktioniert, ist die Idee einer Aufteilung zwischen Lern- und Testdaten sinnvoll. Real existierende Systeme trainieren auf existierenden Daten und wenn dann andere also neue Daten (von Kunden, Sensoren oder anderen Quellen) kommen, muss der trainierte Klassifikator diese neue Daten vorhersagen bzw. klassifizieren. Wir können dies während des Trainings mit einem Trainings- und Testdatenset simulieren - die Testdaten sind eine Simulation von "zukünftigen Daten", die während der Produktion in das System eingehen werden.

In diesem Kapitel unseres Tutorials über Maschinelles Lernen mit Python erfahren Sie, wie Sie die Aufspaltung mit einfachem Python tun können. Danach werden wir zeigen, dass es nicht notwendig ist, dies manuell zu tun, da die Funktion train_test_split aus dem Modul model_selection dies für uns tun kann.

Wenn der Datensatz nach Labels sortiert ist, müssen wir ihn vor dem Teilen mischen.

Wir haben den Datensatz in einen Lerndatensatz (a.k.a. Training) und einen Testdatensatz unterteilt. Die beste Vorgehensweise besteht darin, es in einen Lern-, Test- und Bewertungsdatensatz aufzuteilen.

Wir werden unser Modell (Klassifikator) Schritt für Schritt trainieren und jedes Mal, wenn das Ergebnis getestet werden muss. Wenn wir nur einen Testdatensatz haben. Die Testergebnisse könnten in das Modell einfließen. Daher werden wir einen Bewertungsdatensatz für die gesamte Lernphase verwenden.

In unserem Tutorial werden wir jedoch nur Aufteilungen in Lern- und Testdatensätze verwenden.

Aufteilung des Iris-Datensatzes

Die 150 Datensätze des Iris-Datensatzes sind sortiert, d.h. die ersten 50 Daten entsprechen der ersten Blumeklasse (0 = Setosa), die nächsten 50 der zweiten Blumenklasse (1 = Versicolor) und die restlichen Daten entsprechen der letzten Klasse (2 = Virginica).

Würden wir nun unsere Daten im Verhältnis 2/3 (Lernset) und 1/3 (Testset) aufteilen, enthielte das Lernset alle Blumen der beiden ersten Klassen und das Testset alle Blumen der dritten Klasse. Der KLassifikator könnte also nur zwei Klassen lernen und die dritte Klasse wäre komplett unbekannt. Deshalb müssen wir die Daten dringend mischen.

Unter der Annahme, dass alle Stichproben unabhängig voneinander sind, möchten wir den Datensatz zufällig mischen, bevor wir den Datensatz wie oben dargestellt aufteilen.

Im Folgenden teilen wir die Daten manuell auf:

import numpy as np
from sklearn.datasets import load_iris
iris = load_iris()

Wenn wir uns die Labels anschauen, sehen wir, dass die Daten, wie eben beschrieben, sortiert sind:

iris.target
Ausgabe: :

array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
       1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2])

Als erstes müssen wir die Daten neu anordnen, damit sie nicht mehr sortiert werden. Dazu benutzen wir die permutation-Funktion des random-Submoduls von Numpy:

indices = np.random.permutation(len(iris.data))
indices
Ausgabe: :

array([124,  15,  63, 133,  55, 113,  43, 107,   9, 140, 104, 118,  60,
        17,  76, 119,  95, 148,  89, 102,  87, 122,  69, 123,  75,  22,
        44,  85,  68,  38,  16,  30,  92,   2, 135,  35,  32,  84, 138,
        54,  34,  71,  77, 139,  74, 116, 108, 147,  66,  57,  97,  28,
        80,  91, 127,  20,  18,   3, 144,  86, 110,   8,  26,  65,  51,
         4, 120,  88,  36, 106, 141,  56,  52,  64, 100, 132,  29, 103,
       112, 149,  25,  19,  10,  40, 114,  79,  83, 130,  41,  31,  67,
       131,  82,  42, 105, 142,   0, 121,  11,   5,  14, 145,  27,  39,
        53,  72,  81,  93, 134, 129, 101,   7, 136, 115,  58,  94,  78,
        50,   6,  12,  48,  61,  37,  99, 137,  45,  70,  33, 128, 111,
        59, 117, 143,   1,  96, 125, 109,  13,  47,  49,  46, 146,  24,
        21,  98,  23, 126,  62,  90,  73])
n_training_samples = 12
learnset_data = iris.data[indices[:-n_training_samples]]
learnset_labels = iris.target[indices[:-n_training_samples]]
testset_data = iris.data[indices[-n_training_samples:]]
testset_labels = iris.target[indices[-n_training_samples:]]
print(learnset_data[:4], learnset_labels[:4])
print(testset_data[:4], testset_labels[:4])
[[6.7 3.3 5.7 2.1]
 [5.7 4.4 1.5 0.4]
 [6.1 2.9 4.7 1.4]
 [6.3 2.8 5.1 1.5]] [2 0 1 2]
[[4.6 3.2 1.4 0.2]
 [5.  3.3 1.4 0.2]
 [5.1 3.8 1.6 0.2]
 [6.3 2.5 5.  1.9]] [0 0 0 2]

Aufteilungen mit Sklearn

Obwohl es nicht schwierig war müssen wir die Aufteilung nicht, wie oben gezeigt, manuell durchführen. Da dies beim maschinellen Lernen häufig erforderlich ist, verfügt scikit-learn über eine vordefinierte Funktion zum Aufteilen von Daten in Trainings- und Testsätze.

Wir werden dies im Folgenden demonstrieren. Wir werden 80% der Daten als Trainings- und 20% als Testdaten verwenden. Ebensogut hätten wir 70% und 30% nehmen können, denn es gibt keine festen Regeln. Das Wichtigste ist, dass Sie Ihr System fair anhand von Daten bewerten, die es während des Trainings nicht gesehen hat! Außerdem müssen in beiden Datensätze genügend Daten enthalten sein.

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
iris = load_iris()
data, labels = iris.data, iris.target
res = train_test_split(data, labels, 
                       train_size=0.8,
                       test_size=0.2,
                       random_state=42 # garantees same output for every run
                      )
train_data, test_data, train_labels, test_labels = res    
n = 7
print(f"Die ersten {n}-Datensätze:")
print(test_data[:7])
print(f"Die zugehörigen ersten {n}-Labels:")
print(test_labels[:7])
Die ersten 7-Datensätze:
[[6.1 2.8 4.7 1.2]
 [5.7 3.8 1.7 0.3]
 [7.7 2.6 6.9 2.3]
 [6.  2.9 4.5 1.5]
 [6.8 2.8 4.8 1.4]
 [5.4 3.4 1.5 0.4]
 [5.6 2.9 3.6 1.3]]
Die zugehörigen ersten 7-Labels:
[1 0 2 1 1 0 1]

Geschichtete Zufallsprobe

Insbesondere bei relativ kleinen Datenmengen ist es besser, die Aufteilung zu schichten. Schichtung bedeutet, dass wir den ursprünglichen Klassenanteil des Datensatzes in den Test- und Trainingssätzen beibehalten. Wir berechnen die Klassenanteile der vorherigen Aufteilung in Prozent unter Verwendung des folgenden Codes. Um die Anzahl der Vorkommen jeder Klasse zu berechnen, verwenden wir die Numpy-Funktion 'bincount'. Es zählt die Anzahl der Vorkommen jedes Werts in dem als Argument übergebenen Array nicht negativer Integers.

import numpy as np
print('All:', np.bincount(labels) / float(len(labels)) * 100.0)
print('Training:', np.bincount(train_labels) / float(len(train_labels)) * 100.0)
print('Test:', np.bincount(test_labels) / float(len(test_labels)) * 100.0)
All: [33.33333333 33.33333333 33.33333333]
Training: [33.33333333 34.16666667 32.5       ]
Test: [33.33333333 30.         36.66666667]

Um die Aufteilung zu schichten, können wir das Label-Array als zusätzliches Argument an die Funktion train_test_split übergeben:

from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
iris = load_iris()
data, labels = iris.data, iris.target
res = train_test_split(data, labels, 
                       train_size=0.8,
                       test_size=0.2,
                       random_state=42,
                       stratify=labels)
train_data, test_data, train_labels, test_labels = res 
print('All:', np.bincount(labels) / float(len(labels)) * 100.0)
print('Training:', np.bincount(train_labels) / float(len(train_labels)) * 100.0)
print('Test:', np.bincount(test_labels) / float(len(test_labels)) * 100.0)
All: [33.33333333 33.33333333 33.33333333]
Training: [33.33333333 33.33333333 33.33333333]
Test: [33.33333333 33.33333333 33.33333333]

Dies war ein dummes Beispiel, um die Arbeitsweise der geschichteten Zufallsstichprobe zu testen, da der Iris-Datensatz die gleichen Anteile aufweist, d.h. jede Klasse 50 Items.

Wir werden jetzt mit der Datei strange_flowers.txt des Verzeichnisses data arbeiten. Dieser Datensatz wird im Kapitel Datasets in Python generieren erstellt. Die Klassen in diesem Datensatz haben eine unterschiedliche Anzahl von Elementen. Zuerst laden wir die Daten:

content = np.loadtxt("data/strange_flowers.txt", delimiter=" ")
data = content[:, :-1]    # Letzte Spalte wird abgeschnitten
labels = content[:, -1]
labels.dtype
labels.shape
Ausgabe: :

(795,)
res = train_test_split(data, labels, 
                       train_size=0.8,
                       test_size=0.2,
                       random_state=42,
                       stratify=labels)
train_data, test_data, train_labels, test_labels = res 
# np.bincount expects non negative integers:
print('All:', np.bincount(labels.astype(int))  / float(len(labels)) * 100.0)
print('Training:', np.bincount(train_labels.astype(int)) / float(len(train_labels)) * 100.0)
print('Test:', np.bincount(test_labels.astype(int)) / float(len(test_labels)) * 100.0)
All: [ 0.         23.89937107 25.78616352 28.93081761 21.3836478 ]
Training: [ 0.         23.89937107 25.78616352 28.93081761 21.3836478 ]
Test: [ 0.         23.89937107 25.78616352 28.93081761 21.3836478 ]
In [ ]: