イチゴ(Strawberry)にはRが何個?あなたのAIには、それが難しい理由がわからない
「strawberry」という単語には、Rがいくつ入っているでしょうか?
ChatGPTに聞けば、かなりの確率で「2」と自信満々に返ってくるはずです。そこでこちらが押し返すと、モデルは謝って「3」と言い、さらに追い込んでいくと、返答が「2」に戻ることもあります。
これは、ある意味でとてもおかしい話です。働くコードを12の言語で書けて、法的なブリーフを要約できて、6歳の子に一般相対論を歩いて説明できるようなシステムが、しかし、あなたの姪が綴れる果物の中のRを数えることだけは確実にできないのです。
こうなってしまう理由は、多くの人が想像するものではありません。モデルが「数えるのが下手」なのではありません。私たちと同じように単語を見ているわけでもありません。そして、なぜそうなのかを理解すると、ほかのLLMの奇妙な挙動の数々が、きちんと説明できるようになります。
LLMが“読む”ことについて、誰も教えてくれないこと
あなたが「strawberry」という単語を読むとき、脳はそれを文字の並びとして処理します。S、t、r、a、w、b、e、r、r、y。全10文字でRは3つ。子どもでもできる、簡単なことです。
しかしモデルは、まったくそのようには見ません。モデルは文字を見ていません。見ているのはトークンです。
トークンとは、モデルが単一の単位として扱うテキストのかたまりです。トークンが単語まるごとになることもあります。単語の一部になることもあります。2文字のこともあれば、1文字だけのこともあります。すべてのモデルには、これらのトークンの固定語彙があり、通常それは3万〜20万程度です。そして、その語彙こそが、モデルが知っている“組み立ての材料”すべての世界です。
GPT-4に「strawberry」と入力すると、トークナイザは実際にこう分割します:
"str" + "aw" + "berry"
3つのトークンです。10個の文字ではありません。トークンIDは496、675、15717です。
ここが勝負どころです。モデルは、個々のRを見ることすらありません。個々のRは、モデルが“見る”ものではないからです。モデルは「str」「aw」「berry」の3つのかたまりに対して「それらの3つのチャンクの中にRは何個ある?」と考え、その答えを、学習中に拾ったパターンに基づいて推測するしかありません。うまく当たることもあります。たいていは当たりません。
あなたも自分で約30秒で確かめられます:
import tiktoken
enc = tiktoken.get_encoding("cl100k_base")
ids = enc.encode("strawberry")
print(ids) # [496, 675, 15717]
print([enc.decode([i]) for i in ids]) # ['str', 'aw', 'berry']
あるいは、OpenAIのトークナイザのページにその単語を貼り付けて、分割される様子を見ればいいだけです。
トークナイザはどうやって「トークン」を決めるのか?
最も一般的な方法は、Byte Pair Encoding(BPE)と呼ばれるものです。仕組みは、実際に見てみるとかなりエレガントです。
まず、インターネット上の何十億という単語のような、巨大なテキストの山を用意します。すべての文字は、最初はそれぞれ単独のトークンとして扱われます。次に、最も頻繁に隣り合って出現するトークンのペアを見つけ、それらを1つの新しいトークンにマージします。そしてそれを何千回も繰り返します。
初期のラウンドでは、BPEは「t」と「h」が頻繁に隣り合って出現することに気づきます。なぜなら「the」が、これまでに書かれたほぼあらゆる文に登場するからです。そこで「t」と「h」を「th」にマージします。少し後のラウンドで「th」と「e」をマージして「the」にします。やがて、よく使われる単語が1つのトークンになります。あまり使われない単語は断片のまま残ります。極めて珍しい単語は、3つか4つのトークンになることもあります。
「berry」は学習データ中で至るところに出てきます。Strawberry、blueberry、raspberry、blackberry、cranberryです。だから「berry」は早い段階で、ひとつのトークンとしての席を獲得します。一方、「strawberry」の残りの部分で事態が奇妙になります。「straw」が単独のトークンになることを期待するかもしれませんが、それは違います。学習の過程では「str」と「aw」が選ばれています。おそらく「str」は、street、strange、story、string、strong のような、他のありふれた単語に大量に登場するためで、「straw」より先に独立したトークンとして昇格したのでしょう。その結果、「strawberry」は、最初の直感では思いつかない切り方で3つに分断されます。
これは、とても賢い圧縮の仕組みです。未知の単語であっても、既知の部品に分解することで扱えるようにします。代償は、モデルが基となる文字に直接アクセスできなくなることです。
文字数の数え間違いより、ずっと多くの場面を壊す理由
トークン化を理解すると、ほかのLLMの癖も次々と腑に落ちてきます。
数学。 数値は意外な形でトークン化されます。「1234」は1つのトークンになるかもしれません。「12345」は3つのトークンになるかもしれません。モデルは、数学的な意味がきれいに対応していない“かたまり”に対して計算を行っているためです。だから文章題は押し切れることがある一方、基本的な多桁の掛け算では取りこぼすことがあります。
言語によるコストの差。 OpenAIはトークン単位で課金します。英語のテキストは平均すると、1語あたりだいたい1トークンです。しかし日本語、タイ語、ビルマ語のような言語では、同じ分量の内容でもトークン数が3〜4倍になることがあります。トークナイザの語彙が作られる際、その文字体系が過小評価されていたからです。東京でのAPI請求額は、テキサスでの請求額とまったく別物になります。
意味のないタイプミス挙動。 ときには単語を誤って綴ってもモデルはそのまま素通りします。別のときには、1文字増えるだけで完全に道を踏み外します。これは、あるタイプミスはそれでも認識できるかたまりとしてトークン化される一方で、別のタイプミスは未知の断片の山に砕けてしまい、モデル側の関連づけが弱くなるからです。
グリッチ(不具合)トークン問題。 数年前、研究者たちは、Redditからスクレイピングされたデータには出てくるのに、通常の文章にはほとんど登場しないような、ある種の珍しいトークンがGPT-3に奇妙な挙動を引き起こすことを見つけました。そのトークンは語彙には存在していましたが、モデルには事実上それに対する学習シグナルがありませんでした。だからトークン「SolidGoldMagikarp」を含むプロンプトでは、判じ物のような文字列(がらくた)、拒否、あるいは妙に話題のずれた寄り道が生成されます。人々は今でも、ときどき新しいグリッチトークンを見つけています。
本当に「strawberry問題」は直せるのか?
ある程度は、でも実際には完全ではありません。
いちばん簡単な回避策は、まずモデルに単語を綴らせることです。「strawberryを文字ごとに綴って、それからRの数を数えてください」といった感じです。これは、答えにたどり着く途中でモデルに1文字につき1トークンずつ出力させることを強制できるためにうまくいきます。そして文字が出力に現れたら、モデルはそれを数えられます。これは、人に頭の中でなく紙の上で長除法をやらせるようなものです。うまくいく小技ですが、それを聞くと分かっていなければ気づけませんでした。
返却形式: {"translated": "翻訳されたHTML"}新しいモデルの中には、文字レベルの認識をあらかじめ組み込んで訓練されているものや、トークンレベルと文字レベルの処理を混ぜ合わせたハイブリッドなアプローチを取るものもあります。しかし純粋な文字レベルのモデルは、動作が劇的に遅く、実行コストも高いため、誰もそれを大規模に提供していません。
別の方向からの進展もありました。OpenAIの推論モデル「o1」ファミリーは、社内では内輪で「Strawberry」と呼ばれており、たいてい正しい答えにたどり着けます。トークン化が変わったからではありません。モデルが、答える前に問題を一歩ずつ考え抜くように訓練されているからで、要するに、スペルアウト(文字ごとに書き出す)という回避策が、モデルの内部で自動的に起きているのです。このニックネームは、騒動全体に対するチームの内輪ジョークです。
正直な答えは、これは現代のLLMが作られる仕組みの根本的なクセだということです。来週パッチで直すようなバグではありません。むしろ、アーキテクチャの一種の特徴に近い。モデルは、きめ細かな文字の認識を手放す代わりに、大規模に効率よく言語を処理できる能力を得ていて、ほとんどの時間、その取引は大きな見返りになっています。「strawberry」の件は、その取引が漏れ出している場所にすぎません。
より大きな教訓
最も役に立つ持ち帰りは、「LLMはバカだ」ということではないと思います。モデルが動作しているのが、あなたが読んでいるときに使っているものとは根本的に異なる言語の表現だ、という点です。
あなたが文章を読むときは、文字が見えて、それが言葉になり、さらに意味になっていくのが分かります。モデルは、他のトークンと統計的な関係を持つトークンとして見ています。出力が流暢な英語として返ってくるという事実は、一種のマジックのようです。実際には、その下でシステムは、そもそも文字に分解したことがほとんどないテキストの塊を、ただ入れ替えているだけです。
そのギャップの中に、奇妙な挙動の大半が潜んでいます。幻覚、文字数に関する確信めいた間違い、文字列をきれいに反転できないこと、子どもが作ったように見える数学のミス。どれもモデルがバカだからではありません。モデルのアーキテクチャは、そのようなことをうまく扱えるように設計されていないのに、そういう作業をさせられているのです。
だから次に誰かが、ChatGPTがRの数を落としていることを笑いものにするときは、あなたは「なぜそうなるのか」を説明できる会場の人になれます。たぶん、彼らが聞いてきたときまで待ってください。
もしこれが面白いと思ったなら、コメントで教えてください。LLMの内部で起きている、奇妙で驚くべき出来事についてもっと書く予定です。次に何を掘り下げると役に立ちそうか、ぜひ聞かせてください。




