预算优化的「0% 提升」不是无解,而是诚实结论。饱和响应模型表明当前分配已近局部最优(revenue 提升点估计 ≈ 0.0%);但 block bootstrap 揭示真正的决策风险——95% 置信区间是朴素逐行重抽样的 8.9 倍宽,单点估计会让决策者基于虚假精确感砍预算。本项目不止输出「该花多少」,更回答「这个数字有多可信」。
每渠道最优预算的 95% 置信区间(单品牌 913 行 · block_size=7 天 · N=200)
| 渠道 | 点估计 | block 95% CI | block/naive 宽度比 |
|---|---|---|---|
| meta_facebook | $634,590 | [$632,850, $635,031] | 9.1x |
| meta_instagram | $240,627 | [$238,886, $241,068] | 9.1x |
| google_pmax | $80,043 | [$78,174, $80,413] | 9.3x |
| google_video | $17,542 | [$16,374, $18,335] | 7.4x |
| google_paid_search | $19,276 | [$18,703, $23,764] | 14.6x |
| google_display |
$7,061 | [$706, $7,499] | 17.8x |
| google_shopping |
$2,438 | [$244, $7,320] | 3.1x |
| meta_other | $531 | [$52, $1,561] | 1.0x |
⚠️ = 置信区间逼近 0、系数符号不稳定的渠道——现有数据不支持对其下强结论。
Ridge MMM holdout R² = 0.42(OLS in-sample 0.54 / holdout 0.44),Revenue 提升 CI = [0.00%, 0.08%]。
为什么是 8.9 倍:MMM 残差强自相关(OLS Durbin-Watson = 0.90,无自相关应为 2.0)。朴素 case-resample 逐行重抽样「假装样本独立」,系统性低估不确定性、CI 偏窄,给决策者虚假精确感。block bootstrap 把时间序列切成 7 天整块、有放回重抽整块并保持块内时序,保留自相关结构,得到诚实(更宽)的区间。
tests/test_uncertainty.py::TestBlockVsNaive直接证明:在 AR(1)=0.85 序列上,naive 重抽样把 lag-1 自相关从 0.85 破坏到 ≈0,block 重抽样保留 ≈0.7。
🔧 核心模块详解(5 个模块的完整方法、结果表与诚实解读)
输入: 132,759 rows x 50 cols(含大量空值与千分位逗号分隔符)
输出: 清洗后的 Parquet (日粒度)
关键操作:
- 千分位逗号去除 + Float64 强制转换(解决 Polars 自动推断为 String 的问题)
- CTR、CPM、ROAS 衍生指标计算
- Adstock 衰减特征构造: x_t + 0.5*x_{t-1} + 0.25*x_{t-3} + 0.125*x_{t-7}
- 时间特征提取(year/month/day_of_week/is_weekend)
- 按时间切分而非随机切分:MMM 是日粒度时间序列,早期版本在全量数据上拟合并报 R²,本质是重代入(in-sample)拟合。现按日期留出尾部 20% 作为 holdout,同时报 in-sample R² 与 holdout R²/MAE,二者之差即过拟合程度。
- Ridge/Lasso 标准化 + CV 选 alpha:早期版本在未标准化的特征上施加 alpha=1.0/0.1 的小惩罚,导致 Ridge/Lasso 系数与 OLS 几乎完全相同(三模型实为一模型)。现先
StandardScaler标准化,再用TimeSeriesSplit在对数网格(Ridge logspace(-3,3)、Lasso logspace(-3,1))上选 alpha,使正则化真正生效、三模型显著有别。
Conjura MMM Dataset 为 2024 年 6 月发布的学术数据集,暂无官方竞赛 Baseline。以下为 MMM 领域公认的标准建模基准:
| 参照系 | R^2 | Adj. R^2 | 说明 |
|---|---|---|---|
| MMM 领域基准(Ridge) | 0.70–0.85 | — | 品牌级、含完整促销/价格/竞争数据的 MMM(参考文献:Hanssens et al., Market Response Models, 2nd ed.) |
| naive 均值预测 | ~0.0 | — | 用历史 revenue 均值作为预测 |
| 单变量(最大渠道) | ~0.35 | — | 仅用 spend 最高的单一渠道回归 |
| 本项目 OLS(in-sample / holdout) | 0.542 / 0.440 | 0.536 | 单品牌全渠道线性回归(auto-selected brand=5488b0,按时间留出 183 天 holdout) |
| 本项目 Ridge(in-sample / holdout) | 0.342 / 0.419 | — | L2 正则化,标准化 + CV 选 alpha=1000 |
| 本项目 Lasso(in-sample / holdout) | 0.542 / 0.440 | — | L1 正则化,标准化 + CV 选 alpha=10 |
R^2 ~ 0.54(in-sample)/ 0.44(holdout)反映了跨品牌聚合层面 MMM 的典型挑战:无价格/促销/竞品数据时,仅用渠道 spend 解释 revenue 变异的天然上限;holdout 比 in-sample 低约 0.10,是诚实的泛化估计。品牌级细分模型可达 0.70–0.85。
| 模型 | R² (in-sample) | R² (holdout) | MAE (holdout) | 最优正则化参数 |
|---|---|---|---|---|
| OLS | 0.542 | 0.440 | 1,816,109 | — |
| Ridge | 0.342 | 0.419 | 1,812,313 | alpha = 1000(CV) |
| Lasso | 0.542 | 0.440 | 1,816,124 | alpha = 10(CV) |
Ridge 在 CV 选出的大 alpha 下显著收缩系数(in-sample R² 降至 0.342,但 holdout R² 0.419 与 OLS 的 0.440 接近),说明强正则化牺牲少量偏差换取更稳定的系数——这对预算优化外推尤其重要。Lasso 选出 alpha=10 但未将任何系数压到 0(spend 变量均有解释力)。Durbin-Watson 等诊断量由模型自动输出,参见
data/processed/models/mmm_results.json。
使用真实 Criteo Attribution Modeling for Bidding Dataset(30 天实时流量,1,650 万条展示,61 万用户,4.5 万次转化)。将 impression 级数据按 uid 聚合成用户旅程,取 Top 10 campaigns 作为独立渠道,其余 665 个 campaign 归入 other 桶,对比 5 种归因模型 + 移除效应分析:
| 渠道 | First-Touch | Last-Touch | Linear | Time-Decay | Shapley | Removal Eff. |
|---|---|---|---|---|---|---|
| campaign_10341182 | 4.2% | 5.1% | 4.2% | 5.0% | 4.2% | 16.9% |
| campaign_9100693 | 3.2% | 4.7% | 5.3% | 4.4% | 3.3% | 19.4% |
| campaign_15184511 | 3.3% | 3.2% | 2.7% | 3.2% | 3.2% | 16.1% |
| campaign_30801593 | 3.0% | 3.0% | 3.2% | 2.9% | 2.9% | 1.1% |
| campaign_32368244 | 2.4% | 2.9% | 2.4% | 2.8% | 2.4% | 13.3% |
| campaign_15398570 | 2.2% | 2.2% | 1.7% | 2.2% | 2.3% | 5.8% |
| campaign_29427842 | 1.6% | 1.7% | 1.6% | 1.7% | 1.9% | 6.5% |
| campaign_2869134 | 1.9% | 2.9% | 3.5% | 2.7% | 1.9% | 12.0% |
| campaign_31772643 | 1.2% | 1.1% | 0.8% | 1.1% | 1.3% | 5.4% |
| campaign_28351001 | 1.3% | 1.1% | 0.8% | 1.1% | 1.3% | 3.5% |
| other | 75.7% | 72.0% | 73.7% | 73.0% | 75.5% | 0.0% |
注:campaign ID 为 Criteo 数据中的匿名化广告活动 ID。
other聚合了除 Top 10 外的 665 个长尾 campaign,因此占据绝大部分展示与转化。
关键发现:
- **规则类模型(First/Last/Linear/Time-Decay)**结论高度一致:由于
other桶覆盖了绝大多数展示,各规则模型都将其归因份额排在 72%–76% 之间。 - Shapley Value 在真实数据上仍提供了最稳定的分配,Top 3 渠道(campaign_10341182、campaign_9100693、campaign_15184511)合计贡献约 11%,与规则类模型趋势一致。
- 移除效应分析(Removal Effect) 对
other桶不敏感(移除other后剩余样本极少,导致其份额被压到 0%),但对头部单一 campaign 非常敏感——campaign_9100693 和 campaign_10341182 的移除效应分别达到 19.4% 和 16.9%,说明这两个 campaign 对整体转化率的边际影响最大。 - 方法启示:真实数据下归因模型之间的差异比模拟数据更小(因为
other桶占据主导),但 Shapley 与 Removal Effect 仍能有效识别头部高影响 campaign。
以 Ridge MMM 的系数作为渠道饱和响应函数的渐近上限,在总预算约束下用 SLSQP 求解最优分配:
响应模型(饱和,而非线性):
revenue_i = coef_i · spend_i^gamma / (spend_i^gamma + tau_i^gamma)
其中 coef_i 取自 Ridge 弹性系数,gamma=1.5 为 Hill 斜率,tau_i(半饱和点)锚定在渠道当前日均 spend——这样模型在观测运营点附近与线性弹性一致,但随 spend 远超 tau_i 时收益递减、趋于 coef_i 上限。
| 场景 | 总预算 | 预测 Revenue(当前 → 优化) | 提升幅度 |
|---|---|---|---|
| 当前分配(Baseline) | 100% | $2,082,150 → — | — |
| 重新优化分配 | 100% | $2,082,150 → $2,082,159 | ≈ 0.0% |
| 预算 +10% + 优化 | 110% | $2,082,150 → $2,082,159 | ≈ 0.0% |
| 预算 +20% + 优化 | 120% | $2,082,150 → $2,082,157 | ≈ 0.0% |
| 预算 -10% + 优化 | 90% | $2,082,150 → $2,082,160 | ≈ 0.0% |
诚实的业务启示:早期版本用线性响应函数,在总预算约束下最优解退化为平凡贪心——把预算挪向最高弹性渠道并外推到训练范围之外,得出 +1.8% 的虚高提升(线性模型隐含无限规模回报,本身无法支撑预算建议)。改用饱和响应后,在该品牌的当前运营点(各渠道已接近各自
tau)重新分配几乎无额外收益(≈0%),增量预算的边际收益也被饱和函数天然抑制——这正是预算优化应有的、诚实的结论:当前分配已接近局部最优。若要释放优化空间,需引入更强的特征(价格、促销、竞品)或在更细粒度(子渠道/时段)上建模。
单点估计会害死人。第四节的优化器输出「google_video 应花 $17,542」这样一个单点——CMO 拿它去砍预算时,隐含的假设是「我精确地知道最优分配」。但 Ridge 系数本身有噪声、训练样本只是历史的一小段,单点估计掩盖了真实的决策风险。本节给每个渠道的「最优 spend」与「revenue 提升」配 95% 置信区间,把项目从「会优化」升级为「懂决策风险」。
朴素 case-resample(逐行有放回重抽样)假设样本独立同分布。但 MMM 残差强自相关——本项目 OLS 的 Durbin-Watson = 0.90(无自相关应为 2.0)。在强自相关下逐行重抽样等于「假装样本独立」,会系统性低估不确定性、CI 偏窄,给决策者虚假的精确感。
解决方案是 block bootstrap:把时间序列切成连续的 block(默认 7 天一块),有放回重抽样整块、块内保持原始时间顺序地拼成新训练集——保留块内自相关结构,得到诚实的不确定性区间。
朴素 case-resample block bootstrap
┌─────────────────┐ ┌──┬──┬──┬──┬──┐ 原序列切成连续块
│ 打乱时序 │ └──┴──┴──┴──┴──┘
│ 假装 i.i.d. │ vs. ┌──┐┌──┐┌──┐┌──┐ 有放回抽整块
│ → CI 偏窄 │ │ ││ ││ ││ │ 块内保序
│ → 虚假精确感 │ └──┘└──┘└──┘└──┘
└─────────────────┘ → CI 诚实(更宽)
这不是「为了显得更严谨而加复杂度」——是朴素法在这里根本是错的。
tests/test_uncertainty.py::TestBlockVsNaive直接证明:在 AR(1)=0.85 的自相关序列上,block 重抽样后 lag-1 自相关 ≈ 0.7(保留),naive 重抽样后坍缩到 ≈ 0(破坏)。CI 宽度上 block 均值区间是 naive 的数倍宽——这就是被自相关放大的、真实存在的不确定性。
- 估计稳定的渠道(CI 窄、相对点估计比例小):
meta_facebook、meta_instagram、google_pmax——这三者当前 spend 量级大(数十万级)、且已接近各自饱和点,bootstrap 重抽样后最优分配几乎不动。对这三个渠道,单点估计可以作为决策依据。 - 估计不确定的渠道(CI 极宽、下界逼近 0):
google_display(CI [$706, $7,499],宽 $6,793)、google_shopping(CI [$244, $7,320])。诚实地说:现有数据不支持对这两个渠道下强结论。 它们的 Ridge 系数符号本身就不稳定(display 的 OLS 系数 p=0.31 不显著),优化器在「该砍」和「该加」之间反复横跳,CI 自然横跨整个允许区间。拿单点估计去砍这两个渠道的预算是危险的——需要更多数据(更长时间窗 / 价格促销特征)或更细分粒度才能定论。 - block vs naive 的倍数说明朴素法在这些渠道上会把 CI 低估 7–18 倍。最后两列的
block/naive比值越高,说明该渠道的不确定性越是被自相关主导——朴素法在那里错的越离谱。
这是本项目最想传达的「懂决策风险」的深度:优化器不只输出一个数字,而是告诉你「这个数字有多可信」。CI 宽不代表模型失败,它诚实地反映了数据的信息量——宽 CI 本身就是一个有价值的决策输入(「这个渠道的结论不确定,先别动它」)。
配置(config.py 集中管理,单点修改):BLOCK_SIZE_DAYS=7、N_BOOTSTRAP=200、BOOTSTRAP_CI_LEVEL=0.95、BOOTSTRAP_RANDOM_SEED=42。复用而非复制 mmm_model.fit_ridge / prepare_features / chronological_split 与 budget_optimizer.optimize_budget / extract_params / load_mmm_results。
git clone https://github.com/MeaFew/attributor.git
cd attributor
# 1. 下载 MMM 数据集(GitHub Releases,约 31MB)
bash download_data.sh
# 2.(可选但推荐)下载真实归因数据集 Criteo Attribution Modeling for Bidding Dataset
# 约 623MB,下载后放到 data/raw/criteo_attribution_dataset.tsv.gz
# 官方:https://ailab.criteo.com/criteo-attribution-modeling-bidding-dataset/
# 安装依赖并运行
make setup # 创建虚拟环境 + 安装依赖
make all
# Windows (无 GNU Make): python run_all.py # 运行完整管线:清洗 → MMM → 归因 → 优化
# 归因步骤自动判断:有 Criteo 原始数据则走真实
# preprocess_criteo,否则 fallback 到 generate_touchpoints 合成
make dashboard # 启动 Streamlit 交互看板
make verify # 本地质量门(lint + format + test + audit)📂 项目概览 / 技术架构 / 业务问题
本系统基于 figshare 发布的「Conjura Multi-Region MMM Dataset」(覆盖约 100 个电商品牌、19 个地区、2019–2024 年共 132,759 条日粒度记录),构建了一套从宏观营销组合建模(MMM)到微观用户旅程归因再到预算约束优化的完整分析链路。
核心解决的业务问题:
- 渠道 ROI 量化困难:多个渠道同时投放时,如何剥离各渠道对转化的真实贡献?
- 归因模型选择无依据:First-touch、Last-touch、Shapley Value、移除效应分析 (Removal Effect) 等方法结论差异巨大,如何系统比较?
- 预算分配凭经验:在总预算约束下,如何科学重新分配各渠道 spend 以最大化 revenue?
flowchart LR
A[Raw CSV<br/>132K rows x 50 cols] --> B[Polars ETL]
B --> C[Parquet]
C --> D[MMM Modeling<br/>OLS / Ridge / Lasso]
C --> E[User Journey<br/>Criteo 16.5M]
E --> F[6 Attribution Models]
D --> G[Budget Optimizer<br/>scipy SLSQP]
F --> G
G --> H[Streamlit Dashboard]
| 层级 | 技术选型 | 设计理由 |
|---|---|---|
| 数据清洗 | Polars | 向量化执行 + 惰性求值,处理 132K 行毫秒级 |
| 存储 | Parquet | 列式压缩,高效读写 |
| 宏观建模 | statsmodels + scikit-learn | OLS 提供完整统计推断(p-value、置信区间);Ridge/Lasso 处理渠道间共线性 |
| 微观归因 | 自研 6 种模型 | 覆盖规则类(First/Last/Linear/Time-decay)与博弈论类(Shapley/移除效应分析),便于横向对比 |
| 预算优化 | scipy.optimize SLSQP | 支持等式约束(总预算不变)与不等式约束(单渠道下限),收敛稳定 |
| 交付 | Streamlit + Plotly | 三页交互看板:MMM 概览 / 归因对比 / 预算模拟器 |
📁 项目结构
attributor/
├── scripts/
│ ├── preprocess.py # Polars ETL:缺失值、千分位处理、adstock、衍生指标
│ ├── mmm_model.py # OLS + Ridge + Lasso,VIF / Durbin-Watson / 残差诊断
│ ├── generate_touchpoints.py # 基于真实渠道结构模拟 50K 用户旅程(fallback)
│ ├── preprocess_criteo.py # 将 Criteo impression 数据聚合为用户旅程
│ ├── multi_touch_attribution.py # 6 种归因模型:First / Last / Linear / Time-decay / Shapley / Removal Effect
│ ├── budget_optimizer.py # scipy.optimize SLSQP 预算约束优化(饱和 Hill 响应)
│ └── budget_uncertainty.py # block bootstrap 给最优 spend 配 95% 置信区间(不确定性量化)
├── notebooks/
│ └── 01_eda.ipynb # 探索性数据分析
├── dashboard/
│ └── app.py # Streamlit 三页交互看板
├── tests/
│ ├── test_algorithms.py # 算法单元测试:6 归因模型(含 Shapley 数学正确性)+ MMM 三模型 + 预算优化器
│ ├── test_preprocess.py # 数据清洗单元测试
│ ├── test_mmm.py # 模型输出格式与统计量测试
│ ├── test_uncertainty.py # block bootstrap 不确定性:切块逻辑 / CI 覆盖率 / block vs naive 对比
│ └── test_attribution.py # 归因归一化与边界条件测试
├── data/
│ ├── raw/ # Conjura MMM dataset(figshare)
│ └── processed/ # 清洗后 Parquet
├── reports/
│ └── images/ # 生成的图表
├── config.py # 集中配置:路径、渠道列表、超参数
├── Makefile # 工作流编排
├── requirements.txt
└── .github/workflows/ci.yml # GitHub Actions:lint + test + docker-build
⚠️ 局限与生产化思考
| 局限 | 当前方案 | 生产化路径 |
|---|---|---|
| 多触点归因使用 Criteo 真实数据,但 campaign 已聚合 | 取 Top 10 campaigns + other 桶(665 个 campaign),便于 Shapley 计算 |
直接接入 CDP/Segment/Tealium 获取完整、未聚合的用户旅程;或使用更多计算资源处理全部 campaign |
| MMM 为日粒度 | 原始数据的日粒度已提供一定的时间分辨率 | 引入 hour-of-day 或 daypart 特征进一步精细化 |
| 无竞争环境变量 | 模型假设市场份额不变 | 引入竞品 spend 数据(如 Pathmatics、Sensor Tower) |
| 单节点执行 | 本地 Parquet | 迁移至 Snowflake/BigQuery + dbt 管线编排 |
| 预算优化为静态 | 一次性求解,未考虑动态预算调整 | 强化学习(PPO / MADDPG)实现实时预算竞价 |
| 项目 | 仓库 | 简介 |
|---|---|---|
| 电商用户行为分析 | MeaFew/shoplytics | 2,900万条真实用户行为数据,10大分析模块 |
| 信用风险评分 | MeaFew/riskscore | WOE/IV + XGBoost/LightGBM + SHAP 可解释性 |
| 多元时序预测 | MeaFew/foresight | LSTM / Transformer / XGBoost 时序预测对比 |
| 图神经网络反欺诈 | MeaFew/graphguard | 图神经网络非法交易检测 |
代码采用 MIT License。数据集来源于 figshare 公开发布的 Conjura MMM Dataset,遵循其使用条款。
