数据, 术→技巧

媒体组合模型(Marketing Mix Modeling,MMM)

钱魏Way · · 221 次浏览
!文章内容如有错误或排版问题,请提交反馈,非常感谢!

什么是媒体组合模型?

媒体组合模型(Marketing Mix Modeling,MMM)是一种统计分析方法,用于量化不同营销渠道和外部因素对销售或业务目标的影响,从而优化营销预算分配和策略。

核心目标

  • 效果评估:量化各营销渠道(如电视、数字广告、线下活动)的贡献度。
  • 预算优化:识别高ROI渠道,指导资源分配。
  • 预测与场景模拟:预测不同预算分配下的销售表现,支持决策。
  • 长期策略制定:平衡短期销售提升与长期品牌建设。

关键组成要素

  • 因变量(目标指标):通常为销售额、转化率等业务核心指标。
  • 自变量
    • 营销渠道:广告支出、曝光量、点击量等。
    • 外部因素:季节性、经济指标(如GDP)、竞争对手活动、天气等。
    • 控制变量:产品价格、促销活动、分销渠道覆盖率等。
  • 时间维度:按周/月为单位的面板数据,捕捉长期趋势与短期波动。

核心方法

  • 多元回归分析:传统方法,通过线性/非线性回归分解变量贡献。
  • 时间序列分析:处理自相关性、趋势、季节性(如ARIMA、Prophet)。
  • 滞后期效应:使用Adstock模型(几何衰减或自定义曲线)模拟广告的延迟影响。
  • 机器学习扩展:贝叶斯方法(概率编程库如PyMC)、随机森林、梯度提升树(处理非线性关系)。
  • 归因整合:结合MMM(宏观)与 attribution modeling(微观触点)形成完整视图。

建模步骤

  • 数据整合
    • 聚合跨渠道营销数据、销售数据、外部数据集。
    • 处理缺失值(插补法)和异常值(如IQR检测)。
  • 变量转换
    • Adstock处理:计算各渠道的衰减效应(如λ=0.5表示半衰期为一周期)。
    • 非线性关系建模:对数变换、S形函数(如广告饱和度效应)。
  • 模型构建
    • 变量选择:逐步回归、LASSO(处理共线性)。
    • 参数估计:最大似然估计、MCMC(贝叶斯方法)。
  • 验证与调优
    • 拟合度指标:R²、调整R²、MAPE(平均绝对百分比误差)。
    • 交叉验证:时间序列交叉验证(避免数据泄漏)。
    • 残差分析:检验异方差性、自相关性(Durbin-Watson检验)。
  • 场景模拟
    • 边际效应分析:计算每增加$1万预算对销售的边际贡献。
    • 预算重新分配模拟:蒙特卡洛方法评估不同分配策略的风险与收益。

优势与挑战

  • 优势
    • 宏观视角:适合长期策略和预算规划。
    • 数据要求相对灵活:可使用聚合数据,避免用户级隐私问题。
  • 挑战
    • 数据质量:渠道数据碎片化(如跨平台广告)、归因窗口不一致。
    • 模型复杂性:变量交互效应(如线上线下协同)、非线性关系的准确捕捉。
    • 动态环境适应:快速变化的渠道(如短视频广告)需高频模型更新。
    • 因果推断局限:需结合实验(A/B测试)增强因果结论可信度。

与其他模型的区别

  • 归因模型(Attribution Modeling):侧重用户转化路径中的触点贡献(短期、微观)。
  • 媒体组合模型(MMM):侧重宏观预算分配和长期效果,适合品牌建设或传统媒体评估。

企业实战案例

案例1:某DTC品牌的ROI提升

挑战:2000万美元年广告预算,ROI同比下降15%

解决方案:

  • 识别出Facebook广告存在过度饱和(边际ROI<1)
  • 发现TikTok与电子邮件的协同系数为6
  • 通过优化模块重新分配预算

结果:在总预算不变情况下,季度销售额提升28%

案例2:某电商平台的预算重分配

背景:年度广告预算5000万,但ROI持续下降

分析方法:

  • 发现搜索广告存在75%的饱和阈值
  • 视频广告与社交媒体有1:1.2的协同系数
  • 通过遗传算法优化得出新分配方案

成果:节约23%预算的情况下保持同等销售额案例3:某游戏公司的季节性调整

挑战:节假日广告效果波动剧烈

技术方案:

  • 使用时变系数模型(TVP-VAR)
  • 构建节日影响因子:$\beta_{holiday}=\beta_{base}\times(1+\gamma\cdot I_{holiday})$

效果:节日期间预测准确率提升至92%

案例4:全球快消品牌预算重构

挑战:5亿美元年广告预算,数字渠道ROI持续下降

技术方案:

  • 识别出Instagram广告存在70%的预算浪费(边际ROI<1)
  • 发现CTV(联网电视)与搜索广告的协同效应系数45
  • 重新分配预算至TikTok和程序化音频广告

结果:总销售额提升14%,CPA降低22%

案例5:游戏公司季节性优化

数据:日活用户(DAU)与15个媒体渠道的实时数据

技术方案:

  • 使用Robyn的动态衰减率捕捉假期效应
  • 通过Shapley值分解量化渠道贡献

成果:Q4营销效率提升31%,用户获取成本(CAC)下降19%

经典案例研究

Google “Gold Standard” MMM案例

项目背景

  • 时间:2017年(Google首次公开案例)
  • 合作方:Google与某全球快消巨头(匿名)
  • 传统MMM的局限性
  • 依赖历史数据,难以捕捉快速变化的数字广告效果;
  • 无法区分广告的”品牌效应”(长期)与”转化效应”(短期);
  • 渠道协同效应(如电视广告与搜索广告的交互)难以量化。
  • 目标:验证数字广告(搜索、YouTube)的真实增量价值,解决传统MMM在数字渠道评估中的偏差问题。

实验设计与方法

增量实验(GeoLift Test)

  • 核心逻辑:通过地理区域随机对照实验(RCT)验证MMM预测的准确性。
  • 实施步骤:
    • 市场分割:将目标国家划分为数百个地理区域(DMAs)。
    • 随机分组:分为测试组(增加数字广告预算)与对照组(维持原预算)。
    • 预算调整:在测试组中,将5%-15%的传统媒体预算转移至YouTube/搜索广告。
    • 数据监测:追踪12个月的销售额、品牌搜索量、网站流量等指标。

模型构建

双重验证框架:

  • 传统MMM:使用回归模型量化各渠道贡献,包含:
  • 广告衰减效应(Adstock):$\text{Adstock}(t)=\sum_{i=0}^{L}\lambda^i\cdot X_{t-i}$(衰减因子λ=0.7)
  • 饱和曲线(Saturation):$\text{S-curve}(X)=\frac{X^\alpha}{X^\alpha+\kappa^\alpha}$(α=2,κ=预算阈值)
  • 增量实验:通过实际数据验证MMM预测的增量销售额是否准确。

关键技术突破

  • 协同效应建模:
    • 发现电视广告与YouTube广告存在1:0.8的协同系数(即每1元电视广告需搭配8元YouTube广告以达到最优效果)。
    • 在模型中引入交互项:$\beta_{TV×Digital}\cdot TV_t\cdot Digital_t$
  • 长短期效应分离:使用向量自回归(VAR)模型分解即时销量提升(<1个月)与品牌资产积累(6-12个月)。

核心成果与发现

业务成果

  • 预算优化:将数字广告预算占比从12%提升至22%,总销售额增长3%;
  • ROI提升:搜索广告的增量ROI达4倍于传统模型估算值;
  • 渠道协同:电视+YouTube组合的CPM(每千次曝光成本)降低31%。

方法论突破

  • 实验验证的MMM:首次通过RCT证明MMM预测误差可控制在±15%以内;
  • 增量效应公式:提出数字广告的增量贡献公式:$\Delta Sales=\beta_{base}+\gamma\cdot\text{Adstock}(Digital)+\epsilon$,其中γ为实验校准系数(案例中γ=1.2)。

技术细节与开源资源

数据架构

数据源整合:

  • 传统媒体数据:Nielsen电视收视率、Kantar户外广告监测;
  • 数字数据:Google Ads API(搜索)、YouTube TrueView曝光日志;
  • 销售数据:零售商POS系统(脱敏聚合)。

模型参数示例

参数 传统MMM估值 实验校准后值 变化幅度
YouTube ROI 2.1 2.8 +33%
电视衰减周期 4周 6周 +50%
搜索广告饱和点 $1.2M/月 $0.9M/月 -25%

开源工具

行业影响与后续发展

方法论标准化

  • ISO认证:该框架成为MMA(全球移动营销协会)认证的MMM标准流程;
  • 专利技术:Google申请了“基于地理分组的媒体效果验证系统”(USPTO #20190188590)。

后续应用案例

  • 某汽车品牌:通过复制该方法,发现展示广告(Display)的增量ROI被高估40%;
  • 某零售企业:优化预算分配后,季度净利润提升$27M(+11% YoY)。

争议与改进

  • 挑战:
    • 小区域市场的样本量不足问题(需至少50个DMAs);
    • 跨渠道归因与MMM的优先级争议(Google建议MMM为主、归因为辅)。
  • 改进方向:
    • 引入合成控制法(Synthetic Control)替代随机分组;
    • 整合机器学习模型处理高维数据(如Meta的Robyn模型)。

学习实践建议

复现步骤

  • 使用Kaggle广告数据集构建基础MMM;
  • 在Google Cloud上运行地理实验模拟(BigQuery+GeoLift模块);
  • 对比模型预测与实际模拟结果的偏差。

关键代码片段(Python示例):

# Adstock转换函数
def adstock_transform(x, decay=0.7, lags=4):
    x = np.array(x)
    return np.array([sum(x[max(0, t-lags):t+1] * (decay ** np.arange(t-max(0, t-lags), -1, -1))) for t in range(len(x))])

# 饱和曲线计算
def saturation_effect(x, alpha=2, kappa=1e6):
    return (x**alpha)/(x**alpha + kappa**alpha)

避坑指南

  • 数据陷阱:确保数字广告曝光数据去重(避免跨设备重复计数);
  • 模型过拟合:使用正则化(Lasso)约束渠道系数;
  • 业务解释:用SHAP值可视化渠道贡献(参考SHAP库文档)。

Lyft的逆向工程MMM

背景与核心挑战

业务特殊性

  • 行业属性:共享出行平台具有实时供需匹配特征,受天气、事件、竞品动态等外部因素影响显著;
  • 数据特点:
    • 服务覆盖300+美国城市,存在显著的地理异质性;
    • 高频数据(每分钟订单量)与传统MMM的周粒度数据不兼容;
    • 营销渠道包含动态定价补贴、司机端激励、用户APP推送等多类型干预。

传统MMM的失效

关键问题:

  • 城市级数据稀疏:小城市营销活动样本不足导致参数估计不稳定;
  • 营销即时性:70%的促销效果在24小时内衰减,传统Adstock模型(4周衰减周期)失效;
  • 混杂因素干扰:疫情导致出行需求结构性变化,历史数据规律被打破。

逆向工程方法论

逆向工程的定义核心理念:不从营销活动到销售额的正向归因,而是通过分解总销售额反推各渠道贡献。

公式表达:

$$Sales_t=\underbrace{\alpha\cdot Base_t}_{\text{自然需求}}+\sum_{i=1}^n\underbrace{\beta_i\cdot f(Channel_{i,t})}_{\text{渠道贡献}}+\epsilon_t$$

其中:

  • $Base_t$:通过时间序列分解(STL)剥离出的基线需求
  • $f(\cdot)$:针对各渠道特性设计的响应函数

分层贝叶斯模型架构

模型层级:

  • 全国层:估计渠道效应的先验分布(如补贴的ROI服从Gamma(2,1));
  • 城市层:允许各城市在共享先验的基础上调整本地参数;
  • 时间层:使用动态线性模型(DLM)捕捉参数时变性。

参数估计示例:

# PyMC3代码框架
with pm.Model() as hierarchical_mmm:
    # 全国层先验
    mu_roi = pm.Normal('mu_roi', mu=0, sigma=1)
    sigma_roi = pm.HalfNormal('sigma_roi', 1)

    # 城市层随机效应
    city_roi = pm.Normal('city_roi', mu=mu_roi, sigma=sigma_roi, shape=n_cities)

    # 动态时变系数
    roi_time = pm.GaussianRandomWalk('roi_time', sigma=0.1, shape=(n_cities, n_weeks))

    # 似然函数
    pm.Normal('likelihood',
        mu=city_roi[city_idx]*roi_time[city_idx, week_idx]*channel_spend,
        observed=sales_increment)

关键技术突破

  • 高频响应函数:针对动态定价补贴,设计指数衰减函数:$Effect(t)=\sum_{\tau=0}^{23}e^{-\lambda\tau}\cdot Subsidy_{t-\tau}$($\lambda=0.3$对应半衰期3小时)
  • 地理迁移学习:使用城市特征(人口密度、竞品渗透率)作为层次模型的协变量:$\beta_{city}=\gamma_0+\gamma_1\cdot PopulationDensity+\gamma_2\cdot CompetitorPresence$

实施成果与验证

业务效果

  • 预算优化:识别出25个城市的司机端激励存在过度投入(ROI<1),重新分配后总效率提升19%;
  • 预测精度:相比传统MMM,城市级销售额预测的MAE从7%降至6.3%;
  • 动态响应:成功捕捉到旧金山音乐节期间促销效果的3倍瞬时提升。

模型验证方法

合成控制实验:

  • 选择特征相似的城市组(K-means聚类);
  • 随机暂停部分城市的营销活动;
  • 对比模型预测的”反事实销售额”与实际下降值的差异。

结果:模型误差带(95%CI)控制在±5%以内。

开源资源与复现指南

数据模拟工具

SyntheticDataGenerator:

def generate_city_data(n_cities=100, n_weeks=52):
    city_features = {
        'population': np.random.lognormal(mean=10, sigma=1, size=n_cities),
        'competitor_presence': np.random.beta(a=2, b=5, size=n_cities)
    }
    roi_base = 2.0 + 0.3 * city_features['competitor_presence']
    spend = np.random.gamma(shape=2, scale=10000, size=(n_cities, n_weeks))
    sales = roi_base[:, None] * spend + np.random.normal(scale=5000)
    return pd.DataFrame(sales), city_features

推荐学习路径

  • 基础掌握:
    • 贝叶斯分层模型(Hierarchical Models)概念
    • PyMC3/Stan概率编程工具
  • 进阶实践:
    • 在模拟数据上复现城市级参数估计
    • 尝试加入时间变系数(Time-varying Coefficients)
  • 高阶挑战:
    • 整合自然语言处理(NLP)解析城市事件数据
    • 开发Spark并行化版本处理300+城市数据

行业启示与局限性

创新价值

  • 小数据解决方案:为区域化营销提供了样本不足情况下的建模范式;
  • 实时决策支持:突破传统MMM的周/月分析粒度,实现小时级效果追踪;
  • 可解释性提升:通过层次模型分解出城市特征的影响系数(如γ2反映竞品敏感度)。

应用局限

  • 计算复杂度:需借助GPU加速(城市数>100时,MCMC采样时间超过24小时);
  • 数据门槛:依赖城市级特征数据的完备性(需至少10个描述变量);
  • 业务适配:高频响应函数设计需要领域知识(如确定补贴衰减率λ)。

关键代码解析

贝叶斯层次模型核心片段(PyMC3)

import pymc3 as pm

with pm.Model() as model:
    # 城市特征协变量
    population = pm.Data('population', city_features['population'])
    competitor = pm.Data('competitor', city_features['competitor_presence'])

    # 全国层先验
    mu_alpha = pm.Normal('mu_alpha', mu=0, sigma=1)
    sigma_alpha = pm.HalfNormal('sigma_alpha', 1)

    # 城市层随机效应
    alpha = pm.Normal('alpha',
        mu=mu_alpha + 0.3 * competitor,  # 协变量影响
        sigma=sigma_alpha,
        shape=n_cities)

    # 时变系数随机游走
    beta = pm.GaussianRandomWalk('beta',
        mu=0,
        sigma=0.1,
        shape=(n_cities, n_weeks))

    # 线性预测
    mu = alpha[city_idx] + beta[city_idx, week_idx] * spend

    # 似然函数
    obs = pm.Normal('obs', mu=mu, sigma=500, observed=sales)

开源项目与代码库

LightweightMMM (Spotify)

核心定位与优势

项目背景

  • 开发者:Spotify广告科学团队,2021年开源
  • 目标场景:面向中小型数据集(10-50个媒体渠道)的快速MMM建模
  • 技术栈:基于NumPyro(概率编程库)+JAX(高性能计算),兼顾灵活性与速度

关键优势

  • 轻量化:相比 Meta 的 Robyn,模型训练速度提升 3-5 倍(GPU 加速)
  • 可解释性:内置 SHAP 值计算模块,可视化渠道贡献
  • 可扩展性:支持自定义 Adstock、饱和函数等核心组件

技术架构解析

模型数学表达

基础公式:

$$Sales_t=\alpha+\underbrace{\sum_{i=1}^n\beta_i\cdot f_i(X_{i,t})}_{\text{媒体效应}}+\gamma\cdot Control_t+\epsilon_t$$

其中:

  • $f_i(\cdot)$:渠道 $i$ 的非线性变换(Adstock+Saturation)
  • $\beta_i$:渠道贡献系数(贝叶斯后验估计)

核心组件实现

a) Adstock 衰减效应

指数衰减公式:

def adstock_transform(x, lag_weight: float, normalise: bool=False):
    x = jnp.array(x)
    weights = jnp.array([lag_weight**i for i in range(len(x))])
    adstocked = jnp.convolve(x, weights, mode='full')[:len(x)]
    return adstocked/jnp.sum(weights) if normalise else adstocked

支持自定义衰减曲线(如 Weibull 分布)

b) 饱和效应

使用 Hill 函数:$$saturation(x)=\frac{x^\alpha}{x^\alpha+\kappa^\alpha}$$

参数 $\alpha$ 控制曲线陡峭度,$\kappa$ 为半饱和点贝叶斯推断流程

先验分布设置:

def model(media_data, sales_data):
    # 媒体系数先验
    beta = numpyro.sample("beta", dist.HalfNormal(scale=1.0))
    # Adstock 衰减参数
    lag_weight = numpyro.sample("lag_weight", dist.Beta(concentration1=1, concentration0=3))
    # 饱和函数参数
    alpha = numpyro.sample("alpha", dist.Gamma(concentration=3, rate=1))
    kappa = numpyro.sample("kappa", dist.LogNormal(loc=0, scale=1))
    ...

使用 NUTS 采样器进行后验估计

实战应用指南

安装与数据准备

安装命令:pip install lightweight-mmm

数据要求:

  • 时间序列格式(周/月粒度)
  • 媒体变量矩阵(n_timesteps × n_channels)
  • 可选控制变量(如价格、促销)

基础建模流程

from lightweight_mmm import lightweight_mmm

# 初始化模型
mmm = lightweight_mmm.LightweightMMM()
# 拟合数据
mmm.fit(media_data=media_array,
        media_prior=costs, # 媒体成本作为先验
        target=sales,
        n_iters=2000,
        n_chains=4)
# 结果可视化
mmm.plot_media_channel_posteriors()

预算优化模块

使用 SVI(随机变分推断)快速求解最优分配:

from lightweight_mmm import optimize_media

# 定义预算约束
budget = sum(current_spend)*1.2 # 总预算增加 20%
# 运行优化
optimal_allocation = optimize_media(mmm_model=mmm,
                                  budget=budget,
                                  prices=media_prices,
                                  bounds_lower=0.5,
                                  bounds_upper=2.0)

高级功能与案例

地理分层建模

场景:在多个区域市场分别建模

代码实现:

from lightweight_mmm import preprocessing

# 将数据按地理维度堆叠
geo_media, geo_sales = preprocessing.convert_to_geo_data(
    national_media=media_data,
    national_target=sales_data,
    geo_mapping=geo_mapping_matrix)

# 分层模型拟合
mmm.fit(media_data=geo_media,
        target=geo_sales,
        extra_features=geo_features)

媒体协同效应

添加渠道交互项:

media_interactions = preprocessing.generate_interaction_terms(
    media_data=media_data,
    interaction_matrix=[[0,1,0], # 渠道 1 与渠道 2 交互
                        [1,0,1]])
mmm.fit(media_data=media_data,
        extra_features=media_interactions)

预测与反事实分析

# 未来 12 周预测
forecast = mmm.predict(media_data=future_media,
                      extra_features=future_controls)

# 反事实场景:若停止 YouTube 广告
counterfactual_media = media_data.copy()
counterfactual_media[:,1] = 0 # 假设第 2 列为 YouTube
loss = mmm.predict(counterfactual_media).mean()-original_sales.mean()
print(f"停止 YouTube 预计损失: ${loss:.2f}M")

性能对比与局限

与其他工具对比

特性 LightweightMMM Meta Robyn PyMC Marketing
计算速度 快(JAX 加速) 中等
自定义灵活性 极高
数据规模 中小型 大型 中小型
贝叶斯推断 NumPyro Stan PyMC

已知局限性

  • 数据规模限制:单模型最多支持50个媒体渠道(受JAX内存限制)
  • 先验依赖:需要用户具备贝叶斯先验选择的知识
  • 非结构化数据:不支持文本/图像等富媒体特征

最佳实践建议

先验选择策略

使用历史ROI数据设置弱信息先验:

beta_prior = dist.LogNormal(loc=np.log(historic_roi), scale=0.5)

对Adstock衰减参数使用Beta(1,3)先验(偏向短期效应)

数据预处理技巧

  • 归一化:媒体支出标准化为花费占比(避免量纲问题)
  • 异常值处理:对节假日等事件进行虚拟变量标记
  • 滞后效应:通过PACF分析确定最大Adstock周期

诊断与验证

  • 收敛性检查:plot_trace()观察MCMC链混合度
  • 样本外测试:保留最后8周数据作为验证集
  • 灵敏度分析:改变先验分布观察参数稳定性

PyMCMarketing案例库

项目定位与技术生态

项目背景

  • 开发团队:由PyMCLabs主导,2022年正式开源
  • 技术基底:基于PyMC v5的概率编程框架,整合ArviZ(可视化)、Bambi(公式接口)
  • 设计哲学:提供端到端贝叶斯营销分析解决方案,覆盖从数据预处理到决策优化的全流程

核心优势

  • 灵活性:支持自定义似然函数、先验分布、非线性效应
  • 可解释性:内置SHAP值、ALE(Accumulated Local Effects)等解释工具
  • 生产就绪:与FastAPI、Streamlit等部署工具无缝集成

案例库核心模块

媒体组合模型(MMM)

完整链路:

Adstock转换:支持Weibull衰减(优于传统指数衰减)

def weibull_adstock(x, lam=0.5, k=1.0):
    weights = (lam ** np.arange(len(x))) ** k
    return np.convolve(x, weights, mode='full')[:len(x)]

饱和效应:使用Michaelis-Menten方程建模边际递减

def saturation(x, Vmax=1e6, Km=5e5):
    return (Vmax * x) / (Km + x)

贝叶斯推断:通过NUTS采样器估计后验分布

代码示例:

import pymc as pm
from pymc_marketing.mmm import MMM

mmm = MMM(
    date_column="date",
    media_columns=["TV", "Digital"],
    adstock_lam=0.8,  # Weibull形状参数
    saturation_speed=0.2
)
mmm.fit(data=df, target="sales")

客户生命周期价值(CLV)

Beta-Geometric/NBD模型

  • 预测客户购买频率与流失概率
  • 公式:$P(active|X)=\frac{1}{1+(\frac{r+\alpha}{r+s})^{x}\cdot(\frac{s}{r+s})^{t}}$
  • 应用场景:识别高价值用户群体

地理分层模型

分层贝叶斯框架:

with pm.Model() as geo_model:
    # 全国层先验
    mu_beta = pm.Normal("mu_beta", 0, 1)
    sigma_beta = pm.HalfNormal("sigma_beta", 1)

    # 城市层随机效应
    beta_city = pm.Normal("beta_city", mu=mu_beta, sigma=sigma_beta, dims="city")

    # 媒体效应
    media_effect = pm.math.dot(media_data, beta_city)

    # 似然
    pm.Normal("likelihood", mu=media_effect, observed=sales)

核心案例详解

案例1:多渠道ROI优化

  • 数据:某零售品牌12个月的全渠道数据(TV/搜索/社交)
  • 分析步骤:
  • 非线性效应检测:发现社交媒体的饱和阈值为$150K/月
  • 预算再分配:使用OptimalAllocator模块最大化ROI
allocator = OptimalAllocator(
    mmm_model=mmm,
    budget=1e6,
    prices={"TV": 0.5, "Digital": 0.3}
)
allocator.allocate(verbose=True)

结果:总ROI提升22%,搜索广告预算削减40%

案例2:长短期效应分离

技术方案:

使用状态空间模型(State-Space Model)分解趋势项

公式:

$$Sales_t=\underbrace{\alpha_t}_{趋势}+\underbrace{\sum\beta_iX_{i,t}}_{短期}+\epsilon_t$$

$$\alpha_t=\alpha_{t-1}+\eta_t$$

商业价值:量化品牌广告的长期累积效应(LTV提升19%)

性能优化策略

GPU加速

JAX后端配置:

import pymc.sampling.jax as pmjax

with pm.Model():
    ...
    trace = pmjax.sample_numpyro_nuts(target_accept=0.9)

速度对比:在NVIDIA A100上,10万样本采样时间从4.2小时降至18分钟稀疏数据处理

零膨胀模型(Zero-Inflated Model):

with pm.Model():
    psi = pm.Beta("psi", 1, 1)  # 零膨胀概率
    theta = pm.Gamma("theta", 2, 0.1)
    pm.ZeroInflatedPoisson("obs", psi, theta, observed=data)

企业级部署

API服务化

FastAPI集成:

from fastapi import FastAPI
from pymc_marketing.api import MMMEndpoint

app = FastAPI()
app.include_router(MMMEndpoint(mmm_model).router)

端点功能:

  • /predict: 获取销量预测
  • /optimize: 执行预算优化
  • /shap: 返回渠道贡献度

监控看板

Streamlit 可视化:

import streamlit as st
from pymc_marketing.viz import plot_media_effect

st.title("MMM 实时监控")
st.plotly_chart(plot_media_effect(mmm.trace))

行业应用对比

场景 PyMC 方案 传统方案
小样本市场分析 分层贝叶斯 + 信息先验 普通最小二乘(失效)
长尾渠道评估 零膨胀负二项分布 T 检验(功效不足)
实时预算调整 在线变分推断(ADVI) 月频手动优化

局限性与应对

计算复杂度高

对策:使用 numpyro 替代默认采样器,速度提升 3-5 倍业务解释门槛

对策:集成 shapash 可视化库生成可解释报告大数据支持有限

对策:与 Polars 集成处理亿级数据

MetaRobyn(原 Facebook)

核心定位与开发背景

项目起源

  • 开发者:Meta(原 Facebook)增长营销团队,2020 年开源
  • 设计目标:解决传统 MMM 的三大痛点:
    • 渠道共线性(如 Facebook 与 Instagram 广告的强相关)
    • 超参数调优(Adstock 衰减率、饱和曲线的自动化选择)
    • 大规模数据处理(支持 10,000+ 媒体组合的快速建模)

技术基底

  • 语言:R 语言为主,关键性能模块使用 C++ 加速
  • 推断引擎:Stan 贝叶斯框架 + 弹性网络正则化
  • 核心专利:自动化超参数优化的演化算法(USPTO#20210158193)

技术架构解析

模型数学表达

基础公式:

基础公式:

$$Sales_t=\alpha+\sum_{i=1}^n\beta_i\cdot f_i(X_{i,t})+\gamma\cdot Control_t+\epsilon_t$$

其中:

  • $f_i(\cdot)=Adstock(Saturation(X_{i,t}))$:媒体变量的非线性变换
  • $\beta_i$:通过弹性网络(ElasticNet)正则化的系数

核心技术组件

a) Adstock 效应建模

采用延迟衰减 + 饱和效应双阶段处理:

adstock_geometric<- function(x, theta){
filter(x * theta^(0:(length(x)-1)), sides=1)
}

b) 超参数自动优化

使用演化策略(Evolutionary Algorithm)搜索最佳参数组合:

搜索空间:Adstock 衰减率(0-1)、饱和曲线曲率(Hill 函数参数)

评估指标:交叉验证的 NRMSE(标准化均方根误差)

c) 正则化技术

弹性网络(ElasticNet)防止过拟合:$\min_{\beta}\left\{\frac{1}{2N}\|y-X\beta\|^2+\lambda(\alpha\|\beta\|_1+(1-\alpha)\|\beta\|_2^2)\right\}$

分层贝叶斯扩展

地理分层模型(v3.0 新增):

set_parameters(hyperparameters=list(
"lambda_1"=c(0,0.1), # 全国层正则化
"lambda_2"=c(0,0.05) # 城市层正则化
))

核心功能与使用流程

数据准备

输入要求:

  • 时间序列数据(日/周粒度)
  • 媒体变量(支出或曝光量)
  • 非媒体控制变量(价格、促销等)

数据规范:

InputCollect<- robyn_inputs(
dt_input=data,
dt_holidays=holidays,
media_vars=c("tv","social"),
context_vars=c("price")
)

模型训练与选择

OutputModels<- robyn_run(
InputCollect=InputCollect,
iterations=2000,
trials=5, # 并行模型数
plot_folder="output"
)

预算优化

边际 ROI 递减曲线:

allocator<- robyn_allocator(
OutputModels=OutputModels,
scenario="max_response",
total_budget=1000000
)
plot(allocator)

对比其他工具

特性 Robyn LightweightMMM PyMCMarketing
自动化程度 高(自动调参)
可解释性 Shapley 值 SHAP ALE
大数据支持 Spark 集成 JAX 加速 Dask 并行
部署便捷性 R Shiny Flask API FastAPI

最佳实践建议

数据预处理

  • 对媒体支出进行对数变换缓解异方差
  • 使用移动平均平滑突发性事件影响

超参数调优

hyperparameters<- list(
    "alphas" = c(0.3, 0.7),  # 弹性网络混合参数
    "thetas" = c(0.1, 0.5, 0.9)  # Adstock衰减率搜索范围
)

模型诊断

  • 检查变量重要性图,剔除低贡献变量

基于Kaggle数据集的代码示例

数据集:Advertising Sales Dataset (kaggle.com)

基于线性回归的方案

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error
from statsmodels.stats.outliers_influence import variance_inflation_factor

# 加载数据
url = "https://raw.githubusercontent.com/amankharwal/Website-data/master/advertising.csv"
df = pd.read_csv(url)

# 数据探索
print("数据概览:")
print(df.head())
print("\n数据统计描述:")
print(df.describe())
print("\n缺失值检查:")
print(df.isnull().sum())

# 可视化分析
plt.figure(figsize=(12, 6))
sns.pairplot(df, x_vars=['TV', 'Radio', 'Newspaper'], y_vars='Sales', height=5)
plt.suptitle("广告渠道预算与销售额关系", y=1.02)
plt.show()

# 相关系数矩阵
corr_matrix = df.corr()
sns.heatmap(corr_matrix, annot=True, cmap='coolwarm')
plt.title("变量相关系数矩阵")
plt.show()

# 数据准备
X = df[['TV', 'Radio', 'Newspaper']]
y = df['Sales']

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 构建多元线性回归模型
multi_model = LinearRegression()
multi_model.fit(X_train, y_train)

# 模型评估
def evaluate_model(model, X_test, y_test):
    y_pred = model.predict(X_test)
    r2 = r2_score(y_test, y_pred)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    return r2, rmse

# 多元模型评估
multi_r2, multi_rmse = evaluate_model(multi_model, X_test, y_test)

# 构建单变量模型比较
single_model_results = []
for feature in ['TV', 'Radio', 'Newspaper']:
    X_single = df[[feature]]
    X_train_s, X_test_s, y_train_s, y_test_s = train_test_split(X_single, y, test_size=0.2, random_state=42)
    model = LinearRegression().fit(X_train_s, y_train_s)
    r2, rmse = evaluate_model(model, X_test_s, y_test_s)
    single_model_results.append((feature, r2, rmse))

# 共线性检查
vif_data = pd.DataFrame()
vif_data["feature"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(len(X.columns))]

# 结果展示
print("\n多元模型评估结果:")
print(f"R² Score: {multi_r2:.4f}")
print(f"RMSE: {multi_rmse:.4f}")

print("\n单变量模型比较:")
print("{:<10}{:<10}{:<10}".format("Feature", "R²", "RMSE"))
for result in single_model_results:
    print("{:<10}{:<10.4f}{:<10.4f}".format(*result))

print("\nVIF值(方差膨胀因子):")
print(vif_data)

# 模型解释
print("\n模型系数解读:")
coefficients = pd.DataFrame({
    'Feature': X.columns,
    'Coefficient': multi_model.coef_
})
coefficients.sort_values(by='Coefficient', ascending=False, inplace=True)
print(coefficients)

# 残差分析
y_pred = multi_model.predict(X_test)
residuals = y_test - y_pred

plt.figure(figsize=(12, 6))
plt.scatter(y_pred, residuals)
plt.axhline(y=0, color='r', linestyle='--')
plt.title("残差分析图")
plt.xlabel("预测值")
plt.ylabel("残差")
plt.show()

典型输出结果

数据统计描述:
            TV       Radio   Newspaper       Sales
count  200.000000  200.000000  200.000000  200.000000
mean   147.042500   23.264000   30.554100   14.022500
std     85.854236   14.846809   21.778621    5.217457

多元模型评估结果:
R² Score: 0.9055
RMSE: 1.4113

单变量模型比较:
Feature    R²        RMSE      
TV        0.8020    2.0181    
Radio     0.3323    3.2353    
Newspaper 0.0521    3.3540    

VIF值(方差膨胀因子):
    Feature       VIF
0       TV  2.484726
1    Radio  3.197201
2 Newspaper  4.610297

模型系数解读:
    Feature  Coefficient
0       TV     0.045845
1    Radio     0.187994
2 Newspaper     0.001044

关键结论

渠道效果:

  • TV广告效果最显著(系数 045)
  • 广播广告次之(系数 188)
  • 报纸广告影响最小且不显著

模型表现:

  • 多元模型R²达55%,说明模型解释力强
  • 多元模型RMSE(41)显著低于单变量模型

业务建议:

  • 优先分配预算到电视广告
  • 可适当减少报纸广告投入
  • 广播广告可作为辅助渠道

模型改进方向:

  • 考虑非线性关系(如广告的边际效应递减)
  • 增加交互作用项(如不同媒体组合效应)
  • 收集更多数据(如季节性因素、竞品活动)

这个模型可以作为营销预算分配的基础框架,实际应用中需要结合业务场景进行迭代优化。

基于LightweightMMM的方案

步骤1:环境准备与数据加载

# 安装必要库
!pip install lightweight_mmm numpy ro jax

# 导入库
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from lightweight_mmm import lightweight_mmm, preprocessing, plot, optimize_media

# 加载数据
url = "https://raw.githubusercontent.com/amankharwal/Website-data/master/advertising.csv"
data = pd.read_csv(url)

# 数据预览
print(data.head())
print("\n数据统计描述:")
print(data.describe())

步骤2:数据预处理

# 标准化处理(重要!)
media_data = data[['TV', 'Radio', 'Newspaper']].values
media_scaler = preprocessing.CustomScaler(divide_operation=np.mean)
media_scaled = media_scaler.fit_transform(media_data)

# 提取销售额数据
sales = data.Sales.values

# 时间序列可视化
plt.figure(figsize=(12, 6))
plt.plot(sales, label='Sales')
plt.plot(media_scaled[:, 0], label='TV (scaled)')
plt.plot(media_scaled[:, 1], label='Radio (scaled)')
plt.plot(media_scaled[:, 2], label='Newspaper (scaled)')
plt.title("标准化后的广告支出与销售额趋势")
plt.legend()
plt.show()

步骤3:配置并训练MMM模型

# 模型初始化
mmm = lightweight_mmm.LightweightMMM(
    model_name="adstock_saturation",
    adstock_prior_type="geometric", # 几何衰减
    n_media_channels=3
)

# 模型训练
mmm.fit(
    media=media_scaled,
    target=sales,
    media_prior=costs, # 假设各渠道成本(可自定义)
    number_warmup=1000,
    number_samples=1000,
    chains=2
)

# 绘制后验分布
plot.plot_media_channel_posteriors(media_mix_model=mmm)
plt.show()

步骤4:模型诊断与效果评估

# 拟合效果可视化
plot.plot_model_fit(media_mix_model=mmm, target=sales)
plt.show()

# 渠道贡献分解
plot.plot_media_baseline_contribution_area(media_mix_model=mmm)
plt.show()

# ROI分析
roi = mmm.calculate_roi()
print("\n各渠道ROI:")
print(f"TV: {roi[0]:.2f} | Radio: {roi[1]:.2f} | Newspaper: {roi[2]:.2f}")

# 渠道间协同效应检测
synergy_matrix = mmm.media_synergy
plt.matshow(synergy_matrix, cmap='coolwarm')
plt.title("渠道协同效应矩阵")
plt.colorbar()
plt.show()

步骤5:预算优化建议

# 定义预算约束
total_budget = media_scaled.sum() * 1.2 # 总预算增加20%

# 运行优化器
optimized_alloc = optimize_media.optimize_media(
    media_mix_model=mmm,
    budget=total_budget,
    prices=np.array([0.5, 0.3, 0.2]), # 各渠道单位成本
    bounds=np.array([[0.5, 2.0]] * 3) # 各渠道预算调整范围
)

# 可视化优化结果
labels = ['TV', 'Radio', 'Newspaper']
plt.pie(optimized_alloc, labels=labels, autopct='%1.1f%%')
plt.title("优化后的预算分配")
plt.show()

关键输出解释

  • 后验分布图
    • 显示各媒体渠道的效应系数分布
    • 例如:若TV的95%HDI区间为[0.2,0.5],说明TV广告对销售额有显著正向影响
  • 模型拟合效果
    • 实际值(蓝色)与预测值(橙色)的对比
    • 理想情况下两者趋势应高度一致
  • ROI分析
    • 假设输出:TV:3.2|Radio:1.8|Newspaper:0.4
    • 表示每投入1元,分别带来2元、1.8元、0.4元销售额提升
  • 预算优化建议
    • 示例输出可能显示:
      • TV预算占比从35%提升至52%
      • Newspaper预算占比从25%降至15%

注意事项

Adstock参数调整

# 自定义衰减参数
mmm = lightweight_mmm.LightweightMMM(
    adstock_prior=np.array([0.7, 0.5, 0.3]) # 分别为TV/Radio/Newspaper的衰减率
)
协变量添加
# 添加控制变量(如季节性)
extra_features = pd.get_dummies(data['month']).values
mmm.fit(..., extra_features=extra_features)
模型验证
# 时间序列交叉验证
from lightweight_mmm import cross_validation
cv_results = cross_validation.cross_validate(
    model=mmm,
    media=media_scaled,
    target=sales,
    n_splits=5
)

基于PyMCMarketing的方案

步骤1:环境准备与数据加载

# 安装必要库
!pip install pymc-marketing arviz seaborn

# 导入库
import numpy as np
import pandas as pd
import pymc as pm
import pymc.marketing.mmm as mmm
import arviz as az
import matplotlib.pyplot as plt
import seaborn as sns

# 加载数据
url = "https://raw.githubusercontent.com/amankharwal/Website-data/master/advertising.csv"
data = pd.read_csv(url)
print(data.head())

步骤2:数据预处理与可视化

# 标准化广告支出(关键步骤!)
media_data = data[['TV', 'Radio', 'Newspaper']]
scaler = mmm.preprocessing.CustomScaler(divide_operation=np.mean)
scaled_media = scaler.fit_transform(media_data.values)

# 创建时间索引
time_index = pd.date_range(start='2023-01-01', periods=len(data), freq='W')

# 可视化广告趋势
plt.figure(figsize=(12, 6))
for i, col in enumerate(media_data.columns):
    plt.plot(time_index, scaled_media[:, i], label=col)
plt.title("标准化广告支出趋势")
plt.legend()
plt.show()

步骤3:构建贝叶斯MMM模型

with pm.Model() as bayesian_mmm:
    # 先验分布设置
    intercept = pm.Normal("intercept", mu=data.Sales.mean(), sigma=2)
    sigma = pm.HalfNormal("sigma", sigma=1)

    # Adstock参数(Weibull衰减)
    lam = pm.Beta("lambda", alpha=2, beta=3, shape=3)  # 各渠道衰减率
    alpha = pm.Gamma("alpha", alpha=3, beta=1, shape=3)  # 饱和曲线形状

    # Adstock转换
    adstock_media = pm.Deterministic(
        "adstock_media",
        mmm.delayed_adstock(
            scaled_media,
            lam=lam,
            alpha=alpha,
            l_max=8  # 最大延迟8周
        )
    )

    # 媒体效应
    beta = pm.HalfNormal("beta", sigma=1, shape=3)
    media_effect = pm.math.sum(beta * adstock_media, axis=1)

    # 似然函数
    sales = pm.Normal(
        "sales",
        mu=intercept + media_effect,
        sigma=sigma,
        observed=data.Sales.values
    )

    # MCMC采样
    trace = pm.sample(
        2000,
        tune=1000,
        chains=4,
        target_accept=0.95,
        random_seed=42
    )

步骤4:模型诊断与解释

# 收敛性检查
az.plot_trace(trace, var_names=["intercept", "beta", "lambda", "alpha"])
plt.tight_layout()
plt.show()

# 后验总结
summary = az.summary(trace, var_names=["beta", "lambda", "alpha"])
print("后验分布统计量:\n", summary)

# 渠道贡献分解
contributions = trace.posterior["beta"] * trace.posterior["adstock_media"]
total_contribution = contributions.sum(axis=2)

plt.figure(figsize=(12, 6))
az.plot_forest(
    [total_contribution[:, :, i] for i in range(3)],
    model_names=media_data.columns,
    combined=True
)
plt.title("各渠道贡献度分布")
plt.show()

步骤5:预算优化分析

# 定义优化问题
with bayesian_mmm:
    # 生成后验预测
    posterior_predictive = pm.sample_posterior_predictive(
        trace,
        var_names=["sales"]
    )

    # 边际ROI计算
    baseline_sales = np.percentile(posterior_predictive["sales"], 50, axis=0)

    # 预算优化器
    allocator = mmm.OptimalAllocator(
        model=bayesian_mmm,
        media_data=scaled_media,
        prices=np.array([0.5, 0.3, 0.2]),  # 各渠道单位成本
        budget=scaled_media.sum() * 1.2  # 总预算增加20%
    )
    optimal_allocation = allocator.allocate()

    # 可视化优化结果
    labels = media_data.columns
    plt.figure(figsize=(10, 6))
    plt.bar(labels, optimal_allocation, color=['#1f77b4', '#ff7f0e', '#2ca02c'])
    plt.title("最优预算分配方案")
    plt.ylabel("标准化预算")
    plt.show()

关键输出解释

后验分布统计量:
        mean    sd   hdi_3%  hdi_97%
beta[0] 0.482 0.032    0.423    0.541
beta[1] 0.318 0.028    0.267    0.369
beta[2] 0.051 0.019    0.016    0.086
lambda[0] 0.73  0.12    0.56     0.89
lambda[1] 0.61  0.15    0.38     0.82
lambda[2] 0.42  0.18    0.15     0.68
  • TV广告衰减最慢(lambda=0.73)
  • 报纸广告效应衰减最快

渠道贡献度:

  • TV贡献度:2%±3.2%
  • 广播贡献度:8%±2.8%
  • 报纸贡献度:1%±1.9%

预算优化建议:

  • TV预算占比提升至52%
  • 报纸预算占比降至8%

高级分析技巧

非线性效应验证

#绘制TV广告的响应曲线
tv_grid = np.linspace(0, 2, 100) # 标准化后的预算范围
response = mmm.hill_equation(
    tv_grid,
    alpha = trace.posterior["alpha"][:, :, 0].mean(),
    beta = trace.posterior["beta"][:, :, 0].mean()
)

plt.plot(tv_grid, response)
plt.title("TV广告的饱和效应曲线")
plt.xlabel("标准化预算")
plt.ylabel("边际效应")
plt.show()

地理分层建模

# 假设有城市分组数据
geo_groups = np.random.choice(["A", "B", "C"], size=len(data))

with pm.Model() as geo_model:
    # 城市层随机效应
    mu_beta = pm.Normal("mu_beta", 0, 1)
    sigma_beta = pm.HalfNormal("sigma_beta", 1)
    beta_city = pm.Normal("beta_city", mu=mu_beta, sigma=sigma_beta, dims="city")

    # 将媒体效应与城市特征关联
    media_effect = pm.math.dot(scaled_media, beta_city[geo_groups])

注意事项

先验敏感性分析

# 尝试不同先验对比结果
with pm.Model() as sensitivity_model:
    beta = pm.HalfNormal("beta", sigma=0.5) # 更紧凑的先验
    # ...其他模型组件

计算加速

# 使用JAX加速
from pymc.sampling.jax import sample_blackjax_nuts
trace = sample_blackjax_nuts(2000, tune=1000)

业务报告生成

# 自动生成解释报告
from pymc.marketing.report import generate_mmm_report
generate_mmm_report(trace, output_file="report.html")

关键注意事项

  • 数据陷阱
    • 警惕"天然实验"(Natural Experiment)中的混淆变量
    • 处理零值广告支出的正确方法:使用Tobit模型而非简单填充
  • 模型验证
    • 必须进行样本外预测测试(Holdout Test)
    • 对比增量实验(Geo Lift Test)验证系数可靠性
  • 业务解释性
    • 使用SHAP值解释渠道贡献度
    • 制作动态预算模拟器供业务方交互测试

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注