본문 바로가기

데이터사이언스/데이터사이언티스트4기

텍스트 벡터화 학습노트

텍스트 벡터화 학습노트


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()

한계

  1. 희소성: 대부분 0 → 메모리·연산 낭비, 차원의 저주
  2. 문맥 손실: 단어 순서·다의어 구분 불가
  3. 중요도 미반영: 단어의 단순 출현 빈도만 고려 → 불용어(the, is 등)의 빈도도 반영됨
  4. 불용어 제거 누락 방지: 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)
  • Truncated SVD 적용
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개 단어와 비중을 표시하여 토픽 의미를 해석할 수 있음