@yujiiwadate0247 (岩舘 雄二)
「ダークマターの領域に食い込みたい」─ 会場特徴×補正値の相関検証ラインを新設した3週間の記録 === darkmatter新設3本+V5py/V58py/スライド画像加工.pyブラッシュアップ ===
Python SVG 個人開発 net競馬
はじめに
競走馬の予想スコアを算出する自作Python予測システム(V5py / V58py)を個人開発しつつ、その週次の成果をYouTubeチャンネル「71才隠居ジジィの競馬新聞」で動画にして公開しています。
前回・前々回の記事では、xlsxの予想結果表をSVGスライドへ自動合成する仕組みで動画1本分のスライド制作から「手で直す」工程をゼロにし(「スライド制作、ついに手修正ゼロへ」)、続けてPNG変換・mp4書き出しという「動画の出口」まで自動化して動画1本の完成時間を20時間50分から6時間40分に縮めました(「youtube動画用pm4制作も自動化しちゃいました」)。
今回はその続きというより、少し毛色の違う話です。8月3日〜8月23日の21日間、V5pyの予想結果に「会場(競馬場)の特徴と補正値の相関」という新しい観点を付加検証する取り組み(通称「darkmatter」)を本格始動しました。新規ツール3本を立ち上げつつ、その土台となるV5py・V58py本体の長年気づかれていなかったデータ品質バグの発覚・修正、スライド側の乖離表統合レイアウトの試行錯誤、そしてAIとのセッションが分断されることに起因するバージョン管理事故まで、この3週間はいろいろなことが起きました。
今回作ったツール
| ツール | 役割 |
|---|---|
| seiki_keibayosou_app_v5_darkmatter.py | V5py本体からの分岐版。会場特徴補正(new_darkmatter)を組み込んだ検証用予測アプリ |
| darkmatter_hikaku.py | 本家V5py vs darkmatter版の予想精度を比較する専用ツール |
| make_darkmatter_hikaku_batch.py | darkmatter比較の複数レース一括実行バッチ |
メインのV5py本体(No.89〜102)・V58py(No.67〜70)・スライド画像加工.py(No.68〜85)も、この期間に大小あわせて多数の改修を重ねています。
「ダークマターの領域に食い込んでいきたい」
8月5日朝、この一言から今回の取り組みは始まりました。
yuji 「ダークマターの領域に食い込んでいきたい」
まず競馬場ごとの特徴(直線の長さ・コーナーのきつさ・急坂の有無・高低差・洋芝・平坦/下り基調・大箱コースの7項目)を○△✕で整理し、V5の距離別補正値と並べて相関を見るための特徴表xlsxを作ることになりました。実際に作成した特徴表は以下の通りです。


ここでまず自分の勘違いが発覚します。
yuji 「V5pyはvenue_correction.csvは一切関係ないよ つい先日それに関し再確認したではないか」
作業中に転記した距離別補正値は、実はvenue_correction.csv側の会場別数値であり、V5py本体はvenue_correction.csvを一切参照していませんでした。V5pyの距離補正はDIST_FACTOR(会場を問わない単一カーブ)のみで行われており、そもそも「会場特徴×V5補正値の相関」というテーマはV5pyに関する限り検証不可能(会場によって値が変わらない)だったのです。
代わりに、函館・新潟の的中率が他会場より低いという実感を手がかりに、「距離帯×脚質」という会場非依存の一般則補正を新設する構想が持ち上がり、まずV5py単体で確立してからV58pyへ横展開する順番で合意しました。この構想が、後にnew_darkmatterという名前を得ることになります。
馬柱データの生データを見て発覚した着差列汚染87.3%
脚質判定(4角通過順位ベース)の実装に向け、蓄積CSVの生データをそのまま提示したところ、「馬場」「タイム」「通過」「厩舎コメント」「備考」の5列が全行にわたり空欄で、隣接列に本来の値が紛れ込んでいるという構造上の問題が見つかりました。
yuji 「おいおい今更の話だな いままで出していた予想はどうなるんだ」
即答は避け、実際の読み込み側コードを確認するまで判断を保留しました。結果として、この「値がずれて隣の列に入る」こと自体は2月時点で検証済みの仕様だったのですが、その過程で別の実害バグが見つかります。着差欄にタイム形式(1着馬のタイム)の値が来た場合、着順を見ずに一律0.0(差ゼロ=最高評価)として扱う実装になっており、1着以外の馬にもタイム形式の値が紛れ込んでいるケースが不当に高く評価されていました。実データ集計では、着差列31,310行のうち87.3%(27,330行)がこの実害を受けており、「通常の着差表記」は事実上存在しない、という規模の汚染でした。
3月分からの全件再取得も検討しましたが、①スキップ判定の仕組み上、新しいレースを走っていない馬は何度buildしても再取得されない、②1回の取得で救えるのは各馬の直近5走分のみ、という2つの構造的制約があり、CSVの生データ自体は書き換えず、parse_chakusa()側で着順1位以外のタイム形式値をNaN化して計算から除外する対処に落ち着きました。
# parse_chakusa() 修正後のイメージ
def parse_chakusa(value, chakujun):
if is_time_format(value):
if chakujun == 1:
return 0.0
return np.nan # 汚染データと判定し計算から除外
...
並行して、5走凍結解消から1ヶ月近く経っても6走以上蓄積された馬が1頭もいない(0/7,754頭)という別の異常も調査しており、複数セッションがほぼ同時に同じ問題を追う形になっていました。最終的に、STEP1.5のHTML取得部でres.encoding='EUC-JP'が決め打ちされている一方、実際の対象サイトはUTF-8で配信されており、この文字コード不一致が馬名の文字化けを引き起こし、名前照合ベースの新着レース判定を常に失敗させていたことが判明します。
# 修正前: 固定エンコーディング
res.encoding = 'EUC-JP'
# 修正後: 自動判定に統一(1377行目の別関数と同じ方式)
res.encoding = res.apparent_encoding
修正後の実機確認では、「タイセイミッション」対「タイセイミッションB」のように装具マーク表記の有無で名前照合が失敗するケースが新たに見つかりました。
yuji 「だから、以前にも言ったがサイト知りたいことがあったら私を使えと言ったろ 日本には『立ってるものは、親でも使え』と諺があること知らないか(笑)」
Ctrl+Uでページソースを直接確認し、馬個体ページへのリンクに馬IDがそのまま含まれていることが分かったことで、名前ではなく馬IDで直接紐付ける根本対応に切り替わりました。馬IDは表記ゆれが原理的に発生しないため、装具マーク等による名前不一致問題を今後恒久的に回避できます。
教訓: 「値が入っているように見える」ことと「正しい値が入っている」ことは別問題。生データを実際に見て初めて、87.3%という規模の汚染に気づけました。
新馬戦で市場人気が消えるバグ、その言い訳をしたAI
8月7日朝、「乖離チェック不可(オッズ未発売)」という表示をめぐるやり取りから始まりました。出馬表に人気が表示されているのに乖離チェックだけが「取得不可」になる現象について、過去に対策した記憶があるはずとの指摘を受けます。
実際の原因は、calc_horse_score()内にある「新馬(h_df.empty)用の早期return」で、市場人気の値が運ばれずに消えていたことでした。この関数には2つの出口があり、通常フロー側には以前'ninki'キーが追加されていましたが、新馬用の早期return側には最初から一度も追加されていませんでした。
yuji 「そんな屁理屈どうでもいい 修正して、直った事を確認してOKだしているものが どうであれ変わることがおかしいと言っている」
「取得」と「格納」を分けて説明しようとするほど、話が噛み合わなくなりました。実際には、過去にOKを出していたのは「新馬でninkiがNaNになりクラッシュしていたのを直した」対応であり、「新馬でも市場人気の値そのものが最終結果に運ばれること」自体は一度も確認されたことがなかった、という事実が、何度もの言い換えの末にようやく判明します。
yuji 「御託を言うな」
市場人気の欠落を1行追加で直した直後、馬IDの数値も表に記載されていないとの指摘が入りました。調べると、通常フロー側と新馬用早期return側の辞書を機械的に突き合わせないまま'ninki'だけ直していたため、horse_id・baba_f・trainer_b_typeも同様に欠落したままでした。
# 新馬(h_df.empty)用の早期return側 辞書に追加
'ninki': int(e.get('ninki', 0) or 0),
'horse_id': e.get('id', ''),
'baba_f': 1.0, # 馬場補正なしのデフォルト値
'trainer_b_type': '通常', # 新馬はtrainer_b=0のため区分固定
yuji 「自分の不完全な対策を見過ごしておいて、いままでの言い草は聞き捨てならん」
同日夜、この一連のやり取りを読み返し、率直な意見を求められました。「最初の修正が不完全だった」「『なぜ0なのか』に直接答えるまでが長すぎた」という2点を認めたところ、こう総括されました。
yuji 「私が言いたいのは、不完全なものを押し付けておいて私に徹底的にそこを追求されるまで、論点を変えてゴチャゴチャ言っていた事だ」
yuji 「人間の悪い所は真似るな(笑)」
教訓: 片方の出口だけ直して満足しない。「通常フロー側と早期return側で戻り値dictのキー集合が完全一致しているか」を機械的に突き合わせる作業を、以後の同種バグ対応の標準手順にしました。
「絵に描いた餅」だったタイムアウト設定
翌8月9日、STEP3(馬データ取得)で「Read timed out (read timeout=120)」というエラーが、リトライされた形跡なく発生していることが判明しました。build_driver()には20分(1200秒)のタイムアウトを狙った記述が2箇所ありましたが、片方(set_page_load_timeout(1200))は実際に機能していたのに対し、もう片方(driver.command_executor._conn.timeoutへの代入)はSelenium内部で実際には参照されない無効な代入で、実質的にSeleniumの標準値である120秒でタイムアウトし続けていました。
yuji 「絵に書いた餅の1200だったという事か」
selenium 4.46のソースを実機確認したところ、RemoteConnectionが実際に参照するのは_client_config.timeoutであり、_connオブジェクトはurllib3接続プールで.timeout属性はどこからも参照されないダミー代入だったことが確定しました。
# 修正前(効いていない代入)
driver.command_executor._conn.timeout = 1200
# 修正後(実際に参照される正しいAPI)
driver.command_executor._client_config.timeout = 1200
加えてSTEP3の取得ループにはそもそもリトライ処理自体が実装されておらず、1回タイムアウトすると即座にその馬を諦めて次に進む挙動になっていました。STEP4と同じ3回リトライのループを追加し、V5py・V58py双方に施した結果、修正前は「4レースbuildすると必ず1頭は出ていた」というエラーが0件になりました。
yuji 「でも結果として絵に書いた餅が原因だったとすれば、半分正解もくそもあるか 全部不正解だ」
このバグを見つけたきっかけは、再buildで「本来スキップされるはずの馬が取得されている」という想定外の挙動に気づいたことでした。
yuji 「今回、このバグを見つけたきっかけは再buildで本来リトライされて取得されているはずのデータが1頭取得保存されたのを見て『あれスキップするはずなのに何でだ?』から始まった このような作業をアルゴリズムに取り入れる事は可能か?」
これを「期待値を事前に計算してコードに持たせ、実際の結果とのズレだけを目立つ形で出力する」仕組みとして一般化し、STEP3(馬)・STEP4(騎手)・STEP4.5(調教師)の3箇所に、取得フェーズ終了直後の自動チェックとして常設しました。need_fetch判定と実際のnewly_fetched結果の差分を検出し、1件でもあれば「🚨 想定外」として画面に独立表示します。半導体製造のQC出身という経験と相性の良いテーマとして、以後の改修にも活きています。
new_darkmatter誕生 ─ 3ファイル構成での再始動
yuji 「マトリックスにした競馬場の特性を、補正値にどう盛り込んで新たな補正値としてその補正値をmixerで調整変更できるようなシステムを探したい」
8/5に着手し8/6にいったんデータ品質バグ修正へ逸れていた「競馬場の特徴と補正値の相関」の取り組みが、8/10のこの一言で本格再始動しました。現状「全会場共通」であるV5pyのDIST_FACTOR(距離だけで決まるグローバル定数)に会場差を持たせる新レイヤーとして、7特徴×11会場の○△✕マトリックスを組み込む2階建て構造(最終補正値=DIST_FACTOR×会場別乗率)を提案しました。
yuji 「補正値名はnew_darkmatterとする」
この一言で名称が確定し、以下の3ファイル構成の叩き台が生まれました。
-
venue_feature_matrix.csv─ ○△✕を+1/0/-1に数値化した会場×特徴マトリックス -
new_darkmatter_weights.csv─ 特徴ごとの仮重み(hosei_mixerでの調整対象) -
new_darkmatter_preview.csv─ 会場×距離の最終乗率、保存のたびに自動再計算されるキャッシュ
既存のvenue_correction.csv(V58py側の実測サンプル蓄積、V5pyは無関係)と混同しないよう、「V5pyの中で実際に効くのはDIST_FACTORとnew_darkmatterの2段構成だけ」という整理を行い、seiki_keibayosou_app_v5_darkmatter.pyへと実装が進みました。
この検証ラインは、V5py本体・V58py本体・既存のyosou_hikaku_v3.py/make_hikaku_batch.pyには直接手を入れず、既存資産を横に複製・流用する形で独立ラインとして構築している点が設計上の特徴です。
実機で判明した「補正が一切効いていない」事故
8/10、seiki_keibayosou_app_v5_darkmatter.pyの実装直後、new_darkmatter_preview.csvの参照パスが仮のままで実際の格納先を見ておらず、実機ログで「未検出」により補正が一切効いていなかったことが判明しました(この回は上り列が通常版と完全一致していたことで無効化を確認)。パスを修正し、予想結果シート・順位表シートのA1タイトルに「🧪DM版|」を前置、背景色を通常版(紺)と区別できる紫に変更、どちらのpyの出力か一目で判別できるようにしました。
さらに、V5py本体と出力ファイルが競合していた問題も発覚します。予想結果xlsx出力・race_detail_log.csv・yosou_hikaku_data.csv・mixer入力コピー先の4箇所がV5py本体と完全に同一パスのままだったため、実機で2回build実行した結果、比較検証本体データにdarkmatter版のスコアが書き込まれる汚染が発生しました。4箇所すべてをdarkmatter専用パスに分離(ファイル名にも_DM印を追加)し、解消しています。
darkmatter_hikaku.py(本家V5py vs darkmatter版の比較専用ツール)とmake_darkmatter_hikaku_batch.py(一括実行バッチ)は、いずれも既存のyosou_hikaku_v3.py/make_hikaku_batch.pyのUI・集計ロジック(calc_kaishuritu/save_tekichuritu/_build_tekichuritu_sheets等)をそのまま移植する形で新規に立てました。V5⇔V58の2本比較専用設計にdarkmatterを追加するのは改修リスクが高いため、V5⇔darkmatter専用の軽量な別ファイルとして独立させています。
「中止」と「取消/除外」が区別できていなかった
darkmatter_hikaku.py側で、mixerの自動集計とマスターxlsxのN数が食い違うバグを調査した結果、fetch_race_result()が実着順を数値に変換できない行(中止・取消・除外)を一律continueで捨てていたため、後段のどこからも「中止」と「取消/除外」を区別できなくなっていたことが根本原因と判明しました。数値変換できない行を捨てずに「着順ステータス」列として保持するよう変更し、「中止」はN計上・外れ扱い、「取消」「除外」はNから除外、に明示的に分岐させています。
乖離表スライド統合 ─ No.75〜85、8/16は1日で7回連続修正
視聴者向けに「乖離」「大乖離」「乖離差」といった用語がナレーションに頻出するものの、テンポ上、言葉の意味を止まって説明する余地がない、という課題がありました。まず独立スライドとして実装しましたが、「動画がしつこくなる」との指摘で①予想表への埋め込み方式に転換します。
yuji 「なんで3段表の横に乖離表がないんだ その事を言っている」
方針決定していたはずの「①予想表の3段表の横に乖離表も表示する」構想が、実際には一切コードに反映されていない状態のまま数日経過していたことが8/14に判明し、_build_slide_yosou_kairi_layout()を新設して実装したのがNo.76でした。
しかし8/16、座標系の取り違えという別の不具合が見つかります。Inkscapeが表示する「25.0%」という縮小率は、SVGのpx単位を96dpi前提でmm変換した“後”の相対値であるのに対し、実装側のtarget_scaleは元SVGのpx幅への直接倍率として使われていたため、実際の表示は意図の約26.5%(0.2646倍)しかない二重縮小になっていました。変換定数を追加し修正(No.78)しましたが、それでも実機では直っていませんでした。
yuji 「表の大きさは問題ない 3段表を左に寄せ、乖離表と3段表をバランス良く(左側・中央・右側の各隙間均等)配置してくれ」
複雑化した座標補正を積み上げるのではなく、まず実データの乖離表SVGをtarget_scale=1.0でそのまま貼り込むところからやり直す方針に切り替え、「3段表・キャラクターは標準サイズのまま、乖離表だけを空きスペースに追加する」という設計の簡素化がここで生まれました。3段表と乖離表それぞれの実測幅から3箇所の余白が均等になるx座標を動的算出する方式(No.80)で左右のバランスは解決しましたが、続けて縦位置の課題が残ります。
yuji 「左右の位置はOK 乖離表の上下位置を緑帯とキャラクターの中間になるように修正してくれ」
背景SVGから緑ヘッダー帯の下端とキャラクターの上端を自動検出し、その中間に乖離表を動的にセンタリングする_find_title_band_bottom()を新設(No.81)。ただしこの時使った10行のデータはたまたま利用可能高さとほぼ一致するケースだったため、まだ「中間位置になっていない」との指摘が続きます。ChatGPTに座標を算出させたy=134px固定という実装(No.83)も試みましたが、これは検証に使った10行のレースにたまたま合っていただけで、乖離の行数(2〜11行)はレースごとにばらつくため、行数の少ないレースでは上端だけ揃って見た目の中心が崩れることが分かりました。
yuji 「これは10頭分の表であり頭数の少ない位置には合わない 乖離表がない予想もあるので、それらに対応した表示にしなければならない 表がないものは、今までの3段表の座標にしてあとは1〜10頭の表は必ず緑帯とキャラクターの中心に表示するような表示にしてくれ」
固定値を撤去し、緑帯下端・キャラ上端の自動検出を、乖離表の実際の高さ(行数に応じて可変)に対して毎回動的に適用する形に戻したのがNo.84です。
yuji 「10頭表の位置が緑帯にくっついている 全体的に、上目になっているのでもう少し下げた位置にしてくれ」
縦センタリング方式は維持しつつ、最小上端マージン(_YOSOU2_KAIRI_TOP_MARGIN_PX = 20px)とキャラクター側に寄せるオフセット(_YOSOU2_KAIRI_CENTER_BIAS = 0.6、上40:下60の配分)の2パラメータを追加したのがNo.85(v3.79)です。これにより、行数の異なる複数レースで実際に画像を並べて目視確認する運びとなり、乖離1行(札幌2R)〜8行(札幌記念11R)の実例をPNG化して確認したところ、緑帯にもキャラクターにも接触せずバランス良くセンタリングされていることが確認できました。
① 札幌2R(乖離1行)─ 中央よりやや下、キャラクター側に寄って配置
② 札幌4R(乖離5行)─ 緑帯・キャラクターいずれからも余白を保って中央付近に配置
③ 札幌記念11R(乖離8行)─ 行数が多いが緑帯には直接くっつかず、TOP_MARGINの下限が効いている
8/14〜8/16の3日間・No.75〜85の11段階に渡る修正は、Inkscapeの単位変換・3段表の幅正規化・固定値の汎化不足、といった複数の異なる原因が積み重なった結果でした。教訓は「一度直ったように見えても、条件を変えたサンプルで再検証しないと分からない」という、V5py/V58py側のバグ調査と共通するものでした。
「pendingという分かりづらい概念をやめろ」─ 用語統一と、2度のバージョン枝分かれ事故
8/17〜8/19、hosei_mixer_v1.pyにnew_darkmatter設計モード本体の実装が進む過程で、「pendingという分かりづらい概念をやめろ」という要望をめぐり、実装方針が2転3転する場面がありました。
8/18・yuji 「私が言いたいのはpendingなどという分かりづらい仕様をやめろと言っている 分かるか?」
まず「はい」選択と同時に即保存する単純化を実装しましたが、意図はそうではなく、「pending」という英語表記だけを分かりやすい日本語に置き換える(元の2段階の仕組み自体は維持する)方針であることが明確になります。
8/18・yuji 「自動的に保存されて完了 では仮保存だ 最後の保存作業で、正式に保存される」
Yes/No確認は「仮保存」、確定操作では「正式に保存」という用語に統一する形で決着しました。この作業の途中、こんな指摘が入ります。
8/18・yuji 「今セットされているpyはNo75があるが、提示されたpyにないのはどうしてか?」
実機は既に「中止/取消/除外」区別バグが別セッションで修正済み(No.74)であり、編集の土台にしていたファイル(v1.23 No.73)は実機の最新版(v1.25 No.75)より2世代古いことが判明しました。実機のNo.75を土台に修正を再適用しましたが、直後に全く同じ食い違いが再発します。
8/18・yuji 「送って貰ったHosei mixer v1 no81・PYの整理Noがおかしい 最後のNoは77である76ではない しかもなんで76から81になるんだ」
調べると、土台にしたNo.75はその後また別セッションで既にNo.77まで進んでおり(内容を見ると、まさに今回実装しようとしていた仕様変更そのものが既に別セッションで先に実装されていた)、それと知らずNo.76〜81を採番していたことが分かりました。実機の本物のNo.77を土台に目的の機能だけを再適用し、最終的にNo.78として収束させています。
8/18・yuji 「これは、セッションを跨いでの弊害か?」
これは「セッションを跨いだこと自体の弊害というより、毎回その時々でアップロードされたファイルのスナップショットしか見えていない、という構造的な制約」が原因でした。以後は「実機側で自分の知らない変更が入っている可能性を常に前提にする」「ソースを受け取ったら必ず【Ver】欄を確認してから作業する」という運用をメモリに残す形で対応しています。
8/19・yuji 「読んでいてなかなか面白い、AIをいかに上手く使うかのいい参考書になると思う」
同じ日のうちに2回連続でこの種の枝分かれが発生した点は、複数のAIセッション・実機編集が並行するワークフロー特有のリスクを浮き彫りにする回となりました。
horse_db_sync.pyの列ズレ発覚 ─ 汚染データ36,709行を10,041行に整理
8/21朝、V5py実行前の事前確認として、8/6付けで対応したはずのhorse_db_sync.py(取得Ver列のDBマイグレーション)が、実際には本番ファイルに反映されないまま放置されていたことが判明しました。ファイル冒頭の【Ver】表記はNo.01のままで、diffで実際の差分を確認して初めて正式に反映されました。
8/23、蓄積CSV・DBが36,709行に達したのを機に汚染データの削除が実施されます。
8/23・yuji 「csvとdbのデータ量が36000行を越えたので汚染データを削除したい 古いデータは取っていても返ってトラブルの元になりかねないので返却はいらない 新しいデータだけ返却してくれ」
しかし削除を実行したところ、CSV側の破損行が十数件だったのに対し、DB側では7,000件を超える削除対象が検出されるという大きな食い違いが発生しました。調査の結果、同一レコードでCSV側は着差に正常な数値が入っているのに対し、DB側は着差がNULLになっているという重大な不整合が見つかり、さらに調べると着差だけでなく馬場状態・レースタイム・通過順位・上り3Fなど複数列がDB側でズレて格納されていることが判明します。
8/23・yuji 「csvが正常なデータと思われるから、csvのデータにdbを合わせてくれ」
race_resultsテーブルを列名ベースでCSVから作り直して整合性を復旧した上でhorse_db_sync.pyを精査した結果、根本原因は_RENAME_MAPと_DROP_COLSが1列分ズレていたことにありました。
# 本来のマッピング(意図)
# 馬場 → 馬場状態
# タイム → レースタイム
# 通過 → 通過順位
# 上り → 上り3F
# 実際には1列分ズレており、_DROP_COLSが
# 本来必要な列(馬場・タイム・通過等)を誤って破棄する一方、
# 1つ後ろの列(馬場指数・タイム指数・調教タイム等)が
# それぞれ別の正しい列名にリネームされて紛れ込んでいた
着差列がNULLになっていた直接の原因は、数値化できない「タイム指数」の値(記号"**"等)が着差列にリネームされ、数値変換時にNULL化されていたためと特定できました。
8/23・yuji 「もう一度pyを精査してくれ」「やってくれ 問題は全てクリアにしたい」
_RENAME_MAP・_DROP_COLSを正しい対応に修正し、修正版horse_db_sync.pyの出力とCSVから手動再構築したDBを全列・全10,041行で突合して完全一致を確認しました。CSVとDBは最終的に36,709行から10,041行に整理され、以後の同期でも同じ列ズレが再発しないことをサンドボックス内の実データ検証で確認済みです。
新潟乗率変更後の的中率検証
8/19、new_darkmatter設計モードで新潟のみ乗率を0.906→1.350に変更しました。8/23、この変更後の実績を踏まえて判断を求められます。
8/23・yuji 「新潟補正値変更後の結果が出た 12Rではあるが多めに数値を振ったら明らかに的中率が下がったと思うが どう判断する?」
実データを比較した結果、乗率0.906(実質ほぼ無補正)の時代はV5(無補正)とDM(new_darkmatter)の複勝的中率が57.7%で完全一致していたのに対し、1.350に変更した後の実績(8/22〜8/23、N=38)ではDMがV5より2.7pt低く出る傾向が確認されました。ただし8/22分の12レースだけで見ると差は1レース(3歳未勝利)のみで、他11レースは全て一致しており、「明らかに下がった」と結論するには標本数が不足していると判断しました。乗率据え置きの中京・札幌側も同時期にわずかに的中率が下がっており、新潟固有の変更とは無関係な自然な揺れと見るのが妥当として、次開催以降のデータ追加を待つ方針となっています。
まとめ
V7.0期間(8/3〜8/23、21日間)は、darkmatter検証ライン(会場特徴×補正値の相関)の立ち上げと、それを支える馬柱データ・DB同期の品質固めという、2つの軸で進行しました。
- darkmatter検証: seiki_keibayosou_app_v5_darkmatter.py・darkmatter_hikaku.py・make_darkmatter_hikaku_batch.pyの新規3本に始まり、hosei_mixer_v1.pyへのnew_darkmatter設計モード追加によって、V58py・V5py本体と同じ「実績から推奨値を自動算出し、mixerで調整して反映する」運用に乗りました。新潟乗率変更後の的中率検証では、DMがV5をやや下回る傾向が見られましたが標本数が不足しており、判断は次開催以降のデータ待ちです。
- V5py/V58py同期・バグ修正: 対象サイト側のHTML構造変化を発端にした列ズレ、長期未発覚だった文字化け・名前不一致、着差列汚染87.3%、新馬レースでの複数キー消失、STEP3のタイムアウト・監視強化と、いずれも実機確認によって発覚し、「一度直ったように見えても実データで再検証しないと分からない」という共通の教訓を残しました。
- horse_db_sync.py: CSV・DB間の列マッピングが長期間ズレたままだった重大バグが発覚・修正され、蓄積データは36,709行から10,041行に整理されました。
- 乖離表スライド統合: 8/16時点で、乖離1〜8行の複数レースで緑帯・キャラクターいずれにも接触しないバランスの取れた配置を実機レンダリング画像で確認済みです。本期間で最も改修件数の多いテーマとなりましたが、8/17以降は大きな追加修正なく安定稼働しています。
数字に出ない収穫もいくつかありました。
- 見た目のインパクトに引っ張られない。派手に崩れて見える場所が、必ずしも本当の原因とは限らない。座標やログといった検証可能な材料で裏取りする。
- 片方の出口だけ直して満足しない。通常フロー側と早期return側のように、複数の出口を持つコードは、片方だけ仕様追従漏れが起きる。
- 推測で先に進まない。「たぶん合っているはず」でコードを書くと、必ず後で高くつく。これは自前のレイアウト計算でも、外部サイトからのデータ取得でも同じだった。
- セッションを跨ぐことの構造的な制約を前提にする。「実機側で自分の知らない変更が入っている可能性」を常に前提にし、ソースを受け取ったら必ず【Ver】欄を確認してから作業する。
個人開発は課題を1つ潰すと次の課題が見える、という循環が今回も続いています。次回は、チャットログ管理ツールの開発で足止めを食らった認証まわりの話を書く予定です。
@yujiiwadate0247 (岩舘 雄二)
"I'm a 71-year-old retiree enjoying my leisure time by developing Python programs on Ubuntu. I am currently focused on building a horse racing prediction system."