텍스트 벡터화 학습노트
1️⃣ Bag‑of‑Words(BOW) 개념
- 문맥을 무시하고 단어 등장 횟수로 문서를 표현
- 장점: 단순·빠름, 선형 모델과 상성 좋음
- 단점: 위치 정보 손실, 차원 폭발(희소 행렬)
▶ 통계 기반ㆍ머신러닝 기반
- 통계: 단어 등장 횟수 기반 → CountVectorizer, TF‑IDF
- ML: 벡터 입력 후 분류·군집화 등 학습
2️⃣ BoW 구현 및 비교
📙 CountVectorizer vs Keras Tokenizer
항목 CountVectorizer(sklearn) Tokenizer(Keras)
반환 형식 |
희소행렬(CSR) |
Numpy 배열 |
BoW 방식 |
fit_transform, vocabulary_ |
texts_to_matrix(..., mode='count') |
시퀀스 지원 |
❌ |
✅ (texts_to_sequences) |
📘 단어장(Vocabulary)
- 중복 없는 단어 집합. BoW와는 구분
- Tokenizer.word_counts(OrderedDict) vs vector.vocabulary_(dict)
- Tokenizer는 기본적으로 소문자화 및 구두점 제거 기능을 수행
▶ Binary BoW
- CountVectorizer(binary=True) → 단어 존재 여부만 1/0 기록
- 특정 문서에 단어가 한 번이라도 등장하면 1, 등장하지 않으면 0
- 전통적인 BoW의 대체 옵션으로, 텍스트의 길이 차이가 클 때 효과적
- 일반 BoW에서는 빈도 수를 세지만, binary=True를 설정하면 단어가 등장했는지만 판단하여 1 또는 0으로 기록됨
3️⃣ DTM(Document‑Term Matrix) & 코사인 유사도
- DTM: 문서=행, 단어=열, 셀=빈도
- 코사인 유사도: 벡터 간 각도로 유사도를 측정. 크기와 무관하게 방향만으로 판단 → 길이(단어 수)가 다른 문서도 공정하게 비교 가능
예시
문서 cat dog I like
문서1 |
0 |
1 |
1 |
1 |
문서2 |
1 |
0 |
1 |
1 |
문서3 |
2 |
0 |
2 |
2 |
4️⃣ DTM의 구현과 한계점
구현 예시 (CountVectorizer)
corpus = [
'John likes to watch movies',
'Mary likes movies too',
'Mary also likes to watch football games'
]
vector = CountVectorizer()
X = vector.fit_transform(corpus).toarray()
한계
- 희소성: 대부분 0 → 메모리·연산 낭비, 차원의 저주
- 문맥 손실: 단어 순서·다의어 구분 불가
- 중요도 미반영: 단어의 단순 출현 빈도만 고려 → 불용어(the, is 등)의 빈도도 반영됨
- 불용어 제거 누락 방지: CountVectorizer 생성 시 stop_words='english'로 이중 제거 가능
5️⃣ TF‑IDF 개념
- 단어 빈도(TF) × 역문서 빈도(IDF)
- 흔한 단어 가중치 ↓, 드문 단어 ↑
- 불용어처럼 자주 등장하지만 정보량이 적은 단어의 영향력을 감소시킴
- 모든 상황에서 TF-IDF가 항상 BoW보다 우월한 건 아님
6️⃣ TF‑IDF 구현하기
자동 방식
from sklearn.feature_extraction.text import TfidfVectorizer
corpus = [
'John likes to watch movies and Mary likes movies too',
'James likes to watch TV',
'Mary also likes to watch football games',
]
tfidfv = TfidfVectorizer().fit(corpus)
X = tfidfv.transform(corpus).toarray()
수식 기반 핵심 함수
def tf(t,d):
return d.count(t)
def idf(t):
df = sum(t in doc for doc in docs)
return log(N/(df+1))+1
def tfidf(t,d):
return tf(t,d)*idf(t)
- 수작업으로 TF-IDF를 구현하려면 단어장을 먼저 만들어야 하며, 이는 CountVectorizer를 사용하지 않고 list/set을 이용해 직접 구성해야 함
7️⃣ LSA(Latent Semantic Analysis)
- TF‑IDF/DTM → SVD(A=UΣVᵀ) → k차원 축소 (잠재 의미 공간)
- 단어 간, 문서 간, 문서-단어 간 의미적 유사성 파악
- BoW/TF-IDF보다 의미 기반 검색에 효과적
💡 관련 수학 개념
- 고유값(Eigenvalue), 고유벡터(Eigenvector): 선형 변환 시 축 방향과 크기를 유지하는 특수 벡터
- SVD (Singular Value Decomposition): 직교 행렬 U, 대각 행렬 Σ, 전치행렬 Vᵀ로 분해
- 행렬 용어: 단위행렬(I), 정방행렬(n×n), 역행렬(A⁻¹), 직교행렬(열벡터끼리 직교)
8️⃣ LSA 실습 (TruncatedSVD)
- 뉴스 헤드라인 csv 파일 불러오기 → 중복 제거 및 index 재설정
- 불용어 제거(stopwords.words('english'), 한국어는 별도 구성 필요)
- 토큰화 → 표제어 추출 → 길이 1~2 단어 제거 → 역토큰화
- CountVectorizer로 DTM 생성 (max_features=5000)
c_vectorizer = CountVectorizer(stop_words='english', max_features = 5000)
document_term_matrix = c_vectorizer.fit_transform(train_data)
from sklearn.decomposition import TruncatedSVD
n_topics = 10
lsa_model = TruncatedSVD(n_components=n_topics)
X_lsa = lsa_model.fit_transform(document_term_matrix)
terms = c_vectorizer.get_feature_names_out()
def get_topics(components, feature_names, n=5):
for idx, topic in enumerate(components):
print("Topic %d:" % (idx+1),
[(feature_names[i], topic[i].round(5))
for i in topic.argsort()[:-n - 1:-1]])
get_topics(lsa_model.components_, terms)
9️⃣ LDA(Latent Dirichlet Allocation) 개념
- 문서는 여러 주제의 혼합 분포, 주제는 단어 분포를 가진다는 전제
- 확률 기반 토픽 모델링 기법 → 문서에서 주제 분포 추론 가능
- 고객의 소리, 뉴스 분석 등에 활용
⚖️ LSA vs LDA 차이점 비교
항목 LSA (Latent Semantic Analysis) LDA (Latent Dirichlet Allocation)
접근 방식 |
선형대수 기반: SVD로 차원 축소 |
확률 모델 기반: 문서는 주제 분포의 혼합으로 가정 |
수학적 기반 |
SVD (특잇값 분해) |
베이즈 추론, 디리클레 분포 |
출력 결과 |
문서-주제 행렬, 주제-단어 행렬 |
문서-주제 분포, 주제-단어 분포 |
장점 |
간단하고 빠름, 고차원 데이터 축소에 유리 |
각 문서가 여러 주제에 걸쳐있을 수 있음 (혼합모델) |
단점 |
음수 포함 가능 → 해석 어려움 |
구현과 튜닝이 복잡할 수 있음 |
해석 가능성 |
낮음 (선형 조합 기반) |
높음 (확률 기반 의미 분포) |
🔟 LDA 실습 (TF‑IDF 기반)
from sklearn.feature_extraction.text import TfidfVectorizer
# 상위 5,000개의 단어만 사용
tfidf_vectorizer = TfidfVectorizer(stop_words='english', max_features=5000)
tf_idf_matrix = tfidf_vectorizer.fit_transform(train_data)
# TF-IDF 행렬의 크기를 확인해봅시다.
print('행렬의 크기 :', tf_idf_matrix.shape)
LDA 모델 학습
from sklearn.decomposition import LatentDirichletAllocation
lda_model = LatentDirichletAllocation(n_components=10,
learning_method='online',
random_state=777,
max_iter=1)
lda_model.fit_transform(tf_idf_matrix)
📊 LDA 토픽 및 단어 비중 출력
# 단어 집합(상위 5,000개 단어) 가져오기
terms = tfidf_vectorizer.get_feature_names_out()
# 토픽 출력 함수 정의
def get_topics(components, feature_names, n=5):
for idx, topic in enumerate(components):
print(f"Topic {idx+1}:",
[(feature_names[i], topic[i].round(5))
for i in topic.argsort()[:-n-1:-1]])
# LDA 결과 토픽 출력
get_topics(lda_model.components_, terms, n=5)
- lda_model.components_ : (10 × 5000) 행렬, 각 행은 한 토픽의 단어 중요도 분포
- argsort()를 이용해 중요도가 높은 상위 n개 단어와 비중을 표시하여 토픽 의미를 해석할 수 있음