TimesFM 2.5を使って東京の気温とVTI株価をゼロショット予測する

初めに

今回は、Google Researchが公開した時系列基盤モデル TimesFM 2.5 を使って、東京の日次気温(最低・最高)とVTI(米国株ETF)の終値をゼロショットで予測する方法を紹介します。

TimesFMはLLMと同じデコーダーオンリーのTransformerアーキテクチャを時系列に適用したモデルで、事前学習済みの重みをそのまま使い、学習データなしで任意の時系列を予測できます。2.5では200Mパラメータに軽量化されつつコンテキスト長が16,384に拡張され、連続分位ヘッド(continuous quantile head)による確率的予測にも対応しています。

今回の実験では、気温のような強い季節性を持つデータと、株価のようなランダムウォーク的なデータの両方を予測し、モデルの予測精度や不確実性の表現がどう変わるかを確認します。

github.com

開発環境

項目 バージョン
OS Windows 11
Python 3.12.7
パッケージマネージャ uv 0.9.2
PyTorch 2.11.0+cpu
timesfm 2.0.0 (editable install)
GPU なし(CPU推論)

環境構築

Python バージョンの注意

PyTorch 2.11.0はPython 3.13.8のASTパーサとの間に非互換があり、import torch時に以下のエラーが発生します。

IndentationError: expected an indented block after function definition on line 4

Python 3.12系を明示指定する必要があります。

仮想環境の作成

cd timesfm
uv venv --python 3.12
source .venv/Scripts/activate

パッケージのインストール

TimesFM本体をPyTorchバックエンドでeditable installし、データ取得・可視化用のパッケージも合わせてインストールします。

# TimesFM本体 + PyTorchバックエンド
uv pip install -e ".[torch]"

# データ取得・可視化
uv pip install meteostat yfinance matplotlib pandas

インストールの確認

python -c "import torch; print('torch', torch.__version__); import timesfm; print('timesfm ok')"
torch 2.11.0+cpu
timesfm ok

データの取得

東京の気温(meteostat)

meteostatを使って東京の気象ステーション(WMO ID: 47662)から過去6年分の日次データを取得します。

なお、meteostat 2.xでは旧バージョンのfrom meteostat import Daily, Pointが使えなくなっており、from meteostat import daily(小文字の関数)を使用します。また、Point(lat, lon)での地理検索はProviderの対応状況によって空になることがあるため、ステーションIDを直接指定するのが確実です。

from datetime import datetime, timedelta
from meteostat import daily

TOKYO_STATION = "47662"

end = datetime(2026, 4, 12)
start = end - timedelta(days=365 * 6)

df = daily(TOKYO_STATION, start, end).fetch(fill=True)
temps = df[["tmin", "tmax"]].interpolate(method="linear").ffill().bfill()
2191 daily rows, 2020-04-13 -> 2026-04-12

VTI株価(yfinance)

yfinanceで過去6年分のVTI終値を取得します。auto_adjust=Trueで調整済み終値を使います。

import yfinance as yf

df = yf.download("VTI", start=start, end=end, auto_adjust=True, progress=False)
# yfinanceはMultiIndexを返すことがあるのでフラットにする
if isinstance(df.columns, pd.MultiIndex):
    df.columns = df.columns.get_level_values(0)
vti = df[["Close"]].rename(columns={"Close": "close"})
1507 trading days, 2020-04-13 -> 2026-04-10

モデルの読み込みとコンパイル

重みのダウンロード

from_pretrainedでHugging Face Hubからmodel.safetensors(約800MB)が自動ダウンロードされます。2回目以降は~/.cache/huggingface/のキャッシュから読み込まれます。

import torch
import timesfm

torch.set_float32_matmul_precision("high")

model = timesfm.TimesFM_2p5_200M_torch.from_pretrained(
    "google/timesfm-2.5-200m-pytorch"
)

ForecastConfigによるコンパイル

model.compile()ForecastConfigを渡して推論の設定を行います。max_contextmax_horizonは内部でパッチサイズの倍数に自動調整されます。

model.compile(
    timesfm.ForecastConfig(
        max_context=2048,                    # 入力系列の最大長
        max_horizon=60,                      # 予測ステップ数(約2ヶ月)
        normalize_inputs=True,               # 入力のスケール正規化
        use_continuous_quantile_head=True,    # 分位予測ヘッドを有効化
        force_flip_invariance=True,          # 負のスケーリングにも線形不変性を保証
        infer_is_positive=True,              # 入力が非負なら出力も非負にクランプ
        fix_quantile_crossing=True,          # 分位の交差を修正
    )
)

主要パラメータの解説です。

パラメータ 説明
max_context モデルに渡す過去データの最大長。短い系列はゼロパディング、長い系列は切り詰め
max_horizon 予測する未来のステップ数
use_continuous_quantile_head Trueにすると点予測に加えて分位予測(mean, q10〜q90)が返る
force_flip_invariance TimesFMはf(aX+b) = a*f(X)+ba≥0で保証。このフラグでa<0にも拡張
infer_is_positive 株価のように常に正の系列で負の予測を防ぐ

推論の実行

各系列を1次元のnumpy arrayとしてリストで渡し、model.forecast()を呼びます。

import numpy as np

inputs = [
    temps["tmin"].to_numpy(dtype=np.float32),
    temps["tmax"].to_numpy(dtype=np.float32),
    vti["close"].to_numpy(dtype=np.float32),
]

point_forecast, quantile_forecast = model.forecast(horizon=60, inputs=inputs)

# point_forecast.shape    = (3, 60)       — 3系列 × 60ステップ
# quantile_forecast.shape = (3, 60, 10)   — [mean, q10, q20, ..., q90]

3系列を一括で予測しています。quantile_forecastの10列はそれぞれmean, q10, q20, ..., q90に対応しています。

可視化

matplotlibで日本語ラベルを文字化けなく描画するため、Windows標準のMeiryoフォントを指定し、マイナス記号の文字化けも防止します。

import matplotlib as mpl
import matplotlib.pyplot as plt

mpl.rcParams["font.family"] = "Meiryo"
mpl.rcParams["axes.unicode_minus"] = False

描画では過去1年分の実績と予測線・予測区間を重ねてプロットしています。以下は最低気温の例です。

fig, ax = plt.subplots(figsize=(12, 5))

# 過去1年分の実績
hist = temps["tmin"].iloc[-365:]
ax.plot(hist.index, hist.values, color="#1f77b4", label="実績")

# 予測(平均)
future_idx = pd.date_range(temps.index[-1] + pd.Timedelta("1D"), periods=60, freq="D")
ax.plot(future_idx, point_forecast[0], color="#d62728", label="予測(平均)")

# 予測区間 q10-q90
q10 = quantile_forecast[0, :, 1]
q90 = quantile_forecast[0, :, 9]
ax.fill_between(future_idx, q10, q90, alpha=0.15, color="#d62728", label="予測区間 q10-q90")

ax.set_title("東京 日次最低気温 — TimesFM 2.5 予測(今後60日間)")
ax.set_ylabel("最低気温 (℃)")
ax.set_xlabel("日付")
ax.legend()
fig.savefig("tokyo_tmin_forecast.png", dpi=140)

実行結果

2026年4月12日を基準に60日先を予測した結果です。

系列 予測初日 → 60日後(平均) 60日後の不確実性(q10-q90)
東京 最低気温 12.9℃ → 20.8℃ 17.5 - 24.2 ℃
東京 最高気温 20.7℃ → 27.5℃ 23.0 - 31.4 ℃
VTI 終値 $334.8 → $340.8 $315.8 - $357.5

東京 最低気温

4月中旬から6月中旬にかけて滑らかに上昇するカーブが出ており、梅雨入り前の昇温を再現しています。予測区間は±3℃程度と狭く、モデルが高い確信度を持っていることが分かります。

東京 最高気温

最低気温と同様に季節性を正しく捉えています。60日後の予測平均は27.5℃で、6月上旬の東京としては妥当な値です。

VTI 終値

予測平均はほぼ横ばい(+1.8%)ですが、予測区間は$315〜$358と気温の5倍以上に広くなっています。株価のランダムウォーク的な性質を分位帯の広さで正直に表現しており、点予測だけでは意味がなく、予測区間を見て初めて「方向予測が困難であること」をモデルが示していると読み取れます。

トラブルシューティング

今回の実験でハマったポイントを3つ記録しておきます。

Python 3.13でimport torchが失敗する

PyTorch 2.11.0のJITコンパイラがPython 3.13.8のASTパーサと非互換です。uv venv --python 3.12でPython 3.12系を使います。

meteostatでImportError: cannot import name 'Daily'

meteostat 2.xではAPIが変更されています。

# 旧 (1.x)
from meteostat import Daily, Point

# 新 (2.x)
from meteostat import daily

dailyは関数として呼び出し、.fetch()でDataFrameを取得します。

meteostatのPoint()でデータが0件になる

Point(lat, lon)での地理検索はProvider依存で空になることがあります。ステーションIDを直接指定するのが確実です。

# 空になることがある
daily(Point(35.6762, 139.6503, 44), start, end)

# 確実に取得できる
daily("47662", start, end)  # 47662 = 東京 WMO ID