言語モデルがくり返す部品
トランスフォーマーのブロックは、言語モデルを組み立てる単位です。モデルは、同じブロックを何段も積み重ねたものです。ブロックには、複数のヘッドを持つマスク付きの注意と、トークンごとの小さなネットワークがあります。その両方を、近道と大きさの調整が囲みます。
ブロックを1つ理解すれば、モデル全体の形が分かります。
このレッスンのゴール
メモ
1つのトークンが、トランスフォーマーのブロックの手順を通る様子をたどることができる。
キーワード
| ことば | いみ |
|---|---|
| 因果マスク | トークンが後ろのトークンを見ないようにするもの |
| ヘッド | 自分のクエリ、キー、バリューの表を持つ1つの注意 |
| フィードフォワード | 各トークンに1つずつかける小さなネットワーク |
| 残差接続 | 手順の入力をその出力に足すこと |
| 正規化 | ベクトルの数が一定の大きさに収まるよう調整すること |
| ブロック | 注意とフィードフォワードと残差をまとめた、積み重ねる単位 |
学生が入力するセルは、このプレビューでは空白です。
因果マスクは先を隠す
言語モデルは、次のトークンを当てることを学びます。トークンが自分より後ろを見られたら、答えが見えてしまいます。
そこで因果マスクで、各トークンが自分と前のトークンだけを見るようにします。後ろのトークンの重みは0です。実際には、softmax の前にそのスコアをマイナス無限大にします。e のマイナス無限大乗は0だからです。
例: 先を隠したときの重み
| トークン | スコア | 見てよい相手 | 重み |
|---|---|---|---|
| 1番目 | 1, 2, 0 | 1番目だけ | 1, 0, 0 |
| 2番目 | 2, 1, 1 | 1番目と2番目 | 0.731, 0.269, 0 |
| 3番目 | 0, 1, 3 | 3つすべて | 0.042, 0.114, 0.844 |
学生が入力するセルは、このプレビューでは空白です。
Python で先を隠す
import math
# scores[i][j]: トークン i のクエリとトークン j のキーの合い具合。
scores = [[1, 2, 0], [2, 1, 1], [0, 1, 3]]
for i in range(3):
powers = []
for j in range(3):
if j <= i:
powers.append(math.exp(scores[i][j]))
else:
# 後ろのトークンは隠すので、重みはまったくありません。
powers.append(0)
total = sum(powers)
weights = []
for p in powers:
weights.append(round(p / total, 3))
print("トークン", i, "重み", weights)
# j <= i を True に変えて(何も隠さずに)実行してください。
# 1番目のトークンの行はどう変わり、なぜそれがずるいのですか。
NumPy で同じマスクをかける
docker exec jupyter python3 -c "import numpy as np; S = np.array([[1, 2, 0], [2, 1, 1], [0, 1, 3]], dtype=float); S[np.triu(np.ones((3, 3), dtype=bool), 1)] = -np.inf; W = np.exp(S) / np.exp(S).sum(axis=1, keepdims=True); print(W.round(3).tolist())"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
複数のヘッドは違うものを探す
1つの注意は、1通りの混ぜ方しかできません。そこでブロックでは、複数のヘッドを並べて動かします。ヘッドはそれぞれ、自分のクエリ、キー、バリューの表を持ちます。
あるヘッドは、語を直前の語と結び付けるかもしれません。別のヘッドは、「の」の前後のような、持ち主と持ち物を結び付けるかもしれません。各ヘッドの出力を端から端へつなぎ、学習で決まる表をもう1つかけて、ふつうの長さに戻します。
例: 2つのヘッドをつなぐ
| 手順 | トークン 猫 では |
|---|---|
| ヘッド1の出力 | 7.88, 2.12 |
| ヘッド2の出力 | 1, 3 |
| 端から端へつなぐ | 7.88, 2.12, 1, 3 |
| 混ぜる表をかけた後 | ふつうの長さに戻る |
学生が入力するセルは、このプレビューでは空白です。
フィードフォワード、残差、正規化
注意の後、各トークンは1つずつ、小さなフィードフォワードを通ります。ディープラーニングで学んだとおり、間に ReLU をはさんだ2つの層です。
残差接続は、各手順の入力を出力に足すので、すでに分かっていることが失われません。正規化は、各ベクトルの数の平均を0にし、広がりを一定にします。どちらも、深く積んだモデルを学習できるようにします。
例: 残差の後に正規化する
| 手順 | 数 |
|---|---|
| 猫 の入力 | 1, 2 |
| 注意の出力 | 7.88, 2.12 |
| 残差: 足す | 8.88, 4.12 |
| 正規化: 平均0 | 1, -1 |
学生が入力するセルは、このプレビューでは空白です。
NumPy でベクトルを正規化する
docker exec jupyter python3 -c "import numpy as np; x = np.array([2.0, 4.0, 6.0]); print(x.mean(), ((x - x.mean()) / x.std()).round(2).tolist())"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
よくある間違い
注意
フィードフォワードで、トークンどうしがやり取りすると考えがちです。実際は、各トークンに1つずつ働きます。トークンが情報を分け合うのは注意の中だけなので、ほかのトークンの事実は、先に注意で届いている必要があります。