Identified Bayesian MMM — 基于高斯过程的无混淆贝叶斯营销归因
Skill-Identified-Bayesian-MMM · 15-营销投放分析
1. 解决的问题
CMO 拿到内部 MMM 报告,显示"TikTok ROAS 极高远未饱和,建议把 Meta 预算砍半全移给 TikTok"
2. 核心算法逻辑
传统开源 MMM(Robyn / Meridian)在仅有平稳投放数据时,会陷入一个数学死结:非线性饱和效应(spend 越多边际回报越低) 与 时变效应(节假日转化本就更好) 在数学上"观测等价"——两个完全不同的数据生成过程可以产生完全相同的观测值。这意味着模型给出的 ROAS 可能是系统性偏差的,据此做的预算决策会踩坑。
3. 业务应用场景
- 业务问题:CMO 拿到内部 MMM 报告,显示"TikTok ROAS 极高远未饱和,建议把 Meta 预算砍半全移给 TikTok"。过去半年 TikTok 均匀花出,数据平稳,但心里极没底——不知道高 ROAS 究竟是因为渠道牛还是碰上了旺季。 - 数据要求: - 90 天以上的日级三渠道(Google / Meta / TikTok)spend + conversions 数据 - 每渠道至少一次关停或超投实验记录(期间、倍率、实测提升比) - 操作路径: 1. `mmm.diagnose()` 输出诊断报告,TikTok 被标为 HIGH RISK,附实验处方 2. 按处方在德州区
- 业务问题:品牌方希望在 618 前确认 Google Shopping 当前是否还有边际 ROAS 空间,还是已经深度饱和,不该再加仓。 - 数据要求: - 近 60 天 Google 日级 spend + 转化数据 - 过去 2–3 次周末/节促期间的超投记录(自然发生的预算脉冲也算) - 操作路径: 1. 仅用 Google 渠道数据初始化 `IdentifiedBayesianMMM` 2. 若历史峰谷比 ≥ 2×,直接 `fit()` 即可;若平稳则补充一次测试性周末超投 3. 调用 `gp.predict(spend_query)` 绘制完整饱和曲线,`gp.marginal_r
4. 输入数据要求
请查看原始代码模板获取输入规格。
5. 输出结果
请查看原始代码模板获取输出规格。
6. 业务价值 / ROI
- ROI 预估:
- 实验成本:2 天局部区域(约 5% 流量)的停投损失,约 0.5–2 万元
- 保护价值:避免年度预算误分配,中型品牌(年广告 1000 万元)潜在年化收益 100–500 万元
- 投入产出比:保守估算 50–250 倍
- 实施难度:⭐⭐☆☆☆(2/5)
- 数学部分已封装,业务侧主要难点在于推动团队执行关停实验(需跨部门协调)
7. 代码模板
代码块数量:1 · 路径:未检测到
"""
使用方法示例: Identified Bayesian MMM 三渠道归因与预算优化
依赖: numpy (已内置于 model.py)
"""
import numpy as np
from model import (
ChannelData,
ExperimentalShock,
IdentifiedBayesianMMM,
)
# --- 1. 准备数据 ---
# 替换为真实数据: pd.DataFrame 按渠道拆分后转 numpy
tiktok = ChannelData(
name="TikTok",
spend=np.array([10000] * 90, dtype=float), # 日投放金额
conversions=np.array([14500] * 90, dtype=float), # 日转化金额
)
meta = ChannelData(
name="Meta",
spend=np.linspace(5000, 15000, 90),
conversions=np.linspace(8000, 18000, 90) + np.random.default_rng(0).normal(0, 500, 90),
)
google = ChannelData(
name="Google",
spend=np.where(np.arange(90) % 7 >= 5, 20000, 8000).astype(float),
conversions=np.where(np.arange(90) % 7 >= 5, 28000, 12000).astype(float)
+ np.random.default_rng(1).normal(0, 300, 90),
)
# --- 2. 初始化模型并诊断 ---
mmm = IdentifiedBayesianMMM([tiktok, meta, google])
report = mmm.diagnose()
print("=== 可识别性诊断 ===")
for ch, risk in report.channel_risks.items():
print(f" {ch}: {risk}")
if report.experiment_prescriptions:
print("\n=== 实验处方 ===")
for p in report.experiment_prescriptions:
print(f" {p}")
# --- 3. 录入实验冲击数据(执行实验后填写) ---
# 场景: 第 80-83 天对德州区域停投 TikTok
mmm.add_shock(ExperimentalShock(
channel="TikTok",
shock_period=(80, 83),
spend_multiplier=0.0, # 完全停投
observed_lift=0.08, # 停投后转化跌至基线的 8%(实测数据)
))
# 第 83-86 天同区域超投 3 倍
mmm.add_shock(ExperimentalShock(
channel="TikTok",
shock_period=(83, 86),
spend_multiplier=3.0,
observed_lift=1.75, # 转化提升至基线的 1.75 倍(实测数据)
))
# --- 4. 拟合模型 ---
8. 论文来源
- 2408.07678