批量间隔优化

问题描述 投票:0回答:1

我正在尝试优化广告预算计划。广告预算方案由2个产品组成,每个产品有不同时长的广告。优化器应该实现两个目标:

  1. 决定投放广告的周数
  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 矩阵,我需要对其进行优化,这将给我最大的投资回报率。以下是限制条件:

  1. 矩阵中所有元素的总和等于总预算
  2. 列中所有元素的总和必须等于列预算(例如,第 0 列的总和等于 Prod_A 预算)
  3. 可以设置个人周预算(例如元素 [1,3] 等于 £10k)
  4. 每个广告的时间无关紧要
  5. 只有广告的持续时间适合每个产品的 10 周窗口,才能提供最大的投资回报率
python optimization scipy-optimize constraint-programming scipy-optimize-minimize
1个回答
0
投票

我会在这里尝试做慈善,所以请耐心等待。

决定投放广告的周数

是一个谎言,并且明确地 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

同样,此优化是空操作,等于初始猜测。

© www.soinside.com 2019 - 2024. All rights reserved.