본 포스팅은 패스트캠퍼스 환급 챌린지 참여를 위해 작성하였습니다.

https://abit.ly/lisbva

 

공부 시작

강의 종료

강의장

학습 인증샷

학습후기

 

임베딩이란

데이터를 벡터 공간으로 변환하는 것

복잡한 표현을 컴퓨터가 학습 가능한 수치 벡터로 변환 하는것.

 

["king", "queen", "man", "woman"] 이런 데이터를 

단어임베딩 벡터 (예: 3차원)

king [0.52, -1.23, 0.77]
queen [0.48, -1.10, 0.81]
man [0.20, -0.97, 0.44]
woman [0.19, -0.88, 0.53]

이렇게 변환

 

장점은 10만개의 단어를 10만 차원이 아닌 100~300차원 벡터로 공간을 절약할 수 있음

유사한 의미를 가진 단어를 가까운 벡터로 표현

실수 벡터이기 때문에 딥러닝 모델 학습에 사용 가능

추천시스템에서 유저 임베딩으로 유사한 유저 찾기 가능

 

word2vec 의 핵심 아이디어는 "같은 문맥에서 자주 나오는 단어는 비슷한 의미를 가진다"라는 아이디어에서 나옴

 

1. CBOW

주변 단어를 보고 중심 단어를 예측 

 

  • 예:
    • 문장: "The cat sits on the mat"
    • context: ["The", "cat", "on", "the", "mat"]
    • center: "sits"
  • 특징:
    • 자주 나오는 단어를 더 잘 학습
    • 전체 문맥을 평균해서 예측 → 빠름
    • 희귀 단어엔 약함

 

2. Skip-gram (주로 사용됨)

  • 중심 단어(center)를 보고 주변 단어(context)를 예측
  • 예:
    • 중심: "sits" → 예측 대상: ["The", "cat", "on", "the", "mat"]
  • 특징:
    • 느리지만 희귀 단어도 잘 표현 가능
    • 추천 시스템에서 sequence를 학습할 때 주로 이 방식 사용

 

학습방식

[one-hot 단어 입력] → [임베딩 레이어] → [출력층: context 단어 예측]

  • 임베딩 레이어의 weight가 우리가 얻고 싶은 단어 임베딩
  • 훈련이 끝나면 이 weight 행렬을 그대로 임베딩 테이블로 씀

기본적으로 (입력 단어, 출력 단어) 쌍을 예측하는 방식
예를 들어 Skip-gram에서 "sits"라는 중심 단어로부터 context를 예측하면 다음처럼 학습함

 

 

각 단어는 실수 벡터(예: 100차원)로 표현됨

비슷한 단어는 벡터 공간에서 가까운 위치에 위치함

단어 사이의 관계도 벡터 연산으로 표현 가능

 

word2vec 한계

문맥 순서 정보는 반영 안 됨 (Bag-of-Words 기반)

단어 다의어(예: "bank" = 강변 or 은행) 구분 어려움

문장 수준 이해는 불가

 

코드예제

 

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from collections import Counter
import random

# 1. Sample corpus
corpus = """
we are what we repeatedly do excellence then is not an act but a habit
""".lower().split()

# 2. Vocabulary and word-to-index mapping
vocab = list(set(corpus))
vocab_size = len(vocab)
word2idx = {w: i for i, w in enumerate(vocab)}
idx2word = {i: w for w, i in word2idx.items()}

# 3. Generate Skip-gram training pairs
def generate_pairs(corpus, window_size=2):
    pairs = []
    for i, center in enumerate(corpus):
        for j in range(-window_size, window_size + 1):
            context_idx = i + j
            if j != 0 and 0 <= context_idx < len(corpus):
                context = corpus[context_idx]
                pairs.append((center, context))
    return pairs

pairs = generate_pairs(corpus)

# 4. Dataset class
class Word2VecDataset(torch.utils.data.Dataset):
    def __init__(self, pairs):
        self.pairs = [(word2idx[c], word2idx[t]) for c, t in pairs]

    def __len__(self):
        return len(self.pairs)

    def __getitem__(self, idx):
        return torch.tensor(self.pairs[idx][0]), torch.tensor(self.pairs[idx][1])

# 5. Model definition
class SkipGramModel(nn.Module):
    def __init__(self, vocab_size, embedding_dim):
        super().__init__()
        self.in_embed = nn.Embedding(vocab_size, embedding_dim)
        self.out_embed = nn.Embedding(vocab_size, embedding_dim)

    def forward(self, center, target):
        center_emb = self.in_embed(center)          # (batch, embed_dim)
        target_emb = self.out_embed(target)         # (batch, embed_dim)
        score = torch.mul(center_emb, target_emb).sum(dim=1)  # (batch)
        return score

# 6. Training
embedding_dim = 50
epochs = 100
batch_size = 16
learning_rate = 0.01

dataset = Word2VecDataset(pairs)
dataloader = torch.utils.data.DataLoader(dataset, batch_size=batch_size, shuffle=True)

model = SkipGramModel(vocab_size, embedding_dim)
optimizer = optim.Adam(model.parameters(), lr=learning_rate)

for epoch in range(epochs):
    total_loss = 0
    for center, context in dataloader:
        score = model(center, context)
        loss = F.binary_cross_entropy_with_logits(score, torch.ones_like(score))  # positive sampling only

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        total_loss += loss.item()
    if (epoch + 1) % 10 == 0:
        print(f"Epoch {epoch+1}, Loss: {total_loss:.4f}")

# 7. Get word embeddings
embeddings = model.in_embed.weight.data
print("\n'we' vector:", embeddings[word2idx['we']][:5])  # 예시 출력