在多个约束条件下将 Optim 与数值向量一起使用

use optim with a numeric vector under several constraints

提问人:GNicoletti 提问时间:10/5/2023 最后编辑:ThomasIsCodingGNicoletti 更新时间:10/6/2023 访问量:97

问:

在 R 中,我想创建一个使用两个主要参数的函数:budget_optimal_allocation()

  • data:数据框
  • max_budget:数值

该函数的目标是在产生最佳tot_ROI的数据中找到每个渠道的最佳支出水平。

示例数据框的结构如下:

example_data <- data.frame(
  channel_name = c("Channel 1", "Channel 2", "Channel 3"),
  a = c(5000, 8000, 6000),
  b = c(0.001, 0.002, 0.0015),
  c = c(-1.05, -1.2, -0.95),  
  spend_min = c(100, 200, 150),
  spend_max = c(500, 800, 600)
)

a,并且是响应曲线函数的(已知)参数,该函数将支出作为输入并生成渠道收入,公式如下:bc

revenue <- a / (1 + b * (spend) ^ c)

tot_revenue <- sum(revenue)
tot_spend <- sum(spend)

tot_ROI计算方式为tot_revenue/tot_spend

我们希望优化支出(针对每个渠道名称),以便tot_ROI达到最大值。因此,该函数将优化支出的 VECTOR,因此所有渠道同时进行,而不是一个接一个。这一点非常重要,因此函数必须能够管理向量的优化,而不是逐个通道迭代循环。

优化过程中会有一些限制: 每个支出都必须在输入数据框中并在输入数据框中定义,并且必须等于 中的定义。channel_namespend_minspend_maxtot_spendmax_budgetbudget_optimal_allocation()

以下代码是我到目前为止想出的,但是

  • 我不相信是正确的方法(optim 是正确的函数吗?
  • 我不能做optimal_spend = max_budget
budget_optimal_allocation <- function(data, max_budget) {
  # Extract channel names and parameters
  channel_names <- data$channel_name
  a <- data$a
  b <- data$b
  c <- data$c
  spend_min <- data$spend_min
  spend_max <- data$spend_max
  
  # Objective function to maximize total ROI
  objective_function <- function(spend, a, b, c) {
    # Calculate total revenue and total spend
    revenue <- a / (1 + b * (spend) ^ c)
    tot_revenue <- sum(revenue)
    tot_spend <- sum(spend)
    
    # Calculate total ROI
    tot_ROI <- tot_revenue / tot_spend
    
    # Minimize the negative total ROI (to maximize total ROI)
    return(-tot_ROI)
  }
  
  # Initial guess for spend values (equal allocation for each channel)
  initial_spend <- rep(max_budget / length(channel_names), length(channel_names))
  
  # Box constraints for spend
  lower_bounds <- rep(spend_min, length(channel_names))
  upper_bounds <- rep(spend_max, length(channel_names))
  
  # Optimize spend allocation to maximize total ROI with box constraints
  result <- optim(
    par = initial_spend,
    fn = objective_function,
    a = a,
    b = b,
    c = c,
    method = "L-BFGS-B",
    lower = lower_bounds,
    upper = upper_bounds,
    control = list(fnscale = -1)
  )
  
  # Extract optimal spend for each channel
  optimal_spend <- result$par
  
  # Combine channel names and optimal spend into a data frame
  optimal_allocation <- data.frame(
    channel_name = channel_names,
    optimal_spend = optimal_spend
  )
  
  return(optimal_allocation)
}

# Example data
example_data <- data.frame(
  channel_name = c("Channel 1", "Channel 2", "Channel 3"),
  a = c(5000, 8000, 6000),
  b = c(0.001, 0.002, 0.0015),
  c = c(-1.05, -1.2, -0.95),
  spend_min = c(100, 200, 150),
  spend_max = c(500, 800, 600)
)

# Max budget for optimization
max_budget <- 1000

# Run the optimization function
optimal_allocation <- budget_optimal_allocation(example_data, max_budget)

# Print the optimal allocation
print(optimal_allocation)
R 数学 优化

评论


答:

0赞 JMenezes 10/5/2023 #1

我认为你的问题可以解决,但你将不得不做一些代数,因为它更适合无约束的优化。它可以相对地使用方法和 来处理边界,但没有比这更复杂的约束。optimoptimL-BFGS-BBrent

通过使用拉格朗日乘子,可以将简单的约束优化问题转换为无约束优化问题。从本质上讲,定义一个与原始函数相同的函数,加上约束时间的常数。

L = function(a,b,c,lambda) {objective_function(a,b,c)-lambda*(max_budget-sum(a,b,c))}

并最大化 L。

我对拉格朗日乘数有点生疏,但每次超出预算时,这都应该会惩罚目标。lambda 越高,您对超出预算的宽容就越少。

评论

0赞 GNicoletti 10/5/2023
感谢您的回复,JMenezes。最初的想法确实是这样的 - 但我尝试的所有测试都没有按预期工作(主要是因为我试图围绕优化引擎运行一个循环,这显然是错误的方式)......我会试一试你的解决方案
1赞 ThomasIsCoding 10/6/2023 #2

我认为不能在这里解决您的问题,因为该问题是具有约束的约束优化问题。一个有前途的候选人是.optimmax_budgetconstrOptim

您需要注意两件事:

  1. 既然你已经指定了,你就不需要在你的 ;否则,它将返回最小化过程。fnscale=-1-tot_ROIobjective_function

  2. 您应该有一个明确的语句,这在约束优化问题中至关重要。sum(spend) <= max_budget

法典

下面是一个实现(我想移出,因为如果您需要更改目标函数,这将更容易维护代码)objective_functionbudget_optimal_allocation

# Objective function to maximize total ROI
objective_function <- function(spend, a, b, c) {
    # Calculate total revenue and total spend
    revenue <- a / (1 + b * (spend)^c)
    tot_revenue <- sum(revenue)
    tot_spend <- sum(spend)

    # Calculate total ROI
    tot_revenue / tot_spend
}

budget_optimal_allocation <- function(data, max_budget) {
    # Extract channel names and parameters
    channel_names <- data$channel_name
    a <- data$a
    b <- data$b
    c <- data$c
    spend_min <- data$spend_min
    spend_max <- data$spend_max


    # Initial guess for spend values (equal allocation for each channel)
    init <- spend_min + sqrt(.Machine$double.eps)
    # Box constraints for spend
    k <- nrow(data)
    ui <- rbind(diag(k), -diag(k), -rep(1, k))
    ci <- c(spend_min, -spend_max, -max_budget)
    # Optimize spend allocation to maximize total ROI with box constraints
    result <- constrOptim(
        theta = init,
        f = objective_function,
        a = a,
        b = b,
        c = c,
        ui = ui,
        ci = ci,
        method = "Nelder-Mead",
        control = list(fnscale = -1)
    )

    # Combine channel names and optimal spend into a data frame
    return(list(
        optimal_allocation = data.frame(
            channel_name = channel_names,
            optimal_spend = result$par
        ),
        max_tot_ROI = result$value
    ))
}

输出

使用与您的问题中相同的数据

# Example data
example_data <- data.frame(
    channel_name = c("Channel 1", "Channel 2", "Channel 3"),
    a = c(5000, 8000, 6000),
    b = c(0.001, 0.002, 0.0015),
    c = c(-1.05, -1.2, -0.95),
    spend_min = c(100, 200, 150),
    spend_max = c(500, 800, 600)
)

# Max budget for optimization
max_budget <- 1000

# Run the optimization function
optimal_allocation <- budget_optimal_allocation(example_data, max_budget)

我们拭目以待

> optimal_allocation
$optimal_allocation
  channel_name optimal_spend
1    Channel 1           100
2    Channel 2           200
3    Channel 3           150

$max_tot_ROI
[1] 42.2219

如果你想用完所有的预算

您可以进行以下更改,因此,代码是init <- (spend_min + spend_max) / 2ui <- rbind(diag(k), -diag(k), rep(1, k))ci <- c(spend_min, -spend_max, max_budget)

budget_optimal_allocation <- function(data, max_budget) {
    # Extract channel names and parameters
    channel_names <- data$channel_name
    a <- data$a
    b <- data$b
    c <- data$c
    spend_min <- data$spend_min
    spend_max <- data$spend_max


    # Initial guess for spend values (equal allocation for each channel)
    eps <- sqrt(.Machine$double.eps)
    init <- (spend_min + spend_max) / 2
    # Box constraints for spend
    k <- nrow(data)
    ui <- rbind(diag(k), -diag(k), rep(1, k))
    ci <- c(spend_min, -spend_max, max_budget)
    # Optimize spend allocation to maximize total ROI with box constraints
    result <- constrOptim(
        theta = init,
        f = objective_function,
        a = a,
        b = b,
        c = c,
        ui = ui,
        ci = ci,
        method = "Nelder-Mead",
        control = list(fnscale = -1)
    )

    # Combine channel names and optimal spend into a data frame
    return(list(
        optimal_allocation = data.frame(
            channel_name = channel_names,
            optimal_spend = result$par
        ),
        max_tot_ROI = result$value
    ))
}

您将看到输出

> optimal_allocation
$optimal_allocation
  channel_name optimal_spend
1    Channel 1      252.1112
2    Channel 2      427.5322
3    Channel 3      320.3566

$max_tot_ROI
[1] 18.99994

并验证所有预算是否已用完

> sum(optimal_allocation$optimal_allocation$optimal_spend)
[1] 1000

评论

0赞 GNicoletti 10/6/2023
非常感谢您的回答,ThomasIsCoding!对函数和参数进行一些解释非常有帮助。我从你的代码中遇到的唯一问题(你可以从你发布的输出中看到它)是<......因此,我们目前没有使用所有预算 - 您提到要有一个明确的声明,但是您将其放在脚本的哪个位置?sum(optimal_spend)max_budget
0赞 ThomasIsCoding 10/6/2023
@GNicoletti 查看 和 的行uici
0赞 GNicoletti 10/6/2023
是的,我注意到你在那里提到了max_budget,但是optimal_spend的输出是 100 + 200 + 150,低于 1000 的max_budget。如果这是一个非常愚蠢的问题/观察,我深表歉意,我对优化功能很陌生
0赞 ThomasIsCoding 10/6/2023
@GNicoletti 是分配的上限。您不必用完它,但您永远不会超出预算。max_budgetspend
0赞 GNicoletti 10/6/2023
是的,但作为约束,我想全部使用它 - 我知道早点停止可能更有效,但我想分配所有它