数百件ある正規の分類名を毎回LLMに渡して選ばせる代わりに、正規名を1件も見せずに好きに答えさせ、その"でたらめ"を埋め込みベクトルで正規名にマッピングする。選択肢を絞ってから当てさせる発想を、逆に振り切った技法を読む。
Doug Turnbull氏が提案した「幻覚分類→埋め込みマッチング」の3ステップと、なぜそれが選択肢を全部渡すより安く・軽いモデルでも動くのかという理屈。自分のタグ付け・カテゴリ分類の運用に当てはめるときの、向き不向きの見極め方。
LLMに何かを分類させるとき、素直なやり方は「取りうる分類の一覧をプロンプトに全部書いて、その中から選ばせる」だ。Pydanticなどで構造化出力のスキーマを組めば、モデルは決められた選択肢の中からしか答えられない。安全で扱いやすい方法に見える。
ただしこのやり方には規模の壁がある。Doug Turnbull氏がブログ記事「Don't classify, hallucinate」で扱ったのは、家具ECサイトの検索クエリ分類データセット(Wayfair WANDS)だ。「Furniture / Living Room Furniture / Coffee Tables & End Tables / Coffee Tables」のように、階層をたどる正規分類が数百件存在する規模になると、リクエストのたびに全選択肢をプロンプトへ詰め込むのはトークンとして重く、モデルによっては選択肢数の上限そのものに引っかかる。Simon Willison氏も自身のブログで同じ問題を報告している——氏のブログには1,856個のタグが存在し、新しい記事に既存タグを付けさせようとしても、全タグを毎回渡すのは非現実的だった。
Turnbull氏が提案したのは、正規分類を一切見せない3ステップの手順。
「brown coffee table」のような検索クエリに対して、実在する分類の一覧を渡さず、モデルに「このクエリが属しそうな分類名」をその場で自由に作らせる。モデルは正規の語彙を知らないので、実在しない架空の分類名(例:「Furniture / Living Room / Tables / Coffee」)を生成する——これがタイトルの"hallucinate"(幻覚)にあたる部分だ。
MiniLMのような軽量な埋め込みモデルで、LLMが生成した架空の分類名と、実在する正規分類の一覧(数百件)の両方をベクトル化する。これは分類のたびに毎回LLMへ渡す必要はなく、正規分類側は事前に一度だけベクトル化しておける。
架空分類のベクトルと、正規分類ベクトル群とのドット積(コサイン類似度)を計算し、最も近い正規分類をマッチとして採用する。LLMが「実在すると思って」作った分類名は、意味的には実際の分類にかなり近い場所に位置することが多いため、この近傍探索だけで正しい正規分類に着地する、というのが技法の骨格だ。
ここが逆転している点。従来の発想は「選択肢を絞ってから当てさせる」。この技法は選択肢を一切見せずに自由に答えさせてから、後で正解の近くを探す。制約をかけるほど精度が上がるという直感を、あえて外しにいっている。
この技法の実用上の利点は、精度そのものより「何を送らなくて済むか」にある。
ただし正確な精度の定量比較(従来の構造化出力方式とのA/Bテストなど)は、Turnbull氏の原文には記載がない。「実例ではうまくマッチした」という事例ベースの報告であり、あらゆるデータセット・分類粒度で同等に機能するとは限らない点は留保して読む必要がある。
この技法がそのまま刺さるのは、「正規の分類・タグ・カテゴリの一覧が数十〜数百件あり、それを分類のたびにLLMへ渡している」設計を自分で持っている場合だ。FLOW・SURPLUS双方の記事にタグ付けをする運用や、queue内の記事同士で似たテーマの重複を検知する用途などが該当しうる。件数が数個〜十数個程度であれば、素直に全選択肢を渡す従来方式のほうがシンプルで確実なことが多く、無理に置き換える必要はない。
試すとしたら、まず自分の分類対象のうち一部だけを対象に、①架空分類を生成させるプロンプトを書き、②既存のPythonでMiniLM等の軽量埋め込みモデルを動かして正規分類側をベクトル化し、③コサイン類似度でマッチさせる、という最小構成をローカルで組んでみるところから始められる。正解率が実用に足るかどうかは、結局は自分のデータで確認するしかない——Turnbull氏の記事も精度の定量値までは示していないため、そこは自分で計測して埋めるしかない部分になる。
モデルの選び方そのものについてはOpus/Sonnet/Haikuの使い分けでトークン消費を最適化する話とも接続する。この技法は「軽いモデルで済ませられる仕事を増やす」設計の一種として読める。