🙌

Meridianを実装してみた

2025/01/30に公開

1.はじめに

データアナリティクスラボ株式会社の今川です。
 2025年1月29日にGoogleからMMM(メディアミックスマーケティングモデル)であるLMMMの後継モデルのMeridianが一般ユーザー向けにリリースされました。
LMMMとMeridianの違いについては下記のサイトをご覧ください。

https://dalab.jp/mag/methods/google-meridian/

2.MMMとは

MMMの概要を説明します。 MMMとは企業のマーケティング活動の効果を分析するための統計的手法です。この手法は、企業が実施したさまざまなマーケティング活動(例:TVや新聞による広告、販促活動、販売促進活動など)が売上やその他の業績指標に与える影響を評価するために使用されます。
MMMの主な目的は、企業が行ったマーケティング活動の効果を定量的に評価し、どの活動が最も効果的であるかを明らかにすることです。これにより、企業はマーケティング予算をどのチャネルや活動に最適化するかを決定し、ROI(投資収益率)を最大化するための意思決定をサポートできます。

3.実装

3-1.データ

今回使用したデータはMeridianが公開しているサンプルデータを使用しました。

geo time kpi revenue_per_kpi population control_0 control_1 media_0 media_1 media_2 media_spend_0 media_spend_1 media_spend_2 reach_0 reach_1 frequency_0 frequency_1 rf_spend_0 rf_spend_1
geo_0 2021-01-25 75.625134 3.14 3186.0 10.359339 11.625985 14.0 7.0 10.0 2.764052 1.400157 1.978738 3176.0 3040.0 11.820262 5.000786 2.764052 1.400157
geo_0 2021-02-01 44.738652 3.14 3186.0 6.986378 8.251330 16.0 14.0 0.0 3.240893 2.867558 0.022722 3098.0 3224.0 7.893690 14.204466 1.978738 3.240893
geo_0 2021-02-08 36.702576 3.14 3186.0 5.479311 5.400156 10.0 4.0 4.0 1.950088 0.848643 0.896781 3187.0 2902.0 12.337790 1.886389 2.867558 0.022722
geo_0 2021-02-15 0.970955 3.14 3186.0 8.960030 -1.043606 7.0 6.0 12.0 1.410599 1.144044 2.454274 3095.0 2985.0 7.750442 2.243214 1.950088 0.848643
geo_0 2021-02-22 56.584411 3.14 3186.0 5.675903 6.791143 9.0 6.0 7.0 1.761038 1.121675 1.443863 2990.0 3041.0 2.483906 5.052993 0.896781 1.410599

これらの変数について説明していきます。

変数名 説明
geo 地域
time 週次
kpi キーパフォーマンス指標(KPI)
revenue_per_kpi KPI単位あたりの平均収益。収益がKPIの場合、必ずしも必要ではないが、KPIが収益でない場合に収集する必要がある
population 対象地域の人口
control コントロール変数。KPIやメディアメトリックに因果的に影響を与える要因(例:Google検索ボリューム(GQV))
media 各メディアと期間におけるインプレッション数
media_spend 各メディアチャネルと期間における広告支出
reach 各メディアチャネルで広告に接触したユニークな個人の数(期間ごとのデータ)
frequency 広告に接触した回数の平均。リーチをインプレッション数で割った値で、各期間ごとの接触頻度を示す
rf_spend RFメディアの広告費

データの粒度と推奨事項

地理的粒度

  • 最適なデータ粒度: 地域レベル(geoレベル)
    • 地域ごとの違いを考慮し、ROI推定を精度高く行うためには地域別のデータが重要です。
    • 低ボリュームの地域は除外する: データが少ない地域はモデルの推定に悪影響を与える可能性があるため、除外するのが推奨されます。

時間的粒度

  • 最適なデータ粒度: 週単位
    • 週単位のデータは、変動の度合いとノイズのバランスが良く、安定した推定結果を得ることができます。
    • 日次データ:
      • 日次データを使用することも可能ですが、処理時間が長くなる可能性があります。
      • 非収束や広範な信頼区間が発生することがあります。
    • 月次データ:
      • 月次データでは結果が広範囲になりやすいため、精度が低下することがあります。

メディア粒度

  • 最適なメディアチャネル数: 20以下
    • メディアチャネルが多すぎると、十分な変動とデータの量が確保できなくなる可能性があります。
    • 低予算のメディアは他のメディアチャネルと統合して使用することで、ROI推定が安定します。

データ収集の期間

  • geoレベルデータ:
    • 最低でも2年分の週単位のデータを収集することが推奨されます。
    • 地理レベルでデータを収集することで、詳細な分析が可能になります。
  • 全国レベルデータ:
    • 最低でも3年分の週単位のデータを収集することが推奨されます。
    • 月次データがある場合、3年以上のデータが必要です。

3-2.インポート

Meridian を使用するには、Python 3.11 または 3.12が必要です。Meridian をインストールするには、次のコマンドを実行します。

pip install google-meridian==1.0.0
import numpy as np
import pandas as pd
import tensorflow as tf
import tensorflow_probability as tfp
import arviz as az

import IPython

from meridian import constants
from meridian.data import load
from meridian.data import test_utils
from meridian.model import model
from meridian.model import spec
from meridian.model import prior_distribution
from meridian.analysis import optimizer
from meridian.analysis import analyzer
from meridian.analysis import visualizer
from meridian.analysis import summarizer
from meridian.analysis import formatter

データのロード

列名を変数にマップします。

coord_to_columns = load.CoordToColumns(
    time="time",
    geo="geo",
    controls=["control_0", "control_1"],  # 競合などのコントロール変数
    population="population",
    kpi="kpi",
    revenue_per_kpi="revenue_per_kpi",
    media=[
        "media_0",
        "media_1",
        "media_2"
    ],
    media_spend=[
        "media_spend_0",
        "media_spend_1",
        "media_spend_2"
    ],
    reach = [
        "reach_0",
        "reach_1"
    ],
    frequency = [
        "frequency_0",
        "frequency_1"
    ],
    rf_spend=[
        "rf_spend_0",
        "rf_spend_1"
    ],
    organic_media=None,  # オーガニックチャネルがない場合
    non_media_treatments=None  # プロモーションがない場合
)

メディア変数等をHTML出力に表示するための指定のチャネル名にマップします。

correct_media_to_channel = {
    "media_0": "Channel_0",
    "media_1": "Channel_1",
    "media_2": "Channel_2"
}
correct_media_spend_to_channel = {
    "media_spend_0": "Channel_0",
    "media_spend_1": "Channel_1",
    "media_spend_2": "Channel_2"
}
correct_rf_spend_to_channel = {
    "rf_spend_0": "Channel_3",
    "rf_spend_1": "Channel_4"
}
correct_reach_to_channel = {
    "reach_0": "Channel_3",
    "reach_1": "Channel_4"
}
correct_frequency_to_channel = {
    "frequency_0": "Channel_3",
    "frequency_1": "Channel_4"
}

csvデータをロードします。

DIR = "/content/drive/MyDrive/Colab Notebooks/Meridian/"
pdf_path = DIR + "data/sample_data_media_and_rf.csv"
loader = load.CsvDataLoader(
    csv_path=pdf_path,  # データのパス
    kpi_type="non_revenue",  # KPIが収益でない場合
    coord_to_columns=coord_to_columns,
    media_to_channel=correct_media_to_channel,
    media_spend_to_channel=correct_media_spend_to_channel,
    reach_to_channel=correct_reach_to_channel,
    frequency_to_channel=correct_frequency_to_channel,
    rf_spend_to_channel=correct_rf_spend_to_channel,
)

data = loader.load()

3-3.モデルの構築

Meridianは、ベイジアンフレームワークとマルコフ連鎖モンテカルロ (MCMC) アルゴリズムを使用して事後分布からサンプリングします。今回はすべてのメディアチャネルのROI事前分布は同一として実装します。

# ROI事前分布:Lognormal(0.2, 0.9)
roi_mu = 0.2     # 各メディア・チャネルのROIの事前評価のための平均値
roi_sigma = 0.9  # 各メディア・チャネルのROIの事前評価のための分散
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)

sample_prior()およびsample_posterior()メソッドを使用して、モデルパラメータの事前分布と事後分布からサンプルを取得します。私の場合、20分弱ほどかかりました。公式では最低 1 つのGPUを推奨しています。

# tqdm
from tqdm.notebook import tqdm
import time

with tqdm(total= 5*1000, desc="Training Progress") as pbar:
  def update_progress(current_iteration, total_iterations):
      pbar.update(current_iteration)

  # Remove progress callback
  mmm.sample_posterior(
      n_chains=5,
      n_adapt=500,
      n_burnin=500,
      n_keep=1000,
      # progress_callback=update_progress
  )
  pbar.update(5*1000)

3-4.モデルの評価

  • モデルを構築した後、収束を評価し、必要に応じてモデルをデバッグし、モデルの適合性を評価する必要があります。以下のコードを実行することで、r-hat値を知れます。r_hatが1.1以下の場合は一般的に収束していると言えます。
model_diagnostics = visualizer.ModelDiagnostics(mmm)
model_diagnostics.plot_rhat_boxplot()

  • 予想収益と実際の収益を比較して、モデルの適合性を評価します。
model_fit = visualizer.ModelFit(mmm)
model_fit.plot_model_fit()

  • 地域レベルで予測値と実測値の比較グラフを作成して、モデルの適合度を評価します。公式では、最も大きな地域のみを表示することをおすすめしています。
model_fit = visualizer.ModelFit(mmm)
model_fit.plot_model_fit(n_top_largest_geos=2,
                         show_geo_level=True,
                         include_baseline=False,
                         include_ci=False)

3-5.モデルの結果をHTMLで出力する

Summarizerモデルオブジェクトを使用してクラスを初期化します。次に、ファイル名、ファイルパス、開始日、終了日を渡して、output_model_results_summaryその期間のサマリーを実行し、指定したファイルに保存します。

mmm_summarizer = summarizer.Summarizer(mmm)

file_output = DIR + "data"
start_date = '2021-01-25'
end_date = '2024-01-15'
mmm_summarizer.output_model_results_summary('summary_output.html', file_output, start_date, end_date)

適合度の統計データをチェックできます。Meridianでは、決定係数、平均絶対誤差率(MAPE)、加重平均絶対誤差率(wMAPE)などのモデル適合度の統計データを確認できます。また、Meridian では、収益(またはKPI)の実測値と予測値を比較できる視覚的なデータも確認できます。

収益またはKPIへの貢献度のセクションには、メディアおよびメディア以外のアクティビティが、収益またはKPIのパフォーマンスにどの程度の効果を与えたのかを分析したデータが表示されます。

費用対効果のセクションでは、チャネルの費用対効果を詳細に分析して、パフォーマンスの高いチャネルを特定できます。各チャネルの費用対効果、限界費用対効果、有効性を比較できます。

3-6.予算の最適化を実行し、最適化レポートを生成する

予算配分のために実行するシナリオを選択できます。デフォルトのシナリオでは、特定の予算に対してチャネル全体にわたる最適な配分を見つけて、投資収益率(ROI)を最大化します。
BudgetOptimizerクラスをインスタンス化し、optimize()カスタマイズせずにメソッドを実行して、デフォルトのライブラリの固定予算シナリオを実行し、ROIを最大化します。自分は30分ほどかかりました。

budget_optimizer = optimizer.BudgetOptimizer(mmm)
optimization_results = budget_optimizer.optimize()

最適化レポートを出力します。

optimization_results.output_optimization_summary('optimization_output.html', file_output)

最適化シナリオのプランのセクションには、最適化された予算、費用対効果、収益またはKPIの増分が表示されます。

推奨の予算配分のグラフには、各チャネルの最適化された費用の変化が表示されます。このセクションには、現在の費用と最適化された費用の間で、各チャネルの収益または KPI の増分にどの程度の差異が生まれるのかも表示されます。

チャネル別の最適化された応答曲線のセクションでは、応答曲線上に、チャネル単位の現在の費用と最適化された費用が表示されます

3-7.フリークエンシーの最適化

MMMでリーチとフリークエンシーを使用する主な目的の1つは、指定した期間におけるチャネルの最適なフリークエンシーを見つけることです。各地域のターゲットオーディエンスに広告を表示する平均回数として最適な値や、費用対効果を最大化するために必要な時間を知ることができます。

reach_and_frequency = visualizer.ReachAndFrequency(mmm)
reach_and_frequency.plot_optimal_frequency()

4.参考文献

DAL Tech Blog

Discussion