MLエンジニアへの道 #29 - BERT vs BPT

Last Edited: 11/13/2024

このブログ記事では、ディープラーニングにおけるBERTやGPTについて紹介します。

ML

Transformer

基本的なTransformerアーキテクチャは、入力をコンテキスト化するエンコーダと、エンコーダからの出力と過去の出力を元に新しい出力を生成するデコーダで構成されています。 エンコーダ・デコーダアーキテクチャに慣れていない場合は、MLエンジニアへの道 #18 - VAEという記事を参考にしてください。 エンコーダとデコーダは、埋め込み層、位置エンコーディング、多頭自己注意、クロスアテンション、層正規化、密結合層といった様々なレイヤーから以下の様に構築されます。

Transformer

Transformer内のクロスアテンション層は、エンコーダのキーとバリューを使用して、入力に基づいて埋め込みを変換します。 デコーダへの最初の入力は<SOS>(文の開始)または<EOS>(文の終了)トークンであり、新しい出力は次のイテレーションで前回の入力の右に追加されます。 入力と出力のシーケンスが異なる場合があるため、このアーキテクチャは機械翻訳、質問応答、次の単語予測といった様々なタスクに対応できます。

BERT

上記のTransformerアーキテクチャは強力で多用途ですが、必ずしもエンコーダ・デコーダアーキテクチャが必要なわけではありません。 例えば、出力をシーケンスとして生成する必要のない文書分類のようなタスクがあります。こうした用途には、 エンコーダのみを使用するBidirectional Encoder Representations from Transformers(BERT) というアーキテクチャが利用され、文書分類や感情分析などのタスクに適しています。

膨大なテキストコーパスでBERTをトレーニングして適切な文脈化を行うために、文章内のランダムな単語をマスクし、 その文脈に基づいて欠損している単語を予測させます。この文脈は欠損単語の左右両方から読み取ることができるため、 「双方向エンコーダ表現(Bidirectional Encoder Representations)」という名称がつけられています。 BERTによる変換後の埋め込みは、フィードフォワードネットワーク(FNN)に渡され、次の文予測を含む様々なタスクに利用できます。

class TransformerEncoderBlock(layers.Layer):
  def __init__(self, embed_dim, hidden_dim, num_heads=8):
    super(TransformerEncoderBlock, self).__init__()
    self.attention = MultiHeadedSelfAttention(num_heads, embed_dim, hidden_dim)
    self.layer_norm1 = LayerNormalization(embed_dim)
    self.mlp = tf.keras.Sequential([
        layers.Dense(4 * embed_dim, activation="relu"),
        layers.Dense(embed_dim)
    ])
    self.layer_norm2 = LayerNormalization(embed_dim)
 
  def call(self, x):
    a = self.attention(x)
    x = self.layer_norm1(x + a)
    m = self.mlp(x)
    x = self.layer_norm2(x + m)
    return x
 
class BERT(tf.keras.Model):
  def __init__(self, vocab_size, seq_len, embed_dim, hidden_dim, num_layers=4, num_heads=8):
    super(BERT, self).__init__()
    self.word_embedding = layers.Embedding(vocab_size,
                                      embed_dim,
                                      embeddings_initializer="he_normal",
                                      embeddings_regularizer="l1",
                                      name="w2v_embedding")
    self.positional_encoding = positional_encoding(seq_len, embed_dim)
    self.encoder = tf.keras.Sequential([
        TransformerEncoderBlock(embed_dim, hidden_dim) 
        for i in range(num_layers)
    ])
    self.classifier = tf.keras.Sequential([
        layers.Dense(embed_dim, activation="relu"),
        layers.Dense(vocab_size, activation="softmax")
    ])
 
  def call(self, x):
    x = self.word_embedding(x)
    x += self.positional_encoding
    x = self.encoder(x)
    x = self.classifier(x)
    return x
 
bert = BERT(2000, 1024, 1024, 128)

上記は、TransformerエンコーダブロックとBERTのTensorFlow実装の例です。このBERTモデルは、語彙サイズ200、シーケンス長1024、埋め込みサイズ1024、隠れ層サイズ128で、 約8千万パラメータを持つ比較的小さなモデルです。しかし、それでもGPUやTPUを使用してもトレーニングに数日かかることがあります。 BERTをランダムにマスクされた単語の予測にトレーニングした後、その単語埋め込みとエンコーダは、新しい分類器に利用できる文脈化された埋め込みを生成するために使用できます。

GPT

もう一つのTransformerアーキテクチャが、次の単語を生成するために特化したデコーダ専用モデルであるGenerative Pretrained Transformer(GPT) です。 このアーキテクチャはBERTに似ていますが、左側の文脈のみを利用する点が異なります。左から右に向かってマスキングが適用され(左下のアテンションの値をゼロに設定され)、 GPTは左側の文脈のみから各単語を予測することを学習します。

class TransformerDecoderBlock(layers.Layer):
  def __init__(self, embed_dim, hidden_dim, num_heads=8):
    super(TransformerDecoderBlock, self).__init__()
    self.attention1 = MultiHeadedSelfAttention(num_heads, embed_dim, hidden_dim, masked=True)
    self.layer_norm1 = LayerNormalization(embed_dim)
    self.mlp = tf.keras.Sequential([
        layers.Dense(4 * embed_dim, activation="relu"),
        layers.Dense(embed_dim)
    ])
    self.layer_norm2 = LayerNormalization(embed_dim)
 
  def call(self, x):
    a = self.attention1(x)
    x = self.layer_norm1(x + a)
    m = self.mlp(x)
    x = self.layer_norm2(x + m)
    return x
 
class GPT(tf.keras.Model):
  def __init__(self, vocab_size, seq_len, embed_dim, hidden_dim, num_layers=4, num_heads=8):
    super(GPT, self).__init__()
    self.word_embedding = layers.Embedding(vocab_size,
                                      embed_dim,
                                      embeddings_initializer="he_normal",
                                      embeddings_regularizer="l1",
                                      name="w2v_embedding")
    self.positional_encoding = positional_encoding(seq_len, embed_dim)
    self.decoder = tf.keras.Sequential([
        TransformerDecoderBlock(embed_dim, hidden_dim) 
        for i in range(num_layers)
    ])
    self.classifier = tf.keras.Sequential([
        layers.Dense(vocab_size, activation="softmax")
    ])
 
  def call(self, x):
    x = self.word_embedding(x)
    x += self.positional_encoding
    x = self.decoder(x)
    x = self.classifier(x)
    return x
 
gpt = GPT(2000, 1024, 1024, 128)

上記は、デコーダブロックとGPTのTensorFlow実装の例です。ここでのBERTとの唯一の違いは、マスク付きアテンションの使用です。 トレーニング時には、最後のトークンを除いた入力シーケンスと最初のトークンを除いた出力シーケンスを渡し、 左側の文脈のみに基づいて次の単語を予測することを学習させます。推論時には、次の単語の予測を取り、 それを入力シーケンスに追加して<EOS>トークンが現れるまで繰り返します。BERTと同様に、 このモデルも約8千万パラメータを持ち、トレーニングには大量の時間と計算資源が必要ですが、 トークナイザと実装次第で、機械翻訳などの様々なタスクを高いレベルで実行できます。

結論

この記事では、自然言語処理における二つの人気のあるTransformerアーキテクチャ、BERTとGPTについて説明しました。 どちらのモデルも数百万から数十億のパラメータを持ち、最先端の計算リソースを用いても、トレーニングに数日から数ヶ月かかることがあり、 財務面や環境面でのコストが問題視されることもあります。しかし、前回の記事で説明した帰納的バイアスや、並列性、GPT-1からGPT-4への進化に繋がるまでの膨大な研究努力の甲斐もあって、 これらのモデルの能力はRNNのような従来のモデルとは一線を画しています。

この記事シリーズでは、マルチヘッド自己アテンションブロックの実装詳細や、BERTおよびGPTのデータ準備、推論、 PyTorchによるBERTおよびGPTの実装は敢えて取り扱っていません。機械学習やNLPに興味がある方は、 パイプラインの構築に挑戦し、BERTとGPTに慣れることを強くお勧めします。(何か実装に躓いているなら、ぜひ気軽に連絡してください:)

リソース