概要
前回に引き続き、実装のポイントと備忘録に残す。
- 実施期間: 2025年12月
- 環境:Ubuntu22.04LTS
- Python: condaのPython3.12
使用パケージ
import os
# os.environ["CUDA_LAUNCH_BLOCKING"] = "1"
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
from time import time
import numpy as np
import pandas as pd
from autogluon.timeseries import TimeSeriesDataFrame, TimeSeriesPredictor
import matplotlib.pyplot as plt
import plotly.graph_objects as go
AutoGluon用のDataset準備
df = pd.read_parquet('1348.T_merge.parquet')
df.index = pd.to_datetime(df.index).tz_localize(None).normalize()
df = df.reset_index().rename({'Date': 'timestamp'}, axis=1)
# item_id の追加
# このデータフレーム全体が1つの時系列(1348.T)であることを明示
df["item_id"] = "1348.T"
# agはknown_covariateとして既知の将来の共変数を追加可能
df["iso_week"] = df["timestamp"].dt.isocalendar().week.astype(int)
df["financial_quarter"] = df["timestamp"].dt.quarter
train_df, test_df = df[df['timestamp']<'2025-02-01'], df[df['timestamp']>='2025-02-01']
print(len(train_df), len(test_df))
# Long Formへ変換
# Wide Form (train_df / test_df) から直接読み込む
train_data = TimeSeriesDataFrame.from_data_frame(
train_df,
id_column="item_id",
timestamp_column="timestamp"
)
test_data = TimeSeriesDataFrame.from_data_frame(
test_df,
id_column="item_id",
timestamp_column="timestamp"
)
ちなみにtrain_dataはこんな感じ。
Open High Low Close Volume ... Low_SP close_sp close_vix iso_week financial_quarter
item_id timestamp ...
1348.T 2009-05-13 689.709351 689.709351 689.709351 689.709351 0.0 ... 882.799988 883.919983 33.650002 20 2
2009-05-14 669.553467 669.553467 669.553467 669.553467 0.0 ... 882.520020 893.070007 31.370001 20 2
2009-05-15 682.726851 684.278503 681.175200 684.278503 8840.0 ... 878.940002 882.880005 33.119999 20 2
2009-05-18 667.210449 667.210449 665.658797 667.210449 3830.0 ... 886.070007 909.710022 30.240000 21 2
2009-05-19 681.175248 681.175248 679.623596 679.623596 1140.0 ... 905.219971 908.130005 28.799999 21 2
... ... ... ... ... ... ... ... ... ... ... ...
2025-01-27 2831.907535 2841.283078 2812.662999 2816.610596 72930.0 ... 5962.919922 6012.279785 17.900000 5 1
2025-01-28 2804.274276 2832.894355 2789.964237 2814.636719 87280.0 ... 5994.629883 6067.700195 16.410000 5 1
2025-01-29 2828.453277 2839.309169 2826.479479 2835.361572 19450.0 ... 6012.959961 6039.310059 16.559999 5 1
2025-01-30 2825.986021 2845.230556 2824.999121 2841.282959 20000.0 ... 6027.459961 6071.169922 15.840000 5 1
2025-01-31 2843.256823 2855.099614 2834.868179 2852.138916 41720.0 ... 6030.930176 6040.529785 16.430000 5 1
Training
prediction_length: 任意のhorizon長を指定する。predictするときはこの長さがforecastとして出力される。
freq: Pandasの仕様による。
known_covariates_names: 必要であればlist型でDataFrame中のknown_covariatesを指定する。
eval_metric: オフィシャルサイトに丁寧にまとめられているので用途に合わせて選択する。Lossは望大特性という珍しい仕様のため内部で-1がかけられる。
presets: 予め用意されているモデルのセットを指定する。
time_limit: 秒で上限を指定する。指定がなければ全モデルのtrainingが完了するまで返らない。
if refit:
# train_data に含まれる他の列 (Open, High, Low, Volume) は自動的に説明変数として扱われる
predictor = TimeSeriesPredictor(
prediction_length=5,
freq="B", # Business day frequency
path="autogluon-1348T-Close",
target="Close", # 予測対象の列名を指定
known_covariates_names=["iso_week", "financial_quarter"],
eval_metric="MASE", # Mean absolute scaled error
)
predictor.fit(
train_data,
presets="medium_quality",
time_limit=600,
)
else:
# fit済みのモデルを読み込み。
predictor = TimeSeriesPredictor.load("autogluon-1348T-Close")
print(predictor.model_names())
AutoMLとして使用されるモデルはココの通り。presetを使いたくなければ下記のように辞書型で任意にモデル選択や、ハイパラの指定が可能。ただし公式がいうようにハイパラの指定は推奨されていない。
また、GPUがない環境ではDNN系のモデルは選択されない。
predictor.fit(
train_data,
hyperparameters={
"Chronos": [
{"model_path": "bolt_small"}, # 軽量版
{"model_path": "bolt_base"}, # 標準版
{"model_path": "large"} # 高性能版 (注意: "bolt_large"ではない)
]
},
# enable_ensemble=True # 3つのChronosのいいとこ取りをするならTrue
)
trainingが始まると下記のように表示される。GPUはRTX-3090Tiを使用。
Beginning AutoGluon training...
AutoGluon will save models to '/home/ihmon/Documents/ML/ag/autogluon-1348T-Close_1130'
=================== System Info ===================
AutoGluon Version: 1.4.1b20251025
Python Version: 3.12.12
Operating System: Linux
Platform Machine: x86_64
Platform Version: #88~22.04.1-Ubuntu SMP PREEMPT_DYNAMIC Tue Oct 14 14:03:14 UTC 2
CPU Count: 24
Pytorch Version: 2.7.1+cu126
CUDA Version: 12.6
GPU Memory: GPU 0: 23.54/23.54 GB
Total GPU Memory: Free: 23.54 GB, Allocated: 0.00 GB, Total: 23.54 GB
GPU Count: 1
Memory Avail: 55.90 GB / 62.67 GB (89.2%)
Disk Space Avail: 372.33 GB / 811.13 GB (45.9%)
===================================================
Fitting with arguments:
{'enable_ensemble': True,
'eval_metric': MASE,
'freq': 'B',
'hyperparameters': {'Chronos': [{'model_path': 'large'}]},
'known_covariates_names': ['iso_week', 'financial_quarter'],
'num_val_windows': 1,
'prediction_length': 5,
'quantile_levels': [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9],
'random_seed': 123,
'refit_every_n_windows': 1,
'refit_full': False,
'skip_model_selection': False,
'target': 'Close',
'verbosity': 2}
Evaluation
test datasetを使ってpredictor.leaderboard(test_data)でモデルたちの評価を行うことができる。
"medium_quality"を指定したため合計8個のモデルがTrainingされており、各モデルの-1をかけたlossがsocreとして上位から表示されている。一番上の"WeightedEnsemble"が全モデルをアンサンブルしたモデルだが、この場合そのスコアから"Chronos[bolt_small]"だけが選択されたことがわかる。
GPUがない環境だと"Chronos[bolt_small]"と"TemporalFusionTransformer"はTrainingされない。
data with frequency 'IRREG' has been resampled to frequency 'B'.
Additional data provided, testing on additional data. Resulting leaderboard will be sorted according to test score (`score_test`).
model score_test score_val pred_time_test pred_time_val fit_time_marginal fit_order
0 WeightedEnsemble -0.362003 -0.344317 1.248567 2.390552 0.358120 9
1 Chronos[bolt_small] -0.362003 -0.344317 1.248344 2.390552 3.979786 7
2 SeasonalNaive -0.380603 -1.900188 8.074816 1.274556 0.019266 2
3 TemporalFusionTransformer -0.497300 -0.618541 0.072481 0.020987 64.471157 8
4 Theta -0.561908 -1.067661 1.790953 2.100371 0.024960 6
5 ETS -0.587241 -0.915873 1.797062 2.578173 0.016829 5
6 Naive -0.677659 -0.965803 1.258380 6.939918 0.021187 1
7 RecursiveTabular -1.071249 -1.160690 0.090148 0.045300 0.929499 3
8 DirectTabular -1.459341 -6.769379 0.044259 0.055773 0.551997 4
Predict
known_covariates: Trainingでknown_covariates_namesを使用していれば、都度再計算して渡す。
# fitに使ったデータ(train_data)の末尾から、未来のprediction_length分(5日分)を予測
# known_covariateを再計算(毎回predictに渡すたびに追加する仕様みたい)
known_covariates = predictor.make_future_data_frame(train_df) # <- known_covariatesにはprediction_length分の"item_id"と"timestamp"しか入らない
known_covariates["iso_week"] = known_covariates["timestamp"].dt.isocalendar().week.astype(int)
known_covariates["financial_quarter"] = known_covariates["timestamp"].dt.quarter
predictions = predictor.predict(train_data, known_covariates)
predictは次のようにprediction_length分のDataFrameを返却する。
mean 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9
item_id timestamp
1348.T 2025-02-03 2854.435547 2775.269043 2807.301270 2827.823242 2842.539551 2854.435547 2865.813477 2878.108887 2893.184570 2921.000244
2025-02-04 2852.716309 2766.651855 2803.596924 2825.017334 2840.312256 2852.716309 2865.169434 2877.731445 2892.286133 2920.730469
2025-02-05 2866.354004 2767.522461 2809.018311 2834.395996 2851.531250 2866.354004 2879.750977 2892.969238 2908.532715 2938.487793
2025-02-06 2864.736328 2762.536133 2805.918457 2831.386963 2849.533936 2864.736328 2877.556396 2892.081787 2909.668945 2940.818604
2025-02-07 2856.492676 2755.364990 2798.504150 2823.687012 2842.391602 2856.492676 2869.751465 2883.756836 2901.334473 2934.469727
AutoGluonの良いところはQuantile Regressionであるところと、それがLightGBMのようにmedianを挟んで上下対象の正規分布ではなく、非対称分布である点である。
上チャートのようにmedianに対して下ブレリスクがあることを得ることができる。
運用時の使い方
観測のたびにforecast horizonを予測するなら、都度Training datasetに直近の観測値を追加してpredictする。
# onlineを想定した毎日の予測実行シミュレーション
def simuration(train_data, test_data, target_column, prediction_length, predictor):
# シミュレーション用のデータを初期化
current_history = train_data.copy() # これまでの履歴 (最初は学習データのみ)
remaining_test = test_data.copy() # テストデータ (最初はテストデータ全体)
# 20日分(20回)ループして、毎日データを追加しながら予測を行う
for t in range(20):
# テストデータの先頭1行(翌日の確定値)を取得
# .iloc[[0]] とすることで、SeriesではなくDataFrameとして取得し、concat時の型崩れを防ぐ
new_observation = remaining_test.iloc[[0]]
# 履歴データに結合 (Update Context)
current_history = pd.concat([current_history, new_observation])
# テストデータからその行を削除 (次の日のためにプールを減らす)
remaining_test = remaining_test.iloc[1:]
# 最新の履歴に基づいて未来を予測
known_covariates = predictor.make_future_data_frame(current_history)
known_covariates["iso_week"] = known_covariates["timestamp"].dt.isocalendar().week.astype(int)
known_covariates["financial_quarter"] = known_covariates["timestamp"].dt.quarter
predictions = predictor.predict(current_history, known_covariates)
# ログ出力 (進捗確認用)
current_date = new_observation.index[0][1].date()
print(f"Step {t+1}/20: {current_date} のデータを追加 -> 翌日以降を予測しました。")
true_val = remaining_test.iloc[:prediction_length][target_column].to_numpy()
pref_val = predictions.loc[:,"0.5"].to_numpy() # medianで計算
print(f'RMSE: {get_rmse(true_val, pref_val):.3f}')
以上
