時系列予測にZero-shotを加える ― Chronos-2の仕組みと比較実験の進め方
はじめに
時系列予測というと、ARIMAや状態空間モデル、XGBoost、LightGBM、LSTMなどを使い、対象のデータに合わせてモデルを学習する方法がよく使われます。これらの方法は今も重要ですが、最近は別の選択肢が増えてきました。大量の時系列データで事前学習した**時系列基盤モデル(Time Series Foundation Model)**です。
時系列基盤モデルの中には、初めて扱う観測データに対しても、追加学習なしで予測を行えるものがあります。この使い方を**Zero-shot forecasting(ゼロショット予測)**と呼びます。AmazonのChronos、GoogleのTimesFM、NixtlaのTimeGPTなどが代表例です。
ただし、Zero-shotだから精度が高い、あるいは従来の機械学習が不要になる、という話ではありません。実務で使うなら、予測対象の過去データから何を読み取れるのか、外部の予報情報をどのように使えるのか、比較実験で何を揃える必要があるのかを理解しておく必要があります。
この記事では、Zero-shotの考え方を整理したうえで、Chronos-2を使う最小のPython例と、降雨予測を題材にした比較実験の組み立て方を説明します。モデルの公開状況は2026年10月19日時点の情報に基づいています。
1. Zero-shotは「何も学習していない」わけではありません
Zero-shotとは、予測対象のデータセットに合わせて、事前学習済みモデルの重みを更新せずに予測することです。モデルそのものは、すでに大量の時系列データで学習されています。
例えば、ある雨量観測所の次の3時間を予測するとします。従来のLSTMなら、過去数年間のデータから学習用の系列を作り、モデルを学習してから予測します。一方、Zero-shotでは、事前学習済みモデルに直近168時間の雨量を渡すだけで、将来3時間分の予測を試せます。
よく似た用語も区別しておきます。
| 方法 | 対象データに対して行うこと | 重みの更新 |
|---|---|---|
| Zero-shot | 事前学習済みモデルに過去系列を渡して予測 | なし |
| In-context forecasting | 過去系列や関連系列などを予測時の文脈として与える | 通常なし |
| Fine-tuning | 対象データを使って事前学習済みモデルを追加学習 | あり |
| From scratch | 対象データを使ってモデルを初期状態から学習 | あり |
時系列のZero-shotでは、予測対象の直近の観測値を入力するのが普通です。「追加学習が不要」であって、「観測データが不要」ではありません。
なお、厳密なベンチマークでは、事前学習データに評価用データが含まれていないかも確認します。GIFT-Evalは、一般的な「事前学習済みモデル」と、学習データの重複を避けた「Zero-shot」を区別しています。単に推論時に重みを更新していないだけでは、厳密な転用性能を示したことにならない場合があります。[1]
2. 事前学習によって何を獲得しているのか
時系列には分野を超えて共通する構造があります。電力需要なら日内・週内の周期、小売売上なら曜日や季節による変動、センサー値なら緩やかなトレンドや突然の変化が現れます。基盤モデルは、さまざまな系列でこうした構造を学び、新しいデータにも利用できるように設計されています。
過去の観測値の長さを (L)、予測する時間数を (H) とすると、単変量の予測は概ね次のように書けます。
\hat{\boldsymbol y}_{t+1:t+H} = f_{\theta}(\boldsymbol y_{t-L+1:t})
ここで、(f_{\theta}) は事前学習済みモデル、(\theta) は学習済みのパラメータです。Zero-shot推論では、予測対象が変わっても (\theta) を更新しません。
ただし、時系列の規則性を学んだことと、対象分野の物理現象をそのまま解いていることは別です。降雨を予測するとき、Transformerが積乱雲の発生や移動を数値気象モデルと同じ方法で計算しているわけではありません。特に突発的な豪雨では、この違いが精度に現れる可能性があります。
3. どのモデルを比較対象にするか
Zero-shotに対応するモデルにも、得意な用途や利用条件の違いがあります。
| モデル | 開発元 | 主な特徴 | 比較する際の位置付け |
|---|---|---|---|
| Chronos(初代) | Amazon | 数値のスケーリング・量子化とトークン生成 | 基本的な確率予測の理解に向く |
| Chronos-Bolt | Amazon | パッチ化と分位点の直接予測 | 単変量の高速推論候補 |
| Chronos-2 | Amazon | 単変量・多変量・外生変数を用いる予測 | 今回の中心モデル |
| TimesFM-3 | Google Research | 多変量・共変量に対応したZero-shot予測 | Chronos-2の比較対象 |
| TimeGPT | Nixtla | APIを中心に提供される予測サービス | API型モデルとの比較 |
| MOMENT | 研究チーム(CMUなど) | 時系列の特徴表現、分類・異常検知・補完 | 予測以外の用途も含む評価 |
| Moirai | Salesforce Research | 汎用的な時系列予測モデル系列 | 別アーキテクチャとの比較 |
Chronos-2を中心にする理由
初代Chronosは、観測値をスケーリング・量子化し、数値をトークンとして扱う仕組みでした。一方、Chronos-BoltとChronos-2は、複数時点の値をまとめたパッチを利用します。
Chronos-2は約1億2,000万パラメータのモデルで、時間方向の関係だけでなく、関連する系列間の情報も扱います。対象観測所の雨量に加え、近隣観測所や外生変数を使う検討ができる点が、降雨予測との相性を調べるうえで有用です。[2]
ただし、別々の観測所を単に別の行に並べただけで、必ず相互の情報が利用されるわけではありません。関連系列をどうまとめるかは、モデルの入力仕様に従って指定する必要があります。
TimesFM-3との違い
TimesFM-3はGoogle Researchが2026年8月に公開したモデルで、多変量予測や過去・将来の共変量に対応しています。公開ベンチマークでは良好な成績が報告されていますが、その順位を特定地域の降雨予測にそのまま当てはめることはできません。[3]
利用条件にも注意が必要です。TimesFM-3のソースコードと、公開されている事前学習済み重みのライセンスは同一ではありません。TimesFM-3の公開済み重みは非商用・非本番用途に制限されています。業務システムに組み込む場合は、利用前に条件を確認する必要があります。[4]
TimeGPT・MOMENTはどう扱うか
TimeGPTはAPIで予測を利用しやすい一方、通信環境、利用料金、データ送信先、サービスの提供条件を考慮する必要があります。MOMENTは短期予測にも使えますが、主な特徴は時系列の特徴表現を分類・異常検知・欠測補完などにも利用できることです。予測の比較では、使用したタスク設定と予測ヘッド、追加学習の有無を明記しておきます。[5]
4. Zero-shotに入力できる情報を整理する
単一の過去系列だけを入力する場合は分かりやすいですが、複数観測所や数値気象予報を組み合わせる場合は、情報の種類を分ける必要があります。
| データの種類 | 降雨予測での例 | 取り扱い |
|---|---|---|
| 過去の目的変数 | 対象地点Aの過去の1時間雨量 | 入力可能 |
| 過去の関連系列 | 周辺地点B・Cの過去の1時間雨量 | 対応するモデルへ入力可能 |
| 過去の外生変数 | 気温、湿度、気圧など | 対応するモデルへ入力可能 |
| 予測時点で既知の将来情報 | その時点で発表済みのMSM予報値 | 対応するモデルへ入力可能 |
| 予測時点では未知の情報 | 将来実際に観測される雨量、後から発表されるMSM | 入力してはいけない |
外生変数を利用する場合、概念的には次の形になります。
\hat{\boldsymbol y}_{t+1:t+H}
= f_{\theta}\left(
\boldsymbol y_{t-L+1:t},
\boldsymbol x_{t-L+1:t},
\boldsymbol x^{\mathrm{known}}_{t+1:t+H}
\right)
(\boldsymbol x^{\mathrm{known}}) は、予測したい将来時刻を対象としつつ、予測を開始する時点で既に入手できた値です。MSMの予報値は該当しますが、後から判明した実況雨量は該当しません。
この図は目指す比較実験の構成であり、単変量のコードを実行しただけで自動的にこの構成になるわけではありません。MSMを利用する場合は、過去の共変量と将来既知の共変量を、それぞれ公式APIの形式で渡します。
5. Zero-shotを公平に評価するには
比較実験で最も気を付けたいのは、モデルそのものよりも、予測時点で何の情報が利用可能だったかという条件です。
例えば、9時10分に予測を実行し、10時、11時、12時までの1時間雨量を予測するとします。このとき、観測終了時刻が9時の雨量が9時10分に既に公開されているなら入力できます。しかし、10時以降の実測雨量や、9時10分より後に発表された予報を利用してはいけません。
過去の全データを事後的にダウンロードして評価する場合、観測時刻と公開時刻を区別しないと、こうした情報漏洩が起こります。
予測起点を少しずつ動かして評価する
一つの期間だけで予測結果を比べるのではなく、予測の起点を1時間ずつずらして評価します。これはRolling-origin evaluationと呼ばれる方法です。
観測値の公開遅延を考慮したうえで、各予測起点において、モデルが同じ情報だけを参照するようにします。従来モデルを学習する場合は、学習・検証・テストを時系列順に分け、テスト期間を使って学習や設定調整を行わないようにします。
何を揃えて、何を分けて比較するか
| 項目 | 揃える条件 |
|---|---|
| 予測対象 | 同じ地点・同じ1時間雨量 |
| 予測時間 | 1、2、3時間先をそれぞれ比較 |
| 評価期間 | 同一のテスト期間・豪雨イベント |
| 情報利用 | 各起点で利用可能だった情報のみ |
| 欠測処理 | 補間方法、除外条件を統一 |
| モデル選択 | 評価期間外で調整し、最終テストは固定 |
| 評価指標 | 同じ計算式・同じ閾値 |
| 運用性能 | 同じハードウェア条件と処理単位 |
Zero-shotは「対象データで学習しない」という手法なので、対象データで学習したXGBoostと比べること自体は問題ありません。ただし、その比較が示すのは実務上どの予測方式が有利かであって、同じ学習量での純粋なアルゴリズム比較ではありません。この区別を明記しておきます。
6. 降雨予測では平均誤差だけを見ない
降雨には、雨量ゼロの時間が多いこと、強雨が比較的まれであること、短い時間で急変することなどの特徴があります。全時間帯をまとめた平均誤差だけを見ると、強雨を見逃すモデルが見かけ上は良好な結果になる可能性があります。
数値予測の誤差
平均絶対誤差(MAE)と二乗平均平方根誤差(RMSE)は、代表的な評価指標です。
\mathrm{MAE}=\frac{1}{N}\sum_{i=1}^{N}|y_i-\hat{y}_i|
\mathrm{RMSE}=\sqrt{\frac{1}{N}\sum_{i=1}^{N}(y_i-\hat{y}_i)^2}
ただし、1時間先、2時間先、3時間先の値を一緒に平均してしまうと、時間ごとの得意・不得意が見えなくなります。先行時間別に集計することが大切です。
強雨を検出できたか
例えば、検証上の閾値として10 mm/h、20 mm/hなどを設定し、超過した時間を検出できたかを確認します。閾値は目的に応じて事前に決め、後から成績のよい閾値を選ぶことは避けます。
- POD:実際の強雨をどれだけ見逃さずに捉えたか
- FAR:強雨と予測したうち、どれだけ空振りだったか
- CSI:見逃しと空振りを含めた検出性能
PODが高くても空振りが多ければ、運用上の負担が増えます。反対に空振りを減らそうとして強雨を見逃すようでは、防災用途として十分とはいえません。
確率予測・予測区間を評価する
Chronos-2では、中央値だけでなく10%、50%、90%といった分位点予測を出せます。10%分位点と90%分位点の間は、モデルが示す中央80%予測区間です。
ただし、モデルが「80%区間」を出力しても、実際に約80%の観測値がその範囲に入るとは限りません。テストデータで被覆率を調べ、必要に応じて確率校正を検討します。
| 評価項目 | 指標 | 確認する内容 |
|---|---|---|
| 雨量誤差 | MAE、RMSE | 予測値が実測に近いか |
| 強雨の検出 | POD、FAR、CSI | 見逃しと空振りのバランス |
| 分布予測 | CRPS、分位点損失 | 予測分布の妥当性 |
| 予測区間 | 被覆率、区間幅 | 不確実性が適切か |
| 運用性 | 推論時間、VRAM | 継続運用できるか |
降雨量は負の値を取りませんが、汎用モデルが常に非負の予測を返すとは限りません。負の予測値を0に補正する場合は、補正前後の両方の結果を保存し、予測区間への影響も確認しておくと後から検証しやすくなります。
7. Chronos-2で単変量Zero-shotを試す
ここからはPythonで試します。最初は複数観測所やMSMを入れず、対象観測所の過去168時間だけを使う構成にします。先に単変量予測を確かめておくと、多変量へ進んだときに改善の理由を整理しやすくなります。
実行環境とインストール
Ubuntu 24.04などのPython環境を想定します。GPUが使える場合はCUDAを、使えない場合はCPUを選ぶようにしています。PyTorchは利用するGPUとCUDAに合った方法で先に導入してください。
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install chronos-forecasting pandas numpy
PyTorchの導入方法は環境によって異なるため、公式インストール案内を確認してください。初回のモデル読み込み時は、事前学習済みの重みを取得するためにネットワーク接続が必要です。
入力CSVの形式
rainfall.csvには、次の3列を用意します。
| 列名 | 意味 |
|---|---|
timestamp |
1時間雨量が対応する時間の終了時刻(JST) |
rainfall_mm |
その1時間の雨量(mm) |
available_at |
その値が予測処理に利用可能になった時刻(JST) |
ここでは説明を簡単にするため、日時をすべてJSTのタイムゾーンなし文字列で統一します。実際の運用では、データベース内でUTCなどに正規化し、時刻の意味を明確にするほうが安全です。
timestamp,rainfall_mm,available_at
2026-09-01 07:00:00,0.0,2026-09-01 07:05:00
2026-09-01 08:00:00,1.5,2026-09-01 08:05:00
2026-09-01 09:00:00,2.0,2026-09-01 09:05:00
上の3行はCSV形式の説明用抜粋です。実行時には、予測に使う168時間分の連続データが必要です。available_atは観測データの実際の取得・公開履歴から設定し、説明用の5分遅延を実データに仮定して使わないでください。
Pythonコード
下のコードでは、予測を発行した時刻までに利用できた観測値だけを残し、直近168時間のデータから3時間先を予測します。欠測、時刻の飛び、負の雨量などがあれば、勝手に補完せずにエラーとします。
chronos2_zero_shot.py(クリックしてソースコードを開く)
"""Chronos-2を使った1時間雨量のZero-shot予測例。
入力CSVには timestamp, rainfall_mm, available_at が必要です。
3列はすべて同じ時刻系(この例ではJST)で扱います。
このコードはモデルの追加学習を行いません。入力の欠測値も
自動補完しません。実際の観測データで使用する際には、
公開時刻(available_at)を記録しておくことが重要です。
"""
from __future__ import annotations
import argparse
from pathlib import Path
import numpy as np
import pandas as pd
import torch
from chronos import Chronos2Pipeline
CONTEXT_LENGTH = 168 # 1時間データ7日分
PREDICTION_LENGTH = 3 # 1~3時間先
QUANTILES = [0.1, 0.5, 0.9]
def load_observations(csv_path: Path) -> pd.DataFrame:
"""雨量CSVを読み込み、列と値の基本的な整合性を確認します。
Args:
csv_path: 読み込むCSVファイルのパス。
Returns:
日時列をdatetime型、雨量列をfloat型に整えたDataFrame。
Raises:
ValueError: 必須列欠落、日時・数値の不正、重複時刻がある場合。
"""
df = pd.read_csv(csv_path)
required = {"timestamp", "rainfall_mm", "available_at"}
if not required.issubset(df.columns):
raise ValueError(f"CSVの必須列が不足しています: {required - set(df.columns)}")
for column in ("timestamp", "available_at"):
df[column] = pd.to_datetime(df[column], errors="raise")
df["rainfall_mm"] = pd.to_numeric(df["rainfall_mm"], errors="raise")
if df["timestamp"].isna().any() or df["available_at"].isna().any():
raise ValueError("日時列に欠測値があります。")
if df["timestamp"].duplicated().any():
raise ValueError("観測時刻が重複しています。")
rainfall = df["rainfall_mm"].to_numpy(dtype=float)
if not np.isfinite(rainfall).all() or (rainfall < 0).any():
raise ValueError("雨量に欠測、無限大または負の値があります。")
if (df["available_at"] < df["timestamp"]).any():
raise ValueError("利用可能時刻が観測終了時刻より前です。")
return df.sort_values("timestamp").reset_index(drop=True)
def prepare_context(df: pd.DataFrame, issue_at: str) -> pd.DataFrame:
"""発行時点で利用できた直近168時間を取り出します。
この例では毎時ちょうどの観測値が、その後の分単位の時刻に
利用可能となる運用を想定します。発行時点までに直近の毎時値が
得られていない場合は、未来を暗黙に埋めずにエラーにします。
Args:
df: load_observations()で検証済みのDataFrame。
issue_at: 実際に予測を発行する日時(JSTの文字列)。
Returns:
Chronos-2のpredict_df()へ渡す長形式DataFrame。
Raises:
ValueError: データ不足、欠測、直近観測の公開遅延がある場合。
"""
issue_time = pd.Timestamp(issue_at)
latest_hour = issue_time.floor("h")
# 予測を発行した時刻までに入手できた観測値だけを選びます。
eligible = df.loc[
(df["available_at"] <= issue_time)
& (df["timestamp"] <= latest_hour)
].copy()
eligible = eligible.sort_values("timestamp").tail(CONTEXT_LENGTH)
if len(eligible) != CONTEXT_LENGTH:
raise ValueError(f"連続した{CONTEXT_LENGTH}時間分のデータが必要です。")
if eligible["timestamp"].iloc[-1] != latest_hour:
raise ValueError("直近の毎時観測値が予測発行時刻までに公開されていません。")
if not eligible["timestamp"].diff().iloc[1:].eq(
pd.Timedelta(hours=1)
).all():
raise ValueError("観測時刻に欠けている時間があります。")
# 予測対象をtarget列に統一します。
# item_idは単一観測所を表す固定IDです。
return pd.DataFrame(
{
"item_id": "station_A",
"timestamp": eligible["timestamp"].to_numpy(),
"target": eligible["rainfall_mm"].to_numpy(dtype=float),
}
)
def run_forecast(context: pd.DataFrame) -> pd.DataFrame:
"""事前学習済みChronos-2で3時間先の分位点予測を行います。
Args:
context: prepare_context()が返した168時間の長形式データ。
Returns:
予測日時、代表予測値、0.1/0.5/0.9分位点を含む表。
"""
device = "cuda" if torch.cuda.is_available() else "cpu"
pipeline = Chronos2Pipeline.from_pretrained(
"amazon/chronos-2",
device_map=device,
)
forecast = pipeline.predict_df(
context,
prediction_length=PREDICTION_LENGTH,
quantile_levels=QUANTILES,
id_column="item_id",
timestamp_column="timestamp",
target="target",
freq="h",
)
return forecast
def main() -> None:
"""CLIからデータと予測発行時刻を指定して結果を保存します。"""
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--csv", type=Path, default=Path("rainfall.csv"))
parser.add_argument("--issue-at", required=True, help="例: 2026-09-01 09:10:00")
parser.add_argument("--output", type=Path, default=Path("forecast.csv"))
args = parser.parse_args()
observations = load_observations(args.csv)
context = prepare_context(observations, args.issue_at)
forecast = run_forecast(context)
# 後から実測値と突き合わせるため、予測起点も保存します。
forecast.insert(0, "origin_timestamp", context["timestamp"].iloc[-1])
forecast.insert(0, "issued_at", pd.Timestamp(args.issue_at))
args.output.parent.mkdir(parents=True, exist_ok=True)
forecast.to_csv(args.output, index=False)
print(forecast.to_string(index=False))
print(f"保存先: {args.output}")
if __name__ == "__main__":
main()
実行例は次のとおりです。
python chronos2_zero_shot.py \
--csv rainfall.csv \
--issue-at "2026-09-01 09:10:00" \
--output results/forecast_20260901_0910.csv
forecast.csvには、予測した時刻、代表予測値、各分位点に加え、発行時刻と予測起点を保存します。これらを残しておくと、後で実測値と照合したり、1時間先・2時間先・3時間先の誤差を区別したりできます。
このコードは単一観測所のZero-shot推論例です。入力CSVの整合性検査と予測呼び出しを示したもので、バックテストやMSMとの統合処理までは含みません。推論そのものはここでは未実行であり、実モデルを利用した精度は検証していません。
8. 比較実験は入力条件を段階的に増やす
基盤モデルを導入した効果を知るためには、すべての情報を最初から投入するよりも、段階的に比較した方が結果を読み取りやすくなります。
| ケース | モデル | 入力情報 | 何を調べるか |
|---|---|---|---|
| A | XGBoost | 地点Aの過去雨量 | 学習型の単変量基準 |
| B | Chronos-2 Zero-shot | 地点Aの過去雨量 | 事前学習モデルの単変量性能 |
| C | TimesFM-3 Zero-shot | 地点Aの過去雨量 | 基盤モデル間の違い |
| D | XGBoost | 地点A+周辺地点B・C | 周辺情報の寄与 |
| E | Chronos-2 Zero-shot | 地点A+周辺地点B・C | 多変量Zero-shotの寄与 |
| F | Chronos-2 Zero-shot | 複数地点+MSM | 外生変数の寄与 |
| G | XGBoost | 複数地点+MSM | 同じ情報を与えた学習型との比較 |
A~Cで単一観測所の比較を行い、D・Eで周辺地点を追加します。最後にF・GでMSMの予報値も使います。これなら、モデルを変えた効果と情報を増やした効果を混同しにくくなります。
評価は全期間の平均に加えて、豪雨イベント別、地点別、予測先行時間別にも分けます。イベント中の連続した予測は互いに相関しているため、統計的な差を調べる際には、時間ごとの誤差をすべて独立とみなさず、イベント単位などで再標本化する方法も検討するとよいでしょう。
評価条件を記録しておく
記事の比較結果を再現できるように、少なくともモデル名・モデル重みのリビジョン、入力系列長、予測起点、使用した観測・予報データの版、欠測処理、評価指標、推論ハードウェアを記録します。
さらに、Zero-shot推論でも入力系列長や量子化精度、後処理の設定を変えると結果が変わります。テスト期間で繰り返し調整して最良の値を報告すると、追加学習していなくても評価が楽観的になります。設定を選ぶ期間と、最終的に性能を報告する期間は分けておきます。
9. Zero-shotの結果をどう読むか
例えば、単変量ではChronos-2の精度が低く、多変量で改善したなら、周辺地点の情報が役立った可能性があります。MSMを加えるとさらに改善するなら、観測雨量の過去パターンに数値気象予報の情報を足す意味があったと考えられます。
逆に、平均誤差は小さくても強雨を見逃す場合があります。時系列基盤モデルが平常時の値をうまく捉えている一方、防災上の重要な事象を十分に予測できていない可能性があります。
どちらの場合も、一つの指標だけで結論を出すのは避けたいところです。予測対象の特性、外生変数の利用、評価期間、強雨の事例数を揃えて、改善が再現するかを確認します。
おわりに
Zero-shot時系列基盤モデルは、従来の統計モデルや機械学習を置き換えるというより、新しい予測対象に対して、まず比較の基準を作る方法として考えると使いやすいと思います。
一度も学習していない観測所にChronos-2を適用し、予測がどの程度当たるのかを調べます。その結果をXGBoostや状態空間モデルと比較し、周辺観測所やMSMを追加したときの変化を確かめます。大切なのは、同じ情報と同じ評価期間で比較し、予測の良し悪しを強雨の見逃しや不確実性も含めて判断することです。
この記事では、まず単一観測所のZero-shot推論までを扱いました。次は、複数観測所をChronos-2へ入力する方法と、MSMの予報値を使う外生変数付き予測へ進むと、従来モデルとの差をより詳しく調べられます。
参考資料
- GIFT-Eval(Salesforce AI Research) — Zero-shotの区分、事前学習データの重複、評価手順。
-
Chronos公式GitHub(Amazon Science) — Chronos-2の仕様、インストール、
predict_df()の実装例。 - TimesFM-3: A zero-shot foundation model for multivariate forecasting(Google Research、2026年8月31日) — モデルの概要と評価。
- TimesFM公式GitHub(Google Research) — モデル重みのライセンスと利用方法。
- MOMENT公式GitHub — 分類・異常検知・補完・予測のタスク構成。
- Nixtla公式ドキュメント — TimeGPTのAPIと提供形態。




