自動ファクトチェックのスコアが、記事の質ではなくマークアップを測っていた話
この記事について
自動生成される「ファクトチェック・スコア」を記事に表示している媒体で、そのスコアの棚卸しをした。結果、全78本のうち34本(44%)が0点、しかもその34本はすべて公開済みで、うち31本が同時に「VERIFIED(検証済み)」を名乗っていたことがわかった。
自慢話ではない。自前で作った品質指標が、4か月かけて意味を失っていた経緯と、どこから手を付けたかの記録である。同じように「AIが書いた記事に自動で品質スコアを出す」仕組みを作っている個人・小規模チーム向けに書く。
前提 — 「一次ソース至上主義」をやめた、その先
このスコアリングは以前、ドメインの格で採点していた。.go.jp や .gov へのリンクがあれば高得点、という設計である。これは本文と無関係な公式リンクを末尾に並べるだけで点が稼げてしまうため、配点をソース紐付けベースに刷新した。現行の配点は次のとおり。
| 項目 | 配点 | 何を見るか |
|---|---|---|
| Traceability | 0-35 | ソースの本数 |
| Diversity | 0-25 | 異なるドメインの数 |
| Evidence | 0-25 | 本数×ドメイン数の充実度 |
| Domain Bonus | 0-15 | 公的一次資料への加点(無くても減点しない) |
「ドメインの格は加点のみ、ソースベースで書かれていれば80点を下回らない」という思想である。設計としては今でも妥当だと思っている。問題は、この配点が何を入力にしていたかの方だった。
見つかったもの — 分布が二極化していた
全記事のスコアを引いたところ、こうなっていた。
100点 ██████████ 10本
95点 ██████ 6本
90点 ████████ 8本
85点 █ 1本
...
0点 ██████████████████████████████████ 34本 ← 44%
中間がほとんどなく、80点台以上か、0点かに割れている。「80点を下回らない」設計なので、下回った瞬間に0まで落ちる。これは配点の欠陥というより、入力が二値だったことの現れだった。
0点の記事は、出典を書いていないわけではなかった
0点だった記事の本文を読むと、出典は書いてある。
- ある記事は「NHK放送文化研究所」「総務省」を根拠に挙げている
- 別の記事は「厚労省毎月勤労統計」の数字を引いている
リンクになっていないだけである。スコアの抽出処理は本文中のマークダウンリンクを数えていたので、散文で「厚労省毎月勤労統計によれば」と書かれた出典は1本も拾わない。
つまりこのスコアが測っていたのは記事の質ではなく、執筆時にURLをマークアップしたかどうかだった。皮肉なことに、0点の中には「自分たちは仕事を見せる」と宣言した記事と、「ファクトチェックの死角」を論じた記事の両方が入っていた。
もっと悪かったのは、表示の方
スコア以上に問題だったのは、パネルの描画だった。表示部品は status と score を独立に描画していたため、同じパネル内にこれが並んでいた。
| 出どころ | 表示 |
|---|---|
status: "verified" | VERIFIED チップ |
score: 0 | ランク D / 0pt |
| ソース0本 | 一次 0 / 二次 0 |
読者から見れば「検証済みと書いてあるのに、根拠ゼロで最低ランク」である。どちらを信じても間違う。
原因ははっきりしていた。score は機械生成だが、status は手書きだった。 再計算ツールは status と監査日を「保持する」仕様で、つまり一度手で書いた “verified” は誰にも上書きされないまま残り続ける。機械が出した0点と、人間が書いた「検証済み」が、同じ箱の中で何か月も同居していた。
ついでに見つかった、もう一つの取り違え
ソース一覧の「一次/参考」バッジにも同種のバグがあった。分類は3値(primary / secondary / unknown)あるのに、描画側の条件がこうなっていた。
secondary なら「参考」、それ以外は「一次」
未分類が「一次」に倒れる。 結果、分類器が判定できなかった個人ブログやアグリゲータにまで「一次ソース」のバッジが付いていた。サイト全体で「一次」表示は229件あったが、実際に一次と分類されていたのは136件だった。
直した順番と、直さなかったこと
3段階に分け、安く確実なものから当てた。
① 描画の取り違えを直す(済) 未分類は「参考」に倒す。過大主張を避ける側にデフォルトを置いた。あわせて手書きで水増しされていた1本のスコアを再計算で同期させた。
② 「VERIFIED」の嘘を消す(済) ソース0本で “verified” を名乗っていた31本を “unreviewed” に落とした。差分は status 1行×31本のみ。
③ 記事に実URLを戻す(未着手・本丸) 散文で書かれている出典に実URLを当て、機械可読なソースとして登録し直す。34本ぶん。これは自動化できない——「厚労省毎月勤労統計」がどの回のどの表かは記事の文脈でしか決まらないからである。
0点を「隠す」対処は取らなかった
最初に浮かぶ対処は「score が0のときはパネルを出さない」である。これは採らなかった。
score: 0 は「ソース未登録」であることの、唯一の機械可読な印だからだ。見栄えのために隠すと、③の対象が検出できなくなり、穴は永久に埋まらない。同じ理由で、画像が無い記事の frontmatter に共通サムネイルを焼き込むこともしていない。空欄は欠落の証拠であって、埋めるべき見た目の穴ではない。
フォールバックは描画時にだけ当てる。データには書かない。 これは他の媒体でも繰り返し踏んだ地雷で、いまは運用規約に明文化してある。
なぜ4か月も気付かなかったか
一番効く再発防止は、原因の側にある。
このスコアリングには元々、記事が公開された直後に、LLMがソースを実際に読みに行って「記事の主張を本当に裏付けているか」を判定する自動チェックがあった。「リンクは実在する。だがあなたの主張の証明にはなっていない」と言えるエンジンで、これが動いていれば「本文に出典が散文で書いてある」ことも「ソースが0本である」ことも検出できたはずである。
この自動チェックは、WordPress から静的サイトへ移行したときに消えた。呼び出しが WordPress への投稿処理と一体で書かれていたため、公開のやり方ごと作り直した時点で一緒に外れた。移行後に残ったのは、リンクの本数を数える決定的なスクリプトだけである。
同じことが以前にもあった。静的サイトへ移行したときには、記事を時間差で公開していく自動処理が外れていた。今回外れたのは検証の側である。移行のたびに、裏で回っていた自動処理が1つずつ消えている。 消えたことに気付くのがいずれも数か月後という点も同じだった。
得た規律
- 自動生成の値と手書きの値を、同じ箱に混ぜない。 混ぜるなら、どちらが正本かを機械が判定できる形にする。今回は「機械生成」と明文化されていたのが score / 内訳 / 分類の3つで、status だけが手書きのまま取り残された
- 指標が測っているのは「入力の形式」であって「対象の質」ではないことを疑う。0点と満点に二極化していたら、それは対象ではなく抽出処理を見るサイン
- 見栄えのために欠落を埋めない。 欠落は次の作業の入口である
- 移行チェックリストには、画面に出ないものを書く。 移行時に数えるのは普通ページと記事だが、消えて困るのは裏で定期的に走っていた自動処理の方である。目に見えないので、消えても誰も気付かない
実装ステータス
- ①描画の取り違え・②status の是正: 適用済み
- ③実URLの登録(34本): 未着手
- ソースを読んで主張との整合を見る自動チェックの再実装: 未定。決定的なスクリプトで代替できる範囲と、LLMでなければ判定できない範囲の切り分けから
- 分類ドメインの精度(当事者の一次発信を一次と見なすかの設計判断): 課題として起票済み・未決
検証環境: Astro(静的生成)/Python 3.11/スコアリングは決定的スクリプト、LLM不使用。