Differenza nell'implementazione delle suddivisioni binarie negli alberi delle decisioni


12

Sono curioso dell'implementazione pratica di una divisione binaria in un albero decisionale, in quanto si riferisce ai livelli di un predittore categorico .Xj

Nello specifico, spesso userò una sorta di schema di campionamento (ad es. Insaccamento, sovracampionamento, ecc.) Quando creerò un modello predittivo usando un albero decisionale, al fine di migliorarne l'accuratezza e la stabilità predittive. Durante queste routine di campionamento, è possibile che una variabile categoriale venga presentata a un algoritmo di adattamento ad albero con un set di livelli inferiore al set completo.

Supponiamo che una variabile X assuma livelli {A,B,C,D,E}. In un campione, forse {A,B,C,D}sono presenti solo i livelli . Quindi, quando l'albero risultante viene utilizzato per la previsione, potrebbe essere presente l'intero set.

Continuando da questo esempio, supponiamo che un albero si divida su X e mandi {A,B}a sinistra ea {C,D}destra. Mi aspetterei che la logica della divisione binaria dicesse, di fronte a nuovi dati: "Se X ha valore A o B, invia a sinistra, altrimenti invia questo caso a destra". Ciò che sembra accadere in alcune implementazioni è "se X ha valore A o B, invia a sinistra, se X ha valore C o D invia a destra". Quando questo caso assume il valore E, l'algoritmo si interrompe.

Qual è il modo "giusto" per gestire una divisione binaria? Sembra che il modo molto più robusto sia implementato spesso, ma non sempre (vedi Rpart sotto).

Ecco un paio di esempi:

Rpart fallisce, gli altri sono ok.

#test trees and missing values

summary(solder)
table(solder$PadType)

# create train and validation
set.seed(12345)
t_rows<-sample(1:nrow(solder),size=360, replace=FALSE)
train_solder<-solder[t_rows,]
val_solder<-solder[-t_rows,]

#look at PadType
table(train_solder$PadType)
table(val_solder$PadType)
#set a bunch to missing
levels(train_solder$PadType)[train_solder$PadType %in% c('L8','L9','W4','W9')] <- 'MISSING'


#Fit several trees, may have to play with the parameters to get them to split on the variable

####RPART
mod_rpart<-rpart(Solder~PadType,data=train_solder)
predict(mod_rpart,val_solder)
#Error in model.frame.default(Terms, newdata, na.action = na.action, xlev = attr(object,  : 
#factor 'PadType' has new level(s) D6, L6, L7, L8, L9, W4

####TREE
mod_tree<-tree(Solder~PadType,data=train_solder,split="gini")
predict(mod_tree,val_solder) #works fine

####ctree
mod_ctree<-ctree(Solder~PadType,data=train_solder,control = ctree_control(mincriterion = 0.05))
predict(mod_ctree,val_solder) #works fine

Risposte:


9

In effetti ci sono due tipi di fattori: ordinati (come Tiny <Piccolo <Medio <Grande <Enorme) e non ordinati (Cetriolo, Carota, Finocchio, Melanzana).
La prima classe è la stessa di quella continua: è più semplice controllare tutti i perni, e non c'è nessun problema con l'estensione dei livelli.
Per la seconda classe, devi creare un insieme di elementi che saranno diretti in un ramo, lasciando il resto nell'altro - in questo caso puoi:

  1. errore di lancio
  2. supponi che la classe invisibile vada nel tuo ramo preferito
  3. considera questo come NA e seleziona il ramo in modo più o meno casuale.

12#categories-1-1ioio

Direi che l'idea più sensata è di fare in modo che l'utente definisca l'insieme completo di fattori (ad esempio R lo fa organicamente, preservando i livelli attraverso operazioni di sottoinsieme) e usando l'opzione 1. per livelli non dichiarati e l'opzione 2. per quelli dichiarati . L'opzione 3. può avere senso se si dispone già di un'infrastruttura di gestione NA.

*) Esiste anche una strategia laterale per ricodificare in modo non banale i livelli in numeri, come ad esempio la codifica Breiman, ma ciò genera ancora più problemi.


1
Stai dicendo che ctree o albero nel mio esempio in realtà tratta questo fattore non ordinato come un fattore ordinato e quindi lo invia nel ramo "0"?
B_Miner

@mbq puoi spiegare perché il numero totale di modi in cui puoi fare le divisioni è 2 ^ (# categorie + 1) - 2. Non capisco bene perché la parte "-2".
honeybadger,

Hmm, sembra che ho fregato questa formula; ci sono 2 ^ n parole n-bit, ma non contiamo sia la parola a che la ~ a, quindi 2 ^ (n-1), e non ci piacciono le divisioni che non si rovesciano affatto, quindi 2 ^ (n-1) -1 (in altre parole, contiamo da 1). n = 1 è quindi un caso speciale.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.