1. 単語ベクトルの技術背景
自然言語処理(NLP)では、単語を効果的に表現することが重要な課題です。従来のone-hotエンコーディングは簡単ですが、次元の呪いと単語間の類似性を表現できないという問題があります。単語ベクトル(Word Embedding)技術はこれらの問題を解決し、単語を低次元の連続ベクトル空間にマッピングすることで、意味的に近い単語がベクトル空間でも近い位置に配置されます。この分散表現は、テキスト分類や機械翻訳などのNLPタスクに強力な基盤を提供します。
2. CBOWモデルの原理
Continuous Bag-of-Words (CBOW) モデルは、Mikolovらによって2013年に提案された古典的な単語ベクトル学習モデルです。その基本的なアイデアは、文脈の単語から現在の単語を予測することです。
2.1 モデル構造
CBOWモデルは以下の3層で構成されています:
- 入力層:文脈の単語のone-hot表現
- 隠れ層:単語の分散表現を学習する埋め込み層
- 出力層:目標単語の確率分布を予測
import torch.nn as nn
class CBOW(nn.Module):
def __init__(self, vocab_size, embedding_dim):
super(CBOW, self).__init__()
self.embeddings = nn.Embedding(vocab_size, embedding_dim)
self.linear1 = nn.Linear(embedding_dim, 128)
self.linear2 = nn.Linear(128, vocab_size)
def forward(self, inputs):
embeds = sum(self.embeddings(inputs)).view(1, -1)
out = nn.functional.relu(self.linear1(embeds))
out = self.linear2(out)
log_probs = nn.functional.log_softmax(out, dim=-1)
return log_probs
2.2 学習過程
- 文脈の単語をone-hotベクトルに変換
- 埋め込み層を通じて各単語の単語ベクトルを取得
- 文脈の単語ベクトルの平均または合計を計算
- ニューラルネットワークを通じて目標単語を予測
- 逆伝播法を使用してモデルパラメータを最適化
3. コード実装解析
3.1 データ準備
まず、語彙表を作成し、単語をインデックスに変換します。
vocab = set(raw_text)
word_to_idx = {word: i for i, word in enumerate(vocab)}
idx_to_word = {i: word for i, word in enumerate(vocab)}
3.2 トレーニングデータの作成
例えば、ウィンドウサイズが2の場合、文脈-目標単語のペアを取得します。
CONTEXT_SIZE = 2
data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
context = (
[raw_text[i - (2 - j)] for j in range(CONTEXT_SIZE)]
+ [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]
)
target = raw_text[i]
data.append((context, target))
3.3 モデルのトレーニング
負の対数尤度損失関数とAdamオプティマイザを使用します。
import torch.optim as optim
loss_function = nn.NLLLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(200):
total_loss = 0
for context, target in data:
context_vector = make_context_vector(context, word_to_idx).to(device)
target_tensor = torch.tensor([word_to_idx[target]]).to(device)
model.zero_grad()
log_probs = model(context_vector)
loss = loss_function(log_probs, target_tensor)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch}, Loss: {total_loss}")
4. 単語ベクトルの可視化と応用
トレーニングが完了したら、埋め込み層から単語ベクトルを抽出できます。
word_2_vec = {}
for word in word_to_idx.keys():
word_2_vec[word] = w[word_to_idx[word], :]
これらの単語ベクトルは以下のように使用できます:
- 単語間の類似度の計算
- 下流タスクの入力特徴量
- 単語のクラスタリング分析
5. まとめと展望
CBOWモデルは目標単語を予測することで単語ベクトルを学習し、高速なトレーニングと高頻度の単語に対する良い性能が特徴です。本記事ではPyTorchを使用してCBOWモデルを完全に実装し、単語ベクトルの抽出プロセスを示しました。
完全なコード:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from tqdm import tqdm
CONTEXT_SIZE = 2
raw_text = """We are about to study the idea of a computational process.
Computational processes are abstract beings that inhabit computers.
As they evolve, processes manipulate other abstract things called data.
The evolution of a process is directed by a pattern of rulescalled a program.
People create programs to direct processes. In effectwe ,
conjure the spirits of the computer with our spells.""".split()
vocab = set(raw_text)
vocab_size = len(vocab)
word_to_idx = {word: i for i, word in enumerate(vocab)}
idx_to_word = {i: word for i, word in enumerate(vocab)}
data = []
for i in range(CONTEXT_SIZE, len(raw_text) - CONTEXT_SIZE):
context = (
[raw_text[i - (2 - j)] for j in range(CONTEXT_SIZE)]
+ [raw_text[i + j + 1] for j in range(CONTEXT_SIZE)]
)
target = raw_text[i]
data.append((context, target))
def make_context_vector(context, word_to_ix):
idxs = [word_to_idx[w] for w in context]
return torch.tensor(idxs, dtype=torch.long)
device = "cuda" if torch.cuda.is_available() else "cpu"
print(f"Using {device} device")
model = CBOW(vocab_size, 10).to(device)
optimizer = optim.Adam(model.parameters(), lr=0.001)
loss_function = nn.NLLLoss()
for epoch in tqdm(range(200)):
total_loss = 0
for context, target in data:
context_vector = make_context_vector(context, word_to_idx).to(device)
target_tensor = torch.tensor([word_to_idx[target]]).to(device)
model.zero_grad()
log_probs = model(context_vector)
loss = loss_function(log_probs, target_tensor)
loss.backward()
optimizer.step()
total_loss += loss.item()
print(f"Epoch {epoch}, Loss: {total_loss}")
w = model.embeddings.weight.cpu().detach().numpy()
word_2_vec = {word: w[word_to_idx[word], :] for word in word_to_idx.keys()}
print(word_2_vec)