GPT(Generative Pre-trained Transformer)は、OpenAIが開発した大規模言語モデルのシリーズです。TransformerのDecoder部分のみを使用するDecoder-onlyアーキテクチャを採用し、自己回帰的にテキストを生成します。
本記事では、GPTのアーキテクチャの設計思想と、GPT-1からGPT-3への進化を解説します。
本記事の内容
- GPTとは何か
- Decoder-onlyアーキテクチャの特徴
- GPT-1, GPT-2, GPT-3の進化
- 因果的言語モデリング(Causal LM)
- トークン埋め込みと位置エンコーディング
- Pythonでの簡易実装
前提知識
この記事を読む前に、以下の記事を読んでおくと理解が深まります。
GPTとは何か
GPTは “Generative Pre-trained Transformer” の略で、以下の特徴を持ちます:
- Generative(生成的): 次のトークンを逐次予測することでテキストを生成
- Pre-trained(事前学習済み): 大規模なテキストコーパスで事前学習
- Transformer: Transformerアーキテクチャを基盤とする
GPTの革新性は、大規模な事前学習によって「言語の一般的な理解」を獲得し、少量のデータで様々なタスクに適応できる点にあります。
Decoder-onlyアーキテクチャ
BERTとの違い
| 項目 | GPT (Decoder-only) | BERT (Encoder-only) |
|---|---|---|
| 注意の方向 | 単方向(過去のみ参照) | 双方向 |
| 主な用途 | テキスト生成 | 文分類、NER等 |
| 事前学習タスク | 因果的言語モデリング | MLM + NSP |
| マスク | 因果マスク | パディングマスクのみ |
GPTブロックの構成
GPTの各ブロックは以下の2つのサブレイヤーで構成されます:
入力
↓
Masked Multi-Head Self-Attention
↓ + 残差接続 + LayerNorm
Feed-Forward Network
↓ + 残差接続 + LayerNorm
出力
Cross-Attentionは使用しません。入力系列と出力系列を単一の系列として扱います。
因果的言語モデリング
GPTの学習目標は、文脈が与えられたときの次のトークンの確率を最大化することです。
$$ \mathcal{L} = -\sum_{t=1}^{T} \log P(y_t \mid y_1, \dots, y_{t-1}; \theta) $$
この目標は因果的言語モデリング(Causal Language Modeling)と呼ばれます。
学習と推論の統一
因果マスクにより、学習時には全位置の予測を並列計算できる一方、推論時には1トークンずつ生成します。同じモデル、同じマスクパターンで両方に対応できます。
GPT-1, GPT-2, GPT-3の進化
GPT-1 (2018)
| 項目 | 値 |
|---|---|
| パラメータ数 | 117M |
| 層数 | 12 |
| d_model | 768 |
| ヘッド数 | 12 |
| 学習データ | BooksCorpus (5GB) |
GPT-1は「事前学習 + ファインチューニング」のパラダイムを確立しました。
GPT-2 (2019)
| 項目 | 値 |
|---|---|
| パラメータ数 | 1.5B |
| 層数 | 48 |
| d_model | 1600 |
| ヘッド数 | 25 |
| 学習データ | WebText (40GB) |
GPT-2は「ファインチューニングなしでも多くのタスクをこなせる」ことを示しました(Zero-shot学習)。
GPT-3 (2020)
| 項目 | 値 |
|---|---|
| パラメータ数 | 175B |
| 層数 | 96 |
| d_model | 12288 |
| ヘッド数 | 96 |
| 学習データ | 数百GBのテキスト |
GPT-3は「In-Context Learning」(文脈内学習)の能力を示しました。プロンプトに例を含めるだけで、新しいタスクに適応できます。
Pythonでの簡易実装
import torch
import torch.nn as nn
import torch.nn.functional as F
import math
class GPTBlock(nn.Module):
"""GPTのTransformerブロック"""
def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
super().__init__()
self.d_model = d_model
self.n_heads = n_heads
self.d_k = d_model // n_heads
# Multi-Head Self-Attention
self.W_qkv = nn.Linear(d_model, 3 * d_model)
self.W_o = nn.Linear(d_model, d_model)
self.attn_dropout = nn.Dropout(dropout)
# Feed-Forward Network
self.ffn = nn.Sequential(
nn.Linear(d_model, d_ff),
nn.GELU(),
nn.Linear(d_ff, d_model),
nn.Dropout(dropout)
)
# Layer Normalization (Pre-LN)
self.ln1 = nn.LayerNorm(d_model)
self.ln2 = nn.LayerNorm(d_model)
self.dropout = nn.Dropout(dropout)
def forward(self, x, mask=None):
batch_size, seq_len, _ = x.size()
# Pre-LN
h = self.ln1(x)
# QKV projection
qkv = self.W_qkv(h)
Q, K, V = qkv.chunk(3, dim=-1)
# ヘッド分割
Q = Q.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
K = K.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
V = V.view(batch_size, seq_len, self.n_heads, self.d_k).transpose(1, 2)
# Attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores + mask
attn = F.softmax(scores, dim=-1)
attn = self.attn_dropout(attn)
context = torch.matmul(attn, V)
# ヘッド結合
context = context.transpose(1, 2).contiguous().view(batch_size, seq_len, self.d_model)
attn_out = self.W_o(context)
# 残差接続
x = x + self.dropout(attn_out)
# FFN (Pre-LN)
x = x + self.ffn(self.ln2(x))
return x
class GPT(nn.Module):
"""GPTモデル"""
def __init__(self, vocab_size, d_model=768, n_heads=12, d_ff=3072,
n_layers=12, max_len=1024, dropout=0.1):
super().__init__()
self.d_model = d_model
# 埋め込み
self.token_emb = nn.Embedding(vocab_size, d_model)
self.pos_emb = nn.Embedding(max_len, d_model)
self.dropout = nn.Dropout(dropout)
# Transformerブロック
self.blocks = nn.ModuleList([
GPTBlock(d_model, n_heads, d_ff, dropout) for _ in range(n_layers)
])
# 出力層
self.ln_f = nn.LayerNorm(d_model)
self.head = nn.Linear(d_model, vocab_size, bias=False)
# 因果マスクをバッファとして登録
mask = torch.triu(torch.ones(max_len, max_len), diagonal=1)
mask = mask.masked_fill(mask == 1, float('-inf'))
self.register_buffer('causal_mask', mask)
def forward(self, x):
batch_size, seq_len = x.size()
# 位置インデックス
positions = torch.arange(seq_len, device=x.device).unsqueeze(0)
# 埋め込み
x = self.token_emb(x) + self.pos_emb(positions)
x = self.dropout(x)
# 因果マスク
mask = self.causal_mask[:seq_len, :seq_len]
# Transformerブロック
for block in self.blocks:
x = block(x, mask)
# 出力
x = self.ln_f(x)
logits = self.head(x)
return logits
# 動作確認
vocab_size = 50000
model = GPT(vocab_size=vocab_size, d_model=768, n_heads=12, d_ff=3072, n_layers=12)
x = torch.randint(0, vocab_size, (2, 128))
logits = model(x)
print(f"入力形状: {x.shape}")
print(f"出力形状: {logits.shape}")
print(f"パラメータ数: {sum(p.numel() for p in model.parameters()):,}")
まとめ
本記事では、GPTアーキテクチャについて解説しました。
- Decoder-only構造: TransformerのDecoder部分のみを使用し、因果マスクで未来の情報を遮断
- 因果的言語モデリング: 次のトークンを予測する単純な目標で事前学習
- スケーリング: GPT-1からGPT-3へとパラメータ数を増やすことで、In-Context Learning能力が創発
- Pre-LN: GPT-2以降はLayerNormをブロックの先頭に配置するPre-LN構成を採用
- 位置埋め込み: 学習可能な位置埋め込みを使用
次のステップとして、以下の記事も参考にしてください。