0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ミキサーに3つの構造バグが潜んでいた話 〜 発見・修正・函館係数変更の結果まで シリーズ第6弾 〜

0
Posted at

ミキサーに3つの構造バグが潜んでいた話 〜発見・修正・函館係数変更の結果まで【競馬予想システム開発記 V6.4】

予想屋yuji ― 71歳・隠居・Ubuntu・Python独学。競馬予想AIを半年間開発中。
前回記事:競馬AIの係数を0.700から0.900に変えたら、的中率が7.6pt上がった話


はじめに

「函館のランクA係数を1.000から1.300に変えてみよう」

きっかけはその一言だった。ところがミキサー(hosei_mixer_v1.py)を操作し始めた瞬間、
3つの構造バグが連鎖的に発覚した。

6/12〜16の5日間、予定していた函館係数変更の前に、まずミキサーそのものの修理に追われた。
そしてその修理を終えて係数を変更し、結果まで確認できた。

一気に報告する。


今週の変更サマリー

No. 日付 内容 分類
No.53 06/14 [a]/[s]/[m]を確定項目のみに制限・[t]新設 構造修正(問題A)
No.54 06/15 _VK_PROPOSED_RANK_FACTORS廃止→動的計算に統一 構造修正(問題C)
No.55 06/15 会場効果%:ランク効果% の予算配分ロジック実装 新機能(問題B試算)
No.56 06/15 バナーNo重複修正 修正
No.57 06/17 _is_kakutei()のバグ修正([t]にダークマター混入) BUGfix
V58 No.49/50 06/15 yosou_hikaku_v3.py・seiki_v5.pyへの横展開 BUGfix横展開
V5 No.72 06/16 V58py No.41/42の除外馬処理をV5pyに横展開 BUGfix横展開
CSV変更 06/16 函館ランクA:1.000 → 1.300 係数変更

問題A:「推奨値は提案値であって確定値ではない。それを設計段階で入れるとはどういう事だ」

発覚の経緯

函館ランクAだけ変更しようと[m](競馬場を指定して個別選択)を実行した。
すると函館のランクAだけでなく、ダークマター・ランクCも一緒にpendingに入っていた。

函館 ダークマター: 1.000 → 0.766(差-0.234)  ← 頼んでいない
函館 ランクA:      1.000 → 0.937(差-0.363)  ← これだけ変えたかった
函館 ランクB:      1.000 → 1.000(差   ─  )
函館 ランクC:      1.000 → 1.000(差   ─  )  ← 頼んでいない

yujiの一言が核心を突いた。

「推奨値は提案値であって確定値ではない。それを設計段階で入れるとはどういう事だ」

原因

[3]の集計結果では確定(N≥10)と暫定(N<10)を表示上は区別している。
しかし[m]の保存処理は、その区別を一切参照しなかった。
会場全体を一括でpendingに入れるという設計が、属性別に信頼度が違うという現実と噛み合っていなかった。

修正(No.53)

# 修正前:全属性を一括pending
for key, p in proposals.items():
    if venue == v_in:
        pending[key] = p['factor']

# 修正後:確定項目(tentative=='確定')のみ
def _is_kakutei(p):
    t = p.get('tentative', '')
    return t == '確定' or t == ''  # ダークマターは'-'→確定扱い

for key, p in proposals.items():
    if venue == v_in:
        if _is_kakutei(p):
            pending[key] = p['factor']
        else:
            skipped_tentative.append((key, p))  # [t]で個別確認へ
  • [a]/[s]/[m]:確定項目(N≥10)のみ自動でpendingに追加
  • [t](新設):暫定項目(N<10)を1件ずつ y/N で個別確認・反映

問題C:「.buildozerの残骸がいつまでも残っているような感じ」——ハードコードの罠

発覚の経緯

[4]検証モードで全件実行したところ、tekichuritu_master.xlsxの傾向と全く矛盾する「全会場で軒並み悪化」という結果が出た。

調査の結果、原因が判明した。
[4]検証モードが_VK_PROPOSED_RANK_FACTORSというハードコードされた5月時点の推奨値を使い続けていた。

# 問題のあったコード(No.28実装時にコピペして固定)
_VK_PROPOSED_RANK_FACTORS = {
    '東京': {'A': 0.900, 'B': 1.000, 'C': 0.815, 'S': 1.028},
    '阪神': {'A': 1.300, 'B': 1.111, 'C': 1.300, 'S': 1.246},
    # ... 5月時点のスナップショットがそのまま残っていた
}

[3]は毎回最新データから動的に推奨値を計算していたのに、[4]だけが5月の古い値を抱え込んだまま動いていた。

yujiがAPKビルドの経験から即座に本質を掴んだ。

「ドットビルドザーの残骸がいつまでも残っているような感じだね」

.buildozerのキャッシュが、ソースを更新しても古いビルド結果を抱え込み続ける——全く同じ構造だった。

修正(No.54)

# 修正後:その場で動的に計算
def show_venue_proposals_v2(data, proposals=None):
    """[4]検証モード:毎回最新データから推奨値を動的算出"""
    if proposals is None:
        dm_data = auto_scan_venue(input_dir)   # 最新データをスキャン
        proposals = calc_venue_proposals(dm_data, data)  # 動的に計算
    # ハードコード辞書は完全廃止

_VK_PROPOSED_RANK_FACTORSを廃止し、[3][4]が常に同じ最新データから動くようにした。
さらに[4]はランク係数だけでなくダークマターも含む合成効果を検証できる設計に拡張した。


問題B:妥協を拒否——「後手のやり方では絶対抜ける」

問題AとCを修正した後、Claudeは提案した。
「問題Bは[4]検証で事前確認する運用でカバーできる」

yujiは即座に退けた。

「問題B(ダークマターとランク係数の独立算出)について、運用上は『適用前に検証する』という形でカバーできる状態では絶対抜ける。こういう後手のやり方では。計算段階での相互作用は解決しなければならない」

yujiの着想:「100を分配する」

「ダークマター+ランク係数を100として一つの大きいくくりで考え、100の各々の割合を振る。補正値によって与える影響を振るという考えはどうか?」

この発想は統計学の分散分析(ANOVA)の寄与度分解と全く同じ構造だった。
半導体製造での品質管理経験が、独立した発想として同じ地点に到達していた。

実装(No.55):会場個性配分ロジック

def calc_venue_proposals_v2(dm_data, rank_data, current_data):
    """
    会場効果%:ランク効果% で予算配分した推奨値を算出
    
    会場効果% = |DM-1.0| / (|DM-1.0| + |RANK-1.0|) × 100
    ランク効果% = 100 - 会場効果%
    """
    results = {}
    for venue in VENUES:
        dm_dev   = abs(dm_data[venue] - 1.0)
        rank_dev = sum(abs(v - 1.0) for v in rank_data[venue].values()) / 4
        total    = dm_dev + rank_dev
        if total < 0.001:
            venue_pct = 50.0
        else:
            venue_pct = dm_dev / total * 100
        rank_pct = 100 - venue_pct
        results[venue] = {'venue_pct': venue_pct, 'rank_pct': rank_pct}
    return results

1014レースで試算した結果:

会場 会場効果% ランク効果% 傾向
東京 3.5% 96.5% ランク効果が支配的
福島 1.6% 98.4% ランク効果が支配的
函館 49.8% 50.2% ほぼ均等
阪神 59.8% 40.2% 会場効果優位
新潟 64.9% 35.1% 会場効果優位

直感と一致する。東京はジョッキーの腕が出やすく、函館・阪神は馬場・コース特性が強い。

ただし本採用は[4]検証で精度確認後。 現時点は試算段階。


[t]キーのバグ発覚——レポート作成中に見つけた

V6.4レポートの作成中、[t]の動作確認を改めて実施した。

📋 暫定項目(N<10)一覧(17件):
   中山 ダークマター: N=179  現在1.271 → 推奨1.078  ← N=179なのに暫定!?
   阪神 ダークマター: N=228  現在1.294 → 推奨1.161  ← N=228なのに暫定!?
   東京 ダークマター: N=189  現在1.025 → 推奨1.011  ← おかしい
   ...

「N<10の真の暫定項目のみ」を表示すべき[t]に、ダークマター(N=24〜228)が大量に混入していた。

原因と修正(No.57)

# 修正前:'確定'かどうかしか見ていない
def _is_kakutei(p):
    return p.get('tentative') == '確定'
# → ダークマターのtentativeは'-'(ランク係数なし)
#   '確定'でもないので is_kakutei=False → [t]に混入

# 修正後:'-'も確定扱いに
def _is_kakutei(p):
    t = p.get('tentative', '')
    return t == '確定' or t == ''

sed1行で修正し、バナーをNo.57に更新した。

「レポートは一発で仕上がらない、確認作業の中で問題が出る」——今回もその通りだった。


周辺py横展開——V5py No.72 除外馬修正

函館24Rの比較検証xlsxを確認したところ、V5py側でテリオスルミが9999番人気として計算され、指数1111.32で◎を占有していた。

V58pyでは既にNo.41/42(2026-06-04)で除外処理済みだった。

# V58py No.41/42で実装済み(V5pyには未反映だった)
_excluded_ids = extracted.get('excluded_ids', set())
if _excluded_ids:
    extracted['entries'] = [e for e in extracted['entries']
                            if e['id'] not in _excluded_ids]

yujiのコメント:

「9999番人気問題はV58pyで修正している。その時横展開でV5pyもしていると記憶していたが」

確認するとV5pyには横展開ゼロだった。V5py No.72として即座に移植した。


函館ランクA係数変更の結果

全修正完了後、本題の函館ランクA係数変更を実施した。

変更内容: venue_correction.csv 函館ランクA:1.000 → 1.300

変更前(11:27スナップショット)と変更後(13:24スナップショット)で函館24レースを比較。

函館24レース比較

指標 変更前 V5 変更前 V58 変更後 V5 変更後 V58 V58の変化
複勝的中率 29.2% 33.3% 41.7% 41.7% +8.4pt ✅
◎平均誤差 4.00 3.83 3.38 4.21 +0.38(微増)

⚠️ 「V5側の+12.5pt改善」の正体

V5の複勝的中率も29.2%→41.7%(+12.5pt)と大きく改善しているが、
これはvenue_correction係数変更とは一切無関係だ。

V5pyはvenue_correction.csvを参照しない。改善理由はV5py No.72の除外馬修正だ。

2つの別々の改善が同じ日に偶然起きた。
V5とV58を別々のベンチマーク軸として分離していなければ、混同していたところだった。

「何が何を改善したか」を系統別に厳密に追跡する。V5/V58の2系統分離運用の真価がここで発揮された。


まとめ

「函館の係数を動かす」という小さな動機から、ミキサーに潜んでいた3つの構造問題が連鎖的に発覚した5日間だった。

問題 No. ステータス
A:[m]が暫定も含めて一括pending No.53 ✅ 解決済み
C:[4]がハードコード推奨値を参照 No.54 ✅ 解決済み
B:ダークマターとランク係数の配分 No.55 🔄 試算実装済み・[4]検証待ち
[t]にダークマター混入 No.57 ✅ 解決済み(レポート作成中に発見)

急いで全会場に配分ロジックを適用せず、1会場・1係数の単点変更として検証結果を確認する。
このペースが、6ヶ月を超えた長期開発を支えている。


今週の気づき

「計算そのものは君や他AIにやって貰うしかないが、アルゴリズムだけはしっかり理解しておかないとね」

71歳の隠居ジジィがAIと組んで一からシステムを構築し続ける原動力は、
ブラックボックスにしないことへのこだわりにある。

次回V6.6では、No.55の配分ロジックの[4]検証と、函館24RのRゾーン別詳細分析を中心に進める。


制作者:yuji(@yujiiwadate0247)― 予想屋yuji 競馬予想システム V6.4期間 2026/06

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?