Xgboost: [Nouvelle fonctionnalité] Contraintes monotones dans la construction d'arbres

Créé le 27 août 2016  ·  46Commentaires  ·  Source: dmlc/xgboost

J'ai reçu quelques demandes sur la prise en charge de contraintes monotones sur certaines fonctionnalités par rapport à la sortie,

c'est-à-dire que lorsque d'autres caractéristiques sont fixées, forcer la prédiction à être monotone croissante par rapport à la certaine caractéristique spécifiée. J'ouvre ce numéro pour voir l'intérêt général sur cette fonctionnalité. Je peux ajouter ceci s'il y a suffisamment d'intérêt à ce sujet,

J'aurais besoin de l'aide de volontaires de la communauté pour tester la fonctionnalité bêta et contribuer au document et au didacticiel sur l'utilisation de cette fonctionnalité. Veuillez répondre au problème si vous êtes intéressé

Commentaire le plus utile

Actuellement, cette fonctionnalité n'est pas dans l'API Sklearn. Pouvez-vous, ou quelqu'un d'autre, aider à l'ajouter ? Merci!

Tous les 46 commentaires

Une version expérimentale est fournie dans https://github.com/dmlc/xgboost/pull/1516. Pour l'utiliser avant qu'il ne soit fusionné, clonez le référentiel https://github.com/tqchen/xgboost ,

Activez les options suivantes (probablement possible via python, r API)

monotone_constraints = "(0,1,1,0)"

Il y a deux arguments

  • monotone_constraints est une liste en longueur du nombre de caractéristiques, 1 indique une augmentation monotone, - 1 signifie une diminution, 0 signifie aucune contrainte. S'il est plus court que le nombre de fonctionnalités, 0 sera rempli.

    • Actuellement, il prend en charge le format de tuple de python, vous pouvez passer des choses sous forme de chaîne lorsque vous utilisez r

Choses à vérifier

  • [x] La vitesse des boosters d'arbre d'origine ne ralentit pas (j'ai un peu changé la structure du code, en théorie l'optimisation des modèles les alignera, mais je dois confirmer)
  • [x] La vitesse et la justesse de la régression monotone
  • [x] La performance en introduisant cette contrainte

Limites connues

Actuellement, seul l'algorithme glouton exact est pris en charge sur le multicœur. Pas encore disponible en version distribuée

@tqchen J'ai reçu une demande au travail aujourd'hui pour construire des GBM avec des contraintes monotones pour tester les performances de certains autres modèles. Ce serait avec une perte de déviance tweedie, donc je devrais opter pour une fonction de perte personnalisée telle qu'elle existe aujourd'hui.

Dans tous les cas, cela semble être une bonne occasion d'aider et de faire du travail en même temps.

Sur la base de l'exposé ici , GBM (R Package) n'applique la monotonie que localement.
Pourriez-vous préciser comment XGBoost applique les contraintes monotones ?
Ce serait formidable si XGBoost pouvait appliquer des contraintes globales.

Je ne comprends pas ce que vous entendez par contrainte locale ou globale, pouvez-vous développer ?

Désolé, je colle un mauvais lien, voici le bon (Lien)
Chaque arbre peut uniquement suivre une contrainte monotone dans certains sous-ensembles de la caractéristique intéressée, de sorte que de nombreux arbres ensemble peuvent créer une violation de la monotonie globale sur toute la gamme de cette caractéristique.

OK, à mon avis, il est appliqué à l'échelle mondiale. Vous êtes invités à l'essayer.

Je viens de faire quelques tests simples de contrainte de monotonie dans le cadre d'une régression univariée. Vous pouvez trouver le code et une très brève documentation ici :

https://github.com/XiaoxiaoWang87/xgboost_mono_test/blob/master/xgb_monotonicity_constraint_testing1-univariate.ipynb

Quelques premiers constats :

  • Pour un problème de régression à une seule variable, la contrainte monotone = +1 semble bien fonctionner
  • Pour un problème de régression à une seule variable, dans mon ensemble de données, la contrainte monotone = -1 ne semble pas donner de fonction décroissante de façon monotone. Au contraire, cela donne une constante. Mais cela peut aussi être dû au manque d'amélioration lors du forçage de la contrainte. À confirmer (selon la suggestion de Tianqi, essayez de retourner l'ensemble de données et définissez la contrainte sur +1).
  • L'ajout (correctement) de la contrainte peut potentiellement empêcher le surapprentissage et apporter des avantages en termes de performances/d'interprétation.

Il s'avère que j'introduis un bogue dans le cas de la contrainte = -1. J'ai poussé un correctif, veuillez voir si la dernière version fonctionne bien. Veuillez également vérifier si cela fonctionne lorsqu'il y a plusieurs contraintes

@tqchen J'ai testé votre correctif pour le bogue décroissant, il semble que cela fonctionne maintenant.

xgboost-no-constraint
xgboost-with-constraint

Laissez-nous confirmer s'il y a une diminution de la vitesse par rapport à la version d'origine sur certains des ensembles de données standard, alors nous pouvons le fusionner dans

@tqchen J'ai testé un modèle à deux variables, l'un avec une contrainte croissante et l'autre avec une contrainte décroissante :

params_constrained = params.copy()
params_constrained['updater'] = "grow_monotone_colmaker,prune"
params_constrained['monotone_constraints'] = "(1,-1)"

Les résultats sont bons

xgboost-two-vars-increasing
xgboost-two-vars-decreasing

Je vais essayer de trouver un peu de temps pour faire des tests de chronométrage cet après-midi.

J'ai fait une mise à jour vers #1516 pour permettre la détection automatique des options montone, maintenant l'utilisateur n'a plus qu'à passer monotone_constraints = "(0,1,1,0)" , veuillez vérifier si cela fonctionne.

Je fusionnerai cela si les tests de vitesse se passent bien, et passons à l'étape suivante d'ajout de tutoriels

@madrury @XiaoxiaoWang87

Ajout de tests pour le cas multivarié ici :

https://github.com/XiaoxiaoWang87/xgboost_mono_test/blob/master/xgb_monotonicity_constraint_testing2-multivariate.ipynb

  • Je confirme maintenant que les deux contraintes monotones = 1 et = -1 fonctionnent comme prévu.
  • Contraindre la monotonie ne conduit pas à une dégradation évidente de la vitesse*
    *vitesse = moy [ temps jusqu'à l'arrêt précoce / nombre d'itérations de boost jusqu'à l'arrêt précoce ]

no constraint: 964.9 microseconds per iteration
with constraint: 861.7 microseconds per iteration

(veuillez commenter si vous avez une meilleure façon de faire le test de vitesse)

  • Il faut être prudent lorsque l'on contraint la direction d'une variable non monotone. Cela peut entraîner une dégradation des performances.
  • Voir le code planter à cause de Check failed: (wleft) <= (wright) lors de la lecture de différents hyper-paramètres.

J'ai fait quelques expériences de chronométrage dans un cahier jupyter.

Premier test : quelques données simulées simples. Il y a deux traits, un croissant et un décroissant, mais avec une petite onde sinusoïdale superposée pour que chaque trait ne soit pas vraiment monotone

X = np.random.random(size=(N, K))
y = (5*X[:, 0] + np.sin(5*2*pi*X[:, 0])
     - 5*X[:, 1] - np.cos(5*2*pi*X[:, 1])
     + np.random.normal(loc=0.0, scale=0.01, size=N))

Voici les résultats de synchronisation de xgboosts avec et sans contraintes monotones. J'ai désactivé l'arrêt anticipé et augmenté un nombre défini d'itérations pour chacun.

D'abord sans contraintes monotones :

%%timeit -n 100
model_no_constraints = xgb.train(params, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

100 loops, best of 3: 246 ms per loop

Et ici avec des contraintes de monotonie

%%timeit -n 100
model_with_constraints = xgb.train(params_constrained, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

100 loops, best of 3: 196 ms per loop

Deuxième test : California hHousing data de sklearn. Sans contraintes

%%timeit -n 10
model_no_constraints = xgb.train(params, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

10 loops, best of 3: 5.9 s per loop

Voici les contraintes que j'ai utilisées

print(params_constrained['monotone_constraints'])

(1,1,1,0,0,1,0,0)

Et le timing pour le modèle contraint

%%timeit -n 10
model_no_constraints = xgb.train(params, dtrain, 
                                 num_boost_round = 2500, 
                                 verbose_eval = False)

10 loops, best of 3: 6.08 s per loop

@XiaoxiaoWang87 J'ai poussé un autre PR pour perdre le contrôle sur wleft et wright, veuillez voir que cela fonctionne.
@madrury Pouvez-vous également comparer avec la version précédente de XGBoost sans la fonction de contrainte ?

@tqchen Bien sûr. Pouvez-vous recommander un hachage de validation à comparer ? Dois-je simplement utiliser le commit avant l'ajout des contraintes monotones ?

Oui le précédent fera l'affaire

@tqchen Lors de la reconstruction de la version mise à jour, je reçois des erreurs que je n'avais pas auparavant. J'espère que la raison vous saute aux yeux.

Si j'essaie d'exécuter le même code qu'avant, j'obtiens une exception, voici le retraçage complet :

XGBoostError                              Traceback (most recent call last)
<ipython-input-14-63a9f6e16c9a> in <module>()
      8    model_with_constraints = xgb.train(params, dtrain, 
      9                                        num_boost_round = 1000, evals = evallist,
---> 10                                    early_stopping_rounds = 10)  

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/training.pyc in train(params, dtrain, num_boost_round, evals, obj, feval, maximize, early_stopping_rounds, evals_result, verbose_eval, learning_rates, xgb_model, callbacks)
    201                            evals=evals,
    202                            obj=obj, feval=feval,
--> 203                            xgb_model=xgb_model, callbacks=callbacks)
    204 
    205 

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/training.pyc in _train_internal(params, dtrain, num_boost_round, evals, obj, feval, xgb_model, callbacks)
     72         # Skip the first update if it is a recovery step.
     73         if version % 2 == 0:
---> 74             bst.update(dtrain, i, obj)
     75             bst.save_rabit_checkpoint()
     76             version += 1

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/core.pyc in update(self, dtrain, iteration, fobj)
    804 
    805         if fobj is None:
--> 806             _check_call(_LIB.XGBoosterUpdateOneIter(self.handle, iteration, dtrain.handle))
    807         else:
    808             pred = self.predict(dtrain)

/Users/matthewdrury/anaconda/lib/python2.7/site-packages/xgboost-0.6-py2.7.egg/xgboost/core.pyc in _check_call(ret)
    125     """
    126     if ret != 0:
--> 127         raise XGBoostError(_LIB.XGBGetLastError())
    128 
    129 

XGBoostError: [14:08:41] src/tree/tree_updater.cc:18: Unknown tree updater grow_monotone_colmaker

Si je change tout pour l'argument mot-clé que vous avez implémenté, j'obtiens également une erreur :

TypeError                                 Traceback (most recent call last)
<ipython-input-15-ef7671f72925> in <module>()
      8                                    monotone_constraints="(1)",
      9                                    num_boost_round = 1000, evals = evallist,
---> 10                                    early_stopping_rounds = 10)  

TypeError: train() got an unexpected keyword argument 'monotone_constraints'

supprimer l'argument de mise à jour et conserver les arguments de contrainte monotone dans les paramètres, maintenant que la mise à jour de contrainte monotone est activée automatiquement lorsque des contraintes monotones sont présentées

@tqchen Mon copain @amontz m'a aidé à comprendre cela immédiatement après avoir posté le message. J'avais interprété votre commentaire comme passant monotone_constraints comme kwarg à .train .

Cela fonctionne avec ces ajustements. Merci.

@madrury peux-tu confirmer la vitesse ?

Aussi @madrury et @XiaoxiaoWang87 puisque cette fonctionnalité est maintenant sur le point d'être fusionnée, ce serait formidable si vous pouviez vous coordonner pour créer un didacticiel présentant cette fonctionnalité aux utilisateurs.

Nous ne pouvons pas transférer directement le notebook ipy vers le référentiel principal. mais les images peuvent être poussées vers https://github.com/dmlc/web-data/tree/master/xgboost et markdown vers le référentiel principal.

Nous devons également modifier la conversion de chaîne de l'interface frontale, de sorte que le tuple int puisse être converti au format de tuple de chaîne qui puisse être accepté par le backend.

@hetong007 pour les changements dans R et @slundberg pour Julia

@tqchen Julia est actuellement attachée à la version 0.4 de XGBoost, donc la prochaine fois que je devrai l'utiliser et que j'aurai du temps de côté, je mettrai à jour les liaisons si personne d'autre ne l'a fait d'ici là. À ce stade, ce changement peut également être ajouté.

Voici la comparaison entre les modèles _sans_ une contrainte monotone d'avant l'implémentation à après.

Commit 8cac37 : Avant implémentation de la contrainte monotone.'
Données simulées : 100 loops, best of 3: 232 ms per loop
Données californiennes : 10 loops, best of 3: 5.89 s per loop

Commit b1c224 : Après implémentation de la contrainte monotone.
Données simulées : 100 loops, best of 3: 231 ms per loop
Données californiennes : 10 loops, best of 3: 5.61 s per loop

L'accélération pour la Californie après la mise en œuvre me semble suspecte, mais je l'ai essayé deux fois dans chaque sens, et c'est cohérent.

Je serais heureux de tenter d'écrire un tutoriel. Je vais regarder autour de la documentation existante et mettre quelque chose ensemble dans les prochains jours.

C'est super, le PR est maintenant officiellement fusionné avec le maître. Hâte de voir le tuto

Merci @madrury. Hâte d'y. Faites-moi savoir ce que je peux aider. Je serais certainement disposé à avoir plus d'études sur ce sujet.

Je l'améliorerai demain. Je suis juste curieux de savoir pourquoi communiquer avec C++ via une chaîne au lieu d'un tableau.

Je teste à partir de R. J'ai généré au hasard une donnée à deux variables et j'essaie de faire une prédiction.

Cependant, j'ai trouvé que

  1. xgboost ne contraint pas la prédiction.
  2. le paramètre monotone_constraints rend la prédiction légèrement différente.

Veuillez le signaler si j'ai fait des erreurs.

Le code pour le reproduire (testé sur la dernière version de github , pas à partir de drat ):

set.seed(1024)
x1 = rnorm(1000, 10)
x2 = rnorm(1000, 10)
y = -1*x1 + rnorm(1000, 0.001) + 3*sin(x2)
train = cbind(x1, x2)

bst = xgboost(data = train, label = y, max_depth = 2,
                   eta = 0.1, nthread = 2, nrounds = 10,
                   monotone_constraints = '(1,-1)')

pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'with constraint')
pred.ord = pred[order(train[,1])]
lines(pred.ord)

wc

bst = xgboost(data = train, label = y, max_depth = 2,
                   eta = 0.1, nthread = 2, nrounds = 10)

pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'without constraint')
pred.ord = pred[order(train[,1])]
lines(pred.ord)

woc

La contrainte a été faite sur l'ordre partiel. Donc, la contrainte n'est appliquée que si nous déplaçons l'axe montone, en gardant l'autre axe fixe

@hetong007 Pour faire mes parcelles je

  • J'ai créé un tableau contenant la grille de coordonnées x à laquelle je voulais prédire cette variable, puis la rejoindre dans le tracé linéaire. Cela utiliserait seq dans R.
  • Définissez toutes les autres variables égales à leur valeur moyenne dans les données d'apprentissage. Ce serait quelque chose comme colmeans dans R.

Voici le code python que j'ai utilisé pour les tracés que j'ai inclus ci-dessus, il devrait assez facilement se convertir en code R équivalent.

def plot_one_feature_effect(model, X, y, idx=1):

    x_scan = np.linspace(0, 1, 100)    
    X_scan = np.empty((100, X.shape[1]))
    X_scan[:, idx] = x_scan

    left_feature_means = np.tile(X[:, :idx].mean(axis=0), (100, 1))
    right_feature_means = np.tile(X[:, (idx+1):].mean(axis=0), (100, 1))
    X_scan[:, :idx] = left_feature_means
    X_scan[:, (idx+1):] = right_feature_means

    X_plot = xgb.DMatrix(X_scan)
    y_plot = model.predict(X_plot, ntree_limit=bst.best_ntree_limit)

    plt.plot(x_scan, y_plot, color = 'black')
    plt.plot(X[:, idx], y, 'o', alpha = 0.25)

Voici comment je fais les tracés de dépendance partielle (pour un modèle arbitraire):

  • Scannez une grille de valeurs pour la caractéristique X.
  • Pour chaque valeur de grille de la caractéristique X :

    • Définissez l'intégralité de la colonne X de l'entité (toutes les lignes) sur cette valeur. Autres caractéristiques inchangées.

    • Faites des prédictions pour toutes les lignes.

    • Prenez la moyenne de la prédiction.

  • Les paires résultantes (valeur de caractéristique X, prédiction moyenne) vous donnent la dépendance partielle de la caractéristique X.

Code:

def plot_partial_dependency(bst, X, y, f_id):

    X_temp = X.copy()

    x_scan = np.linspace(np.percentile(X_temp[:, f_id], 0.1), np.percentile(X_temp[:, f_id], 99.5), 50)
    y_partial = []

    for point in x_scan:

        X_temp[:, f_id] = point

        dpartial = xgb.DMatrix(X_temp[:, feature_ids])
        y_partial.append(np.average(bst.predict(dpartial)))

    y_partial = np.array(y_partial)

    # Plot partial dependence

    fig, ax = plt.subplots()
    fig.set_size_inches(5, 5)
    plt.subplots_adjust(left = 0.17, right = 0.94, bottom = 0.15, top = 0.9)

    ax.plot(x_scan, y_partial, '-', color = 'black', linewidth = 1)
    ax.plot(X[:, f_id], y, 'o', color = 'blue', alpha = 0.02)

    ax.set_xlim(min(x_scan), max(x_scan))
    ax.set_xlabel('Feature X', fontsize = 10)    
    ax.set_ylabel('Partial Dependence', fontsize = 12)

Merci pour l'orientation ! J'ai réalisé que j'avais fait une erreur stupide dans l'intrigue. Voici un autre test sur une donnée univariée, le tracé semble correct :

set.seed(1024)
x = rnorm(1000, 10)
y = -1*x + rnorm(1000, 0.001) + 3*sin(x)
train = matrix(x, ncol = 1)

bst = xgboost(data = train, label = y, max_depth = 2,
               eta = 0.1, nthread = 2, nrounds = 100,
               monotone_constraints = '(-1)')
pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'with constraint', pch=20)
lines(train[ind,1], pred.ord, col=2, lwd = 5)

rplot

bst = xgboost(data = train, label = y, max_depth = 2,
               eta = 0.1, nthread = 2, nrounds = 100)
pred = predict(bst, train)
ind = order(train[,1])
pred.ord = pred[ind]
plot(train[,1], y, main = 'without constraint', pch=20)
lines(train[ind,1], pred.ord, col=2, lwd = 5)

woc

@hetong007 Donc, le but de l'interface R est de permettre à l'utilisateur de passer le tableau R en plus des chaînes

monotone_constraints=c(1,-1)

S'il vous plaît laissez-nous savoir quand vous êtes PR le tutoriel

@hetong007 Vous êtes également plus que bienvenu pour créer une version r-blogger

@tqchen Désolé les gars, je suis en voyage de travail depuis la semaine.

J'ai envoyé quelques pull request pour un didacticiel de contraintes monotones. S'il vous plaît laissez-moi savoir ce que vous pensez, je suis heureux avec toute critique ou critique.

Espérons qu'il soit approprié de demander ceci ici : cela fonctionnera-t-il maintenant si nous mettons à jour en utilisant l'habituel git clone --recursive https://github.com/dmlc/xgboost ?

Je demande en voyant le nouveau tutoriel mais rien de nouveau sur une modification du code lui-même. Merci à tous!

oui, la nouvelle fonctionnalité est fusionnée avant la fusion du didacticiel

Bonjour,

Je ne suis pas sûr que vous ayez implémenté avec succès la montonicité globale, d'après ce que j'ai vu dans votre code, cela correspond plus à une monotonie locale.

Voici un exemple simple qui brise la monotonie :

`
df <- data.frame(y = c(2,rep(6,100),1,rep(11,100)),
x1= c(rep(1,101),rep(2,101)),x2 = c(1,rep(2,100),1,rep(2,100)))

bibliothèque (xgboost)
set.seed(0)
XGB <- xgboost(data=data.matrix(df[,-1]),label=df[,1],
objective=" reg:linear ",
bag.fraction=1,nround=100,monotone_constraints=c(1,0),
êta=0.1 )

sans_corr <- data.frame(x1=c(1,2,1,2),x2=c(1,1,2,2))

sans_corr$prediction <- predict(XGB,data.matrix(sans_corr))
`

J'espère que ma compréhension de votre code et mon exemple n'est pas faux

Actuellement, cette fonctionnalité n'est pas dans l'API Sklearn. Pouvez-vous, ou quelqu'un d'autre, aider à l'ajouter ? Merci!

Est-il possible d'imposer une monotonie générale sur une variable, sans préciser si elle doit être croissante ou décroissante ?

@davidADSP, vous pouvez effectuer une vérification de corrélation de lanceur sur le prédicteur et la cible souhaités pour voir si l'augmentation ou la diminution est appropriée.

Cette fonctionnalité semble être invalide lorsque 'tree_method':'hist'. @tqchen de l'aide ? Merci a tous.

Comment fonctionne la contrainte pour un objectif multiclasse comme mllogloss ? La contrainte de monotonie est-elle prise en charge pour la perte multiclasse ? Si oui, comment est-il appliqué. (Comme pour chaque classe il y a un arbre)

Existe-t-il un livre blanc sur l'algorithme de monoticité appliqué dans XGBOOST ? Est-ce global ou local ? Local signifie spécifique à certains nœuds, mais des nœuds dans d'autres parties de l'arbre peuvent créer une violation de la monotonie globale. Quelqu'un peut-il également m'aider à comprendre la ligne L412-417 . Pourquoi "w" est délimité - supérieur et inférieur. Comment cela aide à maintenir la monotonie. Ligne 457 - Pourquoi utiliser « mid » ?

Cette page vous a été utile?
0 / 5 - 0 notes