この記事は Zenn に投稿したものの再掲です。初出: 過学習は「特徴量を減らせば直る」——それ、嘘でした
系列トレードの検証で踏む罠を扱う連載の第4回(山場)です。第1回(未来参照バグ)・第2回(特徴量リーク)・第3回(ライブ/BT乖離)の続きです。
前回までの3回で、検証は信用できるものになりました。時間の漏れ(第1回・未来参照)も情報の漏れ(第2回・特徴量リーク)もなく、ライブとの差も説明できる(第3回)。武器は揃った。
その正しい検証ハーネスで、いよいよパラメータ探索を回します。そして——それでも過学習します。
ここからが本題です。敵はもう、データの中にもコードの中にもいません。探索のやり方と、評価の構造の中にいます。このシリーズの山場、過学習との戦いです。
なぜ相場のMLはこれほど過学習するのか
まず、相場のMLが構造的に過学習しやすい理由を3つ。
- 低いシグナル/ノイズ比。 値動きはほとんどがノイズです。柔軟なモデルは、わずかなシグナルではなく、大量のノイズの方を覚えます
- 非定常(レジーム変化)。 過去に効いた関係が、未来も効く保証がない。学習した相関が賞味期限切れになります
- 探索による多重検定。 これが一番効きます。多数の設定を試して「検証で一番良かったもの」を選ぶと、その"一番"には必ず運が混じります
3番目が本記事の中心です。でもその前に、多くの人(私も含む)が最初に試して、そして裏切られる対策の話をします。
一番効かなかった対策:特徴量削減
直感はこうです。「特徴量が多い→モデルの自由度が高い→過学習する。だから特徴量を減らせばいい。」
私もそう考えて、特徴量を大きく削りました(仮に40個から15個へ)。過学習は、これで確実に減るはずだった。
結果は——ほとんど減りませんでした。
これは、当時いちばん重要な発見でした。もしモデルの容量(自由度)が過学習の主犯なら、容量を削れば効くはずです。効かなかったということは、主犯は特徴量の数ではないということ。
過学習の源は、モデルの容量よりも、評価の構造と探索のプロセスの側にありました。これが分かると、「正則化を強める」「特徴を減らす」「木を浅くする」といった容量いじりが、なぜ限定的にしか効かないのかが腑に落ちます。すべて対症療法だからです。真犯人は別にいます。
本当の敵:探索そのもの(選択バイアス)
多数の設定を検証で評価し、最良を選ぶ。この「選ぶ」行為そのものが過学習を生みます。
コインを100人に投げさせれば、10連続で表を出す人が必ず現れます。その人を「表を出す才能がある」と採用するのが、素朴なパラメータ探索です。選ばれた設定の検証スコアは、真の実力より楽観的に偏ります。試行回数が増えるほど、偶然の勝者が生まれやすくなる。これが多重検定・選択バイアスです。
だから、こう認識を改める必要があります。
検証スコアが良いこと自体は、もう証拠になりません。
必要なのは「その良さが運ではない」ことを示す仕組みです。以下、その仕組みを装置として重ねていきます。
対策の体系:運を無効化する装置を重ねる
1. 正しい時間評価(walk-forward + purge/embargo)
第2回の続きです。時系列を尊重した分割で、学習と検証が未来を跨がないようにする。重なるラベルはpurgeし、境界にembargoを置く。ここが崩れていると、以降の装置は全部無意味になります。土台です。
2. 一度も触っていないホールドアウト
探索にもチューニングにも一度も使わない最終区間を確保します。すべての探索が終わった後、その区間で初めて「本番相当」を測る。
# 探索・チューニング中は絶対に参照しない
holdout = data[data.index >= HOLDOUT_START]
search_pool = data[data.index < HOLDOUT_START]
# ...全ての探索が終わってから、最後に一度だけholdoutで評価する
鉄則は、探索中に一度でも覗いたら、それはもうホールドアウトではないこと。「ちょっとだけ見て調整」を許した瞬間、ホールドアウトは汚染されます。
3. 複数指標の同時ゲート
単一の数字で合否を決めない。これが効きます。たとえば「正規化したPnL」と「素のPnL」の両方が同時に条件を満たすことを要求する。
# 片方の指標だけで合格にしない。複数の独立な指標の同時通過を要求する
passed = (holdout["pnl_normalized"] >= 0) and (holdout["pnl_raw"] >= 0)
片方の指標だけで良く見える設定は、その指標に過適合しているだけのことが多い。独立な複数の物差しを同時に満たす設定は、運だけでそこに到達しにくくなります。ゲートを増やすほど、偶然の通過は難しくなる。
4. 異常値の無効化
「プロフィットファクターが異常に高い × 取引数が極端に少ない」は、勝利ではなくノイズです。これを採用候補から積極的に外します。
def score_stats(stats):
# 取引数が少なすぎる結果は統計的に無意味なので無効化
if stats["trades"] < MIN_TRADES:
return -np.inf
# PFが異常に高い × 取引数が少ない = 偶然のヒット。採用しない
if stats["pf"] >= PF_CAP and stats["trades"] < LOW_TRADE_GUARD:
return -np.inf
return stats["score"]
好成績を「無効化する」ロジックをわざと書く、という発想が肝です。探索は放っておくと必ずこういう偶然のヒットを拾い上げるので、こちらから先回りして潰します。
5. 取引数の下限
上と重なりますが独立に重要です。数件の取引で出た好成績は、統計的に何も言っていません。最低試行回数を満たさない設定は、そもそも評価の土俵に上げない。
6. サンプル重みで偏りを均す
特定の時間帯やレジームだけに過適合して勝っている場合、その領域のサンプル重みを段階的に調整し、一部の局面に頼った勝ちを抑えます。「全体で薄く勝つ」方向へ寄せることで、特定局面への依存を減らせます。
マインドの転換(ここが核心)
ここが、このシリーズ全体で一番伝えたいことです。過学習対策の本質は、テクニックの寄せ集めではありません。目的の再定義です。
- 最良パラメータを探すのをやめる。頑健性を証明する作業に切り替える。 探索は「一番良いのはどれか」を探す営みではなく、「運に頼らず安定して条件を満たすのはどれか」を探す営みになります
- 合格基準は固定する。動かさない。 成績が基準に届かないとき、基準を下げて通すのは自分を騙す行為です。上げるべきは基準ではなく、トレードの質の方
- 対症療法より根本原因。 「正則化を上げたら過学習が減った」で満足しない。なぜ過学習していたのか(多重検定か、リークか、レジーム依存か)を突き止める。特徴量削減が効かなかった話は、まさにこの姿勢から生まれました
- 結論の前に、定量的な証拠。 「たぶん効いた」で採用しない。ホールドアウトの数字と、複数指標での一致を要求する
この4つは、私が開発の最初期から一貫して守っている原則です。派手なモデルより、この規律の方が、システムの生死を分けました。
まとめ
- 相場のMLが過学習する構造的理由は、低S/N・非定常・多重検定
- 「特徴量を減らせば直る」は限定的にしか効かない。主犯はモデルの容量ではなく、評価構造と探索
- 本当の敵は探索の選択バイアス。検証スコアが良いこと自体は、もう証拠にならない
- 対策は装置を重ねる:正しい時間評価/触らないホールドアウト/複数指標ゲート/異常値の無効化/取引数の下限/サンプル重み
- 核心は目的の再定義——最良を探すのをやめ、頑健性を証明する
ここまでの4回で、検証が嘘をつく主要なパターン(時間の漏れ・情報の漏れ・ライブ乖離・過学習)を潰してきました。シリーズの背骨は、これで通りました。
次回は箸休めに、これまで何度も顔を出した「タイムスタンプとタイムゾーン」——地味なのに何日も溶かす、あの罠を単独で掘り下げます。
質問や「うちはこの装置で過学習を止めた」という話があればコメントで。連載として、検証が嘘をつくパターンを順に潰してきました。