WARENKORB: Auswahl des besten Prädiktors für die Aufteilung, wenn die Gewinne bei der Abnahme der Verunreinigungen gleich sind?

8

Meine Frage befasst sich mit Klassifikationsbäumen . Betrachten Sie das folgende Beispiel aus dem Iris-Datensatz:

Geben Sie hier die Bildbeschreibung ein

Ich möchte den besten Prädiktor für die erste Aufteilung manuell auswählen. Nach dem CART-Algorithmus ist das beste Merkmal für eine Aufteilung dasjenige, das die Abnahme der Verunreinigung der Partition maximiert, auch Gini-Verstärkung genannt:

GichnichGeinichn(N.,X.)=Gichnich(N.)- -|N.1||N.|Gichnich(N.1)- -|N.2||N.|Gichnich(N.1)

Wenn ein gegebenes Merkmal ist, ist N der Knoten, auf dem die Aufteilung vorgenommen werden soll, und N 1 und sind die zwei untergeordneten Knoten, die durch Aufteilen von . ist die Anzahl der Elemente in einem Knoten.X.N.N.1N.2N.|.|

Und , wobei die Anzahl der Kategorien im Knoten istGichnich(N.)=1- -k=1K.pk2K.

Da eine Aufteilung basierend auf der Blütenblattbreite (Achse Nr. 1) und der Blütenblattlänge (Achse Nr. 2) dieselbe Partition ergibt (alle Setosa-Blüten sind von der Nicht-Setosa-Blüten getrennt), sind die GinGain-Werte für jede genau gleich Anzeichen. Wie entscheidet der CART-Algorithmus, welcher der beste ist?

Intuitiv kann man sehen, dass die Aufteilung auf die Blütenblattlänge (2) mit dem größten "Rand" verbunden ist, daher sollte die Blütenblattlänge gewählt werden (es ist tatsächlich das, was bei der Implementierung rpartin R passiert ), aber nichts in misst den Rand, also die Entscheidung muss auf etwas anderem basieren.GichnichGeinichn

Verwandter Thread, aber ohne die Antwort auf meine Frage.

Verwandter Thread ohne Antwort.

Antoine
quelle

Antworten:

8

Ich gebe zu, ein mittelmäßiger C-Code-Interpreter zu sein, und dieser alte Code ist nicht benutzerfreundlich. Das heißt, ich habe den Quellcode durchgesehen und diese Beobachtungen gemacht, was mich ziemlich sicher macht zu sagen: "rpart wählt buchstäblich die erste und beste variable Spalte aus". Da die Spalten 1 und 2 minderwertige Teilungen erzeugen, ist die Blütenblattlänge zuerst eine Teilungsvariable, da diese Spalte in data.frame / matrix vor der Blütenblattbreite steht. Zuletzt zeige ich dies, indem ich die Spaltenreihenfolge so invertiere, dass petal.with zuerst die Split-Variable ist.

In der c-Quelldatei "bsplit.c" im Quellcode für Teil zitiere ich aus Zeile 38:

 * test out the variables 1 at at time
me->primary = (pSplit) NULL;
for (i = 0; i < rp.nvar; i++) {

... und so wird in einer for-Schleife von i = 1 bis rp.nvar eine Verlustfunktion aufgerufen, um alle Teilungen durch eine Variable zu scannen. Innerhalb von gini.c wird nach der Zeile 230 für "nicht kategoriale Teilung" die am besten gefundene Teilung gesucht aktualisiert, wenn eine neue Aufteilung besser ist. (Dies könnte auch eine benutzerdefinierte Verlustfunktion sein)

if (temp < best) {
        best = temp;
        where = i;
        direction = lmean < rmean ? LEFT : RIGHT;
}

und letzte Zeile 323 wird die Verbesserung für die beste Aufteilung durch eine Variable berechnet ...

*improve = total_ss - best

... zurück in bsplit.c wird die Verbesserung überprüft, wenn sie größer als zuvor ist, und nur aktualisiert, wenn sie größer ist.

if (improve > rp.iscale)
rp.iscale = improve;        /* largest seen so far */

Mein Eindruck dabei ist, dass das erste und beste (von möglichen Unentschieden ausgewählt wird), denn nur wenn ein neuer Haltepunkt eine bessere Punktzahl hat, wird er gespeichert. Dies betrifft sowohl den ersten besten gefundenen Haltepunkt als auch die erste beste gefundene Variable. Haltepunkte scheinen in gini.c nicht einfach von links nach rechts gescannt zu werden, daher kann es schwierig sein, den ersten gefundenen Bindungsbruchpunkt vorherzusagen. Variablen sind jedoch sehr vorhersehbar und werden von der ersten bis zur letzten Spalte gescannt.

Dieses Verhalten unterscheidet sich von der randomForest-Implementierung, bei der in classTree.c die folgende Lösung verwendet wird:

/* Break ties at random: */
if (crit == critmax) {
    if (unif_rand() < 1.0 / ntie) {
        *bestSplit = j;
        critmax = crit;
        *splitVar = mvar;
    }
    ntie++;
}

Zuletzt bestätige ich dieses Verhalten, indem ich die Iris-Spalten umdrehe, sodass zuerst die Blütenblattbreite ausgewählt wird

library(rpart)
data(iris)
iris = iris[,5:1]  #flip/flop", invert order of columns columns
obj = rpart(Species~.,data=iris)
print(obj) #now petal width is first split 


1) root 150 100 setosa (0.33333333 0.33333333 0.33333333)  
  2) Petal.Width< 0.8 50   0 setosa (1.00000000 0.00000000 0.00000000) *
  3) Petal.Width>=0.8 100  50 versicolor (0.00000000 0.50000000 0.50000000)  
    6) Petal.Width< 1.75 54   5 versicolor (0.00000000 0.90740741 0.09259259) *
    7) Petal.Width>=1.75 46   1 virginica (0.00000000 0.02173913 0.97826087) *

... und wieder zurückblättern

iris = iris[,5:1]  #flop/flip", revert order of columns columns
obj = rpart(Species~.,data=iris)
print(obj) #now petal length is first split 
1) root 150 100 setosa (0.33333333 0.33333333 0.33333333)  
  2) Petal.Length< 2.45 50   0 setosa (1.00000000 0.00000000 0.00000000) *
  3) Petal.Length>=2.45 100  50 versicolor (0.00000000 0.50000000 0.50000000)  
    6) Petal.Width< 1.75 54   5 versicolor (0.00000000 0.90740741 0.09259259) *
    7) Petal.Width>=1.75 46   1 virginica (0.00000000 0.02173913 0.97826087) *
Soren Havelund Welling
quelle
Ich danke Ihnen sehr für Ihre Antwort. Wenn also mehr als ein Prädiktor der optimalen Aufteilung entspricht, wird der erste ausgewählt. Dies hat überhaupt nichts mit Rändern zu tun. Das macht doch irgendwie Sinn.
Antoine
Es hat Spaß gemacht herauszufinden :) Ich denke, Ränder sind in vielen Baummodellen nicht nativ implementiert, da binäre Teilungen nativ nicht parametrisch sind
Soren Havelund Welling
Es kann hilfreich sein zu erwähnen, dass der Quellcode für rpart auch über die R-Konsole über abgerufen werden kann untar(download.packages(pkgs = "rpart",destdir = ".",type = "source")[,2]), und anschließend den srcOrdner im aktuellen Arbeitsverzeichnis (über diesen SO- Thread ) zu öffnen . Dann kann der Code für eine bestimmte Funktion mit Notepad ++ angezeigt werden .
Antoine
Und der Algorithmus stoppt, wenn das Teilen nicht mehr für alle Knoten zu einer Verbesserung führt, oder?
Antoine
ja. in partition.c Zeile 80 isch: "Das ist eher selten - aber ich konnte keinen Split finden, der sich lohnt" ... sagte die imitierte rekursive Funktion. Danach wird der Knoten versiegelt und der rekursive Algorithmus, der zum vorherigen Knoten zurückkehrt, ruft return (0) auf.
Soren Havelund Welling