Robyn / LightweightMMM / 自作 ― MMM実装手段の選び方(2026年版・Meridian登場後の地図)
MMMの実装手段は「Robyn・LightweightMMM・自作」の三択ではなくなりました。LightweightMMMは廃止され、いまの実質的な選択肢はRobyn・PyMC-Marketing・Meridian・自作です。設計思想・実測所感・ケース別の選び方を、再現できる合成データと匿名レンジで整理します。
by Shin
MMMをこれから実装するなら、どのツールを選べばいいのか。結論から言うと、もう「Robyn・LightweightMMM・自作」の三択ではありません。LightweightMMMはGoogleが廃止しており、2026年時点の実質的な選択肢は Robyn・PyMC-Marketing・Meridian・自作 の四つです。本記事では、この四つの設計思想の違い、実測ベースの所感(再現可能な合成データ+匿名レンジ)、そしてケース別の選び方までを、手を動かす視点で整理します。
ここで扱うのは「どれを選ぶか」だけです。MMMそのものの一気通貫の実装手順は MMMを個人で実装する完全手順(Python) に分けてあります。本記事は重複を避け、ツール間で設計が分岐する箇所だけを比較します。
4アプローチの設計思想の違い
四つは「同じMMMを別の書き方で解いている」のではなく、何を最適化対象に置くかという前提からして違います。ここを取り違えると、ツール選びそのものを間違えます。
MMMの数理的な核は二つの非線形変換です。広告の残存効果を表すアドストック(幾何減衰なら )と、出稿の収穫逓減を表す飽和(Hill型なら )。この二つのパラメータ(減衰 、飽和の形 )をどうやって決めるかが、四つの分岐点になります。
| ツール | 言語 | 推定方式 | パラメータの決め方 |
|---|---|---|---|
| Robyn (Meta) | R(Pythonラッパーあり) | Ridge回帰 + 進化的探索 | Nevergrad で多目的最適化(探索) |
| PyMC-Marketing | Python | 完全ベイズ(MCMC) | 事前分布 → 事後分布を推定 |
| Meridian (Google) | Python | 完全ベイズ(MCMC) | 事前分布 → 事後分布、地域階層あり |
| 自作(PyMC等) | Python | 任意(多くはベイズ) | 自分で全部書く |
Robyn は「探索」で解きます。 アドストックや飽和のハイパーパラメータを事前に決め打ちせず、glmnet によるRidge回帰のフィットを、Nevergrad(勾配を使わない進化的アルゴリズム)で多数回まわして良い組み合わせを探します。特徴的なのは目的関数が多目的である点です。予測誤差(NRMSE)だけでなく、出稿シェアと寄与シェアの乖離(RSSD)も同時に小さくしにいきます。「出稿が少ないチャネルに巨大な効果が乗る」ような統計的には合うが実務的にはありえない解を、構造的に弾く設計になっています。Ridge回帰はガウス事前を置いたベイズ回帰のMAP推定と等価なので「ベイズの一種」とも言えますが、実装としては事後分布をサンプリングしない頻度論寄りのアプローチです。
PyMC-Marketing と Meridian は「事前分布」で解きます。 どちらも完全ベイズで、アドストック・飽和・チャネル係数すべてに事前分布を置き、MCMCで事後分布を推定します。違いは思想ではなく射程にあります。PyMC-Marketingは最も柔軟で、変換の差し替え・階層化・カスタム尤度まで自由に組めます(PyPIダウンロード数でも最大)。Meridianは地域階層モデル(geo-hierarchical)、リーチ&フリークエンシー、地域実験(geo-test)による事前分布の校正までを箱に入れて提供し、非技術者向けの対話的HTMLレポートとLooker Studioダッシュボードまで吐きます。要は、PyMC-Marketingは「フレームワーク」、Meridianは「Googleの運用思想込みの完成品」です。
自作は「全部自分で決める」。 PyMCやnumpyroの上に、アドストック関数も飽和関数も事前分布も自分で書きます。最大の自由度と引き換えに、保守も検証も全部背負います。実務というより、手法を本当に理解したいときと、既存ライブラリの仮定が自分のデータに合わないときの選択肢です。
設計の違いがコードに出る場所
同じ「アドストック+飽和+フィット」を、各ツールがどう書かせるか。ここに思想がそのまま出ます。まず、比較の土台になる合成データを作ります。真の値が分かっているデータで回すことで、後述の所感を読者自身が再現・検証できます。
import numpy as np
import pandas as pd
rng = np.random.default_rng(42)
n_weeks = 104
dates = pd.date_range("2024-01-01", periods=n_weeks, freq="W")
# 3チャネルの出稿(規模感の異なる広告費)
spend = pd.DataFrame({
"tv": rng.gamma(2.0, 50_000, n_weeks),
"search": rng.gamma(3.0, 20_000, n_weeks),
"social": rng.gamma(2.5, 15_000, n_weeks),
}, index=dates)
def adstock(x, decay, l_max=8):
w = decay ** np.arange(l_max)
w /= w.sum()
return np.convolve(x, w)[: len(x)]
def hill(x, kappa, slope):
return x**slope / (x**slope + kappa**slope)
# 真のパラメータ(これを各ツールが当てられるかを見る)
true = {
"decay": {"tv": 0.6, "search": 0.2, "social": 0.4},
"kappa": {"tv": 8e4, "search": 4e4, "social": 3e4},
"slope": {"tv": 1.5, "search": 1.2, "social": 1.0},
"beta": {"tv": 3.0e5, "search": 2.2e5, "social": 1.4e5},
}
contrib = np.zeros(n_weeks)
for ch in spend.columns:
a = adstock(spend[ch].to_numpy(), true["decay"][ch])
s = hill(a, true["kappa"][ch], true["slope"][ch])
contrib += true["beta"][ch] * s
trend = np.linspace(0, 1, n_weeks) * 2e5
season = 1e5 * np.sin(np.arange(n_weeks) * 2 * np.pi / 52)
baseline = 8e5
noise = rng.normal(0, 4e4, n_weeks)
y = baseline + trend + season + contrib + noise
df = spend.assign(date=dates, revenue=y).reset_index(drop=True)Robyn(R)— レンジを宣言して探索を投げます。 Robyn は「このパラメータはこの範囲で探して」と幅で渡すのが特徴です。値を当てるのではなく、範囲と目的を与えて探索させる発想がコードに出ます。
library(Robyn)
hyperparameters <- list(
tv_alphas = c(0.5, 3), # 飽和の形(探索範囲)
tv_gammas = c(0.3, 1), # 飽和の半飽和点
tv_thetas = c(0.3, 0.8), # アドストック減衰
search_alphas = c(0.5, 3),
search_gammas = c(0.3, 1),
search_thetas = c(0, 0.3),
social_alphas = c(0.5, 3),
social_gammas = c(0.3, 1),
social_thetas = c(0.1, 0.5)
)
OutputModels <- robyn_run(
InputCollect = InputCollect,
iterations = 2000, # Nevergradの試行回数
trials = 5, # 多目的最適化(NRMSE + RSSD)
ts_validation = TRUE
)PyMC-Marketing(Python)— 変換オブジェクトを組み立てます。 事前分布で解くので、ハイパーパラメータは「探索範囲」ではなく「事前分布」として与えます(既定の弱情報事前を使うなら、ほぼ書かずに済みます)。
from pymc_marketing.mmm import MMM, GeometricAdstock, LogisticSaturation
mmm = MMM(
date_column="date",
channel_columns=["tv", "search", "social"],
adstock=GeometricAdstock(l_max=8),
saturation=LogisticSaturation(),
yearly_seasonality=2,
)
mmm.fit(df, df["revenue"], draws=1000, tune=1000, chains=4, target_accept=0.9)
mmm.plot_components_contributions() # 寄与分解自作(PyMC)— 構造を全部自分で書きます。 同じモデルを生のPyMCで組むと、事前分布の一個一個を自分で決めます。下は骨格のみ(アドストックの再帰スキャンを含む完全版は MMMを個人で実装する完全手順(Python) に置きました。ここでは「自由度の代償」が見える範囲に留めます)。
import pymc as pm
X = df[["tv", "search", "social"]].to_numpy()
y_obs = df["revenue"].to_numpy()
n_ch = X.shape[1]
with pm.Model() as model:
decay = pm.Beta("decay", 3, 3, shape=n_ch) # アドストック減衰
kappa = pm.HalfNormal("kappa", 5e4, shape=n_ch) # 半飽和点
slope = pm.HalfNormal("slope", 1.5, shape=n_ch) # 飽和の形
beta = pm.HalfNormal("beta", 2e5, shape=n_ch) # 係数(非負)
intercept = pm.Normal("intercept", 8e5, 2e5)
# adstock_apply / hill は pytensor で実装(完全版は MMM 実装回に掲載)
media = build_media_contribution(X, decay, kappa, slope, beta)
mu = intercept + media
sigma = pm.HalfNormal("sigma", 5e4)
pm.Normal("revenue", mu=mu, sigma=sigma, observed=y_obs)
idata = pm.sample(1000, tune=1000, chains=4, target_accept=0.9)書く量はそのまま 背負う責任の量 です。Robynは九行のレンジ宣言で済むものを、自作では事前分布の妥当性まで含めて自分で保証します。
精度・自由度・運用コストの比較(実測所感)
ここからは所感の根拠を示します。下表の「合成データ目安」は、前掲の合成データを手元(M2 Mac, CPU)で回したときのおおよその範囲。「実案件レンジ」は、複数のSaaS/EC案件で運用した経験を匿名・レンジで丸めたものであり、特定クライアントの数値ではありません。誇張を避けるため、断定ではなく幅で書きます。
| 観点 | Robyn | PyMC-Marketing | Meridian | 自作 |
|---|---|---|---|---|
| 言語 | R(Python移植進行中) | Python | Python | Python |
| 推定方式 | Ridge + Nevergrad探索 | ベイズMCMC | ベイズMCMC | 任意 |
| セットアップ時間(初回) | 半日〜1日 | 1〜2日 | 2〜4日 | 数日〜 |
| 学習コスト | 中(R+作法) | 中〜高 | 高 | 最高 |
| 実行時間(合成データ目安) | 数分〜十数分 | 5〜15分 | 10〜30分 | 実装次第 |
| 不確実性の出力 | 単一最適解(区間は弱い) | 事後分布で明示 | 事後分布で明示 | 明示可 |
| 実験(geo-test等)での校正 | 標準対応 | 自分で接続 | 標準対応 | 自分で実装 |
| 非技術者向けレポート | 静的PNG | 自前で作る | HTML+Looker標準 | 自前で作る |
| 自由度(仮定の差し替え) | 低〜中 | 高 | 中 | 最高 |
実測所感を三点に絞ります。
第一に、合成データでの「真値の当てやすさ」に劇的な差はありません。 真のパラメータが分かっている素直なデータなら、四つとも寄与分解のオーダーは合います。差が出るのはデータが汚れたときです。共線性が強い(TVとデジタルが連動して動く)期間が長いと、Robynは多目的最適化のおかげで「ありえない配分」を避けやすく、ベイズ勢は事前分布の質に結果が左右されます。逆に、サンプルが短く情報が薄いとき(例:104 週未満)、ベイズの事前分布は安定剤として効き、Robynは探索が広がりすぎて解が不安定になりやすい——というのが実案件での体感です。
第二に、運用コストの大半はモデルではなくデータ整備に乗ります。 きれいな週次データさえあれば、四つのどれでも筋の良いMMMは数十時間で組めます。汚いデータだと、同じ作業が「モデルの問題に見えるデータの問題」のデバッグに溶けます。ツール選びより、入力データの粒度・チャネル定義・売上の整合を先に固める方が、実行時間にも精度にも効きます。
第三に、非技術者に渡すコストが見落とされがちです。 Robynは静的PNG、自作とPyMC-Marketingはレポートを自前で作る前提。Meridianだけが対話的HTMLとLookerダッシュボードを標準で吐きます。部長層に「予算をいくら動かすとどうなるか」を触らせたいなら、この差は実工数で効きます。意思決定者向けの価値の話は なぜMMMが経営にとって重要か に分けてあります。
ケース別の選び方
所感を選択フローに落とします。優先順位の高い条件から順に当てていけばよいでしょう。
Rのチームで、まず一本通したい → Robyn。 既存資産がR、もしくは「統計よりマーケティングの職人技」を重んじるチームに最も馴染みます。レンジを宣言して探索を投げるだけで、実務的に妥当な初版が速く出ます。GeoLiftなど実験での校正も標準。難点は不確実性表現の弱さと静的レポート。
Pythonで、不確実性まで含めて厳密に語りたい → PyMC-Marketing。 事後分布で「効果の幅」を出せるので、止めどき・続けどきの判断に確率で答えられます。柔軟性も最高クラスで、独自の変換や階層構造を足せます。学習コストは中〜高。いま新規にPythonで始めるなら、まずここが基準になります。
Googleチャネル中心で、ダッシュボードまで一気に欲しい → Meridian。 地域階層・実験校正・対話的レポートが箱で揃います。LightweightMMMからの移行先も公式にはここ。ただし強いデータサイエンスの素養が必要で、重いです。
手法を本当に理解したい/既存の仮定が合わない → 自作。 学習目的、または独自の事業構造(極端な季節性・特殊なファネル)でライブラリの仮定が破れるときだけ。それ以外で自作を選ぶのは、たいてい車輪の再発明です。
移行・併用の現実
最後に、実務で実際に起きる「乗り換え」と「併用」の話。一本に決め切る必要はありません。
LightweightMMM → Meridian。 すでにLightweightMMMで運用している場合、公式の移行先はMeridianで、ガイドも出ています。両者はモデル構造が近いぶん移行自体は比較的素直ですが、Meridianはパラメータと複雑さが増すぶんMCMCに時間がかかります。いま動いているものを急いで捨てる必要はありませんが、サポートが切れている以上、次の改修タイミングで移すのが筋です。
Robyn で速く当たりをつけ → PyMC-Marketing で締めます。 実務でよくやる併用。Robynの探索で「効いていそうな配分の当たり」を高速に出し、意思決定の根拠として不確実性が必要な段になってからベイズで組み直します。二つの結果が大きくズレたら、それ自体が「データかモデルのどこかが怪しい」というアラートになります。
実験(geo-test / incrementality)はツールの上位に置きます。 どのツールを使っても、MMMは観測データからの推定でしかありません。地域実験やインクリメンタリティ計測の結果を事前分布や校正の真値として上に乗せると、ツール間の差は相対的に小さくなります。RobynもMeridianもこの校正を標準で持つのは、そこが効くと分かっているからです。逆に言えば、実験を回さずにツール選びだけで精度を語るのは順序が逆です。
MMMの推定結果を「次の予算配分」に変えるところ——ベイズ最適化での配分最適化は ベイズ最適化で広告配分を最適化する実装 に続きます。ツールをどれにしても、出口の最適化は共通の問題になります。
この記事は効果測定の一次資料ベースで書いています。MMMが「答えられること/答えられないこと」を意思決定者の視点から整理した なぜMMMが経営にとって重要か と対で読むと、実装と判断の両側が揃います。