OpenAIのAPIを使用しています。正確性の保証はできませんので、ご注意ください。
60 行の NumPy で学ぶ GPT
この記事は、「GPT in 60 Lines of NumPy」を日本語に翻訳したものです。翻訳を許可していただいた Jay Mody 氏に感謝します。この記事は CC ライセンスに含まれません。
イントロダクション
この記事では、わずか60 行のnumpy
で GPT をゼロから実装します。その後、OpenAI が公開したトレーニング済みの GPT-2 モデルの重みを読み込み、テキストを生成します。
注意:
-
この記事では、Python、NumPy、およびニューラルネットワークの基本的なトレーニング経験についての理解を前提としています
-
この実装は、完全であることを保ちつつ、できるだけシンプルにするために、意図的に多くの機能が欠けています。目標は、教育ツールとして GPT のシンプルかつ完全な技術入門を提供することです
-
GPT アーキテクチャは、現在の LLM(Large Language Models、大規模言語モデル)を形成する要素のほんの一部に過ぎません 1
-
この記事のすべてのコードは、github.com/jaymody/picoGPTで確認することができます
編集(2023 年 2 月 9 日): 「次はなんですか?」セクションを追加し、イントロにいくつかのノートを追加しました。
編集(2023 年 2 月 28 日): 「次はなんですか?」にいくつかの追加セクションを追加しました。
GPT とはなんですか?
GPT はGenerative Pre-trained Transformer の略です。これは、Transformerに基づく一種のニューラルネットワークアーキテクチャです。Jay Alammar 氏のHow GPT3 Worksは、GPT についての優れた高レベルの紹介ですが、以下に要約します。
- Generative: GPT はテキストを生成します
- Pre-trained: GPT は、書籍やインターネットなどの大量のテキストでトレーニングされます
- Transformer: GPT はデコーダーのみのtransformerニューラルネットワークです
OpenAI の GPT-3、Google の LaMDA、およびCohere の Command XLargeなどの大規模言語モデル(LLM)は、本質的には GPT です。それらが特別なのは、**1)**非常に大きい(数十億のパラメータ)ことと、**2)**多くのデータ(数百ギガバイトのテキスト)で訓練されていることです。
基本的に、GPT はプロンプトを与えられた場合にテキストを生成します。この非常にシンプルな API(入力=テキスト、出力=テキスト)でも、訓練が十分に行われた GPT は、あなたのメールを書く、本を要約する、Instagram のキャプションのアイデアを提供する、5 歳の子供にブラックホールを説明する、SQL でコードを書く、遺言書を作成するなど、素晴らしいことができます。
以上が GPT の概要とその機能の高レベルな概要です。さらに詳細に掘り下げてみましょう。
入力 / 出力
GPT の関数シグ ネチャはおおよそ以下のようになります:
def gpt(inputs: list[int]) -> list[list[float]]:
# inputs は [n_seq] の形状を持つ
# 出力は [n_seq, n_vocab] の形状を持つ
output = # beep boop neural networkの魔法
return output
入力
入力は、テキストを表す整数のシーケンスであり、テキスト内のトークンにマップされます:
# 整数はテキスト内のトークンを表します。例えば:
# テキスト = "not all heroes wear capes":
# トークン = "not" "all" "heroes" "wear" "capes"
inputs = [1, 0, 2, 4, 6]
トークンはテキストのサブピースであり、ある種のトークナイザーを使用して生成されます。語彙(ボキャブラリー)を使用してトークンを整数にマッピングすることができます:
# トークンの語彙内でのインデックスは、そのトークンの整数IDを表します
# 例えば、"heroes"の整数IDは2です。なぜならvocab[2] = "heroes"だからです
vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]
# 空白でトークナイズする架空のトークナイザー
tokenizer = WhitespaceTokenizer(vocab)
# encode()メソッドは文字列をlist[int]に変換します
ids = tokenizer.encode("not all heroes wear") # ids = [1, 0, 2, 4]
# 語彙マッピングを通じて実際のトークンを確認できます
tokens = [tokenizer.vocab[i] for i in ids] # tokens = ["not", "all", "heroes", "wear"]
# decode()メソッドはlist[int]を文字列に戻します
text = tokenizer.decode(ids) # text = "not all heroes wear"
要約すると:
- 文字列があります
- トークナイザーを使用して、それを「トークン」と呼ばれる小さな部分に分解します
- これらのトークンを整数にマッピングするために語彙(ボキャブラリー)を使用します
実際には、単純に空白で分割するよりも、Byte-Pair EncodingやWordPieceのような、より高度なトークナイズ方法を使用しますが、原理は同じです:
- 文字列トークンを整数インデックスにマッピングする
vocab
があります str -> list[int]
に変換するencode
メソッドがありますlist[int] -> str
に変換するdecode
メソッドがあります 2
出力
出力は 2 次元配列であり、output[i][j]
はモデルが vocab[j]
のトークンが次のトークン inputs[i+1]
であると予測した確率です。例えば:
vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]
inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"
output = gpt(inputs)
# ["all", "not", "heroes", "the", "wear", ".", "capes"]
# output[0] = [0.75 0.1 0.0 0.15 0.0 0.0 0.0 ]
# "not"のみが与えられた場合、モデルは最も高い確率で単語"all"を予測します
# ["all", "not", "heroes", "the", "wear", ".", "capes"]
# output[1] = [0.0 0.0 0.8 0.1 0.0 0.0 0.1 ]
# シーケンス["not", "all"]が与えられた場合、モデルは最も高い確率で単語"heroes"を予測します
# ["all", "not", "heroes", "the", "wear", ".", "capes"]
# output[-1] = [0.0 0.0 0.0 0.1 0.0 0.05 0.85 ]
# 全シーケンス["not", "all", "heroes", "wear"]が与えられた場合、モデルは最も高い確率で単語"capes"を予測します
シーケンス全体に対する次のトークンの予測を得るためには、output[-1]
の中で最も高い確率を持つトークンを単純に取ります:
vocab = ["all", "not", "heroes", "the", "wear", ".", "capes"]
inputs = [1, 0, 2, 4] # "not" "all" "heroes" "wear"
output = gpt(inputs)
next_token_id = np.argmax(output[-1]) # next_token_id = 6
next_token = vocab[next_token_id] # next_token = "capes"
最も高い確率を持つトークンを予測として取ることを、貪欲デコーディングまたは貪欲サンプリングと呼びます。
シーケンスで次の論理的な単語を予測するタスクは、言語モデリングと呼ばれます。そのため、GPT を言語モデルと呼ぶことができます。
単一の単語を生成するのは素晴らしいことですが、文全体や段落などはどうなるでしょうか?