どこまで遡って学習するか
前回は、 予測の評価が物差し次第で逆転する話でした。今回は学習の側を扱います。
予測モデルを作るとき、過去のどこまでを学習に使うかを決める必要があります。 直近20日か、1年か、5年ぶんか。この長さを「窓(ウィンドウ)」と呼びます。
自己回帰(AR)です。直近3日の変化率から、翌日の変化率を線形回帰で予測します。 LSTMのような複雑なものではありませんが、 「過去から未来を当てる」という構造は同じです。 窓の影響を見るには、単純なほうが分かりやすい。
窓を長くすれば学習データは増えます。ただし古い時期の性質まで混ざります。 短くすれば直近に適応できますが、サンプルが足りません。 どちらが良いのか、測ってみます。
短い窓ほど、成績が良かった
合成データ600日ぶんを用意し、後半400日を評価区間としました。 窓の長さだけを変えて比べます。
| 学習窓 | MAPE | RMSE | 方向一致率 |
|---|---|---|---|
| 20 日 | 1.430 % | 649 | 67.7 % |
| 60 日 | 1.542 % | 697 | 55.4 % |
| 120 日 | 1.537 % | 701 | 57.4 % |
| 180 日 | 1.546 % | 704 | 55.1 % |
20日窓が突出しています。MAPE も RMSE も最良、 方向一致率にいたっては 67.7%。 前回「コイン投げと変わらない」と書いた50%を、大きく上回っています。
解釈もつきました。相場は変わり続けるのだから、直近だけを見るほうが適応が速い—— もっともらしい説明です。実際、そう書きかけました。
学習データに、答えが入っていた
学習サンプルを作るループを見直しました。
// endIdx 時点で、翌日 P[endIdx+1] を予測したい for (let i = endIdx - W + 1; i <= endIdx; i++) { // 特徴量:i 時点までの変化率 const feat = [ logRet(i), logRet(i-1), logRet(i-2) ]; // 目的変数:翌日の変化率 Y.push( logRet(i+1) ); // i=endIdx のとき logRet(endIdx+1) }
ループの最後、i = endIdx のときを見てください。
目的変数が logRet(endIdx+1) になります。
これはまさに予測したい値そのものです。
予測時点では入手できないはずの情報が、学習や特徴量に混入すること。 評価成績だけが良くなり、実運用では再現しません。 時系列では、未来の値を使ってしまう形で起きやすい。
// 最後のサンプルの目的変数が P[endIdx] になるようにする const last = endIdx - 1; for (let i = last - W + 1; i <= last; i++) { const feat = [ logRet(i), logRet(i-1), logRet(i-2) ]; Y.push( logRet(i+1) ); // 最大でも logRet(endIdx) = 既知 }
変えたのはループの終端だけです。1行にも満たない差です。
直したら、結論が逆になった
| 学習窓 | 方向一致率 (リークあり) | 方向一致率 (修正後) | 差 |
|---|---|---|---|
| 20 日 | 67.7 % | 50.9 % | −16.8 |
| 60 日 | 55.4 % | 45.9 % | −9.5 |
| 180 日 | 55.1 % | 46.6 % | −8.5 |
すべて50%前後に戻りました。前回の記事と同じ結論です。 そして20日窓の優位も消えました。
修正後の全体像
| 学習窓 | MAPE | RMSE | 方向 | ナイーブ比 |
|---|---|---|---|---|
| 20 日 | 1.813 % | 821 | 50.9 % | +16.0 % |
| 40 日 | 1.691 % | 764 | 49.9 % | +8.2 % |
| 60 日 | 1.681 % | 762 | 45.9 % | +7.5 % |
| 90 日 | 1.624 % | 744 | 50.4 % | +3.9 % |
| 120 日 | 1.619 % | 742 | 47.6 % | +3.6 % |
| 180 日 | 1.609 % | 736 | 46.6 % | +2.9 % |
| ナイーブ | 1.564 % | 719 | 測定不能 | — |
リークがあったときは、窓の選び方が成績を左右するように見えました。 修正後はそもそも勝てていない。 窓を最適化する以前の問題でした。
窓とリークを、切り替えてみる
学習窓を動かし、リークの有無を切り替えられます。 合成データと実データのどちらでも試せます。
リークありでは、短い窓が有利に見えます。 切り替えると、その優位が消えます。 点線はナイーブ予測の成績です。修正後は、どの窓もこれを超えられません。
なぜ気づきにくいのか
リークはエラーを出しません。計算は最後まで走り、 それらしい数値が返ります。むしろ期待どおりの、良い数値が。
| 起きること | 見え方 |
|---|---|
| 成績が良くなる | 「モデルがうまく学習した」 |
| 窓が短いほど顕著 | 「直近への適応が効いている」 |
| 実運用では再現しない | 気づくのは、動かした後 |
しかも解釈がつきやすいのが厄介です。 「短い窓が良い」という結果には、もっともらしい説明を当てはめられます。 説明できてしまうと、疑わなくなります。
窓は、何で決めるべきか
今回は「どの窓もナイーブに勝てない」という結論でしたが、 勝てるモデルがある場合に窓をどう選ぶかは別の問題です。
| 考え方 | 内容 |
|---|---|
| サンプル数 | パラメータ数の10倍以上は欲しい。3変数なら最低30〜50点 |
| 性質の安定性 | 窓の中で相場の性格が変わっていないか |
| 検証で決める | 学習・検証・テストを時間順に分け、検証で窓を選ぶ |
| やってはいけない | テスト成績が最も良い窓を選ぶ(それは調整であって評価ではない) |
まとめ
窓の長さを変える実験のつもりが、自分の実装を検算する話になりました。 前回書いた「良い数字が出たら測り方を疑う」が、そのまま今回に効いています。
この記事のまとめ
- 学習窓を変えると、同じモデル・同じデータでも成績が動く。
- 短い窓ほど良く見えたのは、学習データに答えが1つ混じっていたから。
- 窓が短いほど、その1サンプルの比重が大きくなる(20日窓なら5%、180日窓なら0.6%)。
- 修正すると方向一致率が 67.7% → 50.9%。20日窓の優位も消えた。
- 修正後はどの窓もナイーブ予測に負ける。窓を最適化する以前の問題。
- 長い窓ほどマシなのは、予測が「前日と同じ」に近づくから。
- リークはエラーを出さず、良い数値を返す。しかも解釈がつきやすい。
- 窓を選ぶ区間と、成績を測る区間は分ける。同じ区間で選べば、それは評価ではない。
- 予測は、当たっているのか
「精度」という言葉が隠すもの - 学習期間を変えると、答えが変わる(この記事)
窓幅の選び方と、1行のリーク - 「異常」は、誰が決めたのか
閾値の恣意性




