如何优化您的市场预算

是时候收获你辛苦进行市场营销组合模型训练的成果了!

使用DALL-E创建的图像。

市场营销组合模型是了解不同营销渠道对销售的影响的强大工具。通过建立市场营销组合模型,营销人员可以量化每个渠道对整体销售的贡献,并利用这些信息来优化预算分配。

到目前为止,我已经写了一整个系列关于建立市场营销组合模型的文章,但我还欠你一篇关于如何使用这些模型优化媒体支出的文章。今天是你的幸运日,因为在这篇文章中,我将向你展示这个过程!

作者创建的图像。

如果你对市场营销组合模型还不熟悉,你可以从我的入门文章开始:

Python中的市场营销组合模型介绍

哪些广告支出真正推动了你的销售?

towardsdatascience.com

先决条件

在我们能够优化某些东西之前,我们必须先建立一个模型。我们将快速完成这个过程,以便尽快进入本文的主要部分。

数据

首先,让我们加载一些数据。我将使用与我的旧文章中相同的数据集。

import pandas as pdfrom sklearn.model_selection import cross_val_score, TimeSeriesSplitdata = pd.read_csv(    'https://raw.githubusercontent.com/Garve/datasets/4576d323bf2b66c906d5130d686245ad205505cf/mmm.csv',    parse_dates=['Date'],    index_col='Date')X = data.drop(columns=['Sales'])y = data['Sales']

数据集的样子如下:

作者创建的图像。

这个表格背后的逻辑是这样的:想象一下,你在一家销售某种产品的公司工作。你可以看到这个产品的每周销售额在Sales列中。为了提升这些销售额,你在广告上花了一些钱,例如电视广播横幅广告。我们现在想使用广告支出和更多的控制变量(例如星期几、月份、产品价格、天气等)来建模销售额。

作者创建的图像。

模型

构建复杂模型,如XGBoost或深度神经网络,很难解释和优化。我们转而使用一种经过验证的方法,该方法使用可解释的传递效应和饱和效应构建广义可加模型,就像这样:

作者创建的图像。

传递效应和饱和效应块是直观的特征转换:

  • 传递效应模型表示在时间t的媒体支出可能仍然会影响t + 1、t + 2等时间的销售,或者反过来,时间t观察到的销售也会受到t – 1、t – 2等时间的支出的影响。
  • 饱和效应模型表示递减的回报率,例如,将渠道的支出从0欧元增加到100,000欧元会产生很大的影响,但从1,000,000,000增加到1,000,100,000则不再产生影响。

注意:在图形中,控制变量被省略了。这是可以的,因为我们对优化来说并不需要它们 —— 我们无法像我们投入媒体渠道的金钱一样去改变它们。我们唯一能改变的控制变量是价格,但我们在这里假设它是恒定的,并且我们只真正想要优化我们的媒体支出。

因此,该模型的形式为

作者提供的图片。

对于一些尚未定义的函数饱和度累积效应。例如,假设

作者提供的图片。

作者提供的图片。

β是饱和系数,λ是累积效应强度,ℓ是累积效应长度。

我们可以将这些参数视为超参数进行学习,也可以使用贝叶斯方法将它们视为普通的可学习参数。我在我的最后几篇关于营销组合建模的文章中详细介绍了如何获取这些参数,所以我不会进一步展开这个话题。

取而代之的是,让我们假设我们现在有了这些数字,并且我们想要使用它们来创建一个优化的媒体预算分配计划。

优化媒体预算

让我们假设我们之前的营销组合建模尝试给出了以下参数:

N = 200 # 观察数量# 前面的营销组合建模给出了以下参数tv_coef = 10000       # αtv_lags = 4           # ℓtv_carryover = 0.5    # λtv_saturation = 0.002 # βradio_coef = 8000radio_lags = 2radio_carryover = 0.2radio_saturation = 0.0001banners_coef = 14000banners_lags = 0banners_carryover = 0.2banners_saturation = 0.001

现在我们将使用numpy在Python中重新构建营销组合模型。

但是为什么呢?我们已经使用scikit-learn或PyMC构建了一个模型!难道我们不能重用它们吗?

好问题!我们可以使用我们预训练的模型,并将其交给一个通用的优化算法,试图找到最大化销售额的媒体支出输入。然而,这被称为黑盒优化,它的问题是它往往会陷入局部最优解而无法找到全局最优解。

黑盒优化的另一个问题是,算法通常有各种参数,你必须调整这些参数以找到一个好的(但可能不是最优的)解决方案。这就是为什么有些人说这种优化更多地是一门艺术而不是科学。

凸性拯救

如果我们能将问题表述为一个凸优化问题,我们就可以使用诸如cvxpy之类的库来解决它,这些库可以保证找到最佳的媒体预算分配。我已经在这里使用过这个库来解决另一个优化问题。

为了使用凸优化方法,我们的模型必须是凸的或者是凹的,也就是说,在模型前面加一个负号使其成为凸的。

作者提供的图片。

举个例子,如果我们的模型是y = x²,那么它将是一个易于最小化的凹函数。y = 100 – x²将是一个易于最大化的凹模型。

我不会再详细介绍了,只要知道我们的模型实际上是一个凹函数!在我们创建的一个模型中,只要饱和函数的二阶导数为负,那么模型就是凹的。

作者提供的图像。

然而,如果我们使用其他饱和函数,比如Adbudg或其他典型的S型函数,它们可能既不是凹的也不是凸的,这会使优化变得更加困难。

作者提供的图像。

好了,理论就讲到这里。现在只要记住我们的模型是凹的,这很棒,因为我们可以找到一个全局最优解,即产生最大销售额的预算分配。

在Numpy中重新实现我们的模型

首先,让我们定义一些处理传递效应的矩阵。

import numpy as np
tv_carryover_matrix = sum([np.diag(tv_carryover**i*np.ones(N-i), k=-i) for i in range(tv_lags)])
radio_carryover_matrix = sum([np.diag(radio_carryover**i*np.ones(N-i), k=-i) for i in range(radio_lags)])
banners_carryover_matrix = np.eye(N)

我知道这很难理解,所以让我们来看看其中一个矩阵。

作者提供的图像。

这实现了一个强度为0.2、长度为1的传递效应。如果你将这个矩阵乘以一个支出向量,你就可以看到这一点。

作者提供的图像。

搞定这个之后,让我们继续处理饱和。这只是一个涉及exp的简单公式,所以没有问题。

我们可以写成:

sales = (    tv_coef * np.sum(1 - np.exp(-tv_saturation * tv_carryover_matrix @ data["TV"]))     + radio_coef * np.sum(1 - np.exp(-radio_saturation * radio_carryover_matrix @ data["Radio"]))     + banners_coef * np.sum(1 - np.exp(-banners_saturation * banners_carryover_matrix @ data["Banners"])))

这给我们了来自我们的营销努力的销售额总和,因为我们在这里忽略了控制变量。这个数字是3,584,648.73 €,现在我们要通过改变媒体支出来增加它!剧透:结果表明,我们可以将这个数字增加约150万到5,054,070.21 €。哇!仅仅是调整一些数字,效果不错。

作者提供的图像。

在CVXPY中重新实现我们的模型

好的,现在我们准备使用cvxpy来得到最优解。首先,我们定义变量,对于每个通道和每个时间步长,我们有一个变量,所以总共有3 * N = 3 * 200 = 600个变量。

如果没有其他限制,最优解将是将所有变量设置为无穷大,因此我们需要一些约束。这些变量应该都是

  1. 非负的,并且
  2. 我们希望这600个变量的和小于或等于我们过去的花费。

然后,我们希望通过使用numpy函数的cvxpy等效函数来优化我们之前实现的模型,这通常意味着将np替换为cp。我们甚至可以重用之前的carryover矩阵!

import cvxpy as cporiginal_total_spends = data[["TV", "Radio", "Banners"]].sum().sum()# 声明要优化的变量,N=每个渠道的200个tv = cp.Variable(N)radio = cp.Variable(N)banners = cp.Variable(N)# 约束条件,正的花费和有界的总预算constraints = [    tv >= 0,    radio >= 0,    banners >= 0,    cp.sum(tv + radio + banners) <= original_total_spends,]# cvxpy公式化,模型类似于numpy版本problem = cp.Problem(    cp.Maximize(        tv_coef * cp.sum(1 - cp.exp(-tv_saturation * tv_carryover_matrix @ tv)) \        + radio_coef * cp.sum(1 - cp.exp(-radio_saturation * radio_carryover_matrix @ radio)) \        + banners_coef * cp.sum(1 - cp.exp(-banners_saturation * banners_carryover_matrix @ banners))    ), # 类似于numpy模型,所有销售额的和    constraints)

现在,我们可以通过以下方式在很短的时间内解决这个最大化问题

problem.solve()# 输出:# 5054070.207463957

很棒!我们可以通过tv.value, radio.value, banners.value获得最优预算。您可以看到每个渠道每周的花费都是恒定的,这可能不像预期的那么有趣。但最优是最优的,所以我们接受它。

过去我们本可以获得500万而不是360万。虽然这很有趣,但现在毫无意义,可能只会让企业感到不安。然而,我们现在可以使用这种逻辑来优化未来的市场支出!

进一步的约束

就这样,现在您有了一个基本的预算优化工具!而且好的一点是,您还可以建模更多可能来自业务的约束。例如,业务可能会说总的广播花费相当高:

sum(radio.value)# 输出:# 524290.3686626207(= 524,290.37 €)

基于战略原因,业务希望它低于30万欧元。好的,没问题,让我们将其添加到约束集中!

constraints = [    tv >= 0,    radio >= 0,    banners >= 0,    cp.sum(tv + radio + banners) <= original_total_spends,    cp.sum(radio) <= 300000 # 新约束]

就这么简单。我们可以再次运行优化,最终得到稍微降低的优化销售额4,990,178.80 €。但是如果我们现在检查广播花费的总和

sum(radio.value)# 输出:# 299999.9992275703

我们可以看到业务的约束得到了遵守。我们甚至可以添加更多的约束,比如

  • 两个渠道的和应该小于或大于某个数值,或者
  • 在某些周我们不允许任何媒体支出。

您只需使用一些求和、等式或不等式来建模即可。

结论

在本文中,我们首先回顾了市场组合模型的公式。这很重要,因为我们需要重新实现这些模型。幸运的是,由于我们的模型简单易懂,这一点都不成问题。

事实上,我们的模型还有另一个很棒的特点:它是凸的!在这种情况下,销售的最大值是唯一定义的,我们可以通过凸优化来实现它。通常情况下,优化非凸或非凹函数是困难的,需要调整许多超参数,这就是为什么我们没有选择这条路线。

作为压轴之作,我们优化了我们的媒体预算!早该这样了。我们甚至看到了如何将更多约束条件纳入模型,例如某些渠道需要一定的最低或最高预算分配。使用这种方法,您现在可以优化您未来的媒体预算分配。

我们没有讨论的另一种优化是最小化您的媒体预算,并同时确保您达到一定的最低销售额,即尽可能少地花钱,仍然达到您的目标。这也是您可以轻松实现的!相比之下,之前我们拿走了我们所有的钱,并尽可能多地进行销售。

我希望您今天学到了一些新的、有趣的、有价值的东西。感谢您的阅读!

最后一点,如果您

  1. 想要支持我继续写关于机器学习的文章并且
  2. 计划购买小猪AI订阅,

为什么不通过这个链接来做呢 ?这将对我非常有帮助!😊

透明起见,您的价格不会改变,但约一半的订阅费用将直接支付给我。

非常感谢您考虑支持我!

如果您有任何问题,请在 LinkedIn 上联系我!