A03 · 合约库存预测与在线分配
先修:公用31–34;A02。
证据类型:明确构造机制实验(非真实广告日志)。本章不声称工业系统复现或线上收益。
学习顺序:先预测 → 只改一个变量 → 运行 → 解释结果。
问题与输入输出契约
合约j有需求d_j,库存t对j有资格边。离线分配满足每个库存至多一次、各合约交付不超过需求;这里最大化10×已履约数量。库存预测不是保证,在线决策无法看见未来。构造flex可给A或B,onlyA只能给A;先把flex给A可能挤掉B的唯一机会。穷举给出小规模离线基准,不是生产求解器。
运行前预测:本章只改变的变量会使哪一项结果变化?哪些不变量必须保持?
from pathlib import Path
import sys
root=Path.cwd()
if not (root/'reclab').exists(): root=root.parent
if str(root) not in sys.path: sys.path.insert(0,str(root))
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from reclab import ads_curriculum as ac运行单变量对照
输入是源码中可见的小型构造数据,输出单位和约束按上文定义。运行断言核验数学与账本。
past_flex=np.array([2,2,2]);forecast=int(np.round(past_flex.mean()))
print('历史均值预测flex',forecast,'本期实际flex',1,'误差',1-forecast)
baseline=ac.contract_allocation(demand=(1,1))
shift=ac.contract_allocation(supply=('onlyA','flex','onlyA'),demand=(1,1))
print('flex先到',baseline);print('次序变化',shift)
assert baseline['offline'][0]==20 and baseline['greedy'][0]==10
short=ac.contract_allocation();print('预测不足与欠投',short)
assert sum(short['offline_shortfall'])==1Notebook 保存的输出 · 本页未执行
历史均值预测flex 2 本期实际flex 1 误差 -1
flex先到 {'offline': (20, (1, 0, -1)), 'greedy': (10, (0, -1, -1)), 'offline_shortfall': [0, 0], 'inventory_counts': {'flex': 1, 'onlyA': 2}}
次序变化 {'offline': (20, (0, 1, -1)), 'greedy': (20, (0, 1, -1)), 'offline_shortfall': [0, 0], 'inventory_counts': {'flex': 1, 'onlyA': 2}}
预测不足与欠投 {'offline': (20, (1, 0, -1)), 'greedy': (10, (0, -1, -1)), 'offline_shortfall': [0, 1], 'inventory_counts': {'flex': 1, 'onlyA': 2}}
解释与局限
库存集合不变,仅请求顺序变化,贪心履约从10变20;离线最优保持20。对稀缺flex保留机会有价值。默认B需求2而flex仅1,任何算法都无法全部交付,不能把预测误差说成分配算法可以消除。
查看源码,再做练习
增加丢弃惩罚、竞价机会收入与合约欠投罚金。枚举不同flex数量,画出最优履约与欠投,解释离线最优为何只是在线上界。
import inspect
print(inspect.getsource(ac.contract_allocation))Notebook 保存的输出 · 本页未执行
def contract_allocation(supply=('flex','onlyA','onlyA'), demand=(1,2)):
"""A accepts both types, B only flex. Reward 10 fulfilled, 0 discarded."""
eligible=lambda t,j: j==0 or (j==1 and t=='flex')
def score(assign):
count=[assign.count(j) for j in (0,1)]
return 10*sum(min(n,d) for n,d in zip(count,demand))
feasible=[]
for assign in product((-1,0,1),repeat=len(supply)):
if all(j==-1 or eligible(t,j) for t,j in zip(supply,assign)):
counts=[assign.count(j) for j in (0,1)]
if all(n<=d for n,d in zip(counts,demand)):
feasible.append((score(assign),assign))
best=max(feasible)
counts=[0,0]; greedy=[]
for t in supply:
options=[j for j in (0,1) if eligible(t,j) and counts[j]<demand[j]]
j=options[0] if options else -1
greedy.append(j)
if j>=0: counts[j]+=1
return {'offline':best,'greedy':(score(tuple(greedy)),tuple(greedy)),
'offline_shortfall':[max(0,d-best[1].count(j)) for j,d in enumerate(demand)],
'inventory_counts':{'flex':supply.count('flex'),'onlyA':supply.count('onlyA')}}
来源与下一步
A-CCR 37页及匹配主题;库存预测与在线分配是Lab展开。这是Lab自行设计的小实验,不复制讲义全文。
GitHub中文单章 · 作者RTB讲义。中文PPT不是现代广告工程全套课;不将历史平台机制描述当作当前市场事实。
完成练习后回到广告学习路径,继续下一语义单元。