Dance with Agents

AttentionとTransformer——LLMの中で実際に起きている計算

ミディアム専門用語を名前のまま使います。初出で解説を添えるので、順に読めば追えます深掘り

言葉が数字になるまでの最後で、Attentionが「周囲の情報を混ぜ込む」と書きました。なぜLLMは、こんなに何でもできるのかでも、Attentionは「どの語をどれだけ見るべきかを学習する仕組み」と一段落で流しています。

この記事は、その「見る」と「混ぜ込む」が実際にはどういう計算なのかを解説するものです。

数式は使いません。代わりに、1つの短い文を最後まで持ち歩きます。


Attentionが解こうとしている問題

この2文を見てください。

田中さんは鈴木さんに本を渡した。彼はそれを読んだ。

「彼」は誰で、「それ」は何か。読めば一瞬で分かります。彼は鈴木さん、それは本です。

ところが、この判断は文字を眺めているだけでは絶対に導けません。

  • 「彼」という2文字自体には、鈴木さんを指す情報が1ミリも含まれていない
  • 「それ」も同じ。それ単独では何も指していない
  • 正解にたどり着くには、1つ前の文にある離れた語を参照するしかない

しかも「渡した」の意味まで関わっています。渡したのは田中さんで、受け取ったのは鈴木さん。だから読んだのは鈴木さんのほう。語と語の関係を、距離を無視して結びつける必要があります。

これがAttentionの仕事です。一言でいえば、「今の語を理解するために、他のどの語を見ればいいか」を決めること。

昔のやり方は、なぜ行き詰まったのか

Transformer以前の主流は、文章を前から順に1語ずつ読んで、読んだ内容を記憶として持ち越していくという方式でした。人間の読み方に近い発想です。

これには2つの弱点がありました。

  1. 遠いものほど薄れる。 記憶を持ち越すたびに情報が混ざり合うので、20語前の「鈴木さん」は、直前の語に押されて曖昧になっていきます
  2. 並列に計算できない。 3語目を処理するには2語目の処理が終わっていないといけない。順番に待つしかないので、計算機を何台並べても速くなりません

応用編で「Transformerが並列計算を可能にし、それが大規模を可能にした」と書いたのは、この2番目を外した、という意味です。

Attentionの正体:クエリ・キー・バリュー

2017年の論文Attention Is All You Needに、こう定義されています。

“An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors.”

(注意関数とは、1つのクエリと、キーと値の組の集合を、出力へ対応づけるものとして説明できる。クエリ・キー・値・出力は、すべてベクトルである)

登場人物は3つです。Query(クエリ)、Key(キー)、Value(バリュー)。日本語にすると問い合わせ・鍵・値ですが、この3つには定着した訳語が無く、他の解説でも英語のまま書かれています。この記事でもクエリ・キー・バリューと呼びます。

図書館で考えると分かりやすくなります。

役割 図書館でいうと 文の中でいうと
Query 窓口に提出する「◯◯について知りたい」というメモ 「彼」が発する「人物を探しています」という要求
Key 棚に並ぶ背表紙。「私はこういう本です」の札 各語が掲げる「私はこういう語です」の看板
Value 本の中身そのもの 実際に混ぜ込まれる情報

やることは、メモと背表紙を照合して、合致度の高い本ほど中身を多く取り込む、それだけです。

ここで押さえておきたいのが、Q・K・Vは3つとも、同じ1つのベクトルから作られるということ。別々のどこかから持ってくるのではありません。各語のベクトルに、それぞれ違う変換をかけて3種類に化けさせます。

同じ人が、「何を探しているか」「自分は何者か」「自分は何を差し出せるか」の3枚の名刺を持っている、という感じです。

実際の手順を、さっきの文で

「彼」というひとつの語が、どう変化するかを追ってみます。

  1. 文中の全部の語について、Q・K・Vの3つを作る
  2. 「彼」のQと、全部の語のKを突き合わせて、それぞれ点数を計算する
  3. 点数を、合計が1になるように割合へ直す
  4. その割合で、各語のVを混ぜて足し合わせる
  5. こうして得られたものが、「彼」の新しいベクトル

3番目の「割合」は、たとえばこんな形になります。

              田中さんは   鈴木さんに   本を   渡した
「彼」の配分      0.18       0.61      0.05    0.11

(分かりやすさのための例で、実際のモデルの値ではありません)

「鈴木さん」に6割配分されれば、「彼」のベクトルには鈴木さんの情報が濃く混ざります。 得られた新しいベクトルは、もはやただの代名詞ではありません。「鈴木さんを指している代名詞」という、より具体的な何かになっています。

同じことが「それ」にも起きて、そちらは「本」に配分が寄ります。

点数は、どうやって付くのか

QとKの「近さ」を測ります。 内積という計算で、2つのベクトルの向きが揃っているほど大きな値になる、というものです。

ここが言葉が数字になるまでと地続きになるところです。その記事の主題は「意味の近さは、ベクトルの近さで表せる」でした。Attentionも、まったく同じ道具を使っています。

違うのは、比べている相手です。語そのものを比べているのではなく、「探している内容(Q)」と「差し出せる内容(K)」を比べている。

学習が進むと、「彼」のQは「人物を指す語を探している」という向きを持つようになり、「鈴木さん」のKは「私は人物です」という向きを持つようになります。だから内積が大きくなり、点数が高くなる。

この向きも、誰かが設計したものではありません。 ベクトルの記事とまったく同じ構図で、次の語を当てる訓練を繰り返した副産物として、勝手にそうなります。

視点は1つではない

1つの見方だけでは足りません。「彼」を理解するには人物を探す必要がありますが、同時に「文全体の主語は何か」も知りたい。要求が1つとは限らないのです。

そこで、同じ文を複数の組で同時に見ます。 論文Attention Is All You Needにはこうあります。

“In this work we employ h = 8 parallel attention layers, or heads.”

(本研究では、8組の並列な注意層、すなわちヘッドを用いる)

8組のQ・K・Vが、それぞれ別の関係を拾いにいきます。あるヘッドは直前の語との繋がりを、別のヘッドは主語と述語の対応を、また別のヘッドは代名詞とその指す先を。

ただし、「あなたは文法担当」と役割を割り振ったわけではありません。 ここもベクトルの記事と同じで、分業は勝手に生まれます。

実際に見つかっている役割:誘導ヘッド

抽象的な話になってきたので、具体例を1つ。

AnthropicのIn-context Learning and Induction Headsで報告されている誘導ヘッド(induction head)は、こういう単純な仕事をしています。

[A][B] … [A] → [B]

「さっきAの次にBが来た。いままたAが現れた。ならBだろう。」

同論文は、このヘッドを2つの性質で定義しています。

“The head attends back to previous tokens that were followed by the current and/or recent tokens.”

(そのヘッドは、現在の語や直近の語が後に続いていた、過去の語のほうへ注意を向ける)

“The head’s output increases the logit corresponding to the attended-to token.”

(そのヘッドの出力は、注意を向けた語に対応する予測値を押し上げる)

つまり「探して、まねる」だけです。ところが同論文は、この単純な仕組みが文脈内学習の大半を担っていると主張しています。訓練の途中で誘導ヘッドが形成される瞬間があり、そのタイミングで文脈内学習の能力が急に伸びる「相転移」が観察された、というのが根拠のひとつです。

ここは実務に直結します。応用編で「例を2つ見せれば3つ目からその形式で返ってくる」と書きました。その正体がこれです。

だから、エージェントに何かを頼むとき、例を見せるなら形をそろえて繰り返すほうが伝わります。 説明を長く書くより、同じ型の例を2〜3並べたほうが伝わることがあるのは、この仕組みが働いているからです。

順番の情報は、どこから来るのか

ここが個人的にいちばん面白いところです。

全部の語を一度に見るということは、順番が分からないということでもあります。

  • 犬が猫を追いかけた
  • 猫が犬を追いかけた

使われている語はまったく同じです。Attentionだけでは、この2つを区別できません。前から順に読む方式なら、順番は自動的に分かっていました。並列にした代償として、順番が失われたのです。

こちらは誘導ヘッドの論文ではなく、Attention Is All You Needのほうがそこを明記しています。

“Since our model contains no recurrence and no convolution, in order for the model to make use of the order of the sequence, we must inject some information about the relative or absolute position of the tokens in the sequence.”

(我々のモデルには再帰も畳み込みも無いため、系列の順序を利用するには、語の相対的あるいは絶対的な位置の情報を注入しなければならない)

やり方は素朴で、語のベクトルに「何番目にいるか」を表す数字を足してから流し込みます。これを位置エンコーディングと呼びます。

「犬(1番目)」と「犬(3番目)」が、少しだけ違うベクトルになる。それだけで、語順の違いが計算に乗ります。

一度では終わらない

ここまでの話は、1層分です。実際は何層も積み重ねます。

“The encoder is composed of a stack of N = 6 identical layers.”

(エンコーダは、同一の層6つを積み重ねて構成される)

エンコーダは、この論文が扱っていた翻訳の仕組みで、入力された文を読み取る側のことです。書き出す側はデコーダと呼ばれ、2つで1組でした。いまのLLMの多くは書き出す側だけを使っていますが、層を積み重ねる形はそのまま受け継いでいます。

論文のオリジナルは6層で、今のモデルはもっと深くなっています。1層目の出力が2層目の入力になり、それが繰り返される。通るたびに、各語のベクトルが少しずつ書き換わっていきます。

そして層の深さによって、見ているものが変わることが知られています。浅い層は語の並びや文法的な係り受けのような表層に近い関係を、深い層はより意味的・抽象的な関係を扱う傾向がある、というものです。

言葉が数字になるまでの「はし」の例でいうと、こうなります。

入力     「川に はし が架かっている」の"はし"
  ↓ 1層目   周りの語をうっすら取り込む
  ↓ 2層目   「川」「架かる」の情報が濃くなる
  ↓ …
  ↓ 深い層   ほぼ「橋」を指すベクトルになっている

一発で決まるのではなく、何十回も塗り重ねるイメージです。

先を見せない仕掛け

文章を生成するとき、まだ書いていない先の語を見てはいけません。 当たり前に思えますが、訓練のときは答えの文章が丸ごと手元にあるので、意図的に隠してやる必要があります。

“We also modify the self-attention sub-layer in the decoder stack to prevent positions from attending to subsequent positions.”

(デコーダ側の自己注意層には、後続の位置へ注意を向けられないようにする変更も加えている)

デコーダは、さきほどの書き出す側です。文章を生成するのはこちらなので、先を隠す仕掛けもこちらに入っています。

各語から見て、自分より後ろにある語への点数を強制的にゼロにする、という処理です。

これは制約に見えて、実は効率のための仕掛けでもあります。 先を隠しさえすれば、「1語目までを見て2語目を予測」「2語目までを見て3語目を予測」……を、全部同時に計算できます。 カンニングを封じたうえで一斉テストをやっているようなものです。

「次の語を当てる」という訓練が現実的な時間で回るのは、これがあるからです。

なぜ、長い会話は高くつくのか

ここまで分かると、日々の実感に説明が付きます。

Attentionは、全部の語が全部の語を見ます。 語がn個あれば、照合すべき組み合わせはn×n個。

文脈の長さ 照合の回数
1,000語 100万
2,000語 400万(2倍の長さで4倍)
10,000語 1億(10倍の長さで100倍)

長さに比例するのではなく、その2乗で増えていく部分があるということです。

「会話が長くなるほど重く、高くつく」の一因はここにあります。 前のやり取りは消えてなくなるわけではなく、毎回すべて読み直されています。 しかも読み直すたびに総当たりです。

だから、トークン節約のためのモデル選びで書いたようなモデルの使い分けに加えて、話題が変わったら会話を切り直すほうが安く済みます。前の話題を引きずったまま続けると、関係ない文脈まで毎回総当たりの対象になるからです。

なお、生成が進むにつれて体感が速くなることがあります。一度計算したKとVを保存しておいて使い回すという工夫が広く使われているためで、2語目以降は「新しく増えた1語ぶん」だけ計算すれば済むようになっています。

まとめ

  • Attentionが解いているのは「今の語を理解するために、他のどの語を見るか」という問題。「彼」が誰を指すか、といった離れた語との結びつきを扱う
  • 登場人物は3つ。Query(何を探しているか)・Key(自分は何者か)・Value(何を差し出すか)。3つとも同じベクトルから作られる、同じ人の3枚の名刺
  • 点数はQとKの近さで決まる。ベクトルの記事と同じ「近さ」の道具を、別の相手に使っている
  • 視点は1つではなく8組(論文の場合)が同時に見る。役割分担は設計されたものではなく、勝手に生まれる
  • 具体的に見つかっている役割の例が誘導ヘッド。「さっきAの次にBが来た。またAが現れた。ならBだろう」。これが文脈内学習の大半を担っているとされる。プロンプトに例を並べると精度が上がるのは、このため
  • 全部を一度に見る代償として順番が失われるので、位置の情報を別途ベクトルに足す
  • 1層では終わらず、何十層も塗り重ねる。浅い層は表層的な関係、深い層は意味的な関係を扱う傾向がある
  • 生成時に先を見ないようマスクをかける。制約であると同時に、訓練を並列化するための仕掛けでもある
  • 全語×全語の総当たりなので、文脈が2倍になると計算は4倍。長い会話が高くつく一因はここ

Attentionは「注意」と訳されますが、実際にやっているのはクエリとキーの照合、そしてバリューの混ぜ合わせです。人間の注意力と同じものが宿っているわけではありません。

とはいえ、単純な照合を8組ぶん、何十層も繰り返した先に、代名詞の解決や文脈内学習が生まれるのは事実です。そこが面白いところだと思います。

この先、モデルの中に実際にどんな概念が含まれているのかという話はなぜLLMは、こんなに何でもできるのかに、そのベクトルを取り出して自分の資料検索に使う話は自分の資料で、専門特化のエージェントを作るにあります。

LLMの仕組みの記事

LLMの仕組みのページへ →