knots 引数の仕組み
メリディアンでは、時間効果をモデリングするために時変切片のアプローチを使用します(スプライン(数学)、Wikipedia。Ng、Wang、Dai。2021 年)。このアプローチでは、各期間( \(T\))の時間効果( \(\mu = [\mu_1, \dots, \mu_T]\) )をモデル化します(週レベルの 3 年間の MMM の期間は \(52 \times 3\) )。 \(T\) の時間効果は、次の関係性を使用して、多数のパラメータ( \(T\)よりは少ない可能性があります)によってモデル化されます。
\[\mu = W \ast b\]
各記号は以下を表します。
\(\mu\) は \(1 \times T\) となり、各時間効果 \(t=1, \dots ,T\)を表します。 \(W\) は \(T \times K\) (決定的重み行列)です。
\(b\) (メリディアンでは
knot_valuesと呼ばれます)は \(K \times 1\)( \(K \leq T\))です。
ベイズ事後分布の推論は \(b\)で行われ、重み行列 \(W\)に即し、\(\mu\) を用いて変換されます。ノットの数( \(K\))は、ユーザー入力によって決まります。重み行列 \(W\) は、期間と 2 つの隣接するノット間の L1 距離によって決まります。
L1 距離によって重み行列がどのように決定されるかを明確にするために、期間\(9\)について考えてみます。この期間では、2 つの隣接するノットが \(6\) と \(11\)にあるとします。期間 \(9\) とノット \(11\) の L1 距離は \(2\)です。期間 \(9\) とノット \(6\) の L1 距離は \(3\)です。そのため、 \(6\)のノットの重み付けは \(0.4 = 1 - \frac{3}{2+3} \) 、 \(11\) のノットの重み付けは\(0.6 = 1 - \frac{2}{2+3} \)となります。この 2 つの隣接するノットの加重平均値によって、 \(\mu_9\)の値が決まります。
knots < n_times の場合は、ある程度の次元削減が発生します。期間 n_times は、n_times 未満のパラメータでモデル化されます。重み付け関数によって、期間の組み合わせが決まります。
メリディアンのデフォルトのノット設定
デフォルトでは、ModelSpec の knots 引数は None に設定され、enable_aks は False に設定されます。knots = None の場合、メリディアンは、地域レベルと国内レベルのどちらのモデルを実行しているかに基づいて、ノット数を自動的に設定します。
- 地域レベルのモデルのデフォルトは
knots = n_timesに設定されます。地域レベルのモデルには、期間ごとに複数の観測値(地域ごとに 1 つ)があるため、knots = n_timesを設定しても数学的に識別可能です。これにより、モデルは時間とともに変化する交絡要因を調整するうえで最大限の柔軟性を発揮でき、因果推定値のバイアスを最小限に抑えることができます。分散が大きすぎて許容できないと感じた場合は、ノットの数を手動で減らすか、AKS を使ってみることをご検討ください。 - 国内レベルのモデルのデフォルトは
knots = 1に設定されます。国内レベルのモデルには、期間ごとに 1 つの観測値しかありません。knots = n_timesを使用したモデルは完全に飽和状態になり、識別不可能になります。knots = 1を設定すると、時間の影響は 1 つの定数項だけに絞られるため、分散の小さいシンプルなベースラインとして機能します。自由度が許す限り、季節性を捉えられるよう、ノット数を徐々に増やすこともできます。
ノットの自動選択(AKS)
ノットを選択するのは容易ではありません。信頼性の高い因果推論のためにバイアスと分散のトレードオフのバランスを取り、ビジネス コンテキストを組み込んでメディアと KPI に対する時間の効果を正確に表し、反復的なモデル チューニング プロセスの一部として、試行錯誤を繰り返す必要があるためです。
これらの課題に対処できるよう、メリディアンではノットの自動選択(AKS)機能を提供しています。ほとんどのユースケースでは、まずは AKS を試してみることをおすすめします。ノットの選択プロセスが自動化されるため、試行錯誤の手間を省くことができます。
ノットの自動選択を使用する
メリディアン モデルで AKS を有効にするには、ModelSpec で enable_aks パラメータを True に設定します。
from meridian.model import model
from meridian.model import spec
# `data` is an `InputData` object
model_spec = spec.ModelSpec(enable_aks=True)
mmm = model.Meridian(input_data=data, model_spec=model_spec)
ModelSpec で AKS を有効にすると、メリディアンが選択したノットのリストを取得できます。
knot_info = mmm.knot_info
selected_knots = knot_info.knot_locations
ModelSpec で AKS を有効にした後は、選択したノットが、データのコンテキストで意味を成しているかどうかを確認することをおすすめします。
AKS の仕組み
この機能は、ノットの自動選択によるスプライン回帰の手法から着想を得たものですが、メリディアン固有の機能強化がいくつか含まれています。基本的に、このアルゴリズムは線形スプライン基底を使用して、時間(ノット)とスケーリングされた結果変数の関係をモデル化します。手法には後退消去法を採用しており、すべての潜在的なノットを含めた状態から始めて、モデルの適合度を向上させないノットを順次削除します。この適合性は、不要な複雑さ(ノット数など)についてモデルにペナルティを課す正則化手法を使用して評価されます。メリディアンのバージョンにおける主な変更点は、地域を考慮したペナルティです。これにより、データ内の地域の数が増加するにつれて、ノット数を増やすように機能します。地域が増えるほど、各期間で利用できるデータも増えるためです。モデルがトレンドと季節性のパターンを正確に捉えられるように、赤池情報量規準(AIC)を使用して最終的な最適なノットの組み合わせが決定されます。
上級: AKS ペナルティ パラメータを調整する
AKS アルゴリズムは、場合によって非常に多くのノットを選択する可能性があります。国内レベルのモデルや地域数が少ないモデルでは、各期間で利用できるデータが限られているため、これが問題になる可能性があります。そのような場合にノット数が異常に多いことが確認された場合は、automatic_knot_selection() 関数を呼び出してアルゴリズムを調整し、ノット数を抑えることができます。
automatic_knot_selection の base_penalty 引数を変更して、正則化ペナルティの検索範囲を指定できます。デフォルトでは np.geomspace(0.1, 100, 100) に設定されています。つまり、デフォルトの基本ペナルティ検索範囲は 0.1 から 100 までとなります。独自のペナルティ パラメータのリストを指定することもできます。ペナルティ パラメータを大きくすると、正則化が強くなり、その結果、選択されるノット数が減少します。たとえば、np.geomspace(10, 200, 100) を使用して、検索範囲を 10 から 200 に設定できます。
min_internal_knots 引数と max_internal_knots 引数を使用して、考慮するノット数の範囲を設定することもできます。
import numpy as np
from meridian.model import knots
from meridian.model import model
from meridian.model import spec
# `data` is an `InputData` object; same as what will be passed to
# `model.Meridian`
aks = knots.AKS(data)
# Modify `base_penalty`, `min_internal_knots`, `max_internal_knots`
# arguments to encourage fewer knots
base_penalty = np.geomspace(10, 200, 100)
knot_locations = aks.automatic_knot_selection(base_penalty=base_penalty,
min_internal_knots=2,
max_internal_knots=10).knots
# You must use the 'knots' argument and NOT set 'enable_aks=True'.
model_spec = spec.ModelSpec(knots=knot_locations)
# `input_data` argument is the same `data` passed to `knots.AKS(data)`
mmm = model.Meridian(input_data=data, model_spec=model_spec)
上級: ハイブリッド ノット選択
AKS と独自のドメイン知識を組み合わせた、ハイブリッド アプローチを採用することもできます。これにより、ホリデー シーズンやモデルの更新といった既知のビジネス ロジックを反映させつつ、残りの必要なノットの選択を AKS アルゴリズムに任せることができます。
ハイブリッド アプローチを実現するには、主に次の 2 つの方法があります。
- 選択したノットを手動で調整する: まず AKS を実行してどのノットが選択されたかを確認し、最終モデルを実行する前に、必要に応じてノットを手動で追加または削除します。
- 必須ノットを指定する: 必須ノットのリストを AKS アルゴリズムに渡すと、それ以外に必要なノットが自動的に選択されます。
選択したノットを手動で調整する
ビジネスの背景情報に合わせて、ノットを手動で追加または削除できます。たとえば、推定値のバイアスを減らすためにホリデー シーズン中にノットを追加したり、分散を減らすためにオフシーズン中にノットを削除したりできます。手動でのノット調整の詳細については、バイアスと分散のトレードオフをご覧ください。
以下のコード スニペットでは、選択されたノットを抽出し、時点 52 に新しいノットを追加して、それをもとに新しい ModelSpec を作成する流れを例として示しています。
import numpy as np
from meridian.model import model
from meridian.model import spec
# `data` is an `InputData` object
model_spec_with_aks = spec.ModelSpec(enable_aks=True)
mmm_with_aks = model.Meridian(input_data=data, model_spec=model_spec_with_aks)
# Retrieve selected knots from a model run with enable_aks=True
selected_knots = mmm_with_aks.knot_info.knot_locations
# Add a knot at time point 52
modified_knots = np.append(selected_knots, 52)
model_spec = spec.ModelSpec(knots=modified_knots)
mmm = model.Meridian(input_data=data, model_spec=model_spec)
AKS に必須ノットを指定する
必須ノットの位置をアルゴリズムに直接伝えることで、AKS と独自のドメイン知識を組み合わせることができます。これを行うには、automatic_knot_selection() メソッドを呼び出し、required_knots のイテラブルを指定します。アルゴリズムはそれらを踏まえたうえで、モデルの適合度と複雑さのバランスを取りながら、時間の効果を説明するために必要な追加のノットを自動的に選択します。
このアプローチは、特定の既知の時点には必ずノットを配置しつつ、それ以外の部分は AKS に任せたいときに役立ちます。例:
- 「毎年 7 月は当社にとって特別な時期であるため、毎年 7 月の各週にノットを設定したいです。あとは AKS の判断に委ねます。」
- 「毎月第 1 週は当社にとって特別な時期であるため、毎月第 1 週にノットを設定したいです。それ以外に月内でノットが必要かどうかは AKS の判断に委ねます。」
import numpy as np
from meridian.model import knots
from meridian.model import model
from meridian.model import spec
# `data` is an `InputData` object; same as what will be passed to
# `model.Meridian`
aks = knots.AKS(data)
# Require knots at specific time periods and automatically select the rest
required_knots = np.array([10, 20, 30])
knot_locations = aks.automatic_knot_selection(
required_knots=required_knots
).knots
# You must use the 'knots' argument and NOT set 'enable_aks=True'.
model_spec = spec.ModelSpec(knots=knot_locations)
# `input_data` argument is the same `data` passed to `knots.AKS(data)`
mmm = model.Meridian(input_data=data, model_spec=model_spec)
ハイブリッド ノット選択によるモデルの更新
新しい時系列データで既存モデルを更新する場合(例: 複数年分のデータセットに最新の四半期データを追加する場合)、初期期間におけるベースライン トレンドの一貫性を保つため、元のモデルで使用されたノットの位置を維持したいことがあります。その一方で、新たに追加されたデータについては、AKS に新規ノットを選択させたい場合もあります。
これを実現するには、元のモデルのノットの位置を required_knots として渡し、restrict_to_right_of_required_knots=True を設定したうえで、automatic_knot_selection() を呼び出します。これにより、AKS は元データに続く期間に対してのみ新規ノットを追加するようになります。
import numpy as np
from meridian.model import knots
from meridian.model import model
from meridian.model import spec
# `data` is an `InputData` object; same as what will be passed to
# `model.Meridian`
aks = knots.AKS(data)
# Use knots selected from a previous model run as required knots and
# automatically select knots located after the last knot
previous_knots = np.array([10, 30, 50, 70, 90])
knot_locations = aks.automatic_knot_selection(
required_knots=previous_knots,
restrict_to_right_of_required_knots=True
).knots
# You must use the 'knots' argument and NOT set 'enable_aks=True'.
model_spec = spec.ModelSpec(knots=knot_locations)
# `input_data` argument is the same `data` passed to `knots.AKS(data)`
mmm = model.Meridian(input_data=data, model_spec=model_spec)
ノットの手動選択
場合によっては、MMM のノットを手動で設定したいケースもあるでしょう。このセクションでは、ノットの位置を手動で選択するための基本的な考え方を紹介します。
ModelSpec の knots を手動で設定する方法を考える際、2 つの極端なケースを想定するとわかりやすいでしょう。ノット数は 1 から期間数(n_times)までのいずれかになります。knots = n_times の場合は、次元削減は行われず、各期間に独自のパラメータが割り当てられます。地域レベルのモデルでは、期間ごとに複数の地域があり、したがって複数の観測値が存在するため、期間と同じ数のノットがある場合でも識別可能です。knots = 1 の場合、すべての期間が単一のパラメータで測定されます。これは、時間に影響がないことを意味します。この効果の欠如は、全期間共通の切片になります。
1 < knots < n_times の場合、この 2 つの極値の中間になります。有効値の空間内のさまざまな値を試すことができます。この 2 つの極値の中間について考える際は、バイアスと分散のトレードオフが参考になります。
推奨の方法は次のとおりです。
地域レベルのモデルは、最初はデフォルト(
knots = n_times)にします。過学習が極端な場合や、メディア効果の推定が現実的ではない場合は、ノットの数を減らすことをおすすめします。時間点あたりの地域の数が少なくなるほど、ノット数を減らす必要性が高まります。全国レベルのモデルでは、始めはデフォルトの
1ノットとし、そこからノット数を増やしていきます。過学習が極端になるか、メディア効果の予測が現実的ではない状態になるまで、増やしていきます。ノットの数が同程度(
knots = 10やknots = 11など)であれば、同様の結果が返される可能性があるため、検証する値は分散させるとよいでしょう。
たとえば、10 個のノットを均等に手動で設定したい場合は次のようにします。
from meridian.model import model
from meridian.model import spec
model_spec = spec.ModelSpec(knots=10)
mmm = model.Meridian(input_data=data, model_spec=model_spec)
あるいは、ノットの正確な位置を手動で指定したい場合は次のようにします。
from meridian.model import model
from meridian.model import spec
model_spec = spec.ModelSpec(knots=[7, 14, 30, 37, 44, 60, 67, 74, 90])
mmm = model.Meridian(input_data=data, model_spec=model_spec)
バイアスと分散のトレードオフ
ノット数の設定は、バイアスと分散のトレードオフであると考えると有効な場合があります。knots = n_times の場合、各期間に独自のパラメータが割り当てられるため、特定の期間の効果は、その期間のデータのみを使用して推定されます。ただし、knots = n_times は、特定の期間で利用できるデータポイントが少ないため、分散が大きくなります。
knots < n_times の場合、各ノットは近い期間のデータを使用して推定され、近い期間ほど重み付けが高くなります。最も近い 2 つのノットによって特定の期間の推論が決まるため、特定の期間の効果は、その期間のデータと近い期間のデータによって推定されます。ノットの数が減るにつれて、近い時間点が特定の時間点の推論に与える影響は大きくなり、近い時間点ほど重み付けが高くなります。これにより、特定の期間の効果の推定に使用される時間点が増えるため、分散が減少します。ただし、データは特定の期間のものではないため、バイアスが強くなります。
要約すると、ノットの数を増やすと時間効果の推定値のバイアスが弱くなり、ノットの数を減らすと時間効果の推定値の分散が減少します。アナリストとして、バイアスと分散のトレードオフを考慮し、適切なバランスを見きわめることが大事です。時間がメディアと KPI の間の重要な交絡因子であるなら、時間効果の推定におけるバイアスと分散のトレードオフは、メディアの因果効果の推定におけるバイアスと分散のトレードオフと言い換えることができます。
また、時間領域ごとにバイアスと分散のトレードオフを変えることもできます。その場合は、knots をノットの位置を指定するリストに設定します。ノットの位置は、推定値のバイアスを弱くしたい領域(ホリデー シーズンなど)では高密度にして、推定値の分散を低くしたい領域(ホリデー シーズン以外など)では低密度にします。
ノット数を減らすことが推奨されるケース
ノット数を設定する際は、期間がメディア施策にどのように影響するかについても考慮することをおすすめします。コントロール変数は、メディア施策と KPI の両方に影響する交絡変数である必要があります。コントロール変数について詳しくは、コントロール変数の選択をご覧ください。
時間についても同様の考え方が当てはまります。期間によってメディア施策が左右されない場合は、時間は実際には交絡変数とならないため、モデル化で時間のノット数を増やして自由度を過剰に高める必要はありません。広告主様は、メディア施策のプランニングにおいて期間を考慮する必要があるかどうかを判断する必要があります。たとえば、旅行ブランドのメディア プランニングは期間によって左右される可能性が高くなります。スナック菓子ブランドでは、どの期間も比較的一貫したメディア プランニングとなるでしょう。また、時間そのものが実際に重視すべき交絡変数なのか、それとも時間は(多くの場合、より低い自由度で)直接モデル化できる他の変数の代替なのかを考えます(例: 実際にメディア施策を促進させた交絡変数は時間だったのか、あるいは全国の COVID 感染者数だったのか)。自社のメディア プランニング戦略を把握しており、こうした事項に関する情報をお持ちの広告主様に適切に判断していただく必要があります。
knots < n_times とする必要があるケース
knots < n_times の設定が必要な状況もあります。たとえば、期間ごとに観測値が複数あるわけではなく、期間ごとに独自のパラメータを取得できるだけの自由度もない、国内レベルのモデルなどです。ある程度の次元削減が必要になります。
また、国内レベルのメディアまたは国内レベルのコントロール変数を含める必要があるケースも考えられます。定義上、国内レベルの変数は時間とともに変化しますが、地域によって変化することはありません。このような変数は時間と完全な共線関係にあるため、期間ごとのパラメータを含むモデルの場合は冗長になります。knots を n_times に近づけると、技術的には識別可能なモデルになる可能性があります。ただし、その場合でも弱く識別可能なだけで、問題が生じる可能性があります。国内モデルでの時間効果の推定に関する懸念事項を考慮すると、国内モデルでは、地域モデルよりも高品質の
コントロールを確保することが重要になります。高品質のコントロールについて詳しくは、コントロール変数の選択をご覧ください。
時間効果のモデル化のその他のアプローチ: バイナリ指標と周期関数
メリディアンでは、バイナリ指標または周期関数をコントロール変数として作成および入力することで、時間効果をモデル化できます。両者は、それぞれ特定のケースでメリットがあります。
メリディアンでは、交絡変数であるコントロール変数を含めることが推奨されます。交絡変数とは、メディア施策と KPI の両方に因果効果を持つ変数のことです。時間効果に関連するコントロール変数も同様です(詳しくは、コントロール変数をご覧ください)。
バイナリ指標
バイナリ指標は、条件が満たされた場合は 1、満たされない場合は 0 の値を取ります。たとえば、値 1 を 12 月のすべての期間とし、値 0 をそれ以外の期間とするなどです。メリディアンでは、バイナリ指標をコントロール変数として使用して、一連の期間で一貫性があり、必要に応じて地域差のある時間効果をモデル化できます。ノットとバイナリ指標は一緒に使用できますが、時間効果のモデル化に使用されるパラメータの総数に注意してください。
一貫した効果
バイナリ指標は複数の期間を対象とすることができ、そのすべての期間について、KPI の効果(地域モデルでは 1 人あたりの効果)が一貫していると仮定されます。バイナリ指標では、複数の期間を使用して一貫した効果を推定します。効果が一貫しているとする仮定がほぼ正しい場合、推定の精度が高まり、自由度が効率的に使用されます。
指標は、示された期間以外の期間には影響しません。一方、特定の期間に配置されたノットは、次の隣接するノットまでの期間に影響します。
一連の期間にわたって時間効果が一定であると仮定したモデル化は、一定の枠組みにはめ込むことで推定値を安定させることができる全国モデルに適しています。地域モデルではしばしば、ノットを数多く使用して柔軟性を高めることが優先されます。
地域差
バイナリ指標が地域レベルのモデルのコントロール変数として使用される場合、地域に依存する効果を持つものとして推定されます。これは、地域によって影響が異なると予想されるイベントに適しています(例: 開催都市でより大きな影響をもたらすスーパーボウルなど)。対照的に、ノットは地域に依存しない時間の効果を推定します。ノットは、すべての地域で一貫性のある、時間ベースのパターンを示す柔軟なスプライン関数を作成します。これは、地域依存の時間効果が想定されない場合に、より優れたパラメータ効率を発揮します。
バイナリ指標の地域差をオフにする
地域依存の時間効果がない場合にバイナリ指標を使用するには、階層分散 xi_c の事前分布をゼロの点質量に設定します。この場合、バイナリ指標の地域固有の各係数は同一になります。すべてのコントロール変数の地域効果をオフにするには、分散の事前分布を 0 の確定値に設定します。
xi_c = tfp.distributions.Deterministic(0)
特定の 1 つのコントロール変数についてのみ地域効果をオフにするには、その事前分布の scale を 0 に設定します。たとえば、4 つのコントロール変数があり、最初の変数の地域依存の効果をオフにする場合は、次のようになります。
xi_c = tfp.distributions.HalfNormal(scale=[0, 5, 5, 5])
多重共線性のリスク
バイナリ指標では、メディア施策変数との間に強い多重共線性のリスクが生じる可能性があります。したがって、多重共線性に注意し、実際に交絡因子であるバイナリ指標のみを含めることが重要です。
周期関数
別の方法として、フーリエ級数などの周期関数をコントロール変数として追加することもできます。周期関数は、特に全国モデルでノットの優れた代替手段になります。
周期関数は、KPI(地域モデルの場合は 1 人あたりの KPI)に対する時間効果を滑らかで周期的なパターンとしてモデル化します。周期関数は、KPI への時間効果を非常にパラメトリックに仮定したものです。これは、一定の枠組みにはめ込むことで季節性の安定した推定値を得られる全国モデルに適しているでしょう。KPI を滑らかで周期的なパターンとして扱うことが求められない地理モデルではしばしば、ノットを数多く使用して柔軟性を高めることが優先されます。
実践的な推奨事項
地域モデルと全国モデルで推奨事項が異なります。
地域モデル
地域モデルでは、バイナリ指標を使用して地域依存の時間効果をモデル化できます。ノットとバイナリ指標は一緒に使用できますが、時間効果のモデル化に使用されるパラメータの総数に注意してください。フルノットのバイナリ指標は推奨されません。
- 時間効果が地域依存「でない」場合は、ノットを使用します。地域間で一貫した時間パターンをモデル化する場合は、ノットを使用すると、過剰なパラメータ化のリスクを回避しながら柔軟性を確保できます。
- 時間効果が地域依存「である」場合は、バイナリ指標を使用します。イベントの効果に地域差があると強く仮定される場合は、バイナリ指標をコントロール変数として使用することをおすすめします。
全国モデル
全国モデルでは特に単純さが重要となります。これは、バイナリ指標、周期関数、または適切に配置された少数のノットで実現できます。これらを組み合わせて使用できますが、全国モデルでは単純さが重要となるため、パラメータの総数には特に注意してください。これらの各オプションは、仮定がほぼ正しい場合に、推定値の精度が高まり、自由度が効率的に使用されます。以下は、それぞれのアプローチの前提となる仮説をまとめたものです。
- 周期関数は、滑らかで周期的なパターンとして KPI に対する時間効果をモデル化します。
- バイナリ指標は、対象となる期間全体で一貫しているものとして時間効果をモデル化します。
- ノットは、時間の経過に伴う区分的線形トレンドとして時間効果をモデル化します。