では、今回は2017年に論文「Attention Is All You Need」で提案された“Transformer”について詳しく解説したいと思います。

Transformerとは自然言語処理において、現時点ではもっとも成果を挙げているモデルであるBERTやXLNetで使われている重要な仕組みです。
ですので、自然言語処理を学ぶ、業務で使う人は是非押さえておきたいモデルです 。
モデルの構造は知っているので、どのように実装するかを知りたい、という方は以下の記事をご参照ください。
Tensorflowを使ってセンチメント分析用のTransformerを実装しています。
ご参考 – 参考書籍
こちらの本ではPyTorchを使ってTransformerとBERTのモデル部分を実装していますので、真似をしながら自分の手で実装することで、TransformerとBERTをしっかりと理解することができると思います。
自然言語処理については最後の方の一部だけですが、逆に言うと画像認識などの勉強もできて非常に参考になります。
PyTorchも学べるオススメの一冊です。
Transformerとは
まず、重要なので非常にざっくりとした概要を言っておきます。
計算速度向上のため、再帰的ニューラルネットワーク(RNN)を使わずに、Attentionメカニズムを使って並列計算を可能にするモデル
こちらのイメージを押さえていただいた上で、もう少し細かい話をしたいと思います。
Transformerは機械翻訳のためのモデルとして提案されました。
その背景としては、2014年に提案されたAttentionメカニズムがあります。
その論文では、LSTMやGRUといった再帰的ニューラル・ネットワーク(RNN)をベースとして、長期の依存関係を捉えるメカニズムとしてattentionメカニズムが提案されており、そのモデルは成功を収めました。
Attentionメカニズムについてはこちらの投稿をご参照ください。
しかしながら、RNNには問題がありました。
それは、実際に作ってみるとわかるのですが、RNNは計算時間が長いということです。
Seq2Seq、Encoder-Decoderと呼ばれる翻訳のモデルでは以下のようなRNNの仕組みを使いますが、時点2の隠れ層の値\(h^{<2>}\)を計算するためにはまず時点1の隠れ層\(h^{<1>}\)を計算しなければならず、 時点3の隠れ層の値\(h^{<3>}\) を計算するためには \(h^{<2>}\) を計算するという風に、前から順番に計算しなければなりません。

したがって、並列計算ができず、GPUなどを使っても、その能力をフルに活用できないのです。
特に長い文章であればあるほど、時間はどんどん長くなってしまいます。
そこで、RNNを使わない仕組みであるTransformerが考えられました。
この論文のタイトルは「Attention Is All You Need」ですが、これはRNNやCNNはいらずattentionだけがあれば十分である、という意味です。
そして、より少ない学習時間で、精度の高いモデルができました。
このTransformerによって、非常に巨大な言語データを学習することができるようになりました。
またRNNやCNNに比べて非常に自由度の高いモデルであることから、大量のデータを学習することにより、さらに性能が向上し、2018年のGPTやBERTなどが生まれることになります。
それでは、RNNを使わずに、どのように自然言語を処理するモデルを作るのか、を見ていきたいと思います。
Transformerの仕組み
では、Transformerを論文にしたがって説明していきます。
ニューラル機械翻訳では、まず、インプットの単語の列から文章の埋め込み表現を計算し(エンコード)、それを別の言語の単語の列をアウトプットします(デコード)。これをTransformerでは、RNNを使わず、Self-Attentionという仕組みを使って実現します。
Self-Attentionとは、attentionの実践のところで紹介しましたが、もともとの論文にあった 翻訳語の単語から翻訳前の単語に注意を向けるattentionではなく、自分自身のどこが重要かに注意を向けるものです。
つまり、文章中の単語の列に対して、そのタスクにはどの単語が重要なのかなぁ?といったことを学習していきます。
Self-Attentionについては、こちらをご参照ください。
ではまず、Transformerの全体像を見てみましょう。全体像は論文の図を借りるとこのようになっています。

図の左側が、エンコーダーであり、単語をインプットとして、文章の埋め込み表現(ベクトル)を求めます。
この文章の埋め込み表現は、デコーダーで翻訳する際に使われます。
そして、右側がデコーダーで、翻訳語の単語列をと埋め込まれた文章をインプットとして、次の単語を予測します。
例えば、以下の翻訳の例を考えてみましょう。
“私が昨日見た映画はすごくよかったです。” → “The movie I watched last night was very good.”
まず、日本語の文章をエンコーダーのインプットとして、埋め込み表現を計算します。
その際、例えば“映画”という単語を処理する場合、”私は”や“昨日”、“見た”という単語に注意を向けながら埋め込み表現を計算します。
注意を向けるというのはわかりにくいですが、この場合だと“映画”という単語との何等かの関連性を学習しているというイメージです。情報を共有していると考えても良いかもしれません。
そして、デコーダーでは逐次的に次の単語を予測していきます。
例えば、まずは“The”をインプットとして、次の単語を予測し、その次は“The movie”をインプットとして次の単語を予測します。
このままでは次に何が来るか全くわかりませんが、翻訳前の日本語の文章を手掛かりにします。
それがデコーダーのもう一つのインプットであるエンコーダーのアウトプットです。
日本語の文章では“私が昨日見た映画”とありますので、“The movie”の次は“I”が来るかな、と予測します。
このときもAttentionを使っており、“The movie”から日本語のどこに注意を向けるべきかや、“movie”は“The”に注意を向けるべきか(つまり前の単語を意識する)、というのを学習していきます。
ちょっと長くなるので、ここで一度簡単にまとめておきます。
- Transformerは通常のAttentionに加えて、Self-AttentionというAttentionメカニズムを使っている。
- Self-Attentionは、自分自身のどこが重要か?を学習する仕組み。
- エンコーダーで翻訳前の文章(例えば日本語の文章)について、どこが重要かや各単語の関連性を考えながら埋め込み表現を計算する。
- デコーダーで翻訳後の単語列をインプットとし、次の単語を予測する。
その際に、エンコーダーで埋め込まれた翻訳前の文章の埋め込み表現をインプットして、参考にしながら予測する。
つまり、自分自身の単語と翻訳前の単語を見比べながら、次の単語を予測するイメージ。

では、以下でより細かいパーツをひとつずつ見てみましょう。
Positional Encoding
まず、インプットとなる単語からembeddingレイヤーを通して、単語の埋め込み表現に変換します。
ただし、TransformerはRNNを使わないので単語の順番の概念がありません。
そこで、Positional Encodingにより単語の位置を考慮した埋め込み表現を作成します。
これによりRNNなどを使わずに位置情報を考慮することができます。

どのように位置情報を埋め込むかというと、以下の式でPositional Encodingを計算し、単語の埋め込み表現に足します。
$$\begin{align}
PE_{pos, 2i} &= \sin\left(pos/100000^{2i/d_{model}} \right) \\
PE_{pos, 2i+1} &= \sin\left(pos/100000^{2i/d_{model}} \right)
\end{align}$$
posというのが、単語列の何番目か?を表し、iが埋め込み表現の何番目の次元か?を表します。
このような方法でなくてもよいのですが、論文によると
$$PE_{pos+k, 2i}=\alpha PE_{pos, 2i} $$
というように、 \(PE_{pos+k, 2i}\) の値が \(PE_{pos, 2i}\)の線形関数になっていることから都合が良いとのことです。
上記の式を図にするとこのようになります。Tensorflowのtutorialより拝借しています。

これを単語の埋め込み表現に足すことにより、位置情報を考慮した単語の埋め込み表現を作成します。
Encoder
エンコーダーは、以下のように表されます。

まず、Multi-Head Attentionというattentionのレイヤー、それに続いて、Add & Normと書かれているのが、“残差結合(skip connection) + 正規化層”です。
残差結合
残差結合とは、画像認識で画期的な成果を残したResNetで使われた方法で、Multi-Head Attentionへのインプットと、 Multi-Head Attentionのアウトプットをインプットとして、足す方法です。
式で書くとこのようになります。
$$h(x)=x+f(x)$$
\(x\)をattentionへのインプットで\(f(x)\)がattentionのアウトプットです。
残差結合は次のレイヤーに\(h(x)\)を流します。
つまり、attentionのアウトプットは\(f(x)=h(x)-x\)となり、attentionは残差\(h(x)-x\)に合わせるようにパラメータを調整します。
attentionレイヤーの存在によりパフォーマンスが悪化するようであれば、attentionレイヤーのアウトプットをゼロにするように学習されるので、パフォーマンスの悪化を防ぎます。
そのため、レイヤーをたくさん追加してもパフォーマンスが悪化しにくいという利点があります。
レイヤー正規化(Layer Normalization)
そして、レイヤーの正規化(Layer Normalization)です。これは単にアウトプットの正規化を行うだけですので、詳細の解説は省略します。
バッチ正規化(Batch Normalization)の改良版と思っていただければ結構です。
詳細が知りたい方はこちらをご参照ください(Batch Normalizationがわかっていれば簡単に理解できると思います)。
この2つで1つのサブレイヤーが構成されています。
Feed Forwardレイヤー
次のサブレイヤーは、全結合層であるFeed Forwardレイヤーと、先ほどと同じように残差結合レイヤーとレイヤー正規化です。
Feed Forwardレイヤーは、2つのレイヤーからなり、1つ目のレイヤーの活性化関数はReLUになっています。
$$\text{FFN}(x)=\max\left(0, xW_1+b_1\right)W_2 + b_2$$
以上がエンコーダーですが、この仕組みを[(N_x\)回繰り返します。
論文では6回繰り返すことになっています。
ですので、インプットの次元とアウトプットの次元は揃える必要があり、論文では\(d_{\text{model}}=512\)とされています。
Decoder
では、次はデコーダーです。デコーダーはこのようになっています。

それほど大きくは変わりませんが、若干違います。
まず、初めがMasked Multi-Head Attentionレイヤーで、そのあと、残差結合と正規化です。
Masked Multi-Head Attentionはあとで説明しますが、先の単語を見ないようにマスクをかけたattentionです。
その次は、っまた同じMulti-Head Attentionからの残差結合と正規化のレイヤーですが、インプットは前の層のアウトプットと、左側から矢印が来ているエンコーダーのアウトプットになっています。
ここが“エンコーダーで埋め込まれた翻訳前の文章の埋め込み表現をインプットして、参考にしながら予測する”という部分です。
ここもポイントですので後程Attentionのところで説明します。
最後は、全結合層と残差結合・正規化層です。
Scaled Dot-Product Attention
attentionの考え方自体は以前の投稿で説明したattentionと同じなのですが、TransformerではScaled Dot-Product Attentionという仕組みを使います。
以前のAttentionの記事で説明したものはAdditive Attention(加法注意)と言われます。
TransformerのScaled Dot-Product Attentionではqueryとkey-valueのペアを使ってattentionを計算します。
Scaled Dot-Product Attentionは以下の図の形で、計算方法自体は極めてシンプルです。

Qがquery、Kがkey、Vがvalueを表しています。
qureryとは検索対象の単語で、key-valueがある意味答えになる単語のkeyとその値になります。
つまり、queryからkey-valueに注意を向けることになります。
前回のattentionでもそうでしたが、context vectorは、
$$\text{context vector} = \text{attention} \cdot \text{source}$$
で計算しました。
Transformerではkey-valueという仕組みを使うので若干違いますが、ほぼ前回と同様で、
$$\text{context vector} = \text{attention} \cdot \underline{\text{value}}$$
でcontext vectorを計算します。
ですので、考え方としては、valueが注意を向ける先で、attentionにvalueをかけることでvalueの注意による加重平均を取り、注意で加重平均された埋め込み表現を計算しています。
では、attentionの計算方法ですが、こちらになります。
$$Attention=\text{softmax}\left(\frac{QK^T}{\sqrt{d_{\text{model}}}}\right)V$$
attention weightはDot-Product Attentionというものを使い、\(QK^T\)で計算します。
\(QK^T\in \mathbb{R}^{length\times length}\)の意味ですが、これはqueryベクトルとkeyベクトルの内積を計算しており、これが大きいところが大きな注意を向けるところになります。
つまり、\(QK^T\)の\(i\)行目\(j\)列は、\(i\)番目の単語から\(j\)番目の単語に向ける注意の大きさになります。
この計算では、各queryから各のkeyに向ける注意を一発ですべて計算することができます。
アウトプットは\(QK^T\in \mathbb{R}^{length\times length}\)なので、Queryの各単語からKeyの各単語に向ける注意がすべて詰まっています。
そして、このDot-Product Attentionの方がAdditive Attentionよりも速く、効率的だとのことです。
ちなみに、前述の通り、QとKのサイズは“文章の長さ×\(d_k\)”なのですが、上記の行列計算をした結果は“文章の長さ×文章の長さ”になり、文章が長くなると非常にメモリを消費します。
ですので、例えば長文のセンチメント分析や文書分類をする場合は、これが大きなネックになってしまいます。
この問題を解消しようとしているのが“Reformer”です。
最後に、\(QK^T\)の要素の値が大きくなりすぎないように調整するために、QとKの次元である\(d_k\)の平方根\(\sqrt{d_k}\)で割ってやります。
\(V\)が上記のValueを表します。
Maskはざっくり言うと将来の情報を使わないようにマスキングするものですが、後程解説したいと思います。
Multi-Head Attention
Multi-Head Attentionは上記のattentionを複数並べることにより、精度の向上を図るものです。
head数をhとすると、query、key、valueに\(W_i^Q\)、\(W_i^K\)、\(W_i^V\), \(i=(1,2,\cdots, h)\)をそれぞれかけて、h個の\(Q_i\) 、\(K_i\) 、\(V_i\)を作ります。
そして、それぞれにScaled Dot-Product Attentionを適用することにより、h種類の注意を使います。
それにより、headが1つのattentionよりも精度が良くなるようです。
図でいうと、全体のインプットがQ、K、Vで、Linearというレイヤーで\(Q_i\) 、\(K_i\) 、\(V_i\) , \(i=(1,2,\cdots, h)\) を求めています。

式で書くと、
$$\begin{align}
\text{MultiHead}(Q, K, V) &= \text{Concat}\left(\text{head}_1, \cdots, \text{head}_h\right)W^O \\
\text{where } \text{head} _i &= \text{Attention}\left(QW_i^Q, KW_i^K, VW_i^V\right)
\end{align}$$
となります。
haedごとのcontext vectorを計算して、それを結合し、最後に\(W^O\)を掛けています。
さて、このインプットV、K、Qですが、何が入るかは使われている場所によって違います。
それをこれから解説したいと思います。
エンコーダーのSelf-Attention
この部分のattentionは、自分自身のどの部分が重要かを判断するSelf-Attentionを使います。
つまり、QもKもVも自分自身で、すべて同じ前の層からのアウトプットです。

デコーダーのSelf-Attention
デコーダーのSelf-Attentionはこちらの青で囲んだ部分です。
この部分もSelf-Attentionなのでエンコーダーと同じで、前の層のアウトプットをQ、K、Vすべて同じように設定します。

ただ、こちらは普通のSelf-Attentionではなく、使うべきでない情報にマスクをかけたMasked Self-Attentionになります。
何故マスクをするかというと、学習時のデコーダーのインプットは、翻訳後の単語列になりますが、翻訳語の単語列は前から順番に作成していく必要があり、i番目の翻訳語の単語を予測する際は、i-1番目までの情報しか使うべきではないからです。
そうしないと、その単語より前の単語だけでなく先の単語の答えも見て当てるということになってしまい、自明になってしまいます。
エンコーダー・デコーダーのAttention
エンコーダー・デコーダーは以下の図の青で囲んだ部分です。
このインプットは、これまでのようにすべて同じではありません。

key、valueは“注意を向ける先”なのでエンコーダーのアウトプットを使用、queryは“どこから注意を向けるか”なので、デコーダーにおける前の層のアウトプットを使用します。
これにより、翻訳後(query)の単語から翻訳前(key-value)のどの部分に注意を向けるか、ということを学習することができます。
まとめ
論文「Attention Is All You Need」をもとに説明してきましたが、 如何でしたでしょうか?こちらで何となく理解できたという人は、実際に論文を読んでみることをオススメします。論文にはこれ以外にも、学習率の設定やドロップアウト、正則化など細かい工夫についての説明がありますし、仕組みについてももう少し詳しく説明されています。
以下の記事でTransformerによる分類モデルを実装し、実際のデータを使って精度を確認したりしていますので、こちらをご参照いただければ、より深く理解できると思います。
こちらの本では、TransformerとBERTをPyTorchで実装していますので、真似をしながら自分の手で実装することで、Transformer、さらにはその後続のBERTをしっかりと理解することができると思います(後でご説明するMulti-headにはなっておらずSingle-headですので、自分でMulti-headにするとさらに理解が深まると思います)。
説明も丁寧でわかりやすいのでオススメです。
ということで、だいぶBERTやXLNetに近づいてきました。次は、Pre-training – Fine-tuningの論文解説をする予定です。
コメント