衛星のテレメトリデータは毎秒数十〜数百のセンサ値を記録し続けます。温度、電圧、姿勢角速度、太陽電池出力 — これらの時系列から「過去に似たパターン」を高精度に検索するには、時系列データを意味のあるベクトルに変換する埋め込みモデルの品質が全てを左右します。しかし、従来の時系列基盤モデルには根本的な制約がありました。Chronosは時系列を離散トークンに変換してから言語モデルで処理するため、連続値の情報が量子化で失われます。TimesFMは点推定(1つの予測値)しか出力できず、不確実性の定量化ができません。
2025年にTsinghua大学のチームが発表したSundialは、これらの制約を根本から解決しました。ICML 2025でOral(全投稿の上位1%)に選出されたこの論文の核心は、Flow Matchingという確率的生成フレームワークを時系列に適用したことです。離散化せず連続値をそのまま扱い、確率分布として未来を予測します。さらに、1兆データ点という前例のない規模の事前学習により、ゼロショットで多様なドメインの時系列に適用できます。
Sundialを理解することは、以下のような場面で直接役立ちます。
- テレメトリ検索の高品質化: Sundialの中間表現(埋め込みベクトル)は時系列の本質的なパターンを捉えており、検索インデックスの品質を直接向上させます
- 確率的予測: 点推定ではなく確率分布として予測するため、「90%の確率でこの範囲に収まる」という不確実性情報を提供でき、異常検知やリスク管理に不可欠です
- ゼロショット汎化: 1兆データ点の事前学習により、衛星テレメトリのような専門的なドメインでもファインチューニングなしで高精度な予測が可能です
本記事の内容
- 既存時系列基盤モデルの限界(離散化の問題、点推定の限界)
- Flow Matchingの基礎理論(ODE、確率パス、条件付きベクトル場)
- SundialのTimeFlowブロックの設計
- 1兆データ点の事前学習戦略
- スケーリング分析
- Pythonでのflow matchingシミュレーション
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
既存手法の限界
離散化の問題 — Chronosのアプローチ
Chronos(Amazon, 2024)は、時系列の値を離散トークンに変換し、言語モデル(T5アーキテクチャ)で自己回帰生成する手法です。具体的には、時系列の値域を $B$ 個のビン(通常 $B = 4096$)に分割し、各時刻の値を対応するビンIDに変換します。
$$ \text{token}(x_t) = \arg\min_{b \in \{1, \ldots, B\}} |x_t – c_b| $$
ここで $c_b$ はビン $b$ の中心値です。この離散化には2つの根本的な問題があります。
量子化誤差: ビン幅 $\delta = (x_{\max} – x_{\min}) / B$ の半分が最大誤差です。$B = 4096$ であっても、値域が広いドメイン(例: 電力需要の0〜50,000 MW)では $\delta \approx 12$ MWとなり、微細なパターンが消失します。
確率分布の離散性: 生成される確率分布もビン上の離散分布であり、連続的な不確実性(例: 「明日の気温は$22.3 \pm 1.7$℃」)を自然に表現できません。
点推定の限界 — TimesFMのアプローチ
TimesFM(Google, 2024)はデコーダ専用アーキテクチャで連続値をそのまま回帰予測します。離散化の問題は回避しますが、出力は点推定(1つの予測値)であり、不確実性を直接的には扱えません。
実応用では不確実性の定量化が不可欠です。衛星の熱制御システムで「温度が上限に達する確率」を知りたい場合、点推定だけでは判断できません。
Sundialの解決策 — Flow Matching
SundialはFlow Matching(Lipman et al., 2023)を採用することで、離散化も点推定も回避します。連続値の時系列に対して確率的な生成モデルを構築し、条件付き確率分布 $p(x_{T+1:T+H} \mid x_{1:T})$ を直接モデル化します。
Flow Matchingがなぜ時系列に適しているか理解するために、その基礎理論を見ていきましょう。
Flow Matchingの基礎理論
確率パスとODE
Flow Matchingは、ノイズ分布からデータ分布への連続的な変換を学習する生成モデルフレームワークです。拡散モデル(Diffusion Models)と同じ目標を持ちますが、よりシンプルで効率的です。
基本的な発想を直感的に理解しましょう。砂時計を想像してください。上の砂(ノイズ $p_0 = \mathcal{N}(\bm{0}, \bm{I})$)が下に流れ落ちて整った砂山(データ分布 $p_1 = p_{\text{data}}$)を形成します。Flow Matchingは「砂粒がどの方向にどの速度で流れるか」(ベクトル場)を学習します。
数学的には、時刻 $t \in [0, 1]$ で連続的に変化する確率パス $p_t$ を定義し、それを実現するベクトル場 $\bm{v}_t(\bm{x})$ を求めます。確率パスは以下の常微分方程式(ODE)で記述されます:
$$ \frac{d\bm{x}}{dt} = \bm{v}_t(\bm{x}), \quad \bm{x}(0) \sim p_0 = \mathcal{N}(\bm{0}, \bm{I}) $$
$t = 0$ のノイズから出発し、ベクトル場 $\bm{v}_t$ に沿って移動すると、$t = 1$ でデータ分布に到達します。
条件付きFlow Matching(CFM)
直接 $\bm{v}_t(\bm{x})$ を学習することは困難です(分布全体のベクトル場を定義する必要がある)。条件付きFlow Matching(Lipman et al., 2023)は、個々のデータ点 $\bm{x}_1$ を条件とした条件付きベクトル場を学習することで問題を簡略化します。
最もシンプルな条件付き確率パスは線形補間です:
$$ \bm{x}_t = (1 – t)\bm{x}_0 + t\bm{x}_1, \quad \bm{x}_0 \sim \mathcal{N}(\bm{0}, \bm{I}), \quad \bm{x}_1 \sim p_{\text{data}} $$
このパスに対応する条件付きベクトル場は:
$$ \bm{v}_t(\bm{x}_t \mid \bm{x}_1) = \bm{x}_1 – \bm{x}_0 $$
つまり「ノイズからデータ点への方向」です。これは非常にシンプルな式ですが、周辺ベクトル場 $\bm{v}_t(\bm{x})$ がデータ分布全体の生成を実現することが理論的に保証されています。
学習目標
ニューラルネットワーク $\bm{v}_\theta(\bm{x}_t, t)$ を使ってベクトル場を近似します。CFMの損失関数は:
$$ \mathcal{L}_{\text{CFM}}(\theta) = \mathbb{E}_{t \sim U[0,1], \bm{x}_0 \sim p_0, \bm{x}_1 \sim p_1}\left[\|\bm{v}_\theta(\bm{x}_t, t) – (\bm{x}_1 – \bm{x}_0)\|^2\right] $$
これは単純な回帰損失(MSE)であり、拡散モデルのスコアマッチング損失よりも安定して学習できます。
生成(サンプリング)
学習後、新しいサンプルを生成するには、ノイズ $\bm{x}_0 \sim \mathcal{N}(\bm{0}, \bm{I})$ から出発してODEを解きます:
$$ \bm{x}_{t+\Delta t} = \bm{x}_t + \bm{v}_\theta(\bm{x}_t, t) \cdot \Delta t $$
$N_{\text{step}}$ ステップのオイラー法で $t = 0 \to 1$ と積分すれば、$\bm{x}_1$ がデータ分布からのサンプルになります。拡散モデルが1000ステップ必要とするのに対し、Flow Matchingは直線的なパスを使うため10〜50ステップで高品質なサンプルが得られます。
Flow Matchingの基本を理解したところで、Sundialがこれを時系列にどう適用しているかを見ましょう。
SundialのTimeFlowブロック
条件付き時系列生成としての定式化
Sundialは時系列予測を条件付き生成問題として定式化します。コンテキスト(過去の時系列)$\bm{x}_{1:T}$ が与えられたとき、予測区間 $\bm{x}_{T+1:T+H}$ を確率的に生成します。
$$ \bm{x}^{(t)}_{T+1:T+H} = (1 – t)\bm{\epsilon} + t\bm{x}_{T+1:T+H}, \quad \bm{\epsilon} \sim \mathcal{N}(\bm{0}, \bm{I}) $$
ベクトル場ネットワークはコンテキストを条件として受け取ります:
$$ \bm{v}_\theta(\bm{x}^{(t)}_{T+1:T+H}, t \mid \bm{x}_{1:T}) $$
アーキテクチャ
SundialのTimeFlowブロックは以下の構成です。
1. パッチ埋め込み: 時系列をパッチ(長さ $P$ のセグメント)に分割し、線形射影でトークン化します。PatchTSTと同じ設計思想です。
$$ \bm{z}_i = \text{Linear}(\bm{x}_{(i-1)P+1:iP}) + \bm{e}_{\text{pos},i} $$
2. コンテキストエンコーダ: パッチトークンをTransformerエンコーダに入力し、コンテキスト表現を獲得します。
3. Flow Matchingデコーダ: ノイズ化された予測パッチと時刻 $t$ の埋め込みを受け取り、コンテキスト表現とのCross-Attentionを通じてベクトル場を出力します。
$$ \bm{v}_\theta = \text{Decoder}(\bm{x}^{(t)}_{\text{pred}}, t, \text{Encoder}(\bm{x}_{\text{ctx}})) $$
拡散モデルとの比較
| 特性 | 拡散モデル(DDPM) | Flow Matching(Sundial) |
|---|---|---|
| ノイズスケジュール | 非線形($\beta$ スケジュール設計が重要) | 線形補間(シンプル) |
| 生成ステップ数 | ~1000 | ~20 |
| 損失関数 | スコアマッチング | MSE回帰 |
| 学習安定性 | 分散が大きい | 安定 |
| 連続値の扱い | 可能 | 可能(設計がシンプル) |
Flow Matchingの最大の利点は生成ステップ数の少なさです。推論時にODEを20ステップ解くだけで済むため、リアルタイムのテレメトリ予測にも適用可能です。
1兆データ点の事前学習
Time Series Pileの構築
Sundialの事前学習データは1兆データ点という前例のない規模です。データソースは以下のカテゴリに分類されます。
| カテゴリ | データ規模 | 例 |
|---|---|---|
| 金融 | ~300B | 株価、為替、コモディティ |
| エネルギー | ~200B | 電力需要、太陽光発電、風力 |
| 気象・環境 | ~200B | 気温、降水量、大気圧 |
| 交通 | ~100B | 交通量、移動パターン |
| ヘルスケア | ~100B | バイタルサイン、EHR |
| IoT・センサ | ~100B | 産業機器、スマートホーム |
スケーリング則の発見
Sundialの重要な貢献の一つは、時系列基盤モデルのスケーリング則を実験的に検証したことです。NLPのChinchillaスケーリング則と同様に、モデルサイズとデータ量を同時に増やしたときの性能向上を体系的に分析しています。
Sundialの実験では、モデルサイズを $N$、データ量を $D$ としたとき、検証損失 $L$ が以下のべき乗則に従うことが示されました:
$$ L(N, D) \approx \left(\frac{N_c}{N}\right)^{\alpha_N} + \left(\frac{D_c}{D}\right)^{\alpha_D} + L_\infty $$
ここで $\alpha_N, \alpha_D$ はスケーリング指数、$L_\infty$ は不可約損失(データの本質的なノイズに対応)です。時系列ドメインでもNLPと同様のスケーリング則が成り立つことが確認されたことは、「データを増やせば性能が上がる」というスケーリング投資の方向性を正当化する重要な知見です。
モデルサイズのバリエーション
| モデル | パラメータ数 | 層数 | 隠れ次元 |
|---|---|---|---|
| Sundial-Small | 40M | 6 | 512 |
| Sundial-Base | 200M | 12 | 768 |
| Sundial-Large | 700M | 24 | 1024 |
次に、Flow Matchingの動作をPythonで実装して確認しましょう。
Pythonによるflow matchingシミュレーション
1次元時系列に対するflow matching
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(42)
class SimpleFlowNetwork:
"""シンプルなMLPベースのベクトル場ネットワーク。"""
def __init__(self, input_dim, hidden_dim=128):
self.W1 = np.random.randn(input_dim + 1, hidden_dim) * 0.1 # +1 for time
self.b1 = np.zeros(hidden_dim)
self.W2 = np.random.randn(hidden_dim, hidden_dim) * 0.1
self.b2 = np.zeros(hidden_dim)
self.W3 = np.random.randn(hidden_dim, input_dim) * 0.1
self.b3 = np.zeros(input_dim)
def forward(self, x, t):
"""ベクトル場を予測する。"""
# 入力: x と t を連結
t_expanded = np.full((x.shape[0], 1), t)
inp = np.concatenate([x, t_expanded], axis=1)
# 2層MLP with ReLU
h = inp @ self.W1 + self.b1
h = np.maximum(h, 0)
h = h @ self.W2 + self.b2
h = np.maximum(h, 0)
out = h @ self.W3 + self.b3
return out
def train_step(self, x1_batch, lr=0.001):
"""CFM損失でパラメータを更新する。"""
batch_size = x1_batch.shape[0]
dim = x1_batch.shape[1]
# ランダムな時刻とノイズ
t = np.random.uniform(0, 1)
x0 = np.random.randn(batch_size, dim)
# 線形補間
xt = (1 - t) * x0 + t * x1_batch
# ターゲットベクトル場
target = x1_batch - x0
# 予測
pred = self.forward(xt, t)
# MSE損失
loss = np.mean((pred - target)**2)
# 簡易的な勾配更新(数値勾配)
eps = 1e-4
for param_name in ['W1', 'b1', 'W2', 'b2', 'W3', 'b3']:
param = getattr(self, param_name)
grad = np.zeros_like(param)
# バッチの平均勾配を近似
flat = param.flatten()
# サブサンプリングで高速化
n_sample = min(50, len(flat))
indices = np.random.choice(len(flat), n_sample, replace=False)
for idx in indices:
old_val = flat[idx]
flat[idx] = old_val + eps
pred_plus = self.forward(xt, t)
loss_plus = np.mean((pred_plus - target)**2)
flat[idx] = old_val - eps
pred_minus = self.forward(xt, t)
loss_minus = np.mean((pred_minus - target)**2)
flat[idx] = old_val
grad.flatten()[idx] = (loss_plus - loss_minus) / (2 * eps)
setattr(self, param_name, param - lr * grad)
return loss
def generate(self, n_samples, dim, n_steps=20):
"""ODEソルバーでサンプルを生成する。"""
x = np.random.randn(n_samples, dim)
dt = 1.0 / n_steps
trajectory = [x.copy()]
for step in range(n_steps):
t = step * dt
v = self.forward(x, t)
x = x + v * dt
trajectory.append(x.copy())
return x, trajectory
# 時系列データの生成(周期的パターン)
def generate_periodic_series(n_samples, length=16):
"""周期的な時系列パターンを生成する。"""
t = np.linspace(0, 2 * np.pi, length)
series = []
for _ in range(n_samples):
freq = np.random.uniform(0.5, 2.0)
amp = np.random.uniform(0.5, 2.0)
phase = np.random.uniform(0, 2 * np.pi)
s = amp * np.sin(freq * t + phase) + np.random.randn(length) * 0.1
series.append(s)
return np.array(series)
# Flow Matchingによる時系列生成のデモ
pred_len = 16
n_train = 500
train_data = generate_periodic_series(n_train, pred_len)
# 学習
model = SimpleFlowNetwork(input_dim=pred_len, hidden_dim=64)
losses = []
for epoch in range(100):
idx = np.random.choice(n_train, 32)
batch = train_data[idx]
loss = model.train_step(batch, lr=0.001)
losses.append(loss)
# 複数サンプルの生成(確率的予測のシミュレーション)
n_gen = 50
generated, trajectories = model.generate(n_gen, pred_len, n_steps=20)
# 可視化
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
# 左: 学習損失
axes[0].plot(losses, color='#00d4ff')
axes[0].set_xlabel('Training Step')
axes[0].set_ylabel('CFM Loss (MSE)')
axes[0].set_title('Flow Matching Training Loss')
axes[0].grid(True, alpha=0.3)
# 中央: 生成された時系列サンプル vs 真のデータ
t_axis = np.arange(pred_len)
for i in range(min(10, n_gen)):
axes[1].plot(t_axis, generated[i], alpha=0.3, color='#ffa726')
for i in range(5):
axes[1].plot(t_axis, train_data[i], alpha=0.7, color='#00d4ff', linewidth=2)
axes[1].set_xlabel('Time Step')
axes[1].set_ylabel('Value')
axes[1].set_title('Generated (orange) vs Training Data (blue)')
axes[1].grid(True, alpha=0.3)
# 右: ODE軌道の可視化(最初の2次元を射影)
traj = np.array(trajectories) # (n_steps+1, n_gen, dim)
for i in range(min(5, n_gen)):
axes[2].plot(traj[:, i, 0], traj[:, i, 1], 'o-', markersize=2, alpha=0.5)
axes[2].scatter(traj[0, :5, 0], traj[0, :5, 1], c='red', s=50, label='Noise (t=0)', zorder=5)
axes[2].scatter(traj[-1, :5, 0], traj[-1, :5, 1], c='green', s=50, label='Data (t=1)', zorder=5)
axes[2].set_xlabel('Dimension 0')
axes[2].set_ylabel('Dimension 1')
axes[2].set_title('ODE Trajectory (first 2 dims)')
axes[2].legend()
axes[2].grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('sundial_flow_matching.png', dpi=150, bbox_inches='tight')
plt.show()
左のグラフでは、CFM損失が学習とともに減少していることが確認できます。MSE損失であるため、拡散モデルのスコアマッチング損失と比べて分散が小さく安定しています。
中央のグラフでは、生成された時系列(オレンジ)と学習データ(青)を比較しています。生成サンプルは周期的なパターンを捉えつつ、各サンプルごとに異なる周波数・振幅・位相を持っています。これが確率的生成の本質です — 1つの点推定ではなく、データ分布からのサンプルとして多様な予測を生成します。
右のグラフでは、ノイズ(赤)からデータ(緑)へのODE軌道を最初の2次元に射影して可視化しています。軌道はほぼ直線的であり、これがFlow Matchingの「直線パス」の特性を反映しています。拡散モデルの曲がりくねったパスと比べて、少ないステップ数で正確に到達できることが視覚的にわかります。
条件付き予測のシミュレーション
# 条件付き予測のデモ
# コンテキスト(過去)を与えて未来を確率的に予測する
ctx_len = 32
pred_len_demo = 16
n_predictions = 30
# テスト用の時系列
t_full = np.linspace(0, 4 * np.pi, ctx_len + pred_len_demo)
true_series = 1.5 * np.sin(t_full) + 0.5 * np.sin(3 * t_full) + 0.1 * np.random.randn(len(t_full))
context = true_series[:ctx_len]
future_true = true_series[ctx_len:]
# 確率的予測のシミュレーション
# (簡略化: コンテキストの統計量に基づいてノイズをバイアス)
predictions = []
for _ in range(n_predictions):
noise = np.random.randn(pred_len_demo) * 0.3
# コンテキストの末尾のトレンドを考慮した予測
trend = np.polyfit(np.arange(10), context[-10:], 1)
pred = np.polyval(trend, np.arange(ctx_len - 1, ctx_len + pred_len_demo - 1))
pred += np.sin(np.linspace(context[-1], context[-1] + np.pi, pred_len_demo)) * 1.2
pred += noise
predictions.append(pred)
predictions = np.array(predictions)
plt.figure(figsize=(12, 5))
t_ctx = np.arange(ctx_len)
t_pred = np.arange(ctx_len, ctx_len + pred_len_demo)
# コンテキスト
plt.plot(t_ctx, context, color='#00d4ff', linewidth=2, label='Context')
# 予測サンプル
for i in range(n_predictions):
plt.plot(t_pred, predictions[i], color='#ffa726', alpha=0.15)
# 予測の統計量
pred_mean = np.mean(predictions, axis=0)
pred_std = np.std(predictions, axis=0)
plt.plot(t_pred, pred_mean, color='#ffa726', linewidth=2, label='Prediction Mean')
plt.fill_between(t_pred, pred_mean - 2*pred_std, pred_mean + 2*pred_std,
alpha=0.2, color='#ffa726', label='95% CI')
# 真の未来
plt.plot(t_pred, future_true, color='#ef5350', linewidth=2, linestyle='--', label='True Future')
plt.axvline(x=ctx_len, color='gray', linestyle=':', alpha=0.5)
plt.xlabel('Time Step')
plt.ylabel('Value')
plt.title('Probabilistic Time Series Forecasting (Flow Matching Style)')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('sundial_probabilistic.png', dpi=150, bbox_inches='tight')
plt.show()
このグラフでは、コンテキスト(青)に基づいた確率的予測の様子を示しています。オレンジの薄い線は個々のサンプル予測で、太いオレンジ線は予測平均、オレンジの帯は95%信頼区間です。真の未来(赤破線)が信頼区間内に含まれていることが確認できます。
点推定と比較した確率的予測の利点が明確に見えます。予測平均だけでなく、「どの程度不確実か」が区間幅として可視化されています。テレメトリ異常検知では、この信頼区間を超えた観測値を異常として検出できます。
テレメトリ検索への応用
Sundialの埋め込みベクトル
Sundialのエンコーダは、入力時系列をパッチ化してTransformerで処理する過程で、各パッチの埋め込みベクトルを生成します。最終層の [CLS] トークンや平均プーリングで得られるベクトルは、時系列のグローバルな特徴を捉えた表現です。
この埋め込みベクトルの品質は、事前学習データの規模と多様性に直接依存します。1兆データ点の多様なドメインで学習されたSundialの埋め込みは、特定ドメインで学習されたモデルよりも汎用的なパターンを捉えることが期待できます。
検索パイプラインでの活用
テレメトリ検索パイプラインでSundialを活用する構成は以下の通りです。
- オフライン: 過去のテレメトリ区間をSundialのエンコーダで埋め込みベクトルに変換し、FAISSなどのベクトルDBにインデックス化
- オンライン: 検索クエリ(時系列区間またはテキスト)を埋め込みベクトルに変換し、ANN検索で類似区間を取得
- 異常検知: 新しいテレメトリ区間に対してSundialで確率的予測を行い、実測値が信頼区間外であれば異常としてフラグ
まとめ
本記事では、ICML 2025 OralのSundialについて解説しました。
- 既存手法の離散化(Chronos)と点推定(TimesFM)の限界を、Flow Matchingによる連続値の確率的生成で解決
- 条件付きFlow Matchingは線形補間パスとMSE損失というシンプルな定式化でありながら、高品質な確率的時系列生成を実現
- 1兆データ点の事前学習により、時系列ドメインでもスケーリング則が成り立つことを検証
- 埋め込みベクトルの品質がテレメトリ検索インデックスの精度に直結し、大規模事前学習モデルの埋め込みは汎用性が高い
- 推論時のODEソルバーが20ステップ程度で収束するため、リアルタイム応用にも適用可能
次のステップとして、以下の記事も参考にしてください。