Jevで同姓同名チェックを321ペア試した:異体字は拾えるが、同音の別名とは区別できなかった
試用と評価の途中経過です。名前はすべて、機械的に作った架空のものです。結果は、この合成データと私の設定に限った話で、サービスの性能や安全性を評価・保証するものではありません。
前回のノートでは、Jevに同姓同名のチェックを1件だけ試し、「該当なし」の選択肢がなかったために誤答になったことを書きました。あのときは「日本語が苦手なのかもしれない」という印象を持ちましたが、原因の多くは質問の作り方でした。今回は、印象で語らないために、件数を増やして測りました。
何を確かめたかったか
想定した作業は、二つの名簿の突き合わせです。リストAとリストBがあり、Bに載っている人のうち、Aにも同じ氏名の人がいれば除きます。数の規模は、Aが約3万人、Bが約3千人です。名簿の突き合わせは、事務所の作業でも起こり得る、ごく普通の処理です。
難しいのは、日本語の氏名の揺れです。
- 姓の異体字:渡辺・渡邉・渡邊・渡部、斎藤・齋藤・齊藤、高橋・髙橋など。
- 名の異体字:恵子・惠子、竜也・龍也など。
- 氏名の途中の空白や、全角・半角の違い。
- 同じ読みで字が違う別の名前:一郎・一朗、翔太・将太、裕子・祐子など。
要件は二つに決めました。第一に、揺れの疑いがあるものは、自動で同一扱いにせず、人が確認する。第二に、間違いの重さは対称ではなく、同一人物を「別人」と誤る見逃しのほうが、別人を「同一かも」と疑う誤検出より重い。したがって、主に見た指標は見逃し率です。
全体の作り
Jevだけで判定するのではなく、規則で絞ってからJevに渡す形にしました。Claude Codeに日本語で依頼して、Pythonの標準ライブラリだけで書いたスクリプトです。
- 正規化:Unicodeの正規化(NFKC)をかけ、空白を全て除き、英字は小文字にする。
- 完全一致:正規化後にAと一致したら「除外」。空白や全角・半角の違いは、ここで吸収される。
- 姓の異体字グループに入っていて名が一致する、または文字列の類似度が閾値(0.72)以上:「要確認」とし、Jevに問い合わせる。
- それ以外:Aに該当なしとして「対象」に残す。
Jevを全部の組み合わせに使わないのは、Aが3万、Bが3千だと、組み合わせが約9,000万になるからです。……というつもりで作ったのですが、実は今のスクリプトは、Bの1件ごとにAの全件を調べる作りでした。実測すると、Aが3万件でもB1件あたり0.13秒で、Bが3,000件でも約6.5分でした。この規模なら、全件走査でも困りません。「絞り込みが必要」という当初の説明は、実装と合っていませんでした。
Jevへの質問の作り方
前回の反省から、選択肢から選ばせる形はやめました。代わりに、二つの質問をしています。
- 「この二つの文字列は、同じ日本語の人名の表記揺れと言えるか」:0から1の確率で答えさせる(noulという型)。
- 「今の判断にどれくらい確信があるか」:段階(score型)で答えさせる。評価の基準を並べる欄は、配列で渡す必要がありました(対応表のような形で渡すとエラーになりました)。
呼び出しはVercelのAI Gateway経由です。応答は速く、後述の321回でも、待ち時間は気になりませんでした。
実験1:15件のサンプルで動作確認
まず、架空の名簿(A40件、B15件)で動かしました。Jevを呼んだのは5件です。
- 空白の揺れ(「山 田 太郎」、全角空白の「吉田 拓也」)は、正規化だけで完全一致になり、除外された。
- 異体字(渡邉翔太、渡部翔太、齋藤誠)は、自動で除外されず、Jevのスコア付きで「要確認」になった。
- 1字違い(田中健大と田中健太、鈴木一朗と鈴木一郎)も「要確認」になった。
- API呼び出しのエラーは0件。同じ入力で2回実行したところ、判定は全件同じで、スコアの揺れは0.06以内だった。
同じ入力でも、スコアは少し揺れます。閾値を決めるときは、この揺れの分だけ余裕を持たせる必要があります。
実験2:321ペアの評価データを作る
15件では、何も言えません。そこで、正解のラベルを付けた氏名のペアを、機械的に321組作りました。乱数の種を固定してあるので、同じものを何度でも再生成できます。
- 同一(異体字だけが違う)150組:姓の異体字100、名の異体字50。
- 別の名前171組:同音の別姓45(阿部と安部、菊地と菊池など)、同音異字の名50(一郎と一朗など)、1字違い40(健太と健大、太郎と次郎など)、形の似た字16(萩原と荻原、徹と撤など)。
- ラベルなし20組:斎と斉の系統。別の字ですが、実務では混同されるので、正解を決めずに様子を見た。
どのペアも、片方の姓か名だけを変えて、残りは同じにしています。採点できたのは285組で、残りの36組はAPIの呼び出しに失敗しました。原因は、ログを取っていなかったので分かりません。失敗は、種類ごとにほぼ均等に散らばっていて、結果に偏りは出ていないと判断しました。
結果1:同一と別名は、かなり分けられた
同一のペアと別名のペアを、Jevのスコアでどれくらい分けられるかを、AUCという指標で見ました。1.0が完全、0.5が偶然と同じです。今回は0.956でした。同一のスコアが、無作為に選んだ別名のスコアより高い確率が、約95.6%という意味です。
- 同一(異体字だけが違う)136組のスコアは、最小0.69、中央値0.84、最大0.95。0.6を下回るものは、1組もなかった。
- 別の名前132組のスコアは、最小0.05から最大0.88まで、広く散らばった。
種類別の平均スコアは、次のとおりです。1字違いで読みも変わるものは、はっきり低く出ました。一方で、同音の別字や、形の似た字は、高めに出ました。
- 名の異体字(同一):0.85
- 姓の異体字(同一):0.82
- 斎と斉の系統(ラベルなし):0.87
- 形の似た字(別名):0.69
- 同音の別姓(別名):0.56
- 同音異字の名(別名):0.56
- 1字違い(別名):0.31
別名なのにスコアが高かった例は、木村陽平と木村洋平(0.88)、林崇と林祟(0.87)、鈴木裕子と鈴木祐子(0.84)、渡辺陽子と渡部陽子(0.82)です。Jevは、「読みが同じ」ことを強く見ているようです。同じ名前の異体字と、同じ読みの別の名前は、Jevには近く見えます。人間にとっても、字面だけでは判断できないので、無理のない結果だと思いました。
前回の「日本語が苦手なのかも」という印象は、この結果を見る限り、少なくとも異体字については当たっていませんでした。
結果2:閾値を決めると、何が起きるか
スコアがある閾値以上なら「要確認」、未満なら「別名」として対象に残す、と決めたときの誤りを数えました。括弧内は、95%の信頼区間の上限です。見逃しは、同一なのに閾値未満になったもの、誤検出は、別名なのに閾値以上になったものです。
- 閾値0.5:見逃し0/136。誤検出は74/132(56.1%)。
- 閾値0.6:見逃し0/136(上限2.7%)。誤検出は53/132(40.2%)。つまり、別名の約6割を、人の確認から外せる。
- 閾値0.7:見逃し1/136。誤検出は31/132(23.5%)。ただし、同一の最低スコアが0.69なので、余裕がない。
- 閾値0.8:見逃しが43/136(31.6%)に増える。
- 閾値0.9:誤検出は0だが、見逃しは116/136(85.3%)。同一の約15%しか拾えない。
見逃しを出さずに人の確認を減らすなら、閾値は0.6が出発点になりそうです。0.9以上を「同一」として自動で除外するやり方は、拾える同一が約15%しかなく、実用になりません。しかも、高得点の側に、同音の別名が混ざります。Jevに自動で除外させることは、今回の結果では勧められません。
結果3:確信度は、誤りの検出には使えなかった
確信度の平均は、同一が0.68、別名が0.41でした。同一のほうが高いのは、自然です。ところが、別名なのにスコアが0.7以上の高得点になった31組の確信度の平均は、0.44でした。「間違えているとき、確信度が低い」という関係は、ほとんど見られません。前回の学びと同じで、確信度は、答えが正しいことの保証ではありませんでした。確信度は、人が確認する順番の目安くらいに考えるのがよさそうです。
いちばん大きな発見:問題はJevの手前にあった
この評価で、一番意外だったのは、Jevの成績よりも、Jevに届く前の絞り込みでした。同一のペア136組のうち、28組(20.6%、信頼区間は14.6%から28.1%)が、Jevに問い合わせる前に「対象」に残されていました。つまり、同一の氏名なのに、対象に残ってしまう組が、約2割あったことになります。
原因は二つです。
- 姓の異体字のグループが、渡辺、斎藤、高橋、浜田、沢田の5組しかなかった。冨永と富永、關と関、廣田と広田、栁田と柳田などは、グループに入っていなかった。
- 類似度の閾値0.72が、短い名前に厳しかった。名前が3文字だと、1文字違いで類似度が0.67になり、閾値を下回る。「佐藤凉」と「佐藤涼」のような組が、これで落ちた。
Jevの見逃しは0件でも、システム全体では約2割を取りこぼしていました。AIの精度を測る前に、AIに渡す候補の作り方を疑うべきでした。
正直なところ、Jevは要るのか
今回の結果から言えるのは、次のとおりです。
- 異体字は、文字ごとの対応表で正規化すれば、確実に、無料で、毎回同じ結果で拾える。氏名を外部に送る必要もない。Jevが得意としたのは、まさにこの部分だった。
- Jevが確かに役立ったのは、「要確認」の件数を、見逃しを出さずに約6割減らす場面。ただし、単純な規則(異体字の対応表と、編集距離1文字以内)でも、かなり絞れる可能性がある。
- Jevのスコアは、同じ入力でも0.06ほど揺れる。判定の理由も説明できないので、後から根拠を問われる場面には向かない。
つまり、今回の設定では、Jevの追加価値は、異体字の対応表ではカバーできない揺れが、どれだけあるかで決まります。それが多くなければ、Jevはほとんど要りません。次のステップは、「異体字表と編集距離だけ」と「それにJevを足したもの」で、要確認の件数と見逃しを比べることです。
注意点があります。今回の正解ラベルは、私が機械的に決めたものです。「異体字だけの違いは同一」と決めているので、異体字の表だけで正解できるように作ってあり、Jevには不利な比べ方です。最終的な判断は、本物の名簿で、人がラベルを付けた例を使って行う必要があります。
作業でつまずいたこと
- APIキーの受け渡し。キーをチャットに貼らず、環境変数で渡す方法を選びましたが、ターミナルへの貼り付けが二重になったり、コマンドが別のシェルで実行されて変数が空になったりしました。何度かやり直す間に、キーが画面と会話の記録に出てしまいました。実験用に発行した専用のキーで、予算の上限も小さくしてあり、終了後にすぐ削除しました。キーは、実験用に別に発行し、使い終わったら削除する運用が、やはり必要です。
- 採点の失敗。321回のうち36回が失敗しました。再試行を入れてありましたが、原因はログに残っておらず、分からずじまいです。再実行すると、失敗した分だけ続きから採点する作りにしてあるので、あとから補えます。
- 説明と実装のずれ。「絞り込みで組み合わせを減らす」と説明した部分が、実際には全件走査でした。「自動で除外しない」と書いた方針にも、Jevのスコアが低いとき自動で対象に戻す例外が、コードに残っていました。実験してみて、初めて気づきました。
個人情報と守秘義務
今回は、すべて架空の名前で試しました。実際の名簿で同じことをする場合、氏名をVercel経由で外部のAI事業者に送ることになります。これが第三者提供や委託にあたるか、契約やデータの取扱い、保管期間がどうなっているかを、実施の前に確認する必要があります。依頼者や相手方の氏名は、特に慎重な判断が必要です。事務所の作業に使うときは、外部に送らない方法(対応表など)で足りるかを、まず検討するのがよさそうです。
限界
- 合成データでの結果で、本物の名簿の氏名の分布とは異なる。
- 「正解」は、異体字だけの違いは同一、同音の別字は別名、という私の決め方によるもの。斎と斉のように、実務で扱いが分かれるものは、ラベルを付けていない。
- 最大でも285組の評価で、A3万・B3千の実際の規模での性能は、確認していない。
- 指示は英語で、氏名だけが日本語。指示も日本語にした場合の差は、確認していない。
- かな表記やローマ字の氏名は、対象にしていない。
次に試すこと
- 異体字の対応表による正規化を実装し、Jevありとなしで、要確認の件数と見逃しを比べる。
- 類似度の基準を、名前の長さに依存しない編集距離に替える。
- 失敗した36組を再採点する。
- 架空でなく、本物の名簿のうち、要確認になった例を人がラベル付けして、最終的な閾値を決める。(この作業の進め方は、守秘義務の点から、別に検討します。)
本記事は、特定のサービスの性能や安全性を評価・保証するものではなく、合成データによる試用の記録です。サービスの仕様や料金は変更されることがあります。依頼者や事件に関する資料は、外部の生成サービスに入力していません。