グレードごとにモデルを分けると競艇AI精度が上がった話

PR本記事はアフィリエイト広告を含みます。リンク経由でのご購入により運営者に成果報酬が支払われることがありますが、読者への価格や条件は変わりません。

競艇予想AIを自作して半年ほど運用しているのですが、ある時点から「全レース1つのモデル」では精度が頭打ちになりました。試行錯誤の末、グレード別にモデルを分けたら的中率が概算で8%ほど改善したので、その実験記録を残しておきます。

結論:グレード別にモデルを分けたら的中率が約8%上がった

先に結果から書きます。3連単の的中率(買い目10点固定)は、単一モデル運用時で概算11%前後だったものが、SG / G1 / G2・G3 / 一般戦の4モデルに分けたあとは19%前後まで上がりました。回収率も80%台前半から90%台後半まで改善しています(直近2ヶ月・約600レースでの集計、目安値)。

ポイントは「モデルを増やしたから当たるようになった」のではなく、「グレードごとに違う前提を、別々の関数で学習させたから無理がなくなった」という点でした。これに気づくまでに数ヶ月かかりました。

全グレード1モデルで運用していた頃の限界と精度の頭打ち

最初の構成はLightGBMの2値分類(1着になるか否か)を1モデルで全レース捌くシンプルな設計でした。特徴量は約120個、学習データは過去3年分で約30万件です。

最初の3ヶ月は順調にスコアが伸びていったのですが、ある時点から「validation lossは下がるのに、本番の的中率が伸びない」現象が出てきました。CV(交差検証)のAUCは0.78あたりで安定するのに、SGだけ妙に外す。一般戦は逆に当たりすぎる。月ごとのバラつきが大きくて、回収率の標準偏差が広がっていく感覚でした。

原因はおそらく「平均的に良いモデル」を作ろうとして、レース性質の違いを丸めてしまっていたことです。

SG・G1・G2/G3・一般戦で「効く特徴量」がまるで違った話

各グレードでSHAP値を出して特徴量重要度を比較したら、想像以上に違いました。私のケースでは以下のような傾向です(あくまで自分のデータでの傾向です)。

  • SG・G1: 直近3ヶ月の勝率、優出回数、当地勝率の重要度が突出
  • G2・G3: モーター2連率、節間成績、進入想定の重要度が高い
  • 一般戦: 階級(A1/A2/B1/B2)と枠なり進入確率がほぼ全てを説明してしまう

つまり一般戦では「A1選手が1号艇」というだけで決着の半分が説明できるのに、SGだとほぼ全員A1なので階級情報がノイズになる。これを1モデルで扱うのは無理があったわけです。

グレード別モデルの設計:4分割したアーキテクチャと学習データの切り分け方

最終的に採用した構成はこんな形です。

予想入力(出走表)
   ↓
グレード判定(SG / G1 / G2・G3 / 一般戦)
   ↓
   ├─ model_sg.pkl       (学習データ: 約8,000件)
   ├─ model_g1.pkl       (学習データ: 約25,000件)
   ├─ model_g2g3.pkl     (学習データ: 約45,000件)
   └─ model_ippan.pkl    (学習データ: 約220,000件)

工夫した点は3つです。

  1. 特徴量セットもグレード別に変えた: 一般戦は階級ダミーを残し、SGは階級を削除して節間成績の細かい派生特徴量を追加
  2. クラスバランスをグレード内で再調整: SGはサンプルが少ないので、SMOTEではなくclass_weightで対応
  3. ハイパラもグレード別にOptunaでチューニング: SGだけ深い木(max_depth=10)、一般戦は浅め(max_depth=6)が効きました

A/Bテスト結果:単一モデル vs グレード別モデルの的中率・回収率を比較

直近2ヶ月、同じ買い方(予測上位10点・3連単フォーメーション)で並走させた結果が以下です(目安値・私の環境での集計)。

指標 単一モデル グレード別4モデル
3連単的中率 約11.2% 約19.4%
回収率 約83% 約97%
SG的中率 約6% 約14%
一般戦的中率 約13% 約21%

特にSGの改善幅が大きかったのが印象的でした。母数が少ないので参考値ですが、「外しまくっていた一番荒れる場面」が改善されたのは体感としてもわかるレベルでした。

失敗した分割パターン:会場別・距離別では精度が下がった理由

実は最初、「会場別24モデル」も試しました。これは見事に失敗しています。1会場あたりの学習データが少なすぎて過学習し、CVスコアは上がるのに本番で総崩れ。住之江と平和島は何とかなりましたが、地方場はサンプル不足で逆効果でした。

「距離別」もやってみたのですが、競艇はそもそも全レース1,800mなので意味がなく、これは設計段階のミスでした(ボートレースに距離別レースは無いに等しい、というのを後から知りました)。

教訓: 分割は「学習データが各分割内で十分残ること」と「分割境界で挙動が本当に変わること」の両方が必要。会場別はサンプル不足、距離別はそもそも変動要因ではなかった、という典型例でした。

次に試すこと:選手級別×グレードのさらなる細分化と注意点

次の実験は「グレード × 1号艇選手の階級」でさらに分割するパターンです。具体的には一般戦モデルを「1号艇A1」「1号艇A2以下」の2つに分ける案を検討中です。一般戦は1号艇の階級で決着パターンが大きく変わるので、ここを分けるとさらに精度が伸びる気がしています。

ただし注意点が2つあります。

  • 分割しすぎるとサンプル不足で過学習する: 各モデルの学習データは最低5,000件は確保したい
  • 運用が複雑になる: モデル数が増えるほどデプロイ・再学習・モニタリングのコストが上がる

「精度が上がるなら何でも分けるべき」ではなく、「分けても十分な学習データが残るか」を先に計算してから実装する、というのが今回の最大の学びでした。

まとめ

競艇AIを「全レース1モデル」で運用すると、グレードごとの前提の違いに引っ張られて精度が頭打ちになります。私の場合はSG / G1 / G2・G3 / 一般戦の4分割で的中率が概算8%改善しました。一方で会場別・距離別の分割はサンプル不足や設計ミスで失敗しました。

機械学習で「モデルを分ける」判断をする時は、分割後にも十分な学習データが残るか・分割境界で本当に挙動が変わるかを先に確認してから実装するのが安全です。次は選手階級も含めた細分化を試しつつ、運用コストとのバランスも見ていく予定です。

関連ツールを見る

この記事で紹介したツール・サービスをまとめてチェック。

おすすめ

エックスサーバー

国内シェアNo.1のレンタルサーバー。WordPressブログをすぐに始められる。このブログも実際にXserverで運営しています。

Xserverを見てみる →

ムカイ
この記事を書いた人

ムカイ

個人事業主エンジニア。C#フルリモート案件に参画しながら、Claude Codeを使ってAI×副業の自動化・コンテンツ制作を実践中。「稼ぐ仕組みを作るのが好き」がモットー。

コメントを残す