Différence dans la mise en œuvre des divisions binaires dans les arbres de décision

12

Je suis curieux de connaître l'implémentation pratique d'une division binaire dans un arbre de décision - en ce qui concerne les niveaux d'un prédicteur catégorique .Xj

Plus précisément, j'utilise souvent une sorte de schéma d'échantillonnage (par exemple, ensachage, suréchantillonnage, etc.) lors de la construction d'un modèle prédictif à l'aide d'un arbre de décision - afin d'améliorer sa précision et sa stabilité prédictives. Au cours de ces routines d'échantillonnage, il est possible qu'une variable catégorielle soit présentée à un algorithme d'ajustement d'arbre avec moins que l'ensemble de niveaux complet.

Disons qu'une variable X prend des niveaux {A,B,C,D,E}. Dans un échantillon, peut-être que seuls les niveaux {A,B,C,D}sont présents. Ensuite, lorsque l'arbre résultant est utilisé pour la prédiction, l'ensemble complet peut être présent.

En reprenant cet exemple, disons qu'un arbre se divise sur X et envoie {A,B}à gauche et {C,D}à droite. Je m'attendrais à ce que la logique de la division binaire dise alors, face à de nouvelles données: "Si X a la valeur A ou B, envoyez à gauche, sinon, envoyez ce cas à droite". Ce qui semble se produire dans certaines implémentations est "si X a la valeur A ou B, envoyer à gauche, si X a la valeur C ou D envoyer à droite". Lorsque ce cas prend la valeur E, l'algorithme tombe en panne.

Quelle est la "bonne" façon de gérer un fractionnement binaire? Il semble que le moyen beaucoup plus robuste soit implémenté souvent, mais pas toujours (voir Rpart ci-dessous).

Voici quelques exemples:

Rpart échoue, les autres vont bien.

#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
B_Miner
la source

Réponses:

9

En fait, il existe deux types de facteurs - ordonnés (comme Tiny <Small <Medium <Big <Huge) et non ordonnés (concombre, carotte, fenouil, aubergine).
La première classe est la même que les classes continues - il est seulement plus facile de vérifier tous les pivots, il n'y a pas non plus de problème avec l'extension de la liste des niveaux.
Pour la deuxième classe, vous devez créer un ensemble d'éléments qui seront dirigés dans une branche, laissant le reste dans l'autre - dans ce cas, vous pouvez soit:

  1. erreur de lancement
  2. supposons que la classe invisible passe dans votre branche préférée
  3. traiter cela comme NA et sélectionner une branche de manière plus ou moins aléatoire.

Maintenant, le traitement direct des facteurs non ordonnés est délicat, de nombreux algorithmes "trichent" et prétendent que les facteurs non ordonnés sont ordonnés, de sorte qu'ils ne touchent même pas à ce problème *. Les autres utilisent généralement un masque int, c'est-à-dire optimisent un nombre entier de à et traitent le -ième bit comme une sélection de branche pour un niveau de facteur . (Vous êtes-vous déjà demandé pourquoi il y a une limite fréquente à 32 niveaux?) Dans ce paramètre, il est tout à fait naturel que les niveaux invisibles passent silencieusement dans la branche "0". Pourtant, cela ne semble pas trop "juste", car pourquoi devrions-nous vraiment le faire? Et qu'en est-il du nombre de niveaux requis pour l'effet de levier d'entropie dans la sélection des attributs?12#categories11ii

Je dirais que l'idée la plus judicieuse est de faire en sorte que l'utilisateur définisse l'ensemble complet des facteurs (par exemple, R le fait de manière organique, en préservant les niveaux via des opérations de sous-ensemble) et utilise l'option 1. pour les niveaux non déclarés et l'option 2. pour ceux déclarés . L'option 3. peut avoir du sens si vous disposez déjà d'une infrastructure de gestion NA.

*) Il existe également une stratégie secondaire pour effectuer un recodage non trivial des niveaux en nombres, comme par exemple le codage Breiman - mais cela génère encore plus de problèmes.


la source
1
Voulez-vous dire que ctree ou tree dans mon exemple traite réellement ce facteur non ordonné comme un facteur ordonné et l'envoie donc dans la branche "0"?
B_Miner
@mbq pouvez-vous s'il vous plaît expliquer pourquoi le nombre total de façons dont vous pouvez effectuer les scissions est de 2 ^ (# catégories + 1) - 2. Je ne comprends pas très bien pourquoi la partie "-2".
honeybadger
Hmm, il semble que j'ai foiré cette formule; il y a comme 2 ^ n mots de n bits, mais nous ne comptons pas les mots a et ~ a, donc 2 ^ (n-1), et nous n'aimons pas les séparations qui ne se sont pas renversées du tout, donc 2 ^ (n-1) -1 (en d'autres termes, nous comptons à partir de 1). n = 1 est alors un cas particulier.