Diana Boat

ボートレース(競艇)全場無料予想サイト ダイアナボート(V4.27.83)

ボートレース平和島の水面と観客
Episode.2

生データを特徴量に変える ― 最初の Feature Engineering

生データを特徴量に変換した最初の格闘。

2026年8月12日5

目次

今回は2024年1月〜3月頃の話題になる(執筆は2026年8月)。年末に時間をかけて生データを揃えたあとの作業について記録しておきたい。

名前が思い出せない

当時のコードからどのような生データが存在していたかがわかる。1レースあたり136項目だった。おそらく本家の出走表から取れるデータに環境データを足したもので、1艇あたり21項目 x 6艇 = 126項目、それに環境系の8項目とDynamoDB のキーとなる2項目を足した項目群だ。

racerId, racerAge, racerRank, racerWeight, racerBranch, racerHometown, racerAvST, racerFCount, racerLCount, racerAllWP/All2WP/All3WP, racerLocalWP/Local2WP/Local3WP, racerMotorNo/Motor2WP/Motor3WP, racerBoatNo/Boat2WP/Boat3WP

環境系とキーの項目を見ると以下のような項目だった。

race_date, race_stadium, raceStartTime, temperature, tide, weather, wind_direction, wind_speed

ターゲット(レース結果として利用するデータ)としては、

san_ren_tan_pay, popularity_order, wining_technique, first, ..., sixth

があって、first〜sixth はおそらく着順だろうと思う。この時点ではどのモデルを利用して予想をするかも決まっておらず、ローカルマシン上で様々なモデルを試している段階だと思う。

この当時、おそらくCopilotはもうあったのだろうが、僕はまだ手で全部書いていて、名前付けにも苦労が伺える。san_ren_tan_pay、これ、三連単の払戻し金額のことだ。現在のモデルでもそのまま使ってる。PayPayに引っ張られたのか、我ながら自分がいかに面倒くさがりかがわかる。

僕は名前付けが苦手だ。名前を覚えるのも苦手だ。20代の頃にとんでもないミスを犯したことがある。お客さんとの会議で、何ヶ月も一緒に同じプロジェクトで働いてきた人の名前が出て来ない。真っ昼間の真剣な会議である。顔はもちろん分かるんだ。知ってるよあなたのことは。

「ちょっとお酒が残っているみたいで、すみません、お名前が出てこなくて...。」

そのあと先輩から怒られたのは言うまでもない。なにかしら忘れた言い訳をしたかったんだろう。

環境系データについて備忘録

天気、風向き、風速、波の高さ、気温、水温。実はこれらの環境データは、本家のレース結果ページに存在する。しかし、予想するときはどうしたら良いのだろうか。予想するときにはそのレースの結果ページはまだ存在しない。

そうか、1つ前のレースの結果に掲載されている天気情報を使えばいいだろう。あれ、じゃあ、1レース目はどうすれば?というわけで、気象庁のAPI※1なんかを利用した時期もあった。地域コード※2は場ごとに手で対応表を書いた。

実は、本家にレース直前情報ページがあることを時間がだいぶ経ってから気づくことになる。なんという時間の無駄と無駄なコーディングだったことか。

特徴量エンジニアリング

生データの特徴量化は利用するモデルに応じても処理が変わってくる。初期に利用していたモデルについては次のエピソードで話したいと思うが、ここでは僕がその当時どのような試行錯誤をしていたか記録しておく。

まず、amazonの購入履歴によれば1月に「事例で学ぶ特徴量エンジニアリング」※3 を購入していた。この書籍には大変助けられた。

例えば、生データには racerAvST (平均ST) がある(実際にはレーン毎に lane1_racerAvST,lane2_racerAvST,...,lane6_racerAvST)。ボートレースは船だから同時にスタートが難しい。プカプカしているからヨーイ、ドン!のときには3号艇は逆方向を向いているかもしれない。このため、大時計が12時(0秒)を指してから1時(1秒)を指すまでの1秒間にスタートラインを通過すれば良いというスタート方式になっている。

平均STは、大時計が12時を指してから何秒後にスタートラインを通過したかという秒数で、これが小さい値ほどスタートが早く、前につきやすいということになる。大体 0.10〜0.20 の間の数字が多いと思う。

で、実際のレースのデータを見てみると、

枠 : 平均スタートタイム
1枠: 0.15
2枠: 0.14
3枠: 0.19
4枠: 0.16
5枠: 0.16
6枠: 0.13

みたいな数値。これだと、ほとんど差がなくて優劣を付けるのが難しそう、ではこれを逆数にするかとか、全体の平均をとってそこからの差でマイナスの値にしたほうがいいのか?(マイナスにしたほうが良いときは確かにある)

新人レーサーで数値がないときはどうしようか。一旦、少し悪い数値をダミーでセットしたほうがいいのか?もはやスキップして、新人レーサーがいるときは学習も予想もしないほうがいいんじゃないか?とか。

級別 A1/A2/B1/B2 はどうする? 0.9/0.7/0.35/0.05 に数値化して差をつけようか。

悩む。実際には答えはなくて学習で試してみるしかない。(アキラメナイで!2026年の今ならAIが全部試してくれる!)手でコーディングしていた頃は本当に時間がかかった。

例の san_ren_tan_pay に対しても、大穴の定義を36,000円にしたら、san_ren_tan_pay > 36000 のときに target = 1 にするなどの処理が必要になる。

こういった試行錯誤を何度もやり直した。極端な例では、74列まで縮小したパターンも試した。あまり学習に寄与しない項目を省く。

例1: 74列に絞る

74列 = 6艇 × 11項目に絞った版。残したのは

racerRank, racerAge, racerWeight, racerAvST, racerFCount, racerLocal3WP, racerMotor3WP + 選手マスタ由来の racerWinPct2, racerGender, racerWinCount, racerRunCount

選手マスタは本家サイトから半年毎に固定長テキストファイルでダウンロードできる※4。捨てたのは支部・出身地・L回数・全国勝率3種・当地勝率2種・モーター/ボート番号と2連対率。

全然ダメ。

例2: 22列に畳む

例1をさらに畳んだ版。22列 = 6艇を「内3艇の平均」と「外3艇の平均」にまとめた。10指標 × 2グループ + target。

racerRank(級別を数値化), racerAvST(逆数), racerWeight, racerFCount, racerLocal3WP, racerMotor3WP, racerBoat3WP, winPct2(コース別2連対), runCount, performTrend(前期比)

全然ダメ。

様々なパターンでモデルを学習させても精度が上がらないのだ。最新の特徴量の状況は後のエピソードで書くが、もはや項目を縮小したりはしていない。

パイプライン

この段階で、僕にもだんだん作業の全体像が見えてきた。これがパイプラインって呼ばれているやつなのかと理解できてきた段階。

夜間:今日の全レース結果収集 → 特徴量エンジニアリング → モデルを学習

(過去のレース情報・結果に毎日追加して学習。学習データは日々増えていく)

日中:次の1レース情報収集 → 特徴量エンジニアリング → 最新モデルで予想

最新のパイプラインでは、過去データの足切りなども行っているが、後述。

次のエピソードでは初期のモデル選択と学習について話していこうと思う。

つづく。

機械学習MLFeature Engineering

関連情報

  1. 気象庁 天気分布予報 API(東京地方の例)本文で使った API。末尾の 130010 が地域コードで、これは「東京地方」を指す。
  2. 気象庁 地域コード一覧(area.json)地域コードは class10s にある142件から選ぶ。桐生=100010(群馬県南部)、戸田=110010(埼玉県南部)など。
  3. 『事例で学ぶ特徴量エンジニアリング』 Sinan Ozdemir 著/大野真一朗 監訳本文で触れた、2024年1月に買った本。
  4. レーサー期別成績ダウンロード本文の選手マスタ。半期ごとの固定長テキスト(fanYYMM)で配布されている。

Amazonのアソシエイトとして、当サイトは適格販売により収入を得ています。