ジョイジョイジョイ

ジョイジョイジョイジョイジョイ

否定文を理解できないAIたち

BERT や GPT の登場により、テキストを扱うモデルは大きく発展しましたが、否定というごくありふれた操作を扱うのが依然難しいです。

本稿では、その理由と、部分的な解決策を紹介します。

目次

否定文を理解できないAIたち

BERT (tohoku-nlp/bert-base-japanese-v3) で
A =「私はお寿司が好きです。」
B =「私の好きな食べ物はお寿司です。」
のテキスト埋め込みのコサイン類似度を求めてみましょう。A と B は同じようなことを言っており、予想されるようにコサイン類似度は 0.9695 と高いです。

では、
A =「私はお寿司が好きです。」
C =「私はお寿司が好きではないです。」
という正反対のテキストのコサイン類似度はどうでしょうか。なんとこれは 0.9762 と、先ほどよりもさらに高い値となります。正反対のことを言っているので、直観的には類似はしていませんし、検索や分類などの多くのアプリケーションでもこれらは似ていないと判定してほしいはずです。BERT はそのような類似度判定に適していません。

ChatGPT はどうでしょうか。

「日本の首都は〇〇ではない。」 の〇〇に当てはまる単語を出力させてみます。「日本の首都は京都ではない。」「日本の首都は岡山ではない。」など、何でもよいです。

自信満々に間違える ChatGPT 4o

しかし、ChatGPT は「日本の首都は東京ではない」とまっすぐ間違ったことを言ってきます。否定を無視して肯定文の「日本の首都は〇〇である。」と同一視してしまっているような振る舞いです。(注:何度か試すと、京都と出力することもあります。また、Chain-of-Thought を許せば、正解率は上がります。しかし、依然として、肯定文と比べると否定文を正しく扱うことにはるかに苦労します。)

数秒考えたのちやはり間違える ChatGPT o1

思慮深く正確とされている ChatGPT o1 も、数秒考えたのちやはり間違えました。

英語でも間違える ChatGPT

英語で違う訊き方をしてもやはり間違えます。

否定文を理解できずに困ること

文書検索やチャットボットで否定文を含むユーザーの疑問に応えられないことがよくあります。

「アラートが出てこない」と言っているのに、アラートが出た場合の対処法を出してしまったり、「メールを送ってほしくない」と言っているのに、メールを受け取りたい場合の案内を出してしまう、などです。

ほかにも、アンケート結果を分析するために、同じ意見の人をクラスタリングしようとしても、「このサービスが好きです」と言っている人と、「このサービスは好きではないです」と言っている人を同じ意見であるとまとめてしまうかもしれません。

なぜ否定文をうまく扱えないのか

単語埋め込み(トークン埋め込み)ベースの言語モデルは構造上、否定文を扱うのが苦手です。

言語モデルはアテンションや MLP などでこれまでの文脈を一本の埋め込みベクトル  z にまとめ、これを出力単語埋め込み層に入力し、softmax 関数  \frac{\exp(z^\top v_i)}{\sum_j \exp(z^\top v_j)} で単語を予測します。

「日本の首都は〇〇ではない。」の〇〇の正解は、東京以外のあらゆるものです。なので、この softmax の結果は東京だけ 0 をとり、京都・岡山・名古屋・札幌などに値を持つのが正解です(りんごやバナナなどにも値を持つべきかもしれません)。しかし、これを実現するためには、文脈ベクトル  z は  v_{\text{京都}} ã‚„  v_{\text{岡山}} ã‚„  v_{\text{バナナ}} とは近く、 v_{\text{東京}} とは遠くなければいけないわけですが、 v_{\text{京都}} と  v_{\text{東京}} は近くにあるため、 v_{\text{京都}} をはじめとするあらゆるものに値を立てて、 v_{\text{東京}} にだけ値を一切立てないというようなことは不可能です。どれだけ前段のアテンションや MLP などのエンコーダーが強力であっても、そもそもそのような  z が存在しないので適切な埋め込み  z が見つかるはずがありません。

文脈ベクトル(赤矢印)をどこに置いても、京都と岡山に近づけて東京とだけ離すことは不可能

では言語モデルはどうするかというと、「日本」と「首都」などの単語の出現を手掛かりにして、これらと共起しやすい「東京」と出力してしまいます。Wikipedia などのコーパスでは、「日本」「首都」と直前で言及があった場合には次の単語は「東京」である確率が極めて高く、このような単純な推論でかなり正解できてしまうので、そういう推論方法が訓練で身に付き、テスト時に訓練で見なかった少し意地悪な否定文が来た時にも同じ対応をしてしまうという訳です。

なぜたまに成功するのか

前述のように、何回か試すと京都と出力できることがあります。これは、京都から東京に遷都した関係で、「日本」「首都」と「京都」もある程度の共起があるのが一因です。もしかすると遷都に関する記述で「日本の首都は京都ではなくなった」などのように明示的に否定文の形での訓練文すらあるかもしれません。「日本の首都は〇〇ではない。」の〇〇としては京都もある程度は答えやすい。しかも、京都と答えるだけならば、 z ã‚’  v_{\text{京都}} の方向に限りなく大きくすれば、他の単語(東京を含む)の確率を 0 にして、京都だけを出力することも構造上可能です。ただし、これはあくまでショートカット的な対処であって、根本から否定文の問題に対処できている訳ではないことに注意してください。これは「日本の首都でないものは京都である」とある意味、肯定文的にこの問題を書き換えて解いており、これは京都以外の日本の首都でないもの(岡山や札幌やバナナ)を無視しているので、否定を論理的に正しく解けているわけではありません。予測分布も真の分布(東京だけ 0 でそれ以外のあらゆるものに値を持つ)と一致している訳ではなく、交差エントロピー誤差は最小化されません。

また、日本の首都は京都や奈良や大阪など分かりやすい「じゃないものの正解」があるのでこのようなショートカットで解けることがありますが、そのようなショートカットが無かったり、より難しかったりすることもあります。例えば、「インドネシアの首都は〇〇ではない。」とすると、日本の場合よりはるかに難しく、ジャカルタと誤答してしまう確率が高くなります。

やはり騙される ChatGPT

対処法

BERT などのモデルが否定文を苦手とすることは以前から指摘されてきました。What BERT is not: Lessons from a new suite of psycholinguistic diagnostics for language models [Ettinger TACL 2020] などが有名です。また、An Analysis of Natural Language Inference Benchmarks through the Lens of Negation [Hossain+ EMNLP 2020] や Understanding by Understanding Not: Modeling Negation in Language Models [Hosseini+ NAACL 2021] のように、対処する方法もいくつか提案されています。

根本的には、上述のように出力層において固定した単語埋め込みを用いていることが問題なので、これをコンテキストに依存するように改変することが必要ですが、この方法はコストが大きいため、わざわざ否定文を扱えるようにするためだけに採用されることはほとんどなく、対症療法的に解決する方法が主流です。

ファインチューニング

ファインチューニングできる場合にはファインチューニングが有効なことが多いです。

感情分析や商品のレビュー値予測などでは否定文の処理が極めて重要です。これらのタスクを教師なしで解くのは難しいです。BERT を使ってある程度解けるのはファインチューニングのおかげです。

これらのタスクでは、否定を厳密に扱う必要はありません。「好きでない」という記述があったとき、「好きでない」なら何か複雑な感情を持っているのだろうかなどと、否定を厳密に処理して他の可能性を律儀に考える必要はありません。これらのタスクでは「好きでない」ならば「嫌いだ」(少なくとも中立よりは嫌い寄りだ)と決めつけてしまっても差支えなく、ある意味ショートカット的に、これらのタスクを解くことができます。ショートカットといってもこれらのタスクにおいてはショートカットすることが正解に繋がる正義なので問題ありません。ファインチューニングによって、ショートカットしても問題ないことやショートカットする方法を学習して、性能を大きく上げることができます。

ただし、自然言語推論 (natural language inference) のように、論理を厳密に扱う必要があるタスクでは、ファインチューニングもある程度までは有効なものの、厳密な否定論理を扱う必要がある場合にはファインチューニングでもこの問題を完全には解決できないことに注意してください。

プロンプトの工夫

ChatGPT などのデコーダー型の言語モデルを用いる場合には、事前にプランニングさせるなど、Chain-of-Thought 的なプロンプトの工夫が有効です。冒頭の例では否定文の難しさを際立たせるために「その単語だけ出力して」という縛りを入れていましたが、余分な出力を挟んでから出力することを許せば、正解の確率はかなり上がります。

余分な出力を許せば騙されなかった ChatGPT

余分な出力を許せば、理論上は前述の softmax の問題は回避できます。前述の議論では一発で出力することを前提としていました。しかし出力するタイミングが任意であれば、各タイミングでの出力が京都だけ、岡山だけ、のような極端な分布になっていても、それらを重ね合わせた分布では東京以外のあらゆるものに値を持つようにすることが理論上は可能です。これはつまり、否定文を無理やり肯定文に直すことを無限回繰り返せば実質否定文と等価になる、ということです。

ただし、理論上は可能でも構成は複雑なのでこれを精度よく学習することは難しいです。たとえ正解を出せたとしても、分布としては正確になりません、これは、否定を直接扱っているのではなく、「日本の首都でないものは京都である」「日本の首都でないものは大阪である」「日本の首都でないものは猫である」のように、否定文を無理やり肯定文に直すことを繰り返しているだけだからです。しかも実際には無限の重ね合わせではなく有限の重ね合わせになるので、否定と等価にはなり得ず、分布として精度が悪くなります。このため、他にも無数に選択肢があるはずなのに、乱数を振り直しても同じ答えばかり出力されます。

テキスト埋め込みが欲しい場合にも問題が起こります。典型的な文書検索に加えて、チャットボットにおける RAG の retrieval 部分など、テキスト埋め込みが必要な場面は多いです。世の中の大半の埋め込み手法は、そのテキストを入力した時点のモデル状態をテキスト埋め込みとして用います。しかし、これは単一の状態から一発で答えを出すということであり、これは「その単語だけ出力して」という縛りがある状態で言語モデルに解かせる状況と同じです。これではやはり上述の softmax と同様の問題が生じるため、プロンプトの工夫で問題を解決することが難しいです。

懲りずに間違える ChatGPT

否定文を意識した訓練

否定文を扱えるような訓練を明示的に施す手法が提案されています。BERTNOT [Hosseini+ NAACL 2021] という手法は、"The capital of Japan is Tokyo" というテキストが訓練コーパスにあれば、ここから "The capital of Japan is not ___" という否定文の予測問題を人工的に作り、___ に対するモデルの出力として Tokyo(もともとの肯定文でそこにあった単語)の確率が低くなるように訓練します。

アーキテクチャは同一なので、softmax の構造上の問題は依然残りますが、通常の訓練と比べるとより良い近似が可能です。例えば、否定の後の単語の予測では文脈ベクトル  z をゼロにしてしまえば、softmax の出力は一様分布になります。候補の単語が 10000 個あれば、「東京」が選ばれる確率は 1/10000 だけで、「東京」以外の正解が出力される確率は 9999/10000 であり、ほとんど正解となります。根本的に否定が解けているわけではなく、これもショートカットといえばショートカットですが、分布としては「京都」を決め打ちするよりは真の否定に迫ったより良い近似といえます。

実験では質問応答や自然言語推論において、BERTNOT は通常の BERT と比べて否定文が含む場合に高い性能を達成しています。

文書数を増やす

文書検索では文書数を増やすことが有効です。

冒頭の例

A =「私はお寿司が好きです。」
B =「私の好きな食べ物はお寿司です。」
C =「私はお寿司が好きではないです。」

で A が検索クエリだとして、A と似たテキストをデータベースから検索したいとしましょう。B と C しか候補が無いと、似た意見の B ではなく、正反対の意見である C が得られてしまいます。

ただし、BERT も A と C を同一視しているわけではなく、コサイン類似度は 0.9762 (< 1)です。コサイン類似度が 0.9763 から 1.0000 の間の正解テキストがデータベース中にあれば正解できます。

A と A のコサイン類似度は 1 なので、検索クエリと全く同じテキストがデータベース中にあればそれを正しく取ってこられます。また、
A =「私はお寿司が好きです。」
D =「私はお寿司が大好きです。」
のコサイン類似度は 0.9974 なので、D も正しく取ってこられます。

データベースがぎっしり詰まっていて、コサイン類似度が 0.9763 から 1.0000 のテキストも大量にある場合には、否定文を誤って取ってしまう確率を下げることができます。

当然といえば当然の解決策ではありますが、シンプルなので汎用性は高い解決策です。

ファインチューニングで埋め込みモデルや類似度関数を改善するのが基本的な手法で、第一に考えるべきです。しかし、埋め込みモデルを API で利用している場合やデータが足りない場合、またファインチューニングが可能でも否定文のように良い類似度を定義するのが一筋縄ではいかない場合もあります。そのときには理想的な類似度関数を追い求めるよりも、文書の側を工夫するアプローチに回った方がうまくいくことは覚えておくと良いことがあると思います。

クエリとキーを拡張する

文書検索ではクエリとキーを拡張・水増しすることが有効です。これは否定文についても同様です。

例えば、ChatGPT にテキストの続きを生成させてみましょう。

ChatGPT にテキストを拡張させる

以下のような拡張テキストが得られました。

A' = 「私はお寿司が好きで、特にマグロやサーモンの握りは外せません。新鮮な魚の旨みと酢飯の絶妙なバランスがたまらなく、食べるたびに幸せな気分になります。時々、友人や家族と回転寿司に行くこともありますが、少し特別な日にはカウンター席のある寿司屋で職人さんが握る本格的なお寿司を楽しみます。季節によって変わるネタを味わうのも楽しみの一つで、春は鯛、秋はサンマといった旬の味覚を堪能するのが好きです。お寿司はただの食事ではなく、特別な時間を演出してくれる料理だと感じています。」
B' = 「私の好きな食べ物はお寿司です。お寿司はシンプルな見た目ながら、使われる食材や職人の技術によって無限のバリエーションがあります。特に、マグロやサーモンの握りは見た目も美しく、口に入れるととろけるような食感がたまりません。回転寿司に行くのも楽しいですが、時には少し奮発してカウンターで食べるお寿司も格別です。職人さんが目の前で握ってくれるお寿司は、ネタの鮮度やシャリの温かさが感じられて、まるで特別なご馳走のようです。最近は、海外でもお寿司が人気で、創作寿司やロール寿司も話題になっていますが、やっぱりシンプルな日本のお寿司が一番だと感じます。」
C' = 「私はお寿司が好きではないですが、それでも友人や家族と一緒に回転寿司や寿司屋に行くことはよくあります。なぜなら、食べ物だけが外食の楽しみではないからです。みんなが笑顔でおしゃべりしながら食事をしている時間や雰囲気が好きで、それだけでも十分に楽しいのです。お寿司の代わりに、唐揚げや茶碗蒸し、うどんなどのサイドメニューを頼むことが多いです。最近では寿司屋にもこういった選択肢が増えたので、私のようにお寿司が苦手な人でも安心して楽しめるようになりました。それでも、「お寿司が苦手なんて珍しいね」と言われることがあります。確かにお寿司は日本人のソウルフードのような存在なので、嫌いというと驚かれるのかもしれません。でも、好き嫌いは人それぞれですし、自分に合った美味しいものを見つけるのも食の楽しみのひとつだと思っています。」

この拡張テキストのコサイン類似度を BERT (tohoku-nlp/bert-base-japanese-v3) で計算すると、A'(私はお寿司が好きです。)と B'(私の好きな食べ物はお寿司です。)のコサイン類似度は 0.9494、A'(私はお寿司が好きです。)と C'(私はお寿司が好きではないです。)のコサイン類似度は 0.9316 であり、同じ意見が近いことが正しく認識できています。

この解法も、モデルが扱うのが難しい否定文を無理やり肯定文に直して解いているとみることができます。上記の C' では「お寿司が好きではない」を拡張することで「唐揚げや茶碗蒸し、うどんなどのサイドメニューを頼む」という肯定文に繋がっています。これにより肯定文どうしの比較をすることで済んでいます。正面から否定文を対処するのではなく、回り道をすることで否定文を直接扱うのを避け、問題の発生を抑制していると言えます。

しかも、ここまで拡張してしまえば、BERT を使うまでもなく、単純な bag-of-words ベクトルでも、A' と B' のコサイン類似度は 0.8424、A' と C' のコサイン類似度は 0.8223 となり、同じ意見を正しく取得することができてしまいます。

テキストがある程度長くなると、同じ意見のテキストでは単語被りが増えてきます。テキストが長くなるにつれてパラフレーズをしたりして、どんどんと単語被りが増えていきます。短いテキストだと同じ意見でもたまたま違う表現をしてしまったり、異なる意見でもたまたま表現が被ったりすることがありますが、テキストが長くなるにつれてそういった偶然によるランキングのブレが無くなっていきます。このため、ある程度テキストが長くなると、単純な bag-of-words ベクトルでも十分になっていきます。

これも「理想的な類似度関数を追い求めるよりも、文書の側を工夫するアプローチに回った方がうまくいく」の例の一つです。

RAG ではこれと同様の考えから、文脈付き検索 (Contextual Retrieval) が有効です。

典型的な RAG の実装では、文書はパッセージやチャンクと呼ばれる数百文字程度の小さな単位に分割され、チャンク単位で検索を行います。このチャンクは短いので、検索が難しく、否定がうまく処理できなかったりします。

このときにもテキスト拡張が有効です。Anthropic の提案する文脈付き検索では、以下のようなプロンプトでチャンクを拡張します。

<document> 
{{文書全体}} 
</document> 
以下は、文書全体に配置したいチャンクです。 
<chunk> 
{{チャンクテキスト}} 
</chunk> 
このチャンクの検索結果の改善を目的として、ドキュメント全体にこのチャンクを配置するための簡潔なコンテクストを提示してください。簡潔なコンテクストのみを回答し、それ以外は回答しないでください。

こうすることで、文書全体の文脈に沿った内容でパッセージを拡張でき、チャンクそのものに付随していない情報を検索で用いることができるようになります。

検索データベースにはオリジナルのチャンクと拡張した部分を連結して入れておき、検索し終わったあとの生成段階ではオリジナルの部分だけを使います。

また、このプロンプトは前半の

<document> 
{{文書全体}} 
</document> 
以下は、文書全体に配置したいチャンクです。 
<chunk> 

の部分が全てのチャンクで共通です。なので、この部分に相当するデコーダーの計算は前計算しておき、残り僅かのチャンクに相当する部分だけを都度計算すればよくなります。ChatGPT や Claude はこのようなプロンプトのキャッシュに対応しており、特に Claude はこのキャッシュによる費用の削減が非常に大きいので、この方法を使うことで安価にチャンクを拡張することができます。

Anthropic の報告では、この方法により文書取得ミスが 5.0% から 2.9% にまで減少したとされています。(注:この報告自体は否定文を扱うものではありません。一般に、このような処理を行うと検索の精度が上がるという報告です。ただし、この改善は完璧ではないにしろ、否定文にも同程度には有効であると考えられます。)

チャンクの取得ミスが文脈拡張により大きく低減した

おわりに

やはり、否定文の扱いはまだ根本からは解決していないという印象です。

根本的には、本文中でも述べたように、出力層の単語埋め込みをコンテキストに依存するように改変することが必要です。ただしこれをしたからと言ってただちに解決するわけではなく、訓練コーパス中に否定文の割合が少ないうえ、「日本」「首都」と「東京」の共起が強いので、この共起の壁を上回るほどの教師信号が得られず、結局ショートカットが学習されてしまう可能性もあります。

本文中ではコンテキストに依存する単語埋め込みを使うのはコストが大きいの否定文を扱えるようにするためだけにわざわざ採用されることは少ないと書きましたが、Big Tech 持ち前の計算リソースとデータリソースでそんな壁は軽々と超えてくるかもしれませんし、直接的に解決しなくても、対症療法的な手法を Big Tech 持ち前の物量で補うことで、存在を感じられないくらいの問題になるかもしれません。

いずれにしても、根本的な問題は基盤モデルのアーキテクチャと訓練方法という深いところにあるので、ユーザー側からは根本的には解決できません。なので、まずはこのような問題があることを認識し、その上でこの問題にぶち当たったときには上で述べた対症療法でやりくりしていくことが重要だと思います。

否定文の扱いについては @awakia さんとディスカッションするうちに考えが深まりました。ここに御礼申し上げます。

皆さんもこの問題についてのご意見やご感想などあれば SNS 等でコメントいただければ嬉しいです。ぜひ皆さんもこの問題について考えてみてくださいね。

連絡先: @joisino_ / https://joisino.net

▼拙著発売中