PyTorchにおけるEmbeddingとTokenizerの徹底解説

## はじめに

自然言語処理（NLP）において、テキストデータを数値ベースのデータに変換するためには**Tokenizer**と**Embedding**が不可欠です。特にPyTorchでは、`torch.nn.Embedding`を用いた埋め込み操作や、カスタムTokenizerを活用することで効率的なNLPモデルの構築が可能です。

本記事では、PyTorchにおける**Tokenizer**と**Embedding**の役割と実装を詳しく解説し、具体的なコード例を通じてその挙動を確認します。

使用する例文は以下の3つです：

1. **「中野哲平は強いエンジニアになりたい」**
2. **「中野哲平は、形態素解析について勉強をしている」**
3. **「中野哲平は、東京大学大学院に在籍していた」**

---

## 1. Tokenizerの役割と実装

### 1.1 Tokenizerの基本

Tokenizerは、文章を単語やサブワード単位に分割し、数値IDに変換するプロセスを担います。この処理により、テキストデータをニューラルネットワークで扱える形式に変換できます。

### 1.2 簡単なTokenizerの実装

以下は、単純な単語ベースのTokenizerの例です：

```python
class SimpleTokenizer:
    def __init__(self):
        self.word2idx = {}
        self.idx2word = {}
        self.vocab_size = 0

    def build_vocab(self, sentences):
        unique_words = set()
        for sentence in sentences:
            unique_words.update(sentence.split())
        
        self.word2idx = {word: idx + 1 for idx, word in enumerate(unique_words)}  # 0はPAD用
        self.word2idx['<PAD>'] = 0
        self.idx2word = {idx: word for word, idx in self.word2idx.items()}
        self.vocab_size = len(self.word2idx)

    def tokenize(self, sentence):
        return [self.word2idx.get(word, 0) for word in sentence.split()]

# 使用例
sentences = [
    "中野哲平 は 強い エンジニア に なりたい",
    "中野哲平 は 形態素解析 について 勉強 を している",
    "中野哲平 は 東京大学大学院 に 在籍 していた"
]

tokenizer = SimpleTokenizer()
tokenizer.build_vocab(sentences)

for sentence in sentences:
    print(f"Original: {sentence}")
    print(f"Tokenized: {tokenizer.tokenize(sentence)}\n")
```

**出力例**：
```
Original: 中野哲平 は 強い エンジニア に なりたい
Tokenized: [3, 5, 7, 2, 8, 1]

Original: 中野哲平 は 形態素解析 について 勉強 を している
Tokenized: [3, 5, 10, 9, 6, 4, 11]

Original: 中野哲平 は 東京大学大学院 に 在籍 していた
Tokenized: [3, 5, 12, 8, 13, 14]
```

---

## 2. Embeddingの役割と実装

### 2.1 Embeddingの基本

`torch.nn.Embedding`は、語彙内の各単語IDを固定次元のベクトルにマッピングするためのレイヤーです。このベクトルは学習可能であり、文脈に沿った意味的な関係性を捉えることができます。

### 2.2 Embeddingの実装例

```python
import torch
import torch.nn as nn

# パラメータ設定
vocab_size = tokenizer.vocab_size
embedding_dim = 8  # 埋め込みベクトルの次元数

# Embeddingレイヤーの定義
embedding_layer = nn.Embedding(num_embeddings=vocab_size, embedding_dim=embedding_dim)

# サンプル文を埋め込み
for sentence in sentences:
    tokenized = tokenizer.tokenize(sentence)
    input_tensor = torch.tensor(tokenized)
    embedded = embedding_layer(input_tensor)

    print(f"Sentence: {sentence}")
    print(f"Token IDs: {tokenized}")
    print(f"Embeddings:\n{embedded}\n")
```

**出力例**：
```
Sentence: 中野哲平 は 強い エンジニア に なりたい
Token IDs: [3, 5, 7, 2, 8, 1]
Embeddings:
 tensor([[ 0.25, -0.34,  0.41, ...,  0.12],
         [-0.67,  0.13, -0.22, ...,  0.45],
         ...])
```

### 2.3 PaddingとBatch処理

バッチ処理を行う際、文の長さを揃えるためにPaddingを適用します。

```python
from torch.nn.utils.rnn import pad_sequence

# トークン化
tokenized_sentences = [torch.tensor(tokenizer.tokenize(s)) for s in sentences]

# パディング適用
padded_sentences = pad_sequence(tokenized_sentences, batch_first=True, padding_value=0)

# 埋め込み
embedded_batch = embedding_layer(padded_sentences)

print("Padded Token IDs:")
print(padded_sentences)
print("\nEmbedded Batch:")
print(embedded_batch)
```

**出力例**：
```
Padded Token IDs:
 tensor([[ 3,  5,  7,  2,  8,  1,  0],
         [ 3,  5, 10,  9,  6,  4, 11],
         [ 3,  5, 12,  8, 13, 14,  0]])

Embedded Batch:
 tensor([[[ 0.12, -0.34, ...],
          [ 0.56,  0.14, ...],
          ...],
         ...])
```

---

## 3. 高度なTokenizer：Subword Tokenization（BPE）

自然言語には多くの未知語が存在するため、単語ベースのトークナイザーだけでは限界があります。その解決策として**サブワード分割**（例：Byte Pair Encoding, WordPiece）が用いられます。

### 3.1 BPEの基本

BPEは頻出の文字ペアを繰り返し結合して新たなサブワード単位を作成し、未知語への対応力を高めます。

### 3.2 実装例（`tokenizers`ライブラリ使用）

```python
from tokenizers import ByteLevelBPETokenizer

# BPEトークナイザーの初期化
bpe_tokenizer = ByteLevelBPETokenizer()

# 学習データ
bpe_tokenizer.train_from_iterator(sentences, vocab_size=50)

# トークナイズ
for sentence in sentences:
    tokens = bpe_tokenizer.encode(sentence)
    print(f"Sentence: {sentence}")
    print(f"Tokens: {tokens.tokens}")
    print(f"Token IDs: {tokens.ids}\n")
```

**出力例**：
```
Sentence: 中野哲平は強いエンジニアになりたい
Tokens: ['中', '野', '哲', '平', 'は', '強', 'い', 'エ', 'ン', 'ジ', 'ニ', 'ア', 'に', 'な', 'り', 'たい']
Token IDs: [12, 34, 45, 23, 56, 78, 89, ...]
```

---

## 4. まとめ

- **Tokenizer**：テキストを数値IDに変換する役割を担い、単語ベースやサブワードベース（BPEなど）の手法があります。
- **Embedding**：トークンIDを固定次元のベクトルに変換し、文脈的な意味を捉える基盤となります。
- **PyTorch**：`torch.nn.Embedding`を使って簡単にエンベディングレイヤーを実装でき、バッチ処理やパディングにも対応しています。

これらの基礎を理解することで、自然言語処理タスクにおけるデータ前処理とモデル設計の質が向上します。