年底复盘,品牌广告的 ROI 总是排在末位,效果好像都被搜索广告拿走了。这不一定是品牌广告没用——很可能是模型结构本身,把它的贡献错误分配给了下游渠道。
几乎每个做全渠道营销的团队都遇到过这个场景:年底把 MMM 结果拉出来复盘,TV、信息流、社媒这类偏"认知曝光"的渠道,ROI 排在末位,甚至逼近 1.0;搜索广告、Retargeting 这类偏"临门一脚"的渠道,ROI 排在前列。结论看起来很直白——砍掉品牌预算,把钱挪去搜索,效率会更高。
这个结论可能是对的,但也可能是模型结构造出来的假象。消费者的真实决策路径通常不是"看到广告直接下单",而是"看到 TV 广告 → 几天后对品牌产生印象 → 需要购买时主动搜索品牌词 → 点击搜索广告完成转化"。在这条路径里,TV 广告才是真正制造需求的那一步,搜索广告只是承接了已经被制造出来的需求——但如果模型没有能力区分"制造需求"和"承接需求"这两种不同的角色,搜索广告会把整条路径的功劳都记在自己头上,TV 广告的贡献凭空消失。
如果某个渠道的花费和另一个渠道的搜索量/点击量在时间上高度同步(比如 TV 广告投放后一周,品牌词搜索量稳定上涨),但这个渠道自己算出来的 ROI 却很低,这通常是一个值得怀疑"贡献被下游抢走"的信号,而不是"这个渠道真的没用"的结论。
标准 MMM 方程是这样的:
这个方程把所有渠道(不管是 TV、社媒,还是搜索、Retargeting)放在同一层,模型只知道"这些都是自变量,都在解释同一个 Sales",不知道渠道之间存在"上游制造需求、下游承接需求"这种先后角色关系。当 Upper Funnel 渠道(TV/社媒/展示广告)和 Lower Funnel 渠道(搜索/Retargeting)的花费或者效果在时间上高度相关时,普通回归会遇到经典的共线性问题——模型没有办法可靠地区分"这部分销售是搜索广告自己带来的"还是"是 TV 广告制造需求、搜索广告只是执行了最后一次点击",统计上的默认行为是把系数分配给时间上更贴近转化、数据上更"干净"的那个渠道——而这几乎总是 Lower Funnel 渠道,因为它的转化路径短、归因窗口窄、噪音小。
这不是某个引擎实现得不好,是模型结构本身没有表达因果先后关系的能力。只要 Upper Funnel 和 Lower Funnel 被放进同一条方程当平级自变量,这个问题就会存在,换引擎、调参数都解决不了,因为问题不在于"怎么拟合",而在于"拟合的目标方程本身漏掉了一层因果结构"。
解决办法不是继续在同一条方程里加更多变量,而是承认"品牌搜索量""详情页浏览(DPV)""品类搜索指数"这类指标,在因果地位上跟渠道花费不是平级的——它们是 Upper Funnel 广告的结果,同时也是 Lower Funnel 转化的原因,是一个中介变量(Mediator)。把这条因果链显式拆成两段回归,这在计量经济学里叫中介分析(Mediation Analysis):
第一阶段回答"Upper Funnel 花费能不能拉动品牌指标",第二阶段回答"品牌指标能不能转化为最终销售,在控制了 Lower Funnel 自身花费之后"。这个结构比单阶段方程多出的信息是:它把"制造需求"和"承接需求"这两件事分开衡量,而不是把它们混在一起交给模型自己去猜权重。
这里有一个容易忽略但很关键的细节:第二阶段的 Lower Funnel 系数 β2,衡量的是"Lower Funnel 在剥离了品牌指标贡献之后,剩余的独立贡献"——如果 Lower Funnel 的花费本身也带来一部分不依赖品牌认知的直接转化(比如通用词搜索、跟品牌认知无关的比价类搜索),这部分依然会被 β2 正确捕捉,中介模型不会把 Lower Funnel 的全部贡献都抹掉,只是把原本被它"顺手拿走"的那部分还给了 Upper Funnel。
两段回归各自的系数不是终点,真正回答业务问题的是间接效应(Indirect Effect)——Upper Funnel 通过品牌指标这条路径,对最终销售的真实拉动量:
这个乘积就是"还给 Upper Funnel"的那部分贡献——如果单阶段模型给 TV 广告算出的 ROI 是 0.8(低于 1,看起来在亏钱),但间接效应显示 TV 广告通过品牌搜索这条路径还带来了相当于直接效应 60% 的额外贡献,那 TV 广告真实的综合回报可能远高于单阶段模型给出的数字。
间接效应的显著性检验有个常见的错误做法:直接看两段回归各自的 p 值,只要两个都小于 0.05 就认为间接效应显著。这是不对的——两个显著的系数相乘,乘积的显著性不能简单类推,标准做法是对间接效应本身做 Bootstrap 重采样:
α1 × β1样本量较小时(比如少于 100 周的数据),也可以用Sobel 检验作为更快速的近似方法,但 Bootstrap 得到的区间通常更保守、更贴近真实的不确定性,数据量允许的情况下优先用 Bootstrap。
完整的归因拆解需要三个数字,缺一个都不够完整:
| 效应类型 | 怎么算 | 回答什么问题 |
|---|---|---|
| 总效应(Total Effect) | 单独跑一次不含品牌指标的模型,Sales 直接对 Upper Funnel 花费回归得到的系数 | Upper Funnel 对最终销售的全部影响,不管走哪条路径 |
| 间接效应(Indirect Effect) | α1 × β1,两阶段回归的乘积 | Upper Funnel 通过品牌指标这条中介路径带来的影响 |
| 直接效应(Direct Effect) | 总效应 − 间接效应 | Upper Funnel 不经过品牌指标、直接影响销售的部分(比如展示广告本身也有临场冲动转化) |
如果间接效应占总效应的比例很高(比如超过 60%),说明这个渠道的价值几乎完全依赖"制造需求、被下游承接"这条路径,评估它的效果时必须把中介模型的结果纳入,单独看它自己的直接 ROI 会严重低估其真实价值。如果间接效应占比很低,说明这个渠道本身就有较强的直接转化能力,单阶段模型给出的 ROI 相对更可信,中介模型的修正幅度不会很大。
写这篇文章之前,我们先去读了 Meridian 装在平台里的 Python 源码,一开始得出的结论是"Meridian 目前没有真正的两阶段中介结构,只是把品牌指标当额外自变量塞进同一条方程"。这个结论后来被证明是错的——查了 Google 官方最新文档才发现,Meridian 从某个版本开始,已经原生实现了跟本文讲的完全同源的两阶段中介建模,而且官方给它起的名字正是"Full-Funnel MMM"。这里把完整的核实过程和纠正后的结论一起写出来,因为这个纠正本身就是一个值得讲的案例——查证的时候版本号是个容易被忽略但决定结论对错的细节。
根据 Meridian 官方文档,Full-Funnel MMM 要解决的核心问题跟本文前面讲的完全一致:品牌指标(比如 Branded Google Query Volume,简称 bGQV)在因果关系里同时扮演两个角色——对品牌广告(Upper Funnel)来说它是中介变量(Mediator),对下游渠道(Lower Funnel,尤其是搜索广告)来说它是混杂变量(Confounder)。单阶段模型必须二选一,而两个选择都会出错:
Meridian 给出的解法,跟本文讲的方式一致,官方文档里的表述是拆成两个模型:
| 阶段 | 目标 | 处理变量 | 响应变量 |
|---|---|---|---|
| Stage 1 | 刻画品牌广告如何积累品牌资产(Brand Equity) | 品牌广告(TV / 视频 / PR) | 品牌资产变量(如 bGQV) |
| Stage 2 | 刻画品牌资产 + 全部广告如何驱动最终 KPI | 全部媒介渠道 + Stage 1 的品牌资产变量 | 最终 KPI(销售/转化) |
两个模型分别拟合完成后,用官方提供的 AnalyzerFullFunnel 类把 Stage 1 里"品牌广告对品牌资产的效应",传递到 Stage 2 里"品牌资产对最终 KPI 的效应",得到的间接效应会被纳入 ROI、mROI、响应曲线和预算优化的最终估计——这跟本文前面讲的 α₁ × β₁ 是同一套数学结构,只是 Google 官方用了一套更严谨的因果推断语言(mediator/confounder)去描述,并且提供了现成的类去做效应传递,不需要像本文"手动相乘、手动 Bootstrap"这样自己拼。
问题出在版本上。SparkX 平台的 requirements-meridian.txt 目前锁定的是 google-meridian==1.1.0(2025 年 5 月发布),而 Full-Funnel MMM 这个功能是官方文档里明确列在"Advanced Modeling"分类下的较新能力,我们核实源码时只搜到了 Reach & Frequency、Organic Media、Non-media Baseline 这几类平级输入,没有搜到 mediation、two-stage、AnalyzerFullFunnel 这些关键词——因为这些确实是 1.1.0 版本里还不存在的东西。截至这篇文章写作时,Meridian 在 PyPI 上的最新版本是 2.0.0(2026 年 9 月发布),平台锁定的版本落后了将近一年、跨越了从 1.1 到 2.0 的多个版本迭代。
Meridian 官方确实原生支持真正的两阶段中介建模(Full-Funnel MMM),这个能力不是营销包装,是有具体实现(AnalyzerFullFunnel)和完整因果推断文档支撑的真实功能。但 SparkX 平台当前锁定的 Meridian 版本(1.1.0)还没有这个功能,要用上它需要升级依赖版本——升级本身有风险,需要确认新版本在 numpy/TensorFlow Probability 依赖链上跟平台其他部分(尤其是 PyMC,两者都对 numpy 版本敏感)不会冲突,这也是requirements-meridian.txt里当时锁定 1.1.0 的原因之一。
这个纠正过程本身也值得作为一个方法论提醒:核实一个开源工具"支持不支持某个功能",光看装在自己环境里的那个版本的源码是不够的,还要去看官方最新文档和版本变更记录——本地代码只能告诉你"当前部署的版本能做什么",回答不了"这个工具本身的能力边界在哪里"。这两个问题在做技术选型或者对外沟通时,答案可能完全不同。
在平台把 Meridian 依赖升级到支持 AnalyzerFullFunnel 的版本之前,两阶段中介模型不需要等这个功能才能用——只要建模引擎能做常规回归并给出后验分布(PyMC、Stan 都可以),两次独立的建模就能拼出完整流程,跟用不用 Meridian 无关。具体操作:
α1。β1,Lower Funnel 渠道的系数是剥离了中介贡献之后的 β2。α1 × β1 的点估计和置信区间。这一步目前没有平台内置功能,需要导出数据后用脚本计算。这套流程现在需要三次独立的建模运行加手动合并结果,因为平台当前每次运行只支持一个目标变量,不支持"一次运行同时估计两阶段、自动算出间接效应及其置信区间"。这跟处理跨渠道双向外溢效应遇到的架构限制是同一处——如果这类多阶段建模需求变得常态化,值得作为一个产品功能去排期,而不是每次都靠分析师手动拼三次模型结果。
做两阶段中介建模,比标准 MMM 多需要一类数据——中介指标本身的历史时间序列,跟渠道花费、销售额一样,需要按周或按天对齐。常见的可用来源:
数据质量上的一个基本要求:中介指标的时间序列至少要覆盖跟花费数据同样长的历史窗口(推荐 26 周以上),且尽量避免跟渠道花费本身用同一个数据源统计口径混淆(比如如果"品牌搜索量"本身就是从广告平台后台拉的、跟广告曝光量高度重叠,中介效应的估计会受到影响,最好用独立于广告平台的数据源,比如 Google Trends 或者自己网站的自然流量统计)。
β1 和 β2 的估计依然会不稳定,第二阶段回归前最好检查一下这两者的相关系数或者 VIF。年底复盘时看到品牌广告 ROI 排在末位,第一反应不该是直接砍预算,而是先问一句:"这个数字是渠道真的没用,还是模型结构本身没有能力看见它的间接贡献?"两阶段中介模型不能保证每次都能"救回"品牌广告的价值——如果间接效应的置信区间跨过零,那说明确实没有找到显著的中介路径,砍预算的决策依然是合理的。但至少这是一个经过验证、而不是拍脑袋的答案。