데이터 분석/머신러닝, 딥러닝
TF-IDF
fullfish
2025. 11. 20. 10:22
TF-IDF (Term Frequency – Inverse Document Frequency)
TF (Term Frequency)
- 한 문서 안에서 얼마나 자주 나왔나
→ BoW에서 쓰던 “횟수” 혹은 “비율”
IDF (Inverse Document Frequency)
전체 문서 중에서 얼마나 희귀한 단어인가
→ 모든 문서에 다 나오는 단어는 중요도가 떨어짐
핵심 아이디어
- 한 문서 안에서 자주 나오고 (TF ↑),
- 다른 문서에서는 잘 안 나오는 단어 (IDF ↑)
→ 그 문서를 잘 대표하는 “좋은 단어”로 가중치를 크게 준다.
TF(w) = (특정 단어 w의 문서 내 등장 횟수) / (해당 문서 내 총 단어 수)
IDF(w) = log(총 문서 수 / 특정 단어 w를 포함한 문서 수)
TF-IDF(w) = TF(w) × IDF(w)
장점
- 중요 단어 식별: 문서에서 중요한 단어를 구분하는 데 유용하며, 불필요한 단어의 가중치를 낮출 수 있음
- 정보 검색에 유용: 검색 엔진에서 문서의 관련성을 평가하는 데 자주 사용됨
단점
- 희소 행렬 문제: 큰 코퍼스에서는 매우 큰 희소 행렬이 생겨 메모리 사용량이 증가함
- 단어 의미 무시: 단어의 순서나 의미적 관계를 고려하지 않아 문맥 정보를 반영하기 어려움
vectorizer = TfidfVectorizer(
tokenizer=tokenize_korean, # 우리가 만든 함수
token_pattern=None, # 기본 정규식 비활성화
min_df=1, # 최소 문서 빈도
ngram_range=(1, 1), # 일단 unigram만
)
CountVectorizer와 공통 속성
속성 설명 예시
| vocabulary_ | 단어 → 인덱스 매핑 딕셔너리 | {'오늘': 0, '날씨': 1, '좋다': 2} |
| stop_words_ | 불용어로 제거된 단어 목록 | {'the', 'is', 'and'} |
| get_feature_names_out() | 인덱스 순서대로 정렬된 단어 리스트 | ['날씨', '오늘', '좋다'] |
| dtype | TF-IDF 행렬의 데이터 타입 | <class 'numpy.float64'> |
| ngram_range | n-gram 범위 (예: (1,2)) | (1, 2) |
| analyzer | 분석 단위(word/char 등) | 'word' |
| lowercase | 영문 소문자 변환 여부 | True |
TfidfVectorizer 전용 속성
속성 설명 예시
| idf_ | 단어별 IDF(역문서빈도) 값 (단어 희귀도) | [1.29, 1.58, 1.11] |
| use_idf | IDF를 적용할지 여부 (True면 TF×IDF 계산) | True |
| smooth_idf | IDF 계산 시 0 나누기 방지를 위한 스무딩 사용 여부 | True |
| sublinear_tf | TF를 1 + log(tf) 형태로 스케일링할지 여부 | False |
| norm | TF-IDF 벡터를 정규화할 방법 ('l2', 'l1', None) | 'l2' |
코드
from sklearn.feature_extraction.text import TfidfVectorizer
docs = ["오늘 날씨 날씨 정말 좋다", "오늘 기분 정말 좋다", "오늘은 기분이 좋지 않다"]
tfidf = TfidfVectorizer()
X_tfidf = tfidf.fit_transform(docs)
print("vocab : ", tfidf.get_feature_names_out())
print("shape : ", X_tfidf.shape)
print("X_tfidf : \n", X_tfidf.toarray().round(3))
'''
vocab : ['기분' '기분이' '날씨' '않다' '오늘' '오늘은' '정말' '좋다' '좋지']
shape : (3, 9)
X_tfidf :
[[0. 0. 0.835 0. 0.318 0. 0.318 0.318 0. ]
[0.605 0. 0. 0. 0.46 0. 0.46 0.46 0. ]
[0. 0.5 0. 0.5 0. 0.5 0. 0. 0.5 ]]
3번째 0.835는 날씨가 첫 문장에 2번 혼자만 있음. 중요도 높음'''
Cosine_similarity(문장 유사도)
# TF-IDF로 문서를 벡터화한 뒤, 코사인 유사도로 가장 비슷한 문서를 찾는 예제
from sklearn.feature_extraction.text import TfidfVectorizer # TF-IDF 벡터라이저
from sklearn.metrics.pairwise import cosine_similarity # 코사인 유사도 계산
import numpy as np # argmax 등 수치 연산
# 비교할 문서(코퍼스) 리스트
docs = [
"오늘 날씨가 좋아서 산책을 했다",
"오늘은 비가 와서 우울하다",
"점심에 맛있는 파스타를 먹었다",
"저녁에 산책하면서 음악을 들었다" # 따옴표 통일 (직접 입력 시 스마트쿼트 주의)
]
# TF-IDF 벡터라이저 생성 (기본 설정: 공백 기반 토큰화)
vectorizer = TfidfVectorizer()
# 문서 코퍼스를 학습(fit)하고, TF-IDF 희소행렬로 변환(transform)
X = vectorizer.fit_transform(docs) # 형태: (문서 수, 단어 수)
# 검색 쿼리(비교 대상) 문장
query = ["산책을 하면서 날씨를 즐겼다"]
# 코퍼스에서 학습한 같은 단어 사전으로 쿼리를 TF-IDF 벡터로 변환
X_query = vectorizer.transform(query) # 형태: (1, 단어 수)
# 쿼리와 각 문서 간 코사인 유사도 계산 → 결과 형태: (1, 문서 수)
sim = cosine_similarity(X_query, X)
# 유사도 행렬 출력 (한 줄짜리 벡터)
print("similarity:", sim)
# 가장 유사도가 높은 문서의 인덱스 선택
most_sim_idx = np.argmax(sim[0])
# 원 쿼리 문장 출력
print("Query:", query[0])
# 가장 유사한 문서 출력
print("Most similar doc:", docs[most_sim_idx])
실습
import re
import numpy as np
import pandas as pd
from konlpy.tag import Okt
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
"""
1. movie_reviews.csv 읽어서 DataFrame (결측치 삭제)
2. train_test_split() 함수로 층화추출로 샘플 10000개 추출
3. 샘플 10000개에 대해서 train, test로 분할
4. 텍스트전처리 (함수로 구현)
- 영문자(소문자)
- 한글/영문/숫자/공백 외 특수문자 제거
- 형태소 분석 + 품사 태깅
- 사용할 품사만 선택 (명사, 동사, 형용사)
- 불용어 제거
5. TfdifVectorizer()로 훈련/테스트 데이터 벡터화
6. 모델 훈련(LogisticRegression)
7. 모델 테스트 검증 점수 출력"""
# 1. movie_reviews.csv 읽어서 DataFrame (결측치 삭제)
df = pd.read_csv(
"stat_nlp/document_vector/movie_reviews.csv",
header=0,
names=["id", "review", "label"],
)
df = df.dropna()
# label을 정수형으로 변환 (0, 1). 지금 정수형이긴 하지만 혹시 모르니까
df["label"] = df["label"].astype(int)
print(df.head())
# print(df["label"].value_counts()) # 개수 확인
print(df["label"].value_counts(normalize=True)) # 비율 확인
# 2. train_test_split() 함수로 층화추출로 샘플 10000개 추출
_, df_sample = train_test_split(
df,
test_size=10000,
stratify=df["label"],
shuffle=True, # 기본값이라 사실 안 써도 됨
random_state=42,
)
print(len(df_sample))
print(len(_))
X = df_sample["review"]
y = df_sample["label"]
# 3. 샘플 10000개에 대해서 train, test로 분할
x_train, x_test, y_train, y_test = train_test_split(
X,
y,
test_size=0.2, # 0.2 → 20퍼센트 test
stratify=y, # 샘플 안에서도 0/1 비율 유지
shuffle=True,
random_state=42,
)
"""
4. 텍스트전처리 (함수로 구현)
- 영문자(소문자)
- 한글/영문/숫자/공백 외 특수문자 제거
- 형태소 분석 + 품사 태깅
- 사용할 품사만 선택 (명사, 동사, 형용사)
- 불용어 제거"""
okt = Okt()
# 불용어 가져오기
with open(
"/Users/choimanseon/Documents/multicampus/example/nlp/stat_nlp/stopwords-ko.txt",
encoding="utf-8",
) as f:
basic_stopwords = set(w.strip() for w in f if w.strip())
possible_pos = ["Noun", "Verb", "Adjective"]
def preprocess(text):
str_reg = re.sub(r"[^가-힝0-9a-zA-Z\s]", "", text).lower()
pos = okt.pos(str_reg, norm=True, stem=True, join=True)
pos = [word.split("/") for word in pos]
filtered_pos = [
word
for word, tag in pos
if word and word not in basic_stopwords and tag in possible_pos
]
return filtered_pos
# 5. TfdifVectorizer()로 훈련/테스트 데이터 벡터화
vectorizer = TfidfVectorizer(
tokenizer=preprocess,
token_pattern=None, # 기본 정규식 토큰 분리 끔
ngram_range=(1, 2),
)
x_train_vec = vectorizer.fit_transform(x_train)
x_test_vec = vectorizer.transform(x_test)
# 6. 모델 훈련(LogisticRegression)
clf = LogisticRegression(max_iter=1000)
clf.fit(x_train_vec, y_train)
# 7. 모델 테스트 검증 점수 출력
y_pred = clf.predict(x_test_vec)
print("y_pred", y_pred)
print(classification_report(y_test, y_pred, digits=3))
# 문장 받아서 긍정 부정 출력
test_sents = [
"와 진짜 너무 재밌고 감동적이었다",
"스토리가 너무 지루하고 시간 아깝네",
"배우 연기는 좋았는데 결말이 별로다",
"미친 영화다... 올해 최고",
"연기 연습좀 해야할듯",
"친구 추천해줘야 겠다",
]
def predict_sentences(sent_list):
vec = vectorizer.transform(sent_list)
preds = clf.predict(vec)
# 긍정 확률 (1일 확률)
probs = clf.predict_proba(vec)[:, 1]
for sent, label, prob in zip(sent_list, preds, probs):
print("문장:", sent)
print("예측 라벨:", label, "(1=긍정, 0=부정)")
print(f"긍정 확률: {prob:.3f}")
print()
predict_sentences(test_sents)