見出し画像

理解して差をつける!ChatGPTの裏側! ~シリーズ1: 概要と入力文章の処理~

はじめに

この記事はChatGPTを使っていて、どうやって動いているのか知りたい人向けの記事の第一弾です。このシリーズを読み終わった時あなたは以下のような状態になっています。

  • ChatGPTがどのように文章を作っているのかわかる。

  • これからのAIの動きを追うための基礎がわかる。

  • 同僚や先輩に「ChatGPT、当たり前のように使ってますけど、その裏側どうなってるか知ってます?」という豆知識を披露できるようになる

今回は上記の状態になるためのシリーズ第一弾です!数学の知識があまりなくても分かりやすいように結構気合い入れて書きました。

大規模言語モデルについて

まず、ChatGPTの裏側について知る前にLarge Language Model(大規模言語モデル)についての概要を知っておくことが大切です。Large Language Modelとは、一言で言うと「大規模なデータセットで学習された自然言語モデル」です。今まで自然言語モデルの作成には多くの方法が研究されてきて、畳み込みニューラルネットワークや再帰型ニューラルネットワークなどがその一つです。しかし、今までの方法ではいくつか問題点がありました。

  1. 一つのモデルが一つのタスクにしか適用できない

  2. 構造上、計算が直列にしかできないので計算量が膨大になると時間がかかりすぎる

このような問題を解決するために、2017年にGoogleがTransformerという新しいニューラルネットワークの構造を提案しました。[1]
Transformerは並列計算が可能で、上記の2の問題を解決しました。その後、さらに一定以上の学習データと、一定以上のTransformer内で使用されるパラメーターを用いた場合にこれらを利用して作られたモデルがさまざまなタスクを解決し始めたというのがLarge Language Modelの歴史になります。「大量のデータとパラメーターを持つ自然言語モデル」だからLarge Language Model(大規模言語モデル)ということです。最近話題になっているNVIDIAはこのTransformerの構造と相性が抜群の、並列計算が得意なGPUというものを作っているから、株価が上がっているということなのです。

ここで出た「パラメーター」というワードについてもあとで説明するので安心してください。

ChatGPTがしていること

詳細に入る前に、どうやってこのTransformerを使ったモデル(以下、ChatGPTとします)が文章を生成しているのかの概要を理解しましょう。
例えば、「私はみかんを」という文章がモデルに入力されたとします。このとき、ChatGPTは以下のことをしています。

  1. 「私はみかんを」という文章をトークンに分割する。本来は違いますが、ここでは分かりやすくトークン=単語としてしまいましょう。

  2. 分割されたトークンの情報を、モデルの語彙集から取り出す。ここでいう語彙集とは、モデルが理解できる単語のリストのようなものです。

  3. 2の情報を踏まえて、各トークンがお互いにどれくらい関係しているのかを計算する。

  4. 3の計算結果を踏まえて、次に来る確率が高いトークンをモデルの語彙集から選ぶ。(例として「食べる」とします。)

  5. 3で選んだトークン(食べる)を「私はみかんを」の次に加える。→私はみかんを食べる

  6. 同じプロセスを繰り返し、「私はみかんを食べる」の次に来る可能性が高いトークンを語彙集の中から選ぶ。(「。」とします。) -> 私はみかんを食べる。

  7. これらを出力トークン数のMaxまで繰り返す。

これがChatGPTが文章を生成している仕組みになります。(※これは文章を生成する時の流れであり、モデル自体を訓練するときには少し流れが異なります。) つまり、入力された文章の「次にくる可能性が高いトークンを選び続ける」ことで、文章を生成しているのがChatGPTのからくりです。
正直、友達に披露するならここまででも十分わかってるフリができるはずです。
この時の「3」のトークンがお互いにどれくらい関係しているのかを計算するのが「Attention」という仕組みになり、それが"Attention" is all you need. の由来です。
Attentionとは日本語訳すると「注意」とかの意味になるので「各トークンがお互いにどれくらい注意を払っているのか」というイメージを持つと分かりやすいかなと思います。

また、これらを繰り返す中で、ベクトルの計算を使うのですがその時のベクトルの要素数(行と列の積)がパラメーター数となります。1~7の中でさまざまな「ベクトル」を使用するので、これらの中で使われる「さまざまなベクトルの要素数の和」が「大規模言語モデルのパラメーター」と呼ばれるものです。ちなみにこのパラメーター数は古いモデルのGPT3で約1.75兆個とされています。[2]

Transformerの中身の最初と最後

では、先ほどのChatGPTが何をしているのかをもとに、Transformerの中身を説明していきます。(実際には論文で提唱されたTransformerの構造とChatGPTで動いているGPTが使用している構造は異なります。このシリーズではGPTが採用している構造を扱いますが、基本的な理解に違いはないです。)今回はTransformerの最初の部分、「入力された文章の処理」についてみていきます。

入力された文章の処理

Transformerを利用して作成されたモデルが「私はみかんを」という文章を受け取ったときを考えます。モデルはこれを以下のようなトークンに分割します。実際にはトークンは単語単位ではないですが、理解のためここでは単語単位とします。

["私", "は", "みかん", "を"]

同時に、モデルは「語彙集」を持っていて"私", "は", "みかん", "を"に対応するベクトル(数値)を持っています。他にもたくさんのトークンのベクトルを持っています。
例:

語彙集のイメージ

語彙集から入力された文章のトークンに該当するものを取り出し、分割されたトークンをベクトルで表します。

入力のベクトル化

ここで、ベクトルは「座標内で矢印を表す」ということを思い出しましょう。
簡単に2次元のベクトルを考えます。例えば、語彙集の中でみかんとオレンジというトークンが以下のベクトルを持っていたとします。(便宜上1次元ベクトルになっていますが、2次元だと思ってください)

みかん: [3, 4]
オレンジ: [3, 4.5]

この時、xy座標では以下のようにベクトルをかけます。[ソースコード1]この座標から「この2単語はとても近い性質を持っている」ということがベクトルでの表現でわかります。このような各トークンのベクトル情報を、訓練されたモデルは獲得しています。

みかんとオレンジの類似度

今回は2次元で表現しましたが、実際にはGPT3では12,248次元!!!!!もの次元数でトークンが表現されています。現在のChatGPTで使われているGPT4はこの次元数を公開していませんが、12,248よりもさらに大きな次元数であることは間違いないです。ここで「次元数が多いと何が嬉しいか」というと、より多くの情報を含めることができるという点だと考えられています。例えば先ほどの2次元の例では、「形」と「色」しか考慮できないが、次元を3次元にすると「形」「色」「大きさ」も表現できると考えるとわかりやすいはずです。
このような訓練で獲得されたデータを用いて、入力された文章を「数値」として扱えるようにしているのです。
実際にはもう少し複雑なことをしていて、この「数値」にそれぞれのトークンの文章中の位置を考慮することで語彙集から取得したベクトルの値を更新していますしかし、基本的な変換方法は上記のものなのでこの位置を考慮したデータの更新は気にする必要はないと思っています。(ちなみに位置を考慮するために位置符号というものを使用します。興味のある方は調べてみてください。)

実験してみる

GPT3で使われている変換方法を用いると、「みかん」という単語が「オレンジ」と似たベクトルを持っていたり、「みかん」と「果物」も似たベクトルを持っていたりします。「みかん」と「正月」も関連性が高いかもしれません。書いていて気になったので試してみました。[ソースコード2]

{'みかん': [0.00979894120246172,
  -0.02237303927540779,
  0.007836510427296162,
  -0.016228051856160164,
  -0.009977344423532486,
  0.013677551411092281,
  -0.014047572389245033,
  -0.035178400576114655,
  -0.01994147337973118,
  -0.018197091296315193,
  0.004516235087066889,
  0.0102680753916502,
  -0.011774588376283646,
  -0.021276190876960754,
  -0.020245419815182686,
  0.0007730789948254824,
  0.03509910777211189,
  -0.027117233723402023,
  0.03412119671702385,
  -0.03827071562409401,
  0.0008073555072769523,
  0.003359920345246792,
  0.00593024305999279,
  -0.025571074336767197,
  -0.03200679272413254,
  0.02164621278643608,
  0.01984896883368492,
  -0.018686046823859215,
  0.0024893805384635925,
  -0.011919952929019928,
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.......],
'オレンジ': [0.0017554564401507378,
  -0.022219110280275345,
  -0.00476790638640523,
  -0.014830522239208221,
  0.007568634580820799,
  0.022819265723228455,
  -0.028914185240864754,
  0.008882309310138226,
  0.006428338121622801,
  -0.027820566669106483,
  0.0011527996975928545,
  0.002232247032225132,
  -0.016310907900333405,
  -0.005774834658950567,
  -0.01731116697192192,
  -0.011102886870503426,
  0.03520915284752846,
  -0.013936957344412804,
  0.016390928998589516,
  -0.011216249316930771,
  -0.027820566669106483,
  -0.0259267408400774,
  -0.007181867491453886,
  -0.01584411971271038,
  0.013156753964722157,
  -0.003225838765501976,
  0.011803069151937962,
  -0.011663032695651054,
  0.0128099974244833,
  -0.01993851736187935,
  0.05436079949140549,
  -0.016350917518138885,
  0.0082621481269598,
  -0.01948506571352482,
  -0.032648488879203796,
  -0.021045472472906113,
  0.0017671261448413134,
  0.006961809936910868,
  -0.004264442250132561,
  -0.00870893057435751,
  0.006781763397157192,
  0.006428338121622801,
  0.002662358805537224,
  -0.014430418610572815,
  -0.019031614065170288,
  0.007795359939336777,
  -0.03902347758412361,
  0.001569574698805809,
  -0.005831516347825527,
  0.008855636231601238,
  0.0031991652213037014,
  0.008835630491375923,
  -0.0072018723003566265,
  -0.01599082350730896,
  0.011823073960840702,
  0.0016587646678090096,
~~~~~~~~~~~~
.....],
'果物': [~~~~~~~~~~~],
'正月': [~~~~~~~~~~~]
}

これだとやはり次元数が多すぎて、何がなんだか分からないので、ベクトルが「どれだけ似ているか」を計算して視覚化してみます。

この計算にはコサイン類似度という方法を使います。そもそもベクトルの類似性は2つのベクトルの内積を計算することで計算できます。この結果を-1~1の間に丸めるためにそれぞれの大きさで割ります。これがコサイン類似度です。
一応式は載せておきますが、2つのベクトルがどれだけ似た性質を持っているかを計算できるとだけ理解すれば問題ないです。

数学の景色、コサイン類似度とは~定義と具体例~, https://mathlandscape.com/cos-similar/より引用

結果をヒートマップにまとめると、以下のようになりました。[ソースコード3]ここでは、日本語でラベルを表示しようとすると文字化けするので英語でのMikanなどの表記ですが、計算したのは日本語の「みかん」「オレンジ」「果物」「正月」です。

みかん、オレンジ、果物、正月の類似度

赤が関係が強く、淡い青から濃い青になるについて関連が低くなります。みかんと「オレンジ、果物」はそれぞれ関係が深いけど、「みかんと正月」は「果物と正月」よりも関係が低くなり、「オレンジと正月」はもっと関連が低くなります。大体の直感と合っているのではないでしょうか?このような各トークンの情報を表すデータをモデルはあらかじめ学習しているのです。ある実験では、GPT3が持っている「語彙集」で、「男性」を表すベクトルから「女性」を表すベクトルを引いた時の値が、「王様」を表すベクトルから「女王様」を表すベクトルを引いた時と近いという結果が出ています。学習能力が素晴らしいです。

用語について

ちなみにここでいう「語彙集」が持つ、トークンの量をVocaburarly sizeと呼び、GPT3では50257個ものVocaburaryを持っています。
そして、入力された文章から各トークンのベクトルの情報に変換することをEmbeddingと呼び、このEmbeddingの次元数がGPT3の場合、12248個であるという言い方をします。
つまり、語彙集はEmbedding(12248次元) ✖️ Vocaburary size(50257個) = 615,547,736個の要素数のベクトルを持っていて、これがEmbeddingのパラメーター数となり、総パラメーター数のうちの一部分となります。(615,547,736個/1.75兆個)
このような感じで各フェーズで使用される、ベクトルの要素数があります。今回は615,547,736個でした。各フェーズで現れるこのようなモデル自体が持つベクトルの要素数の和が総パラメーター数なのです。入力から変換されるベクトルの要素数は関係ありません。イメージが湧いてきましたか?
この段階でかなり理解が進んだのではないでしょうか?

色々すっ飛ばして最後の出力

今まで「入力された文章をどのようにして数値にして、モデル内で扱えるようにするのか」を説明しました。ここからtransformerの核となる部分に入っていくのですが、理解のために色々すっ飛ばして最終的に今取得した数値をどうするのかを説明します。
次の流れではこの数値を次回以降説明する方法で計算し、更新していきます。そして 何回も計算し終わった後の最後の出力を特別な方法で計算して、先ほどの「語彙集の中のトークンごと」に「次にそのトークンが現れる可能性」を取得します。概要の部分の「4」の部分です。(3の計算結果を踏まえて、次に来る確率が高いトークンをモデルの語彙週から選ぶ) イメージで言うと、[燃やす: 0.01, バナナ: 0.0000000001, 食べる: 0.7, あげる: 0.2…]のような感じです。そしてこの例では「食べる」の可能性が一番高いので「食べる」を選択し、「私はみかんを」の次に出力します。次にまた「私はみかんを食べる」を分割し、同じことを繰り返していく。というのが全体の流れになります。かなり説明したので今回はこれで終わりにします。

次回

次回以降は、今回理解のためにすっ飛ばした、入力の数値を更新していく方法、出力の計算方法について説明していきます。

  1. 今回、ベクトルに変換したデータを使ってお互いのトークンがどのように関係しているのかを計算する仕組みを解説

  2. 計算結果を用いて、どうやって次に来る単語生成しているのかを解説

これらを理解するために、今回の大規模言語モデルが実際に行っていることの流れと最初の段階の仕組みを把握しておくことはとても重要になります。
忘れないためにおさらいです。

  • 大規模言語モデルとは、ニューラルネットワークの一種

  • 大量のデータとパラメーターで学習される

  • ChatGPTは次にくる確率が最も高い単語を選び続けることで文章を生成している

  • 入力は、Embeddingという方法を使ってベクトルに変換される

  • 古いモデルのGPT3ですら、Embeddingの次元数は12248次元、モデルが持つトークンの数は50257個でEmbeddingのパラメーター数は12248 ✖️ 50257

  • このベクトルを使ってTransformerの次の層で色々計算がされる


次回、このベクトルを使ってお互いのトークンがどのように関係しているのかを計算する理由、仕組みについて解説していきます!

資料


引用

[1]Attention is All You Need,

[2] Language Models are Few-Shot Learners,

https://arxiv.org/pdf/2005.14165

ソースコード

[1]

import matplotlib.pyplot as plt
import numpy as np


vector_mikan = np.array([3.0, 4.0])
vector_orange = np.array([3.0, 4.5])
fig, ax = plt.subplots()
origin = np.array([0, 0])
ax.quiver(*origin, *vector_mikan, angles='xy', scale_units='xy', scale=1, color='orange', label='Mikan')
ax.quiver(*origin, *vector_orange, angles='xy', scale_units='xy', scale=1, color='red', label='Orange')
ax.text(*vector_mikan, 'Mikan', fontsize=12, ha='right')
ax.text(*vector_orange, 'Orange', fontsize=12, ha='right')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_title('Fruit Vectors')
ax.legend()
ax.set_aspect('equal')

plt.grid(True)
plt.xlim(0, 5)
plt.ylim(0, 5)

plt.show()

[2]

from openai import OpenAI
import os
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())

API_KEY = os.environ.get("OPENAI_KEY")


words = ["みかん", "オレンジ", "果物", "正月"]
client = OpenAI(
    api_key=API_KEY,
)

def get_embedding(word):
    response = client.embeddings.create(
        input=word,
        model="text-embedding-ada-002"
    )
    return response.data[0].embedding

embeddings = {word: get_embedding(word) for word in words}
print(embeddings)

[3]

from sklearn.metrics.pairwise import cosine_similarity
import seaborn as sns


embedding_vectors = np.array([embeddings[word] for word in words])

similarity_matrix = cosine_similarity(embedding_vectors)

words=['Mikan', 'Orange', 'Fruit', 'New Year']
plt.figure(figsize=(8, 6))
sns.heatmap(similarity_matrix, xticklabels=words, yticklabels=words, annot=True, cmap='coolwarm')
plt.title('Cosine Similarity between Word Embeddings')
plt.show()

この記事が気に入ったらサポートをしてみませんか?