はじめに
麻雀牌をスマホのカメラで読み取り、点数計算まで進めるPWA「mjtensu」を個人で作っています。牌の認識モデルは、実装を生成AIに任せ、実験の計画と採用の判断を自分で持つ分担で作ってきました。
AIに任せると、学習と評価はどんどん回ります。学習済みモデルは約200個、評価結果は1,200件を超えました。
ここまで増えると、数字は覚えていられません。「あのモデルは速かった」「あれは精度が強かった」までは覚えていても、具体的な値は出てきません。研究室や会社には過去の数字を全部覚えている人もいましたが、自分はそうではありません。そこで、覚えている前提で判断するのをやめました。
代わりに作ったのが、「前のあのモデルと比べてどうなん?」と聞けば答えが返ってくる状態です。この記事では、そのための実験の書かせ方、動かし方、比べ方を書きます。
目次
1. 同じ実験の中は、画面で見る
学習と評価の実行は、実験管理ツールのClearMLに任せています。計算機クラスタのノードへの割り当て、失敗時の再実行、ログやグラフの表示まで、ここが受け持ちます。
1つの実験の中で条件を振るなら、ClearMLの画面で足ります。比較の基準にする今のモデルを同じ実験に入れておけば、画面で並べて見られます。
困るのは、実験をまたいで比べたいときです。ClearMLの画面は、1つの結果を詳しく見るのには使えます。ただ、別々の実験を並べて比べるのには向いていません。「以前試したあの構造と比べてどうか」を知るには、画面を行き来して数字を拾うことになります。
2. 実験の書き方を、最初に決めておく
実験をまたいで比べるには、過去の実験の中身が分かっている必要があります。何を、どの条件で、どの評価方法で測ったかです。実装をAIに任せると、この書き方はすぐにばらばらになります。
そこで、実験の書き方を規格として決めました。AIには、この形でしか実験を書かせません。この規格と、それに沿った記録をまとめて、自作のMLDBと呼んでいます。
決めていることは3つです。
- 実験を部品に分けて書く: 学習データ、モデルの構造、学習方法、評価方法を別々に定義し、実験はその組み合わせで書きます。部品は書き換えず、変えるときは新しいバージョンを作ります。
- 実行の前に条件を固定する: 実行計画をgitのcommitに、実行をライブラリ一式のバージョンに固定します。固定できなければ実行させません。
- 「完了」と正式な結果を分ける: ClearMLで完了と出ても、それだけでは正式な結果にしません。出力の形と出どころを確かめてから、MLDBが受け付けます。
この3つで、どの結果も「どの評価方法の、どのバージョンで測ったか」まで辿れます。比べるときに使うのは、正式な結果だけです。
結果をどう読み、何を採用したかは、実験ごとに文章で残しています。背景・目的・手法・結果・考察の形で、スライドの代わりです。書式には、自作の設計記録の仕組み(brewprint)を流用しています。
3. 過去の実験とは、AIに聞いて比べる
書き方が揃っていれば、実験をまたいだ比較はAIに任せられます。AIが記録を読み、同じ評価方法の結果どうしを拾って並べてくれます。
個々の比較結果は残していません。新しい構造の実験が次々に流れてくるので、比べる相手はすぐに変わります。比較結果を残しても、すぐに古くなります。
代わりに残しているのは、比べ方のほうです。どんな問いには、どの評価方法のどの指標を見るか。比べ方なら、構造が変わっても使い回せます。
実際のやりとり
牌種分類器の構造を検討したときの、ChatGPTとのやりとりです。
題材は、今アプリに入っている「2/2/2」という版です。軽量化した構造(C8 narrow)で、2〜4段目の畳み込みを分解し、depthwiseの出力をそれぞれ2倍にしています。構造の詳細はモデル構造の記事にあります。
この版を採用したことは覚えていました。ただ、採用した理由の数字までは覚えていません。そこで、まずそこから聞きました。
自分: これ2/2/2が一番良かったんだっけ?
AI: うん。DW mult※1検討の中では 2/2/2 を最終採用してた。
比較はこうだった:
stage2/3/4 DW mult iPhone p50※2 full mean※3 worst class recall※4 2/1/2 1.38 ms 93.10% 78.67% 2/2/1 1.41 ms 94.44% 83.56% 2/2/2 1.54 ms 95.13% 85.11% なので、最速は
2/1/2、次が2/2/1だけど、tail性能※5込みのバランスで 2/2/2 が一番良かった。なお、DW mult以外も含めると stage3-only※6 が worst 89.33% で精度自体はもっと良い。ただし2.55 msだったので、速度とのPareto※7で
2/2/2にした、という流れ。
自分: これfull c8と比べてどうだった?
AI: full C8※8 と比べると、2/2/2 narrow はかなり速い代わりに worst 側を落としてる。
C8 narrow 2/2/2 full C8 mean 95.13% 96.68% worst condition 85.11% 92.67% all-real acc※9 99.986% 99.994% iPhone p50 1.54 ms 13.30 ms iPhone p95 1.66 ms 13.88 ms なので差は、
- mean: -1.55pt
- worst: -7.56pt
- latency: 約8.6倍高速
って感じ。
特に Manzu worst※10 では narrow が 78.72%、full C8 が 91.49% で、ここは -12.77pt と結構落ちてた。
つまり結論としては、平均精度はかなり維持できてるけど、tail robustness を削って1/9近い速度にしたモデル。
当時2/2/2を採ったのも、full C8に精度で勝ったからではなく、iPhone実用速度とのParetoでかなり強かったから。
注釈(※1〜※10)
AIの答えは原文のままです。分かりにくい語の意味をまとめておきます。
※1 DW mult: 分解した畳み込みのうち、depthwiseの出力を何倍にするか。表の「stage2/3/4」は2・3・4段目のこと。
※2 iPhone p50: iPhoneのSafariで、1回の推論にかかった時間の中央値。p95は95パーセンタイル。
※3 full mean: 斜めから写したときの歪みや切り出し位置のずれなど、15通りの条件それぞれで測った正解率の平均。
※4 worst class recall: 一番苦手な牌の種類での再現率(その種類の牌を何割正しく当てたか)。この表の値については後述。
※5 tail性能: 平均ではなく、苦手な条件での成績のこと。
※6 stage3-only: 3段目だけを分解した版。
※7 Pareto: 速度と精度のどちらかを犠牲にしないともう一方を良くできない組み合わせの中から選ぶこと。
※8 full C8: 軽量化する前の、幅を絞っていない回転同変CNN。
※9 all-real acc: 実際に撮った牌の切り出し画像すべてでの正解率。
※10 Manzu worst: 萬子の5・6・7に絞った診断用データでの、一番悪かった条件の正解率。この3つは、数字の漢字部分が潰れると見分けにくいことが分かっていた。細かい特徴を見分ける力を測るため、専用の評価にしている。
2回聞いて、検討の中での位置づけ、採用の理由、捨てたものまで分かった気になりました。ところが、1つめの表を見直して引っかかりました。2/2/2 の worst class recall が 85.11% になっています。ClearMLで見たときは、6割くらいだった覚えがあります。
自分: 1つめさ、worst class recallじゃなくてworst cond引いてない?
clearmlで6割みたいに出てたはずだが
AI: うん、その指摘が正しい。さっき俺が 85.11% を worst class recall として扱ったのは間違い。あれは worst-condition accuracy 側。
ClearMLで見てた値だと、
- C8 narrow 2/2/2
- worst-class recall: 66.7%
- worst class × condition recall: 33.3%
- worst-condition accuracy: 85.11%
- full C8
- worst-class recall: 81.25%
- worst class × condition recall: 33.3%
- worst-condition accuracy: 92.67%
なので、「6割くらいだったはず」は合ってる。
2/2/2 narrow は worst class recall 66.7% まで落ちてて、full C8 の 81.25% に対して -14.55pt。
つまり前の「2/2/2でも worst 85%あるから十分」はかなり見え方が違う。85.11%はクラス単位の最悪値じゃなく、条件単位のaccuracyだった。ここは混同しちゃダメなやつ。
自分: worst class recallは分類器が分類器として牌分類できるのか?って最低限の能力を見る指標じゃない
ai failure log※11追記
AI: 追記した。
INC-20261021-01として、worst class recall = 分類器が各牌クラスを最低限ちゃんと分類できる能力を見る指標、worst-condition accuracyは条件別robustnessで別物、と明記済み。実metric名・値を確認せず取り違えないルールも入れた。
注釈(※11)
※11 ai failure log: AIがよく間違える作業と、その防ぎ方を残している記録。AIは返事をする前に、毎回これを読む決まりにしている。比較の取り違えに限らず、実験の回し方で起きたミスもまとめている。
AIのまとめは、数字は記録どおりでした。間違えていたのは、指標の名前です。
worst class recall は、一番苦手な牌の種類を何割当てられたかです。分類器として牌を見分けられるかという、最低限の能力を見ています。worst-condition accuracy は、歪みや切り出しのずれを加えた条件のうち、一番悪い条件での正解率です。こちらは、条件の変化への強さを見ています。
どちらも「worst」ですが、見ているものが違います。取り違えたままなら、苦手な牌の正解率が7割を切っているのに、「worst でも85%ある」と読んでいたはずです。
実は、指標の意味は評価方法の定義に書いてあります。ただ、AIは毎回そこまでは読みません。そこで、AIがよく間違える作業を、ミスの履歴として別に残しています。AIには、返事の前に毎回これを読ませています。今回の取り違えも、やりとりの最後でここに足しました。
なお、このやりとりの数値と、自分が覚えていた「6割くらい」は、当時の評価セットで測ったものです。モデル構造の記事では、全構成を同じ評価セットで測り直した値や、同じ構造の別モデルで測った推論時間を使っているので、同じモデルでも数値は一致しません。
おわりに
実験の数字は、覚えておく前提にしていません。実験の書き方を決めてAIに書かせ、正式な結果だけを残し、比べたいときはAIに聞きます。
AIのまとめは、今回のように間違えることもあります。それでも、記憶と食い違ったときには、記録から正しい値を引き直せました。数字を覚えておくより、どの数字にも記録から戻れることのほうが大事だと考えています。