Skip to content

tuning with list-columns in grid #625

@SHo-JANG

Description

@SHo-JANG

I want to customize objective function.

For example, what I want to implement now is a loss function called Focal loss, which can be used when there is a class imbalance.
Focal Loss

Because focal loss is a generalization of cross-entropy, setting certain hyperparameters will result in exactly the same result as CE.

library(tidymodels)
library(xgboost)
#> 
#> Attaching package: 'xgboost'
#> The following object is masked from 'package:dplyr':
#> 
#>     slice

data(agaricus.train, package = "xgboost")
data(agaricus.test, package = "xgboost")

dtrain <- with(agaricus.train, xgb.DMatrix(data, label = label, nthread = 2))
dtest <- with(agaricus.test, xgb.DMatrix(data, label = label, nthread = 2))
watchlist <- list(train = dtrain, eval = dtest)

# original cross entropy loss
logregobj <- function(preds, dtrain) {
  labels <- getinfo(dtrain, "label")
  preds <- 1/(1 + exp(-preds))
  grad <- preds - labels
  hess <- preds * (1 - preds)
  return(list(grad = grad, hess = hess))
}

# focal_loss --------------------------------------------------------------

focal_loss <- function(preds, dtrain,alpha=0.5,focal_gamma=0) {
  labels <- getinfo(dtrain, "label")
  preds <- 1 / (1 + exp(-preds))
  
  p<- preds
  y <- labels
  
  grad <- (y*alpha*(1-p)^focal_gamma*(focal_gamma*log(p)*p-(1-p))-
             (1-y)*(1-alpha)*p^(focal_gamma)*(focal_gamma*log(1-p)*(1-p)-p))
  
  
  
  du <- y*alpha*(1-p)^(focal_gamma-1)*(log(p)*(-focal_gamma^2 *p + (1-p)*focal_gamma)+ 2*focal_gamma*(1-p)+(1-p))
  dv <- -(1-y)*(1-alpha)*p^(focal_gamma-1)*(log(1-p)*(focal_gamma^2*(1-p)-p*focal_gamma)-2*focal_gamma*p-p)
  
  hess <- (du+dv)*p*(1-p)
  
  return(list(grad = grad, hess = hess))
}


param <- list(max_depth = 2, eta = 1, nthread = 2,
              objective = "binary:logistic", eval_metric = "auc")
bst <- xgb.train(param, dtrain, nrounds = 5, watchlist)
#> [1]  train-auc:0.958228  eval-auc:0.960373 
#> [2]  train-auc:0.981413  eval-auc:0.979930 
#> [3]  train-auc:0.997070  eval-auc:0.998518 
#> [4]  train-auc:0.998757  eval-auc:0.998943 
#> [5]  train-auc:0.999298  eval-auc:0.999830
#> [1]  train-auc:0.958228  eval-auc:0.960373 
#> [2]  train-auc:0.981413  eval-auc:0.979930

bst$params$objective
#> [1] "binary:logistic"
#> [1] "binary:logistic"

param$objective <- logregobj

bst <- xgb.train(param, dtrain, nrounds = 5, watchlist)
#> [1]  train-auc:0.958228  eval-auc:0.960373 
#> [2]  train-auc:0.981413  eval-auc:0.979930 
#> [3]  train-auc:0.997070  eval-auc:0.998518 
#> [4]  train-auc:0.998757  eval-auc:0.998943 
#> [5]  train-auc:0.998120  eval-auc:0.999830
#> [1]  train-auc:0.958228  eval-auc:0.960373 
#> [2]  train-auc:0.981413  eval-auc:0.979930

param$objective <- focal_loss# alpha = 0.5, gamma= 0 -> same result! 

bst <- xgb.train(param, dtrain, nrounds = 5, watchlist)
#> [1]  train-auc:0.958228  eval-auc:0.960373 
#> [2]  train-auc:0.981413  eval-auc:0.979930 
#> [3]  train-auc:0.997070  eval-auc:0.998518 
#> [4]  train-auc:0.998166  eval-auc:0.998943 
#> [5]  train-auc:0.998823  eval-auc:0.999830

I want to tuning alpha , focal_gamma

param$objective <- partial(focal_loss, alpha = 0.3, focal_gamma = 0)

bst <- xgb.train(param, dtrain, nrounds = 5, watchlist)
#> [1]  train-auc:0.979337  eval-auc:0.980196 
#> [2]  train-auc:0.992593  eval-auc:0.993159 
#> [3]  train-auc:0.999934  eval-auc:0.999917 
#> [4]  train-auc:0.999978  eval-auc:0.999972 
#> [5]  train-auc:0.999978  eval-auc:0.999972

Created on 2023-02-28 with reprex v2.0.2

I checked that it works well on the existing xgb.train, but I don't know how to apply the tune function.
From now on, the code below is the way I tried.

library(tidymodels)
library(xgboost)
#> 
#> Attaching package: 'xgboost'
#> The following object is masked from 'package:dplyr':
#> 
#>     slice
library(scales)
library(dials)



alpha <- function(range = c(0,1), trans = NULL) {
  new_quant_param(
    type = "double",
    range = range,
    inclusive = c(TRUE, TRUE),
    trans = trans,
    label = c(num_initial_terms = "# Initial alpha"),
    finalize = NULL
  )
}


focal_gamma <- function(range = c(0, 5), trans = NULL) {
  new_quant_param(
    type = "double",
    range = range,
    inclusive = c(TRUE, TRUE),
    trans = trans,
    label = c(num_initial_terms = "# Initial gamma"),
    finalize = NULL
  )
}


data<- two_class_dat |> 
  rename(y=Class)

set.seed(100)
splits<- initial_split(data,prop = 0.8,strata = y)
train_data <- training(splits)
test_data <- testing(splits)
resamples<- vfold_cv(data = train_data,v = 5,strata = y)

xgb_model <- boost_tree( mode = "classification",
                               tree_depth     =tune(),
                               trees          =tune()) |> 
  set_engine(engine = "xgboost" ,
             objective = partial(focal_loss,
                                 focal_gamma=tune())) # 
#I wanted to tune the two hyperparameters together at first, but due to the error message, I am aiming to tune one first.

#>Error: Only one tunable value is currently allowed per argument.
#>The current argument has: `partial(focal_loss, focal_gamma = tune(), alpha = tune())`.



xgb_model |> translate()
#> Boosted Tree Model Specification (classification)
#> 
#> Main Arguments:
#>   trees = tune()
#>   tree_depth = tune()
#> 
#> Engine-Specific Arguments:
#>   objective = partial(focal_loss, focal_gamma = tune())
#> 
#> Computational engine: xgboost 
#> 
#> Model fit template:
#> parsnip::xgb_train(x = missing_arg(), y = missing_arg(), weights = missing_arg(), 
#>     nrounds = tune(), max_depth = tune(), objective = partial(focal_loss, 
#>         focal_gamma = tune()), nthread = 1, verbose = 0)



rec_base<- train_data %>% 
  recipe(y~.) |> 
  #step_mutate_at(all_numeric_predictors(), fn = list(orig = ~.)) %>%
  step_normalize(all_predictors(), -all_outcomes()) 


xgb_workflow <- 
  workflow() %>% 
  add_recipe(rec_base) %>% 
  add_model(xgb_model)


xgb_workflow %>%
  extract_parameter_set_dials()
#> Collection of 3 parameters for tuning
#> 
#>  identifier       type    object
#>       trees      trees nparam[+]
#>  tree_depth tree_depth nparam[+]
#>   objective  objective   missing
#> The parameter `objective` needs a `param` object. 
#> See `vignette('dials')` to learn more.

Created on 2023-02-28 with reprex v2.0.2

The extract_parameter_set_dials() function does not recognize the focal_gamma argument.
How I can tuning objective function?
Ultimately, I want to tune not only one parameter but also several parameters together.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugan unexpected problem or unintended behavior

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions