此 Jupyter Notebook 描述了银行和金融服务行业常见的营销活动优化问题。
该问题使用 Gurobi Python API 构建,并使用 Gurobi Optimizer 解决。
在这个营销活动问题中,数学优化模型的关键参数是通过机器学习预测响应模型预估出来的。
营销活动优化问题是指在满足各种业务约束的情况下,为使总预期利润最大化而向每个客户提供何种产品。
该建模示例处于初级阶段,我们假设你了解Python,并且具有一些有关构建数学优化模型的知识。希望你可以从示例中了解所有遗漏的概念。
注意: 你可以通过单击 此处 下载包含此示例和其他示例的代码。为了正确运行此 Jupyter Notebook,你必须具有 Gurobi 许可证。如果你没有,则可以商业用户身份申请 试用许可证,或以学术用户身份下载 免费许可证。
银行和金融服务行业营销的主要目标是“在正确的时间向正确的客户提供正确的产品”。然而,实际上能够实现这一目标是一项复杂且具有挑战性的任务。让这变得特别困难的是,公司拥有多种产品,并在一组复杂的业务约束下运作。为了最大化市场投资回报和满足业务约束,选择向哪些客户提供哪些产品是非常复杂的。
假设有一家大型银行,该银行已努力成为一家以客户为中心的机构,而不是一家垂直产品驱动型公司。该银行的目标是“通过提供满足客户独特需求的解决方案,尽最大可能帮助他们在经济上有所改善”。此目标的直接结果是,营销活动是多个产品活动,而不是单个产品活动。
这将数据科学和营销活动的定位过程从相当简单的单个响应模型变为一个相当复杂的过程,包括选择哪个产品通过哪个渠道提供给哪个客户。
银行的营销团队习惯于将业务规则直接应用于目标客户。例如,他们仅根据产品差距或营销人员的业务直觉来瞄准客户。该银行的营销人员还应用了RFM类型分析,使用一般近因、频率、货币度量以及产品缺口来针对特定的目标客户。
营销团队目前广泛使用的方法是依赖预测响应模型来锁定客户。这些模型估计客户响应特定报价的可能性,并且可以显着提高对产品报价的响应率。然而,当一家公司有好几种产品要推销,在营销计划中要考虑其他业务约束时,仅仅知道客户对特定报价做出响应概率是不够的。
一般来说,营销团队还面临一个问题,就是要知道该向客户提供哪种产品,而不仅仅是该向哪个客户提供产品。在实践中,使用了许多特殊规则:
基于响应率或估计预期盈利能力指标的优先级规则
对可以销售的产品进行优先级排序的业务规则
为特定活动选择客户的产品响应模型。
有一种方法很容易实现,但可能不能产生最佳的客户联系方案,它依赖于预期报价获利能力的指标,以选择向客户提供哪些产品。然而,这种方法的一个缺点是它不能有效地处理客户联系方案上的复杂约束。
为了解决这个营销活动优化问题,M.D.Cohen[1] 提出了一种基于丰业银行数据的MIP方法。营销活动优化问题考虑了11种独特的报价:5种投资、3种贷款和3种日常银行报价。投资优惠包括担保投资证书(GICs)、共同基金、注册教育储蓄计划(RESP)和两个独特的折扣经纪服务。贷款优惠包括一项抵押贷款和两项信用卡贷款。
日常银行服务包括两项 Scotia 网上银行服务中的一项和存款账户获取服务。在这里,“活动”一词指的是一个大型的、积极主动的客户联系活动,包括11个不同的优惠;它可以被认为是十一个单一的产品活动,通常是在同一时间提供给一个不重叠的客户群。该活动的潜在目标市场包括大约250万客户。
在此Jupyter Notebook中,我们将使用此MIP方法来解决银行的营销活动优化问题。应该注意的是,几乎所有行业的任何公司都可以使用这种方法在其业务限制优化营销活动方案。
银行的营销团队需要确定向每个客户提供什么样的产品,以使营销活动的投资回报最大化,同时考虑以下限制因素:
活动可用资金的限制。
活动中可以提供的最低产品数量的限制。
活动必须满足的投资回报率最低要求。
数学优化(也称为数学编程)是一种声明性方法,其中建模者制定了一个优化问题,该问题捕获了复杂决策问题的关键特征。然后 Gurobi 使用最先进的数学和计算机科学技术来解决这一类数学优化问题。
数学优化模型具有五个组成部分:
现在,我们针对此营销活动优化问题提出一种MIP方法。
Cohen[1]提出的MIP解决方案方法是对传统的短视方法(myopic approach)的改进,即选择对特定产品具有最大预期价值的客户,因为它从银行的角度产生了一个全局最优的解决方案,并允许有效地在构建有关客户和业务部门的业务约束。这种方法考虑到有限的资源和其他业务限制。
我们假设对客户/要约的预期增量利润,成本和业务约束的估计可以用作营销活动优化方法的输入。优化阶段与这些输入的构造无关。
MIP方法涉及一个策略和操作问题。
对于策略问题,我们根据单个预期利润参数汇总客户。可以使用诸如预测响应模型之类的数据科学技术来确定估计的个人预期利润。关键思想是对估计的单个预期利润进行聚类,然后将聚类质心视为代表单个聚类中所有单个客户的数据的代表。这种聚合使问题可以表述为线性规划问题,因此该模型无需为单个客户分配报价,而是在考虑业务约束的同时,为每个产品报价在每个集群中确定要最大化营销活动投资回报率的比例。通常,集群中的客户数量将成千上万,这是策略问题的主要决策变量,因此,这些变量可以视为连续变量;因此,线性规划方法是合理的。
操作问题可以表述为MIP模型,其中估计的个人预期利润和战术模型的输出可以用作输入,以将产品报价 分配给每个集群的单个客户,从而使整个营销活动投资回报最大化。
$k \in K$: 类的索引和集合。
$j \in J$: 产品的索引和集合。
$\pi_{k,j}$: 将产品 $j \in J$ 提供给类 $k \in K$ 的客户时所获得的平均期望利润。
$\nu_{k,j}$: 将产品 $j \in J$ 提供给类 $k \in K$ 的客户时平均可变成本。
$N_{k}$: 类 $k \in K$ 中包含的客户数量。
$Q_{j}$: 产品 $j \in J$ 的最低要约数量。
$R$: 企业最低回报率。此最低回报率用于计算营销活动的投资回报率。
$B$: 营销活动预算。
$M$: Big M 惩罚。此惩罚与预算修正相关,预算修正是满足其他业务限制所必需的。
$y_{k,j} \geq 0$: 类 $k \in K$ 中的客户接受产品 $j \in J$ 要约的客户数量。
$z \geq 0$: 增加预算,以便有一个可行的营销活动。
一旦找到了策略模型中所有 $j \in J$ 和 $k \in K$ 的最优值 $y_{k,j}$ ,我们就需要确定集群 $k$ 中的哪个个体客户得到哪个产品的要约。假设给定集群 $k \in K$ 中,产品 $j_1$ 和 $j_2$ 的要约分配为正,即 $y_{k,j_1} > 0$ 和$y_{k,j_2} > 0$。那么,集群 $k$ 中的顾客 $y_{k,j_1}$ 和 $y_{k,j_2}$ 必须分别是产品 $j_1 $和 $j_2$ 的要约。要做到这一点,最优的方法是使用单个客户的预期利润来解决分配问题,而不是使用集群的预期利润。
我们现在提供操作问题的表述。
$i \in I^{k}$: 集群 $k \in K$ 中客户的索引和集合。
$j \in J^{k}$: 集群 $k \in K$ 中提供给客户的产品索引和子集,其中 $J^{k} = \{ j \in J: y_{k,j} > 0 \}$ .
$r_{k,i,j}$: 从客户 $i \in I^{k}$ 的产品要约 $j \in J^{k}$ 的预期个人利润。
$Y_{k,j} = \lfloor y_{k,j} \rfloor $: 在集群 $k$ 中产品 $j \in J^{k}$ 提供要约的客户数量 .
$x_{k,i,j} \in \{0,1 \}$: 如果产品 $j \in J^{k}$ 给客户 $i \in I^{k}$ 提供了要约,此变量等于1, 否则为 0。
我们考虑两个产品、十个客户和两个客户群。公司的最低回报率预期是百分之二十。
下表定义了提供产品时每个集群中平均客户的预期利润。
| Product 1 | Product 2 | |
|---|---|---|
| cluster 1 | $\$2,000$ | $\$1,000$ |
| cluster 2 | $\$3,000$ | $\$2,000$ |
下表确定了向集群中的普通客户提供产品的预期成本。
| Product 1 | Product 2 | |
|---|---|---|
| cluster 1 | $\$200$ | $\$100$ |
| cluster 2 | $\$300$ | $\$200$ |
这个营销活动的预算是$\$200$。
下表给出了每个集群中的客户数量。
| Num. Customers | |
|---|---|
| cluster 1 | 5 |
| cluster 2 | 5 |
下表列出每种产品的最低要约数量。
| Min Offers | |
|---|---|
| product 1 | 2 |
| product 2 | 2 |
下表显示了向每个集群中每个客户提供产品的预期利润。
| Product 1 | Product 2 | |
|---|---|---|
| cluster 1, customer 1 | $\$2,050$ | $\$1,050$ |
| cluster 1, customer 2 | $\$1,950$ | $\$950$ |
| cluster 1, customer 3 | $\$2,000$ | $\$1,000$ |
| cluster 1, customer 4 | $\$2,100$ | $\$1,100$ |
| cluster 1, customer 5 | $\$1,900$ | $\$900$ |
| cluster 2, customer 6 | $\$3,000$ | $\$2,000$ |
| cluster 2, customer 7 | $\$2,900$ | $\$1,900$ |
| cluster 2, customer 8 | $\$3,050$ | $\$2,050$ |
| cluster 2, customer 9 | $\$3,100$ | $\$2,100$ |
| cluster 2, customer 10 | $\$2,950$ | $\$1,950$ |
下表显示了向集群中的客户提供产品的成本。
| Product 1 | Product 2 | |
|---|---|---|
| cluster 1, customer 1 | $\$205$ | $\$105$ |
| cluster 1, customer 2 | $\$195$ | $\$95$ |
| cluster 1, customer 3 | $\$200$ | $\$100$ |
| cluster 1, customer 4 | $\$210$ | $\$110$ |
| cluster 1, customer 5 | $\$190$ | $\$90$ |
| cluster 2, customer 6 | $\$300$ | $\$200$ |
| cluster 2, customer 7 | $\$290$ | $\$190$ |
| cluster 2, customer 8 | $\$305$ | $\$205$ |
| cluster 2, customer 9 | $\$310$ | $\$210$ |
| cluster 2, customer 10 | $\$295$ | $\$195$ |
首先导入 Gurobi 的 Python 模块和相关 Python 库。然后用给定的数据初始化数据结构。
import gurobipy as gp
from gurobipy import GRB
# 官方测试版本 Gurobi v9.0.0 & Python 3.7.0
# 译者测试版本 Gurobi v9.1.0 & Python 3.8.6
### 集合
products = ['p1', 'p2']
clusters = ['k1', 'k2']
下表显示了提供产品时每个集群中客户的预期利润。
| Product 1 | Product 2 | |
|---|---|---|
| cluster 1 | $\$2,000$ | $\$1,000$ |
| cluster 2 | $\$3,000$ | $\$2,000$ |
### 参数
# 预期利润
cp, expected_profit = gp.multidict({
('k1', 'p1'): 2000,
('k1', 'p2'): 1000,
('k2', 'p1'): 3000,
('k2', 'p2'): 2000
})
# 预期成本
cp, expected_cost = gp.multidict({
('k1', 'p1'): 200,
('k1', 'p2'): 100,
('k2', 'p1'): 300,
('k2', 'p2'): 200
})
# 每个集群中的客户数量
clusters, number_customers = gp.multidict({
('k1'): 5,
('k2'): 5
})
# 每个产品的最低要约数量
products, min_offers = gp.multidict({
('p1'): 2,
('p2'): 2
})
# 标量
R = 0.20
# 预算紧张
budget = 200
# 初始化决策变量
mt = gp.Model('Tactical')
### 决策变量
# 分配要约给集群中的客户。
y = mt.addVars(cp, name="allocate")
# 预算纠正
z = mt.addVar(name="budget_correction")
Using license file /Users/arvinxx/gurobi.lic Academic license - for non-commercial use only - expires 2021-02-01
其中:
$y_{k,j} \geq 0$: 集群 $k \in K$ 中被提供产品 $j \in J$ 要约的客户数量。
$N_{k}$: 集群 $k \in K$ 中总共客户数量。
### 约束
# 约束:集群中的要约数量
maxOffers_cons = mt.addConstrs((y.sum(k,'*') <= number_customers[k] for k in clusters), name='maxOffers')
其中:
$y_{k,j} \geq 0$: 集群 $k \in K$ 中被提供产品 $j \in J$ 要约的客户数量。
$z \geq 0$: 为了使营销活动可行的额外增加的预算。
$\nu_{k,j}$: 与向集群 $k \in K$ 的客户提供产品 $j \in J$ 的平均成本。
$B$: 营销活动总预算。
# 预算约束
budget_con = mt.addConstr((y.prod(expected_cost) - z <= budget), name='budget')
其中:
$y_{k,j} \geq 0$: 集群 $k \in K$ 中被提供产品 $j \in J$ 要约的客户数量。
$Q_{j}$: 产品 $j \in J$ 的最小要约数量。
# 约束:最小要约数量
minOffers_cons = mt.addConstrs( (y.sum('*',j) >= min_offers[j] for j in products), name='min_offers')
其中:
$y_{k,j} \geq 0$: 集群 $k \in K$ 中被提供产品 $j \in J$ 要约的客户数量。
$\pi_{k,j}$: 与向集群 $k \in K$ 的客户提供产品 $j \in J$ 的预期利润。
$\nu_{k,j}$: 与向集群 $k \in K$ 的客户提供产品 $j \in J$ 的平均成本。
$R$: 企业要求的最低 ROI。
# 约束:保证 ROI
ROI_con = mt.addConstr((y.prod(expected_profit) - (1 + R)*y.prod(expected_cost) >= 0), name='ROI')
其中:
$y_{k,j} \geq 0$: 集群 $k \in K$ 中被提供产品 $j \in J$ 要约的客户数量。
$z \geq 0$: 为了使营销活动可行,额外增加的预算。
$\pi_{k,j}$: 与向集群 $k \in K$ 的客户提供产品 $j \in J$ 的预期利润。
注意: $M$ 的值应该高于任何预期利润,以确保仅在不增加此参数且模型不可行时才增加预算。
### 目标哈数
# 最大化总利润
M = 10000
mt.setObjective(y.prod(expected_profit) -M*z, GRB.MAXIMIZE)
# 验证模型公式
mt.write('tactical.lp')
# 执行优化引擎
mt.optimize()
Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (mac64)
Thread count: 6 physical cores, 12 logical processors, using up to 12 threads
Optimize a model with 6 rows, 5 columns and 17 nonzeros
Model fingerprint: 0x1eac2f22
Coefficient statistics:
Matrix range [1e+00, 3e+03]
Objective range [1e+03, 1e+04]
Bounds range [0e+00, 0e+00]
RHS range [2e+00, 2e+02]
Presolve removed 1 rows and 0 columns
Presolve time: 0.00s
Presolved: 5 rows, 5 columns, 13 nonzeros
Iteration Objective Primal Inf. Dual Inf. Time
0 2.5000000e+04 8.787500e+01 0.000000e+00 0s
4 -3.9940000e+06 0.000000e+00 0.000000e+00 0s
Solved in 4 iterations and 0.01 seconds
Optimal objective -3.994000000e+06
### 输出报告
# 为集群客户提供产品要约的最优分配方案
total_expected_profit = 0
total_expected_cost = 0
print("\n📃 最优分配方案")
print("--------------------------------------------")
for k,p in cp:
if y[k,p].x > 1e-6:
#print(y[k,p].varName, y[k,p].x)
print(f"集群 {k} 中被提供产品 {p} 要约的客户数量为:{y[k,p].x}")
total_expected_profit += expected_profit[k,p]*y[k,p].x
total_expected_cost += expected_cost[k,p]*y[k,p].x
increased_budget = '${:,.2f}'.format(z.x)
print(f"\n营销预算的增加预算为: {increased_budget}.")
# 财务报告
optimal_ROI = round(100*total_expected_profit/total_expected_cost,2)
min_ROI = round(100*(1+R),2)
money_expected_profit = '${:,.2f}'.format(total_expected_profit)
money_expected_cost = '${:,.2f}'.format(total_expected_cost)
money_budget = '${:,.2f}'.format(budget)
print(f"\n📃 财务报告")
print("--------------------------------------------")
print(f"最优总预期利润为: {money_expected_profit}.")
print(f"最优总预期成本为: {money_expected_cost} ,其中目标预算为: {money_budget},额外开支为:{increased_budget}.")
print(f"最优 ROI 为: {optimal_ROI}% ,最小为 {min_ROI}%.")
📃 最优分配方案 -------------------------------------------- 集群 k1 中被提供产品 p1 要约的客户数量为:2.0 集群 k1 中被提供产品 p2 要约的客户数量为:2.0 营销预算的增加预算为: $400.00. 📃 财务报告 -------------------------------------------- 最优总预期利润为: $6,000.00. 最优总预期成本为: $600.00 ,其中目标预算为: $200.00,额外开支为:$400.00. 最优 ROI 为: 1000.0% ,最小为 120.0%.
将产品分配到集群的成本需要增加预算 $\$400$ 。总预期利润是 $\$6,000$ 。总成本为 $\$600$ ,初步预算为 $\$200$ ,额外增加的预算为 $\$400$。预期投资回报率为 1000%,远高于最低 ROI 要求。
### 集合
customers = ['c1', 'c2','c3','c4','c5','c6','c7','c8','c9','c10']
### 参数
# 为集群中的不同客户提供不同产品的预期利润表
ccp, customer_profit = gp.multidict({
('k1', 'c1', 'p1'): 2050,
('k1', 'c1', 'p2'): 1050,
('k1', 'c2', 'p1'): 1950,
('k1', 'c2', 'p2'): 950,
('k1', 'c3', 'p1'): 2000,
('k1', 'c3', 'p2'): 1000,
('k1', 'c4', 'p1'): 2100,
('k1', 'c4', 'p2'): 1100,
('k1', 'c5', 'p1'): 1900,
('k1', 'c5', 'p2'): 900,
('k2', 'c6', 'p1'): 3000,
('k2', 'c6', 'p2'): 2000,
('k2', 'c7', 'p1'): 2900,
('k2', 'c7', 'p2'): 1900,
('k2', 'c8', 'p1'): 3050,
('k2', 'c8','p2'): 2050,
('k2', 'c9', 'p1'): 3100,
('k2', 'c9', 'p2'): 3100,
('k2', 'c10', 'p1'): 2950,
('k2', 'c10', 'p2'): 2950
})
# 为集群中的不同客户提供不同产品的预期成本表
ccp, customer_cost = gp.multidict({
('k1', 'c1', 'p1'): 205,
('k1', 'c1', 'p2'): 105,
('k1', 'c2', 'p1'): 195,
('k1', 'c2', 'p2'): 95,
('k1', 'c3', 'p1'): 200,
('k1', 'c3', 'p2'): 100,
('k1', 'c4', 'p1'): 210,
('k1', 'c4', 'p2'): 110,
('k1', 'c5', 'p1'): 190,
('k1', 'c5', 'p2'): 90,
('k2', 'c6', 'p1'): 300,
('k2', 'c6', 'p2'): 200,
('k2', 'c7', 'p1'): 290,
('k2', 'c7', 'p2'): 190,
('k2', 'c8', 'p1'): 305,
('k2', 'c8','p2'): 205,
('k2', 'c9', 'p1'): 310,
('k2', 'c9', 'p2'): 310,
('k2', 'c10', 'p1'): 295,
('k2', 'c10', 'p2'): 295
})
$x_{k,i,j} \in \{0,1 \}$: 如果产品 $j \in J^{k}$ 给客户 $i \in I^{k}$ 提供了要约,此变量等于1, 否则为 0。
# 定义并初始化模型
mo = gp.Model('Operational')
### 定义变量
x = mo.addVars(ccp, vtype=GRB.BINARY, name="assign")
其中:
$x_{k,i,j} \in \{0,1 \}$: 如果产品 $j \in J^{k}$ 给客户 $i \in I^{k}$ 提供了要约,此变量等于1, 否则为 0。
$Y_{k,j} = \lfloor y_{k,j} \rfloor $: 在集群 $k$ 中产品 $j \in J^{k}$ 提供要约的客户数量。
# 约束:产品要约
productOffers = {}
for k in clusters:
for j in products:
productOffers[k,j] = mo.addConstr(gp.quicksum(x[k,i,j] for kk,i,jj in ccp if (kk ==k and jj == j)) ==
int(y[k,j].x), name='prodOffers_' + str(k) + ',' + str(j) )
其中:
$x_{k,i,j} \in \{0,1 \}$: 如果产品 $j \in J^{k}$ 给客户 $i \in I^{k}$ 提供了要约,此变量等于1, 否则为 0。
# 对集群中每个客户的要约数量进行限制。
ki = [('k1', 'c1'),
('k1', 'c2'),
('k1', 'c3'),
('k1', 'c4'),
('k1', 'c5'),
('k2', 'c6'),
('k2', 'c7'),
('k2', 'c8'),
('k2', 'c9'),
('k2', 'c10')]
customerOffers = {}
for k,i in ki:
customerOffers[k,i] = mo.addConstr(gp.quicksum(x[k,i,j] for kk,ii,j in ccp if (kk == k and ii == i) ) <= 1,
name ='custOffers_' + str(k) + ',' + str(i) )
其中:
$x_{k,i,j} \in \{0,1 \}$: 如果产品 $j \in J^{k}$ 给客户 $i \in I^{k}$ 提供了要约,此变量等于1, 否则为 0。
$r_{k,i,j}$: 从客户 $i \in I^{k}$ 的产品要约 $j \in J^{k}$ 的预期客户利润。
### 目标函数
# 最大化总利润
mo.setObjective(x.prod(customer_profit), GRB.MAXIMIZE)
# 验证模型
mo.write('operational.lp')
# 运行优化
mo.optimize()
Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (mac64)
Thread count: 6 physical cores, 12 logical processors, using up to 12 threads
Optimize a model with 14 rows, 20 columns and 40 nonzeros
Model fingerprint: 0xe1e9d99f
Variable types: 0 continuous, 20 integer (20 binary)
Coefficient statistics:
Matrix range [1e+00, 1e+00]
Objective range [9e+02, 3e+03]
Bounds range [1e+00, 1e+00]
RHS range [1e+00, 2e+00]
Found heuristic solution: objective 6050.0000000
Presolve removed 7 rows and 10 columns
Presolve time: 0.00s
Presolved: 7 rows, 10 columns, 20 nonzeros
Variable types: 0 continuous, 10 integer (10 binary)
Root relaxation: objective 6.100000e+03, 2 iterations, 0.00 seconds
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
* 0 0 0 6100.0000000 6100.00000 0.00% - 0s
Explored 0 nodes (2 simplex iterations) in 0.01 seconds
Thread count was 12 (of 12 available processors)
Solution count 2: 6100 6050
Optimal solution found (tolerance 1.00e-04)
Best objective 6.100000000000e+03, best bound 6.100000000000e+03, gap 0.0000%
### 输出报告
# 向客户提供产品要约的最优分配方案
total_customer_profit = 0
total_customer_cost = 0
kvalue = None
first = True
num_assignments = 0
print("\n📃 最优分配方案")
print("--------------------------------------------")
for k,i,j in ccp:
if k != kvalue:
prevk = kvalue
kvalue = k
if not first:
print("--------------------------------------------")
print(f"集群 {prevk} 中分配的要约数量为 {num_assignments}")
print("--------------------------------------------")
num_assignments = 0
if first:
first = False
if x[k,i,j].x > 0.5:
#print(x[k,i,j].varName, x[k,i,j].x)
profit = '${:,.2f}'.format(customer_profit[k,i,j])
cost = '${:,.2f}'.format(customer_cost[k,i,j])
print(f"集群 {k} 的客户 {i} 获得产品 {j}的要约:利润为 {profit},成本为 {cost}")
total_customer_profit += customer_profit[k,i,j]*x[k,i,j].x
total_customer_cost += customer_cost[k,i,j]*x[k,i,j].x
num_assignments += 1
print("--------------------------------------------")
print(f"集群 {kvalue} 中分配的要约数量为 {num_assignments}")
print("--------------------------------------------\n")
# 财务报告
customers_ROI = round(100*total_customer_profit/total_customer_cost,2)
money_customers_profit = '${:,.2f}'.format(total_customer_profit)
money_customers_cost = '${:,.2f}'.format(total_customer_cost)
print(f"\n📃 财务报告")
print("--------------------------------------------")
print(f"最优总客户预期利润为:{money_customers_profit}.")
print(f"最优总成本为: {money_customers_cost} ,其中预期预算为 {money_budget},额外预算为: {increased_budget}.")
print(f"最优 ROI 为 {customers_ROI}%,最小值为: {min_ROI}%.")
📃 最优分配方案 -------------------------------------------- 集群 k1 的客户 c1 获得产品 p2的要约:利润为 $1,050.00,成本为 $105.00 集群 k1 的客户 c2 获得产品 p2的要约:利润为 $950.00,成本为 $95.00 集群 k1 的客户 c3 获得产品 p1的要约:利润为 $2,000.00,成本为 $200.00 集群 k1 的客户 c4 获得产品 p1的要约:利润为 $2,100.00,成本为 $210.00 -------------------------------------------- 集群 k1 中分配的要约数量为 4 -------------------------------------------- -------------------------------------------- 集群 k2 中分配的要约数量为 0 -------------------------------------------- 📃 财务报告 -------------------------------------------- 最优总客户预期利润为:$6,100.00. 最优总成本为: $610.00 ,其中预期预算为 $200.00,额外预算为: $400.00. 最优 ROI 为 1000.0%,最小值为: 120.0%.
每个顾客最多只能得到一个产品要约。 产品 p2 的要约提供给了客户 c1 和 c2, 产品 p1 的要约提供给了客户 c3 和 c4。产品 p1 和 p2 都提供了两份要约——这是来自策略模型的约束。然而需要注意的是,为了确保这些严格的业务限制,预算需要增加 $\$400$。
提供产品要约的总成本是 $\$610$,,这稍微违反了可用的总预算 of $\$600$. 总客户预期利润为 $\$6,100$。ROI 为 1,000 %,远高于最低 ROI 要求。
如果需要约束总可用预算,可以向该营销模型添加以下约束:
新的预算是原预算加上修正后的预算,即 $B' = B + z$
约束总可用预算,在这个场景下,操作模型为:
### 约束总预算的营销模型
# 声明模型
mob = gp.Model('OperationalB')
### 决策变量
xb = mob.addVars(ccp, vtype=GRB.BINARY, name="assign")
# 约束:产品要约
productOffersb = {}
for k in clusters:
for j in products:
productOffersb[k,j] = mob.addConstr(gp.quicksum(xb[k,i,j] for kk,i,jj in ccp if (kk ==k and jj == j)) ==
int(y[k,j].x), name='prodOffersb_' + str(k) + ',' + str(j) )
# 约束:对集群中每个客户的要约数量进行限制。
customerOffersb = {}
for k,i in ki:
customerOffersb[k,i] = mob.addConstr(gp.quicksum(xb[k,i,j] for kk,ii,j in ccp if (kk == k and ii == i) ) <= 1,
name ='custOffersb_' + str(k) + ',' + str(i) )
# 约束:预算
# 新预算
new_budget = budget + z.x
totBudget = mob.addConstr(xb.prod(customer_cost) <= new_budget, name='total_budget')
### 目标函数
# 最大化总利润
mob.setObjective(xb.prod(customer_profit), GRB.MAXIMIZE)
# 验证模型
mob.write('operationalB.lp')
# 运行优化
mob.optimize()
Gurobi Optimizer version 9.1.0 build v9.1.0rc0 (mac64)
Thread count: 6 physical cores, 12 logical processors, using up to 12 threads
Optimize a model with 15 rows, 20 columns and 60 nonzeros
Model fingerprint: 0x96da858f
Variable types: 0 continuous, 20 integer (20 binary)
Coefficient statistics:
Matrix range [1e+00, 3e+02]
Objective range [9e+02, 3e+03]
Bounds range [1e+00, 1e+00]
RHS range [1e+00, 6e+02]
Found heuristic solution: objective 5950.0000000
Presolve removed 7 rows and 10 columns
Presolve time: 0.00s
Presolved: 8 rows, 10 columns, 28 nonzeros
Variable types: 0 continuous, 10 integer (10 binary)
Root relaxation: objective 6.000000e+03, 2 iterations, 0.00 seconds
Nodes | Current Node | Objective Bounds | Work
Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time
* 0 0 0 6000.0000000 6000.00000 0.00% - 0s
Explored 0 nodes (2 simplex iterations) in 0.01 seconds
Thread count was 12 (of 12 available processors)
Solution count 2: 6000 5950
Optimal solution found (tolerance 1.00e-04)
Best objective 6.000000000000e+03, best bound 6.000000000000e+03, gap 0.0000%
### 输出报告
# 向客户提供产品要约的最优分配方案
total_customer_profitb = 0
total_customer_costb = 0
kvalueb = None
firstb = True
num_assignmentsb = 0
print("\n📃 最优分配方案")
print("--------------------------------------------")
for k,i,j in ccp:
if k != kvalueb:
prevkb = kvalueb
kvalueb = k
if not firstb:
print("--------------------------------------------")
print(f"集群 {prevkb} 中分配的要约数量为 {num_assignmentsb}")
print("--------------------------------------------")
num_assignmentsb = 0
if firstb:
firstb = False
if xb[k,i,j].x > 0.5:
#print(x[k,i,j].varName, x[k,i,j].x)
profitb = '${:,.2f}'.format(customer_profit[k,i,j])
costb = '${:,.2f}'.format(customer_cost[k,i,j])
print(f"集群 {k} 的客户 {i} 获得产品 {j}的要约:利润为 {profitb},成本为 {costb}")
total_customer_profitb += customer_profit[k,i,j]*xb[k,i,j].x
total_customer_costb += customer_cost[k,i,j]*xb[k,i,j].x
num_assignmentsb += 1
print("--------------------------------------------")
print(f"集群 {kvalueb} 中分配的要约数量为 {num_assignmentsb}")
print("--------------------------------------------\n")
# Financial reports
customers_ROIb = round(100*total_customer_profitb/total_customer_costb,2)
money_customers_profitb = '${:,.2f}'.format(total_customer_profitb)
money_customers_costb = '${:,.2f}'.format(total_customer_costb)
print(f"\n📃 财务报告")
print("--------------------------------------------")
print(f"最优客户利润为: {money_customers_profitb}.")
print(f"最优客户总成本为 {money_customers_costb} ,其中原预算为: {money_budget} ,超额预算为: {increased_budget}.")
print(f"最佳 ROI 为 {customers_ROIb}% , 最小 ROI 为 {min_ROI}%.")
📃 最优分配方案 -------------------------------------------- 集群 k1 的客户 c1 获得产品 p2的要约:利润为 $1,050.00,成本为 $105.00 集群 k1 的客户 c2 获得产品 p1的要约:利润为 $1,950.00,成本为 $195.00 集群 k1 的客户 c4 获得产品 p1的要约:利润为 $2,100.00,成本为 $210.00 集群 k1 的客户 c5 获得产品 p2的要约:利润为 $900.00,成本为 $90.00 -------------------------------------------- 集群 k1 中分配的要约数量为 4 -------------------------------------------- -------------------------------------------- 集群 k2 中分配的要约数量为 0 -------------------------------------------- 📃 财务报告 -------------------------------------------- 最优客户利润为: $6,000.00. 最优客户总成本为 $600.00 ,其中原预算为: $200.00 ,超额预算为: $400.00. 最佳 ROI 为 1000.0% , 最小 ROI 为 120.0%.
每个客户最多只能被提供一个产品要约。产品p1和p2提供给至少两个客户。将产品分配给客户的成本是 $\$600$,这等于最初sh可用的总预算。ROI为1,000%,远远高于所需的最低ROI。
在这个场景中,我们限定总可用预算为定值,从而得到一个不同的执行方案。产品 p1 提供给客户 c2 和 c4,产品 p2 提供给 c1 和 c5。
在这个 Jupyter Notebook 中,我们讨论了营销活动对银行业的重要性。我们讨论了机器学习预测响应模型可用于提供营销活动优化问题的输入数据。我们展示了如何将营销活动优化问题分解为策略问题和操作问题。
这个策略问题被表述为一个线性规划问题,我们将机器学习预测响应模型产生的数据进行聚合。策略问题的解决方案确定了向集群中的客户提供哪些产品,同时最大化了营销活动的预期利润,并考虑了以下约束:
该操作问题被表述为MIP模型,其中预期的客户利润和策略模型的输出可以作为输入,将产品提供分配给单个客户,从而使客户总利润最大化。我们考虑了两个操作问题的案例。在第一种情况下,不强制限定由策略问题决定的总可用预算。这意味着这个问题的最佳解决方案可能会稍微超出总可用预算。在第二种情况下,我们限定总可用预算。
[1] M. D. Cohen. Exploiting response models—optimizing cross-sell and up-sell opportunities in banking. Information Systems. Vol. 29. issue 4, June 2004, Pages 327-341
Copyright © 2020 Gurobi Optimization, LLC
翻译 By Arvin Xu