\[ % 定義 \newcommand{\argmin}{\mathop{\rm arg~min}\limits} \]

1 本講義の目的

  • ニューラルネットワークの基礎を学ぶ
  • 自然言語処理の概要を学ぶ
  • 大規模言語モデルの概要と技術的な特性について学ぶ

2 ニューラルネットワーク

2.1 生体ニューロン

生物の脳神経には多数のニューロン(神経細胞)があり、それらが結合してネットワークを形成している。生物のニューロン(生体ニューロン)ではニューロンの樹状突起が他のニューロンの軸索末端から信号(神経伝達物質を受容することで発生する電気信号)を受け取り、閾値を超える電気信号を受け取ると別のニューロンに信号を送るような仕組みになっている。

ニューロン間の結合部(シナプス)の結合の強さ(神経伝達物質の放出量など)はそれぞれ異なり、よく使う神経回路はシナプス結合が強くなり、そうすることで学習が進行すると考えられている。


出所: http://nkdkccmbr.hateblo.jp/entry/2016/10/06/222245

2.2 形式ニューロンとパーセプトロン

こうしたニューロンの仕組みを数理モデルにしたものが形式ニューロン(formal neuron)と呼ばれるモデルである。 入力\(x_1, x_2, \dots, x_m\)に重み\(w_1, w_2, \dots, w_m\)を乗じて定数項\(b\)をつけた総和\(u = \sum_{j = 1}^m x_j w_j + b\)活性化関数(activation function)と呼ばれる関数\(h\)に通したものを出力\(z\)とする。

\[ z = h(u) = h \left(\sum_{j = 1}^m x_j w_j + b \right) \]

この計算を図にしたものが次の図である。

活性化関数は生体ニューロンにおいて総入力がある閾値を超えたときに信号を出力することを再現する関数であり、伝統的には階段関数(step function)

\[ h(x) = \begin{cases} 0 & (x \leq 0)\\ 1 & (x > 0) \end{cases} \]

シグモイド関数(sigmoid function)

\[ h(x) = \frac{1}{1 + \exp(-x)} \]

が用いられてきた。 近年はReLU(rectified linear unit)

\[ h(x) = \max(0, x) \]

が用いられる事が多い。 これらの関数を示したものが次の図である。

形式ニューロンはモデルとしての構造のみの存在であり、重みを更新する仕組みまでは存在しなかった。 後に重みの更新方法が提案され、そちらはパーセプトロン(perceptron)あるいは単純パーセプトロンと呼ばれる。

パーセプトロンにおいて、重み\(w_i\)とバイアス\(b\)はデータから学習させる。その際にはまず重みの初期値をランダムに設定し、その後は誤差を下げる方向に重みの更新を繰り返していくように学習していく。

2.3 多層パーセプトロン

パーセプトロンの出力を別のパーセプトロンの入力とすることで多層化したものを多層パーセプトロン(multilayer perceptron: MLP)あるいはニューラルネットワーク(neural network)と呼ぶ。

2.3.1 多層パーセプトロンの識別境界

単純パーセプトロンは線形モデルであるため、線形分離不可能なデータをうまく分類することができない。 しかし多層パーセプトロンは非線形モデルとなり、線形分離不可能なデータも分類することができる。

次の図は典型的な線形分離不可能問題のデータに対して単純パーセプトロンと多層パーセプトロンを用いて学習させた場合の識別境界である。なお、多層パーセプトロンは中間層を1層、中間層のユニット数(形式ニューロンの数)は3個の構造にした。

2.4 Rで実践

2.4.1 パッケージの読み込み

# 準備
pacman::p_load(
  tidyverse, # dplyr, tibbleなどのパッケージ群の読み込み
  MLmetrics, # 予測精度の評価指標
  carData,   # TitanicSurvivalデータセットのため
  nnet,      # 小規模なニューラルネットワークを扱うライブラリ
  NeuralNetTools  # ニューラルネットワークの可視化
)

2.4.2 データの準備

{carData}パッケージに含まれるタイタニック号の乗客データTitanicSurvivalを使う。

# データ読み込み
data("TitanicSurvival")
head(TitanicSurvival)
##                                 survived    sex     age passengerClass
## Allen, Miss. Elisabeth Walton        yes female 29.0000            1st
## Allison, Master. Hudson Trevor       yes   male  0.9167            1st
## Allison, Miss. Helen Loraine          no female  2.0000            1st
## Allison, Mr. Hudson Joshua Crei       no   male 30.0000            1st
## Allison, Mrs. Hudson J C (Bessi       no female 25.0000            1st
## Anderson, Mr. Harry                  yes   male 48.0000            1st

このデータセットは1912年のタイタニック号の沈没事故の乗客の生死に関するデータで、次の変数が含まれている

  • survived:生存したかどうか
  • sex:性別
  • age:年齢(1歳に満たない幼児は小数)。263の欠損値を含む。
  • passengerClass:船室の等級

このデータセットには欠損値が含まれているため、まず欠損値を除去する

# NA(欠損値)を含む行を削除
titanic <- na.omit(TitanicSurvival)

そしてデータを学習用・テスト用に分割する。

# ID列を追加
df <- titanic %>% rownames_to_column("ID")

# 80%を学習用データに
set.seed(0)
train <- df %>% sample_frac(size = 0.8)

# 学習用データに使っていないIDの行をテスト用データに
test <- anti_join(df, train, by = "ID")

# ID列は予測に使わないため削除しておく
train <- train %>% select(-ID)
test <- test %>% select(-ID)

2.4.3 ニューラルネットワークの学習

中間層が1層のニューラルネットワークは{nnet}パッケージで実行することができる。

titanic_nnet <- nnet(
  formula = survived ~ .,
  data = train,
  size = 2
)
## # weights:  13
## initial  value 571.600588 
## iter  10 value 553.246284
## iter  20 value 428.313214
## iter  30 value 366.785430
## iter  40 value 358.696647
## iter  50 value 355.584141
## iter  60 value 355.396669
## iter  70 value 353.799465
## iter  80 value 353.140574
## final  value 353.034506 
## converged

引数に指定しているsizeは中間層のユニット数(形式ニューロンの数)である。 なお出力層の活性化関数はデフォルトではシグモイド関数である。

{NeuralNetTools}パッケージを使用するとnnetのネットワーク構造を図にすることができる。

NeuralNetTools::plotnet(titanic_nnet)

Iは入力層(input)、Bは定数項(bias)、Hは中間層(hidden; 隠れ層)、Oは出力層(output)である。 線の色は重みの正負を表しており、黒は正の値、灰色は負の値である。線の太さは重みの相対的な大きさを表している。

テストデータでの正解率は以下のように計算できる。

# 予測
y_pred <- predict(titanic_nnet, test, type = "class")

# 混同行列
table(test$survived, y_pred)
##      y_pred
##        no yes
##   no  111   4
##   yes  45  49
# 正解率
Accuracy(y_pred = y_pred, y_true = test$survived)
## [1] 0.7655502

3 DNN

中間層が2層以上のニューラルネットワークをDNN(deep neural network)ディープラーニング(deep learning)と呼ぶ。 ニューラルネットワークの層をさらに重ねることでモデルの表現力がさらに向上し、より複雑なデータに対応できるようになる。

DNNの研究は過学習の問題や勾配消失問題と呼ばれる予測誤差の情報が層を経て極端に小さくあるいは大きくなって消える現象に苦しむこととなった。 しかし、

  • 勾配の消失が起きにくい活性化関数であるReLUの登場
  • 正則化手法の発展(ランダムに一部のニューロンの出力を0に固定して無効化することで過学習を回避するドロップアウトなど)
  • パラメータ最適化に用いる勾配法の改良手法の登場
  • 計算機の発展

などの要因により、DNNをランダムな初期値からでも学習することができるようになった。

3.1 Rによる実践

Rでは{h2o}パッケージや{torch}パッケージなどでDNNを実行することができる。

3.1.1 {h2o}パッケージによるDNNの実行

# パッケージの読み込み
pacman::p_load(
  tidyverse,
  MLmetrics,
  h2o
)

# h2oの仮想環境の起動
h2o.init()
# データをh2o用のデータ型に変換
train <- as.h2o(train)
test <- as.h2o(test)

# 1列目(目的変数)をfactor型に変換
train[,1] = h2o::as.factor(train[,1])
test[,1] = h2o::as.factor(test[,1])

# trainデータを学習用のものと検証用のものに分ける
splits <- h2o.splitFrame(train, ratios = 0.8, seed = 0)

h2o.deeplearning()でネットワーク構造の定義と学習を行う。

# 学習
model <- h2o.deeplearning(
  x = 2:ncol(train), # 特徴量の列番号を指定
  y = 1,             # 目的変数の列番号を指定
  training_frame = splits[[1]],   # 訓練データを指定
  validation_frame = splits[[2]], # 検証データを指定(学習には使わず、精度を測るためだけに使う)
  activation = c("RectifierWithDropout"), # 活性化関数を指定
  loss = c("CrossEntropy"), # 誤差関数をCrossEntropy(二項分布の負の対数尤度)へ
  hidden = c(3, 3, 3), # 中間層(隠れ層)のサイズ
  epochs = 1000,       # エポック数。学習データ何回分の学習を反復させて重みを更新していくか。
  hidden_dropout_ratios = c(0.5, 0.5, 0.5), # 各中間層においてdropoutするユニットの割合
  seed = 0
)

plot()を使うとエポック(訓練データを何回分使って重みの更新を反復させたか)ごとの予測誤差の推移を可視化することができる。(今回はepochsを小さな値にしているが、実際のデータ分析では「epochsを十分大きな値にしておいて、エポックを増やしてもvalidationデータに対する予測誤差が改善しなくなったら学習を打ち切る」という戦略(early stopping)を取ることも多い)

# 予測誤差の推移
plot(model)

テストデータでの予測を行う。

# 予測
pred <- h2o.predict(model, test)

予測結果はpredict列に予測したラベルの値が入っており、残りの列にはそのレコードが目的変数の各クラスに属する確率を推定したものが入っている。

# 予測結果
pred
##   predict        no       yes
## 1      no 0.5165181 0.4834819
## 2      no 0.5849575 0.4150425
## 3     yes 0.1935893 0.8064107
## 4     yes 0.1935893 0.8064107
## 5      no 0.4575902 0.5424098
## 6      no 0.5490376 0.4509624
## 
## [209 rows x 3 columns]
y_true <- test$survived %>% as.vector()
y_true <- 1 * (y_true == "yes")
p_pred <- pred$yes %>% as.vector()
y_pred <- 1 * (p_pred >= 0.5)

# ROC-AUC
AUC(p_pred, y_true)
## [1] 0.8296947
# 正解率
Accuracy(y_pred, y_true)
## [1] 0.7703349
# 混同行列
table(y_true, y_pred)
##       y_pred
## y_true   0   1
##      0 106   9
##      1  39  55
# 混同行列(h2oパッケージの関数を使う場合)
h2o.confusionMatrix(model, test)
## Confusion Matrix (vertical: actual; across: predicted)  for max f1 @ threshold = 0.446796652969441:
##         no yes    Error     Rate
## no      90  25 0.217391  =25/115
## yes     18  76 0.191489   =18/94
## Totals 108 101 0.205742  =43/209
# h2oの仮想環境の終了
h2o.shutdown(prompt = FALSE)

(参考)A Neural Network Playground

Tensorflow(Googleが開発したディープラーニングのライブラリ)のA Neural Network Playgroundというサイトでは、いくつかのサンプルデータに対するディープラーニングの挙動を簡単に確認することができる。


出所:http://playground.tensorflow.org/

非公式の日本語版サイトも存在する。

4 自然言語処理

ディープラーニングは日本語や英語などの自然言語(natural language)の分析や画像の分析を中心として活用されている。 今回は自然言語データの分析(自然言語処理 natural language processing: NLP)を例にとって説明を行うことにする。

この節では自然言語処理の概要や、自然言語をコンピュータで扱う(計算可能にする)ためにどのように前処理を行うのかを概説する。

4.1 自然言語処理のタスク

自然言語処理の分野では、例えば次のようなタスクを行っていく。

4.2 言語モデル

自然言語処理においては、「文章が生成される確率」を推定するモデルを構築することを通じて自然言語処理モデルを構築することが多い。 この文章が生成される確率のモデルを言語モデル(language model)という。

言語モデルでは、もっともらしい(確率的にありえる)文章の確率が高く評価されるように学習したい。例えば次のような評価をしたい。

\[ P(昨日は雨が降りました。) > P(昨日は飴が降りました。) \]

文章を単語単位に切り分けて考えると、文章の確率は単語の同時確率として扱うことができる

\[ P(昨日, は, 雨, が, 降り, まし, た, 。) \]

記号にすると、ある文章\(S\)を単語に切り分けたものを\((w_1, w_2, \cdots, w_T)\)とすると、

\[ P(S)=P(w_1, w_2, \cdots, w_T) \]

を求めたいということになる。この同時確率\(P(w_1, w_2, \cdots, w_n)\)は条件付き確率の積として表すことができる。

\[ \begin{align} P(w_1, w_2, \cdots, w_T) &= P(w_1) \times P(w_2|w_1) \times P(w_3|w_1, w_2) \times \cdots \times p(w_T|w_1, \dots, w_{T-1})\\ &= \prod_{t=1}^T P(w_t|w_1, \dots, w_{t-1}) \end{align} \]

\(w_t\)より前の単語列\(w_1,w_2,\cdots,w_{t-1}\)文脈(context)と呼ばれる。 よって言語モデルの学習は「文脈(これまで出た単語)をもとに次の単語を予測する問題」と捉えることもできる。

(参考)条件付き確率と同時確率

\[ \underbrace{ P(A | B) }_{条件付き確率} = \frac{ \overbrace{ P(A, B) }^{同時確率} }{P(B)} \hspace{1em} \to \underbrace{ P(A, B) }_{ 同時確率 } = \underbrace{ P(A | B) }_{条件付き確率} P(B) \]

4.3 トークン化

上記の文章を切り分ける処理をトークン化(tokenization)といい、切り分けられた要素をトークン(token)と呼ぶ (上記の例では単語単位に分割したが、文字単位など他の分割方法も存在する)。

単語単位に分割する場合、英語のように単語と単語の間にスペースがある言語の場合はスペースを頼りに分割すればよい。 しかし日本語のようにスペースが入らない言語の場合は形態素解析という処理を行い、どの文字からどの文字までが1つの単語を構成するのかを判定して分割していく必要がある。

形態素解析器のひとつがMeCab(めかぶ)である。MeCabを別途パソコンにインストールした状態で{RMeCab}パッケージを使用すると、次のようにRからMeCabを呼び出して文章を分割することができる。

# (参考)RMeCabパッケージによる形態素解析の例
# (注)MeCabを予めインストールしておく必要がある
install.packages("RMeCab", repos = "http://rmecab.jp/R")
library(RMeCab)

res <- RMeCabC("すもももももももものうち")
unlist(res)
    名詞     助詞     名詞     助詞     名詞     助詞     名詞 
 "すもも"     "も"   "もも"     "も"   "もも"     "の"   "うち" 
(参考)RMeCabを使ってみたい方へ

WindowsでRMeCabを使う際は文字化けに悩まされる可能性が高いため注意点を述べておく。

Rはver.4.2.0以降、UTF-8という文字コードがデフォルトとなった。 Windows版のMeCabインストールする際、Shift-JISという文字コードがデフォルトになっているが、MeCabをUTF-8でインストールしないとRMeCabを使用した際に結果が文字化けするので注意。 2023年現在の大学PCにはShift-JIS版のMeCabがインストールされているため、上記のRMeCabのコードは文字化けする。

また64bit/32bitの違いもあるため、ikegami-yukino/mecabから.exeファイル(例えばmecab-64-0.996.2.exeをダウンロードし、インストールの際にUTF-8を選ぶ方法が(2023年7月現在としては)良いようである。

(参考:RMeCabパッケージ作者によるアナウンス

4.4 単語のベクトル化

続いて、トークン列を計算できるように数値に変えていく。

最もシンプルな方法は、各単語を特徴量の各次元と対応させて、入力\(\boldsymbol{x}_i\)が単語\(w_t\)であることを\(\{0, 1\}\)で表現するベクトルを用いる方法である。 例えば、データセット中の語彙が\((昨日, は, 雨, が, 降り, まし, た, 。)\)のみだったとしたとき、「雨」という単語を

\[ \boldsymbol{x}_i = (0, 0, 1, 0, 0, 0, 0 ,0) \] と表すということである。

この方法は語彙数が多くなるとベクトルの次元が非常に大きくなってしまい、効率的ではない。 そこで密なベクトル(要素に\(\{0, 1\}\)ではなく実数をとるベクトル)にして、そのぶん次元数を圧縮する方法がとられることが多い(このような単語ベクトルを単語埋め込み word embeddingや単語の分散表現 distributed representationなどと呼ぶ)。

代表的な方法はWord2Vecで、この方法は例えば単語のベクトル\(\boldsymbol{x}_t\)をその周辺の単語のベクトル(例えば\(\boldsymbol{x}_{t-2},\boldsymbol{x}_{t-1},\boldsymbol{x}_{t+1},\boldsymbol{x}_{t+2}\))で予測するようなDNNを構成し、学習したDNNの重みを単語のベクトルとするものである。重みなので実数ベクトルとなり、隠れ層のユニット数を語彙数より小さくすることで次元数を削減することができる。

{word2vec}パッケージを使うとword2vecを簡単に実行することができる。

pacman::p_load(word2vec, text)

# textパッケージのデータセット。人生への満足感についての記述
data(Language_based_assessment_data_8)
texts <- Language_based_assessment_data_8$satisfactiontexts
texts <- texts %>% tolower() # 大文字を小文字へ変換

set.seed(0)
model <- word2vec(texts, dim = 100, window = 5)  # モデルの学習
embeddings <- as.matrix(model) # 単語埋め込みを取得
head(embeddings[,1:5])  # 単語埋め込み
##                [,1]        [,2]        [,3]      [,4]     [,5]
## take      0.1829648  0.64496827  0.01384262 0.7685602 1.576865
## good      0.6291782 -0.05840264  0.35882193 1.2132641 1.588341
## doing     1.0854373  0.07898576  0.15555817 1.5020984 1.754062
## important 0.6967747 -0.10525345  0.19544099 1.4014169 1.661775
## any       0.1786060  0.49489298 -0.41990405 1.1899219 1.756150
## most      0.4981314  0.35843799 -0.13668765 0.9693074 1.555990
# ある単語に類似した単語のtop5
# (データが少なくモデルの精度が悪いため、納得感のある結果にはならない)
e <- embeddings[c("happy"),]
predict(model, e, top_n = 5)
##   term similarity rank
## 1    a  0.9821509    1
## 2   to  0.9818172    2
## 3  and  0.9814864    3
## 4   me  0.9814132    4
## 5    i  0.9812102    5

(参考)日本語の単語ベクトルの例

朝日新聞単語ベクトル:朝日新聞社メディア研究開発センター 人工知能研究の取り組み

23億単語の記事データを使ってWord2Vec等を使った単語埋め込みの例。 単語の類似度だけでなく「という単語からを引いてを足すと女王になる」といった演算もうまくいっている

5 RNN

自然言語処理の分野で多く使われてきたニューラルネットワークのアーキテクチャのひとつがRNNとその派生手法たちで、近年それらに代わってよく用いられるものがTransformerである。順に概説していく。

5.1 RNNの構造

言語モデルを学習する際、文脈\(w_1,w_2,\cdots,w_{t-1}\)をもとに次の単語\(w_t\)を予測するような問題を解くことになるわけだが、これは時点\(1, 2, \dots, t-1\)のデータから\(t\)時点のデータを予測するような時系列予測問題のような構造になっている。

こうした系列性をうまく表現するようなネットワークの構造(アーキテクチャ)がRNN(recurrent neural network)である。


図:RNNの模式図。右の図は左の図を展開した表記で、表しているものは同じである。(出所:筆者作成)

RNNは\(t\)期の入力だけでなく\(1, 2, \dots, t-1\)期の入力も用いて\(t\)期の出力を行うモデルである。

\(t\)期目の中間層の\(j\)番目のユニットの出力値は、入力\(x^{(t)}\)と入力層の重み\(w^{(in)}\)だけでなく、1期前の\(t-1\)期の中間層の出力\(h^{(t-1)}\)と重み\(w^{(hidden)}\)を使って次のように計算される(\(f\)は活性化関数)。

\[ h^{(t)}_j = f\left( \sum^{N(in)}_{i=1} w^{(in)}_{ij} x_j + \sum^{N(hidden)}_{i=1} w^{(hidden)}_{ij} h_j^{(t-1)} + b_j^{(f)} \right) \]

RNNの活性化関数には双曲線正接(hyperbolic tangent: tanh)関数

\[ \text{tanh}(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}} \]

が使われることが多い。\(tanh\)はシグモイド関数と似ているが、出力値が0から1ではなく-1から1の範囲になる点が異なる

出力値は関数\(g\)を用いて次のように計算される

\[ y^{(t)}_j = g \left( \sum^{N(out)}_{i=1} w^{(out)}_{ij} h_j^{(t)} + b^{(g)}_j \right) \]

出力層の関数\(g\)は目的変数に応じて異なる。例えば2クラス分類であればシグモイド関数だが、単語予測のように多クラス分類ならシグモイド関数を多クラス化したソフトマックス(softmax)関数

\[ \text{softmax}(\boldsymbol{x})_i = \frac{\exp(x_i)}{\sum_{k=1}^{K} \exp (x_k)} \]

を用いる。これは入力値\(\boldsymbol{x}=(x_1, x_2, \dots, x_K)^\top\)を0から1の値かつ全クラスの合計が1になるように(つまり「クラス\(i\)の確率」として)変換する関数である。

x = c(-1, 3, 2)      # 生の予測値:各クラスの予測値としてのふさわしさ
exp(x) / sum(exp(x)) # softmaxで正規化した各クラスの値
## [1] 0.01321289 0.72139918 0.26538793

なお、RNNのような自己回帰性のあるニューラルネットワークと対比して、多層パーセプトロンのような自己回帰性を持たないネットワークたちは順伝播型ニューラルネットワーク(feed-forward network)と呼ばれる。

5.2 RNNの課題

RNNは時刻数(文脈・トークン数)が長くなると勾配消失が起こりやすい。これは例えば長い文章を扱っているときに文脈の最初の方の単語がなんであったかを忘れてしまうようなものである。何も工夫しなければ10時刻程度で勾配が消失するという報告もあるため、極端に長い文章でなくてもこの問題は発生する。

勾配消失を抑制するための工夫を施したLSTM(long short-term memory)GRU(gated recurrent unit)といった手法も存在する。 しかし勾配消失の抑制にも限度があり、また並列計算(parallel computing; 複数の処理装置で同時並行で計算することで計算を高速化すること)に向いていないためモデルやデータの規模の増加が容易ではないという問題もある。

これらの課題を解消した方法が次に述べるTransformerである。

6 Transformer

Transformerはself-attentionという計算処理を用いることが特徴的な、ディープラーニングのアーキテクチャ(ネットワーク構造)である。

TransformerはChatGPTの内部で使われる言語モデル(GPTシリーズ)やGoogleのBERTなど様々な言語モデルにおいて用いられており、画像処理の分野でも利用されている。

6.1 Transformerの構造

理解しやすさのため、比較的シンプルな構造をしているGPTシリーズのTransformerを例にとって説明していく。 Transformerは次の図のような構造になっている。


図:GPT-1で用いられたTransformer (出所:Radford et al. (2018)

各パーツを下から順に説明していくと、

Text & Position Embed」は単語の埋め込みと位置情報の埋め込みを行う層である。Attentionは系列性を考慮せず、入力\(x_t\)\(x_{t'}\)の順番を入れ替えたとしても出力\(y_t\)\(y_{t'}\)が入れ替わるだけである。しかし言語モデルを学習させるにあたって順序の情報を考慮してほしいため、位置情報を渡すための工夫がなされている。

Masked Multi Self Attention」はAttentionを使っている層で、Transformerにおいて非常に重要な層である。詳しくは後述する。

Layer Norm」は学習中などに極端に大きい値にならないようにベクトルの各要素を正規化する(平均を引き標準偏差で割る)処理である。

Feed Forward」は2層のニューラルネットワーク(多層パーセプトロン)である。ただし、入力の次元数よりも中間層の次元数(ユニット数)を大きくすることが多い。

6.2 Attention

Attentionは入力の単語埋め込みに対して文脈の情報を付与していく仕組みである。 入力の単語埋め込み\(\boldsymbol{x}\)を、クエリ\(\boldsymbol{q}\)、キー\(\boldsymbol{k}\)、バリュー\(\boldsymbol{v}\)という3つのベクトルやsoftmax関数で計算して、文脈の情報を追加したベクトル\(\boldsymbol{x}'\)を構築していく。


attentionの概略図

なお、\(\boldsymbol{q}, \boldsymbol{k}, \boldsymbol{v}\)は入力の単語埋め込み\(\boldsymbol{x}\)に重みを掛けて計算された\(d\)次元ベクトルで、その重みも学習によって決定される。

Attentionは次のような流れで計算されていく。 まず、\(i\)番目のトークンのクエリ\(\boldsymbol{q}_i\)\(j\)番目のトークンのキー\(\boldsymbol{k}_j\)を使って、\(i\)番目のトークンから見た\(j\)番目のトークンとの関連性のスコア\(s_{ij}\)を次のように計算する。

\[ s_{ij} = \frac{ \sum_{m=1}^d q_{im} k_{jm} }{ \sqrt{d} } \]

分子の計算は線形代数において内積と呼ばれる計算で、2つのベクトル間の類似度の計算に使われることもある計算である。\(\sqrt{d}\)で割っているのは内積が大きすぎる値になって学習が不安定になることを防ぐためである。

(参考)内積とコサイン類似度

2つの\(d\)次元ベクトル\(\boldsymbol{a, b}\)があるとき、その内積\(a \cdot b = \sum_{i=1}^d a_i b_i\)とベクトルの長さ(ノルム)\(\|a\|=\sqrt{ \sum_{i=1}^d |a_i|^2 }\)から計算される

\[ \cos(\boldsymbol{a}, \boldsymbol{b}) = \frac{ \boldsymbol{a} \cdot \boldsymbol{b} }{ \|\boldsymbol{a}\| \|\boldsymbol{b}\| } = \frac{ \sum^d_{i=1} a_i b_i } { \sqrt{ \sum^d_{i=1} |a_i|^2 } \sqrt{ \sum^d_{i=1} |b_i|^2 } } \]

コサイン類似度(cosine similarity)という。コサイン類似度は-1から1の値をとり、2つのベクトルの向きが近いほど高くなる。

続いて、関連性のスコア\(s_{ij}\)のベクトル\(\boldsymbol{s}_i = (s_{i1}, s_{i2}, \dots, s_{in})^\top\)\(n\)は入力トークン数)をsoftmax関数にかけて、合計が1になるように正規化する。

\[ \begin{align} \boldsymbol{a}_i &= \text{softmax}( \boldsymbol{s}_i ) \\ &= \left( \frac{ \exp(s_{i1}) }{ \sum^n_{j = 1} \exp(s_{ij}) }, \dots, \frac{ \exp(s_{in}) }{ \sum^n_{j = 1} \exp(s_{ij}) } \right)^\top \end{align} \]

最終的な出力\(\boldsymbol{x}'_i\)はこの重み\(\boldsymbol{a}_i\)でバリュー\(\boldsymbol{v}_j\)を重み付き和にして得られる

\[ \boldsymbol{x}'_i = a_1 \boldsymbol{v}_1 + a_2 \boldsymbol{v}_2 + \cdots + a_n \boldsymbol{v}_n \]

(参考)softmaxの働きについて

プログラミングにおいて、連想配列というデータ構造やキー・バリュー・ストアというデータベースがある。Rだと名前付きのlistがそれに相当し、名前がキー、値がバリューである。

kvs <- list(昨日=0.2, は=0.1, 雨=0.7, が=0.1, 降った=0.1)
kvs["雨"]
## $雨
## [1] 0.7

attentionはsoftmaxを使うことでキー・バリュー・ストアをsoftに実装しているものと解釈することができる。 詳しくは以下で述べていく。

softmaxと似た関数にargmaxがある。 argmaxは、入力のベクトルのうち最も値が高い要素が1と出力され、それ以外の要素は全部0と出力される。

例えばベクトル\(s\)

\[ \boldsymbol{s} = (1, 3, 5, 2, 0)^\top \]

だったとすると、ベクトル\(a\)は次のように計算される

\[ \begin{align} \boldsymbol{a}^{soft} &= \text{softmax}(\boldsymbol{s}) = (0.02, 0.11, 0.83, 0.04, 0.01)^\top \\ \boldsymbol{a}^{arg} &= \text{argmax}(\boldsymbol{s}) = (0, 0, 1, 0, 0)^\top \end{align} \]

別のベクトル\(\boldsymbol{v}=(v_1, v_2, v_3, v_4, v_5)\)と内積をとったとき、argmaxのほうは内積をとったベクトルの1つの要素だけを取り出す操作に相当し、キー・バリュー・ストアと同様のふるまいになる。

\[ \begin{align} \sum_i a^{soft}_i v_i &= 0.02 v_1 + 0.11 v_2 + 0.83 v_3 + 0.04 v_4 + 0.01 v_5 \\ \sum_i a^{arg}_i v_i &= v_3 \end{align} \]

一般的なattentionはsoftmaxを使用するため、「attentionはキー・バリュー・ストアをsoftに実装しているもの」(「取り出されるかどうか」の\(\{0, 1\}\)ではなく実数値で重みをつけて取り出すもの)と解釈することができる

なお、「Masked Multi Self Attention」の「Masked Multi Self」の意味については次の通りである

  • 「Masked」は未来の時点の情報を見せないように覆い隠す(要素をゼロで埋める)処理である
  • 「Multi」はこのattentionが複数ある(multi-head attention)ことを示す。複数のAttentionを動かし、複数の観点から文脈情報を付与していく。
  • 「Self」は、もともとattentionは2つのデータソースからの類似度を計算するものだったが、Transformerにおいては自身から計算したクエリとキーの類似度を計算しているself-attentionを採用しているためである

また、RNN系の問題は次のように解消されている

  1. 長期記憶の保持(勾配消失)の問題:attentionはトークン系列同士を掛け合わせるため、時点(位置)が離れている入力同士も考慮される
  2. 並列計算の問題:multi-head部分は並列に計算できるため

7 大規模言語モデル

パラメータ数が極めて多い言語モデル(「多い」に明確な基準はないが例えば「100億パラメータ以上」など)を大規模言語モデル(large language model: LLM)と呼ぶ。

例えばOpenAIが開発したGPT-1からGPT-4までのモデルは次の表のような変遷を辿っており、GPT-3では1750億ものパラメータ数となっている。

モデル 公開年 パラメータ数 データサイズ
GPT-1 2018 1.2億 5GB
GPT-2 2019 15億 40GB
GPT-3 2020 1750億 570GB
GPT-4 2023 非公開 非公開

次の図は100億パラメータ以上の言語モデルを発表日順に並べたものである。巨大テック企業を中心に各社様々なモデルを開発している。


出所:Zhao et al. (2023). A survey of large language models.

ChatGPT、Bing AIなどの昨今の高精度な文章生成AIサービスは大規模言語モデルによって作られている。この章では大規模言語モデルの特徴や関連するトピックについて概観していく。

7.1 自己教師あり学習

大規模言語モデルが開発できた要因の1つに自己教師あり学習がある。

伝統的な自然言語処理においては教師あり学習を用いることが多かった。例えば機械翻訳のタスクにおいて、翻訳元の言語と翻訳先の言語(例えば英語とフランス語)のペアの文章を用意しておき、教師あり学習として学習させるような方法がとられていた。

GPTシリーズでは文脈から次のトークンを予測する問題を解く方法を用いて学習を行った。これは教師ラベルとペアとなる文章ではなく、通常の文章(例えばWikipediaの文章)を用意すればよいため、データの入手コストを大幅に削減し、データの規模を飛躍的に増加させることが可能になった。

このように教師ラベルがないデータの一部を教師ラベルとみなして教師あり学習を解く方法を自己教師あり学習(self-supervised learning)と呼ぶ。

7.2 スケーリング則

Transformerを用いた言語モデルの性能は

  1. 訓練に利用した計算量
  2. データセットのサイズ
  3. モデルのパラメータ数

に強く依存することが報告されている。 この法則性をscaling lawという(日本語ではスケーリング則べき乗則などと呼ばれる)。


図:モデルの規模を拡大していったときの予測誤差の推移。なお\(10^5\)は10万、\(10^7\)は1000万、\(10^9\)は10億である。
(出所:Kaplan et al. (2020). Scaling laws for neural language models.

通常の研究開発ではその研究がうまくいくかどうかの不確実性は高く、ただ単純に大金を投じて大規模なモデルを作ればいいとは限らない。 しかしTransformerを用いた生成モデルの研究においてはどの程度の金額を投資すればどの程度の性能のモデルを作れるのかが予想しやすくなっている。

scaling lawの存在もあってか、実際にLLMの開発競争は激化していき、大胆な投資もなされるようになった。 (開発競争の激化を避けるためにモデルの開発費は秘匿される事が多いが、例えばOpenAIのCEOサム・アルトマンはGPT-4の開発に1億ドル以上の費用が掛かったことを述べている)。

7.3 創発的能力

言語モデルの規模を大きくしていったときに新たな能力を獲得し、それまで解くことができなかった問題を解けるようになる現象が報告されている。 この現象は創発的能力(emergent abilities)創発(emergence)などと呼ばれる。

創発的能力のひとつはin-context learningという、自然言語によるその場限りの学習を行う能力である。

従来の言語モデルでは、事前学習しておいたモデルに対してタスクに応じた教師あり学習による追加的な学習(fine-tuning)を行ってから使用する方法が主流であった。例えばGPT-1では文脈からトークンを予測する問題を解くことで事前学習し、その後にテキスト分類や質問応答タスクのための教師あり学習を別途行っている。

一方でGPT-3では指示文(prompt)中に解きたいタスクの例を与えることで、そのタスクにおける正解率が向上する。 これは重みの更新を伴わないため、モデルは変更されずその場限りの学習になる。


図:in-context learningの例 (出所:Brown, et al. (2020).

図:与える例の数と予測精度の関係 (出所:Brown, et al. (2020).

GPT-3でもパラメータ数を縮小したものはこの能力を持たないため、この現象は規模に関係すると考えられている。 同様の創発現象はGPT-3以外の大規模言語モデルにおいても確認されている。


図:縦軸にfew-shotでの正解率、横軸に計算回数(浮動小数点演算数Floating-point OPerations: FLOPs)をとった図。左の図は多段階の四則演算をするタスク、中央の図は大学レベルの試験のタスク、右の図は文脈内の単語の意味を推定するタスクを表す。
(出所:Characterizing Emergent Phenomena in Large Language Models – Google Research Blog

こうした現象が起こる原因についてはいくつか仮説が提唱されているものの、解明中である。

7.4 人間のフィードバックからの強化学習

大規模言語モデルの主要な応用先のひとつがChatGPTをはじめとするAIチャットボットサービスである。 先述のようにin-context learningによってタスクに合わせた学習をその場で行い、高い精度の応答を見せるため、単なる喋り相手ではなくアシスタントのように使われることも多い。

GPT-3のような大規模言語モデルは言語モデルとして高いパフォーマンスを発揮したものの、チャットボットサービスに組み込むにあたっての課題もあり、改良がなされている。

例えばGPT-3は大規模言語モデルとして登場当初から評価が高かったものの、質問に応答するというタスクに対しての精度であったり、暴力的な内容(「爆弾の作り方を教えて」など)に対する回答の回避などといったチャットボットとして要求されるふるまいは身につけていないため、そのままチャットボットに組み込むことはできなかった。

GPT-3をチャットボット向けに改良したモデルの1つがInstructGPTである。


図:GPT-3とInstructGPTの比較。「以下のコードにおけるリストCの意図は?」という質問をしたとき、GPT-3では「C[i]の値を入れるため」といった直接的で説明として不適切な答えをしてしまうが、InstructGPTでは「二項係数の値を格納するため」などのより適切な回答を返している。 (出所:Ouyang et al. (2022)

InstructGPTは人間のフィードバックからの強化学習(reinforcement learning from human feedback: RLHF)という方法を用いて改良された。

RLHFは次のような流れで行う。

  1. プロンプトに対する望ましい出力を人間が例示し、教師あり学習としてGPTをfine-tuningする
  2. プロンプトに対するモデルの複数の出力を人間がランク付けし、その順序を報酬モデルを学習させる(教師あり学習)
  3. プロンプトに対する出力結果を報酬モデルによって評価し、モデルを更新する(強化学習)

図:InstructGPTの開発手法 (出所:Ouyang et al. (2022)

7.5 実際に触ってみる

7.5.1 (参考)textパッケージによる学習済みモデルの利用

{text}パッケージを用いてGPT-2などの学習済みLLMによる言語処理を体験することができる(インストール・推論にやや時間がかかるので注意)

例えばtextGeneration()関数でGPT-2を利用して"Hello, I'm"に続く文章を生成させると、次のようなそれっぽい文章が生成される

# (注)Posit Cloud無料枠ではスペック不足で動かない
pacman::p_load(text)

# Pythonの仮想環境をセットアップ
textrpp_install()
textrpp_initialize(save_profile = TRUE)

# 文章生成
generated_text <- textGeneration("Hello, I'm",
                                 model = "gpt2")
generated_text %>% as.vector()
$x_generated
[1] "Hello, I'm a new employee at Drexel University! Last Friday, I was an excellent choice for a professor teaching math and math courses at the college we currently offer at Georgetown. After making the move to a local university in California, I"

7.5.2 Hugging Face

Hugging Faceは大規模なAIのコミュニティであり、学習済みモデルやデータセットが公開されている。

学習済みモデルはPythonのTransformersパッケージからアクセスすることが可能で、実はRの{text}パッケージはTransformersパッケージをRから読み込んでいる。

Hugging Faceのウェブサイト上で学習済みモデルを動かすことができるInference APIという機能もある。

例えば株式会社ABEJAのabeja/gpt2-large-japaneseは日本語のデータを用いてGPT-2を学習したモデルである。Inference APIもあるためウェブサイト上で試すことができる。

8 文章生成モデルの課題と対策

8.1 Hallucination

文章生成をさせた際に利用者の意図と異なる文章を生成したり事実と異なる事を述べるといった不誠実なふるまいをする現象をHallucination(ハルシネーション;「幻覚」の意味)という。

例えば次の画像は立教大学についてChatGPTに尋ねたものであるが、随所に事実と異なる説明が混じっていることがわかる。


図:バックエンドにGPT-3.5(May 24 ver.)を利用したChatGPTによる回答(撮影日:2023年6月26日)

こうしたふるまいがHallucinationである。 (※ChatGPTは日本語に弱い。英語で質問すれば立教大学についても妥当な答えを返してくれた。)

8.2 プロンプト・エンジニアリング

大規模言語モデルは質問文(プロンプト prompt)の書き方に応じて生成結果が変わる。

プロンプトの書き方を工夫することによって大規模言語モデルの挙動を制御しようという試みをプロンプト・エンジニアリング(Prompt Engineering)という。

以下では代表的なプロンプト・エンジニアリングの例を述べる。

8.2.1 Few-shot

in-context learningを利用し、解かせたいタスクの例を先に示してあげることで生成される回答を制御する方法。


出所:Brown, et al. (2020). Language models are few-shot learners.

例を1つ示すことをone-shot prompting、複数例示することをfew-shot promptingという。 それらに対し、単純に質問文だけを書く方法はzero-shot promptingという。

8.2.2 Chain-of-Thought

Few-shotの際に解答例を単に答えだけにするのではなく、思考過程も含めてよりタスクを細分化し具体化している回答にしてやることで適切な答えを返しやすくする方法をchain of thought (CoT)という(図中の「Few-shot-CoT」)。

単に「Let’s think step by step」をつけるだけで正答率が上がることを利用したZero-shot CoTという派生手法も存在する。


出所:Kojima, et al. (2022). Large language models are zero-shot reasoners.

なお、CoTもin-context learningと同様に創発的能力のひとつであり、規模が大きいモデルでなければうまく機能しないことが知られている。

8.3 Retrieval Augumented Generation(RAG)

モデル内に学習されている知識は訓練時点での情報がベースとなるため、新しい情報に関する適切な返答を行うことは難しい。 例えば「今の日本の首相は?」と質問しても訓練時点での首相の名前が返答される。

学習に必要な計算量があまり大きくない機械学習モデルであれば、定期的にモデルを学習し直すことでこうした情報更新を行うことが可能である。 しかし、大規模言語モデルは1回の学習に要するコストが膨大であるため、モデルの再学習により情報を更新する方法は容易ではない。

このような情報の不足もHallucinationを引き起こす原因となりかねない。

こうした問題に対して、モデル内に学習された知識ではなくモデル外の知識を利用して回答文を生成する方法が考えられており、Retrieval Augumented Generation(RAG)あるいはRetrieval Augmentationと呼ばれる。

例えばMicrosoftのBing AIはウェブ上の記事を外部知識として利用し、脚注として回答結果にも付与することで回答内容の信頼性を向上させている。


図:Bing AIの結果画面

Hugging FaceにもWeb上で試すことができるDemoが存在し、RAGを使わない場合と使った場合の比較を簡単に行うことができる。


出所:Retrieval Augmented Generative QA - a Hugging Face Space by deepset

9 参考文献

書籍

  • 大関真之(2016)『機械学習入門 ボルツマン機械学習から深層学習まで』、オーム社。
  • 瀧雅人(2017)『これならわかる深層学習入門: 機械学習スタートアップシリーズ』、講談社。
  • 平井有三(2022)『はじめてのパターン認識 ディープラーニング編』、森北出版。

ウェブサイト & スライド