web-dev-qa-db-fra.com

xgboost dans R: comment xgb.cv passe-t-il les paramètres optimaux dans xgb.train

J'explorais le paquet xgboost dans R et suis allé à travers plusieurs démonstrations ainsi que des tutoriels, mais cela me perturbe toujours: après avoir utilisé xgb.cv pour effectuer une validation croisée, comment les paramètres optimaux sont-ils passés à xgb.train Ou devrais-je calculer les paramètres idéaux (tels que nround, max.depth) en fonction du résultat de xgb.cv?

param <- list("objective" = "multi:softprob",
              "eval_metric" = "mlogloss",
              "num_class" = 12)
cv.nround <- 11
cv.nfold <- 5
mdcv <-xgb.cv(data=dtrain,params = param,nthread=6,nfold = cv.nfold,nrounds = cv.nround,verbose = T)

md <-xgb.train(data=dtrain,params = param,nround = 80,watchlist = list(train=dtrain,test=dtest),nthread=6)
24
snowneji

On dirait que vous avez mal compris xgb.cv, ce n'est pas une fonction de recherche de paramètres. La validation croisée des plis en k, rien de plus.

Dans votre code, cela ne change pas la valeur de param

Pour trouver les meilleurs paramètres dans XGBoost de R, il existe quelques méthodes. Ce sont 2 méthodes, 

(1) Utilisez le paquetage mlr, http://mlr-org.github.io/mlr-tutorial/release/html/

Il y a un code XGBoost + mlr example dans le défi Prudential de Kaggle, 

Mais ce code est pour la régression, pas la classification. Autant que je sache, il n'y a pas encore de métrique mlogloss dans le paquet mlr; vous devez donc coder vous-même la mesure mlogloss. CMIIW. 

(2) Deuxième méthode, en définissant manuellement les paramètres puis en répétant, par exemple, 

param <- list(objective = "multi:softprob",
      eval_metric = "mlogloss",
      num_class = 12,
      max_depth = 8,
      eta = 0.05,
      gamma = 0.01, 
      subsample = 0.9,
      colsample_bytree = 0.8, 
      min_child_weight = 4,
      max_delta_step = 1
      )
cv.nround = 1000
cv.nfold = 5
mdcv <- xgb.cv(data=dtrain, params = param, nthread=6, 
                nfold=cv.nfold, nrounds=cv.nround,
                verbose = T)

Ensuite, vous trouvez le meilleur (minimum) mlogloss, 

min_logloss = min(mdcv[, test.mlogloss.mean])
min_logloss_index = which.min(mdcv[, test.mlogloss.mean])

min_logloss est la valeur minimale de mlogloss, alors que min_logloss_index correspond à l'index (round). 

Vous devez répéter le processus ci-dessus plusieurs fois, à chaque fois modifiez les paramètres manuellement (mlr fait la répétition à votre place). Jusqu'à ce que vous obteniez finalement le meilleur minimum global min_logloss

Remarque: Vous pouvez le faire dans une boucle de 100 ou 200 itérations, dans laquelle vous définissez la valeur des paramètres de manière aléatoire pour chaque itération. De cette façon, vous devez enregistrer le meilleur [parameters_list, min_logloss, min_logloss_index] dans des variables ou dans un fichier. 

Remarque: il est préférable de définir une graine aléatoire par set.seed() pour le résultat reproductible. Un résultat différent pour chaque graine aléatoire. Donc, vous devez enregistrer [parameters_list, min_logloss, min_logloss_index, seednumber] dans les variables ou le fichier. 

Disons que finalement vous obtenez 3 résultats en 3 itérations/répétitions: 

min_logloss = 2.1457, min_logloss_index = 840
min_logloss = 2.2293, min_logloss_index = 920
min_logloss = 1.9745, min_logloss_index = 780

Ensuite, vous devez utiliser les troisièmes paramètres (il a le minimum global min_logloss de 1.9745). Votre meilleur indice (nombre) est 780

Une fois que vous obtenez les meilleurs paramètres, utilisez-le dans la formation, 

# best_param is global best param with minimum min_logloss
# best_min_logloss_index is the global minimum logloss index
nround = 780
md <- xgb.train(data=dtrain, params=best_param, nrounds=nround, nthread=6)

Je ne pense pas que vous ayez besoin de watchlist dans la formation, car vous avez effectué la validation croisée. Mais si vous voulez toujours utiliser watchlist, c'est très bien. 

Mieux encore, vous pouvez utiliser un arrêt précoce dans xgb.cv

mdcv <- xgb.cv(data=dtrain, params=param, nthread=6, 
                nfold=cv.nfold, nrounds=cv.nround,
                verbose = T, early.stop.round=8, maximize=FALSE)

Avec ce code, lorsque mlogloss valeur ne diminue pas en 8 étapes, le xgb.cv s’arrête. Vous pouvez gagner du temps. Vous devez définir maximize sur FALSE, car vous vous attendez à un minimum de pertes. 

Voici un exemple de code, avec 100 itérations en boucle et des paramètres choisis au hasard. 

best_param = list()
best_seednumber = 1234
best_logloss = Inf
best_logloss_index = 0

for (iter in 1:100) {
    param <- list(objective = "multi:softprob",
          eval_metric = "mlogloss",
          num_class = 12,
          max_depth = sample(6:10, 1),
          eta = runif(1, .01, .3),
          gamma = runif(1, 0.0, 0.2), 
          subsample = runif(1, .6, .9),
          colsample_bytree = runif(1, .5, .8), 
          min_child_weight = sample(1:40, 1),
          max_delta_step = sample(1:10, 1)
          )
    cv.nround = 1000
    cv.nfold = 5
    seed.number = sample.int(10000, 1)[[1]]
    set.seed(seed.number)
    mdcv <- xgb.cv(data=dtrain, params = param, nthread=6, 
                    nfold=cv.nfold, nrounds=cv.nround,
                    verbose = T, early.stop.round=8, maximize=FALSE)

    min_logloss = min(mdcv[, test.mlogloss.mean])
    min_logloss_index = which.min(mdcv[, test.mlogloss.mean])

    if (min_logloss < best_logloss) {
        best_logloss = min_logloss
        best_logloss_index = min_logloss_index
        best_seednumber = seed.number
        best_param = param
    }
}

nround = best_logloss_index
set.seed(best_seednumber)
md <- xgb.train(data=dtrain, params=best_param, nrounds=nround, nthread=6)

Avec ce code, vous exécutez la validation croisée 100 fois, à chaque fois avec des paramètres aléatoires. Ensuite, vous obtenez le meilleur jeu de paramètres, à savoir l'itération avec min_logloss minimum. 

Augmentez la valeur de early.stop.round au cas où vous découvririez qu'il est trop petit (arrêt trop tôt). Vous devez également modifier la limite des valeurs de paramètre aléatoires en fonction de vos caractéristiques de données. 

Et, pour 100 ou 200 itérations, je pense que vous voulez changer verbose en FALSE. 

Note latérale: C’est un exemple de méthode aléatoire, vous pouvez l’ajuster par exemple. par optimisation bayésienne pour une meilleure méthode. Si vous avez la version Python de XGBoost, il existe un bon script hyperparamètre pour XGBoost, https://github.com/mpearmain/BayesBoost pour rechercher le meilleur jeu de paramètres à l'aide de l'optimisation bayésienne. 

Edit: Je veux ajouter une 3ème méthode manuelle, postée par "Davut Polat", maître Kaggle, dans le forum Kaggle .

Edit: Si vous connaissez Python et Sklearn, vous pouvez également utiliser GridSearchCV avec xgboost.XGBClassifier ou xgboost.XGBRegressor

60
silo

C'est une bonne question et une excellente réponse de silo avec beaucoup de détails! Je l’ai trouvé très utile pour quelqu'un de nouveau à xgboost comme moi. Je vous remercie. La méthode de randomisation et de comparaison aux limites est très inspirante. Bon à utiliser et bon à savoir. Maintenant, en 2018, quelques légères modifications sont nécessaires, par exemple, early.stop.round devrait être early_stopping_rounds. La sortie mdcv est organisée légèrement différemment:

  min_rmse_index  <-  mdcv$best_iteration
  min_rmse <-  mdcv$evaluation_log[min_rmse_index]$test_rmse_mean

Et dépend de l'application (linéaire, logistique, etc.), la objective, eval_metric et les paramètres doivent être ajustés en conséquence. 

Pour la commodité de ceux qui exécutent une régression, voici la version légèrement ajustée du code (la plupart sont les mêmes que ci-dessus). 

library(xgboost)
# Matrix for xgb: dtrain and dtest, "label" is the dependent variable
dtrain <- xgb.DMatrix(X_train, label = Y_train)
dtest <- xgb.DMatrix(X_test, label = Y_test)

best_param <- list()
best_seednumber <- 1234
best_rmse <- Inf
best_rmse_index <- 0

set.seed(123)
for (iter in 1:100) {
  param <- list(objective = "reg:linear",
                eval_metric = "rmse",
                max_depth = sample(6:10, 1),
                eta = runif(1, .01, .3), # Learning rate, default: 0.3
                subsample = runif(1, .6, .9),
                colsample_bytree = runif(1, .5, .8), 
                min_child_weight = sample(1:40, 1),
                max_delta_step = sample(1:10, 1)
  )
  cv.nround <-  1000
  cv.nfold <-  5 # 5-fold cross-validation
  seed.number  <-  sample.int(10000, 1) # set seed for the cv
  set.seed(seed.number)
  mdcv <- xgb.cv(data = dtrain, params = param,  
                 nfold = cv.nfold, nrounds = cv.nround,
                 verbose = F, early_stopping_rounds = 8, maximize = FALSE)

  min_rmse_index  <-  mdcv$best_iteration
  min_rmse <-  mdcv$evaluation_log[min_rmse_index]$test_rmse_mean

  if (min_rmse < best_rmse) {
    best_rmse <- min_rmse
    best_rmse_index <- min_rmse_index
    best_seednumber <- seed.number
    best_param <- param
  }
}

# The best index (min_rmse_index) is the best "nround" in the model
nround = best_rmse_index
set.seed(best_seednumber)
xg_mod <- xgboost(data = dtest, params = best_param, nround = nround, verbose = F)

# Check error in testing data
yhat_xg <- predict(xg_mod, dtest)
(MSE_xgb <- mean((yhat_xg - Y_test)^2))
2
Yang Liu

J'ai trouvé la réponse de silo très utile. En plus de son approche de recherche aléatoire, vous pouvez utiliser l'optimisation bayésienne pour faciliter le processus de recherche d'hyperparamètre, par exemple Bibliothèque rBayesianOptimization . Ce qui suit est mon code avec la bibliothèque d’optimisation rbayesian.

cv_folds <- KFold(dataFTR$isPreIctalTrain, nfolds = 5, stratified = FALSE, seed = seedNum)
xgb_cv_bayes <- function(nround,max.depth, min_child_weight, subsample,eta,gamma,colsample_bytree,max_delta_step) {
param<-list(booster = "gbtree",
            max_depth = max.depth,
            min_child_weight = min_child_weight,
            eta=eta,gamma=gamma,
            subsample = subsample, colsample_bytree = colsample_bytree,
            max_delta_step=max_delta_step,
            lambda = 1, alpha = 0,
            objective = "binary:logistic",
            eval_metric = "auc")
cv <- xgb.cv(params = param, data = dtrain, folds = cv_folds,nrounds = 1000,early_stopping_rounds = 10, maximize = TRUE, verbose = verbose)

list(Score = cv$evaluation_log$test_auc_mean[cv$best_iteration],
     Pred=cv$best_iteration)
# we don't need cross-validation prediction and we need the number of rounds.
# a workaround is to pass the number of rounds(best_iteration) to the Pred, which is a default parameter in the rbayesianoptimization library.
}
OPT_Res <- BayesianOptimization(xgb_cv_bayes,
                              bounds = list(max.depth =c(3L, 10L),min_child_weight = c(1L, 40L),
                                            subsample = c(0.6, 0.9),
                                            eta=c(0.01,0.3),gamma = c(0.0, 0.2),
                                            colsample_bytree=c(0.5,0.8),max_delta_step=c(1L,10L)),
                              init_grid_dt = NULL, init_points = 10, n_iter = 10,
                              acq = "ucb", kappa = 2.576, eps = 0.0,
                              verbose = verbose)
best_param <- list(
booster = "gbtree",
eval.metric = "auc",
objective = "binary:logistic",
max_depth = OPT_Res$Best_Par["max.depth"],
eta = OPT_Res$Best_Par["eta"],
gamma = OPT_Res$Best_Par["gamma"],
subsample = OPT_Res$Best_Par["subsample"],
colsample_bytree = OPT_Res$Best_Par["colsample_bytree"],
min_child_weight = OPT_Res$Best_Par["min_child_weight"],
max_delta_step = OPT_Res$Best_Par["max_delta_step"])
# number of rounds should be tuned using CV
#https://www.hackerearth.com/practice/machine-learning/machine-learning-algorithms/beginners-tutorial-on-xgboost-parameter-tuning-r/tutorial/
# However, nrounds can not be directly derivied from the bayesianoptimization function
# Here, OPT_Res$Pred, which was supposed to be used for cross-validation, is used to record the number of rounds
nrounds=OPT_Res$Pred[[which.max(OPT_Res$History$Value)]]
xgb_model <- xgb.train (params = best_param, data = dtrain, nrounds = nrounds)
0
Penning Yu