用 Google Meridian 构建端到端贝叶斯营销组合模型:媒体测量、ROI 分析与预算优化
End-to-End Bayesian Marketing Mix Modeling with Google Meridian: Media Measurement, ROI Analysis, and Budget Optimization
本教程使用 Google Meridian 构建完整的贝叶斯营销组合建模工作流,涵盖数据加载、ROI 先验配置、NUTS 采样拟合及收敛性评估。通过 Analyzer API 提取渠道贡献、ROI、边际 ROI、adstock 与饱和曲线等后验指标,并计算渠道间 ROI 比较概率。最后用 BudgetOptimizer 优化固定与灵活预算,生成可分享的 HTML 报告并保存模型复用。
完整工作流把先验设定、后验采样、诊断和预算优化串联为可执行步骤,让营销分析团队能自行量化渠道贡献与 ROI 不确定性。
在本教程中,我们使用 Google Meridian 构建一套完整的贝叶斯营销组合建模工作流。我们首先安装所需的库,验证 GPU 可用性,并探索一个地理层级的营销数据集,其中包含媒体曝光、支出、控制变量、促销、转化、人口和收入。随后,我们将原始列映射到 Meridian 的数据 schema,定义可解释的基于 ROI 的先验,并在使用先验和后验 NUTS 采样拟合模型之前完成模型配置。训练完成后,我们评估收敛性和预测准确度,考察渠道贡献、ROI、边际 ROI、有效性、adstock、饱和度和响应曲线,并使用 Analyzer API 提取自定义后验指标。最后,我们通过优化固定预算和灵活预算、生成可分享的 HTML 报告,并保存拟合后的模型以供复用来结束整个工作流。
!pip install --upgrade -q "google-meridian[and-cuda]"
import numpy as np
import pandas as pd
import altair as alt
import tensorflow as tf
import tensorflow_probability as tfp
from IPython.display import display, HTML
from meridian import constants
from meridian.data import load
from meridian.model import model
from meridian.model import spec
from meridian.model import prior_distribution
from meridian.analysis import analyzer
from meridian.analysis import visualizer
from meridian.analysis import optimizer
from meridian.analysis import summarizer
def show(chart_or_obj, title=None):
if title:
display(HTML(f"<h3 style='font-family:sans-serif'>{title}</h3>"))
display(chart_or_obj)
print("TensorFlow:", tf.__version__)
gpus = tf.config.experimental.list_physical_devices("GPU")
print("GPUs detected:", gpus if gpus else "NONE — sampling will be slow on CPU!")
CSV_URL = (
"https://raw.githubusercontent.com/google/meridian/refs/heads/main/"
"meridian/data/simulated_data/csv/geo_all_channels.csv"
)
df = pd.read_csv(CSV_URL)
print("\nShape:", df.shape)
print("Geos:", df["geo"].nunique(), "| Weeks:", df["time"].nunique())
print("Date range:", df["time"].min(), "->", df["time"].max())
display(df.head())
spend_cols = [c for c in df.columns if c.endswith("_spend")]
spend_share = df[spend_cols].sum().rename("total_spend").reset_index()
spend_share["share_%"] = 100 * spend_share["total_spend"] / spend_share["total_spend"].sum()
display(spend_share)
kpi_by_week = df.groupby("time")["conversions"].sum().reset_index()
show(
alt.Chart(kpi_by_week).mark_line().encode(
x=alt.X("time:T", title="Week"),
y=alt.Y("conversions:Q", title="Total conversions (all geos)"),
).properties(width=700, height=250),
"National KPI over time",
)
我们安装支持 GPU 的 TensorFlow 版本的 Google Meridian,并导入建模、可视化和分析所需的库。我们验证运行时环境,检测可用的 GPU,并加载 Meridian 的模拟地理层级营销数据集。我们还通过查看数据维度、日期覆盖范围、支出分布和全国转化趋势来进行初步探索性分析。
coord_to_columns = load.CoordToColumns(
time="time",
geo="geo",
controls=["competitor_sales_control", "sentiment_score_control"],
population="population",
kpi="conversions",
revenue_per_kpi="revenue_per_conversion",
media=[
"Channel0_impression",
"Channel1_impression",
"Channel2_impression",
"Channel3_impression",
"Channel4_impression",
],
media_spend=[
"Channel0_spend",
"Channel1_spend",
"Channel2_spend",
"Channel3_spend",
"Channel4_spend",
],
organic_media=["Organic_channel0_impression"],
non_media_treatments=["Promo"],
)
media_to_channel = {f"Channel{i}_impression": f"Channel_{i}" for i in range(5)}
media_spend_to_channel = {f"Channel{i}_spend": f"Channel_{i}" for i in range(5)}
loader = load.CsvDataLoader(
csv_path=CSV_URL,
kpi_type="non_revenue",
coord_to_columns=coord_to_columns,
media_to_channel=media_to_channel,
media_spend_to_channel=media_spend_to_channel,
)
data = loader.load()
print("\nInputData loaded. Media tensor shape (geo, time, channel):", data.media.shape)
roi_mu = 0.2
roi_sigma = 0.9
prior = prior_distribution.PriorDistribution(
roi_m=tfp.distributions.LogNormal(roi_mu, roi_sigma, name=constants.ROI_M)
)
model_spec = spec.ModelSpec(prior=prior)
mmm = model.Meridian(input_data=data, model_spec=model_spec)
我们使用 CoordToColumns 将原始数据集列映射到 Meridian 期望的 schema。在加载结构化输入数据之前,我们定义付费媒体、支出、自然渠道、控制变量、处理变量、人口、KPI 和收入相关字段。随后,我们配置基于 ROI 的先验,创建模型规格,并初始化 Meridian 模型。
mmm.sample_prior(500)
mmm.sample_posterior(
n_chains=7,
n_adapt=500,
n_burnin=500,
n_keep=1000,
seed=1,
)
print("Sampling complete.")
model_diagnostics = visualizer.ModelDiagnostics(mmm)
show(model_diagnostics.plot_rhat_boxplot(), "R-hat convergence check (want < 1.05)")
show(
model_diagnostics.plot_prior_and_posterior_distribution(),
"Prior vs. posterior (ROI parameters)",
)
model_fit = visualizer.ModelFit(mmm)
show(model_fit.plot_model_fit(), "Model fit: expected vs. actual outcome")
display(model_diagnostics.predictive_accuracy_table())
media_summary = visualizer.MediaSummary(mmm)
display(media_summary.summary_table())
show(media_summary.plot_channel_contribution_area_chart(),
"Outcome decomposition over time (baseline + channels)")
show(media_summary.plot_contribution_pie_chart(),
"Share of outcome: baseline vs. media")
show(media_summary.plot_spend_vs_contribution(),
"Spend share vs. contribution share (spot over/under-investment)")
show(media_summary.plot_roi_bar_chart(),
"ROI by channel (with credible intervals)")
show(media_summary.plot_roi_vs_effectiveness(),
"ROI vs. effectiveness (bubble = spend)")
show(media_summary.plot_roi_vs_mroi(),
"ROI vs. marginal ROI — mROI drives optimization, not average ROI")
我们从先验中采样,并使用后验 NUTS 采样在多个链上拟合贝叶斯模型。我们使用 R-hat 诊断评估收敛性,比较先验和后验分布,并针对观测结果评估模型拟合度。我们还分析预测准确率、渠道贡献、ROI、边际 ROI 和媒体效果。
media_effects = visualizer.MediaEffects(mmm)
show(media_effects.plot_response_curves(),
"Response curves (incremental outcome vs. spend)")
show(media_effects.plot_adstock_decay(),
"Adstock decay by channel")
show(media_effects.plot_hill_curves(),
"Hill saturation curves by channel")
analysis = analyzer.Analyzer(mmm)
roi_draws = analysis.roi()
roi_np = np.asarray(roi_draws)
channels = list(data.media_channel.values)
roi_table = pd.DataFrame({
"channel": channels,
"roi_mean": roi_np.mean(axis=(0, 1)),
"roi_p05": np.quantile(roi_np, 0.05, axis=(0, 1)),
"roi_p95": np.quantile(roi_np, 0.95, axis=(0, 1)),
})
print("\nPosterior ROI summary (custom, from raw draws):")
display(roi_table)
p_better = (roi_np[..., 1] > roi_np[..., 0]).mean()
print(f"P(ROI Channel_1 > ROI Channel_0) = {p_better:.1%}")
summary_metrics = analysis.summary_metrics()
print("\nsummary_metrics() xarray variables:", list(summary_metrics.data_vars))
inc_outcome = np.asarray(analysis.incremental_outcome())
print("Incremental outcome draws shape (chains, draws, channels):", inc_outcome.shape)
我们检查渠道响应曲线、adstock 衰减和 Hill 饱和行为,以理解收益递减和延续效应。我们使用 Analyzer API 提取后验 ROI 抽样,并计算渠道层面的均值和可信区间。我们还计算概率化的渠道比较,检查汇总指标,并获取增量结果估计。
budget_optimizer = optimizer.BudgetOptimizer(mmm)
optimization_results = budget_optimizer.optimize()
show(optimization_results.plot_budget_allocation(),
"Optimized budget allocation")
show(optimization_results.plot_spend_delta(),
"Recommended spend change per channel")
show(optimization_results.plot_incremental_outcome_delta(),
"Incremental outcome gained by reallocating")
show(optimization_results.plot_response_curves(),
"Response curves with current vs. optimal spend points")
flexible_results = budget_optimizer.optimize(
fixed_budget=False,
target_roi=1.5,
)
show(flexible_results.plot_budget_allocation(),
"Flexible-budget allocation at target ROI = 1.5")
mmm_summarizer = summarizer.Summarizer(mmm)
mmm_summarizer.output_model_results_summary(
"model_results_summary.html", "/content", "2021-01-25", "2024-01-15"
)
optimization_results.output_optimization_summary(
"budget_optimization_summary.html", "/content"
)
print("Reports written to /content/model_results_summary.html "
"and /content/budget_optimization_summary.html")
save_path = "/content/saved_mmm.pkl"
model.save_mmm(mmm, save_path)
mmm_reloaded = model.load_mmm(save_path)
print("Model saved and reloaded from", save_path)
roi_reloaded = np.asarray(analyzer.Analyzer(mmm_reloaded).roi()).mean(axis=(0, 1))
print("Reloaded ROI means:", np.round(roi_reloaded, 3))
print("\n" + "=" * 70)
print("TUTORIAL COMPLETE ✔")
print("Next steps with YOUR data:")
print(" 1. Replace CSV_URL and CoordToColumns with your columns.")
print(" 2. Calibrate per-channel ROI priors with experiment results.")
print(" 3. Check R-hat < 1.05 before trusting any output.")
print(" 4. Use holdout_id in ModelSpec for out-of-sample validation.")
print("=" * 70)
我们在固定预算和目标 ROI 两种场景下优化营销支出。我们可视化推荐分配、支出变化、预期结果增益以及响应曲线上的优化位置。随后我们生成 HTML 报告,保存并重新加载拟合后的模型,并验证恢复的模型能复现相同的 ROI 估计。
总之,我们开发了一套端到端框架,用于衡量媒体表现,并将贝叶斯模型估计转化为实际的营销决策。在解读渠道层面的结果之前,我们通过收敛诊断和预测指标对模型进行了验证,从而帮助我们避免依赖不稳定或具有误导性的估计。我们使用贡献度、ROI、边际 ROI、有效性、延续效应和饱和效应来评估每个渠道,并利用后验抽样来量化不确定性,并以概率方式比较各渠道。随后,我们将这些洞察转化为在固定预算和目标 ROI 情景下的优化预算分配。最后,我们导出了结果并持久化保存了拟合后的模型,使我们能够重复分析、测试新情景,并将该工作流适配到真实业务数据,而无需重新运行计算成本最高的那些步骤。
来源:MarkTechPost(RSS) · marktechpost.com