第三部分:掌握市場組合模型 (MMM) 的實用指南
歡迎來到我的市場組合模型 (MMM) 系列的第三部分!這是一個實用的指南,幫助你掌握 MMM。在這個系列中,我們將討論一些重要的主題,例如模型訓練、驗證、校準和預算優化,所有這些都使用強大的 pymc-marketing Python 套件。不論你是 MMM 的新手還是想提升技能,這個系列將提供實用的工具和見解,幫助你改善市場策略。
如果你錯過了第二部分,可以在這裡查看:
在這個系列的第三部分中,我們將探討如何從市場組合模型中獲取商業價值,具體包括以下幾個方面:
- 為什麼組織想要優化他們的市場預算?
- 我們如何利用市場組合模型的輸出來優化預算?
- 一個使用 pymc-marketing 進行預算優化的 Python 演示。
完整的筆記本可以在這裡找到:
這句名言(我想是約翰·瓦納梅克 (John Wanamaker) 說的?!)說明了市場營銷中的挑戰和機會。雖然現代分析技術已經取得了很大進展,但挑戰依然存在:理解你的市場預算中哪些部分能夠帶來價值。
市場渠道的表現和投資回報率 (ROI) 可能會因為幾個因素而有很大差異:
- 受眾觸及和參與度 — 某些渠道在觸及特定目標受眾方面更有效。
- 獲客成本 — 不同渠道觸及潛在客戶的成本不同。
- 渠道飽和 — 過度使用某個市場渠道可能會導致收益減少。
這種變異性使我們能夠提出關鍵問題,這些問題可以改變你的市場策略:
有效的預算優化是現代市場策略的重要組成部分。通過利用 MMM 的輸出,企業可以做出明智的決策,決定將資源分配到哪裡以達到最大的影響。MMM 提供了各種渠道對整體銷售的貢獻的見解,使我們能夠識別改進和優化的機會。在接下來的部分中,我們將探討如何將 MMM 的輸出轉化為可行的預算分配策略。
2.1 反應曲線
反應曲線可以將 MMM 的輸出轉化為一個綜合的形式,顯示每個市場渠道的銷售如何隨著支出而變化。
反應曲線本身非常強大,允許我們進行假設情境分析。使用上面的反應曲線作為例子,我們可以估算社交媒體的銷售貢獻隨著支出增加而變化的情況。我們還可以直觀地看到收益減少的開始。但如果我們想嘗試回答更複雜的假設情境,例如在固定的總預算下優化渠道級預算,這時線性規劃就派上用場了——讓我們在下一部分探討這個問題!
2.2 線性規劃
線性規劃是一種優化方法,可以用來在給定一些約束的情況下找到線性函數的最佳解。這是一個非常多用途的工具,來自運籌學領域,但並不經常得到應有的認可。它被用來解決排程、運輸和資源分配問題。我們將探討如何使用它來優化市場預算。
讓我們通過一個簡單的預算優化問題來理解線性規劃:
- 決策變數 (x):這些是我們想要估算最佳值的未知數,例如每個渠道的市場支出。
- 目標函數 (Z):我們試圖最小化或最大化的線性方程,例如最大化每個渠道的銷售貢獻的總和。
- 約束條件:對決策變數的一些限制,通常用線性不等式表示,例如總市場預算為 5000 萬英鎊,渠道級預算在 500 萬到 1500 萬之間。
所有約束的交集形成了一個可行區域,這是滿足給定約束的所有可能解的集合。線性規劃的目標是找到可行區域內的點,以優化目標函數。
考慮到我們對每個市場渠道應用的飽和轉換,優化渠道級預算實際上是一個非線性規劃問題。序列最小二乘規劃 (SLSQP) 是一種用於解決非線性規劃問題的算法。它允許同時存在等式和不等式約束,這使它成為我們用例的合理選擇。
SciPy 提供了 SLSQP 的出色實現:
以下示例說明了我們如何使用它:
from scipy.optimize import minimize
result = minimize(fun=objective_function, # 定義你的 ROI 函數
x0=initial_guess, # 初始支出猜測
bounds=bounds, # 渠道級預算約束
constraints=constraints, # 等式和不等式約束
method='SLSQP')
print(result)
從頭開始編寫預算優化代碼是一個複雜但非常有意義的練習。幸運的是,pymc-marketing 團隊已經完成了繁重的工作,提供了一個穩健的框架來運行預算優化情境。在下一部分中,我們將探討他們的套件如何簡化預算分配過程,使其對分析師更易於使用。
現在我們了解了如何使用 MMM 的輸出來優化預算,讓我們看看我們能從上篇文章的模型中獲得多少價值!在這個演示中,我們將涵蓋:
- 模擬數據
- 訓練模型
- 驗證模型
- 反應曲線
- 預算優化
3.1 模擬數據
我們將重用第一篇文章中的數據生成過程。如果你想回顧數據生成過程,可以查看第一篇文章,我們做了詳細的演示:
np.random.seed(10)
# 設置數據生成器的參數
start_date = "2021-01-01"
periods = 52 * 3
channels = ["tv", "social", "search"]
adstock_alphas = [0.50, 0.25, 0.05]
saturation_lamdas = [1.5, 2.5, 3.5]
betas = [350, 150, 50]
spend_scalars = [10, 15, 20]
df = dg.data_generator(start_date, periods, channels, spend_scalars, adstock_alphas, saturation_lamdas, betas)
# 使用最大銷售值縮放 betas - 這樣可以使其與 pymc 的擬合 beta 可比較
betas_scaled = [((df["tv_sales"] / df["sales"].max()) / df["tv_saturated"]).mean(),((df["social_sales"] / df["sales"].max()) / df["social_saturated"]).mean(),((df["search_sales"] / df["sales"].max()) / df["search_saturated"]).mean()]
# 計算貢獻
contributions = np.asarray([round((df["tv_sales"].sum() / df["sales"].sum()), 2),round((df["social_sales"].sum() / df["sales"].sum()), 2),round((df["search_sales"].sum() / df["sales"].sum()), 2),round((df["demand"].sum() / df["sales"].sum()), 2)])
df[["date", "demand", "demand_proxy", "tv_spend_raw", "social_spend_raw", "search_spend_raw", "sales"]]
3.2 訓練模型
我們現在要重新訓練第一篇文章中的模型。我們將以與上次相同的方式準備訓練數據:
- 將數據分為特徵和目標。
- 為訓練和時間外切片創建索引。
然而,由於這篇文章的重點不在於模型校準,我們將包括需求作為控制變數,而不是需求代理。這意味著模型將非常好地校準——雖然這不太現實,但它將給我們一些良好的結果,以說明我們如何優化預算。
# 設置日期列
date_col = "date"
# 設置結果列
y_col = "sales"
# 設置市場變數
channel_cols = ["tv_spend_raw","social_spend_raw","search_spend_raw"]
# 設置控制變數
control_cols = ["demand"]
# 創建數組
X = df[[date_col] + channel_cols + control_cols]
y = df[y_col]
# 設置測試(樣本外)長度
test_len = 8
# 創建訓練和測試索引
train_idx = slice(0, len(df) - test_len)
out_of_time_idx = slice(len(df) - test_len, len(df))
mmm_default = MMM(adstock=GeometricAdstock(l_max=8),saturation=LogisticSaturation(),date_column=date_col,channel_columns=channel_cols,control_columns=control_cols,)
fit_kwargs = {"tune": 1_000,"chains": 4,"draws": 1_000,"target_accept": 0.9,}
mmm_default.fit(X[train_idx], y[train_idx], **fit_kwargs)
3.3 驗證模型
在進行優化之前,讓我們檢查一下我們的模型是否擬合良好。首先,我們檢查真實貢獻:
channels = np.array(["tv", "social", "search", "demand"])
true_contributions = pd.DataFrame({'Channels': channels, 'Contributions': contributions})
true_contributions= true_contributions.sort_values(by='Contributions', ascending=False).reset_index(drop=True)
true_contributions = true_contributions.style.bar(subset=['Contributions'], color='lightblue')
true_contributions
如預期,我們的模型與真實貢獻非常接近:
mmm_default.plot_waterfall_components_decomposition(figsize=(10,6));
3.4 反應曲線
在進行預算優化之前,讓我們看看反應曲線。在 pymc-marketing 套件中,有兩種方式來查看反應曲線:
- 直接反應曲線
- 成本分攤反應曲線
讓我們從直接反應曲線開始。在直接反應曲線中,我們只是創建每個渠道的每週支出與每週貢獻的散點圖。
下面我們繪製直接反應曲線:
fig = mmm_default.plot_direct_contribution_curves(show_fit=True, xlim_max=1.2)[ax.set(xlabel="spend") for ax in fig.axes];
成本分攤反應曲線是比較渠道有效性的另一種方式。當 δ = 1.0 時,渠道支出保持在與訓練數據相同的水平。當 δ = 1.2 時,渠道支出增加了 20%。
下面我們繪製成本分攤反應曲線:
mmm_default.plot_channel_contributions_grid(start=0, stop=1.5, num=12, figsize=(15, 7));
我們還可以更改 x 軸以顯示絕對支出值:
mmm_default.plot_channel_contributions_grid(start=0, stop=1.5, num=12, absolute_xrange=True, figsize=(15, 7));
反應曲線是幫助思考未來市場預算規劃的好工具。接下來,讓我們將它們付諸實踐,運行一些預算優化情境!
3.5 預算優化
首先,讓我們設置幾個參數:
- perc_change:這用於設置每個渠道的最小和最大支出的約束。這個約束幫助我們保持情境的現實性,並意味著我們不會將反應曲線的外推超出模型在訓練中所見的範圍。
- budget_len:這是預算情境的長度(以週為單位)。
我們將首先使用預算情境的所需長度來選擇最近的數據期間。
perc_change = 0.20
budget_len = 12
budget_idx = slice(len(df) - test_len, len(df))
recent_period = X[budget_idx][channel_cols]
recent_period
然後,我們使用這個最近的期間來設置整體預算約束和每週的渠道約束:
# 設置整體預算約束(四捨五入到最近的 £1k)
budget = round(recent_period.sum(axis=0).sum() / budget_len, -3)
# 記錄當前的預算分配按渠道
current_budget_split = round(recent_period.mean() / recent_period.mean().sum(), 2)
# 設置渠道級約束
lower_bounds = round(recent_period.min(axis=0) * (1 - perc_change))
upper_bounds = round(recent_period.max(axis=0) * (1 + perc_change))
budget_bounds = {channel: [lower_bounds[channel], upper_bounds[channel]] for channel in channel_cols}
print(f'整體預算約束: {budget}')
print('渠道約束:')
for channel, bounds in budget_bounds.items():
print(f' {channel}: 下限 = {bounds[0]}, 上限 = {bounds[1]}')
現在是時候運行我們的情境了!我們將相關數據和參數輸入,並獲得最佳支出。我們將其與將總預算按當前預算分配比例(我們稱之為實際支出)進行比較。
model_granularity = "weekly"
# 運行情境
allocation_strategy, optimization_result = mmm_default.optimize_budget(budget=budget,num_periods=budget_len,budget_bounds=budget_bounds,minimize_kwargs={"method": "SLSQP","options": {"ftol": 1e-9, "maxiter": 5_000},},)
response = mmm_default.sample_response_distribution(allocation_strategy=allocation_strategy,time_granularity=model_granularity,num_periods=budget_len,noise_level=0.05,)
# 提取最佳支出
opt_spend = pd.Series(allocation_strategy, index=recent_period.mean().index).to_frame(name="opt_spend")
opt_spend["avg_spend"] = budget * current_budget_split
# 繪製實際支出與最佳支出
fig, ax = plt.subplots(figsize=(9, 4))
opt_spend.plot(kind='barh', ax=ax, color=['blue', 'orange'])
plt.xlabel("支出")
plt.ylabel("渠道")
plt.title("各渠道實際支出與最佳支出")
plt.legend(["最佳支出", "實際支出"])
plt.legend(["最佳支出", "實際支出"], loc='lower right', bbox_to_anchor=(1.5, 0.0))
plt.show()
我們可以看到建議是將預算從數位渠道轉移到電視。但這對銷售有什麼影響呢?
要計算最佳支出的貢獻,我們需要輸入每個渠道的新支出值以及模型中的其他變數。我們只有需求,因此我們將最近期間的平均值輸入進去。我們還將以相同的方式計算平均支出的貢獻。
# 創建包含最佳支出的數據框
last_date = mmm_default.X["date"].max()
new_dates = pd.date_range(start=last_date, periods=1 + budget_len, freq="W-MON")[1:]
budget_scenario_opt = pd.DataFrame({"date": new_dates,})
budget_scenario_opt["tv_spend_raw"] = opt_spend["opt_spend"]["tv_spend_raw"]
budget_scenario_opt["social_spend_raw"] = opt_spend["opt_spend"]["social_spend_raw"]
budget_scenario_opt["search_spend_raw"] = opt_spend["opt_spend"]["search_spend_raw"]
budget_scenario_opt["demand"] = X[budget_idx][control_cols].mean()[0]
# 計算整體貢獻
scenario_contrib_opt = mmm_default.sample_posterior_predictive(X_pred=budget_scenario_opt, extend_idata=False)
opt_contrib = scenario_contrib_opt.mean(dim="sample").sum()["y"].values
# 創建包含平均支出的數據框
last_date = mmm_default.X["date"].max()
new_dates = pd.date_range(start=last_date, periods=1 + budget_len, freq="W-MON")[1:]
budget_scenario_avg = pd.DataFrame({"date": new_dates,})
budget_scenario_avg["tv_spend_raw"] = opt_spend["avg_spend"]["tv_spend_raw"]
budget_scenario_avg["social_spend_raw"] = opt_spend["avg_spend"]["social_spend_raw"]
budget_scenario_avg["search_spend_raw"] = opt_spend["avg_spend"]["search_spend_raw"]
budget_scenario_avg["demand"] = X[budget_idx][control_cols].mean()[0]
# 計算整體貢獻
scenario_contrib_avg = mmm_default.sample_posterior_predictive(X_pred=budget_scenario_avg , extend_idata=False)
avg_contrib = scenario_contrib_avg.mean(dim="sample").sum()["y"].values
# 計算銷售的百分比增長
print(f'銷售增長百分比: {round((opt_contrib / avg_contrib) - 1, 2)}')
最佳支出使我們的銷售增長了 6%!這是非常令人印象深刻的,尤其是我們固定了整體預算!
今天我們看到了預算優化的強大功能。它可以幫助組織進行每月、每季和每年的預算規劃和預測。正如往常一樣,做出良好建議的關鍵在於擁有一個穩健且良好校準的模型。
本文由 AI 台灣 運用 AI 技術編撰,內容僅供參考,請自行核實相關資訊。
歡迎加入我們的 AI TAIWAN 台灣人工智慧中心 FB 社團,
隨時掌握最新 AI 動態與實用資訊!