文脈付き埋め込み(ELMo→BERT)— 静的表現から文脈依存表現への進化

「彼は川のバンクに座った」と「彼はバンクにお金を預けた」— 日本語の「バンク」に相当する英語の “bank” は、前者では「川岸」、後者では「銀行」を意味します。人間であれば、周囲の単語を見て瞬時にどちらの意味かを判断できます。しかし、Word2VecやGloVeのような静的な単語埋め込みでは、”bank” に対してたった1つの固定ベクトルしか割り当てられません。川岸の bank も銀行の bank も、まったく同じベクトルで表現されてしまうのです。

これは単なる学術的な問題ではありません。質問応答システムで「Which bank did he visit?」という質問に答えるとき、bank の意味を文脈から正しく把握できなければ、「銀行」に関する段落と「川岸」に関する段落を区別できません。機械翻訳でも、”I went to the bank” を文脈に応じて「銀行に行った」と「川岸に行った」のどちらに訳すべきかは、周囲の文脈なしには判断できません。

この多義語問題を解決したのが、2018年に登場したELMo(Embeddings from Language Models)です。ELMoは双方向LSTMを用いて、同じ単語でも文脈に応じて異なるベクトルを出力する文脈付き埋め込み(contextual embeddings)を実現しました。さらに同年、GoogleのBERTがTransformerベースの双方向文脈表現で圧倒的な性能を達成し、NLP分野のパラダイムシフトを引き起こしました。

文脈付き埋め込みを理解すると、以下のことが可能になります。

  • 質問応答(QA)の高精度化: 文脈を考慮した表現により、質問の意図と文書の内容をより正確にマッチングできます
  • 機械翻訳の品質向上: 多義語の適切な訳し分けが可能になります
  • 感情分析・テキスト分類: 「このバッテリーは長持ちする」の「長持ち」がポジティブであることを文脈から理解できます
  • 固有表現抽出: 「Apple」が企業名か果物かを文脈から判定できます

静的埋め込みから文脈付き埋め込みへの進化は、NLP史上最も重要なブレークスルーの1つです。本記事では、この進化の過程を「静的埋め込みの限界 → ELMo → BERT」の流れに沿って、数式の導出とPython実装で丁寧に解説します。

本記事の内容

  • 静的埋め込み(Word2Vec/GloVe)の限界と多義語問題
  • ELMoの双方向LSTMによる文脈付き埋め込みの理論
  • ELMoの数学的定式化 — 各層の重み付き結合
  • PyTorchによる簡易ELMoの実装
  • GPT/BERTの登場とTransformerベースへの転換
  • BERTの文脈表現と各層の役割
  • ELMo vs BERT の比較 — feature-based vs fine-tuning
  • 多義語 “bank” の文脈ベクトル比較実験

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

画像なし
単語埋め込みの理論 — Word2Vec・GloVe・FastTextの比較
静的な単語埋め込みの仕組みとWord2Vec、GloVe、FastTextの数理を解説しています。
画像なし
トークナイゼーションの理論 — BPE・WordPiece・SentencePiece
テキストの分割手法であるBPE、WordPiece、SentencePieceの仕組みを解説しています。
画像なし
BERTのアーキテクチャと事前学習を解説
BERTのTransformer Encoderベースの構造と事前学習タスクMLM・NSPの目的関数を解説しています。

静的埋め込みの限界 — なぜ1語1ベクトルでは不十分なのか

Word2VecとGloVeの成功

Word2Vec(Mikolov et al., 2013)とGloVe(Pennington et al., 2014)は、「ある単語の意味はその周囲の単語によって決まる」という分散仮説に基づき、単語の意味を密なベクトルで表現することに成功しました。有名な「king – man + woman = queen」のようなベクトル演算が成立し、単語間の意味的な関係を幾何学的に捉えることができました。

Word2Vecは語彙 $V$ の各単語 $w$ に対して、1つの埋め込みベクトル $\bm{e}_w \in \mathbb{R}^d$ を学習します。$d$ は埋め込みの次元数で、一般的に100〜300程度です。この埋め込みは大規模コーパスから学習されますが、一度学習が完了すると、各単語のベクトルは固定されます。つまり、どのような文脈で使われても、同じ単語には同じベクトルが割り当てられるのです。

多義語問題の具体例

この「1語1ベクトル」の設計がもたらす根本的な問題を、具体例で見てみましょう。

英語の “bank” は主に以下の2つの意味を持ちます。

  1. 銀行: “I deposited money in the bank.”(銀行にお金を預けた)
  2. 川岸: “The boat was tied to the bank.”(ボートが川岸に繋がれていた)

Word2Vecでは、これら2つのまったく異なる意味が、たった1つのベクトル $\bm{e}_{\text{bank}}$ で表現されます。結果として、$\bm{e}_{\text{bank}}$ は「銀行」と「川岸」の意味が混ざった中間的なベクトルになります。

数式で表すと、理想的には意味ごとに別のベクトルを持ちたいのです。

$$ \bm{e}_{\text{bank}}^{\text{(銀行)}} \neq \bm{e}_{\text{bank}}^{\text{(川岸)}} $$

しかし、静的埋め込みではこれが実現できず、以下のような単一ベクトルに集約されてしまいます。

$$ \bm{e}_{\text{bank}} \approx \alpha \cdot \bm{e}_{\text{bank}}^{\text{(銀行)}} + (1 – \alpha) \cdot \bm{e}_{\text{bank}}^{\text{(川岸)}} $$

ここで $\alpha$ はコーパス中での各意味の出現頻度に依存する重みです。銀行の意味で使われることが多ければ $\alpha$ は大きくなり、結果のベクトルは「銀行」寄りになりますが、「川岸」の意味は適切に表現されません。

多義語問題がもたらす実務上の影響

多義語問題は理論的な関心事にとどまりません。英語には驚くほど多くの多義語が存在します。

  • “run”: 走る / 経営する / 実行する / 流れる
  • “light”: 光 / 軽い / 点火する
  • “play”: 遊ぶ / 演奏する / 演じる / 戯曲
  • “spring”: 春 / ばね / 泉 / 跳ぶ

Oxford English Dictionary によると、”run” には実に645もの異なる意味があるとされています。高頻度語ほど多義的である傾向があるため、多義語問題はまれに起きるエッジケースではなく、日常的なテキスト処理で頻繁に遭遇する問題です。

さらに、下流タスクへの影響も深刻です。質問応答において “Where is the bank?” という質問に対し、「銀行」に関する文章と「川岸」に関する文章の両方がヒットしてしまいます。”bank” のベクトルが1つしかないため、文脈に基づく絞り込みができないのです。

問題の本質 — 表現の静的性質

問題の本質を整理しましょう。静的埋め込みには以下の限界があります。

文脈非依存: 入力テキストの文脈にかかわらず、同じ単語には同じベクトルが出力されます。

意味の混合: 多義語のベクトルは、異なる意味の「平均」のようなものになります。

構文情報の欠如: “dog bites man” と “man bites dog” で “bites” のベクトルが同一であり、主語・目的語の関係が反映されません。

これらの限界を打破するには、同じ単語でも文脈に応じて異なるベクトルを出力する仕組み — すなわち文脈付き埋め込みが必要です。では、最初にこれを実現したELMoの仕組みを見ていきましょう。

ELMoの登場 — 文脈付き埋め込みの幕開け

ELMoの基本アイデア

2018年、Allen Institute for AI の Peters et al. が提案した ELMo(Embeddings from Language Models)は、文脈付き埋め込みの先駆けとなったモデルです。ELMoのアイデアは非常にシンプルかつ強力です。大規模テキストで学習した双方向の言語モデル(bidirectional Language Model, biLM)の内部表現を、単語の文脈付き埋め込みとして利用するのです。

直感的に言えば、ELMoは「文全体を読んで各単語の意味を理解した上で、文脈に応じたベクトルを出力する」モデルです。料理に例えると、静的埋め込みが「材料を見ただけで味を決める」ようなものだとすれば、ELMoは「すべての材料を鍋に入れて煮込んでから味を判定する」ようなものです。周囲の材料(単語)によって、同じ材料(単語)でも最終的な味(意味)が変わるのです。

双方向LSTM言語モデル(biLM)

ELMoの中核は、双方向LSTM言語モデルです。通常のLSTM言語モデルは、テキストを左から右に読みながら、次の単語を予測します。一方、逆方向のLSTM言語モデルは右から左に読み、前の単語を予測します。ELMoはこの2つを組み合わせます。

順方向LSTMは、位置 $k$ における単語を、それより前の単語列から予測します。

$$ P(t_k \mid t_1, t_2, \ldots, t_{k-1}) $$

逆方向LSTMは、位置 $k$ における単語を、それより後の単語列から予測します。

$$ P(t_k \mid t_{k+1}, t_{k+2}, \ldots, t_N) $$

ここで $t_1, t_2, \ldots, t_N$ は入力トークン列、$N$ は系列長です。

biLMの学習目的は、順方向と逆方向の対数尤度の和を最大化することです。

$$ \sum_{k=1}^{N} \left( \log P(t_k \mid t_1, \ldots, t_{k-1}; \bm{\Theta}_{\text{LSTM}}, \overrightarrow{\bm{\Theta}}) + \log P(t_k \mid t_{k+1}, \ldots, t_N; \bm{\Theta}_{\text{LSTM}}, \overleftarrow{\bm{\Theta}}) \right) $$

ここで $\bm{\Theta}_{\text{LSTM}}$ は両方向で共有されるトークン埋め込みとソフトマックス層のパラメータ、$\overrightarrow{\bm{\Theta}}$ と $\overleftarrow{\bm{\Theta}}$ はそれぞれ順方向・逆方向LSTM固有のパラメータです。

ここで重要なのは、順方向LSTMと逆方向LSTMが独立に学習されるという点です。つまり、位置 $k$ の表現を計算するとき、順方向は $t_1, \ldots, t_{k-1}$ の情報しか使わず、逆方向は $t_{k+1}, \ldots, t_N$ の情報しか使いません。これは後述するBERTの「真の双方向性」とは異なる設計です。

feature-based アプローチ

ELMoのもう1つの重要な特徴は、feature-basedアプローチを採用したことです。ELMo自体を下流タスクに対してファインチューニングするのではなく、ELMoが出力する文脈付き埋め込みを「特徴量」として既存のモデルに追加するのです。

具体的には、下流タスク(質問応答、固有表現抽出など)のモデルの入力層にELMoの出力を結合(concatenate)します。これにより、既存のモデルのアーキテクチャを大きく変更することなく、文脈付きの情報を注入できるのです。

たとえるなら、feature-basedアプローチは「外付けのセンサー」のようなものです。既存のロボット(下流モデル)に新しいセンサー(ELMo)を取り付けて、追加の情報を与えます。ロボット自体の構造は変わりませんが、入力される情報が豊かになることで性能が向上するのです。

ELMoの具体的な数式をもう少し掘り下げてみましょう。次のセクションで、各層の表現がどのように組み合わされて最終的な文脈付き埋め込みになるかを詳しく見ていきます。

ELMoの数学的定式化

各層の隠れ状態

ELMoでは、$L$ 層の双方向LSTMを使用します(原論文では $L = 2$ 層)。位置 $k$ のトークンについて、各層から以下の隠れ状態が得られます。

入力層(第0層): 文脈非依存のトークン埋め込み(文字レベルCNNまたは単語埋め込みから取得)。

$$ \bm{h}_{k,0} = \bm{x}_k $$

ここで $\bm{x}_k$ は位置 $k$ のトークンに対する静的な入力表現です。ELMoの原論文では、文字レベルのCNN(Char-CNN)を用いてサブワード情報を含む入力表現を構築しています。

第 $j$ 層($j = 1, 2, \ldots, L$): 順方向LSTMと逆方向LSTMの隠れ状態を連結(concatenate)します。

$$ \bm{h}_{k,j} = \left[ \overrightarrow{\bm{h}}_{k,j} ; \overleftarrow{\bm{h}}_{k,j} \right] $$

ここで $\overrightarrow{\bm{h}}_{k,j} \in \mathbb{R}^{d_{\text{LSTM}}}$ は順方向LSTMの第 $j$ 層における位置 $k$ の隠れ状態、$\overleftarrow{\bm{h}}_{k,j} \in \mathbb{R}^{d_{\text{LSTM}}}$ は逆方向LSTMの第 $j$ 層における隠れ状態です。連結されたベクトルの次元は $2d_{\text{LSTM}}$ になります。

位置 $k$ のトークンに対して、ELMoは $L + 1$ 個の表現を持つことになります。

$$ R_k = \{ \bm{h}_{k,0}, \bm{h}_{k,1}, \ldots, \bm{h}_{k,L} \} $$

タスク固有の重み付き結合

Peters et al. の重要な発見の1つは、各層が異なる種類の情報を捉えているということです。

  • 第0層(入力層): 形態論的な情報(単語の形、活用、品詞など)
  • 第1層(下位のLSTM層): 構文的な情報(品詞タグ、構文構造など)
  • 第2層(上位のLSTM層): 意味的な情報(単語の文脈依存の意味、多義語の曖昧性解消など)

したがって、下流タスクに応じて各層への重みを調整するのが合理的です。品詞タグ付けタスクでは下位層の重みを大きく、質問応答タスクでは上位層の重みを大きくする、というように。

この考えに基づき、ELMoは下流タスク $\text{task}$ ごとに、以下の重み付き結合で最終的な文脈付き埋め込みを計算します。

$$ \text{ELMo}_k^{\text{task}} = \gamma^{\text{task}} \sum_{j=0}^{L} s_j^{\text{task}} \cdot \bm{h}_{k,j} $$

各パラメータの意味を順に説明します。

$s_j^{\text{task}}$ はソフトマックスで正規化された層の重みです。

$$ s_j^{\text{task}} = \frac{\exp(w_j^{\text{task}})}{\sum_{j’=0}^{L} \exp(w_{j’}^{\text{task}})} $$

ここで $w_j^{\text{task}}$ は学習可能なスカラーパラメータです。ソフトマックス正規化により、$\sum_{j=0}^{L} s_j^{\text{task}} = 1$ かつ $s_j^{\text{task}} \geq 0$ が保証されます。

$\gamma^{\text{task}}$ はタスク固有のスケーリング因子で、これも学習可能なスカラーパラメータです。下流タスクの損失関数を通じて、ELMo表現全体のスケールを調整します。

ELMoの利用方法

ELMoの文脈付き埋め込みは、下流タスクのモデルの入力に結合して使います。もとの入力トークン表現を $\bm{x}_k$ とすると、新しい入力は以下のようになります。

$$ \tilde{\bm{x}}_k = [\bm{x}_k ; \text{ELMo}_k^{\text{task}}] $$

これを下流タスクのモデル(例えば双方向LSTMベースの固有表現抽出モデル)に入力します。学習時には、$s_j^{\text{task}}$ と $\gamma^{\text{task}}$ がタスクのデータに基づいて最適化される一方、biLM自体の重みは固定(frozen)されます。

ELMoの数式が理解できたところで、このモデルをPyTorchで実装して動きを確かめてみましょう。実際にコードを動かすことで、文脈に応じてベクトルが変わる様子を体感できます。

ELMoの実装(PyTorch)

簡易版biLSTMベースの文脈埋め込み

ここでは、ELMoの核心となる「双方向LSTMの各層の隠れ状態を重み付き平均する」仕組みを、PyTorchで簡易的に実装します。大規模言語モデルの事前学習は省略し、構造と重み付き結合のメカニズムに焦点を当てます。

まず、biLSTMの構造を定義し、各層の出力を取得する部分を実装します。

import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt

class SimpleBiLM(nn.Module):
    """ELMoの核心を再現する簡易的な双方向LSTM言語モデル"""

    def __init__(self, vocab_size, embed_dim, hidden_dim, num_layers=2):
        super().__init__()
        self.num_layers = num_layers
        self.hidden_dim = hidden_dim

        # トークン埋め込み(第0層)
        self.embedding = nn.Embedding(vocab_size, embed_dim)

        # 双方向LSTM(各層の出力を取得するためnum_layers=1で積む)
        self.lstm_layers = nn.ModuleList()
        for i in range(num_layers):
            input_size = embed_dim if i == 0 else hidden_dim * 2
            self.lstm_layers.append(
                nn.LSTM(input_size, hidden_dim, num_layers=1,
                        batch_first=True, bidirectional=True)
            )

    def forward(self, x):
        """
        各層の隠れ状態を返す
        x: (batch, seq_len) のトークンID
        returns: list of (batch, seq_len, dim) — L+1個の層表現
        """
        # 第0層: 静的なトークン埋め込み
        h = self.embedding(x)  # (batch, seq_len, embed_dim)
        all_layers = [h]

        # 第1〜L層: 双方向LSTMの各層
        for lstm in self.lstm_layers:
            h, _ = lstm(h)  # (batch, seq_len, hidden_dim*2)
            all_layers.append(h)

        return all_layers

次に、ELMoのタスク固有の重み付き結合を実装します。

class ELMoRepresentation(nn.Module):
    """タスク固有の重み付き結合でELMo表現を計算する"""

    def __init__(self, num_layers, repr_dim):
        super().__init__()
        # タスク固有の層重み(学習可能)
        self.layer_weights = nn.Parameter(torch.zeros(num_layers + 1))
        # スケーリング因子 gamma(学習可能)
        self.gamma = nn.Parameter(torch.ones(1))

        # 各層の次元を揃えるための射影層
        self.proj = None  # 必要に応じて設定

    def forward(self, layer_outputs):
        """
        layer_outputs: list of (batch, seq_len, dim) — L+1個の層表現
        returns: (batch, seq_len, dim) — 重み付き結合された文脈埋め込み
        """
        # ソフトマックスで正規化
        normed_weights = F.softmax(self.layer_weights, dim=0)

        # 重み付き和を計算
        elmo_repr = torch.zeros_like(layer_outputs[-1])
        for w, layer_out in zip(normed_weights, layer_outputs):
            # 第0層の次元がLSTM層と異なる場合は射影が必要
            if layer_out.shape[-1] != elmo_repr.shape[-1]:
                # 簡易的にゼロパディングで次元を揃える
                pad_size = elmo_repr.shape[-1] - layer_out.shape[-1]
                layer_out = F.pad(layer_out, (0, pad_size))
            elmo_repr = elmo_repr + w * layer_out

        # スケーリング
        elmo_repr = self.gamma * elmo_repr

        return elmo_repr

この2つのモジュールを組み合わせて、文脈付き埋め込みを取得してみましょう。

# ハイパーパラメータ
vocab_size = 1000
embed_dim = 64
hidden_dim = 64  # embed_dimと揃えて簡単にする
num_layers = 2

# モデルの構築
bilm = SimpleBiLM(vocab_size, embed_dim, hidden_dim, num_layers)
elmo = ELMoRepresentation(num_layers, hidden_dim * 2)

# ダミー入力(バッチサイズ2、系列長6)
# "bank" に相当するトークンID = 42 とする
x = torch.tensor([
    [10, 20, 42, 30, 40, 50],  # 文1: ... bank ...
    [60, 70, 42, 80, 90, 100]  # 文2: ... bank ...(異なる文脈)
])

# 各層の表現を取得
with torch.no_grad():
    layer_outputs = bilm(x)
    contextual_emb = elmo(layer_outputs)

print(f"入力形状: {x.shape}")
print(f"層の数: {len(layer_outputs)}")
for i, lo in enumerate(layer_outputs):
    print(f"  第{i}層の形状: {lo.shape}")
print(f"ELMo出力の形状: {contextual_emb.shape}")

# "bank"(位置2)のベクトルを比較
bank_vec1 = contextual_emb[0, 2]  # 文1での bank
bank_vec2 = contextual_emb[1, 2]  # 文2での bank

# コサイン類似度
cos_sim = F.cosine_similarity(bank_vec1.unsqueeze(0), bank_vec2.unsqueeze(0))
print(f"\n同じ 'bank' でも異なる文脈でのコサイン類似度: {cos_sim.item():.4f}")
print("→ 文脈が異なるため、1.0にはならない(文脈付き埋め込みの特徴)")

上の出力で注目すべき点は2つあります。まず、入力層(第0層)ではembedding次元のベクトルが、LSTM層を通過すると $2 \times d_{\text{LSTM}}$ 次元(順方向と逆方向の連結)のベクトルに変わります。そして最も重要なのは、同じトークンID(42 = “bank”)であっても、異なる文脈(文1と文2)では異なるベクトルが出力されるということです。コサイン類似度が1.0にならないのは、biLSTMが周囲のトークンの情報を取り込んで、文脈に応じた表現を生成しているからです。これが静的埋め込みとの根本的な違いです。

各層の重みの可視化

学習済みのELMoで各層にどのような重みが付くかを可視化してみましょう。ここではダミーの重みを使って、異なるタスクでの重みの分布の違いを示します。

# 異なるタスクでの層重みの例(実際の学習結果に基づくイメージ)
tasks = {
    '品詞タグ付け': [0.35, 0.45, 0.20],   # 下位層重視
    '固有表現抽出': [0.20, 0.35, 0.45],   # 上位層重視
    '感情分析':     [0.15, 0.30, 0.55],   # 上位層をさらに重視
}

fig, ax = plt.subplots(figsize=(8, 5))
x_pos = np.arange(3)
width = 0.25

for i, (task, weights) in enumerate(tasks.items()):
    ax.bar(x_pos + i * width, weights, width, label=task)

ax.set_xlabel('Layer')
ax.set_ylabel('Weight ($s_j^{\\mathrm{task}}$)')
ax.set_title('ELMo Layer Weights by Task')
ax.set_xticks(x_pos + width)
ax.set_xticklabels(['Layer 0\n(Embedding)', 'Layer 1\n(Syntax)', 'Layer 2\n(Semantics)'])
ax.legend()
ax.set_ylim(0, 0.7)
plt.tight_layout()
plt.show()

このグラフからいくつかの重要な傾向が読み取れます。品詞タグ付けのような構文的なタスクでは第1層(構文層)の重みが最も大きくなり、感情分析のような意味理解を要するタスクでは第2層(意味層)の重みが支配的になります。つまり、ELMoの層重みはタスクの性質を反映しており、モデルが自動的に「このタスクにはどの層の情報が重要か」を学習しているのです。これは、biLMの各層が異なるレベルの言語情報をエンコードしているというPeters et al.の知見と一致します。

ELMoは文脈付き埋め込みの先駆けとして大きなインパクトを与えましたが、いくつかの限界も抱えていました。LSTMベースであるため系列の長距離依存関係を捉えにくいこと、順方向と逆方向が独立で「真の双方向性」を持たないこと、そしてfeature-basedアプローチのためタスク適応の柔軟性に限界があることです。これらの限界を一挙に克服したのが、Transformerベースの事前学習モデルであるGPTとBERTです。

GPTとBERTの登場 — Transformerベースへの転換

パラダイムシフトの背景

ELMoの成功を受けて、2018年後半に2つの画期的なモデルが登場しました。OpenAIのGPT(Generative Pre-trained Transformer)とGoogleのBERT(Bidirectional Encoder Representations from Transformers)です。どちらもTransformerアーキテクチャを採用し、大規模テキストでの事前学習により強力な言語表現を獲得しますが、アプローチが根本的に異なります。

ELMoからGPT/BERTへの移行は、大きく2つの点でパラダイムシフトをもたらしました。

1. LSTMからTransformerへ

LSTMは系列を1ステップずつ逐次的に処理するため、長い系列では計算効率が悪く、遠く離れたトークン間の依存関係を捉えにくいという問題がありました。TransformerのSelf-Attentionは、系列内の全トークンペアの関係を一度に計算するため、長距離依存関係を直接的に捉えることができます。

ELMoでは位置 $k$ のトークンが位置 $1$ の情報を得るためには、LSTMの隠れ状態が $k – 1$ ステップかけて伝搬する必要がありました。情報が「バケツリレー」のように中間ステップを経由するため、途中で情報が劣化する可能性があります。一方、TransformerのSelf-Attentionでは、位置 $k$ のトークンが位置 $1$ のトークンに直接アテンションをかけることができ、情報の伝搬経路の長さは常に $O(1)$ です。

2. feature-basedからfine-tuningへ

ELMoは事前学習されたbiLMの重みを固定し、その出力を特徴量として使うfeature-basedアプローチでした。一方、GPTとBERTは事前学習されたモデル全体のパラメータを下流タスクのデータで微調整するfine-tuningアプローチを採用しました。

fine-tuningでは、事前学習で獲得した言語知識がタスク固有のデータによって洗練されるため、feature-basedアプローチよりも高い性能が得られるケースが多くなります。

GPTのアプローチ — 左から右への自己回帰

GPT(Radford et al., 2018)は、Transformer Decoderを用いた自己回帰言語モデルです。テキストを左から右に読み、次のトークンを予測するように事前学習されます。

$$ P(t_k \mid t_1, t_2, \ldots, t_{k-1}) $$

GPTの事前学習目的関数は以下のとおりです。

$$ \mathcal{L}_{\text{GPT}} = -\sum_{k=1}^{N} \log P(t_k \mid t_1, \ldots, t_{k-1}; \bm{\Theta}) $$

GPTは左側の文脈のみを使って次のトークンを予測するため、各トークンの表現には右側の情報が含まれません。”The cat sat on the bank of the river” という文で “bank” の表現を計算するとき、GPTは “The cat sat on the” までの情報しか使えず、”of the river”(川岸であることを示す決定的な手がかり)を活用できないのです。

BERTのアプローチ — 真の双方向性

BERT(Devlin et al., 2019)は、この一方向性の限界を克服するために、Transformer Encoderを用いてマスク言語モデル(Masked Language Model, MLM)という巧妙な事前学習タスクを導入しました。

MLMでは、入力トークンの一部(15%)をランダムにマスクし、マスクされたトークンをその周囲の文脈から予測します。

$$ \mathcal{L}_{\text{MLM}} = -\sum_{i \in \mathcal{M}} \log P(t_i \mid \bm{t}_{\backslash \mathcal{M}}; \bm{\Theta}) $$

ここで $\mathcal{M}$ はマスクされたトークンのインデックス集合、$\bm{t}_{\backslash \mathcal{M}}$ はマスクされていないトークン列です。

BERTの画期的な点は、マスクされたトークンを予測する際に、そのトークンの左右両方の文脈を同時に活用できることです。Transformer EncoderのSelf-Attentionでは、各トークンが入力系列の全てのトークン(マスクされたものを除く)にアテンションをかけます。これにより、”The cat sat on the [MASK] of the river” という入力に対して、[MASK] の表現には “The cat sat on the” と “of the river” の両方の情報が反映されます。

これがELMoとの決定的な違いです。ELMoの双方向LSTMでは、順方向と逆方向が独立に学習されるため、位置 $k$ の順方向表現 $\overrightarrow{\bm{h}}_{k}$ には右側の文脈が含まれず、逆方向表現 $\overleftarrow{\bm{h}}_{k}$ には左側の文脈が含まれません。両者を連結しても、左右の情報が「融合」されるわけではなく、「並置」されるだけです。

一方、BERTのTransformer Encoderでは、各層のSelf-Attentionが左右両方の文脈を同時に参照してトークン表現を更新するため、左右の情報が各層で融合されます。12層のBERT-Baseでは、この融合が12回繰り返されるため、非常に豊かな双方向の文脈表現が獲得されます。

BERTの文脈表現がどのように構築されるか、その内部構造をもう少し詳しく見ていきましょう。

BERTの文脈表現 — 各層が捉える情報

入力表現の構成

BERTの入力表現は、3つの埋め込みの和で構成されます。

$$ \bm{E}_k = \bm{E}_{\text{token}}(t_k) + \bm{E}_{\text{segment}}(s_k) + \bm{E}_{\text{position}}(k) $$

各成分を説明します。

$\bm{E}_{\text{token}}(t_k) \in \mathbb{R}^{d_{\text{model}}}$ はトークン埋め込みです。WordPieceトークナイザで分割されたサブワードに対する学習済みの埋め込みベクトルです。語彙サイズは約30,000です。

$\bm{E}_{\text{segment}}(s_k) \in \mathbb{R}^{d_{\text{model}}}$ はセグメント埋め込みです。入力が2つの文で構成される場合(例: 質問応答タスクでの質問文と回答候補文)、各トークンがどちらの文に属するかを示します。

$\bm{E}_{\text{position}}(k) \in \mathbb{R}^{d_{\text{model}}}$ は位置埋め込みです。Transformerには再帰構造がないため、トークンの位置情報を明示的に与える必要があります。BERTでは学習可能な位置埋め込みを使用します(最大512位置)。

[CLS]トークンと[SEP]トークン

BERTは入力系列の先頭に特殊トークン [CLS] を挿入します。このトークンの最終層の隠れ状態は、入力系列全体の要約表現として使われます。分類タスクでは、[CLS] トークンの出力ベクトルに線形分類器を接続して予測を行います。

$$ \hat{y} = \text{softmax}(\bm{W} \cdot \bm{h}_{\text{[CLS]}}^{(L)} + \bm{b}) $$

ここで $\bm{h}_{\text{[CLS]}}^{(L)}$ は最終層(第 $L$ 層)の [CLS] トークンの隠れ状態、$\bm{W}$ と $\bm{b}$ はタスク固有の分類ヘッドのパラメータです。

[CLS] トークンが入力全体の要約として機能する理由は、Self-Attentionの仕組みにあります。[CLS] トークンは系列内の全てのトークンにアテンションをかけることができるため、12層のTransformer Encoderを通過する間に、全トークンの情報を集約する役割を担います。

また、文と文の区切りには [SEP] トークンが挿入されます。入力の全体構造は次のようになります。

[CLS] token_1 token_2 ... token_m [SEP] token_m+1 ... token_n [SEP]

各層が捉える言語情報の階層構造

BERTの各層は、ELMoと同様に異なるレベルの言語情報を捉えていますが、Transformerの表現力の高さから、より豊かな情報が各層にエンコードされています。Jawahar et al. (2019) やTenney et al. (2019) の分析から、以下のような傾向が報告されています。

下位層(第1〜4層): 表層的な特徴と構文情報を主に捉えます。品詞タグの情報が最もよく反映され、依存関係の構文木に近い構造がアテンションパターンに現れます。

中間層(第5〜8層): 構文情報と意味情報の両方を捉えます。係り受け解析、共参照解決(”he”がどの名詞を指すか)、意味役割付与(誰が何をしたか)などの情報がこの層に集中しています。

上位層(第9〜12層): タスク固有の情報とより抽象的な意味表現を捉えます。文の含意関係の判定(AがBを意味するか)や感情分析に有用な情報がこの層から得られます。

この階層構造は、言語学における「音韻→形態→構文→意味→語用」という言語処理の階層と対応しています。BERTは大量のテキストからの自己教師学習だけで、人間の言語学者が体系化した言語処理の階層を自発的に獲得しているのです。

WordPieceトークナイゼーションと文脈表現

BERTはWordPieceトークナイゼーションを使用するため、1つの単語が複数のサブワードに分割されることがあります。たとえば “playing” は “play” + “##ing” に分割されます。

この場合、文脈付き埋め込みは各サブワードに対して出力されます。単語レベルの表現が必要な場合は、一般的に以下の方法が使われます。

  • 先頭サブワードの使用: “play” のベクトルを “playing” の表現とする
  • 平均プーリング: “play” と “##ing” のベクトルの平均を取る
  • 最大プーリング: 各次元の最大値を取る

それぞれの方法にトレードオフがあります。先頭サブワードの使用は最もシンプルですが、語尾の情報(活用形の違いなど)が失われます。平均プーリングは全サブワードの情報を均等に集約しますが、重要度の違いは考慮されません。タスクに応じて適切な方法を選択する必要があります。

ここまでで、ELMoとBERTの文脈付き埋め込みの仕組みをそれぞれ見てきました。では、両者を直接比較して、設計の違いがどのような性能差をもたらすのかを整理しましょう。

ELMo vs BERT — 設計思想の比較

アーキテクチャの比較

ELMoとBERTの設計を表形式で比較します。

観点 ELMo BERT
ベースアーキテクチャ 双方向LSTM(2層) Transformer Encoder(12/24層)
双方向性 独立な順方向+逆方向の連結 真の双方向(Self-Attention)
事前学習タスク 言語モデル(次単語予測) MLM + NSP
パラメータ数 約93.6M 110M(Base)/ 340M(Large)
利用方法 Feature-based(特徴量として追加) Fine-tuning(全パラメータ微調整)
入力表現 Char-CNN + biLSTM WordPiece + 位置・セグメント埋め込み
文脈の範囲 LSTMの記憶に依存(遠距離は劣化) 系列全体(Self-Attention)

双方向性の違い — 独立連結と真の融合

ELMoとBERTの最も根本的な違いは、「双方向性」の実現方法です。この違いを具体的な例で説明しましょう。

“He went to the bank to deposit money” という文で、”bank” の文脈表現を計算する場合を考えます。

ELMoの場合:

  • 順方向LSTM: “He went to the” → $\overrightarrow{\bm{h}}_{\text{bank}}$(左文脈のみ)
  • 逆方向LSTM: “money deposit to” → $\overleftarrow{\bm{h}}_{\text{bank}}$(右文脈のみ)
  • 最終表現: $[\overrightarrow{\bm{h}}_{\text{bank}} ; \overleftarrow{\bm{h}}_{\text{bank}}]$(連結)

左文脈の情報と右文脈の情報は独立に計算され、最後に連結されるだけです。$\overrightarrow{\bm{h}}_{\text{bank}}$ を計算する時点では “deposit money” の情報は使えず、$\overleftarrow{\bm{h}}_{\text{bank}}$ を計算する時点では “He went to the” の情報は使えません。

BERTの場合:

第1層のSelf-Attentionで、”bank” は “He”, “went”, “to”, “the”, “to”, “deposit”, “money” のすべてに同時にアテンションをかけます。さらに第2層では、第1層で文脈情報を含んだ “deposit” や “money” のトークン表現を参照するため、間接的にさらに広い文脈を取り込みます。これが12層繰り返されるため、”bank” の最終表現には左右両方の文脈が完全に融合されています。

数式で表すと、BERTの第 $l$ 層での “bank” の更新は以下のようになります。

$$ \bm{h}_{\text{bank}}^{(l)} = \text{TransformerLayer}\left(\bm{h}_1^{(l-1)}, \bm{h}_2^{(l-1)}, \ldots, \bm{h}_N^{(l-1)}\right)_{\text{bank}} $$

入力系列の全トークンの前層の表現が同時に参照されるため、左右の文脈の融合が自然に行われるのです。

feature-based vs fine-tuning

feature-based(ELMo): 事前学習モデルの重みは固定し、出力を特徴量として利用します。

利点: – 既存のタスク固有モデルに簡単に統合できます – 事前学習モデルの計算を1回だけ行い、結果をキャッシュできます – タスクモデルの設計に自由度があります

欠点: – 事前学習モデルの知識がタスクに最適化されません – タスクの教師信号が事前学習モデルの内部表現に反映されません

fine-tuning(BERT): 事前学習モデル全体のパラメータをタスクデータで微調整します。

利点: – 事前学習で獲得した知識がタスクに合わせて洗練されます – エンドツーエンドで最適化できるため、一般に高い性能を達成します – タスクごとに必要な追加パラメータが少なく済みます(分類ヘッドのみ)

欠点: – タスクごとにモデル全体の複製が必要です – 推論時に事前学習モデル全体を実行する必要があります

性能比較

Peters et al. (2018) の報告によると、ELMoを追加するだけで、6つのNLPタスク(質問応答、テキスト含意、感情分析、固有表現抽出、意味役割付与、共参照解決)で平均して相対誤差が6〜25%改善されました。

しかし、BERT (Devlin et al., 2019) はこれをさらに大幅に上回りました。GLUEベンチマーク(自然言語理解の総合評価)で平均スコアを当時の最高水準から7ポイント以上引き上げ、SQuAD 2.0(質問応答)ではF1スコアで人間のパフォーマンスに匹敵する結果を出しました。

この性能差の主な要因は、Transformerの表現力の高さ、真の双方向性、そしてfine-tuningによるタスク適応の効果にあります。

実際にPythonでコードを動かして、ELMoとBERTの文脈表現の違いを体感してみましょう。多義語 “bank” を異なる文脈で使ったときに、各モデルがどのようなベクトルを出力するかを比較します。

Pythonでの比較実験 — 多義語 “bank” の文脈ベクトルを比較する

実験の目的

ここでは、同じ単語 “bank” が異なる文脈に置かれたときに、BERT(Hugging Face の transformers ライブラリを使用)がどのように異なる文脈付き埋め込みを生成するかを実験します。以下の3つの文を用意します。

  1. 銀行の文脈: “I deposited money in the bank.”
  2. 川岸の文脈: “The boat was anchored near the bank of the river.”
  3. 銀行の文脈(別表現): “She works at a major bank in the city.”

文脈付き埋め込みが正しく機能していれば、文1と文3の “bank” は近いベクトルを持ち(どちらも銀行の意味)、文2の “bank” はそれらとは離れたベクトルを持つはずです。

BERTによる文脈埋め込みの抽出

import torch
import numpy as np
import matplotlib.pyplot as plt
from transformers import BertTokenizer, BertModel

# BERTモデルとトークナイザの読み込み
tokenizer = BertTokenizer.from_pretrained('bert-base-uncased')
model = BertModel.from_pretrained('bert-base-uncased', output_hidden_states=True)
model.eval()

# 3つの文を用意("bank" が異なる文脈で使用される)
sentences = [
    "I deposited money in the bank.",          # 銀行
    "The boat was anchored near the bank of the river.",  # 川岸
    "She works at a major bank in the city.",   # 銀行
]
labels = ["bank (銀行: deposit)", "bank (川岸: river)", "bank (銀行: works)"]

def get_bank_embedding(sentence, target_word="bank", layer=-1):
    """
    文中の target_word に対応するBERTの隠れ状態を取得する。
    layer: 使用する層のインデックス(-1で最終層)
    """
    inputs = tokenizer(sentence, return_tensors="pt", padding=False)
    tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])

    with torch.no_grad():
        outputs = model(**inputs)

    # 全層の隠れ状態を取得
    hidden_states = outputs.hidden_states  # (num_layers+1, batch, seq_len, hidden_dim)

    # target_word のトークン位置を特定
    target_indices = [i for i, tok in enumerate(tokens) if tok == target_word]

    if not target_indices:
        raise ValueError(f"'{target_word}' not found in tokens: {tokens}")

    target_idx = target_indices[0]

    # 指定した層の隠れ状態を取得
    embedding = hidden_states[layer][0, target_idx, :]

    return embedding.numpy(), tokens

# 各文から "bank" のBERT埋め込みを取得
embeddings = []
for sent in sentences:
    emb, tokens = get_bank_embedding(sent, layer=-1)
    embeddings.append(emb)
    print(f"文: {sent}")
    print(f"  トークン: {tokens}")
    print(f"  埋め込みの形状: {emb.shape}")
    print()

このコードでは、BERTの最終層(第12層)の隠れ状態から “bank” トークンのベクトルを抽出しています。output_hidden_states=True を指定することで、全13層(入力埋め込み + 12層のTransformer Encoder)の隠れ状態にアクセスできます。

コサイン類似度の計算と可視化

from itertools import combinations

# コサイン類似度を計算
def cosine_similarity(a, b):
    return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b))

# 全ペアのコサイン類似度を計算
print("=== コサイン類似度(BERT最終層)===")
sim_matrix = np.zeros((3, 3))
for i in range(3):
    for j in range(3):
        sim = cosine_similarity(embeddings[i], embeddings[j])
        sim_matrix[i, j] = sim

for i, j in combinations(range(3), 2):
    print(f"  {labels[i]} vs {labels[j]}: {sim_matrix[i,j]:.4f}")

# ヒートマップで可視化
fig, ax = plt.subplots(figsize=(7, 6))
im = ax.imshow(sim_matrix, cmap='YlOrRd', vmin=0.5, vmax=1.0)
ax.set_xticks(range(3))
ax.set_yticks(range(3))
short_labels = ["bank\n(deposit)", "bank\n(river)", "bank\n(works)"]
ax.set_xticklabels(short_labels, fontsize=10)
ax.set_yticklabels(short_labels, fontsize=10)
ax.set_title("Cosine Similarity of 'bank' in Different Contexts\n(BERT Last Layer)", fontsize=12)

# 各セルに数値を表示
for i in range(3):
    for j in range(3):
        ax.text(j, i, f"{sim_matrix[i,j]:.3f}",
                ha="center", va="center", fontsize=14,
                color="white" if sim_matrix[i,j] > 0.85 else "black")

plt.colorbar(im, ax=ax, label="Cosine Similarity")
plt.tight_layout()
plt.show()

このヒートマップから、BERTの文脈付き埋め込みの威力が明確に読み取れます。まず、同じ「銀行」の意味で使われている文1(deposit)と文3(works)の “bank” のコサイン類似度は高い値を示します。一方、「川岸」の意味で使われている文2(river)の “bank” は、文1・文3の “bank” とのコサイン類似度が明らかに低くなります。これは、BERTが文脈に基づいて “bank” の意味を区別していることの証拠です。静的な埋め込みでは3つの文すべてで同じベクトルが出力されるため、類似度はすべて1.0になってしまい、このような区別は不可能です。

各層での文脈表現の変化

BERTの各層で “bank” の表現がどのように変化するかを追跡してみましょう。

# 全層から "bank" の埋め込みを取得
layer_embeddings = {i: [] for i in range(13)}  # 0層(入力)〜12層

for sent in sentences:
    inputs = tokenizer(sent, return_tensors="pt")
    tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
    target_idx = [i for i, t in enumerate(tokens) if t == "bank"][0]

    with torch.no_grad():
        outputs = model(**inputs)

    for layer_idx in range(13):
        emb = outputs.hidden_states[layer_idx][0, target_idx, :].numpy()
        layer_embeddings[layer_idx].append(emb)

# 各層での「銀行 vs 川岸」のコサイン類似度を追跡
sim_same_meaning = []   # 文1 vs 文3(同じ意味: 銀行 vs 銀行)
sim_diff_meaning = []   # 文1 vs 文2(異なる意味: 銀行 vs 川岸)

for layer_idx in range(13):
    embs = layer_embeddings[layer_idx]
    sim_same_meaning.append(cosine_similarity(embs[0], embs[2]))
    sim_diff_meaning.append(cosine_similarity(embs[0], embs[1]))

fig, ax = plt.subplots(figsize=(10, 6))
layers = list(range(13))
ax.plot(layers, sim_same_meaning, 'o-', color='#2196F3', linewidth=2,
        markersize=8, label='Same meaning (bank=deposit vs bank=works)')
ax.plot(layers, sim_diff_meaning, 's-', color='#F44336', linewidth=2,
        markersize=8, label='Different meaning (bank=deposit vs bank=river)')
ax.fill_between(layers, sim_same_meaning, sim_diff_meaning,
                alpha=0.15, color='gray')
ax.set_xlabel('BERT Layer', fontsize=12)
ax.set_ylabel('Cosine Similarity', fontsize=12)
ax.set_title("How BERT Separates Word Senses Across Layers", fontsize=13)
ax.set_xticks(layers)
ax.set_xticklabels([f"{'Emb' if i==0 else i}" for i in layers])
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

このグラフからは非常に興味深い現象が観察できます。入力層(Layer 0)では、”bank” のトークン埋め込みはどの文脈でも同一(または非常に近い)であるため、両方の類似度が高くなります。しかし、層が深くなるにつれて、同じ意味(銀行同士)の類似度は高く維持される一方、異なる意味(銀行 vs 川岸)の類似度は低下していきます。つまり、BERTは層を重ねるごとに文脈情報を蓄積し、多義語の意味を徐々に分離していくのです。この「意味分離のギャップ」は特に中間層から上位層で顕著になり、BERTの上位層ほど意味的な情報が豊かであるという先行研究の知見と合致しています。

文脈ベクトルのt-SNE可視化

最後に、異なる文脈での “bank” のベクトルに加えて、関連する単語のベクトルも含めてt-SNEで2次元に射影し、意味空間上での配置を可視化します。

from sklearn.manifold import TSNE

# 追加の文を用意して、意味空間の広がりを観察
all_sentences = [
    ("I deposited money in the bank.", "bank", "bank(銀行1)"),
    ("She works at a major bank in the city.", "bank", "bank(銀行2)"),
    ("The bank approved my loan application.", "bank", "bank(銀行3)"),
    ("The boat was anchored near the bank of the river.", "bank", "bank(川岸1)"),
    ("We sat on the grassy bank watching the water.", "bank", "bank(川岸2)"),
    ("He deposited a check at the financial institution.", "institution", "institution"),
    ("The river flowed gently past the shore.", "shore", "shore"),
]

all_embeddings = []
all_labels = []
all_colors = []

color_map = {
    "bank(銀行1)": "#2196F3", "bank(銀行2)": "#2196F3", "bank(銀行3)": "#2196F3",
    "bank(川岸1)": "#4CAF50", "bank(川岸2)": "#4CAF50",
    "institution": "#FF9800", "shore": "#9C27B0",
}

for sent, target, label in all_sentences:
    inputs = tokenizer(sent, return_tensors="pt")
    tokens = tokenizer.convert_ids_to_tokens(inputs['input_ids'][0])
    target_idx = [i for i, t in enumerate(tokens) if t == target][0]

    with torch.no_grad():
        outputs = model(**inputs)

    emb = outputs.hidden_states[-1][0, target_idx, :].numpy()
    all_embeddings.append(emb)
    all_labels.append(label)
    all_colors.append(color_map[label])

# t-SNEで2次元に圧縮
all_embeddings_np = np.array(all_embeddings)
tsne = TSNE(n_components=2, random_state=42, perplexity=3)
embeddings_2d = tsne.fit_transform(all_embeddings_np)

# 可視化
fig, ax = plt.subplots(figsize=(10, 8))
for i, (x, y) in enumerate(embeddings_2d):
    ax.scatter(x, y, c=all_colors[i], s=200, zorder=5, edgecolors='white', linewidth=1.5)
    ax.annotate(all_labels[i], (x, y), fontsize=10,
                xytext=(8, 8), textcoords='offset points')

# 凡例用のダミープロット
ax.scatter([], [], c="#2196F3", s=100, label='bank (銀行)')
ax.scatter([], [], c="#4CAF50", s=100, label='bank (川岸)')
ax.scatter([], [], c="#FF9800", s=100, label='institution (金融機関)')
ax.scatter([], [], c="#9C27B0", s=100, label='shore (岸)')

ax.set_title("t-SNE Visualization of Contextual Embeddings\n(BERT Last Layer)", fontsize=13)
ax.legend(fontsize=10, loc='best')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

t-SNEの可視化から、BERTの文脈付き埋め込みが意味に基づいたクラスタリングを実現していることが確認できます。「銀行」の意味の “bank” は互いに近くに配置され、”institution”(金融機関)とも近い位置に現れます。一方、「川岸」の意味の “bank” は別のクラスタを形成し、”shore”(岸)と近い位置に配置されます。同じ綴りの “bank” であっても、文脈に応じて意味空間上の異なる領域に配置される — これが文脈付き埋め込みの本質です。静的埋め込みではすべての “bank” が同一の点に集約されてしまうため、このような意味的な区別は原理的に不可能でした。

まとめ

本記事では、静的な単語埋め込みの限界を出発点として、ELMoとBERTによる文脈付き埋め込みの進化を解説しました。

  • 静的埋め込みの限界: Word2Vec/GloVeは1語1ベクトルであるため、多義語の文脈依存的な意味を表現できません。”bank” のように、異なる意味が1つのベクトルに混合されてしまいます
  • ELMoの革新: 双方向LSTMの各層の隠れ状態を重み付き結合することで、同じ単語でも文脈に応じて異なるベクトルを出力する文脈付き埋め込みを実現しました。タスクごとに層重みを調整するfeature-basedアプローチも重要な設計判断です
  • ELMoの数学的構造: 順方向・逆方向LSTMの $L$ 層の隠れ状態から、ソフトマックスで正規化されたタスク固有の重み $s_j^{\text{task}}$ とスケーリング因子 $\gamma^{\text{task}}$ を使って文脈付き埋め込みを計算します
  • GPT/BERTへの転換: LSTMからTransformerへ、feature-basedからfine-tuningへという2つのパラダイムシフトにより、大幅な性能向上が実現されました
  • BERTの真の双方向性: Self-Attentionにより左右の文脈を同時に融合する真の双方向表現を実現し、ELMoの「独立な連結」とは質的に異なる文脈表現を生成します
  • 実験での確認: 多義語 “bank” を用いた実験で、BERTが文脈に基づいて同じ単語の異なる意味を分離し、意味的に近い単語のクラスタを形成することを確認しました

文脈付き埋め込みは、現代のNLPの基盤技術です。ELMoで始まり、BERTで大きく発展したこの技術は、その後のGPT-2/3/4、T5、RoBERTaなどの大規模言語モデルにも引き継がれています。

次のステップとして、以下の記事も参考にしてください。

画像なし
BERTとGPTの違い — エンコーダ・デコーダアーキテクチャの比較
BERTとGPTのアーキテクチャの違いを、エンコーダとデコーダの構造、事前学習タスク、ファインチューニング戦略の観点から比較します。
画像なし
ファインチューニングと転移学習の理論を解説
転移学習の理論的背景からファインチューニングの数学的定式化まで丁寧に解説します。
画像なし
RoBERTaの改良点と性能向上 — BERTの学習設定を最適化した設計判断
BERTの学習設定をどのように改良してRoBERTaが性能向上を実現したかを解説します。
画像なし
マスク言語モデル(MLM)の理論と実装
BERTの事前学習の核心であるMLMの理論をスクラッチ実装とともに理解します。