エラーが出ないバグの見つけ方|5年半ずっと風速が日照時間だった話

5年半ずっと風速が日照時間だったという、エラーが出ないバグを表したアイキャッチ画像 競馬AI開発記

競馬予想AIを3年作っていて、いちばん怖いバグは何かと聞かれたら、私は迷わずこう答えます。エラーが出ないバグです。

落ちてくれるバグは、その場で気づけます。画面が止まる、赤い文字が出る、処理が進まない。だから直せます。

ところが、画面は正常、処理も最後まで通る、けれど中身だけが間違っているという壊れ方があります。これは見に行かない限り、一生気づけません。私は実際に、5年半ずっと間違ったデータで学習していたことがあります。

この記事では、私が実際に踏んだ「エラーが出ないバグ」を型ごとに並べて、どうやって見つけたのかを書きます。競馬AIの話として書きますが、データを扱う仕事なら同じことが起きます。

▶ 私が毎週150万円を賭けながら3年かけて作った競馬予想AIの全体像は、「競馬予想AIの作り方・完全ガイド」に最初から順にまとめています。

実例1:東京の「風速」が、5年半ずっと日照時間だった

いちばん背筋が寒くなったのがこれです。

レースの日の風速をAIに教えるために、気象のデータを取ってきています。表の何列目が風速かは、列の名前で探す作りにしていました。

ところが東京開催だけ、データの出どころが違っていたのです。名前で探しても必ず外れる。そして外れたときは8列目を読む、という作りになっていました。その8列目が、日照時間でした。

結果、出走の13.8%が、風速のつもりで日照時間を渡されていたことになります。しかも学習に使う値と本番で使う値がズレていたので、AIから見れば「学習したときと本番で意味が変わる列」です。

ここで大事なのは、この5年半、エラーは1回も出ていないということです。数字は入っている。型も合っている。ただ意味が違うだけ。プログラムには区別がつきません。

教訓:「見つからなかったときの代わりの動き」は、いちばん危ない。名前で列を探して、見つからなければ何列目、という作りは一見親切ですが、静かに間違い続けます。見つからなかったら止まるべきでした。

実例2:AIに渡している材料14本が、本番ではほぼ全部カラだった

乗り替わりと騎手の質にまつわる材料を14本、AIに渡していました。

調べたら、学習のときは0〜5%しか欠けていないのに、本番では97.8%と71%がカラでした。つまりAIは、この14本について本番では何も見ていなかったことになります。

学習のときと本番でデータの欠け方を比べた表。本番だけ97.8%が欠けている

原因は単純でした。その材料の元になるファイルが、一度作ったきり、どの定期処理からも作り直されていなかったのです。作った時点で存在していないレース、つまり未来のレースは、行そのものがありません。だから黙って空になります。

ここでも、エラーは1つも出ていませんでした。画面は正常、処理も最後まで通る。ただ材料だけが空です。

教訓:学習のときと本番で、欠けている割合を必ず比べる。片方だけ見ていると絶対に気づけません。0.05%と97.8%を並べて初めて「おかしい」と分かります。

実例3:レースの識別番号を上書きして、ルールが黙って無効になっていた

予想の印を決めるルールを新しく入れたのですが、常駐しているプログラムの側でだけ、そのルールが効いていませんでした。

原因は、常駐プログラムがレースの識別番号を一時的に「X」という文字で上書きしていたことでした。本物の番号がその時点で消えるので、後ろのルールが「このレースが何なのか」を引けなくなる。

そしてルールの側は、引けなかったときは例外を握りつぶして、何もしないで通す作りでした。安全のつもりで入れた「握りつぶし」が、ルールが効いていないことを隠す蓋になっていたわけです。

教訓:例外の握りつぶしは、必ず「何回握りつぶしたか」を数えて出す。握りつぶすこと自体は悪くありません。黙って握りつぶすのが悪いのです。

実例4:着順の条件が「1着から1着」のままで、1着馬しか集計していなかった

成績を集計する画面を自動で操作しているのですが、着順の範囲を「1着から28着」に設定する処理が、画面のレイアウトが変わった拍子に効かなくなりました。

この画面の初期値は「1着から1着」です。つまり設定に失敗すると、1着になった馬だけを集計した表が出てきます。

恐ろしいのは、表はちゃんと出てくることです。数字も並んでいる。ただし全部の馬ではなく、勝った馬だけ。パッと見て気づけるものではありません。

教訓:設定したあと、必ず読み返して「狙った値になっているか」を確かめる。「設定する処理を書いた」と「設定できている」は別物です。

実例5:障害レースが5,082頭ぶん混ざっていた

クラス別の集計をしていたとき、平地の未勝利戦のつもりで見ていた数字に、障害の未勝利戦が5,082頭(442レース)混ざっていました。

理由は、障害戦のクラス名が平地とまったく同じ「未勝利」だからです。クラス名だけで絞ると、障害が必ず紛れ込みます。

障害と平地では走り方も求められる能力もまったく違いますから、これが混ざると数字の意味が変わります。それでもエラーは出ません。クラス名は正しく「未勝利」なのですから。

教訓:名前が同じでも中身が同じとは限らない。絞り込みの条件は、名前だけでなく「その名前で何件になるはずか」を先に見積もっておくと気づけます。

実例6:ファイルを丸ごと置き換えて、画像が全部消えるところだった

これはつい先日、私が寸前で気づいた話です。

公開済みの記事の文章を直すために、手元のファイルで本文を丸ごと置き換えようとしました。ところが公開側には画像が埋め込まれていて、手元のファイルにはまだ画像が入っていない状態でした。

そのまま流していたら、記事から画像が5枚消えて、代わりに「ここに画像を入れる」という、読者に見えてはいけない目印の文字が5か所に並んでいたはずです。もちろんエラーは出ません。文章の差し替えとしては成功なのですから。

気づけたのは、文字数が減っていたからでした。直して増えるはずの本文が、8,622字から7,800字に減っている。そこで手が止まりました。

教訓:上書きの前に、必ず「増えるはずか、減るはずか」を確かめる。そして予想と逆に動いたら、その場で止まる。

6つに共通する「型」

並べてみると、エラーが出ないバグには決まった型があります。

エラーが出ないバグの6つの型と、それぞれの見つけ方をまとめた表

やさしくまとめると、どれも「失敗したのに、処理としては成功している」という形です。プログラムは仕事を終えたつもりでいる。だから何も言いません。

いちばん効いた対策は「わざと壊してみる」

いろいろ試して、いちばん効いたのはこれでした。

「検知の仕組みを作った」は、「検知できる」の証明になりません。

欠けているデータを見つける仕組みを作ったとします。作った本人は「これでもう安心だ」と思います。でも、その警報が本当に鳴るかどうかは、わざと壊してみるまで分かりません。鳴らない警報を持っているだけかもしれない。

だから私は、検査を作ったら必ずわざと引っかかる材料を用意して、ちゃんと止まるところまで見届けるようにしました。今週も新しい検査を1つ作りましたが、わざと引っかかる文を4本、通るはずの文を4本用意して、4件すべて捕まえて、通るほうは誤検出ゼロであることを確認してから使い始めています。

検査をわざと壊して試した結果。4件すべて検出し誤検出はゼロだった

これをやらないと、警報を持っている安心感だけが増えて、実際の守りは1ミリも増えません。むしろ油断するぶん、悪くなります。

もう1つ:おかしな数字が出たら、まず数字の作り方を疑う

最後に、自分の恥をひとつ書いておきます。

元ファイルの棚卸しを始めた1回目に、「7本が10か月前で止まっている」という結果が出ました。正直かなり焦りました。10か月ぶん間違ったデータで走っていたのかと。

調べたら、同じ名前の古いファイルを見ていただけでした。本物は当日の朝にちゃんと更新されていました。

これも大事な教訓です。おかしな数字が出たときは、まずその数字の作り方を疑う。慌てて本体を直しにいくと、直さなくていいものを壊します。

2台で回すと、片方の穴をもう片方が点検できる

私は中央競馬と地方競馬で、別々のAIを走らせています。今週、その良さがはっきり出ました。

地方側で「14本の材料がカラだった」という穴が見つかったので、その日のうちに中央でも同じ棚卸しをやりました。中央は白でした。元ファイルは木曜・金曜・当日の処理がそれぞれ作り直していて、今週ぶんも当日の朝に更新されていました。

ただし、同じ型が無いわけではありません。中央では陣営のコメントを点数にしたデータが同じ構造をしていて、コメントと馬をひもづけるのに結果のファイルを使っています。そのファイルは週に1回しか更新されないので、結果がまだ出ていない当日のレースは行ごと消える可能性がある。ここは実際の予想を出した直後に確かめて決着させました。

1台でやっていたら、14本がカラのまま何か月も走り続けていたはずです。片方が踏んだ穴を、その日のうちにもう片方が点検できる。これは思っていた以上に大きい利点でした。

まとめ:落ちるバグより、黙るバグを探す

ここまでの6つを、もう一度短くまとめます。

  1. 代わりの動きを入れない。見つからなかったら止める
  2. 学習と本番で、欠け方を比べる。片方だけ見ない
  3. 握りつぶすなら、回数を数えて出す
  4. 設定したら読み返す。「書いた」と「効いている」は別物
  5. 名前で絞ったら、件数を見積もりと比べる
  6. 上書きの前に、増えるか減るかを予想する

そして最後にもう一度。検査を作ったら、わざと壊して警報が鳴るところまで見届ける。

正直に言うと、この6つはどれも私が実際に踏んだ穴です。5年半ぶんの風速が日照時間だったと分かったときは、これまでの成績は何だったのかと思いました。

ただ、見つかったということは、直せるということでもあります。そして直したぶんだけ、次の予想は良くなります。実際、この6つを潰していった結果、7月からの本命は自信度4以上に絞れば単勝107円・複勝107円まで来ました。穴を1つ塞ぐたびに、数字はちゃんと動きます。

私はこのブログで、自分が先に転んでおくことにしています。5年半ぶんの風速が日照時間だったことも、記事から画像を消しかけたことも、格好は悪いですが全部書きます。私が転んだぶんだけ、これを読んでくださっている方は同じ穴を踏まずに済むからです。

穴は、探しにいった人にしか見つかりません。今週も探しにいきます。見つかったらまた、正直に書きます。

この内容を実際に使った週の実戦記録(買い目と収支つき)はこちらです。→ 人がやっていたらこうはならなかった|自動公開の代償

コメント

タイトルとURLをコピーしました