RNN은 순서를 처리하지만, 긴 문장에서는 초반 단어를 잊어버립니다. 이를 장기 의존성 문제(Long-term Dependency Problem)라고 해요.
왜 이런 일이 일어날까요?
Vanishing Gradient (기울기 소실):
역전파 시 기울기가 계속 곱해지면서 0에 가까워짐
→ 초반 정보가 후반까지 전달되지 못함
예시: 0.5 × 0.5 × 0.5 × ... (20번) = 0.00000095 😱
LSTM (Long Short-Term Memory)은 RNN의 이 문제를 해결합니다. 핵심은 Cell State라는 "장기기억 고속도로"를 만드는 거예요!
LSTM의 핵심 아이디어:
• Cell State: 장기기억을 담는 "고속도로"
→ 정보가 거의 변하지 않고 긴 거리를 이동 가능
• 3개의 Gate: 정보를 선택적으로 추가/제거
→ 중요한 정보는 오래 기억, 불필요한 정보는 잊기
• Gradient 전달: Cell State 덕분에 기울기 소실 방지
→ 100개 이상의 시간 단계도 학습 가능!
GloVe (Global Vectors)는 Word2Vec과 비슷하지만, 전체 말뭉치의 통계를 활용해서 더 나은 임베딩을 만듭니다.
| 특징 | Word2Vec | GloVe |
|---|---|---|
| 학습 방식 | 예측 기반 (주변 단어 예측) | 통계 기반 (동시 등장 행렬) |
| 학습 속도 | 느림 | 빠름 |
| 희귀 단어 | 약함 | 강함 |
| 성능 | 좋음 | 더 좋음 |
| 사전학습 모델 | Google News (300d) | Wikipedia (50d, 100d, 200d, 300d) |
# GloVe 사전학습 모델 다운로드 # https://nlp.stanford.edu/projects/glove/ import numpy as np def load_glove(filepath): embeddings = {} with open(filepath, 'r', encoding='utf-8') as f: for line in f: values = line.split() word = values[0] vector = np.asarray(values[1:], dtype='float32') embeddings[word] = vector return embeddings # GloVe 로드 (100차원) glove = load_glove('glove.6B.100d.txt') print(f"단어 수: {len(glove):,}") # 400,000개 print(glove['cat'].shape) # (100,)
이제 LSTM과 GloVe를 결합해서 장기 의존성 문제를 해결해봅시다!
import torch import torch.nn as nn class LSTMClassifier(nn.Module): def __init__(self, vocab_size, embedding_dim, hidden_dim, num_classes, pretrained_embeddings=None): super().__init__() # GloVe 임베딩 (freeze) self.embedding = nn.Embedding(vocab_size, embedding_dim) if pretrained_embeddings is not None: self.embedding.weight.data.copy_(pretrained_embeddings) self.embedding.weight.requires_grad = False # 🔒 Freeze! # LSTM (RNN 대신!) self.lstm = nn.LSTM( input_size=embedding_dim, hidden_size=hidden_dim, batch_first=True, num_layers=2, # 2층 LSTM dropout=0.3 # 층 사이 Dropout ) # 분류기 self.dropout = nn.Dropout(0.5) self.fc = nn.Linear(hidden_dim, num_classes) def forward(self, x): # x: [batch, seq_len] embedded = self.embedding(x) # [batch, seq_len, embedding_dim] # LSTM output, (hidden, cell) = self.lstm(embedded) # output: [batch, seq_len, hidden_dim] # hidden: [num_layers, batch, hidden_dim] # cell: [num_layers, batch, hidden_dim] ← LSTM만 있음! # 마지막 층의 hidden state 사용 final_hidden = hidden[-1] # [batch, hidden_dim] return self.fc(self.dropout(final_hidden)) # 모델 생성 model = LSTMClassifier( vocab_size=20000, embedding_dim=100, # GloVe 차원 hidden_dim=128, num_classes=4, pretrained_embeddings=glove_weights # GloVe 가중치 )
LSTM으로 많이 개선했지만, 아직 더 나아질 수 있습니다:
남은 문제:
• 임베딩이 고정(freeze)되어 있음 → 우리 데이터에 최적화 안 됨
• 모든 단어가 똑같이 중요하게 취급됨
• "not", "very" 같은 중요한 수식어를 강조하지 못함
다음 Step 13에서 Attention + FastText로 중요 단어를 집중해봅시다! 🚀