同じコードをローカル LLM に 51 回監査させたら、多数決は「正しさ」より「しつこさ」を選んでいた
結論
- 温度 0・seed 固定・GPU 固定なら、指摘集合の実行間一致(Jaccard)は 1.00。seed を 6 通り変えても出力はバイト単位で同じ=温度 0 では seed は効かない。
- 温度 0.7 にすると同じコードでも指摘がばらつく。7B の Jaccard 0.52(行ズレを 1〜2 行許すと 0.80)、1.5B は 0.07。不一致の大半は「同じバグを違う行に指す」ドリフト。
- 多数決が残すのは「毎回出る指摘」であって「正しい指摘」ではない。本物のバグでも指す行が割れると過半に届かず落ちる。安全網のつもりが安全網にならない。
前提
- 誰向け: 「1 回では不安だから AI レビューを何回か回して、多数決を取ればいい」と考えている人。
- 使ったもの: Qwen2.5-Coder 1.5B / 7B(Apache-2.0)を Ollama 0.31.1 で実行。NVIDIA RTX 4060 Ti(CUDA)、量子化 Q4_K_M。
- 対象コード 4 種:
target_a(欠陥 7 件)、clean(欠陥なし・偽陽性の基準)、decoy(危険に見えるが実害の低い構造)、easy(誰でも分かる欠陥 6 件)。正解はコードの外のgt.csvに事前登録し、コード内にもプロンプト内にもヒントを書かない。 - 反復: 温度 0.7 で N=51(run ごとに seed 0〜50)。温度 0 の基準は N=11。
- 「同じ指摘」の定義:
(行番号, カテゴリ)の複合キー。自然文の言い換えは無視する。埋め込みの類似度は使わない(12 行目の null チェック漏れと 45 行目のそれを同一視するため)。
手順
- 出力は Ollama の structured outputs(JSON スキーマ強制)。各指摘は
{line_number, category, severity, description}、カテゴリは列挙型。 - 温度 0 条件は seed 固定+
top_k=1, top_p=1.0を明示(貪欲デコード)。温度 0.7 条件は run ごとに seed を変える。 - 生成(非決定)は著者が 1 回だけ回してログを凍結。集計(決定的)は誰でも同じログから再現できる二層構成。
- 集計コードとは別に、生ログから主要数値を独立に再計算する検算スクリプトを用意し、ALL PASS を確認してから公開。
うまくいかなかったこと
踏んだ誤りは 5 つ。③は測定器そのもののバグで、この記事でいちばん役に立つ部分です。
- 解答がプロンプトに漏れていた。 欠陥行に
# DEFECT: ...とコメントを書いたまま監査対象に渡していた。recall が高くて「良いベンチだ」と喜んだが、モデルはコメントを読んでいただけ。削除したら recall は崩落し、ばらつきは深まった。正解はテスト対象から見えない場所に置く。 - CPU だと思っていたら 100% GPU だった。 スレッド数(1 vs 4)を変数にしようとしたが、
ollama psが 100% GPU。num_threadは CPU 推論でしか効かない。 - 集計器に、直したそばからバグを入れた。 クラスタリングの補助関数を 1 行に「最適化」したとき、リストを append してから同じ参照を clear する古典的なミューテーションバグを埋め込み、多数決の真陽性・偽陽性のカウントを静かに壊した。気づけたのは、ある指摘数を手で数え直したから。
- 自分が「偽陽性」と呼んだものは本物のスメルだった。
decoyのshell=True・evalは Bandit 等が実際に警告する。多数決を生き残った「偽陽性」は、むしろ一貫して正しい指摘だった可能性がある。だから主張は「無害/有害」でなく「しつこさ」で立てた。 - 測った本人の記憶が結果を捏造した。 原稿の初期版に「
md5への警告も多数決を生き残った」と書いたが、ログでは 7B・decoy で md5 系の票は 51 回中 0 回(生き残ったのはeval33 票・shell=True28 票だけ)。印象で数字が頭の中にできあがっていた。
数値
| 条件 | 指摘集合の実行間一致(Jaccard) |
|---|---|
| 温度 0・seed 固定・GPU 固定(4 条件すべて) | 1.00(実行間で変動なし) |
温度 0.7・target_a・7B |
0.52(行ズレ 1〜2 行を許すと 0.80) |
温度 0.7・target_a・1.5B |
0.07 |
- 温度 0 で seed を 6 通り変えても出力はバイト単位で一致。
target_a・per-run の recall は 7B 0.31、1.5B 0.05。7B は precision 0.98(出せば当たる)。1.5B は 51 回でファイル 32 行中 25 行を叩く多弁かつ低 recall だったため、主張は 7B で立て、1.5B は対照群。- 多数決を生き残った
decoyの指摘:eval33 票・shell=True28 票・md50 票(51 回中)。
まとめ
固定すれば決定的、揺らせば揺れる。そして多数決が見ているのは指摘の「顕著さ」であって「正しさ」ではありません。AI レビューを検証に使うなら、回数を増やすより、正解を先に外へ置き、集計器を自分でも疑い、数値を独立に再計算する側の規律が要ります。
元の記事(手順の全文・データ・再現手順): Zenn(技術版・凍結ログと集計コード)・2026-07-02
AI の出力を検証の合否基準で確かめる設計は Works で受けています。
この記事での AI の利用
監査役のモデルはローカル LLM(Qwen2.5-Coder 1.5B / 7B・Ollama)。実験ハーネスと集計コードの下書きに生成 AI を使った。正解ラベル(gt.csv)はコードの外に人間が事前登録し、本文の数値はすべて集計コードとは独立に生ログから再計算して一致を確認した。文章は人間が決めた。