MOMENT — オープンソースのEncoder-only時系列基盤モデル【ICML 2024】

時系列基盤モデルの多くはDecoder-only(自己回帰生成)アーキテクチャを採用しています。Chronosは次パッチ予測、TimesFMも同様です。しかし、時系列データの全てのユースケースが「未来の予測」ではありません。異常検知、分類、クラスタリング、そして検索では、時系列を意味のある固定長ベクトルに変換する埋め込み能力が最も重要です。

NLPの世界で、BERTがGPTと対照的にEncoder-onlyアーキテクチャを採用し、文の埋め込みや分類タスクで圧倒的な性能を示したことを思い出してください。同じ発想で、CMU(カーネギーメロン大学)のGoswami et al.が提案したのがMOMENT(ICML 2024)です。

MOMENTはEncoder-onlyアーキテクチャでマスク再構成(masked reconstruction)による事前学習を行い、時系列の汎用的な埋め込みベクトルを生成します。このベクトルは、そのまま検索インデックスに格納して類似時系列検索に使えます。さらに、MOMENTは完全にオープンソース(モデル重み + コード + データ)で公開されており、今すぐ実験できるのが大きな利点です。

MOMENTの概念図:パッチの75%をマスクし双方向Transformerで再構成しつつ、平均プーリングで汎用埋め込みを得る

全体像はこの図です。入力パッチの75%を隠し、双方向Transformerで「隠れた部分を復元」させます。この事前学習を通じて育つ中間表現を平均プーリングすれば、分類・異常検知・検索に使える汎用埋め込みが得られます。予測専用のDecoder型と違い、系列全体を見渡せるのがポイントです。

1つのモデルで5タスクを解く、という問題意識

MOMENTが挑む核心の問いは「時系列のための、タスク非依存の単一基盤モデルは作れるか」です。NLPや画像では「巨大コーパスで自己教師あり事前学習した1つのモデルを、軽量なヘッドの付け替えだけで多様な下流タスクに使い回す」というパラダイムが定着しました。しかし時系列では、長期予測には予測専用モデル、分類には分類専用モデル……とタスクごとに専用設計を作り直すのが当たり前でした。論文はこれを、(1) 多様な時系列を集めた公開コーパスが無かったこと、(2) 時系列は周期・トレンド・サンプリング周波数が極端に多様で「1つのモデルで全部を捉える」のが難しいこと、の2点が阻んできたと整理します。MOMENTはこの2つを、後述する Time Series Pile(大規模公開コーパス)と マスク再構成による汎用事前学習 で正面突破します。

MOMENTは単一モデルで長期予測・短期予測・分類・異常検知・補完の5タスクを高水準で解く(出典: Goswami et al., MOMENT, ICML 2024, Fig.1)

出典: Goswami et al., “MOMENT: A Family of Open Time-series Foundation Models”, ICML 2024, Fig.1

論文のFigure 1は、MOMENT(橙)を専用設計の強豪 GPT4TS(青)・TimesNet(緑)と5軸のレーダーで比較したものです。中心から外に伸びるほど高性能で、MOMENTは長期予測・補完で互角、そして分類と異常検知では専用モデルを明確に上回る形に外側へ張り出しています。1つの事前学習済みモデルが、タスクごとの専用品と肩を並べ、得意分野ではそれを超える——これがMOMENTの主張の出発点です。この「1モデル多タスク」を支える具体的な仕組みを、以降で事前学習→アーキテクチャ→タスク適応の順に分解していきます。

MOMENTを理解することは、以下のような場面で直接役立ちます。

  • 時系列検索インデックスの構築: MOMENTの埋め込みベクトルをFAISSに格納し、類似する時系列パターンの高速検索が可能です
  • ゼロショット分類・異常検知: 事前学習済みの埋め込みに線形プローブを付けるだけで、少量データでの分類・異常検知が実現できます
  • すぐに実験できる: オープンソースで、Hugging Faceからpip installで利用可能です

本記事の内容

  • Encoder-only vs Decoder-onlyの設計思想の違い
  • マスク再構成事前学習の数理とアーキテクチャのデータフロー
  • Time Series Pile(多様な事前学習データ)とデータ汚染回避の分割
  • MOMENTが埋め込みに学ぶ生成因子(論文Fig.4の読み解き)
  • 5タスク統一ベンチマークの実験設定(論文Table 1)
  • Pythonでのマスク再構成と埋め込み抽出の実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

時系列基盤モデルの全体像
PatchTST・Chronosなど主要手法の比較を理解します
Masked Autoencoder(MAE)の理論と実装
MOMENTの事前学習手法の基盤となるMAEを理解します
画像なし
ベクトルデータベースの仕組み
MOMENTの埋め込みを格納する検索インデックスを理解します

Encoder-only vs Decoder-onlyの設計思想

Decoder-onlyの特徴と限界

Chronos、TimesFM、Lag-LlamaなどのDecoder-only基盤モデルは、自己回帰生成を前提として設計されています。入力系列の後ろに1パッチずつ生成を繰り返すことで、未来の予測を行います。

$$ p(x_{T+1:T+H} \mid x_{1:T}) = \prod_{h=1}^{H} p(x_{T+h} \mid x_{1:T+h-1}) $$

この設計は予測(forecasting)には最適ですが、以下のタスクでは制約になります。

埋め込みの品質: Decoder-onlyモデルの自己回帰マスク(因果マスク)は、各トークンが過去のトークンしか参照できないようにします。つまり、系列の後半のトークンは前半を参照できますが、前半のトークンは後半を参照できません。埋め込みベクトル(通常は最後のトークンまたは平均プーリング)は、この非対称な情報を反映するため、系列全体の特徴を均等に捉えることが難しくなります。

分類タスク: 時系列分類では「系列全体のパターン」を捉える必要がありますが、自己回帰モデルは「次のトークンを予測するのに有用な特徴」を学習するため、分類に最適な表現とは限りません。

Encoder-onlyの利点

Encoder-only(BERTスタイル)モデルは、双方向Attentionを使います。各トークンが系列内の全てのトークンを参照できるため、系列全体のコンテキストを考慮した埋め込みが得られます。

$$ \bm{z}_i = \text{Attention}(\bm{q}_i, [\bm{k}_1, \ldots, \bm{k}_n], [\bm{v}_1, \ldots, \bm{v}_n]) $$

因果マスクがないため、位置 $i$ のトークンは位置 $j > i$ のトークンも参照できます。これは予測タスクでは「カンニング」になりますが、埋め込み・分類・異常検知では系列全体の情報を集約した表現が得られるという大きな利点です。

双方向Attention(MOMENT/BERT)と因果Attention(Chronos/GPT)の対比:双方向は未来側のトークンも参照できる

左がMOMENTの双方向Attention、右がDecoder型の因果Attentionです。Decoder型では各トークンが過去しか見られないため、埋め込みに前後非対称な偏りが残ります。MOMENTは未来側も見られるので、系列全体を均等に捉えた表現になります。これが「予測は苦手でも埋め込みは得意」という住み分けの源です。

NLPでの類似関係を整理すると:

特性 NLP Encoder (BERT) NLP Decoder (GPT) TS Encoder (MOMENT) TS Decoder (Chronos)
Attention 双方向 因果(左→右) 双方向 因果
事前学習 マスク言語モデル 次トークン予測 マスク再構成 次パッチ予測
得意タスク 分類, 埋め込み 生成, 予測 分類, 埋め込み, 検索 予測

では、MOMENTの事前学習がどのように設計されているかを見ていきましょう。

マスク再構成による事前学習

パッチ化と入力表現

MOMENTはPatchTSTと同様に、入力時系列をパッチに分割してトークン化します。長さ $T$ の時系列をパッチサイズ $P$、ストライド $S$ で分割し、$N = \lfloor(T-P)/S\rfloor + 1$ 個のパッチを得ます。

$$ \bm{p}_i = [x_{(i-1)S+1}, x_{(i-1)S+2}, \ldots, x_{(i-1)S+P}] \in \mathbb{R}^P $$

各パッチは線形射影で $d$ 次元の埋め込みに変換されます:

$$ \bm{z}_i^{(0)} = \bm{W}_{\text{proj}} \bm{p}_i + \bm{b}_{\text{proj}} + \bm{e}_{\text{pos},i} $$

ここで $\bm{e}_{\text{pos},i}$ は学習可能な位置埋め込みです。

マスク戦略

BERTがトークンの15%をランダムにマスクするのと同様に、MOMENTはパッチの一定割合をマスクします。マスク率は実験的に75%が最適とされています(MAEと同じ知見)。

マスクされたパッチは学習可能な [MASK] トークンに置き換えられます:

$$ \tilde{\bm{z}}_i^{(0)} = \begin{cases} \bm{z}_i^{(0)} & \text{if } i \notin \mathcal{M} \\ \bm{e}_{\text{mask}} & \text{if } i \in \mathcal{M} \end{cases} $$

ここで $\mathcal{M}$ はマスクされたパッチのインデックス集合です。

再構成目標

Transformerエンコーダでマスクされたパッチを含む全パッチ列を処理し、マスク位置の出力から元のパッチ値を再構成します。

$$ [\bm{z}_1^{(L)}, \ldots, \bm{z}_N^{(L)}] = \text{TransformerEncoder}([\tilde{\bm{z}}_1^{(0)}, \ldots, \tilde{\bm{z}}_N^{(0)}]) $$

$$ \hat{\bm{p}}_i = \bm{W}_{\text{head}} \bm{z}_i^{(L)} + \bm{b}_{\text{head}}, \quad i \in \mathcal{M} $$

損失関数はマスクされたパッチのMSEです:

$$ \mathcal{L}_{\text{recon}} = \frac{1}{|\mathcal{M}|}\sum_{i \in \mathcal{M}} \|\bm{p}_i – \hat{\bm{p}}_i\|^2 $$

アーキテクチャの全体像とデータフロー

ここまでの式を、論文のアーキテクチャ図に沿って一本のデータフローとして並べ直しましょう。

MOMENTのアーキテクチャ:Masking→Patching→Encoding→Transformer Encoder→Reconstruction Headの順に処理する(出典: Goswami et al., MOMENT, ICML 2024, Fig.3)

出典: Goswami et al., “MOMENT: A Family of Open Time-series Foundation Models”, ICML 2024, Fig.3

論文のFigure 3は、入力の単変量時系列 $\bm{x} \in \mathbb{R}^{1\times T}$ が右へ流れていく様子を5段階で描いています。それぞれの段で、テンソルの形(図の下に併記された次元)がどう変わるかを追うと処理の全体像が掴めます。

  1. Masking($\{0,1\}^{1\times T}$) — まずパッチ単位のマスク列を引きます。図中の塗りつぶし/白抜きのマスがマスクされたパッチ・残されたパッチに対応します。事前学習では一定割合(後述の通り実装では軽め)をマスクします。
  2. Patching($\mathbb{R}^{P\times N}$) — 系列を長さ $P$ の互いに重ならないパッチ $N$ 個に分割します。MOMENTの既定はストライド $S=P$(非重複)で、これが式 $N=\lfloor(T-P)/S\rfloor+1$ を単純な $N=T/P$ にします。点ごとではなくパッチ(部分系列)をトークン扱いするのがPatchTST譲りの設計です。
  3. Encoding($\mathbb{R}^{D\times N}$) — 各パッチを線形射影 $\bm{W}_{\text{proj}}$ で $D$ 次元に持ち上げ、位置埋め込みを足します。マスクされたパッチは学習可能な $\bm{e}_{\text{mask}}$ に置き換えられます(図で色の薄いトークン)。
  4. Transformer Encoder — 図右の点線で囲まれたブロックがこの中身で、$L$ 層の Multi-Head Attention + Norm + MLP からなります。ここが双方向(因果マスク無し)で、各パッチが系列全体を見渡して文脈化された表現 $\mathbb{R}^{D\times N}$ を返します。重みはT5エンコーダから初期化されます。
  5. Reconstruction Head($\mathbb{R}^{P\times N}$) — 軽量な線形ヘッド $\bm{W}_{\text{head}}$ が、各トークン表現を元のパッチ長 $P$ に射影し直して再構成します。事前学習ではマスク位置の再構成誤差が損失になり、下流タスクではこのヘッドを付け替えるだけでタスク適応します。

この図の要点は「重い本体(Transformer Encoder)は固定し、入口のPatchingと出口のHeadだけを差し替える」という分離です。予測ならHeadを「将来 $H$ 点を出す予測ヘッド」に、分類なら「埋め込み→クラス確率の線形プローブ」に、補完なら「マスク位置を埋めるヘッド」に替えるだけで、同じ事前学習済みエンコーダを5タスクへ流用できます。これがFigure 1の「1モデル多タスク」を物理的に成立させている構造です。

なぜマスク再構成が良い埋め込みを生成するか

マスク再構成が有効な理由を直感的に理解しましょう。パッチの75%がマスクされた状態で元の値を復元するには、モデルは残りの25%のパッチから時系列の全体的な構造(周期性、トレンド、振幅、位相)を推定する必要があります。この過程で学習される中間表現は、時系列の本質的な特徴を捉えた汎用的な埋め込みになります。

マスク再構成の直感:見えている25%の手がかりから系列全体の周期・振幅・位相を推定する

図のように、見えているのはわずか25%(シアン)。残り75%を埋めるには、「この系列はこういう周期と振幅を持つはずだ」という構造の理解が不可欠です。穴埋めを強制されることで、モデルは表面的な値ではなく系列の本質を学ぶ——これが良い埋め込みが育つ仕組みです。

Time Series Pile

多様な事前学習データの重要性

MOMENTの事前学習にはTime Series Pileと名付けられた多様なデータセットが使われます。NLPのThe Pileに倣い、多様なドメインの時系列を収集したコーパスです。

データソース ドメイン 系列数
ETTh/ETTm 電力変圧器温度 14
Weather 気象観測 21
Electricity 電力消費 321
Traffic 交通量 862
ILI インフルエンザ患者数 7
Exchange 為替レート 8
+ UCR Archive 多ドメイン分類 ~20,000

UCR Time Series Archiveから128個のデータセット(約20,000系列)を含むことが特徴で、電子機器の振動、心電図、人の動作、音声、合成パターンなど、極めて多様なドメインをカバーしています。

Time Series Pileのデータ多様性:UCR Archive約2万系列を筆頭に交通・電力・気象・為替など多ドメインを収録

系列数を対数軸で見ると、UCR Archive(約2万系列)が圧倒的で、交通・電力・気象・為替など性質の異なるドメインが混ざっています。この多様性こそが「初見のドメインでも効く」ゼロショット汎化の土台です。1種類のデータに偏ると、その癖に過適合して汎用埋め込みになりません。

データ汚染を避ける厳密な分割

基盤モデルの評価で見落とされがちなのがデータ汚染(data contamination)——「事前学習で見たデータを評価でも使ってしまい、性能を過大評価する」問題です。論文のFigure 2は、Time Series Pileがこれをどう回避するかを示しています。

Time Series Pileのデータ分割:各データセットをtrain/val/testに厳密分割し、事前学習にはtrainのみ使う(出典: Goswami et al., MOMENT, ICML 2024, Fig.2)

出典: Goswami et al., “MOMENT: A Family of Open Time-series Foundation Models”, ICML 2024, Fig.2

ポイントは2つの分割軸です。左の Horizontal Split は1本の長い系列を時間方向にtrain(60%)/val(10%)/test(30%)へ切り、右の Vertical Split は複数系列を系列単位でtrain/val/testへ振り分けます。論文はデータセットごとに公式の分割があればそれに従い、無ければこの60/10/30で分けます。そして事前学習に使うのは全データセットのtrain split のみです。こうすることで、下流タスクのtest split は事前学習時に一度も見られていないことが保証され、「ゼロショットで効く」という主張がフェアになります。基盤モデルの数字を読むときは、こうした分割設計まで含めて評価が公正かを確認する習慣が大切です。

チャネル独立処理

MOMENTはチャネル独立(Channel Independent)設計を採用しています。多変量時系列の各チャネルを独立に処理するため、チャネル数が異なるデータセットを統一的に扱えます。

$$ \bm{z}_c = f_\theta(\bm{x}^{(c)}), \quad c = 1, \ldots, C $$

チャネル間の相互作用を捉えられないという制約はありますが、PatchTSTの研究で示されたように、多くのベンチマークでチャネル独立設計はチャネル混合設計と同等以上の性能を達成します。

チャネル独立処理:各チャネルを共有のMOMENTで別々にエンコードし、チャネル数の違うデータも統一的に扱う

図のように、多変量時系列の各チャネルを「同じ1つのMOMENT」で別々に通します。こうすると、3チャネルでも100チャネルでも同じモデルで扱え、チャネル数がバラバラなデータセット群をまとめて事前学習できます。チャネル間の相互作用は捨てるトレードオフですが、その分シンプルで汎用性が高まります。

Pythonによる実装

マスク再構成の簡易実装

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(42)

class SimpleMOMENT:
    """MOMENTの簡易実装(マスク再構成事前学習)。"""

    def __init__(self, patch_size=16, d_model=64, n_patches=16, mask_ratio=0.75):
        self.patch_size = patch_size
        self.d_model = d_model
        self.n_patches = n_patches
        self.mask_ratio = mask_ratio

        # パッチ埋め込み
        self.W_proj = np.random.randn(patch_size, d_model) * 0.02
        self.pos_embed = np.random.randn(n_patches, d_model) * 0.02
        self.mask_token = np.random.randn(d_model) * 0.02

        # 簡略化されたTransformer(1層のSelf-Attention)
        self.W_q = np.random.randn(d_model, d_model) * 0.02
        self.W_k = np.random.randn(d_model, d_model) * 0.02
        self.W_v = np.random.randn(d_model, d_model) * 0.02

        # 再構成ヘッド
        self.W_head = np.random.randn(d_model, patch_size) * 0.02

    def patchify(self, x):
        """時系列をパッチに分割する。"""
        T = len(x)
        n = T // self.patch_size
        patches = x[:n * self.patch_size].reshape(n, self.patch_size)
        return patches

    def encode(self, patches, mask=None):
        """パッチ列をエンコードする。"""
        n = len(patches)

        # パッチ埋め込み
        z = patches @ self.W_proj + self.pos_embed[:n]

        # マスク適用
        if mask is not None:
            for i in range(n):
                if mask[i]:
                    z[i] = self.mask_token + self.pos_embed[i]

        # Self-Attention(簡略化: 1層)
        Q = z @ self.W_q
        K = z @ self.W_k
        V = z @ self.W_v

        scale = np.sqrt(self.d_model)
        attn = Q @ K.T / scale
        attn = np.exp(attn - np.max(attn, axis=1, keepdims=True))
        attn = attn / np.sum(attn, axis=1, keepdims=True)

        z_out = attn @ V
        return z_out

    def reconstruct(self, z):
        """埋め込みからパッチを再構成する。"""
        return z @ self.W_head

    def get_embedding(self, x):
        """時系列の埋め込みベクトルを取得する(平均プーリング)。"""
        patches = self.patchify(x)
        z = self.encode(patches, mask=None)
        return np.mean(z, axis=0)  # 平均プーリング

    def masked_reconstruction(self, x):
        """マスク再構成を実行する。"""
        patches = self.patchify(x)
        n = len(patches)

        # ランダムマスク
        n_mask = int(n * self.mask_ratio)
        mask_indices = np.random.choice(n, n_mask, replace=False)
        mask = np.zeros(n, dtype=bool)
        mask[mask_indices] = True

        # エンコード(マスクあり)
        z = self.encode(patches, mask=mask)

        # マスク位置の再構成
        recon = self.reconstruct(z)

        return patches, recon, mask


# デモ
model = SimpleMOMENT(patch_size=16, d_model=64, n_patches=16)

# テスト時系列
T = 256
t = np.linspace(0, 4 * np.pi, T)
x = np.sin(t) + 0.5 * np.sin(3 * t) + 0.1 * np.random.randn(T)

# マスク再構成
patches, recon, mask = model.masked_reconstruction(x)

# 可視化
fig, axes = plt.subplots(2, 2, figsize=(14, 10))

# 左上: 元の時系列とマスク位置
axes[0, 0].plot(x, color='#00d4ff', label='Original')
for i in range(len(mask)):
    if mask[i]:
        start = i * 16
        axes[0, 0].axvspan(start, start + 16, alpha=0.2, color='red')
axes[0, 0].set_title('Original Time Series (red = masked patches)')
axes[0, 0].set_xlabel('Time')
axes[0, 0].set_ylabel('Value')
axes[0, 0].legend()
axes[0, 0].grid(True, alpha=0.3)

# 右上: マスクされたパッチの再構成
for i in range(len(mask)):
    t_patch = np.arange(i * 16, (i + 1) * 16)
    if mask[i]:
        axes[0, 1].plot(t_patch, patches[i], 'o-', color='#00d4ff',
                        markersize=2, alpha=0.5, label='Original' if i == mask.nonzero()[0][0] else '')
        axes[0, 1].plot(t_patch, recon[i], 'x-', color='#ffa726',
                        markersize=2, alpha=0.8, label='Reconstructed' if i == mask.nonzero()[0][0] else '')
axes[0, 1].set_title('Masked Patch Reconstruction')
axes[0, 1].set_xlabel('Time')
axes[0, 1].set_ylabel('Value')
axes[0, 1].legend()
axes[0, 1].grid(True, alpha=0.3)

# 左下: 埋め込みの類似度マトリックス
# 異なるパターンの時系列の埋め込みを比較
patterns = {
    'Sin': np.sin(np.linspace(0, 4*np.pi, T)),
    'Sin+noise': np.sin(np.linspace(0, 4*np.pi, T)) + 0.3*np.random.randn(T),
    'Cos': np.cos(np.linspace(0, 4*np.pi, T)),
    'Linear': np.linspace(-1, 1, T),
    'Step': np.concatenate([np.zeros(T//2), np.ones(T//2)]),
    'Random': np.random.randn(T),
}

embeddings = {}
for name, series in patterns.items():
    embeddings[name] = model.get_embedding(series)

names = list(embeddings.keys())
n_patterns = len(names)
sim_matrix = np.zeros((n_patterns, n_patterns))
for i in range(n_patterns):
    for j in range(n_patterns):
        e_i = embeddings[names[i]]
        e_j = embeddings[names[j]]
        sim = np.dot(e_i, e_j) / (np.linalg.norm(e_i) * np.linalg.norm(e_j) + 1e-10)
        sim_matrix[i, j] = sim

im = axes[1, 0].imshow(sim_matrix, cmap='RdBu_r', vmin=-1, vmax=1)
axes[1, 0].set_xticks(range(n_patterns))
axes[1, 0].set_xticklabels(names, rotation=45, ha='right')
axes[1, 0].set_yticks(range(n_patterns))
axes[1, 0].set_yticklabels(names)
axes[1, 0].set_title('Embedding Cosine Similarity')
plt.colorbar(im, ax=axes[1, 0])

# 右下: 埋め込みの2D射影(PCA)
all_embeddings = np.array([embeddings[n] for n in names])
# 簡易PCA
mean = np.mean(all_embeddings, axis=0)
centered = all_embeddings - mean
cov = centered.T @ centered
eigenvalues, eigenvectors = np.linalg.eigh(cov)
top2 = eigenvectors[:, -2:]
projected = centered @ top2

colors = ['#00d4ff', '#4dd0e1', '#ffa726', '#ef5350', '#66bb6a', '#ab47bc']
for i, name in enumerate(names):
    axes[1, 1].scatter(projected[i, 0], projected[i, 1], c=colors[i], s=100,
                       label=name, zorder=5)
axes[1, 1].set_xlabel('PC1')
axes[1, 1].set_ylabel('PC2')
axes[1, 1].set_title('Embedding Space (PCA projection)')
axes[1, 1].legend(fontsize=8)
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.savefig('moment_embeddings.png', dpi=150, bbox_inches='tight')
plt.show()

元の時系列とマスクされたパッチ(赤帯が75%)

まず入力です。赤帯がマスクされたパッチで、全体の75%を占めます。モデルは残り25%の情報だけから、この赤帯部分を埋めなければなりません。

マスクパッチの再構成:学習前なのでまだ粗い(元の値=青、再構成=橙)

マスク位置の元の値(青)と再構成値(橙)の比較です。ここではランダム初期化のままなので再構成はまだ粗いですが、事前学習を重ねるとこの復元が高精度になり、その過程で育つ中間表現が高品質な埋め込みになります。

異なるパターンの埋め込みのコサイン類似度行列(実測)

埋め込み同士のコサイン類似度です。学習前でも、SinとCosの類似度は1.00と非常に高く(同じ振幅・周期の正弦波だから当然)、SinとSin+noiseは0.49、SinとRandomは0.12と低くなりました。似た形は近く、無秩序な系列は遠いという構造が、ランダム初期化ですら部分的に現れています。学習後はこの構造がさらに明瞭になります。

埋め込み空間のPCA射影(実測):似たパターンが近くに配置される

埋め込みを2次元(主成分)に落とした図です。周期的なSin・Cosが近くに固まり、LinearやStepといった質の違うパターンが離れて配置されています。埋め込み空間が「時系列の見た目の近さ」をある程度反映していることが読み取れます。これが、埋め込みをそのまま類似検索に使える根拠です。

上の実測は学習前のランダム初期化モデルでしたが、論文では事前学習済みのMOMENTが同じことを遥かに鮮明にやってのけることが示されています。次にそれを見ましょう。

MOMENTは何を学んでいるか

「マスク再構成で良い埋め込みが育つ」という主張を、論文は美しい統制実験で裏付けています。$y=\sin(2\pi f x + c)$ のような関数に対し、トレンド指数 $c$・振幅 $c$・周波数 $c$・ベースライン・位相 $c$ を1つずつ系統的に変えた合成波を多数生成し、MOMENTで埋め込んでPCAで2次元に落とします。

MOMENTの埋め込みは、トレンド・振幅・周波数・ベースライン・位相といった生成因子に沿って整然と並ぶ(出典: Goswami et al., MOMENT, ICML 2024, Fig.4)

出典: Goswami et al., “MOMENT: A Family of Open Time-series Foundation Models”, ICML 2024, Fig.4

論文のFigure 4で目を引くのは、各パネルの色(生成因子の値)が空間内で連続的なグラデーションを描いていることです。とくに (iii) Frequency は周波数の増加に沿って点が滑らかな弧を描き、(v) Phase は位相 $f$ ごとに島が分かれます。これは、MOMENTの埋め込み空間が「トレンド・振幅・周波数・位相」という時系列の本質的な生成因子に沿った軸を自然に獲得していることを意味します。誰も「周波数を表す次元を作れ」とは教えていないのに、マスク穴埋めを解くうちにモデルがそれらの構造を内部表現に刻んだわけです。一方 (ii) Amplitude(iv) Baseline Shift は分離が弱く、振幅やオフセットの情報は(正規化の影響もあり)相対的に捨てられ気味なことも読み取れます——埋め込みが「何を重視し、何を均すか」まで見えるのがこの図の価値です。これこそ、先ほど自作デモで部分的に見えた「似た形は近く、無秩序は遠く」の完成版です。

では、この表現が実際の下流タスクでどう評価されたのかを見ていきましょう。

実験設定とベンチマーク

MOMENTは5つの時系列タスクを横断する統一ベンチマークで評価されます。論文のTable 1が、その全体設計(タスク・教師の与え方・データセット・指標・ベースライン・実験条件)を一望できる表です。

MOMENTの評価ベンチマーク:5タスクそれぞれの教師付与方法・データセット・指標・ベースライン(出典: Goswami et al., MOMENT, ICML 2024, Table 1)

出典: Goswami et al., “MOMENT: A Family of Open Time-series Foundation Models”, ICML 2024, Table 1

この表からMOMENTの「適応の仕方」が読み取れます。5タスクは適応方式で2種類に分かれます。

  • 線形プローブ(Linear Probing) — 事前学習済みエンコーダは凍結し、出口のヘッド(線形層)だけを下流データで学習します。長期予測・補完・異常検知で採用。重い本体を触らずヘッドだけ替える、という前節のアーキ設計がそのまま評価方式に現れています。
  • ゼロショット(Zero-shot) — 重みを一切追加学習せずに使います。短期予測(M3/M4競技データ)、異常検知、補完で評価。事前学習だけで初見ドメインに通用するかを問う、最も厳しい設定です。
  • 分類は教師なし表現学習として評価され、凍結したMOMENT埋め込みの上にSVMを載せた精度で表現の質を測ります。

指標はタスクに応じて使い分けられます。予測・補完はMSE/MAE、短期予測はsMAPE、分類はAccuracy、そして異常検知はAdjusted Best F1 と VUS-ROC——後者はpoint-adjustの水増しを避けるための堅牢な指標です。比較相手も豪華で、GPT4TS・TimesNet・PatchTST・FEDformer・DLinear・N-BEATS(予測系)、Anomaly Transformer(異常検知)、TS2Vec・T-Loss・TS-TCC(表現学習)など各タスクの代表的な専用モデルが並びます。look-back窓は予測で $L=512$、補完はマスク率 $\{12.5, 25, 37.5, 50\}\%$ で連続区間(長さ8)をマスク、といった条件まで明記されており、Figure 1の「専用モデルと互角〜凌駕」という結論が、この統一された土俵の上で得られたものだと分かります。

検索インデックスとしての活用

MOMENTの埋め込みを時系列検索に使う具体的な手順は以下の通りです。

  1. パッチウィンドウのスライド: 対象の時系列を固定長(例: 512点)のウィンドウでスライドし、各ウィンドウをMOMENTでエンコード
  2. FAISSインデックスの構築: 埋め込みベクトル($d = 768$ 次元)をFAISSのHNSWインデックスに格納
  3. クエリ検索: 検索したいパターンをMOMENTでエンコードし、FAISSでANN検索
  4. 後処理: 検索結果のランキングをCross-Encoderで精緻化(オプション)

検索インデックスとしての活用フロー:窓スライド→MOMENTでエンコード→FAISS格納→ANN近傍検索

流れはこの図の通りです。時系列を窓でスライドして各窓をMOMENTで埋め込み、FAISSに格納しておけば、クエリパターンの埋め込みで近傍検索(ANN)するだけで類似パターンを高速に引けます。MOMENTの埋め込み次元は768であり、1ウィンドウあたり768×4=3KBのストレージです。100万ウィンドウで約3GBと、FAISSで十分扱える規模です。

まとめ

本記事では、ICML 2024のMOMENTについて解説しました。

  • Encoder-onlyアーキテクチャは双方向Attentionにより系列全体の情報を均等に捉え、埋め込み・分類・検索タスクに適している
  • マスク再構成事前学習(75%マスク率)により、時系列の本質的なパターンを捉えた汎用的な埋め込みを学習
  • Time Series Pileで多様なドメインのデータを使って事前学習し、ゼロショットでの汎化を実現
  • 埋め込みベクトルをそのまま検索インデックスに使えるため、時系列検索パイプラインへの統合が容易
  • 完全にオープンソースで今すぐ実験可能

次のステップとして、以下の記事も参考にしてください。

Sundial — 最新最大の時系列基盤モデル
MOMENTと比較されるDecoder型の最新手法を理解します
画像なし
ベクトルデータベースの仕組み
MOMENTの埋め込みを格納するFAISSインデックスの理論を理解します