본문으로 건너뛰기

086 종합 프로젝트 - 텍스트 분류 (TF-IDF + FLAML)

키워드: 텍스트, TF-IDF, 자연어 처리, NLP

개요

텍스트 데이터를 TF-IDF로 벡터화하고 FLAML AutoML로 분류 모델을 구축합니다. 스팸 필터링, 감성 분석, 문서 분류 등에 적용할 수 있는 기본 NLP 파이프라인을 알아봅니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: flaml[automl]
pip install flaml[automl] pandas numpy scikit-learn

TF-IDF 개념

import numpy as np
import pandas as pd

tfidf_concept = {
'요소': ['TF (Term Frequency)', 'IDF (Inverse Document Frequency)', 'TF-IDF'],
'의미': [
'문서 내 단어 빈도',
'전체 문서에서 희귀도',
'TF × IDF'
],
'특징': [
'자주 등장하면 높음',
'희귀 단어일수록 높음',
'중요한 단어 강조'
]
}

print("TF-IDF 개념:")
print(pd.DataFrame(tfidf_concept).to_string(index=False))

데이터 생성 (뉴스 분류)

np.random.seed(42)

# 086 카테고리별 키워드
category_keywords = {
'sports': ['game', 'team', 'player', 'score', 'win', 'match', 'championship', 'league', 'coach', 'season'],
'technology': ['software', 'app', 'device', 'digital', 'data', 'computer', 'AI', 'cloud', 'security', 'internet'],
'politics': ['government', 'election', 'vote', 'policy', 'president', 'congress', 'law', 'democrat', 'republican', 'bill'],
'business': ['market', 'stock', 'company', 'profit', 'revenue', 'investment', 'CEO', 'growth', 'trade', 'economy']
}

# 086 공통 단어
common_words = ['the', 'is', 'a', 'in', 'to', 'of', 'and', 'for', 'on', 'with', 'that', 'this', 'new', 'more', 'said']

def generate_document(category, n_words=50):
"""카테고리 기반 문서 생성"""
keywords = category_keywords[category]
words = []

for _ in range(n_words):
if np.random.random() < 0.4: # 40% 키워드
words.append(np.random.choice(keywords))
else: # 60% 공통 단어
words.append(np.random.choice(common_words))

return ' '.join(words)

# 086 데이터셋 생성
n_samples_per_class = 500
documents = []
labels = []

for category in category_keywords.keys():
for _ in range(n_samples_per_class):
doc = generate_document(category)
documents.append(doc)
labels.append(category)

df = pd.DataFrame({'text': documents, 'category': labels})
print(f"데이터 크기: {len(df)}")
print(f"카테고리 분포:\n{df['category'].value_counts()}")
print(f"\n샘플 문서 (sports):")
print(df[df['category'] == 'sports']['text'].iloc[0][:100])

TF-IDF 벡터화

from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.model_selection import train_test_split

# 086 데이터 분할
X_train_text, X_test_text, y_train, y_test = train_test_split(
df['text'], df['category'],
test_size=0.2, random_state=42, stratify=df['category']
)

# 086 TF-IDF 벡터화
tfidf = TfidfVectorizer(
max_features=1000, # 최대 특성 수
min_df=2, # 최소 문서 빈도
max_df=0.95, # 최대 문서 빈도
ngram_range=(1, 2), # 유니그램 + 바이그램
stop_words='english' # 불용어 제거
)

X_train = tfidf.fit_transform(X_train_text).toarray()
X_test = tfidf.transform(X_test_text).toarray()

print(f"TF-IDF 특성 수: {X_train.shape[1]}")
print(f"학습 데이터: {X_train.shape}")
print(f"테스트 데이터: {X_test.shape}")

# 086 상위 TF-IDF 단어 확인
feature_names = tfidf.get_feature_names_out()
print(f"\n상위 20개 특성: {feature_names[:20].tolist()}")

FLAML로 텍스트 분류

from flaml import AutoML
from sklearn.metrics import accuracy_score, classification_report
from sklearn.preprocessing import LabelEncoder

# 086 레이블 인코딩
le = LabelEncoder()
y_train_encoded = le.fit_transform(y_train)
y_test_encoded = le.transform(y_test)

# 086 FLAML AutoML
automl = AutoML()
automl.fit(
X_train, y_train_encoded,
task="classification",
metric="accuracy",
time_budget=60,
estimator_list=['lgbm', 'xgboost', 'rf', 'extra_tree'],
n_jobs=-1,
seed=42,
verbose=2
)

print(f"\n=== FLAML 결과 ===")
print(f"최적 모델: {automl.best_estimator}")
print(f"최적 설정: {automl.best_config}")

모델 평가

# 086 예측
y_pred = automl.predict(X_test)

# 086 성능 평가
print("\n=== 분류 성능 ===")
print(f"정확도: {accuracy_score(y_test_encoded, y_pred):.4f}")
print("\n분류 리포트:")
print(classification_report(y_test_encoded, y_pred, target_names=le.classes_))

혼동 행렬 시각화

import matplotlib.pyplot as plt
from sklearn.metrics import confusion_matrix
import seaborn as sns

# 086 혼동 행렬
cm = confusion_matrix(y_test_encoded, y_pred)

plt.figure(figsize=(10, 8))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=le.classes_, yticklabels=le.classes_)
plt.xlabel('예측')
plt.ylabel('실제')
plt.title('텍스트 분류 혼동 행렬')
plt.tight_layout()
plt.show()

특성 중요도 분석

# 086 모델의 특성 중요도
if hasattr(automl.model.estimator, 'feature_importances_'):
importances = automl.model.estimator.feature_importances_

# 상위 중요 단어
top_indices = np.argsort(importances)[-20:]
top_words = feature_names[top_indices]
top_importances = importances[top_indices]

print("\n=== 상위 20개 중요 단어 ===")
for word, imp in zip(reversed(top_words), reversed(top_importances)):
print(f" {word}: {imp:.4f}")

# 시각화
plt.figure(figsize=(10, 8))
plt.barh(top_words, top_importances)
plt.xlabel('중요도')
plt.title('텍스트 분류 중요 단어')
plt.tight_layout()
plt.show()

카테고리별 핵심 단어

def get_category_keywords(tfidf, X_train, y_train, feature_names, top_n=10):
"""카테고리별 핵심 단어 추출"""
keywords = {}

for category in np.unique(y_train):
# 해당 카테고리 문서
category_mask = (y_train == category)
category_tfidf = X_train[category_mask].mean(axis=0)

# 상위 단어
top_indices = np.argsort(category_tfidf)[-top_n:]
top_words = [(feature_names[i], category_tfidf[i]) for i in reversed(top_indices)]
keywords[le.classes_[category]] = top_words

return keywords

# 086 카테고리별 키워드
category_keywords_result = get_category_keywords(
tfidf, X_train, y_train_encoded, feature_names, top_n=10
)

print("\n=== 카테고리별 핵심 단어 ===")
for category, words in category_keywords_result.items():
word_list = [w[0] for w in words[:5]]
print(f"{category}: {', '.join(word_list)}")

새 텍스트 예측

def predict_category(text, tfidf, model, label_encoder):
"""새 텍스트의 카테고리 예측"""
# TF-IDF 변환
features = tfidf.transform([text]).toarray()

# 예측
pred = model.predict(features)[0]
proba = model.predict_proba(features)[0]

# 결과
category = label_encoder.classes_[pred]
confidence = proba[pred]

return category, confidence, dict(zip(label_encoder.classes_, proba))

# 086 테스트 문장들
test_sentences = [
"The team scored a winning goal in the championship match",
"New AI software improves data security in cloud computing",
"President signs new bill after congress vote",
"Stock market shows growth as company profits increase"
]

print("\n=== 새 텍스트 예측 ===")
for sentence in test_sentences:
category, confidence, proba = predict_category(sentence, tfidf, automl, le)
print(f"\n텍스트: {sentence[:50]}...")
print(f"예측: {category} (신뢰도: {confidence:.2%})")

다양한 벡터화 비교

from sklearn.feature_extraction.text import CountVectorizer

# 086 다양한 벡터화 방법
vectorizers = {
'TF-IDF (1-gram)': TfidfVectorizer(max_features=500, ngram_range=(1, 1)),
'TF-IDF (1,2-gram)': TfidfVectorizer(max_features=500, ngram_range=(1, 2)),
'Count (1-gram)': CountVectorizer(max_features=500, ngram_range=(1, 1)),
}

results = []

for name, vec in vectorizers.items():
X_tr = vec.fit_transform(X_train_text).toarray()
X_te = vec.transform(X_test_text).toarray()

model = AutoML()
model.fit(X_tr, y_train_encoded, task="classification",
time_budget=30, verbose=0)

y_pred = model.predict(X_te)
acc = accuracy_score(y_test_encoded, y_pred)

results.append({'벡터화': name, '정확도': acc, '모델': model.best_estimator})
print(f"{name}: {acc:.4f}")

print("\n=== 벡터화 방법 비교 ===")
print(pd.DataFrame(results).to_string(index=False))

파이프라인 구축

from sklearn.pipeline import Pipeline

# 086 전체 파이프라인
class TextClassificationPipeline:
def __init__(self, max_features=1000, time_budget=60):
self.max_features = max_features
self.time_budget = time_budget
self.tfidf = None
self.model = None
self.label_encoder = None

def fit(self, texts, labels):
"""학습"""
# TF-IDF 벡터화
self.tfidf = TfidfVectorizer(
max_features=self.max_features,
ngram_range=(1, 2),
stop_words='english'
)
X = self.tfidf.fit_transform(texts).toarray()

# 레이블 인코딩
self.label_encoder = LabelEncoder()
y = self.label_encoder.fit_transform(labels)

# FLAML 학습
self.model = AutoML()
self.model.fit(X, y, task="classification",
time_budget=self.time_budget, verbose=0)

return self

def predict(self, texts):
"""예측"""
X = self.tfidf.transform(texts).toarray()
y_pred = self.model.predict(X)
return self.label_encoder.inverse_transform(y_pred)

def predict_proba(self, texts):
"""확률 예측"""
X = self.tfidf.transform(texts).toarray()
return self.model.predict_proba(X)

# 086 파이프라인 사용
pipeline = TextClassificationPipeline(max_features=500, time_budget=30)
pipeline.fit(X_train_text, y_train)

# 086 테스트
predictions = pipeline.predict(X_test_text[:5])
print("\n=== 파이프라인 테스트 ===")
for text, pred, actual in zip(X_test_text[:5], predictions, y_test[:5]):
print(f"예측: {pred}, 실제: {actual}")

모델 저장

import joblib

# 086 파이프라인 저장
joblib.dump({
'tfidf': tfidf,
'model': automl,
'label_encoder': le
}, 'text_classifier.pkl')

print("\n텍스트 분류 모델 저장: text_classifier.pkl")

정리

  • TF-IDF: 텍스트를 수치 벡터로 변환
  • n-gram: 유니그램 + 바이그램으로 문맥 포착
  • FLAML: 자동 모델 선택 및 하이퍼파라미터 튜닝
  • 특성 중요도: 분류에 중요한 단어 확인
  • 파이프라인: 전처리부터 예측까지 자동화

다음 글 예고

다음 글에서는 A/B 테스트 분석 자동화 프로젝트를 진행합니다. FLAML을 활용한 실험 분석 자동화 방법을 알아봅니다.


FLAML AutoML 마스터 시리즈 #086