次の文字を当てることを学ぶ
言語モデルは、次のトークンの予測で学習します。次のトークンを当て、正しいものを見て、もっとよく当たるように数を少し動かします。膨大な文章でこれをくり返すことが、学習のすべてです。
ここでは、短い1行でとても小さなモデルを数秒で学習させ、予測がよくなる様子を見ます。
このレッスンのゴール
メモ
次の文字を当てる小さなモデルを学習させ、その損失を読むことができる。
キーワード
| ことば | いみ | れい |
|---|---|---|
| 次のトークンの予測 | 次に来るトークンを当てること | |
| 正解 | 実際に次に来るトークン | か の後の な |
| 確率 | モデルが1つのトークンに与える0から1の割合 | |
| 損失 | モデルが正解にどれだけ驚いたか | |
| 学習の1回 | すべてのスコアを少しずつ動かすこと | |
| 学習率 | 1回に動かす大きさ | 2.0 |
学生が入力するセルは、このプレビューでは空白です。
文章のどの場所も1つの問いになる
「さかな、さかなや、たかな、さかなや、さかな」の1行を使います。読点も1つのトークンです。どの場所でも、問いは「次に何が来るか」です。答えの正解は、文章の次の文字です。
だから21文字から20の問いができ、手で答えを付ける必要はありません。モデルがどんな文章からでも学べるのは、このためです。
例: 最初の6つの問い
| 場所 | このトークン | 正解 |
|---|---|---|
| 0 | さ | か |
| 1 | か | な |
| 2 | な | 、 |
| 3 | 、 | さ |
| 4 | さ | か |
| 5 | か | な |
学生が入力するセルは、このプレビューでは空白です。
このモデルは1つ前のトークンだけを見る
この小さなモデルは、スコアの表です。このトークンごとに1行、次に来うるトークンごとに1列があります。softmax で、1行を確率に変えます。6種類のトークンなので、表のスコアは36個です。
このモデルは、直前のトークンしか見ません。トランスフォーマーは、マスク付きの注意で、前のすべてのトークンを見ます。学習のしかたは同じです。
損失は驚きを測る
1つの問いの損失は、モデルが正解に与えた確率の log にマイナスを付けたものです。自信を持って当てれば、ほとんど0です。正解に小さな確率しか与えなければ、大きくなります。
始めはすべてのスコアが0なので、6種類のトークンがそれぞれ 0.17 ずつ受け取ります。このとき損失は、どの問いでも 1.79 です。
例: 正解の確率と損失
| 正解に与えた確率 | 損失 |
|---|---|
| 1.0 | 0 |
| 0.9 | 0.11 |
| 0.5 | 0.69 |
| 6分の1(約 0.17) | 1.79 |
| 0.1 | 2.30 |
学生が入力するセルは、このプレビューでは空白です。
学習の1回でスコアを少し動かす
問いごとに、このトークンの行を見ます。その行の各スコアは、自分の確率の分だけ下がります。正解のスコアだけは、さらに1だけ上がります。
この押し具合を20の問いで足し、20で割ります。それに学習率をかけて当てはめます。これが学習の1回です。くり返すと、損失が下がります。
小さなモデルを50回学習させる
import math
text = "さかな、さかなや、たかな、さかなや、さかな"
chars = ["、", "か", "さ", "た", "な", "や"] # 語彙は6種類
pairs = len(text) - 1 # このトークンと次のトークンの組が20
table = [] # スコアはすべて0から
for i in range(6):
table.append([0.0] * 6)
for step in range(51):
loss = 0
for a in range(6): # 直前のトークンごとに、行を1つずつ直す
powers = []
for s in table[a]:
powers.append(math.exp(s))
total = sum(powers)
nudge = [0.0] * 6
for k in range(pairs):
if text[k] == chars[a]:
b = chars.index(text[k + 1]) # 正しい次のトークン
loss = loss - math.log(powers[b] / total)
nudge[b] = nudge[b] - 1
for j in range(6):
nudge[j] = nudge[j] + powers[j] / total
for j in range(6):
table[a][j] = table[a][j] - 2.0 * nudge[j] / pairs # 2.0 を 0.5 に変えて試す
if a == 0:
sa = powers[2] / total # 読点の後に さ が来る確率
if step % 10 == 0:
print("回", step, "損失", round(loss / pairs, 3), "読点の後の さ", round(sa, 2))
例: 学習が進むと損失はどうなるか
| 回 | 0 | 10 | 20 | 30 | 40 | 50 |
|---|---|---|---|---|---|---|
| 損失 | 1.792 | 0.642 | 0.450 | 0.380 | 0.345 | 0.324 |
学生が入力するセルは、このプレビューでは空白です。
よくある間違い
注意
モデルは読んだ文を覚えていて、それを調べていると考えがちです。このモデルが持つのは36個のスコアで、文章は持っていません。学んだのは、か の後にはいつも な が来る、という傾向です。モデルは傾向を数で持ち、だから自信満々に間違えることもあります。
学習の台本をラボに書く
docker exec jupyter sh -c "printf '%s\n' 'import numpy as np' 'text = \"sa ka na ten sa ka na ya ten ta ka na ten sa ka na ya ten sa ka na\".split()' 'chars = [\"ten\", \"ka\", \"sa\", \"ta\", \"na\", \"ya\"]' 'ids = np.array([chars.index(c) for c in text])' 'x, y = ids[:-1], ids[1:]' 'W = np.random.default_rng(0).normal(0, 0.1, (6, 6))' 'for step in range(101):' ' p = np.exp(W[x]) / np.exp(W[x]).sum(axis=1, keepdims=True)' ' if step % 25 == 0:' ' print(\"step\", step, \"loss\", round(-np.log(p[np.arange(20), y]).mean(), 3))' ' p[np.arange(20), y] -= 1' ' np.add.at(W, x, -2.0 * p / 20)' 'p = np.exp(W[0]) / np.exp(W[0]).sum()' 'print(\"after ten:\", dict(zip(chars, p.round(2).tolist())))' > steps/tiny.py"ここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。
種を決めた乱数から学習させる
docker exec jupyter python3 steps/tiny.pyここでは表示のみで、実行されません。無料アカウントを作成すると、実行ボタンで自分のラボのターミナルに入力できます。