リモート開発メインのソフトウェア開発企業のエンジニアブログです

BERTについて勉強したことまとめ (2)モデル構造について

前回は特徴と予備知識を確認しました。
今回はBERTの構造についてです。

BERTのモデルの構造

BERTのモデル構造の主な部分は、TransformerのEncoderを複数重ねただけです。
それに合わせて、その上下にどのような層がつくのかを把握すると良いと思います。

Transformer Encoderの中身や入出力の詳細を棚に上げれば、BERTは結構単純な構造です。
以下では、BERTを使う人向けに知っておくと良さそうなレベルで説明します。

Transformer Encoder

基本はTransformerのEncoder部分を複数重ねただけです。

Transformer自体は、BERTから約1年前に登場した、NLPにおいてとても注目されているモデルです。
(先にBERTを知った入門者にとっては、また新しい概念が、…と大変な気持ちになりますが…)

このTransformerについては参考になる記事が多数ありますので、そちらをご覧ください。

私自身も、全ての記事に目を通して完全に理解しているわけではありません。ただ、BERTはこのTransformerという構造のEncoder部分を利用しているだけということを抑えておきましょう。
そしてそのEncoder部分は、入力と出力は配列であり同じ長さになります。
図にすると以下のようになります。

このモデルに文章を入力するのですが、文章はトークンという単位で分割して、トークン列として入力されます。
そしてEncoderは、このトークン列の長さはそのままに、個別のトークンをベクトルに変換します。

Transformer自体はEncoderとDecoderの組み合わせですが、BERTが利用してるのはEncoderの部分だけです。
transformer encoder自体の内側の詳細を省いてしまえば、とても単純な構造をしていることが理解できると思います。

BERTの論文中でも、transformerについては詳しく説明はされておりません。
つまりモデル構造については、BERT特有の特別な工夫があるわけではありません。

BERTに限らず、Transformerを利用したモデルが現在のNLPにおいて多く登場しています。

注意点は、ネット上の記事を見るとよく「Transformer」と書かれていますが、これは「TransformerのEncoderの部分」の省略である場合が多いです。

BERT及びTransformerの、個別の層やベクトルのサイズまで把握したい方は以下の記事がおすすめです

Transformer encoderの後ろ(pooler layer)

これは、学習するタスクによって変わります。
文章のトークン列の位置を利用するものは、その対応するベクトルを後の別のレイヤーに渡します。

注意点ですが、分類問題のために特別に文頭に[CLS]というトークンをつけて学習します。
分類問題を解く場合には、対応する先頭のベクトルを、分類問題を解くためのレイヤーに渡します。

分類タスクの場合

質問から解答を得るタスクの場合(Question Answering Task: SQuAD 1.1)

画像は元論文より引用

Transformer Encoderの後ろにくるレイヤーのことを、BERTの実装されたリポジトリではpooler layerと呼ばれています。
(これが、機械学習の用語であるのか、英語の一般名詞なのかは私には分かりませんでした。検索した範囲で確信が得られなかったので、英語の一般名詞のように思えます)

事前学習での2つのタスクや、ファインチューニングのときに、そのタスクに応じてpooler layerが切り替えられます。すなわち、ここのレイヤーは転移学習として利用されません。

Transformer encoderの前、入力部分

トークナイズとid化

上の節では、単に「文章をトークン列として入力する」と書きましたが、そもそも深層学習ではあらゆる入力はベクトルとして扱わないといけません。文字列をベクトルとして扱う変換を省略していました。他の記事でもここはよく省略されています。

文章の文字列を何らかのトークナイザで分割した上で、単語ごとにidを振る必要があります。
このトークナイズ処理とid割当はBERT本体のモデル構造とは別の話です。実際、Googleによる実装ではWordPieceというトークナイザを使っています。(これもGoogle製)

英語とは別の言語の場合、必要であったり性能比較のために異なるトークナイザが使えます。
逆に言うと、このトークナイザが文章分割とid化を行うので、そのidを用いて事前学習したBERTのモデルは、後からトークナイザを変更することはできません。

id化されたトークンは、one-hotベクトルとして入力した上で、一度行列変換をはさみ、その後にTransformer Encoderに入力されます。

positional embeddingとsegment embedding

もう一点、BERTへの入力に関しては面白い点があります。
それは、トークンのembeddingとは別に、2種類の情報を入力に組み込んでいるということです。
それは position embeddingとsegment embeddingです。

もう一度、Transformer encoderの入力の図を貼ります。

モデル構造としては、transformer encoderは、すべての入力がトポロジー的に同一であり、順番を入れ替えても対称になっています。なのでこのまま学習をさせたとしても、トークンの順番が学習に考慮できません。

Image from Gyazo

それを解決するために、そもそも入力自体に、位置番号を混ぜて学習をさせているのです。それがpositional encodingの意味です。

厳密に言えば、位置を整数としてではなく、ベクトルで表現した上で、ベクトルとして足します。
回転行列を使って、いい感じに位置情報をモデルに理解させる工夫などを行っているようです。
詳細は元論文か、こちらの資料が参考になります。BERTとTransformer – サイボウズラボ 機械学習勉強会

さらに、同じように複数の文章を入れる際の、「文章を2つ入れたとき、このトークンはどっちの文章のもの?」という情報が、上記の入力の対称性で失われてしまいます。
これを、segment embeddingとして埋め込みます。

以上で、トークンそれ自体とその他の情報2種類を足し合わせたものが、Transformer Encoderに入力されます。

  • トークン自体のembedding
  • 文章のセグメントのembedding
  • トークン位置のembedding

元論文より引用

個人的には、この「構造をトークンに対して並列にするために失われた情報を、入力自体に埋め込んで解決」というところが非常に面白いと思います。

ただし、ライブラリ等を使ってファインチューニングを行う場合は、ここらへんの入力について意識することは無いかもしれません。


次回は学習データ、BERTの汎用性についてです。

← 前の投稿

機械学習・自然言語処理のお勧め本など

次の投稿 →

SkyWay vs. Twilio Video

コメントを残す