第3章 トークン埋め込み:単語を意味のベクトルに変える

こんにちは。ゆうせいです。

第2章では、文章がトークンに分割され、それぞれに番号(トークンID)が割り当てられることを確認しました。しかし、この番号には、意味がまったく含まれていません。IDが3797の単語とIDが3798の単語は、辞書に隣り合って並んでいるだけであり、意味が近いわけではありません。番号のままでは、モデルは「猫」と「犬」が似た概念であることを知りようがないのです。第3章では、この意味を持たない番号を、意味を持つ数値のベクトルに変換する処理、すなわちトークン埋め込みを扱います。Transformer Explainerで、トークンの箱をクリックすると現れる、縦に長い数値の列が、この章の主役です。

トークン埋め込みとは何か

トークン埋め込み(Token Embedding)とは、トークンIDを、意味を表す数値のベクトルに変換する処理です。

Transformer Explainerの解説では、トークンが、単語の意味的な意味を捉えた埋め込みと呼ばれる数値ベクトルに変換されると説明されています。

GPT-2(small)では、1つのトークンが、768個の数値からなるベクトルに変換されます。この768という数字を、埋め込み次元、または隠れ層の次元と呼びます。

たとえば、「cat」というトークンは、次のような768個の数値の並びになります。

[0.23, -0.51, 0.08, \ldots, -0.17]

この768個の数値それぞれが何を意味しているかを、人間が解釈することはできません。しかし、これらの数値の組み合わせ全体が、「cat」という単語の意味を表現している、という仕組みになっています。

なぜ番号のままではいけないのか

トークンIDをそのまま使うと、何が問題なのでしょうか。三つの問題があります。

問題1:番号の大小に意味がない

トークンIDは、辞書における登録順に振られた番号にすぎません。

「猫」のIDが100、「犬」のIDが5000だったとします。この数値の差である4900という値には、何の意味もありません。「猫と犬は4900だけ離れた概念である」ということにはならないのです。

一方で、モデルの計算は、数値の大小や差に基づいて行われます。意味のない大小関係を持つ数値を入力すると、モデルは誤った関係を学習してしまいます。

問題2:1つの数値では情報量が足りない

単語の意味は、一次元の数直線上に並べられるほど単純ではありません。

「犬」という単語には、動物である、四本足である、ペットとして飼われる、忠実な性格の象徴として使われる、といった複数の側面があります。これらを1つの数値で表現することは不可能です。

768次元のベクトルにすることで、多くの側面を同時に表現できるようになります。

問題3:似た単語を似た値にできない

理想的には、「猫」と「犬」のように意味が近い単語は、数値としても近い値になってほしいものです。

トークンIDでは、この性質をまったく満たせません。埋め込みベクトルであれば、学習の過程で、意味の近い単語が、ベクトル空間の中で近い位置に配置されるようになります。

埋め込みはどう実現されているか:埋め込み行列

仕組みは巨大な変換表

埋め込みの実装は、非常に単純です。巨大な変換表を用意しておき、トークンIDを使って、その表から該当する行を引いてくるだけです。

この変換表を、埋め込み行列(Embedding Matrix)と呼びます。

GPT-2(small)の埋め込み行列は、次の大きさを持ちます。

行数:50257(語彙の数)

列数:768(埋め込み次元)

つまり、50257行768列の行列です。各行が、1つのトークンに対応するベクトルになっています。

トークンIDが3797であれば、この行列の3797行目にある768個の数値を取り出す。これが埋め込みの処理のすべてです。

パラメータ数を計算してみる

この埋め込み行列に含まれる数値の個数は、次の通りです。

50257 \times 768 = 38597376

約3860万個です。GPT-2(small)の全パラメータ数が1億2400万個ですから、埋め込み行列だけで、モデル全体の約31パーセントを占めていることになります。

言語モデルにおいて、単語の意味を表現する部分が、いかに大きな比重を占めているかが分かります。

実装上は行列の掛け算として書かれる

概念としては「表から行を引く」処理ですが、実装上は、行列の掛け算として書かれることがあります。

トークンIDを、該当する位置だけが1で他がすべて0のベクトル(One-Hotベクトル)に変換し、それを埋め込み行列に掛けるという方法です。

埋め込みベクトル = OneHotベクトル \times 埋め込み行列

数学的には、この掛け算の結果は、該当する行を取り出すことと等価になります。ただし、実際の実装では、計算の無駄を避けるため、掛け算をせずに、直接インデックスで行を参照する方式が使われます。

PyTorchのnn.Embeddingは、この直接参照の方式で実装されています。

埋め込みベクトルはどのように学習されるのか

ここで、多くの初学者が抱く疑問があります。埋め込み行列の中の3860万個の数値は、誰がどうやって決めたのでしょうか。

答えは、学習によって自動的に決まる、です。

学習の仕組み

埋め込み行列の値は、学習の開始時点では、ランダムな数値が入っています。この時点では、「猫」のベクトルにも「犬」のベクトルにも、何の意味もありません。

学習の過程では、大量の文章を使って、次のトークンを予測する訓練が繰り返されます。予測が外れると、誤差逆伝播法によって、モデル全体のパラメータが調整されます。

このとき、埋め込み行列の数値も、調整の対象に含まれます。埋め込み行列は、固定された辞書ではなく、学習されるパラメータなのです。

なぜ意味が反映されるのか

学習を重ねると、なぜ意味の近い単語のベクトルが近づくのでしょうか。

理由は、意味の近い単語は、似た文脈で使われるからです。

「猫が魚を食べた」「犬が肉を食べた」という文があったとき、「猫」と「犬」は、どちらも「〜が〜を食べた」という同じ構造の中に登場します。

次のトークンを正しく予測するためには、似た文脈に登場する単語には、似た振る舞いをさせるのが有利です。その結果、学習が進むにつれて、似た文脈で使われる単語のベクトルが、自然と近い値に収束していきます。

この考え方は、分散表現と呼ばれる概念の根幹です。「単語の意味は、その単語が使われる文脈によって決まる」という言語学的な仮説(分布仮説)が、その背景にあります。

埋め込み空間で何が起きているか

学習後の埋め込み空間には、興味深い性質が現れることが知られています。

有名な例として、次のような関係が成り立つ場合があります。

king - man + woman \approx queen

「王」のベクトルから「男」のベクトルを引き、「女」のベクトルを足すと、「女王」のベクトルに近い位置になる、というものです。

これは、埋め込み空間の中で、「性別」という概念が、特定の方向のベクトルとして表現されていることを示唆しています。

ただし、この性質は、常にきれいに成り立つわけではありません。単語の選び方や、学習に使われたデータによって、成立したりしなかったりします。分かりやすい例として紹介されることが多いものの、埋め込みの性質を過度に単純化した説明でもあるという点は、押さえておいてください。

Transformer Explainerで観察できること

Transformer Explainerでトークンの箱をクリックすると、そのトークンに対応する埋め込みベクトルが、縦に並んだ数値として表示されます。

このとき、次の点を確認してみてください。

一つ目は、数値の個数です。768個という次元数を、視覚的に確認できます。

二つ目は、値の範囲です。多くの値が、おおよそマイナス1からプラス1の範囲に収まっていることが分かります。極端に大きな値や小さな値は、あまり現れません。

三つ目は、同じ単語であれば、文中のどこにあっても同じベクトルになるという点です。この段階では、まだ文脈の情報は一切含まれていません。

三つ目の点は特に重要です。この時点での埋め込みは、辞書を引いた結果にすぎず、文脈による意味の違いを区別できません。

「銀行の口座」の「銀行」と、「川の銀行」(英語のbankには土手の意味もあります)の「銀行」は、この段階ではまったく同じベクトルです。

この区別を可能にするのが、第5章以降で扱う自己注意の役割です。埋め込みは出発点であり、そこから文脈に応じてベクトルが変化していくのが、Transformerの処理だと理解してください。

実務でつまずきやすい点

つまずき1:埋め込みと文脈化された表現の混同

「単語埋め込み」という言葉は、Word2VecやGloVeといった、より古い手法を指す場合にも使われます。これらは、1単語に1ベクトルを固定的に割り当てる手法でした。

Transformerにおける埋め込みは、あくまで最初の入力であり、そこから各層を通るたびにベクトルが変化していきます。最終的にモデルが扱っている表現は、文脈を反映したものです。

「BERTの埋め込み」といった表現が使われるとき、それが入力の埋め込みを指すのか、各層を通った後の文脈化された表現を指すのかは、文脈によって異なります。注意して読み分けてください。

つまずき2:次元数を大きくすれば性能が上がると考える

768という次元数を大きくすれば、より豊かな表現ができそうに思えます。

しかし、次元数を増やすと、パラメータ数が増え、学習に必要なデータ量と計算資源が増加します。データ量が不足していると、かえって過学習を招きます。

次元数は、モデルの規模、学習データの量、計算資源のバランスの中で決められる設計値です。大きければよいというものではありません。

つまずき3:埋め込み行列の重み共有に気づかない

GPT-2をはじめとする多くのモデルでは、入力の埋め込み行列と、出力層で使う行列が、同じものを共有していることがあります。これを重み共有(Weight Tying)と呼びます。

パラメータ数を削減し、性能を向上させる効果があることが知られています。

第14章で出力層を扱う際に、この点をあらためて説明しますが、「入力と出力で同じ行列が使われることがある」という点は、頭の片隅に置いておいてください。

演習

演習1:埋め込み行列のサイズを計算する

次の条件のモデルについて、埋め込み行列のパラメータ数を計算してください。

語彙数が32000、埋め込み次元が4096のモデル。

このパラメータ数は、GPT-2(small)の埋め込み行列と比べて、何倍になるでしょうか。

演習2:Pythonで埋め込みを取り出す

実際に埋め込みベクトルを取り出して、その性質を確認してみてください。

from transformers import GPT2Tokenizer, GPT2Model
import torch

tokenizer = GPT2Tokenizer.from_pretrained("gpt2")
model = GPT2Model.from_pretrained("gpt2")

# 埋め込み行列の形を確認
embedding_matrix = model.wte.weight
print(f"埋め込み行列の形: {embedding_matrix.shape}")

# 特定の単語の埋め込みを取り出す
words = [" cat", " dog", " computer"]
vectors = {}

for word in words:
    token_id = tokenizer.encode(word)[0]
    vector = embedding_matrix[token_id]
    vectors[word] = vector
    print(f"{word} のトークンID: {token_id}")
    print(f"ベクトルの最初の5要素: {vector[:5].tolist()}")

# コサイン類似度で単語どうしの近さを比較
cos = torch.nn.CosineSimilarity(dim=0)
print(f"\ncat と dog の類似度: {cos(vectors[' cat'], vectors[' dog']):.4f}")
print(f"cat と computer の類似度: {cos(vectors[' cat'], vectors[' computer']):.4f}")

実行前に、次のコマンドで必要なライブラリをインストールしてください。

pip install transformers torch

演習の解答例と解説

演習1の解答は、次の通りです。

32000 \times 4096 = 131072000

約1億3100万個です。GPT-2(small)の埋め込み行列が約3860万個ですから、およそ3.4倍になります。

この計算から、語彙数と埋め込み次元のどちらを増やしても、パラメータ数が線形に増加することが分かります。大規模モデルでは、埋め込み行列だけで数億個のパラメータになることも珍しくありません。

演習2では、catとdogの類似度が、catとcomputerの類似度より高くなることが期待されます。実際に実行して、その傾向が観察できるかを確認してください。

ただし、入力層の埋め込みだけでは、期待するほどきれいに意味の近さが反映されない場合もあります。これは、Transformerでは意味の表現が各層を通じて段階的に構築されるためです。入力の埋め込みは、あくまでその出発点にすぎません。

講師向けの補足

この章で受講者がつまずきやすいのは、「768個の数値が、どうして意味を表せるのか」という点です。

数値の一つひとつに意味があるのではなく、768個の組み合わせ全体で意味が表現されている、という点を強調してください。

分かりやすい導入として、色の表現を例に使う方法があります。ある色は、赤、緑、青の3つの数値の組み合わせで表現されます。赤の値だけを見ても色は分かりませんが、3つ揃えば、あらゆる色を表現できます。単語の意味を768個の数値で表現するのも、これと同じ発想である、という説明が有効です。

理解度を確認する問い

第3章の内容を、次の問いで確認してみてください。

トークンIDをそのままモデルに入力してはいけない理由を、三つ挙げてください。

埋め込み行列の中の数値は、どのようにして決まるのでしょうか。

学習の結果、意味の近い単語のベクトルが近づくのは、なぜでしょうか。

同じ単語であれば、文中のどこにあっても埋め込みベクトルは同じです。この性質は、どのような問題を引き起こすでしょうか。

まとめ

トークン埋め込みとは、意味を持たないトークンIDを、意味を表す数値のベクトルに変換する処理です。GPT-2(small)では、1トークンが768次元のベクトルに変換されます。

この変換は、50257行768列の埋め込み行列から、トークンIDに対応する行を取り出すことで実現されます。この行列だけで約3860万個のパラメータを持ち、モデル全体の約31パーセントを占めます。

埋め込み行列の数値は、あらかじめ人間が決めるものではなく、学習によって自動的に獲得されます。似た文脈で使われる単語は、次のトークンを予測するうえで似た振る舞いが有利であるため、学習が進むにつれてベクトルが近づいていきます。

ただし、この段階の埋め込みには、文脈の情報が一切含まれていません。同じ単語は、文中のどこにあっても同じベクトルになります。文脈による意味の違いを反映する仕組みは、後の章で扱う自己注意が担います。

次の第4章では、位置エンコーディングを扱います。埋め込みだけでは失われてしまう「単語の並び順」の情報を、どのようにしてベクトルに加えるのかを解説します。

第4章に進む前に、Transformer Explainerで、同じ単語を文章の異なる位置に2回入れてみてください。その2つのトークンの埋め込みベクトルが同じであるかどうかを確認しておくと、次章で位置情報が必要になる理由が、実感を持って理解できるはずです。

セイ・コンサルティング・グループでは新人エンジニア研修のアシスタント講師を募集しています。

投稿者プロフィール

山崎講師
山崎講師代表取締役
セイ・コンサルティング・グループ株式会社代表取締役。
岐阜県出身。
海外放浪の末、2000年創業、2004年会社設立。
IT企業向け人材育成研修歴業界歴20年以上。
すべての無駄を省いた費用対効果の高い「筋肉質」な研修を提供します!
この記事に間違い等ありましたらぜひお知らせください。

学生時代は趣味と実益を兼ねてリゾートバイトにいそしむ。長野県白馬村に始まり、志賀高原でのスキーインストラクター、沖縄石垣島、北海道トマム。高じてオーストラリアのゴールドコーストでツアーガイドなど。現在は野菜作りにはまっている。