我正在尝试优化广告预算计划。广告预算方案由2个产品组成,每个产品有不同时长的广告。优化器应该实现两个目标:
以下是主要的用户输入:
Total Advertising Budget: £150K
Product-A Budget: £50K
Product-B Budget: £100K
Adverts Duration Table:
|‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾|
| Advert_No | Duration | Products |
|___________|__________|__________|
| Advert_1 | 2 Weeks | Product_A|
| Advert_2 | 4 Weeks | Product_A|
| Advert_3 | 5 Weeks | Product_B|
| Advert_4 | 3 Weeks | Product_A|
| Advert_5 | 2 Weeks | Product_B|
| Advert_6 | 1 Weeks | Product_A|
| Advert_7 | 3 Weeks | Product_B|
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
给定一个 10 周的窗口,我希望优化器以最佳方式分配广告以最大化目标函数(即广告应该最佳放置,预算应该最佳分配)。
下面是我希望优化器输出的信息示例:
|‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|
| Weeks | Product A | Product B |
|_______|_______________|_______________|
|Week 1 | Advert_4 | Advert_5 |
|Week 2 | (£15k) | (£50k) |
|Week 3 |_______________|‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|
|Week 4 | Advert_6 (£5k)| Advert_7 |
|Week 5 |‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾| (£15k) |
|Week 6 | Advert_2 |‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾|
|Week 7 | (£10k) | Advert_3 |
|Week 8 |_______________| (£35k) |
|Week 9 | Advert_1 | |
|Week 10| (£20k) | |
‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾
这是我对解决方案的尝试。由于这是一个分解为数周的年度广告预算计划,因此这里的 x[i] 是变量,即每周的预算。我一直在使用 Scipy Optimize 库,我想继续使用它。
# Import Libraries
import pandas as pd
import numpy as np
import scipy.optimize as so
import random
# Define Objective function (Maximization)
def obj_func(matrix):
def prod_a_func(x):
# Advert budgets for Prod_a is concave Exponential Function
return (1 - np.exp(-x / 70000)) * 0.2
def prod_b_func(x):
# Advert budgets for Prod_a is concave Exponential Function
return (1 - np.exp(-x / 200000)) * 0.6
prod_a = prod_a_func(matrix.reshape((-1, 2))[:,0])
prod_b = prod_b_func(matrix.reshape((-1, 2))[:,1])
output_matrix = np.column_stack((prod_a, prod_b))
return np.sum(output_matrix)
# Create optimizer function
def optimizer_result(tot_budget, col_budget_list, bucket_size_list):
# Create constraint 1) - total matrix sum range
constraints_list = [{'type': 'eq', 'fun': lambda x: np.sum(x) - tot_budget},
{'type': 'eq', 'fun': lambda x: (sum(x[i] for i in range(0, 10, 5)) - col_budget_list[0])},
{'type': 'eq', 'fun': lambda x: (sum(x[i] for i in range(1, 10, 5)) - col_budget_list[1])},
{'type': 'eq', 'fun': lambda x, advert_len_list[0]: [item for item in x for i in range(advert_len_list[0])]},
{'type': 'eq', 'fun': lambda x, advert_len_list[1]: [item for item in x for i in range(advert_len_list[1])]}]
# Create an inital matrix
start_matrix = [random.randint(0, 3) for i in range(0, 10)]
# Run optimizer
optimizer_solution = so.minimize(obj_func, start_matrix, method='SLSQP', bounds=[(0, tot_budget)] * 10,
tol=0.01,
options={'disp': True, 'maxiter': 100}, constraints=constraints_list)
return optimizer_solution
# Initalise constraints
tot_budget = 150000
col_budget_list = [100000, 50000]
advert_len_list = [[2,4,3,1], [5,2,3]]
# Run Optimizer
y = optimizer_result(tot_budget, col_budget_list, advert_len_list)
advert_plan = pd.DataFrame(y['x'].reshape(-1,2),columns=["Product-A", "Product-B"])
编辑
这里是这个问题的数学总结:
我有一个 10x2 矩阵,我需要对其进行优化,这将给我最大的投资回报率。以下是限制条件:
我会在这里尝试做慈善,所以请耐心等待。
决定投放广告的周数
是一个谎言,并且明确地 not 您的代码尝试的东西。你自己说每个广告的时间不重要,所以我忽略这个。
你不应该为
prod_a_func
和prod_b_func
定义函数。除其他原因外,它比矢量化慢,并且不可推广到更高的产品数量。那些神秘的参数 70000
、0.2
等需要放入命名向量中并在单个 ROI 表达式中使用。
目标函数最重要的问题之一是它声称最大化,但并没有最大化。要做到这一点,你需要否定你的总和。
你的约束很混乱。它们都需要被删除并替换为一个线性约束:用于每个产品的总预算至多传递给函数的产品预算。预算通常不是“你必须花这么多”;他们是“你最多只能花这么多”。给定单个产品约束,总约束是多余的。
初始值是一个非常糟糕的主意。不要在这里传递随机数据;这会让你得到一个非确定性的优化。我们可以做出更好的猜测,事实上,由于整个问题是如此微不足道,优化后的值最终等于猜测:将预算平均分配给所有广告。
除非你有很好的理由,否则不要设置特定的方法、公差和迭代次数。删除所有这些。
* 10
不是一个好主意。您的边界需要基于函数输入。
import string
from typing import Sequence
import pandas as pd
import numpy as np
from scipy.optimize import minimize, Bounds, LinearConstraint
def obj_func(flat: np.ndarray, A: np.ndarray) -> float:
# These mystery constants should be given better names and passed in as arguments
a = ((70_000,), (200_000,))
b = ((0.2,), (0.6,))
# Convert 'flat' into a matrix of n_products by n_advertisements with zeros where the
# advertisement is not for that product.
expenditure = np.zeros_like(A, dtype=float)
expenditure[A] = flat
# advert ROI for product is a concave exponential function
roi = (1 - np.exp(-expenditure / a)) * b
# to maximize, this must be negated
return -roi.sum()
def optimizer_result(
product_budgets: Sequence[float],
advert_days: Sequence[tuple[int, ...]],
) -> pd.DataFrame:
# the total number of advertisements
n_adverts = sum(len(p) for p in advert_days)
n_products = len(advert_days)
# the number of days over which the campaign runs
n_days = sum(advert_days[0])
# This is a selection matrix used in both the linear budget
# constraint and to spread the budget decision variable
A = np.zeros((n_products, n_adverts), dtype=bool)
start = np.empty(n_adverts)
upper = np.empty(n_adverts)
i_advert = 0
for i_product, (budget, days) in enumerate(zip(product_budgets, advert_days)):
next_advert = i_advert + len(days)
# wherever this product has an advertisement, write a 1
A[i_product, i_advert: next_advert] = 1
# the initial value for each budget is the product's
# budget evenly distributed over each advertisement
start[i_advert: next_advert] = budget / len(days)
# the upper bound for each advertisement budget is the budget for the entire product
upper[i_advert: next_advert] = budget
i_advert = next_advert
solution = minimize(
fun=obj_func,
args=(A,),
x0=start,
bounds=Bounds(lb=np.zeros_like(upper), ub=upper),
constraints=LinearConstraint(
A=A,
lb=np.zeros(n_products),
ub=product_budgets,
))
assert solution.success
df = pd.DataFrame(
columns=pd.Index(name='product', data=tuple(string.ascii_uppercase[:n_products])),
index=pd.RangeIndex(name='day', start=1, stop=n_days+1),
dtype=float,
)
i_advert = 0
for i_product, days in enumerate(advert_days):
today = 0
for day_span in days:
next_day = today + day_span
# the displayed budget for this product and advertisement on this day is the
# advertisement's budget divided evenly among its day span
df.iloc[today: next_day, i_product] = solution.x[i_advert] / day_span
today = next_day
i_advert += 1
return df
advert_plan = optimizer_result(
product_budgets=(100_000, 50_000),
advert_days=((2, 4, 3, 1), (5, 2, 3)),
)
print(advert_plan)
product A B
day
1 12500.000000 3333.333333
2 12500.000000 3333.333333
3 6250.000000 3333.333333
4 6250.000000 3333.333333
5 6250.000000 3333.333333
6 6250.000000 8333.333333
7 8333.333333 8333.333333
8 8333.333333 5555.555556
9 8333.333333 5555.555556
10 25000.000000 5555.555556
同样,此优化是空操作,等于初始猜测。