facebookexperimental / Robyn

Robyn is an experimental, AI/ML-powered and open sourced Marketing Mix Modeling (MMM) package from Meta Marketing Science. Our mission is to democratise modeling knowledge, inspire the industry through innovation, reduce human bias in the modeling process & build a strong open source marketing science community.
https://facebookexperimental.github.io/Robyn/
MIT License
1.16k stars 347 forks source link

Optimizer Used for Budget Allocator #760

Closed noob100000 closed 1 year ago

noob100000 commented 1 year ago

Hi. I am not familiar with R. Could anyone please tell me what optimization library is used for budget allocator? As far as I can see NLOPTR is used, but I think for this the gradient needs to be given. I cannot find where it is specified. If possible, could you please point to the source of the library used and how to use it? Thank you for this amazing work.

shamzos commented 1 year ago

Hello! Before we delve into your question, I want to clarify that I am not part of the Robyn team; I'm just providing some insight based on my understanding.

It appears that the team utilizes the nloptr package in R, which provides an interface to NLopt. If you examine the allocator.R file, it's evident that they call the optimization function for different scenarios. For example, here is what they do for the max_response scenario:

if (scenario == "max_response") {
    ## bounded optimisation
    nlsMod <- nloptr::nloptr(
      x0 = x0,
      eval_f = eval_f,
      eval_g_eq = if (constr_mode == "eq") eval_g_eq else NULL,
      eval_g_ineq = if (constr_mode == "ineq") eval_g_ineq else NULL,
      lb = lb, ub = ub,
      opts = list(
        "algorithm" = "NLOPT_LD_AUGLAG",
        "xtol_rel" = 1.0e-10,
        "maxeval" = maxeval,
        "local_opts" = local_opts
      ),
      target_value = NULL
    )
    ## unbounded optimisation
    nlsModUnbound <- nloptr::nloptr(
      x0 = x0_ext,
      eval_f = eval_f,
      eval_g_eq = if (constr_mode == "eq") eval_g_eq else NULL,
      eval_g_ineq = if (constr_mode == "ineq") eval_g_ineq else NULL,
      lb = lb_ext, ub = ub_ext,
      opts = list(
        "algorithm" = "NLOPT_LD_AUGLAG",
        "xtol_rel" = 1.0e-10,
        "maxeval" = maxeval,
        "local_opts" = local_opts
      ),
      target_value = NULL
    )
  }

Looking at the nloptr documentation, it shows that the function indeed contains the following parameters:

eval_f: This function returns the value of the objective function. It can also return gradient information simultaneously in a list with elements objective and gradient. eval_grad_f: This function returns the value of the gradient of the objective function. Not all algorithms require a gradient.

Thus, I believe eval_f is the parameter you're interested in:


eval_f <- function(X, target_value) {
  # eval_list <- get("eval_list", pos = as.environment(-1))
  eval_list <- getOption("ROBYN_TEMP")
  coefs_eval <- eval_list[["coefs_eval"]]
  alphas_eval <- eval_list[["alphas_eval"]]
  inflexions_eval <- eval_list[["inflexions_eval"]]
  # mediaSpendSortedFiltered <- eval_list[["mediaSpendSortedFiltered"]]
  hist_carryover_eval <- eval_list[["hist_carryover_eval"]]

  objective <- -sum(mapply(
    fx_objective,
    x = X,
    coeff = coefs_eval,
    alpha = alphas_eval,
    inflexion = inflexions_eval,
    x_hist_carryover = hist_carryover_eval,
    SIMPLIFY = TRUE
  ))

  gradient <- c(mapply(
    fx_gradient,
    x = X,
    coeff = coefs_eval,
    alpha = alphas_eval,
    inflexion = inflexions_eval,
    x_hist_carryover = hist_carryover_eval,
    SIMPLIFY = TRUE
  ))

  objective.channel <- mapply(
    fx_objective.chanel,
    x = X,
    coeff = coefs_eval,
    alpha = alphas_eval,
    inflexion = inflexions_eval,
    x_hist_carryover = hist_carryover_eval,
    SIMPLIFY = TRUE
  )

  optm <- list(objective = objective, gradient = gradient, objective.channel = objective.channel)
  return(optm)
}

I hope this helped.

noob100000 commented 1 year ago

@shamzos Thank you for your response!! Did not know about the eval_f. Will look into it.

gufengzhou commented 1 year ago

feel free to reopen if necessary