この差は再現する差か — 精度低下の測り方
目標
同じテストサンプルで、FP32のオリジナル・以前のデプロイ版・整数化した候補の3つを動かして、予測と出力値を残し、精度の差が再現される差かどうかを判断するツールacccmp.pyを作成します。区間・ペア比較・反転したサンプル・出力の差をそれぞれ測り、ベースラインを2つ置いて判定を下します。
なぜ重要なのか
「精度が0.3%落ちた」は、それ自体では判断の根拠になりません。サンプル400個で、精度1つの95%信頼区間は、幅が8パーセントポイントに近くなります。2つのモデルを別々に測って引き算した値では、これより小さい差を語れません。 語れるようにするのは、同じサンプルです。2つのモデルが同じ入力を見たなら、どちらも当てたサンプルと、どちらも外したサンプルは判定に使えず、片方だけが当てたサンプルだけが情報になります。その分かれた件数が実際の分母です。 ベースラインも1つではありません。FP32のオリジナルは、量子化でどれだけ削られたかに答え、以前のデプロイ版は、今動いているものより悪くなるかに答えます。2つの答えが逆の向きになることがあるので、両方を書きます。 そして、出力の差とタスクの指標は別のものです。最大絶対誤差が大きく出ても精度は変わらないことがあり、その逆もあります。判断の境界から遠いサンプルは、出力が揺れても答えが変わらないからです。 採点ツールは、皆さんが書いた数字を信じません。毎回違うシードとサンプル数で自分の予測表を用意して皆さんのツールを実際に実行し、同じ計算をやり直して照合します。ステップ1は、皆さんが残したシードでテスト入力を作り直して、皆さんのモデルを直接動かしてみます。
ステップ
- /root/acc/gen_eval.pyを作成して実行し、モデル3つを作ってください(出力先: /root/acc/preds.json、/root/acc/eval_seed.json)。
- /root/acc/acccmp.pyに
summaryを作成して、サンプル数と実行ごとの精度を出力させてください。 intervalを追加して、精度1つの95%信頼区間を、Wilsonスコア区間で出力させてください。pairedを追加して、同じサンプルでの2x2の分かれた件数と、ペア比較の統計量を出力させてください。flipsを追加して、反転した予測だけを向きごとに数えさせてください。outputsを追加して、出力値の差(最大絶対誤差・平均絶対誤差・コサイン)を出力させてください。baselineを追加して、2つのベースラインをそれぞれ比べさせ、皆さんのデータの判定を書いてください(出力先: /root/acc/verdict.json)。- /root/acc/accuracy_report.mdを4つのセクションで書いてください。
参考
- Pythonは
/opt/onnx-lab/bin/pythonです。システムのpython3には、numpyもonnxもありません。 - 実行の契約:
/opt/onnx-lab/bin/python /root/acc/acccmp.py <명령> <preds.json> [인자...](プレースホルダーは、コマンドと引数です)。成功すれば終了コードは0、ファイルがなければ3、使い方が間違っていれば2です。答えはJSONを1つの塊で標準出力に出します。 preds.jsonの形は{"labels": [정수...], "runs": {"fp32": {"pred": [정수...], "logits": [[실수...], ...]}, "prev": {...}, "int8": {...}}}です(プレースホルダーは、整数と実数です)。logitsは丸めず、そのまま書きます。eval_seed.jsonの形は{"seed": 정수, "n": 400, "features": 16, "classes": 10, "label_sigma": 1.0}です(プレースホルダーは整数です)。- テスト入力は
numpy.random.default_rng(seed).standard_normal((n, features)).astype(numpy.float32)です。正解ラベルは、FP32の出力(float64に上げた値)にnumpy.random.default_rng(seed + 1).standard_normal(모양) * label_sigmaを加えてargmaxしたものです(プレースホルダーは形状です)。採点ツールがこの規則で作り直し、皆さんのモデルを直接動かします。 - モデルは、入力名が
x、入力の形が[None, 16]、出力クラスが10個です。prev.onnxはquantize_dynamic、int8.onnxはquantize_static(quant_format=QDQ)で作ります。3つのファイル名は、/root/acc/fp32.onnx・/root/acc/prev.onnx・/root/acc/int8.onnxです。 summaryの応答は{"n": 정수, "runs": {이름: {"correct": 정수, "accuracy": 실수}}}です(プレースホルダーは、整数、名前、実数です)。intervalの応答は{"run", "n", "correct", "accuracy", "center", "low", "high", "half_width"}です。Wilsonスコア区間をz = 1.96で計算します。中心はpではなく(p + z^2/(2n)) / (1 + z^2/n)で、半幅はz/(1 + z^2/n) * sqrt(p(1-p)/n + z^2/(4n^2))です。paired <a> <b>の応答は{"a","b","n","both_correct","only_a","only_b","both_wrong","accuracy_a","accuracy_b","delta","discordant","statistic","significant"}です。deltaは、bからaを引いた値です。discordantは、only_aとonly_bの合計です。statisticは、このラボが固定した物差しで(|only_a - only_b| - 1)^2 / discordantであり、discordantが0なら0.0です。significantは、statisticが3.841459より大きいかどうかです。flips <a> <b>の応答は{"a","b","changed","to_wrong","to_right","changed_both_wrong","top_flip"}です。changedは予測が異なるサンプルの数、to_wrongはaが当たりbが外れた数、to_rightはその逆、changed_both_wrongは両方外れたのに答えが変わった数です。top_flipは、最も多い反転の[a 예측, b 예측, 건수]で(プレースホルダーは、aの予測、bの予測、件数です)、同数ならa予測・b予測が小さいほう、反転がなければnullです。outputs <a> <b>の応答は{"a","b","max_abs_error","mean_abs_error","cos_min","cos_mean","argmax_changed"}です。平均絶対誤差は要素全体の平均で、コサインはサンプルごとに求めて、最小値と平均を出します。分母が0なら、そのサンプルのコサインは1.0とします。baseline <run>の応答は{"run","baselines","vs","worst"}です。baselinesはrunを除いた残りの名前を並べ替えたもので、vsの各項目は{"delta","only_a","only_b","discordant","statistic","significant"}で、そのベースラインをa、runをbとした値です。worstは、deltaが最も小さいベースラインの{"baseline","delta"}です。verdict.jsonの形は{"candidate","n","accuracy","delta_vs_fp32","delta_vs_prev","half_width","discordant_vs_fp32","statistic_vs_fp32","significant_vs_fp32","decision","reason"}です。half_widthは候補のWilson半幅です。decisionはこのラボが固定した規則で、significant_vs_fp32が真でdelta_vs_fp32が0より小さければhold、そうでなければshipです。accuracy_report.mdは## 무엇을 견주었나## 같은 표본에서 본 차이## 이 차이가 재현되는가## 판정과 한계の4つのセクションです(韓国語の見出しは、順に「何を比べたか」「同じサンプルで見た差」「この差は再現されるのか」「判定と限界」という意味です)。- 公式ドキュメント: ONNX Runtimeの量子化 · ONNX Concepts · QuantizeLinear
- よくある間違い: 別々に測った2つの精度を引き算して報告すること、区間が重なっているからという理由で差がないと言うこと、最大絶対誤差で精度の代わりにすること、ベースラインをFP32だけにすることです。
- このラボは、時間・スループットを測りません。このMacはエミュレーションなので、レイテンシが2倍まで揺れます。
3つのバージョンを同じサンプルで動かして予測を残す
/root/acc/gen_eval.pyを作成して実行し、/root/accの下にfp32.onnx・prev.onnx・int8.onnxと、/root/acc/preds.json、/root/acc/eval_seed.jsonを作ってください。テスト入力と正解ラベルは、参考のセクションのシードの規則にそのまま従います。
モデルは、onnx.helperでMatMul + Add + Reluをつなげて作ります。prev.onnxはquantize_dynamic、int8.onnxはquantize_staticにCalibrationDataReaderを渡して作ります。正解ラベルをFP32の出力から作りつつ、ノイズを加えるのは、精度が1.0だとどんな差も見えないからです。logitsはfloat()でそのまま書き、丸めないでください。
サンプル数と精度を1か所にまとめる
/root/acc/acccmp.pyにsummary <preds.json>を作成して、サンプル数と実行ごとの正解件数・精度をJSONで出力させてください。
合計から出さずに、サンプルごとに当たったかどうかを真偽のリストで残してください。あとのステップのペア比較と反転の集計は、すべてこのリストの上で行われます。実行名は並べ替えて入れます。
精度1つに区間を付ける
interval <preds.json> <run>を追加して、その実行の精度に、95%のWilsonスコア区間を付けて出力させてください。run・n・correct・accuracy・center・low・high・half_widthを入れます。
zは1.96に固定します。Wilson区間の中心は、サンプルの比率pではなく、0.5のほうに少し引き寄せられた値です。そのため、lowとhighは、pを真ん中にしません。精度が0や1でも幅が0にならないことが、この区間を使う理由です。
同じサンプルでペアにして比べる
paired <preds.json> <a> <b>を追加して、2x2の表(both_correct・only_a・only_b・both_wrong)と、delta・discordant・statistic・significantを出力させてください。
どちらも当てたサンプルと、どちらも外したサンプルは、2つのモデルを分けられません。判定の分母はnではなく、分かれた件数です。統計量は参考のセクションの式をそのまま使い、分かれた件数が0なら0.0とします。
反転した予測だけを向きごとに数える
flips <preds.json> <a> <b>を追加して、changed・to_wrong・to_right・changed_both_wrong・top_flipを出力させてください。
changedは、残り3つの合計です。この恒等式が合わなければ、どこかを2回数えたか、数え漏らしたのです。間違った答えが別の間違った答えに変わった件数は、精度に影響しませんが、ユーザーの目には答えが変わったと見えます。
出力の差とタスクの指標を分けて測る
outputs <preds.json> <a> <b>を追加して、max_abs_error・mean_abs_error・cos_min・cos_mean・argmax_changedを出力させてください。
出力が大きく動いたからといって、判断が変わるとは限りません。判断の境界から遠いサンプルは、出力が揺れても答えがそのままです。2つの値を1つの応答に入れて並べて見ると、その関係が目に入ります。
ベースラインを2つ置いて判定する
baseline <preds.json> <run>を追加して、残りの実行をそれぞれベースラインにして比べさせ、皆さんのデータの判定を、参考のセクションのキーで書いてください(出力先: /root/acc/verdict.json)。
オリジナルと以前のデプロイ版で、答えが逆の向きになることがあります。worstは、deltaが最も小さいベースラインで、同数なら名前が先のほうです。判定の規則は参考のセクションに固定されているので、そのまま写し、reasonには、なぜそうなったのかを1行で書いてください。
読む人が判断できるように書く
/root/acc/accuracy_report.mdを、## 무엇을 견주었나 ## 같은 표본에서 본 차이 ## 이 차이가 재현되는가 ## 판정과 한계の4つのセクションで書いてください(韓国語の見出しは、順に「何を比べたか」「同じサンプルで見た差」「この差は再現されるのか」「判定と限界」という意味です)。サンプル数・分かれた件数・判定が、数字と言葉で本文に入っている必要があります。
数字を書くときは、どこから出た数字なのかも一緒に書いてください。限界のセクションには、この判定が語らないことを書きます。このラボは、時間もメモリも測っておらず、1組のテストサンプルだけで見たものです。