贝叶斯 MMM 的 PyMC3 实现:较差的后验推理

机器算法验证 时间序列 贝叶斯 pymc 营销
2022-04-14 15:13:57

谷歌在 2017 年发布了一份关于媒体混合建模 (MMM) 的白皮书;vanilla MMM(成立于 1960 年代)使用多元回归。这是一个很好的机制来了解您的哪个营销渠道具有最大的投资回报率。但是,它很简单,因为它没有考虑延迟效应(今天花费 1000 美元可能会在 3 天后带来最多的客户)或市场饱和(1000 美元可能会将您的信息传达给 90% 的消费者,但需要 2000 美元才能达到由于收益递减,95% 和指数更多接近 100%。)

谷歌的论文试图使用贝叶斯方法来解释延迟效应和市场饱和。请注意,本文实现了基于 Stan 的采样器以及自定义 Gibbs/Slice 采样器;他们发现两者都可以有效地采样,但后者要快得多。(Stan 代码可以在附录中找到。)我对 Stan 不是很精通,所以我决定在 PyMC3 中实现模型。

延迟效应和市场饱和转变:

import theano.tensor as tt

def geometric_adstock(x, theta, alpha,L=12):
    w = tt.as_tensor_variable([tt.power(alpha,tt.power(i-theta,2)) for i in range(L)])
    xx = tt.stack([tt.concatenate([tt.zeros(i), x[:x.shape[0] -i]]) for i in range(L)])
    return tt.dot(w/tt.sum(w), xx)

def saturation(x,s,k,b):
    return b/(1 + (x/k)**(-s))

该模型:

import arviz as az
import pymc3 as pm

with pm.Model() as m:
    #var,      dist, pm.name,          params,  shape   
    alpha = pm.Beta('alpha',           3 , 3,   shape=X.shape[1]) # retain rate in adstock 
    theta = pm.Uniform('theta',        0 , 12,  shape=X.shape[1]) # delay in adstock
    k     = pm.Beta('k',               2 , 2,   shape=X.shape[1]) # "ec" in saturation, half saturation point
    s     = pm.Gamma('s',              3 , 1,   shape=X.shape[1]) # slope in saturation, hill coefficient
    beta  = pm.Normal('beta',          0,  1,   shape=X.shape[1]) # regression coefficient 
    tau    = pm.Normal('intercept',    0,  5                    ) # model intercept
    noise = pm.InverseGamma('noise',   0.05,  0.005             ) # variance about y 
    

    computations = []
    for idx,col in enumerate(X.columns):
        comp = saturation(x=geometric_adstock(x=X[col].values, 
                                                alpha=alpha[idx],
                                                theta=theta[idx],
                                                L=12),
                          b=beta[idx],
                          k=k[idx],
                          s=s[idx])
        
        computations.append(comp)

    
    y_hat = pm.Normal('y_hat', mu= tau + sum(computations),
                  sigma=noise, 
                  observed=y)
    
    trace1 = pm.sample(chains=4)

请注意,饱和效果应用于延迟效果的输出。这是我困惑的症结所在。

MMM 后部 1

对于模型的所有复杂性,它基本上是在预测截距,其偏差几乎可以忽略不计。这是有道理的。饱和函数返回 0 到 1 之间的输出,有点像 sigmoid 函数,由回归系数 b 缩放。

下面和谷歌作者在他们的模型中使用的唯一区别是季节性控制。我还没有实现它们,但我对上述结果持谨慎态度;季节性控制将根据需要简单地偏离截距以实现准确性,从而否定延迟效应和饱和函数的重要性。我很好奇作者的期望以及他们如何取得不同的成就。

我不是凭空写下这段代码的。事实上,HelloFresh 的工程师对延迟效果和饱和功能做出了贡献。看:

带有残留和形状效应的媒体混合建模的贝叶斯方法 - Google Research(白皮书)

使用 PyMC3 进行贝叶斯媒体混合建模,既有趣又赚钱 通过卢卡菲亚斯基 | HelloTech (hellofresh.com)

Michael Johns 和Zhenyu Wang 的媒体混合建模的贝叶斯方法 - YouTube(评论文章的视频)

有人可以解释如何取得更好的结果吗?执行过程中是否存在缺陷?

最后,使用的数据来自Kaggle

编辑:经过一些进一步的审查/思考,我在想的是 - 衰减函数需要每天输入一些广告支出。假设最大滞后设置为 4(作者使用 12),在 2 天达​​到峰值效果。因此,今天预测的新客户/销售将采用今天的支出和前 3 天的支出,并通过衰减函数,该函数将根据其接近峰值效应(2 天)对每一天进行加权。接下来,此输出被传递给说明收益递减的饱和函数。它输出一个介于 0 和 1 之间的数字。最后,此输出按回归系数(针对该广告渠道)进行缩放,并通过截距进行增强。

这么大的图景,这个模型的主力是回归系数(类似于多元回归),唯一的区别是对于任何一天,回归系数不直接应用于 X(营销渠道支出),而是一个缩放抑制回归系数大小的因素。

考虑到这一点,我现在看到回归系数的先验非常重要。也许我的先验过于具体?

一般来说,我想要一些反馈——我读对了吗?该模型是否只是回归系数的动态缩放,由模型截距移动?

1个回答

更新:

我尝试了您的数据集并得到了类似的结果。但是,我注意到噪声的估计(误差项的方差)相对较大。根据我们的模型,它表明噪声解释了这个特定数据集的大部分变化。至少,对于具有相同模型的另一个数据集,我得到了不同的结果(见下文)。

如果您为噪声选择不同的先验(例如,具有较小上限的均匀分布),那么您可能会得到具有更显着偏差的结果(尽管可能会导致问题)。


我遇到了同样的问题:预测值与截距的偏差可以忽略不计。但是,当我将“观察到”转移到一个数组时,我设法解决了这个问题,如下所示:

 y_hat = pm.Normal('y_hat', mu= tau + sum(computations),
                  sigma=noise, 
                  observed=df.y.values )

这是我的数据的后验。这实际上是一条直线,就像你的情节一样。(我没有包括时间趋势)。我不会说它做得很好(至少我们在这里有一些偏差)。也许我的数据集不够大。

在此处输入图像描述

也许,您可以尝试运行一个简单的分布式滞后模型来检查您的因变量和解释变量之间是否存在很强的关系。