一、问题背景与核心前提
核心目标:在总预算≤200万元的约束下,为100万名用户分配0元(不发)、5元、10元优惠券,最大化整体下单增量(Uplift)。
核心前提:通过Uplift Model已预测每个用户在不同券值下的下单概率,进而计算出每种券值对应的增量收益(Uplift)——即“发券比不发券多带来的下单概率提升”。示例数据如下(简化版):
| 用户 ID | P(不发) | P(5元券) | P(10元券) | 5元增量 (Uplift) | 10元增量 (Uplift) |
|---|---|---|---|---|---|
| 用户 A | 0.1 | 0.12 | 0.13 | 0.02 | 0.03 |
| 用户 B | 0.4 | 0.70 | 0.85 | 0.30 | 0.45 |
| 用户 C | 0.8 | 0.83 | 0.87 | 0.03 | 0.07 |
| 用户 D | 0.9 | 0.91 | 0.92 | 0.01 | 0.02 |
注:0元券即不发券,增量为0,成本也为0。
二、基础解法的局限:暴力穷举与简单贪心为何失效?
面对优惠券分配问题,最直观的两种思路是“暴力穷举”和“简单贪心”,但在实际业务场景(百万级用户、有限预算)中均会失效,核心问题如下。
2.1 暴力穷举:指数级爆炸+约束失效
暴力穷举的逻辑的是:每个用户有3种选择(0元/5元/10元),遍历所有组合,筛选出“总预算≤200万”且“总增量最大”的组合。举个具体例子,结合前文4个简化用户(A、B、C、D),其所有组合共3⁴=81种,部分组合及对应效果如下:
组合求解非常的直观,每个用户有3种面额(0,5,10)可以选择,那么总过的组合数就有3^4种选择,如下所示
| 组合 | 用户A | 用户B | 用户C | 用户D | 增量收益 |
|---|---|---|---|---|---|
| 1 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 5 | |
| 1 | 0 | 0 | 0 | 10 | |
| 1 | 0 | 0 | 5 | 0 | |
| 1 | 0 | 0 | 5 | 5 | |
| 1 | 0 | 0 | 5 | 10 | |
| ... |
这种“暴力穷举”的方法会遇到两个致命的问题
1. 指数级爆炸 (Complexity)
如果有1000万个用户,那么总的组合是3^10000000种组合,电脑直接计算所有组合是不可能的
2. 约束条件的硬性限制
假设我们的预算是30元,暴力穷举遍历所有81种组合时,会优先选择“总净收益最高”的组合,而非“预算内增量最大”的组合。比如
- 组合“0元/5元/0元/0元”,总预算5元,总净收益=0.30-5=-4.7;
- 组合“0元/10元/0元/0元”,总预算10元,总净收益=0.45-10=-9.55;
- 组合“5元/5元/0元/0元”,总预算10元,总净收益=0.02+0.30-10=-9.68。
对比来看,组合“0元/5元/0元/0元”的净收益(-4.7)是三者中最高的,算法会优先选择这个组合,此时仅花费5元预算,远低于30元的给定预算。
核心原因的是:当券值成本过高、导致“增量收益无法覆盖成本”时,暴力穷举的净收益计算逻辑会让算法主动放弃发放更多优惠券,哪怕有剩余预算,也不会为了“花完预算”而选择净收益更低的组合。
而在实际业务中,老板可能为了抢占市场份额、提升用户活跃度,要求必须花完指定预算,此时暴力穷举的这种逻辑就会完全失效,无法满足业务约束。
- 上面公式: 。结果可能是:为了利润最大化,算法建议只花 10 万块。但老板的目标是市场份额,要求必须花完 100 万。
- 实际解法: 这是一个带约束的优化问题。我们需要在的前提下,最大化 。
贪心求解
贪心求解也非常的直观简单,我们知道了用户在不同金额下的增量收益,直接按照每个用户增量收益的最大值倒排就可以。然后依次选取用户,直到满足预算要求
| 用户 ID | 5元增量 (Uplift) | 10元增量 (Uplift) | 取值 |
|---|---|---|---|
| 用户 A | 0.02A-5 | 0.03A-10 | max(0.02A-5,0.03A-10) |
| 用户 B | 0.30B-5 | 0.45B-10 | max(0.30B-5,0.45B-10) |
| 用户 C | 0.03C-5 | 0.07C-10 | max(0.03C-5,0.07C-10) |
| 用户 D | 0.01D-5 | 0.02D-10 | max(0.01D-5,0.02D-10) |
这种“按每个用户最大收益倒排”的贪心策略非常直观,但它在资源受限(有总预算)的情况下,会掉入一个经典的“局部最优陷阱”。
这种做法存在两个核心逻辑漏洞,会导致你最终的总增量收益远低于分数阶背包(边际性价比排序)方案:
1. 忽略了“成本占位” (The Efficiency Problem)
贪心算法如果只看“绝对增量”,会优先把钱分给那些“胃口大”的用户,导致预算被迅速耗尽。
举个例子: 假设你剩下 10 元预算:
- 用户 A: 10元券带来 0.1 增量。按你的逻辑,他的
max收益是 0.1,排在前面。 - 用户 B: 5元券带来 0.08 增量。
- 用户 C: 5元券带来 0.08 增量。
简单贪心策略: 把 10 元全部给用户 A。总收益 = 0.1。
背包策略(看性价比): 用户 B 和 C 的性价比(0.08/5=0.016)高于用户 A(0.1/10=0.01)。 结果: 把 10 元拆开给 B 和 C。总收益 = 0.08 + 0.08 = 0.16。
结论: 只看增量最大值,会让你为了抢几个“大客户”,而丢掉了一群“高性价比小客户”。
2. 忽视了“升档的代价” (The Marginal Gain Problem)
在多面额场景下,每个用户内部的选项是互斥的。
假设用户 B:
- 5元增量:0.30
- 10元增量:0.32
按简单贪心策略,用户 B 的 max 是 0.32(10元档)。你会直接花 10 元在他身上。
但实际上,多花的那 5 元钱只带来了 0.02 的极小增量。
如果这 5 元钱省下来发给另一个“5元增量为 0.1”的用户 D,你原本可以用同样的 10 元预算换取 0.3+0.1=0.4的收益,而不是 0.32。
三、工业界最优解:约束规划与拉格朗日乘子法
在工业界,当面对几千万用户、多个面额、多种预算限制时,你之前提到的“穷举组合”或“简单贪心”都会失效。约束规划(Constrained Optimization) 的本质是把你的业务目标和限制条件翻译成数学语言,然后用高效的算法求出那个“最优组合”。
3.1 约束规划:将业务目标转化为数学语言
目标函数: 最大化总增量(如总 GMV 增量或总转化人数)。
约束条件:
- 预算约束: (总预算不能超)。
- 唯一性约束: (每个用户只能拿到一种券或不拿)。
- ROI 约束(可选): 例如,增量 GMV / 投入成本 >= 目标阈值,10元券的总数不能超过5万张等 。
由于用户量太大,直接解这个 0/1 整数规划(IP)是 NP-Hard 问题,算不动。工业界的标准解法使用拉格朗日乘子法。
通过拉格朗日乘子 ,把“预算约束”从限制条件里挪到目标函数里,变成:
这里的就像是一个动态杠杆 或者 影子价格,它代表了:在当前预算下,你每多花 1 块钱,必须至少换回多少增量。
拉格朗日乘子法
当用户规模达到千万甚至亿级时,直接解线性规划(LP)太慢。工业界常用分数阶背包(Fractional Knapsack)的思想:
-
计算性价比 (Efficiency Score): 对于每个用户,计算每一档券的“单位成本增量”:
-
寻找最优阈值: 通过二分查找或随机梯度下降,找到一个全局的 代表每一块钱预算能买到的最小增量)。
-
决策逻辑: 只有当某个券额带来的增益 时,才发放该券。如果有多档券满足,选最大的那一档。
1. 数据准备(离线打分)
首先,通过 Uplift Model 为每个用户预测在不同券值下的下单概率。
| 用户 ID | P(不发) | P(5元券) | P(10元券) | 5元增量 (Uplift) | 10元增量 (Uplift) |
|---|---|---|---|---|---|
| 用户 A | 0.1 | 0.12 | 0.13 | 0.02 | 0.03 |
| 用户 B | 0.4 | 0.70 | 0.85 | 0.30 | 0.45 |
| 用户 C | 0.8 | 0.81 | 0.82 | 0.01 | 0.02 |
2. 计算“性价比系数” (ROI/Efficiency)
计算每一块钱能换回多少“增量下单概率”:
- 用户 A:
- 5元券性价比:0.02/5=0.004
- 10元券性价比:0.03/10=0.003
- 用户 B:
- 5元券性价比:0.3/5=0.06
- 10元券性价比:0.45/10=0.045
- 用户 C:
- 性价比极低(近乎 0),属于“无论如何都会买”的自然转化。
3. 寻找全局最优阈值 ()
系统会对全量用户的所有券种性价比进行倒序排列。 假设根据 200万 预算,算出的全局性价比阈值
- 最终分配决策逻辑
对于每个用户,我们只选 且 性价比最高 的那一档:
- 用户 A: 5元和10元的性价比(0.004, 0.003)都低于阈值 0.005。
- 决策:不发券(省钱,因为他不敏感)。
- 用户 B: 5元(0.06) 和 10元(0.045) 都高于阈值。
- 虽然 10 元券带来的绝对增量(0.45)更大,但 5元券的性价比(0.06)更高。
- 决策:发 5 元券(为了在预算限制下覆盖更多像 B 这样的人)。
- 用户 C: 性价比太低。
- 决策:不发券(避免浪费)。
四、拉格朗日乘子法的核心证明(通俗理解)
1. 直观证明:边际性价比相等即最优
假设存在一个比拉格朗日法更好的方案,那意味着在这个方案里,一定存在两个用户 A 和 B,他们的边际 ROI 不相等(比如 A 是 5,B 是 3)。
- 既然不相等,我就可以从 B 那里扣掉 1 元钱,补给 A。
- B 损失了 3 元收益,但 A 创造了 5 元收益。
- 总收益增加了 2 元,且总预算没变。
- 这说明原方案不是最优的。只有当所有人的边际收益都等于同一个时,你才无法通过“挪动预算”来增加总收益。这就是全局最优。
2. 数学条件:KKT条件与强对偶性
1. 目标函数的拆解(解耦证明)
- :这是你想“赚”到的东西。
- :这是你为了赚这些钱所**“付出的代价”**,经过一个系数(汇率)折算后的结果。
- :这是一个**“转换杠杆”**。
- 如果,意思是在你眼里,1块钱的成本支出必须能换回3块钱的增量收益,这笔买卖才算保本(净增益为0)。
- 如果某个方案的 ,说明这个方案的 ROI 超过了你的及格线,值得发券。
但这里隐藏了两个关键约束
- 排他性约束(每个用户只能领一张券)
- 是第i个用户
- 是第j种优惠券金额(例如0,3,5)
- 预算约束(总钱数不能超)
- 是你给第 个用户发券的成本。
- 是财务或老板批给你的总经费(比如:这次双11红包活动总共只能发 1000 万元)。
求解最值时(暂时忽略常数B)
- 这个公式是一个大求和。
- 每一个用户 对应的部分是相互独立的。
- 因为每个用户只能选一张券(即每个对应的只有一个能等于 1),所以对于每个用户 来说,他这一项能提供的最大贡献,就是选出那个能让 结果最大的 。
如果每一项加数都是它能达到的最大值,那么它们的总和必然也是最大值。这就是拉格朗日法把“全局大难题”化解为“个人小计算”的魔力。
2. KKT 条件(最优解的必要条件)
对于带约束的优化问题,最优解必须满足 KKT 条件。其中最核心的一条是:梯度对齐。
- 在最优状态下,目标函数的梯度 和约束条件的梯度 必须方向一致,比例就是 。
- 即:
这证明了:最优分配时,所有人的“边际性价比”必须相等。 如果 A 的边际性价比是 5,B 是 3,那你显然应该把给 B 的钱挪给 A,直到两者的边际收益相等为止。
3. 强对偶性(Strong Duality)的保证(没看懂)
- 在优惠券场景中,如果我们将收益函数看作是拟凸的(或者在离散情况下取凸包),该问题就满足 Slater 条件。
- 数学证明,当满足这些条件时,原问题的最优解等于对偶问题的最优解(即强对偶性成立)。
- 这意味着,我们去寻找那个让预算刚好耗尽的 ,这个过程本身就在逼近原问题的全局最优解。
3. 具体操作
第一步:设定一个全场统一的“及格线”
这个 的物理意义是:**“每多花 1 元预算,必须至少多换回元的收益”**。
- 如果 ,说明你要求每一块钱投入至少产出 2 块钱增量。
- 你可以先随手猜一个数字,比如 。
第二步:让每个用户“各扫门前雪”(独立决策)
这是拉格朗日法最天才的地方。不需要全局排序,每个用户 只需要在自己面前的几种面额(0元, 5元, 10元...)中选一个最划算的。
选哪个呢?选能让下面这个“净收益”最大的面额:
- 例子:用户 A 面前有两张券:
- 5元券:产出 20 元,
- 10元券:产出 22 元,
- 结果:在 的要求下,用户 A 选 5元券。
第三步:看总钱数,调“水坝高度” ()
当所有用户都选好后,系统把大家选的券金额加起来,看看总预算:
- 如果钱花多了(超过预算):说明及格线定得太低了,要求太松。调高 (比如调到 5),这会挤掉那些性价比不高的券。
- 如果钱没花完(剩太多):说明及格线定太高了。**调低 **(比如调到 1.5),让更多人能领到券。
拉格朗日法通过 预先协调了所有人。它告诉所有人:“大家的‘机会成本’都是
- 这样,当 A 选择了一个大面额券时,是因为他确实能产生比更高的边际收益;
- 如果 A 产生不了那么高的收益,在的法则下,他会自动缩减到小面额券,把预算“让”给更有潜力的 B。