本文属于 Bidder 竞价服务架构 系列,是 预算 Pacing:PID 控制与消耗平滑 的姊妹篇。
Pacing 那篇讲的是控制问题:给定一条目标消耗曲线
r(t),如何用 PID 闭环把实际消耗跟住它。但它把「r(t)到底怎么画」一笔带过——只说了「流量加权最常用」。本篇专门补上这半张图:目标曲线r(t)背后的时段消耗权重w(τ)该怎么设计。建议先读 Pacing 那篇 的第二节,再回到这里深入。
Pacing 有一句反直觉的经验:很多 pacing 跑不稳,根子不在控制器,而在目标曲线画错了。 PID 再好,也只是在「执行纠偏」——它假设你给的目标曲线本身是合理的。如果 r(t) 让 Bidder 在一个根本没多少流量的时段去追一个很高的目标,控制器只会徒劳地把出价系数 α 拉满、追不上、积分饱和,最后在流量回来时剧烈过冲。
所以「怎么定权重」是 pacing 的上半场。这是一个预测问题:预估「今天每个时段值得花多少预算」,把它累积、归一化成目标曲线。本篇就围绕这个 w(τ) 展开。
TL;DR
- 目标曲线 = 权重的累积归一化:
r(t) = Σ(τ≤t) w(τ) / Σ w(τ),w(τ)是每个时段的消耗权重。设计 pacing 目标曲线,本质就是设计w(τ)。 - 权重对齐什么,决定曲线形状:可以对齐流量量(触达均匀)、可得量(请求 × 赢率,贴合真能买到的量)、或价值量(请求 × 赢率 × 转化价值 ÷ 价格,向高 ROI 时段倾斜)。三者形状差别很大。
- 纯量或纯价值都不最优:纯流量加权把钱平摊、但高峰更贵;纯价值加权向晚间集中、会塌缩成 ASAP 并丢覆盖、丢样本。生产多用价值打底 + 向均匀回拉的折中。
- 离线构造管线:聚合历史 → 原始加权 → 跨天平滑(EMA、剔异常)→ 混合回拉(
λ)→ 地板/天花板(floor/cap)→ 归一化。a,b,λ,floor,cap是可调旋钮。 - 必须按日型/时区建模:至少区分工作日 / 周末,节假日大促单独建。用周一的曲线投周日必跑偏。
- 预测与控制分工:小的随机残差交给 PID 秒级吸收;系统性偏差(每天同一时段都追不上)要回灌离线重估
w(τ),别让 PID 天天硬扛。 - 权重是天级慢更新:曲线本身别频繁抖,否则和秒级 PID 叠加会二阶震荡。冷启动无历史时降级为
uniform或同类目模板。
Table of contents
Open Table of contents
一、先厘清:目标曲线和权重是什么关系
Pacing 的目标曲线 r(t) 定义了「到时刻 t,累计消耗应该达到日预算的百分之几」,它是控制器的设定值(setpoint)。而 r(t) 不是凭空画的,它是把每个时段的消耗权重 w(τ) 累积、再归一化得来:
Σ(τ ≤ t) w(τ) w(τ) = 分配给 τ 时段的消耗权重(相对量)
r(t) = ────────────────── ∈ [0,1] 分母 = 全天权重之和 → 保证 r(T) = 1(收盘恰好花完)
Σ(all τ) w(τ)
所以:
- 均匀曲线是
w(τ) ≡ 常数的特例(每个时段权重相等 →r(t)是直线)。 - ASAP 是「权重全压在最前面能花的时段」的极端。
- 流量加权 / 价值加权就是让
w(τ)顺着某种「值得花」的信号分布。
设计 pacing 目标曲线,就等于设计 w(τ)。 剩下的问题只有一个:w(τ) 该正比于什么?
二、权重对齐什么:量、可得量、还是价值量
「值得花多少」有三种口径,直接决定曲线往哪个时段倾斜:
三种口径下的
w(τ) 形状:面积都等于 100%(花完同一笔预算),差别在「往哪个时段倾斜」。纯量触达均匀但高峰更贵;价值向晚间高 ROI 倾斜但更集中。
| 口径 | w(τ) ∝ | 曲线特征 | 适合 | 风险 |
|---|---|---|---|---|
| 量(流量) | 该时段可竞价请求数 | 触达均匀、节奏平稳 | 品牌 / 覆盖类;默认起点 | 高峰流量大但也最贵,单位成本偏高 |
| 可得量 | 请求数 × 预估赢率 | 贴合「真能买到的量」 | 竞争激烈、赢率时段差异大 | 依赖赢率预估 |
| 价值量 | 请求数 × 赢率 × 转化价值 ÷ 成交价 | 向高 ROI 时段倾斜 | 效果 / 转化类 | 易塌缩成 ASAP,丢覆盖、丢样本 |
2.1 为什么纯流量加权不是最优
直觉上「按流量分布花钱」很合理——流量多的时段多花。但流量多的时段(上午/晚间高峰)也是竞争最激烈、成交价最高的时段。纯按量加权,等于主动把更多预算投进最贵的窗口,单位成本吃亏。
2.2 为什么纯价值加权也危险
那干脆按「每块钱能买到多少价值」加权?从单次优化看这最优——钱该往边际 ROI 最高的时段堆。但它会带来两个隐患:
- 塌缩成 ASAP:如果晚间价值密度显著更高,权重会高度集中到几个小时,退化成「集中猛投」,重新捡回 pacing 本想避免的所有毛病(前重后轻、覆盖窄、样本单一)。
- 反馈坍缩:只在少数时段投放 → 只在这些时段拿到曝光与转化样本 → CTR/CVR 模型和流量预测越学越偏向这几个小时,形成自我强化的偏差。
2.3 实践:价值打底 + 向均匀回拉
生产里几乎都是折中:以可得量或价值量为骨架体现「往哪倾斜」,再用一个混合系数 λ 往均匀回拉,保证覆盖与学习。写成一步就是:
w(τ) = λ · w_value(τ) + (1 − λ) · uniform # λ ∈ [0,1]
└ 效率:向高价值倾斜 ┘ └ 稳健:保覆盖/保样本 ┘
λ 越小越安全(越接近均匀),越大越激进(越向价值集中)。从保守起步、看效果再逐步加大,是稳妥的上线路径。
三、离线构造管线:从历史日志到 r(t)
把上面的想法工程化,就是一条离线批处理管线,每天(或更细)跑一次,产出次日各 campaign 的目标曲线:
权重构造管线:聚合 → 加权 → 平滑 → 回拉 → 限幅 → 归一化。
a,b,λ,floor,cap 是把「效率 vs 稳健」拧到合适位置的旋钮。
逐步拆开:
- 聚合历史(近 N 天):按时段
slot统计{请求数, 赢率, eCPM/成交价, 转化率/价值}。slot粒度常取 15 分钟或 1 小时——细粒度更贴流量形状,但单格样本少、噪声大,小预算 campaign 宜用小时级。 - 原始加权:
w_raw(τ) = req(τ) × winrate(τ)^a × value(τ)^b。a,b控制向「可得/价值」倾斜的强度,a=b=0就退回纯流量。 - 跨天平滑:单日数据噪声大,用跨天 EMA 或加权平均降噪;剔除异常日(大促、故障、爬虫刷量),否则一次异常会污染很多天的模板。
- 混合回拉:
w = λ·w_raw + (1−λ)·uniform,把曲线往均匀拉一点,保覆盖、保样本(见 2.3)。 - 地板 / 天花板:
clamp每个 slot 的占比到[floor, cap]再重新归一化。用floor防某时段被压到 0(那段完全不投、留覆盖空洞);用cap防塌缩成 ASAP(单时段占比过高)。 - 归一化:
w(τ) / Σw(τ),累积即得r(t),天然满足r(T)=1(收盘恰好花完)。
一句话本质
这条管线做的,是把「哪个时段值得花」这个模糊判断,变成一条受约束的、可复现的目标曲线:用
a,b表达倾向,用λ和floor/cap兜住底线,最后归一化保证钱不多不少。
四、按日型与时区分别建模
一个极其常见、又极其致命的错误是:用一条曲线覆盖所有日子。一天的流量形状强烈依赖「今天是星期几」:
工作日与周末的流量形状截然不同:工作日早晚通勤双峰,周末起得晚、白天单个宽峰。权重必须按「日型」分别建。
- 至少区分工作日 / 周末:工作日有通勤早晚双峰,周末起得晚、白天一个宽峰。用周一的模板投周日,早高峰预算会砸进一个没有早高峰的日子。
- 节假日 / 大促单独建:这些日子的流量与转化分布和平日差异巨大,要么单独建模,要么用运营配置临时覆盖默认曲线。
- 时区要对齐 campaign 的目标受众时区,而不是服务器时区——跨时区投放最容易在这里错配。
进一步可以做「星期几」逐日建模(周五晚和周日晚的形态也不一样),但收益递减,工程上先把「工作日/周末 + 节假日覆盖」做好,性价比最高。
一句话本质
一天的形状取决于「今天星期几、受众在什么时区」。按日型 + 时区分别建模,是最便宜、收益最大的一步——它把预测误差从「结构性错配」压到「同型内的小抖动」。
五、预测与控制分工:残差归 PID,系统性偏差归权重
w(τ) 是预测,现实总会偏离预测。关键设计原则是:别让 PID 替预测的错误擦屁股。预测和控制是两条不同时间尺度的回路,各管各的:
两条回路:秒级快回路(残差 → PID 实时纠偏)与天级慢回路(系统性偏差 → 离线重估
w(τ))。分工清楚,pacing 才稳。
- 小的随机残差 → PID(秒级):今天晚高峰比预期弱了一点、某分钟赢率抖了一下——这类噪声由 PID 实时调
α吸收,不用惊动权重。 - 系统性偏差 → 重估
w(τ)(天级):如果「每天晚 8 点都追不上目标」,那不是随机噪声,是w(τ)系统性错了。这时该把偏差回灌离线管线重估权重,而不是让 PID 天天在这个时段硬顶——硬顶不仅治标不治本,还会诱发积分饱和(见 Pacing 篇的 windup)。 - 在线滚动学习:可以把「昨日实际消耗形状」按 EMA 融进明日权重,让模板自适应流量漂移。但要控制更新的幅度与频率——见下一节。
| 维度 | 随机残差 | 系统性偏差 |
|---|---|---|
| 特征 | 无规律抖动、时正时负 | 每天同一时段稳定同号偏离 |
| 例子 | 某分钟赢率抖一下、今晚高峰略弱 | 每天晚 8 点都追不上目标 |
| 归谁管 | PID(秒级快回路) | 重估 w(τ)(天级慢回路) |
| 动作 | 实时调 α 吸收 | 回灌离线管线修正权重 |
六、伪代码:设计思路落地
把前面的设计落成伪代码——重点是说明思路,不是实现细节。一个核心事实贯穿始终:权重构造是离线批处理(每天跑一次,产出次日各日型的目标曲线),在线只读——出价路径绝不做这些计算。分三块看:离线怎么构造 w(τ)、在线怎么查 r(t)、慢回路怎么判断该不该重估。
6.1 离线构造器:管线骨架
对应第三节那张管线图,每一步都标了「为什么这么做」:
# 离线批处理:为「每个日型」各产出一条归一化权重 w(τ)(Σ=1)
# 设计意图:脏活累活全在离线,用 a/b/λ/floor/cap 几个旋钮把「效率 ↔ 稳健」拧到位
FUNCTION build_weights(history, cfg) -> { day_type: w[n_slots] }:
uniform ← 每格相等的均匀权重 # 兜底 + 回拉的基准
FOR EACH day_type IN { 工作日, 周末, 节假日 }: # 关键:按日型分别建,别一条曲线通吃
rows ← history 中属于该 day_type 的记录
IF 历史天数 < 阈值: # 冷启动:宁可降级,也不用脏数据硬算
w[day_type] ← 同类目模板 或 uniform
CONTINUE
# ── 管线六步 ──
raw ← FOR EACH slot: req × 赢率^a × ROI^b # ①② 聚合 + 加权:a,b 定倾向
# req→量 · ×赢率→可得 · ×ROI→价值(含 ÷价格)
raw ← EMA_跨天(raw) 且 剔除异常日 # ③ 平滑:大促/故障/刷量不许污染模板
w ← λ·normalize(raw) + (1−λ)·uniform # ④ 向均匀回拉:λ 小=稳、λ 大=激进
w ← clamp 每格占比到 [floor, cap]×均匀 # ⑤ 限幅:floor 防覆盖空洞、cap 防塌成 ASAP
w[day_type] ← normalize(w) # ⑥ 归一化:Σ=1 → 收盘恰好花完
RETURN w
一眼能看出设计取舍全在几个旋钮上:a,b 决定往「可得 / 价值」倾斜多少,λ 决定往均匀回拉多少,floor/cap 兜住覆盖与集中度的底线。异常日剔除、跨天 EMA 这类实现细节,思路上只需记住「历史要抗噪、要抗突发」即可。
6.2 在线查询:把 w(τ) 变成此刻的 r(t)
在线路径只做一件事:查「到此刻累计该花到多少」,当作 PID 的设定值。slot 内线性插值,避免目标曲线出现台阶:
# 设计意图:复杂度留给离线,在线只留一次 O(1) 查表 + 插值
预热(服务启动时):
FOR EACH day_type: cum[day_type] ← 前缀和(w[day_type]) # 累计曲线 r(t),cum[末]=1.0
FUNCTION target_ratio(now) -> r(t) ∈ [0,1]:
w, cum ← 按 now 选对应日型的表 # 日型 / 时区在这里对齐受众
(k, frac) ← now 落在第 k 个 slot 的 frac 处
RETURN cum[k] + frac × w[k] # slot 内线性插值,交给 PID 当设定值
6.3 天级慢回路:判断该不该重估
第五节的分工在这里落地:偶发抖动交给 PID,只有多天、同一时段、稳定同号的偏差才算系统性、才值得回去重估权重。
# 设计意图:把「随机残差」和「系统性偏差」分开 —— 判错了要么过度反应、要么长期跑偏
FUNCTION need_reestimate(近 N 天: 目标增量[day][slot], 实际增量[day][slot]) -> [坏 slot]:
FOR EACH slot:
resid ← 实际 − 目标 # 该 slot 各天的残差
IF |mean(resid)| > 阈值 AND 同号天数占比 > 70%: # 又大又稳 → 系统性
标记该 slot
RETURN 被标记的 slot
# 命中的 slot → 下次离线 build 时修正(调其观测权重 / 排查预测源),
# 而不是让在线 PID 天天在这些时段硬顶 —— 硬顶治标不治本,还会诱发积分饱和。
一句话本质
全部设计意图都收敛到几个旋钮:
a,b定倾向、λ定回拉、floor/cap定底线、EMA/剔异常定抗噪。而三条边界自始至终不变:离线算 / 在线读、按日型建模、残差归 PID · 系统性偏差归权重。
七、几个必须处理的坑
- 权重更新太频繁 → 二阶震荡。
w(τ)是天级慢过程。如果曲线本身天天大幅抖动,和秒级 PID 叠加,就成了「两个控制器互相追」,消耗曲线会锯齿化。权重更新要慢、要平滑。 - 忘了
floor→ 覆盖空洞。 某时段权重被压到近 0,那段就完全不投,campaign 的人群覆盖出现结构性缺口。 - 忘了
cap→ 塌缩成 ASAP。 价值加权太激进,权重高度集中,pacing 名存实亡。 - 异常日污染模板。 大促、故障、爬虫刷量的历史若不剔除,会被平滑进未来很多天的曲线。
- 时区 / 日型错配。 前面强调过,最隐蔽也最常见。
- 小预算 campaign 用了太细的 slot。 单格样本太少,
w_raw噪声大,反而不如粗粒度稳。按预算规模选 slot 粒度。 - 冷启动无历史。 新 campaign / 新创意没有可靠的流量与价值预估,直接降级为
uniform或同类目/账户的聚合模板,攒够数据再切到加权(呼应冷启动一文)。
八、速查表
- 目标曲线 = 权重累积归一化:
r(t) = Σ(τ≤t)w(τ) / Σw(τ),设计曲线就是设计w(τ)。 - 三种口径:量(均匀)、可得量(请求 × 赢率)、价值量(× 转化价值 ÷ 价格)。纯量偏贵、纯价值偏挤。
- 折中公式:
w(τ) = λ·w_value + (1−λ)·uniform,λ从小到大逐步放开。 - 管线六步:聚合 → 加权(
a,b)→ 平滑(EMA + 剔异常)→ 回拉(λ)→ 限幅(floor/cap)→ 归一化。 - 必按日型/时区建模:工作日 / 周末 / 节假日分开,时区对齐受众。
- 分工:随机残差归 PID(秒级)、系统性偏差归权重重估(天级);别让 PID 硬扛预测错误。
- 慢更新:
w(τ)是天级过程,别频繁抖,否则与 PID 二阶震荡;冷启动降级uniform。
设计一条好的 pacing 目标曲线,本质是回答「今天这笔钱,各个时段各值得花多少」——用价值量表达倾向、用均匀回拉与地板天花板兜住覆盖和稳健、按日型时区分别建模,再把它当一个天级慢更新的预测,把秒级残差留给 PID。目标不是把曲线预测得分毫不差,而是让它顺着真实价值分布走、同时不塌缩、不留空洞——剩下的偏差,交给 PID 闭环 去抹平。
延伸阅读
- Bidder 预算 Pacing:PID 控制与消耗平滑:本篇的姊妹篇——目标曲线画好之后,如何用 PID 闭环把实际消耗跟住它。
- Bidder 竞价服务架构(总览):第六节把「预算与 Pacing」称作 Bidder 最难的一致性问题,本篇与 Pacing 篇一起补齐它。
- 冷启动:程序化栈每一层都在解同一个问题:新 campaign 没有可靠流量与价值预估时,权重如何先降级再收敛。
- Grafana JVM 监控分析:pacing 是「不看监控就发现不了跑偏」的典型——目标 vs 实际曲线是必看看板。
一手资料与背景:
- Karlsson, N. & Zhang, J. Applications of Feedback Control in Online Advertising (ACC 2013):把在线广告投放建模成反馈控制问题的经典论文,目标曲线 + 反馈控制的理论出处之一。
- Xu, J. et al. Smart Pacing for Effective Online Ad Campaign Optimization (KDD 2015):工业界 pacing 的代表性论文,讨论如何按流量/表现设定投放节奏。
- IAB Tech Lab. OpenRTB 2.6 Specification:赢率、成交价、
burl等信号的定义——它们是构造w(τ)的原始输入。
附录:术语表
- 目标消耗曲线
r(t):控制的设定值——到时刻t累计应消耗的预算占比;由权重累积归一化得到。 - 时段消耗权重
w(τ):分配给时段τ的消耗相对量;本篇的主角。 - 量 / 可得量 / 价值量:三种加权口径,分别正比于请求数、请求 × 赢率、请求 × 赢率 × 转化价值 ÷ 价格。
- 混合系数
λ:在「价值加权」与「均匀」之间插值的旋钮,λ越大越向价值集中。 - 地板 / 天花板(floor / cap):单时段权重占比的下限与上限,分别防覆盖空洞与防塌缩成 ASAP。
- 日型(day type):工作日 / 周末 / 节假日等,不同日型流量形状不同,需分别建模。
- 系统性偏差 vs 随机残差:前者是每天同一时段稳定偏离(该重估权重),后者是无规律抖动(交给 PID)。
- 冷启动:新 campaign / 创意缺乏历史,权重降级为均匀或同类目模板的阶段。