본문으로 건너뛰기

039 분류 실전 - 스팸 메일 분류

키워드: 스팸, 텍스트 분류

개요

스팸 메일 분류는 텍스트 분류의 대표적인 예입니다. 이 글에서는 텍스트 데이터를 전처리하고 PyCaret으로 스팸 분류 모델을 구축합니다.

실습 환경

  • Python 버전: 3.11 권장
  • 필요 패키지: pycaret[full]>=3.0, nltk

프로젝트 개요

목표: 이메일/SMS가 스팸인지 정상인지 분류

비즈니스 가치:

  • 불필요한 메일 자동 필터링
  • 사용자 경험 향상
  • 보안 위협 차단

데이터 로드

import pandas as pd
import numpy as np

# 039 UCI SMS Spam Collection 데이터
url = "https://raw.githubusercontent.com/justmarkham/pycon-2016-tutorial/master/data/sms.tsv"
data = pd.read_csv(url, sep='\t', header=None, names=['label', 'message'])

print(f"데이터 크기: {data.shape}")
print(f"\n클래스 분포:")
print(data['label'].value_counts(normalize=True))
print(f"\n샘플 메시지:")
print(data.head())

텍스트 전처리

import re
import nltk
from nltk.corpus import stopwords
from nltk.stem import PorterStemmer

# 039 필요한 NLTK 데이터 다운로드
nltk.download('stopwords', quiet=True)

# 039 전처리 함수
def preprocess_text(text):
# 소문자 변환
text = text.lower()

# 특수문자 제거
text = re.sub(r'[^a-zA-Z\s]', '', text)

# 토큰화
tokens = text.split()

# 불용어 제거
stop_words = set(stopwords.words('english'))
tokens = [t for t in tokens if t not in stop_words]

# 스테밍
stemmer = PorterStemmer()
tokens = [stemmer.stem(t) for t in tokens]

return ' '.join(tokens)

# 039 전처리 적용
data['processed'] = data['message'].apply(preprocess_text)

print("전처리 전후 비교:")
print(f"원본: {data['message'].iloc[0]}")
print(f"처리: {data['processed'].iloc[0]}")

특성 추출 (TF-IDF)

from sklearn.feature_extraction.text import TfidfVectorizer

# 039 TF-IDF 벡터화
tfidf = TfidfVectorizer(max_features=3000)
X_tfidf = tfidf.fit_transform(data['processed'])

# 039 DataFrame으로 변환
feature_names = tfidf.get_feature_names_out()
df_tfidf = pd.DataFrame(X_tfidf.toarray(), columns=feature_names)
df_tfidf['label'] = data['label'].values

print(f"TF-IDF 특성 수: {len(feature_names)}")
print(f"최종 데이터 크기: {df_tfidf.shape}")

PyCaret 설정

from pycaret.classification import *

# 039 환경 설정
clf = setup(
data=df_tfidf,
target='label',
session_id=42,
verbose=False
)

모델 비교

# 039 스팸 분류에 효과적인 알고리즘 비교
print("=== 모델 비교 ===")
best_models = compare_models(
include=['nb', 'lr', 'rf', 'xgboost', 'lightgbm'],
sort='F1',
n_select=3
)

Naive Bayes 상세 분석

# 039 Naive Bayes는 텍스트 분류에 특히 효과적
print("\n=== Naive Bayes 분석 ===")
nb = create_model('nb')

# 039 평가
plot_model(nb, plot='confusion_matrix', save=True)
plot_model(nb, plot='class_report', save=True)

로지스틱 회귀 분석

# 039 로지스틱 회귀로 중요 단어 파악
print("\n=== 로지스틱 회귀 분석 ===")
lr = create_model('lr')

# 039 계수 분석
import matplotlib.pyplot as plt

# 039 모델 계수 추출
coefficients = lr.coef_[0]
top_indices = np.argsort(coefficients)

# 039 스팸 관련 상위 단어
top_spam_idx = top_indices[-15:]
top_spam_words = [feature_names[i] for i in top_spam_idx]
top_spam_coefs = [coefficients[i] for i in top_spam_idx]

# 039 정상 관련 상위 단어
top_ham_idx = top_indices[:15]
top_ham_words = [feature_names[i] for i in top_ham_idx]
top_ham_coefs = [coefficients[i] for i in top_ham_idx]

# 039 시각화
fig, axes = plt.subplots(1, 2, figsize=(14, 5))

axes[0].barh(top_spam_words, top_spam_coefs, color='red')
axes[0].set_title('Top Spam Indicator Words')
axes[0].set_xlabel('Coefficient')

axes[1].barh(top_ham_words, top_ham_coefs, color='green')
axes[1].set_title('Top Ham Indicator Words')
axes[1].set_xlabel('Coefficient')

plt.tight_layout()
plt.savefig('spam_word_analysis.png', dpi=150)

모델 튜닝

# 039 최고 모델 튜닝
print("\n=== 모델 튜닝 ===")
tuned_model = tune_model(best_models[0], optimize='F1')

앙상블

# 039 앙상블
print("\n=== 앙상블 ===")
blended = blend_models(best_models)

비즈니스 임계값 조정

스팸 필터에서는 정상 메일을 스팸으로 오판(FP)하면 안 됨:

# 039 예측 결과
predictions = predict_model(blended)

# 039 Precision 중시 (FP 최소화)
from sklearn.metrics import precision_recall_curve

y_true = (predictions['label'] == 'spam').astype(int)
y_score = predictions['prediction_score']

precision, recall, thresholds = precision_recall_curve(y_true, y_score)

# 039 Precision 95% 이상인 임계값 찾기
target_precision = 0.95
valid_idx = precision[:-1] >= target_precision
if valid_idx.any():
optimal_threshold = thresholds[valid_idx][0]
print(f"Precision {target_precision*100}% 달성 임계값: {optimal_threshold:.4f}")
else:
print("목표 Precision 달성 불가")

최종 모델 저장

# 039 최종화
final_model = finalize_model(blended)

# 039 저장
save_model(final_model, 'spam_classifier')

# 039 TF-IDF 벡터라이저도 저장
import joblib
joblib.dump(tfidf, 'tfidf_vectorizer.pkl')

print("모델 및 벡터라이저 저장 완료")

새 메시지 예측

def predict_spam(message):
# 전처리
processed = preprocess_text(message)

# TF-IDF 변환
tfidf_loaded = joblib.load('tfidf_vectorizer.pkl')
features = tfidf_loaded.transform([processed])
df_features = pd.DataFrame(features.toarray(), columns=tfidf_loaded.get_feature_names_out())

# 모델 로드 및 예측
model = load_model('spam_classifier')
prediction = predict_model(model, data=df_features)

return prediction['prediction_label'].values[0], prediction['prediction_score'].values[0]

# 039 테스트
test_messages = [
"Congratulations! You've won a free iPhone. Click here to claim.",
"Hey, are we still meeting for lunch tomorrow?",
"URGENT: Your account has been compromised. Click to verify.",
"Can you send me the project files?"
]

print("=== 새 메시지 예측 ===")
for msg in test_messages:
label, score = predict_spam(msg)
print(f"\n메시지: {msg[:50]}...")
print(f"예측: {label} (확률: {score:.2%})")

성능 개선 팁

# 1. N-gram 추가
tfidf_ngram = TfidfVectorizer(max_features=5000, ngram_range=(1, 2))

# 2. 더 많은 특성
tfidf_more = TfidfVectorizer(max_features=10000)

# 3. 불균형 처리
clf = setup(
data=df_tfidf,
target='label',
fix_imbalance=True,
session_id=42
)

# 4. 커스텀 특성 추가
# 039 - 메시지 길이
# 039 - 대문자 비율
# 039 - 특수문자 개수
# 039 - URL 포함 여부

정리

  • 텍스트 분류는 전처리 → 벡터화 → 모델링 단계
  • TF-IDF로 텍스트를 수치 벡터로 변환
  • Naive Bayes가 텍스트 분류에 효과적
  • 스팸 필터는 Precision 중시 (정상을 스팸으로 오판하면 안 됨)
  • 중요 단어 분석으로 모델 해석 가능

다음 글 예고

다음 글에서는 분류 실전 - 질병 진단 예측을 다룹니다.


PyCaret 머신러닝 마스터 시리즈 #039