MLP + Bag of Words는 간단했지만, 모든 단어를 똑같이 취급했습니다. 하지만 실제로는 어떤 단어는 중요하고, 어떤 단어는 그렇지 않죠!
"Apple", "iPhone", "camera"는 핵심 키워드인데,
"with", "new" 같은 흔한 단어와 똑같이 취급됩니다. 😞
TF-IDF (Term Frequency - Inverse Document Frequency)는 단어가 얼마나 중요한지 숫자로 나타냅니다!
TF-IDF = TF × IDF
1. TF (Term Frequency): 문서 내에서 단어가 얼마나 자주 나오나?
$$ \text{TF}(t, d) = \frac{\text{단어 } t \text{의 등장 횟수}}{\text{문서 } d \text{의 총 단어 수}} $$
2. IDF (Inverse Document Frequency): 이 단어가 여러 문서에서 흔한가?
$$ \text{IDF}(t) = \log \frac{\text{전체 문서 수}}{\text{단어 } t \text{를 포함한 문서 수}} $$
3. TF-IDF: 두 값을 곱하면 끝!
$$ \text{TF-IDF}(t, d) = \text{TF}(t, d) \times \text{IDF}(t) $$
from sklearn.feature_extraction.text import TfidfVectorizer # 문서 리스트 documents = [ "iPhone is great", "Galaxy is great", "Pixel is great" ] # TF-IDF 벡터화 vectorizer = TfidfVectorizer() tfidf_matrix = vectorizer.fit_transform(documents) # 결과 확인 print(vectorizer.get_feature_names_out()) # ['galaxy' 'great' 'iphone' 'is' 'pixel'] print(tfidf_matrix[0].toarray()) # 문서1의 TF-IDF # [[0. 0.408 0.707 0. 0. ]] # galaxy great iphone is pixel # ↑ ↑ → iPhone이 가장 높음!
CNN은 이미지만을 위한 게 아닙니다! 텍스트에서도 지역적 패턴(n-gram)을 찾을 수 있어요.
텍스트 CNN의 장점:
• n-gram 패턴 감지: "not good" 같은 연속된 단어 조합 인식
• 위치 불변성: "very good"이 문장 어디 있든 인식
• 병렬 처리: RNN보다 빠름
이미지 CNN과 차이점:
• 1D Conv 사용 (가로 방향만 슬라이딩)
• 필터 크기 = n-gram 크기 (보통 2, 3, 4)
TF-IDF + CNN을 결합한 모델을 만들어봅시다!
import torch import torch.nn as nn class TextCNN(nn.Module): def __init__(self, vocab_size, num_classes=4): super().__init__() # TF-IDF 입력을 1D Conv에 맞게 변환 # [batch, vocab_size] → [batch, 1, vocab_size] # 여러 크기의 필터 사용 (2-gram, 3-gram, 4-gram) self.conv1 = nn.Conv1d(1, 100, kernel_size=3) self.conv2 = nn.Conv1d(1, 100, kernel_size=4) self.conv3 = nn.Conv1d(1, 100, kernel_size=5) self.dropout = nn.Dropout(0.5) self.fc = nn.Linear(300, num_classes) # 100*3 = 300 def forward(self, x): # x: [batch, vocab_size] x = x.unsqueeze(1) # [batch, 1, vocab_size] # 3가지 크기의 필터로 특징 추출 x1 = torch.relu(self.conv1(x)) # [batch, 100, L1] x2 = torch.relu(self.conv2(x)) # [batch, 100, L2] x3 = torch.relu(self.conv3(x)) # [batch, 100, L3] # Global Max Pooling (각 필터별 최댓값) x1 = torch.max(x1, dim=2)[0] # [batch, 100] x2 = torch.max(x2, dim=2)[0] x3 = torch.max(x3, dim=2)[0] # 결합 x = torch.cat([x1, x2, x3], dim=1) # [batch, 300] x = self.dropout(x) x = self.fc(x) return x # 모델 생성 model = TextCNN(vocab_size=20000, num_classes=4) print(f"파라미터 수: {sum(p.numel() for p in model.parameters()):,}")
TF-IDF + CNN으로 개선했지만, 여전히 한계가 있습니다:
문제점:
• TF-IDF는 여전히 희소 벡터 (대부분이 0)
• 단어 간 의미 관계를 표현 못 함
• "cat"과 "dog"이 둘 다 동물인 걸 모름
다음 Step 11에서 Word2Vec + RNN으로 단어 의미를 학습해봅시다! 🚀