🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

스팸 메일 분류기 만들기 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 5. · 14 Views

스팸 메일 분류기 만들기 완벽 가이드

머신러닝을 활용해 스팸 메일을 자동으로 분류하는 방법을 배웁니다. 텍스트 전처리부터 나이브 베이즈, SVM 분류기까지 단계별로 실습하며 이해할 수 있습니다.


목차

  1. 이메일_데이터셋_로드
  2. 텍스트_전처리
  3. TF-IDF_벡터화
  4. 나이브_베이즈_분류기
  5. SVM_분류기_비교
  6. 분류_성능_평가

1. 이메일 데이터셋 로드

신입 개발자 김개발 씨는 오늘 팀장님으로부터 특별한 미션을 받았습니다. "우리 회사 고객센터에 스팸 메일이 너무 많이 들어와서 업무가 마비될 지경이야.

자동으로 스팸을 걸러내는 시스템 좀 만들어줄 수 있겠어?" 김개발 씨는 어디서부터 시작해야 할지 막막했습니다.

스팸 메일 분류기를 만들기 위한 첫 번째 단계는 학습에 사용할 데이터셋을 준비하는 것입니다. 마치 요리를 시작하기 전에 재료를 준비하는 것과 같습니다.

좋은 재료 없이는 맛있는 요리를 만들 수 없듯이, 양질의 데이터 없이는 정확한 분류기를 만들 수 없습니다.

다음 코드를 살펴봅시다.

import pandas as pd
from sklearn.model_selection import train_test_split

# SMS Spam Collection 데이터셋 로드
df = pd.read_csv('spam.csv', encoding='latin-1')

# 필요한 컬럼만 선택하고 이름 변경
df = df[['v1', 'v2']]
df.columns = ['label', 'message']

# 레이블을 숫자로 변환 (ham=0, spam=1)
df['label'] = df['label'].map({'ham': 0, 'spam': 1})

# 학습용과 테스트용 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(
    df['message'], df['label'], test_size=0.2, random_state=42
)

김개발 씨는 먼저 인터넷을 뒤져 스팸 메일 데이터셋을 찾아보았습니다. 다행히 SMS Spam Collection이라는 유명한 공개 데이터셋이 있었습니다.

이 데이터셋에는 5,572개의 문자 메시지가 담겨 있고, 각 메시지가 스팸인지 아닌지 이미 분류되어 있습니다. 선배 개발자 박시니어 씨가 다가와 조언을 건넸습니다.

"데이터를 다룰 때는 pandas 라이브러리가 정말 편해. 엑셀처럼 표 형태로 데이터를 다룰 수 있거든." pandas의 read_csv 함수는 CSV 파일을 읽어서 DataFrame이라는 표 형태의 데이터 구조로 변환해줍니다.

마치 엑셀 파일을 열어서 스프레드시트로 보는 것과 비슷합니다. encoding='latin-1' 옵션은 파일의 문자 인코딩을 지정하는 것인데, 이 데이터셋이 그 형식으로 저장되어 있기 때문입니다.

원본 데이터셋의 컬럼 이름이 v1, v2처럼 알아보기 어렵게 되어 있어서, 'label'과 'message'로 이름을 바꿔주었습니다. 코드의 가독성을 높이는 작은 습관이지만, 나중에 유지보수할 때 큰 차이를 만듭니다.

여기서 중요한 작업이 하나 있습니다. 컴퓨터는 'ham'이나 'spam' 같은 문자열보다 숫자를 훨씬 잘 처리합니다.

그래서 map 함수를 사용해서 'ham'은 0으로, 'spam'은 1로 변환해주었습니다. 이것을 레이블 인코딩이라고 부릅니다.

마지막으로 가장 중요한 단계가 남았습니다. 바로 데이터를 학습용테스트용으로 나누는 것입니다.

왜 굳이 나눌까요? 비유하자면 이렇습니다.

수학 시험을 준비할 때 연습 문제만 달달 외워서 시험장에 들어가면 어떻게 될까요? 똑같은 문제가 나오면 100점이겠지만, 조금만 다른 문제가 나오면 손도 못 댑니다.

마찬가지로 머신러닝 모델도 학습에 사용한 데이터로만 테스트하면 실제 성능을 알 수 없습니다. train_test_split 함수는 데이터를 무작위로 섞은 뒤 80%는 학습용으로, 20%는 테스트용으로 나눠줍니다.

test_size=0.2가 바로 20%를 의미합니다. random_state=42는 난수 생성의 시드값인데, 이 값을 고정해두면 코드를 여러 번 실행해도 항상 같은 방식으로 데이터가 나뉩니다.

재현 가능한 실험을 위해 꼭 필요한 설정입니다. 김개발 씨는 고개를 끄덕였습니다.

"아, 그래서 학습용 데이터로 모델을 훈련시키고, 테스트용 데이터로 실제 성능을 확인하는 거군요!"

실전 팁

💡 - 데이터 로드 후 df.head()로 처음 5개 행을 확인하는 습관을 들이세요

  • df.info()와 df.describe()로 데이터의 전체적인 구조와 통계를 파악하세요

2. 텍스트 전처리

데이터를 로드한 김개발 씨는 의기양양하게 바로 분류기를 만들려고 했습니다. 그런데 박시니어 씨가 말렸습니다.

"잠깐, 그 데이터 그대로 쓰면 안 돼. 텍스트는 먼저 깨끗하게 정리해야 해." 김개발 씨는 의아했습니다.

데이터가 멀쩡해 보이는데 왜 정리가 필요할까요?

텍스트 전처리는 원본 텍스트를 머신러닝 모델이 이해하기 좋은 형태로 다듬는 과정입니다. 마치 요리 전에 채소를 씻고 다듬는 것처럼, 불필요한 요소를 제거하고 일관된 형태로 정리합니다.

이 과정이 제대로 되지 않으면 아무리 좋은 알고리즘을 써도 좋은 결과를 얻기 어렵습니다.

다음 코드를 살펴봅시다.

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

nltk.download('stopwords')
stop_words = set(stopwords.words('english'))
stemmer = PorterStemmer()

def preprocess_text(text):
    # 소문자 변환
    text = text.lower()
    # 특수문자 제거 (알파벳과 숫자만 남김)
    text = re.sub(r'[^a-zA-Z0-9\s]', '', text)
    # 토큰화 (단어 단위로 분리)
    tokens = text.split()
    # 불용어 제거 및 어간 추출
    tokens = [stemmer.stem(word) for word in tokens
              if word not in stop_words]
    return ' '.join(tokens)

# 전처리 적용
X_train_clean = X_train.apply(preprocess_text)
X_test_clean = X_test.apply(preprocess_text)

박시니어 씨가 화면을 가리키며 설명을 시작했습니다. "이 메시지 좀 봐.

'FREE!!! Win a $1000 prize NOW!!!' 이런 게 있잖아.

사람 눈에는 바로 스팸처럼 보이지만, 컴퓨터는 'FREE'랑 'free', 'Free'를 전부 다른 단어로 인식해." 그래서 첫 번째로 하는 작업이 소문자 변환입니다. 모든 텍스트를 소문자로 통일하면 'FREE', 'Free', 'free'가 모두 'free'로 인식됩니다.

이렇게 하면 같은 의미의 단어를 하나로 묶을 수 있습니다. 두 번째는 특수문자 제거입니다.

'!!!'나 '$', '@' 같은 특수문자는 대부분의 경우 의미 분석에 도움이 되지 않습니다. 정규표현식 re.sub를 사용해서 알파벳과 숫자를 제외한 모든 문자를 제거합니다.

r'[^a-zA-Z0-9\s]' 패턴에서 ^ 기호는 '제외한다'는 의미입니다. 세 번째는 토큰화입니다.

긴 문장을 개별 단어로 쪼개는 작업입니다. 마치 긴 기차를 개별 객차로 분리하는 것과 같습니다.

Python에서는 간단하게 split() 함수로 공백을 기준으로 문장을 나눌 수 있습니다. 네 번째는 불용어 제거입니다.

불용어란 'the', 'is', 'a' 같이 너무 흔해서 의미 분석에 도움이 되지 않는 단어들입니다. 마치 음식에서 물을 빼고 영양분만 추출하는 것과 비슷합니다.

NLTK 라이브러리에는 영어 불용어 목록이 이미 준비되어 있어서 편리하게 사용할 수 있습니다. 다섯 번째는 어간 추출입니다.

'running', 'runs', 'ran'은 모두 'run'이라는 어간에서 파생된 단어입니다. PorterStemmer는 이런 단어들을 모두 어간 형태로 변환해줍니다.

이렇게 하면 같은 의미의 다양한 형태를 하나로 통합할 수 있습니다. 김개발 씨가 물었습니다.

"그런데 이렇게 단어를 자르고 바꾸면 원래 의미가 손상되지 않나요?" 박시니어 씨가 고개를 저었습니다. "좋은 질문이야.

물론 어느 정도 정보 손실은 있어. 하지만 스팸 분류 같은 작업에서는 세세한 문법보다 어떤 단어가 등장하는지가 더 중요해.

'FREE', 'WINNER', 'PRIZE' 같은 단어가 많이 나오면 스팸일 확률이 높잖아." 마지막으로 pandas의 apply 함수를 사용해서 모든 데이터에 전처리 함수를 일괄 적용합니다. 이렇게 하면 수천 개의 메시지를 한 번에 처리할 수 있습니다.

실전 팁

💡 - 전처리 전후의 텍스트를 비교해보면 어떤 변화가 일어났는지 이해하기 쉽습니다

  • 한국어 텍스트의 경우 KoNLPy 라이브러리를 사용하세요

3. TF-IDF 벡터화

전처리를 마친 김개발 씨는 다음 단계가 궁금해졌습니다. "선배, 이제 텍스트가 깨끗해졌는데, 이걸 어떻게 컴퓨터가 계산할 수 있는 형태로 바꾸죠?" 박시니어 씨가 미소 지었습니다.

"드디어 핵심 질문이 나왔네. 바로 TF-IDF를 배울 차례야."

TF-IDF는 Term Frequency-Inverse Document Frequency의 약자로, 텍스트를 숫자 벡터로 변환하는 기법입니다. 단순히 단어 출현 빈도만 세는 것이 아니라, 특정 문서에서 얼마나 중요한 단어인지를 수치화합니다.

마치 금의 가치가 희소성에서 나오듯이, 흔하지 않은 단어에 더 높은 가중치를 부여합니다.

다음 코드를 살펴봅시다.

from sklearn.feature_extraction.text import TfidfVectorizer

# TF-IDF 벡터라이저 생성
tfidf = TfidfVectorizer(max_features=3000)

# 학습 데이터로 벡터라이저 학습 및 변환
X_train_tfidf = tfidf.fit_transform(X_train_clean)

# 테스트 데이터는 변환만 수행 (fit 하지 않음)
X_test_tfidf = tfidf.transform(X_test_clean)

# 결과 확인
print(f"벡터 크기: {X_train_tfidf.shape}")
print(f"특성 개수: {len(tfidf.get_feature_names_out())}")

박시니어 씨가 칠판에 그림을 그리며 설명을 시작했습니다. "컴퓨터는 문자를 직접 이해하지 못해.

숫자만 계산할 수 있지. 그래서 텍스트를 숫자로 바꿔야 하는데, 이걸 벡터화라고 해." 가장 단순한 방법은 단어가 몇 번 등장했는지 세는 것입니다.

이것을 Bag of Words라고 부릅니다. 하지만 이 방법에는 문제가 있습니다.

'the'나 'is' 같은 단어는 모든 문서에 자주 등장하기 때문에 높은 점수를 받지만, 실제로 문서의 특징을 구분하는 데는 도움이 되지 않습니다. 그래서 등장한 것이 TF-IDF입니다.

이름을 풀어서 설명해보겠습니다. **TF(Term Frequency)**는 특정 문서에서 단어가 등장하는 빈도입니다.

"free라는 단어가 이 메시지에 3번 나왔다"와 같은 정보입니다. **IDF(Inverse Document Frequency)**는 전체 문서에서 해당 단어가 얼마나 희귀한지를 나타냅니다.

모든 문서에 등장하는 단어는 IDF가 낮고, 몇몇 문서에만 등장하는 단어는 IDF가 높습니다. 이 두 값을 곱하면 TF-IDF가 됩니다.

결과적으로 특정 문서에 자주 등장하면서 동시에 다른 문서에는 잘 등장하지 않는 단어에 높은 점수가 부여됩니다. 스팸 메일에 자주 등장하는 'winner', 'prize', 'congratulations' 같은 단어들이 높은 TF-IDF 값을 갖게 되는 것입니다.

김개발 씨가 코드를 보며 질문했습니다. "max_features=3000은 무슨 의미인가요?" "전체 단어 중에서 가장 중요한 3000개만 사용하겠다는 뜻이야.

단어가 너무 많으면 계산이 느려지고, 오히려 노이즈가 될 수도 있거든." 여기서 중요한 점이 하나 있습니다. fit_transformtransform의 차이입니다.

학습 데이터에는 fit_transform을 사용해서 벡터라이저를 학습시키면서 동시에 변환합니다. 하지만 테스트 데이터에는 transform만 사용합니다.

왜일까요? 만약 테스트 데이터에도 fit을 적용하면, 테스트 데이터의 정보가 모델에 새어 들어가는 데이터 누수가 발생합니다.

마치 시험 문제를 미리 보고 공부하는 것과 같습니다. 그러면 실제 성능을 정확히 평가할 수 없습니다.

결과를 출력해보면 각 메시지가 3000차원의 숫자 벡터로 변환된 것을 확인할 수 있습니다. 이제 드디어 머신러닝 알고리즘을 적용할 준비가 완료되었습니다.

실전 팁

💡 - max_features 값은 데이터셋 크기에 따라 조절하세요

  • ngram_range=(1,2) 옵션으로 바이그램까지 포함하면 성능이 올라갈 수 있습니다

4. 나이브 베이즈 분류기

벡터화까지 마친 김개발 씨에게 박시니어 씨가 물었습니다. "자, 이제 어떤 알고리즘으로 분류할 건지 정해야 해.

텍스트 분류의 고전 중의 고전, 나이브 베이즈부터 시작해볼까?" 김개발 씨는 대학 시절 확률론 수업에서 베이즈 정리를 배웠던 기억이 어렴풋이 떠올랐습니다.

나이브 베이즈 분류기는 베이즈 정리를 기반으로 하는 확률적 분류 알고리즘입니다. '나이브(순진한)'라는 이름이 붙은 이유는 모든 특성이 서로 독립이라는 단순한 가정을 하기 때문입니다.

가정이 단순함에도 불구하고 텍스트 분류에서 놀라울 정도로 좋은 성능을 보여줍니다.

다음 코드를 살펴봅시다.

from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import accuracy_score, classification_report

# 나이브 베이즈 분류기 생성 및 학습
nb_classifier = MultinomialNB()
nb_classifier.fit(X_train_tfidf, y_train)

# 예측 수행
y_pred_nb = nb_classifier.predict(X_test_tfidf)

# 성능 평가
accuracy_nb = accuracy_score(y_test, y_pred_nb)
print(f"나이브 베이즈 정확도: {accuracy_nb:.4f}")
print("\n분류 보고서:")
print(classification_report(y_test, y_pred_nb,
                          target_names=['ham', 'spam']))

박시니어 씨가 쉬운 비유로 설명을 시작했습니다. "우체국에서 편지를 분류하는 직원을 상상해봐.

이 직원은 오랜 경험으로 편지를 보자마자 어떤 종류인지 감을 잡아. 'FREE'라는 단어가 크게 써있으면 '이건 광고겠구나' 하고, '청구서'라고 써있으면 '이건 공과금이구나' 하고 말이야." 나이브 베이즈도 비슷한 방식으로 작동합니다.

학습 데이터를 통해 "스팸 메일에는 이런 단어들이 자주 등장하고, 정상 메일에는 저런 단어들이 자주 등장하는구나"라는 패턴을 학습합니다. 베이즈 정리의 핵심은 이것입니다.

"이 메시지에 'FREE', 'WINNER', 'PRIZE'라는 단어가 있을 때, 이것이 스팸일 확률은 얼마인가?" 이 확률을 계산해서 일정 기준을 넘으면 스팸으로 분류하는 것입니다. '나이브(순진한)'라는 별명이 붙은 이유는 각 단어가 서로 독립이라고 가정하기 때문입니다.

실제로 언어에서 단어들은 서로 연관되어 있습니다. 'New'와 'York'은 함께 등장할 확률이 높지요.

하지만 나이브 베이즈는 이런 연관성을 무시합니다. 김개발 씨가 의아해했습니다.

"그렇게 단순한 가정으로 제대로 된 결과가 나오나요?" "신기하게도 잘 돼. 수학적으로 완벽하지 않아도 실용적으로는 훌륭한 결과를 내는 알고리즘이 있거든.

나이브 베이즈가 바로 그런 경우야." 코드를 살펴보겠습니다. MultinomialNB는 다항 분포를 가정하는 나이브 베이즈로, TF-IDF처럼 단어 빈도 기반의 특성에 적합합니다.

fit 메서드로 모델을 학습시키고, predict 메서드로 예측을 수행합니다. classification_report는 정확도 외에도 정밀도, 재현율, F1 점수를 함께 보여줍니다.

스팸 분류에서는 특히 재현율이 중요합니다. 스팸을 정상 메일로 잘못 분류해도 그나마 괜찮지만, 정상 메일을 스팸으로 분류하면 중요한 메일을 놓칠 수 있기 때문입니다.

나이브 베이즈의 장점은 학습 속도가 매우 빠르다는 것입니다. 수백만 개의 이메일도 금방 처리할 수 있습니다.

또한 적은 데이터로도 꽤 좋은 성능을 보여줍니다. 하지만 복잡한 패턴을 잡아내는 데는 한계가 있습니다.

실전 팁

💡 - 나이브 베이즈는 기준선(baseline) 모델로 먼저 시도해보기 좋습니다

  • alpha 파라미터로 라플라스 스무딩을 조절할 수 있습니다

5. SVM 분류기 비교

나이브 베이즈로 90% 이상의 정확도를 달성한 김개발 씨는 뿌듯했습니다. 하지만 박시니어 씨는 한 발 더 나가자고 제안했습니다.

"나이브 베이즈가 좋은 시작점이긴 한데, SVM으로 더 높은 성능을 낼 수 있을지도 몰라. 한번 비교해볼까?"

**SVM(Support Vector Machine)**은 데이터를 고차원 공간에서 최적의 경계로 분리하는 알고리즘입니다. 마치 두 집단 사이에 가장 넓은 도로를 내는 것과 같습니다.

텍스트 분류에서 나이브 베이즈와 함께 가장 많이 사용되는 알고리즘이며, 특히 고차원 데이터에서 뛰어난 성능을 발휘합니다.

다음 코드를 살펴봅시다.

from sklearn.svm import LinearSVC
from sklearn.metrics import accuracy_score, classification_report

# SVM 분류기 생성 및 학습
svm_classifier = LinearSVC(random_state=42, max_iter=10000)
svm_classifier.fit(X_train_tfidf, y_train)

# 예측 수행
y_pred_svm = svm_classifier.predict(X_test_tfidf)

# 성능 평가
accuracy_svm = accuracy_score(y_test, y_pred_svm)
print(f"SVM 정확도: {accuracy_svm:.4f}")
print("\n분류 보고서:")
print(classification_report(y_test, y_pred_svm,
                          target_names=['ham', 'spam']))

# 나이브 베이즈와 비교
print(f"\n성능 비교:")
print(f"나이브 베이즈: {accuracy_nb:.4f}")
print(f"SVM: {accuracy_svm:.4f}")

박시니어 씨가 종이에 점들을 그리며 설명했습니다. "여기 빨간 점들과 파란 점들이 있어.

이 두 그룹을 나누는 선을 그으려면 어디에 그어야 할까?" 김개발 씨가 대충 중간쯤에 선을 그었습니다. "그것도 나쁘지 않아.

하지만 SVM은 더 똑똑해. 두 그룹에서 가장 가까운 점들, 이걸 서포트 벡터라고 하는데, 이 점들로부터 최대한 멀리 떨어진 선을 찾아.

마치 두 나라 사이에 완충지대를 최대한 넓게 두는 것처럼." 이 개념을 마진 최대화라고 부릅니다. 마진이 넓을수록 새로운 데이터가 들어왔을 때 올바르게 분류할 확률이 높아집니다.

약간 위치가 흔들려도 경계를 넘지 않을 테니까요. 텍스트 데이터는 TF-IDF 변환 후 수천 차원의 벡터가 됩니다.

이렇게 고차원 공간에서는 나이브 베이즈의 단순한 확률 계산보다 SVM의 기하학적 접근이 더 효과적인 경우가 많습니다. 코드에서 LinearSVC를 사용한 이유가 있습니다.

일반 SVM(SVC)은 커널 트릭을 사용하는데, 데이터가 많으면 매우 느려집니다. LinearSVC는 선형 커널만 사용하지만 훨씬 빠르고, 텍스트 분류에서는 선형 커널로도 충분한 성능을 낼 수 있습니다.

max_iter=10000은 최적화 알고리즘의 최대 반복 횟수입니다. 기본값으로 수렴하지 않을 때 이 값을 늘려줍니다.

김개발 씨가 결과를 비교해보았습니다. "오, SVM이 정확도가 조금 더 높네요!" "그렇지?

하지만 항상 그런 건 아니야. 데이터셋에 따라 나이브 베이즈가 더 좋을 수도 있어.

그래서 여러 알고리즘을 비교해보는 거야. 또한 SVM은 학습 시간이 더 오래 걸린다는 단점도 있어." 실무에서는 정확도뿐만 아니라 학습 시간, 예측 속도, 해석 가능성 등 여러 요소를 고려해서 알고리즘을 선택합니다.

단순히 숫자가 높은 것이 항상 좋은 선택은 아닙니다.

실전 팁

💡 - C 파라미터를 조절하여 마진의 엄격함을 조정할 수 있습니다

  • 대용량 데이터에서는 SGDClassifier로 대체하면 더 빠릅니다

6. 분류 성능 평가

김개발 씨는 두 모델의 정확도를 비교하고 뿌듯해했습니다. 그런데 박시니어 씨가 진지한 표정으로 말했습니다.

"정확도만 보면 안 돼. 스팸 분류에서는 다른 지표들이 더 중요할 수 있어." 김개발 씨는 처음 들어보는 이야기에 귀를 기울였습니다.

분류 성능 평가는 단순한 정확도를 넘어서 모델이 실제로 얼마나 잘 작동하는지를 다각도로 분석하는 과정입니다. 정밀도, 재현율, F1 점수, 혼동 행렬 등 다양한 지표를 통해 모델의 강점과 약점을 파악할 수 있습니다.

특히 클래스 불균형이 있는 데이터에서는 정확도가 오해를 불러일으킬 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay
import matplotlib.pyplot as plt

# 혼동 행렬 계산 및 시각화
cm = confusion_matrix(y_test, y_pred_svm)
disp = ConfusionMatrixDisplay(confusion_matrix=cm,
                               display_labels=['ham', 'spam'])
disp.plot(cmap='Blues')
plt.title('SVM Confusion Matrix')
plt.savefig('confusion_matrix.png')

# 상세 지표 출력
print("=== 성능 지표 상세 분석 ===")
tn, fp, fn, tp = cm.ravel()
print(f"True Negative (정상을 정상으로): {tn}")
print(f"False Positive (정상을 스팸으로): {fp}")
print(f"False Negative (스팸을 정상으로): {fn}")
print(f"True Positive (스팸을 스팸으로): {tp}")

precision = tp / (tp + fp)
recall = tp / (tp + fn)
print(f"\n정밀도: {precision:.4f}")
print(f"재현율: {recall:.4f}")

박시니어 씨가 예시를 들어 설명했습니다. "1000개의 메일 중에 스팸이 10개뿐이라고 해봐.

모든 메일을 정상으로 분류하면 정확도가 얼마일까?" 김개발 씨가 계산했습니다. "99%요?" "맞아.

하지만 스팸은 하나도 못 잡았잖아. 이게 정확도의 함정이야.

클래스 불균형이 심할 때 정확도는 의미가 없을 수 있어." 그래서 등장한 것이 정밀도재현율입니다. **정밀도(Precision)**는 모델이 스팸이라고 예측한 것 중에서 실제로 스팸인 비율입니다.

"내가 스팸이라고 한 것들이 진짜 스팸일까?"라는 질문에 답합니다. 정밀도가 낮으면 중요한 정상 메일이 스팸함으로 들어갑니다.

**재현율(Recall)**은 실제 스팸 중에서 모델이 스팸으로 찾아낸 비율입니다. "실제 스팸을 얼마나 잘 잡아냈나?"라는 질문에 답합니다.

재현율이 낮으면 스팸이 받은편지함에 그대로 남습니다. 스팸 분류에서는 어떤 것이 더 중요할까요?

상황에 따라 다릅니다. 중요한 비즈니스 이메일을 놓치면 안 되는 경우라면 정밀도가 중요합니다.

반대로 스팸을 절대 허용하면 안 되는 보안 환경에서는 재현율이 중요합니다. **혼동 행렬(Confusion Matrix)**은 이 모든 것을 한눈에 보여줍니다.

2x2 표로, 실제 값과 예측 값의 조합을 보여줍니다. True Positive, True Negative, False Positive, False Negative 네 가지 경우를 모두 확인할 수 있습니다.

특히 주목해야 할 것은 False PositiveFalse Negative입니다. False Positive는 정상 메일을 스팸으로 잘못 분류한 것이고, False Negative는 스팸을 정상으로 잘못 분류한 것입니다.

업무 환경에서는 대체로 False Positive가 더 치명적입니다. 중요한 클라이언트의 메일이 스팸함에 들어가면 큰 문제가 될 수 있으니까요.

김개발 씨가 혼동 행렬을 보며 고개를 끄덕였습니다. "이제 숫자 하나만 보고 판단하면 안 된다는 게 이해가 돼요." 박시니어 씨가 마무리했습니다.

"모델 평가는 단순히 점수를 높이는 게 아니야. 실제 비즈니스 상황에서 어떤 실수가 더 치명적인지 파악하고, 그에 맞게 모델을 조정하는 게 중요해."

실전 팁

💡 - 클래스 불균형이 심하면 F1 점수나 AUC-ROC를 함께 확인하세요

  • 실제 서비스에서는 임계값을 조정하여 정밀도와 재현율의 균형을 맞춥니다

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#Python#MachineLearning#NLP#NaiveBayes#SVM#Machine Learning,Python,NLP

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.