前回のおさらい

前回の記事では、ChatGPTと一緒に競馬AIを作り始めた時のことを書きました。

PythonとVS Codeの開発環境を整える。
競馬AI用の仮想環境を作る。
必要なライブラリをインストールする。
ChatGPTにコードを作ってもらい、実際に動かしてみる。
エラーが出れば、その内容を伝えて修正してもらう。

そうしたやり取りを繰り返しながら、指定した開催日のレース情報を調べ、
対象となるレースIDを取得し、各レースのHTMLをローカルに保存できるようになりました。

しかし、HTMLを保存しただけでは、競馬AIは何も学べません。

次に必要になったのは、保存したHTMLから必要な情報を取り出し、
分析や機械学習で扱える形へ整えることでした。

ここから、競馬AIの最初の前処理が始まりました。

HTMLは、そのままでは学習データになりません

競馬サイトのHTMLには、レースに関するさまざまな情報が含まれています。

開催日
競馬場
距離
芝かダートか
天候や馬場状態
出走馬
騎手
枠番や馬番
オッズ
着順
レースタイム

人間がWebページを見る場合は、
画面に表示された表や文字を読めば内容を理解できます。

しかし、機械学習のモデルが、
そのままHTMLを見てレースを理解してくれるわけではありません。

HTMLの中から必要な情報だけを取り出し、
行と列で整理されたデータへ変換する必要があります。

つまり、競馬情報を集める処理と、
AIが使えるデータを作る処理は別の作業でした。

最初にHTMLを保存したのは、競馬データを手元に確保するためです。

その次に、そのHTMLを解析し、必要な項目を抽出していく必要がありました。

レース情報と出走馬情報を分けました

データを保存する時、レースに関する情報と出走馬に関する情報は、
最初から別のCSVファイルに分けました。

レース情報を保存する races
出走馬情報を保存する entries

という形です。

競馬のデータには、一つのレースにつき一つだけ存在する情報と、
出走馬ごとに異なる情報があります。

開催日、競馬場、距離、馬場状態などは、
同じレースに出走するすべての馬に共通します。

一方で、馬名、騎手、枠番、馬番、オッズ、着順などは、
出走馬ごとに異なります。

これらをすべて一つのCSVへ入れることもできます。

しかし、その場合は、同じレース情報が出走馬の数だけ繰り返し保存されることになります。
netkeibaのページ自体も、レース情報と出走馬情報が分かれた構造になっていました。
また、将来的にはCSVだけではなく、データベースへ移行することも考えていました。

そのため、最初からレースと出走馬の情報を分け、
レースIDを使って両者を関連づける形にしました。

当時は、データベース設計を細かく完成させていたわけではありません。
それでも、後からデータを増やしたり、保存方法を変更したりする可能性を考えると、
情報の単位を分けておいた方が扱いやすいと考えました。

どの項目を使うかは、ChatGPTの提案を採用しました

保存したHTMLから、どの項目を取り出すのか。
これは、競馬AIの精度にも関わる重要な問題です。

レース情報
馬の情報
騎手
オッズ
過去成績
馬場状態
着順やタイム

競馬には、予測に使えそうな情報が数多くあります。
ただ、この時点では、競馬データの全体像も、機械学習でどのような情報が必要になるのかも、
私はまだ十分に理解できていませんでした。

その状態で、一つひとつの項目について長時間考えても、正しい判断ができるとは限りません。
重要そうに見える項目が、実際にはあまり役に立たないかもしれません。
逆に、最初は見落としていた項目が、後になって必要になる可能性もあります。

そこで、最初の項目選びについては、ChatGPTの提案を採用することにしました。

まずは、一般的に必要になりそうな情報を取り出して、データとして形にする。
実際にモデルを作り、結果を確認した後で、不足している項目を追加する。
不要な項目があれば、その時に外す。

まだ全体も学習方法も分かっていない段階で、最初から完成形にこだわるより、
まず動くものを作った方が効率が良いと考えました。

ここでも、競馬AIの方針を決めた時と同じように、小さく始めて後から改善する方法を選びました。

ChatGPTにHTMLの解析処理を作ってもらいました

必要な項目の方向性が決まると、次はHTMLから情報を取り出すPythonコードが必要になります。
この処理についても、基本的にはChatGPTに作ってもらいました。

保存したHTMLがどのような構造になっているのか。
どの部分にレース情報があるのか。
どの表に出走馬や結果が入っているのか。
どの項目を、どのCSVへ保存したいのか。

そうした情報を伝えながら、HTMLを解析する処理を作っていきました。
コードを実行すると、HTMLから取り出された情報がCSVとして保存されます。
それまでWebページ上で見ていた競馬情報が、表形式のデータへ変わっていきました。

レース情報は、1レースを1行として保存する。
出走馬情報は、1頭を1行として保存する。
そして、両方のCSVにレースIDを持たせる。

これによって、後から出走馬情報にレースの距離や馬場状態などを結びつけられるようになりました。
HTMLのままでは扱いにくかった情報が、少しずつデータ分析に使える形へ変わっていきました。

コードが動いても、データが正しいとは限りません

HTMLを解析するコードがエラーなく終了しても、それだけで処理が正しいとは判断できません。

必要な項目が空欄になっているかもしれません。
数字として扱いたい値が、文字列として保存されているかもしれません。
ページによって表記や構造が異なり、正しく取得できていない可能性もあります。

レース情報と出走馬情報の件数が合っているか。
取得したはずのレースが抜けていないか。
馬名や騎手名が正しい列に入っているか。
着順やオッズが想定した形式になっているか。

CSVを開き、実際のレースページと見比べながら確認しました。
こうした確認は、競馬AIに限った特別な話ではありません。
システム開発では、プログラムがエラーなく終了したことと、
期待した結果が得られたことは別です。

ChatGPTにコードを作ってもらった場合も、それは変わりません。
返されたコードがもっともらしく見えても、
実際のHTML構造やデータ形式を正しく理解しているとは限らないため、
出力結果を人間が確認する必要がありました。

細かなエラーや修正内容については、開発から時間がたったこともあり、
今ではすべてを覚えているわけではありません。

当時のチャットを見返せば、具体的なやり取りが残っているものもあると思います。
ただ、取得したCSVの中身を確認し、問題があればChatGPTへ状況を伝え、
何度もコードを修正しながら形を整えていったことは覚えています。

HTMLが、競馬AIの材料へ変わり始めました

こうして、保存したHTMLから必要な情報を取り出し、
レース情報と出走馬情報を別々のCSVへ保存できるようになりました。

まだ、この段階で機械学習に使える完成済みのデータができたわけではありません。

欠損している値をどう扱うのか。
文字列をどのように数値へ変換するのか。
どの項目を学習へ使うのか。
複勝圏内に入ったかどうかを、どのように正解データとして作るのか。

考えるべきことは、まだ多く残っていました。
それでも、競馬サイトのHTMLとして保存されていた情報が、
行と列を持つデータへ変わりました。

レースと出走馬を分け、レースIDで関連づける。
必要な項目を取り出し、CSVとして蓄積する。

これによって、競馬AIが学習するための材料が、ようやく見える形になってきました。
完成形を最初から決めるのではなく、まずデータを作ってみる。
そして、実際に使いながら必要なものを足し、不要なものを見直す。

この進め方は、その後の競馬AI開発でも続いていくことになります。

次回へ

競馬情報をHTMLとして保存し、そこからレース情報と出走馬情報を取り出せるようになりました。

しかし、CSVができただけでは、まだ予測モデルを作ることはできません。

学習に使う項目を選ぶ。
文字列や欠損値を処理する。
複勝圏内に入った馬を正解として表現する。
学習用と評価用のデータを分ける。

そうした準備を経て、ようやく最初の予測モデルを作る段階へ進みます。

次回は、集めた競馬データを使い、最初の機械学習モデルを作り始めた時のことを振り返ってみたいと思います。