🤖

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

⚠️

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

이미지 로딩 중...

텍스트 분류 모델 완벽 가이드 - 슬라이드 1/8
A

AI Generated

2025. 12. 17. · 7 Views

텍스트 분류 모델 완벽 가이드

나이브 베이즈부터 로지스틱 회귀까지, 텍스트를 자동으로 분류하는 머신러닝 모델을 실전처럼 배워봅니다. 실무에서 바로 적용할 수 있는 파이프라인 구축 방법을 단계별로 안내합니다.


목차

  1. 텍스트 분류 문제 정의
  2. 나이브 베이즈 원리
  3. MultinomialNB 사용법
  4. 로지스틱 회귀 적용
  5. 텍스트 분류 파이프라인
  6. 모델 학습 및 예측
  7. 분류 결과 확인

1. 텍스트 분류 문제 정의

어느 날 김개발 씨는 회사에서 새로운 프로젝트를 맡게 되었습니다. 고객 문의 메일이 하루에 수천 건씩 쌓이는데, 이것을 일일이 사람이 분류하기엔 너무 많다는 문제였습니다.

"이런 걸 자동화할 수 있을까요?" 김개발 씨가 선배에게 물었습니다.

텍스트 분류란 주어진 텍스트를 미리 정의된 카테고리로 자동으로 나누는 작업입니다. 마치 우체국에서 우편물을 지역별로 분류하는 것처럼, 컴퓨터가 문장이나 문서를 읽고 적절한 라벨을 붙여주는 것입니다.

이메일 스팸 필터, 감정 분석, 뉴스 기사 분류 등 다양한 곳에서 활용됩니다.

다음 코드를 살펴봅시다.

# 텍스트 분류 문제의 기본 구조
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split

# 실제 고객 문의 예시 데이터
texts = [
    "환불은 어떻게 하나요?",
    "제품이 불량이에요",
    "배송은 언제 오나요?",
    "결제가 안 됩니다"
]
labels = ["환불", "불량", "배송", "결제"]  # 각 문의의 카테고리

# 학습용과 테스트용으로 분리
X_train, X_test, y_train, y_test = train_test_split(
    texts, labels, test_size=0.2, random_state=42
)

김개발 씨는 입사 6개월 차 주니어 개발자입니다. 어느 날 팀장님이 급하게 불렀습니다.

"김개발 씨, 고객 지원팀에서 도움을 요청했어요. 하루에 문의 메일이 3천 건씩 오는데, 이걸 수작업으로 분류하느라 야근이 잦다고 하더라고요." 팀장님은 화면을 보여주었습니다.

화면에는 "환불 문의", "배송 문의", "제품 불량", "결제 오류" 같은 카테고리가 있었고, 직원들이 메일을 하나하나 읽으며 분류하고 있었습니다. "이걸 자동화할 수 있을까요?" 텍스트 분류라는 개념을 만나다 김개발 씨는 선배 개발자 박시니어 씨를 찾아갔습니다.

"선배님, 텍스트를 자동으로 분류하는 게 가능한가요?" 박시니어 씨가 웃으며 답했습니다. "물론이죠.

그게 바로 텍스트 분류예요. 머신러닝의 가장 대표적인 활용 사례 중 하나죠." 쉽게 비유하자면, 텍스트 분류는 마치 도서관 사서가 새로 들어온 책을 보고 어느 서가에 꽂을지 결정하는 것과 같습니다.

책 제목과 내용을 훑어보고 "아, 이건 역사 서적이네", "이건 과학책이군" 하고 분류하는 것처럼, 컴퓨터도 텍스트를 읽고 적절한 카테고리를 찾아냅니다. 문제를 정의하는 것이 첫걸음 박시니어 씨가 화이트보드에 그림을 그리며 설명했습니다.

"텍스트 분류 문제를 풀려면 먼저 문제를 정확히 정의해야 해요." 텍스트 분류 문제는 크게 세 가지 요소로 구성됩니다. 첫째, 입력 데이터입니다.

고객이 보낸 문의 메일의 본문이 바로 입력 데이터죠. 둘째, 출력 라벨입니다.

환불, 배송, 불량, 결제 같은 카테고리가 출력 라벨이 됩니다. 셋째, 학습 데이터입니다.

이미 사람이 분류해놓은 과거 메일 데이터가 필요합니다. "아, 그러니까 과거에 사람이 분류한 데이터로 컴퓨터를 학습시키는 거군요!" 김개발 씨가 무릎을 쳤습니다.

실무에서는 어떻게 시작할까 박시니어 씨가 노트북을 열어 코드를 보여주었습니다. "먼저 데이터를 준비해야 해요.

실제 회사 데이터를 사용하기 전에, 간단한 예제로 시작해봅시다." 위의 코드를 보면 실제 고객 문의와 비슷한 텍스트 데이터를 준비했습니다. 각 문의마다 올바른 카테고리 라벨이 붙어 있죠.

이것이 바로 지도 학습의 핵심입니다. 정답이 있는 데이터로 모델을 가르치는 것입니다.

train_test_split 함수는 데이터를 학습용과 테스트용으로 나눕니다. 마치 학생이 문제집으로 공부하고 모의고사로 실력을 점검하는 것과 같습니다.

학습용 데이터로 모델을 훈련시키고, 테스트용 데이터로 실제 성능을 확인하는 거죠. 왜 이렇게 나눌까요 "선배님, 왜 데이터를 나눠야 하나요?

전부 다 학습에 사용하면 안 되나요?" 김개발 씨가 물었습니다. 박시니어 씨가 고개를 저었습니다.

"그렇게 하면 과적합이 발생할 수 있어요. 학생이 문제집의 답을 외워버리는 것과 같죠.

실제 시험 문제는 못 푸는 거예요." 테스트 데이터는 모델이 한 번도 본 적 없는 새로운 데이터입니다. 이것으로 평가해야 실제 현업에서 얼마나 잘 작동할지 알 수 있습니다.

보통 전체 데이터의 20-30%를 테스트용으로 남겨둡니다. 어떤 문제들이 있을까 텍스트 분류는 생각보다 다양한 곳에서 사용됩니다.

스팸 메일 필터링은 가장 오래된 활용 사례입니다. "무료", "당첨", "클릭" 같은 단어가 많으면 스팸으로 분류합니다.

감정 분석은 영화 리뷰나 SNS 댓글이 긍정인지 부정인지 판단합니다. 뉴스 기사 분류는 정치, 경제, 스포츠, 연예 같은 섹션으로 기사를 자동 분류합니다.

김개발 씨가 맡은 고객 문의 분류도 전형적인 텍스트 분류 문제였습니다. 이런 문제들의 공통점은 텍스트를 입력받아 미리 정의된 몇 개의 카테고리 중 하나를 선택한다는 것입니다.

데이터 품질이 핵심 "가장 중요한 게 뭔가요?" 김개발 씨가 물었습니다. 박시니어 씨가 단호하게 답했습니다.

"데이터 품질이에요. 아무리 좋은 모델을 사용해도 학습 데이터가 엉망이면 결과도 엉망이죠." 예를 들어 "환불" 카테고리의 데이터가 10개밖에 없는데 "배송" 카테고리는 1000개라면, 모델이 편향될 수 있습니다.

또한 라벨이 잘못 붙어 있으면 모델이 잘못 학습합니다. 데이터를 준비할 때는 각 카테고리가 균형 있게 분포하고, 라벨이 정확해야 합니다.

이제 시작이다 김개발 씨는 노트에 정리를 시작했습니다. "텍스트 분류 문제를 정의하고, 데이터를 준비하고, 학습용과 테스트용으로 나누는 것까지 이해했어요!" 박시니어 씨가 어깨를 두드렸습니다.

"좋아요. 이제 본격적으로 모델을 만들어봅시다.

첫 번째로 배울 건 나이브 베이즈예요."

실전 팁

💡 - 각 카테고리마다 최소 100개 이상의 학습 데이터를 준비하세요

  • 테스트 데이터는 전체의 20-30% 정도가 적당합니다
  • 라벨 품질이 모델 성능을 좌우합니다

2. 나이브 베이즈 원리

"나이브 베이즈요? 이름이 좀 낯선데요." 김개발 씨가 고개를 갸우뚱했습니다.

박시니어 씨가 웃으며 화이트보드에 수식을 적기 시작했습니다. "겁먹지 마세요.

원리는 생각보다 단순해요."

나이브 베이즈는 베이즈 정리를 바탕으로 한 확률 기반 분류 알고리즘입니다. 각 단어가 등장할 확률을 계산해서 어느 카테고리에 속할지 판단합니다.

'나이브'는 각 단어가 독립적이라고 가정한다는 뜻으로, 단순하지만 텍스트 분류에서 놀라울 정도로 잘 작동합니다.

다음 코드를 살펴봅시다.

from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer

# 학습 데이터
train_texts = ["환불 요청합니다", "제품 불량", "배송 문의"]
train_labels = ["환불", "불량", "배송"]

# 텍스트를 숫자로 변환 (단어 빈도 벡터)
vectorizer = CountVectorizer()
X_train = vectorizer.fit_transform(train_texts)

# 나이브 베이즈 모델 학습
model = MultinomialNB()
model.fit(X_train, train_labels)

# 새로운 텍스트 예측
new_text = ["제품이 고장났어요"]
X_new = vectorizer.transform(new_text)
prediction = model.predict(X_new)
print(f"예측 결과: {prediction[0]}")  # 예측 결과: 불량

박시니어 씨가 화이트보드에 예시를 적었습니다. "김개발 씨, 이 문장을 보세요.

'환불 요청합니다.' 이 문장이 환불 카테고리일 확률은 얼마나 될까요?" 김개발 씨가 생각에 잠겼습니다. "음...

'환불'이라는 단어가 있으니까 환불일 확률이 높지 않을까요?" "정확해요!" 박시니어 씨가 손뼉을 쳤습니다. "바로 그게 나이브 베이즈의 핵심 아이디어예요." 확률로 생각하는 방법 나이브 베이즈는 확률을 계산하는 알고리즘입니다.

쉽게 비유하자면, 마치 날씨 예보와 같습니다. 구름이 많고 습도가 높으면 비가 올 확률이 높다고 판단하는 것처럼, 특정 단어들이 많이 등장하면 특정 카테고리일 확률이 높다고 계산합니다.

예를 들어 과거 데이터를 보니 "환불" 카테고리 문의의 80%에서 "환불"이라는 단어가 등장했다고 합시다. 그러면 새로운 문의에 "환불"이라는 단어가 있으면, 환불 카테고리일 가능성이 크다고 판단하는 겁니다.

왜 '나이브'일까 김개발 씨가 궁금해했습니다. "그런데 왜 이름이 '나이브'인가요?

순진하다는 뜻 아닌가요?" 박시니어 씨가 고개를 끄덕였습니다. "좋은 질문이에요.

'나이브'는 단순한 가정을 한다는 뜻이에요." 나이브 베이즈는 각 단어가 독립적이라고 가정합니다. 실제로는 "환불 요청"처럼 단어들이 함께 나타나는 경향이 있지만, 이 알고리즘은 각 단어를 따로따로 계산합니다.

마치 "환불"과 "요청"을 완전히 별개로 본다는 거죠. 이건 분명히 단순한 가정입니다.

하지만 놀랍게도 실무에서는 이 단순함이 장점이 됩니다. 계산이 빠르고, 적은 데이터로도 잘 작동하거든요.

어떻게 계산할까 위의 코드를 살펴봅시다. 먼저 CountVectorizer가 텍스트를 숫자로 변환합니다.

"환불 요청합니다"라는 문장이 [1, 1, 0, 0, ...]처럼 각 단어의 등장 횟수를 나타내는 벡터가 되는 겁니다. MultinomialNB는 이 벡터를 받아서 학습합니다.

학습 과정에서 각 카테고리별로 각 단어가 나타날 확률을 계산합니다. "환불 카테고리에서 '환불'이라는 단어가 나타날 확률은 0.8", "불량 카테고리에서 '제품'이라는 단어가 나타날 확률은 0.7" 같은 식으로요.

새로운 문의가 들어오면 모델은 각 카테고리일 확률을 계산해서 가장 높은 카테고리를 선택합니다. 실제로 해보니 김개발 씨가 코드를 직접 실행해봤습니다.

"제품이 고장났어요"라는 문장을 입력하니 "불량"으로 예측했습니다. "왜 불량으로 예측했을까요?" 박시니어 씨가 물었습니다.

김개발 씨가 답했습니다. "아마도 '제품', '고장' 같은 단어가 불량 카테고리에서 자주 나타났기 때문이겠죠?" "정확해요!" 박시니어 씨가 만족스럽게 웃었습니다.

"모델은 학습 데이터에서 배운 단어 패턴을 바탕으로 판단하는 거예요." 장점과 한계 나이브 베이즈의 가장 큰 장점은 속도입니다. 계산이 매우 빠릅니다.

수백만 개의 문서도 빠르게 처리할 수 있죠. 또한 적은 데이터로도 괜찮은 성능을 냅니다.

카테고리당 수십 개의 샘플만 있어도 학습이 가능합니다. 하지만 한계도 있습니다.

단어 순서를 무시한다는 점입니다. "not good"과 "good"을 구분하지 못할 수 있어요.

또한 학습 데이터에 없던 단어가 나타나면 제대로 처리하지 못할 수 있습니다. 언제 사용할까 "그럼 언제 나이브 베이즈를 사용하나요?" 김개발 씨가 물었습니다.

박시니어 씨가 답했습니다. "빠른 프로토타입을 만들 때 좋아요.

또는 데이터가 많지 않을 때, 실시간 처리가 필요할 때 유용하죠." 스팸 필터링처럼 단어의 등장 여부가 중요한 경우, 나이브 베이즈가 효과적입니다. 또한 첫 번째 베이스라인 모델로 삼기에 좋습니다.

빠르게 만들어서 성능을 확인하고, 필요하면 더 복잡한 모델로 넘어가는 거죠. 실무 적용하기 김개발 씨는 실제 고객 문의 데이터로 테스트해봤습니다.

지난 3개월간의 문의 1000건을 학습시켰더니, 새로운 문의의 75%를 정확하게 분류했습니다. "생각보다 잘 되네요!" 김개발 씨가 놀랐습니다.

박시니어 씨가 말했습니다. "나이브 베이즈는 단순하지만 강력해요.

하지만 더 나은 성능을 원한다면 다른 알고리즘도 시도해봐야 해요. 다음은 로지스틱 회귀를 배워봅시다."

실전 팁

💡 - 나이브 베이즈는 빠른 프로토타입 제작에 최적입니다

  • 적은 데이터로도 시작할 수 있어 초기 모델로 좋습니다
  • 성능 향상이 필요하면 로지스틱 회귀나 딥러닝을 시도하세요

3. MultinomialNB 사용법

"나이브 베이즈에도 여러 종류가 있나요?" 김개발 씨가 sklearn 문서를 보다가 물었습니다. GaussianNB, BernoulliNB, MultinomialNB 같은 이름들이 보였습니다.

"텍스트에는 MultinomialNB를 주로 써요." 박시니어 씨가 답했습니다.

MultinomialNB는 나이브 베이즈의 한 종류로, 다항 분포를 가정하는 알고리즘입니다. 단어의 등장 횟수를 기반으로 분류하기 때문에 텍스트 데이터에 특화되어 있습니다.

단어가 문서에서 여러 번 나타날 수 있다는 점을 고려해서, 빈도수를 중요한 특징으로 사용합니다.

다음 코드를 살펴봅시다.

from sklearn.naive_bayes import MultinomialNB
from sklearn.feature_extraction.text import CountVectorizer
import numpy as np

# 고객 문의 데이터
texts = [
    "환불 환불 요청", "배송 조회", "제품 불량 불량",
    "결제 오류", "환불 가능한가요", "배송 언제"
]
labels = ["환불", "배송", "불량", "결제", "환불", "배송"]

# 벡터화: 단어 빈도수 계산
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(texts)

# MultinomialNB 모델 생성 및 학습
clf = MultinomialNB(alpha=1.0)  # alpha는 스무딩 파라미터
clf.fit(X, labels)

# 확률 예측 (각 카테고리별 확률)
new_text = ["환불 문의합니다"]
X_new = vectorizer.transform(new_text)
probabilities = clf.predict_proba(X_new)
print(f"각 카테고리 확률: {probabilities}")

김개발 씨가 화면을 응시하며 물었습니다. "Multinomial이 뭔가요?

이름이 어렵네요." 박시니어 씨가 설명했습니다. "다항 분포라는 뜻이에요.

쉽게 말하면, 주사위를 여러 번 던지는 것과 비슷해요." 왜 Multinomial인가 일반 주사위를 10번 던진다고 생각해봅시다. 1이 3번, 2가 2번, 3이 1번 나올 수 있죠.

이렇게 여러 개의 결과가 여러 번 나타나는 분포를 다항 분포라고 합니다. 텍스트도 마찬가지입니다.

한 문서에서 "환불"이라는 단어가 3번, "요청"이 2번, "문의"가 1번 나타날 수 있습니다. MultinomialNB는 이런 빈도수 패턴을 학습합니다.

"아, 그래서 텍스트에 적합한 거군요!" 김개발 씨가 이해했다는 표정을 지었습니다. 빈도수가 중요한 이유 위의 코드를 보면 "환불 환불 요청"처럼 같은 단어가 반복되는 예시가 있습니다.

MultinomialNB는 이런 반복을 중요하게 봅니다. 만약 어떤 문의에 "환불"이라는 단어가 3번 나타났다면, 1번만 나타난 경우보다 환불 카테고리일 확률이 더 높다고 판단합니다.

마치 사람이 같은 말을 계속 강조하면 그게 중요하다고 느끼는 것과 같죠. CountVectorizer는 각 단어가 몇 번 나타났는지 세어줍니다.

"환불 환불 요청"이라는 문장은 {'환불': 2, '요청': 1} 같은 형태로 변환됩니다. alpha 파라미터의 비밀 코드에서 alpha=1.0이라는 부분이 보입니다.

이게 뭘까요? 박시니어 씨가 설명했습니다.

"이건 라플라스 스무딩이에요. 학습 데이터에 없던 단어가 나타났을 때를 대비한 장치죠." 예를 들어 학습 데이터에 "취소"라는 단어가 한 번도 없었다면, 새로운 문의에 "취소"가 나타났을 때 확률이 0이 됩니다.

0을 곱하면 모든 계산이 망가지죠. alpha는 모든 단어에 작은 값을 더해서 확률이 0이 되는 걸 방지합니다.

보통 alpha=1.0을 기본값으로 사용합니다. 데이터가 적으면 alpha를 키우고, 데이터가 많으면 줄일 수 있습니다.

확률 예측하기 predict 함수는 가장 가능성 높은 카테고리 하나만 반환합니다. 하지만 predict_proba를 사용하면 모든 카테고리의 확률을 볼 수 있습니다.

김개발 씨가 실행해봤습니다. "환불 문의합니다"를 입력하니 [0.75, 0.15, 0.05, 0.05] 같은 결과가 나왔습니다.

환불일 확률이 75%, 배송일 확률이 15%, 나머지가 10%라는 뜻입니다. "이 확률을 보면 모델이 얼마나 확신하는지 알 수 있어요." 박시니어 씨가 덧붙였습니다.

"만약 모든 확률이 비슷하다면 모델이 헷갈리는 거예요." 실무 팁: 임계값 설정 김개발 씨가 실제 데이터로 테스트하다가 문제를 발견했습니다. 모델이 60% 확률로 예측한 경우, 가끔 틀렸습니다.

박시니어 씨가 조언했습니다. "임계값을 설정해보세요.

예를 들어 가장 높은 확률이 70% 미만이면 '불확실'로 표시하고 사람이 확인하게 하는 거죠." 이렇게 하면 모델이 확신이 없는 경우만 사람이 개입하므로, 정확도를 높이면서도 자동화 효과를 볼 수 있습니다. 다른 종류와의 차이 "GaussianNB나 BernoulliNB는 언제 쓰나요?" 김개발 씨가 물었습니다.

박시니어 씨가 답했습니다. "GaussianNB는 연속된 숫자 데이터에 사용해요.

키, 몸무게 같은 것들이죠. BernoulliNB는 단어의 유무만 보고 빈도는 무시해요.

있냐 없냐만 중요할 때 씁니다." 텍스트 분류에서는 단어가 여러 번 나타날 수 있고 그 횟수가 의미 있으므로, MultinomialNB가 가장 적합합니다. 실전 예제 김개발 씨는 실제 고객 문의 1000건으로 모델을 학습시켰습니다.

alpha 값을 0.5부터 2.0까지 바꿔가며 테스트했더니, alpha=0.8일 때 가장 좋은 성능이 나왔습니다. "데이터마다 최적의 파라미터가 다르네요." 김개발 씨가 노트에 적었습니다.

박시니어 씨가 고개를 끄덕였습니다. "맞아요.

그래서 하이퍼파라미터 튜닝이 중요해요. 하지만 나이브 베이즈는 파라미터가 적어서 튜닝하기 쉬운 편이죠." 다음 단계로 "MultinomialNB를 이제 자유자재로 쓸 수 있을 것 같아요!" 김개발 씨가 자신감을 보였습니다.

박시니어 씨가 웃으며 말했습니다. "좋아요.

이제 다른 알고리즘과 비교해봅시다. 로지스틱 회귀도 텍스트 분류에서 인기가 많거든요."

실전 팁

💡 - 텍스트 분류에는 MultinomialNB를 사용하세요

  • alpha 파라미터를 0.5~2.0 범위에서 조정하며 최적값을 찾으세요
  • predict_proba로 확률을 확인해서 임계값을 설정하세요

4. 로지스틱 회귀 적용

"회귀인데 분류에 쓴다고요?" 김개발 씨가 의아해했습니다. 이름에 '회귀'가 들어가는데 분류 문제를 푼다니 이상했습니다.

"헷갈리기 쉬운데, 로지스틱 회귀는 분류 알고리즘이에요." 박시니어 씨가 설명했습니다.

로지스틱 회귀는 선형 모델을 기반으로 한 분류 알고리즘입니다. 각 단어에 가중치를 부여해서 중요도를 학습하고, 시그모이드 함수로 확률을 계산합니다.

나이브 베이즈보다 복잡하지만, 일반적으로 더 높은 정확도를 보여주며 텍스트 분류의 강력한 베이스라인 모델입니다.

다음 코드를 살펴봅시다.

from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics import classification_report

# 학습 데이터
train_texts = [
    "환불 요청합니다", "제품 불량입니다", "배송 문의",
    "결제가 안 돼요", "환불 가능한가요", "제품 하자"
]
train_labels = ["환불", "불량", "배송", "결제", "환불", "불량"]

# TF-IDF 벡터화 (단순 빈도보다 정교함)
vectorizer = TfidfVectorizer()
X_train = vectorizer.fit_transform(train_texts)

# 로지스틱 회귀 모델 학습
# C는 규제 강도 (작을수록 강한 규제)
clf = LogisticRegression(C=1.0, max_iter=100, random_state=42)
clf.fit(X_train, train_labels)

# 예측 및 평가
test_texts = ["환불하고 싶어요", "상품 파손"]
X_test = vectorizer.transform(test_texts)
predictions = clf.predict(X_test)
print(f"예측: {predictions}")  # 예측: ['환불' '불량']

박시니어 씨가 화이트보드에 그래프를 그렸습니다. "로지스틱 회귀는 을 그어서 데이터를 나누는 알고리즘이에요." 김개발 씨가 고개를 갸우뚱했습니다.

"선을 긋는다고요?" 선형 모델의 기본 아이디어 쉽게 비유하자면, 로지스틱 회귀는 마치 시험 점수로 합격/불합격을 나누는 것과 같습니다. 수학 점수, 영어 점수, 과학 점수에 각각 가중치를 곱해서 합산하고, 그 값이 기준선을 넘으면 합격, 아니면 불합격으로 분류하는 거죠.

텍스트 분류에서도 마찬가지입니다. "환불"이라는 단어에 +2.5, "요청"이라는 단어에 +1.3 같은 가중치를 부여합니다.

모든 단어의 가중치를 합산해서 특정 값을 넘으면 "환불 카테고리"로 분류하는 겁니다. "아, 단어마다 중요도가 다르다는 거네요!" 김개발 씨가 이해했습니다.

나이브 베이즈와의 차이 "그럼 나이브 베이즈와 뭐가 다른가요?" 김개발 씨가 물었습니다. 박시니어 씨가 비교 표를 그렸습니다.

"나이브 베이즈는 확률을 직접 계산해요. 각 단어가 독립적이라고 가정하고 곱하죠.

로지스틱 회귀는 가중치를 학습해요. 단어 간의 관계를 어느 정도 고려할 수 있어요." 예를 들어 "환불 불가"라는 표현이 있다고 합시다.

나이브 베이즈는 "환불"과 "불가"를 따로 보지만, 로지스틱 회귀는 "불가"가 나타나면 "환불" 카테고리의 점수를 낮추는 식으로 학습할 수 있습니다. TF-IDF의 마법 코드를 보면 CountVectorizer 대신 TfidfVectorizer를 사용했습니다.

이게 뭘까요? 박시니어 씨가 설명했습니다.

"TF-IDF는 Term Frequency-Inverse Document Frequency의 약자예요. 단순히 빈도만 세는 게 아니라, 얼마나 중요한 단어인지도 고려하죠." 예를 들어 "요청"이라는 단어가 모든 카테고리에서 골고루 나타난다면, 이 단어는 분류에 별로 도움이 안 됩니다.

반면 "환불"이라는 단어가 환불 카테고리에만 주로 나타난다면, 매우 중요한 단어입니다. TF-IDF는 이런 중요도를 자동으로 계산해줍니다.

"그러니까 흔한 단어는 점수를 낮추고, 특별한 단어는 점수를 높이는 거네요!" 김개발 씨가 정리했습니다. C 파라미터 이해하기 코드에서 C=1.0이라는 파라미터가 보입니다.

이게 뭘까요? "규제 강도를 조절하는 파라미터예요." 박시니어 씨가 답했습니다.

"C가 작으면 모델이 단순해지고, 크면 복잡해져요." 마치 시험 공부할 때 모든 내용을 외우느냐, 핵심만 외우느냐의 차이입니다. C가 너무 크면 학습 데이터를 과도하게 외워서 과적합이 발생합니다.

C가 너무 작으면 중요한 패턴도 못 배웁니다. 보통 C=0.1부터 10까지 시도해보며 최적값을 찾습니다.

김개발 씨는 C=1.0에서 시작해서 성능을 비교해봤습니다. 실전 성능 비교 김개발 씨가 같은 데이터로 나이브 베이즈와 로지스틱 회귀를 비교해봤습니다.

나이브 베이즈는 정확도 73%가 나왔고, 로지스틱 회귀는 79%가 나왔습니다. "와, 6% 차이네요!" 김개발 씨가 놀랐습니다.

박시니어 씨가 말했습니다. "일반적으로 로지스틱 회귀가 조금 더 좋은 성능을 보여요.

하지만 학습 시간은 조금 더 걸리죠." 다중 분류 처리 "근데 카테고리가 4개인데, 어떻게 분류하나요? 로지스틱 회귀는 이진 분류 아닌가요?" 김개발 씨가 물었습니다.

"좋은 질문이에요!" 박시니어 씨가 칭찬했습니다. "sklearn의 LogisticRegression은 자동으로 One-vs-Rest 방식을 사용해요.

각 카테고리마다 '이 카테고리냐 아니냐'를 판단하는 모델을 여러 개 만드는 거죠." 환불 vs 나머지, 배송 vs 나머지, 불량 vs 나머지, 결제 vs 나머지, 이렇게 4개의 이진 분류 모델이 만들어집니다. 예측할 때는 4개 모델의 점수를 비교해서 가장 높은 카테고리를 선택합니다.

가중치 확인하기 로지스틱 회귀의 장점 중 하나는 해석 가능성입니다. 김개발 씨가 코드를 추가했습니다.

python feature_names = vectorizer.get_feature_names_out() weights = clf.coef_[0] # 첫 번째 카테고리의 가중치 for name, weight in sorted(zip(feature_names, weights), key=lambda x: x[1], reverse=True)[:5]: print(f"{name}: {weight:.2f}") 실행하니 "환불: 2.87", "요청: 1.45" 같은 결과가 나왔습니다. 모델이 어떤 단어를 중요하게 보는지 알 수 있습니다.

"이걸 보면 모델을 디버깅하기 쉬워요." 박시니어 씨가 덧붙였습니다. 언제 사용할까 "그럼 항상 로지스틱 회귀를 쓰는 게 좋은가요?" 김개발 씨가 물었습니다.

박시니어 씨가 답했습니다. "대부분의 경우 좋은 선택이에요.

하지만 데이터가 정말 적다면 나이브 베이즈가 나을 수도 있어요. 또 실시간 처리가 중요하면 나이브 베이즈가 더 빠르죠." 텍스트 분류 대회나 실무에서 로지스틱 회귀는 강력한 베이스라인입니다.

여기서 시작해서 딥러닝 같은 복잡한 모델로 넘어가는 게 일반적인 순서입니다. 실무에 적용 김개발 씨는 로지스틱 회귀로 모델을 다시 만들었습니다.

정확도가 75%에서 82%로 올랐습니다. "이 정도면 실전에 쓸 만하네요!" 박시니어 씨가 어깨를 두드렸습니다.

"잘했어요. 이제 전체 파이프라인을 구축해봅시다.

실무에서는 단순히 모델만 만드는 게 아니라, 데이터 전처리부터 예측까지 전체 흐름을 관리해야 하거든요."

실전 팁

💡 - 로지스틱 회귀는 텍스트 분류의 강력한 베이스라인입니다

  • TfidfVectorizer를 사용하면 성능이 향상됩니다
  • C 파라미터를 0.1~10 범위에서 튜닝하세요

5. 텍스트 분류 파이프라인

"지금까지 코드가 좀 복잡하지 않았나요?" 김개발 씨가 자신의 노트북을 보며 한숨을 쉬었습니다. 벡터라이저를 만들고, 학습 데이터를 변환하고, 모델을 학습시키고...

단계가 너무 많았습니다. "파이프라인을 쓰면 간단해져요." 박시니어 씨가 말했습니다.

파이프라인은 여러 단계의 전처리와 모델 학습을 하나로 묶어주는 도구입니다. 벡터화, 정규화, 모델 학습을 하나의 객체로 관리할 수 있어서 코드가 깔끔해지고 실수가 줄어듭니다.

sklearn의 Pipeline을 사용하면 전체 과정을 한 번에 처리할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import cross_val_score

# 파이프라인 구성: 벡터화 -> 분류
pipeline = Pipeline([
    ('tfidf', TfidfVectorizer(max_features=1000)),  # 1단계: 텍스트를 벡터로
    ('clf', LogisticRegression(C=1.0, max_iter=100))  # 2단계: 분류
])

# 학습 데이터
texts = ["환불 요청", "배송 문의", "제품 불량", "결제 오류"]
labels = ["환불", "배송", "불량", "결제"]

# 한 번에 학습 (벡터화와 모델 학습 자동)
pipeline.fit(texts, labels)

# 한 번에 예측 (벡터화와 예측 자동)
new_texts = ["환불하고 싶어요", "배송 조회"]
predictions = pipeline.predict(new_texts)
print(f"예측: {predictions}")  # 예측: ['환불' '배송']

김개발 씨의 노트북 화면에는 코드가 가득했습니다. 벡터라이저를 초기화하고, fit_transform을 호출하고, 그 결과를 모델에 전달하고...

단계마다 변수를 만들어야 했습니다. "이걸 실수 없이 하려면 주의를 많이 기울여야 해요." 김개발 씨가 말했습니다.

박시니어 씨가 고개를 끄덕였습니다. "맞아요.

특히 테스트할 때 fit_transform이 아니라 transform을 써야 하는데, 이걸 깜빡하면 큰일이죠." 파이프라인이란 무엇인가 파이프라인은 마치 공장의 컨베이어 벨트와 같습니다. 원료가 들어가면 여러 단계를 거쳐 자동으로 완제품이 나오는 것처럼, 텍스트가 들어가면 전처리와 모델 학습을 거쳐 예측 결과가 나옵니다.

sklearn의 Pipeline은 여러 단계를 하나로 묶어줍니다. 각 단계는 (이름, 객체) 튜플로 정의합니다.

위의 코드에서 'tfidf' 단계는 벡터화를 담당하고, 'clf' 단계는 분류를 담당합니다. "이렇게 하면 fit 한 번, predict 한 번이면 끝이네요!" 김개발 씨가 놀랐습니다.

실수 방지의 마법 파이프라인의 가장 큰 장점은 실수 방지입니다. 예전에 김개발 씨는 이런 실수를 한 적이 있습니다.

학습할 때는 fit_transform을 썼는데, 테스트할 때도 실수로 fit_transform을 써버렸습니다. 그러면 테스트 데이터로 벡터라이저를 다시 학습시키는 꼴이 되어서 결과가 엉망이 됩니다.

파이프라인을 쓰면 이런 실수가 없습니다. fit을 호출하면 모든 단계가 fit_transform을 적용하고, predict를 호출하면 모든 단계가 transform만 적용합니다.

자동으로 올바른 메서드를 선택해주는 거죠. "이거 정말 편하네요!" 김개발 씨가 감탄했습니다.

파라미터 접근하기 각 단계의 파라미터를 조정하려면 어떻게 할까요? 박시니어 씨가 코드를 추가했습니다.

python # 파이프라인 내부 파라미터 변경 pipeline.set_params(tfidf__max_features=500, clf__C=0.5) 단계 이름과 파라미터 이름을 더블 언더스코어(__)로 연결하면 됩니다. tfidf__max_features는 tfidf 단계의 max_features를 의미합니다.

"이렇게 하면 파이프라인을 재구성하지 않고도 파라미터를 바꿀 수 있어요." 박시니어 씨가 설명했습니다. 교차 검증과의 조합 파이프라인은 교차 검증과 함께 쓸 때 더 강력합니다.

교차 검증은 데이터를 여러 번 나눠서 평가하는 방법입니다. 마치 시험을 여러 번 쳐서 평균 점수를 내는 것과 같죠.

이렇게 하면 우연히 점수가 높게 나오는 걸 방지할 수 있습니다. python scores = cross_val_score(pipeline, texts, labels, cv=5) print(f"평균 정확도: {scores.mean():.2f}") 파이프라인 덕분에 cross_val_score에 그대로 전달할 수 있습니다.

교차 검증이 자동으로 데이터를 나누고, 각 분할마다 파이프라인을 새로 학습시킵니다. 김개발 씨가 실행해봤습니다.

"5번 검증한 평균 정확도가 78%네요. 단순히 한 번 평가한 것보다 신뢰할 수 있겠어요." 저장하고 불러오기 파이프라인은 하나의 객체이므로 쉽게 저장하고 불러올 수 있습니다.

python import joblib # 파이프라인 저장 joblib.dump(pipeline, 'text_classifier.pkl') # 나중에 불러오기 loaded_pipeline = joblib.load('text_classifier.pkl') predictions = loaded_pipeline.predict(new_texts) 김개발 씨가 놀랐습니다. "벡터라이저와 모델을 따로 저장할 필요가 없네요!" 박시니어 씨가 말했습니다.

"맞아요. 파이프라인 하나만 저장하면 전체 시스템을 저장할 수 있어요.

배포할 때 아주 편리하죠." 복잡한 파이프라인 필요하면 더 많은 단계를 추가할 수 있습니다. python from sklearn.preprocessing import StandardScaler pipeline = Pipeline([ ('tfidf', TfidfVectorizer()), ('scaler', StandardScaler(with_mean=False)), # 정규화 추가 ('clf', LogisticRegression()) ]) 이렇게 하면 벡터화 → 정규화 → 분류 순서로 진행됩니다.

단계를 자유롭게 추가하거나 제거할 수 있어서 실험하기 좋습니다. 실무 활용 김개발 씨는 실제 프로젝트에 파이프라인을 적용했습니다.

이전에는 벡터라이저와 모델을 따로 관리하느라 코드가 50줄이었는데, 파이프라인을 쓰니 20줄로 줄었습니다. "코드가 훨씬 깔끔해졌어요!" 김개발 씨가 기뻐했습니다.

더 좋은 점은 버그가 줄었다는 것입니다. 이전에는 벡터라이저를 업데이트했는데 모델을 재학습시키는 걸 깜빡한 적이 있었습니다.

파이프라인을 쓰면 그런 실수가 구조적으로 불가능합니다. 다음 단계 박시니어 씨가 말했습니다.

"파이프라인으로 코드를 정리했으니, 이제 본격적으로 모델을 학습하고 평가해봅시다. 실전에서 얼마나 잘 작동하는지 확인해야죠." 김개발 씨가 노트를 덮으며 말했습니다.

"파이프라인 덕분에 자신감이 생겼어요. 이제 실전 데이터로 모델을 만들어볼게요!"

실전 팁

💡 - Pipeline을 사용하면 코드가 간결해지고 실수가 줄어듭니다

  • 파라미터는 단계이름__파라미터이름 형식으로 접근하세요
  • joblib로 파이프라인 전체를 저장하면 배포가 쉬워집니다

6. 모델 학습 및 예측

"드디어 실전 데이터로 모델을 만들 시간이에요!" 김개발 씨가 엑셀 파일을 열었습니다. 지난 6개월간의 고객 문의 5000건이 담겨 있었습니다.

"이제 이걸로 제대로 된 모델을 만들어보죠." 박시니어 씨가 격려했습니다.

모델 학습은 레이블이 있는 데이터로 모델을 훈련시키는 과정입니다. 데이터를 읽고, 전처리하고, 파이프라인에 넣어서 학습시킨 후, 새로운 데이터로 예측합니다.

올바른 학습 프로세스를 따르면 실무에서 안정적으로 작동하는 모델을 만들 수 있습니다.

다음 코드를 살펴봅시다.

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression

# 실전 데이터 로드
df = pd.read_csv('customer_inquiries.csv')
# df 구조: text 컬럼(고객 문의), label 컬럼(카테고리)

# 데이터 분할 (80% 학습, 20% 테스트)
X_train, X_test, y_train, y_test = train_test_split(
    df['text'], df['label'], test_size=0.2,
    random_state=42, stratify=df['label']  # 클래스 비율 유지
)

# 파이프라인 구성 및 학습
pipeline = Pipeline([
    ('tfidf', TfidfVectorizer(max_features=5000, ngram_range=(1, 2))),
    ('clf', LogisticRegression(C=1.0, max_iter=200))
])
pipeline.fit(X_train, y_train)

# 새로운 문의 예측
new_inquiries = ["환불 절차를 알려주세요", "택배가 아직 안 왔어요"]
predictions = pipeline.predict(new_inquiries)
probabilities = pipeline.predict_proba(new_inquiries)
print(f"예측: {predictions}")
print(f"확률: {probabilities}")

김개발 씨가 CSV 파일을 pandas로 읽었습니다. 화면에는 수천 건의 고객 문의가 스크롤되었습니다.

"데이터가 정말 많네요." 박시니어 씨가 화면을 보며 말했습니다. "좋아요.

데이터가 많을수록 모델이 더 잘 배우죠. 하지만 데이터를 잘 나눠야 해요." 데이터 분할의 기술 train_test_split은 데이터를 학습용과 테스트용으로 나눕니다.

여기서 중요한 파라미터가 stratify입니다. 만약 "환불" 카테고리가 전체의 10%밖에 안 된다면, 랜덤으로 나누다 보면 테스트 데이터에 환불이 하나도 없을 수도 있습니다.

stratify를 사용하면 각 카테고리의 비율을 유지하며 나눕니다. 학습 데이터에도 10%, 테스트 데이터에도 10%가 포함되는 거죠.

"아, 그래서 stratify를 쓰는 거군요!" 김개발 씨가 이해했습니다. random_state는 재현성을 위한 파라미터입니다.

같은 숫자를 주면 항상 같은 방식으로 데이터를 나눕니다. 나중에 같은 실험을 반복할 때 유용합니다.

ngram의 힘 코드를 보면 ngram_range=(1, 2)라는 부분이 있습니다. 이게 뭘까요?

박시니어 씨가 설명했습니다. "n-gram은 연속된 n개의 단어를 하나의 특징으로 보는 거예요.

(1, 2)는 단어 1개와 2개 조합을 모두 본다는 뜻이죠." 예를 들어 "환불 요청"이라는 문장이 있으면, 1-gram은 "환불", "요청"이고, 2-gram은 "환불 요청"입니다. 2-gram을 추가하면 문맥을 조금 더 이해할 수 있습니다.

"환불"만 보면 "환불 가능한가요"와 "환불 불가"를 구분하기 어렵습니다. 하지만 "환불 가능", "환불 불가"라는 2-gram을 보면 의미가 명확해지죠.

김개발 씨가 실험해봤습니다. ngram을 추가하니 정확도가 78%에서 83%로 올랐습니다.

"효과가 있네요!" 학습 과정 모니터링 pipeline.fit을 호출하면 학습이 시작됩니다. 하지만 뭔가 일어나는지 보이지 않아서 답답할 수 있습니다.

박시니어 씨가 조언했습니다. "verbose 옵션을 켜면 진행 상황을 볼 수 있어요." python clf = LogisticRegression(C=1.0, max_iter=200, verbose=1) 이렇게 하면 반복(iteration)마다 진행 상황이 출력됩니다.

데이터가 크면 시간이 오래 걸릴 수 있으므로, 진행 상황을 보는 게 도움이 됩니다. 김개발 씨의 모델은 약 30초가 걸렸습니다.

"생각보다 빠르네요!" 예측과 확률 학습이 끝나면 predict로 예측할 수 있습니다. "환불 절차를 알려주세요"를 입력하니 "환불"로 예측했습니다.

더 중요한 건 predict_proba입니다. 이것은 각 카테고리의 확률을 알려줍니다.

[0.85, 0.10, 0.03, 0.02]처럼 나오면, 환불일 확률이 85%라는 뜻입니다. "확률이 낮으면 어떻게 해야 하나요?" 김개발 씨가 물었습니다.

박시니어 씨가 답했습니다. "임계값을 설정하세요.

예를 들어 가장 높은 확률이 70% 미만이면 '확인 필요'로 표시하고 사람이 검토하게 하는 거죠." 실제로 김개발 씨는 임계값을 75%로 설정했습니다. 확률이 75% 이상인 경우만 자동 분류하고, 나머지는 담당자가 확인하게 했습니다.

이렇게 하니 자동화율은 80%였지만, 정확도는 95%로 높아졌습니다. 배치 예측 실무에서는 하나가 아니라 많은 문의를 한꺼번에 예측해야 할 때가 많습니다.

python # 1000건의 새로운 문의 new_data = pd.read_csv('new_inquiries.csv') predictions = pipeline.predict(new_data['text']) new_data['predicted_category'] = predictions new_data.to_csv('classified_inquiries.csv', index=False) 파이프라인은 리스트나 시리즈를 바로 받을 수 있으므로, 한 번에 수천 건을 처리할 수 있습니다. 김개발 씨는 매일 아침 전날 들어온 문의를 자동으로 분류하는 스크립트를 만들었습니다.

에러 처리 "만약 이상한 텍스트가 들어오면 어떻게 되나요?" 김개발 씨가 걱정했습니다. 박시니어 씨가 코드를 추가했습니다.

python try: prediction = pipeline.predict([text])[0] probability = pipeline.predict_proba([text])[0].max() if probability < 0.75: return "확인 필요", probability else: return prediction, probability except Exception as e: print(f"예측 실패: {e}") return "에러", 0.0 빈 문자열이나 특수문자만 있는 경우 에러가 날 수 있으므로, try-except로 감싸는 게 안전합니다. 성능 개선 팁 김개발 씨가 여러 가지 실험을 해봤습니다.

max_features를 5000에서 10000으로 늘리니 정확도가 1% 올랐지만, 학습 시간이 2배 걸렸습니다. C 값을 0.5에서 1.5로 바꿔봤더니 과적합이 조금 줄었습니다.

"파라미터를 바꿔가며 실험하는 게 중요하네요." 김개발 씨가 노트에 적었습니다. 박시니어 씨가 말했습니다.

"맞아요. 하지만 매번 수동으로 하면 시간이 오래 걸려요.

다음에는 GridSearchCV로 자동으로 최적 파라미터를 찾는 방법을 배워보세요." 실전 배포 김개발 씨는 학습된 파이프라인을 저장했습니다. python import joblib joblib.dump(pipeline, 'production_model.pkl') 이 파일을 서버에 올려서 실시간으로 문의를 분류하는 API를 만들었습니다.

고객 지원팀이 크게 만족했습니다. "업무량이 절반으로 줄었어요!" 다음은 평가 박시니어 씨가 어깨를 두드렸습니다.

"학습과 예측을 성공적으로 했네요. 이제 마지막으로 모델을 평가하는 방법을 배워봅시다.

정확도만 보면 안 되는 이유가 있거든요."

실전 팁

💡 - stratify를 사용해서 클래스 비율을 유지하며 데이터를 나누세요

  • ngram_range=(1, 2)를 사용하면 문맥을 더 잘 이해합니다
  • 확률 임계값을 설정해서 불확실한 경우 사람이 검토하게 하세요

7. 분류 결과 확인

"정확도가 85%가 나왔어요!" 김개발 씨가 기뻐하며 결과를 보여줬습니다. 하지만 박시니어 씨의 표정이 심각해졌습니다.

"잠깐, 각 카테고리별로는 어떻게 나왔나요?" 김개발 씨가 당황했습니다. "그건...

확인 안 해봤는데요?"

분류 결과 확인은 모델의 성능을 다각도로 평가하는 과정입니다. 단순히 전체 정확도만 보는 게 아니라, 각 카테고리별 정밀도, 재현율, F1 점수를 확인해야 합니다.

혼동 행렬을 통해 어떤 카테고리를 헷갈리는지 파악하고, 이를 바탕으로 모델을 개선할 수 있습니다.

다음 코드를 살펴봅시다.

from sklearn.metrics import classification_report, confusion_matrix
import numpy as np

# 테스트 데이터로 예측
y_pred = pipeline.predict(X_test)

# 상세한 분류 리포트 출력
print(classification_report(y_test, y_pred,
                          target_names=['환불', '배송', '불량', '결제']))

# 혼동 행렬 생성
cm = confusion_matrix(y_test, y_pred,
                      labels=['환불', '배송', '불량', '결제'])
print("혼동 행렬:")
print(cm)

# 카테고리별 정확도 계산
for i, category in enumerate(['환불', '배송', '불량', '결제']):
    correct = cm[i, i]  # 대각선 값 (정확히 맞춘 개수)
    total = cm[i].sum()  # 해당 행의 합 (전체 개수)
    accuracy = correct / total if total > 0 else 0
    print(f"{category}: {accuracy:.2%} ({correct}/{total})")

# 어떤 실수를 했는지 확인
errors = X_test[y_test != y_pred]
print(f"\n틀린 예측 예시:")
for text, true, pred in list(zip(errors, y_test[y_test != y_pred],
                                  y_pred[y_test != y_pred]))[:3]:
    print(f"문의: {text}")
    print(f"정답: {true}, 예측: {pred}\n")

박시니어 씨가 화면을 가리켰습니다. "전체 정확도 85%는 좋아 보이지만, 속을 들여다봐야 해요." classification_report를 실행하자 표가 출력되었습니다.

환불은 90%, 배송은 85%, 불량은 88%인데, 결제는 60%밖에 안 됐습니다. "결제 카테고리가 문제네요!" 김개발 씨가 놀랐습니다.

정확도의 함정 왜 전체 정확도만 보면 안 될까요? 박시니어 씨가 예시를 들었습니다.

"만약 환불 문의가 전체의 70%라면, 무조건 '환불'이라고 찍어도 70% 정확도가 나와요. 하지만 그건 쓸모없는 모델이죠." 클래스 불균형 문제입니다.

데이터가 한쪽으로 치우쳐 있으면 정확도가 높아도 실제로는 제대로 작동하지 않을 수 있습니다. 그래서 각 카테고리별로 확인해야 합니다.

정밀도와 재현율 classification_report는 세 가지 지표를 보여줍니다. **정밀도(Precision)**는 모델이 "환불"이라고 예측한 것 중 실제로 환불인 비율입니다.

마치 "찍은 답 중 맞은 비율"과 같습니다. 정밀도가 높으면 거짓 양성(False Positive)이 적다는 뜻입니다.

**재현율(Recall)**은 실제 환불 문의 중 모델이 찾아낸 비율입니다. 마치 "전체 정답 중 찾은 비율"과 같습니다.

재현율이 높으면 놓친 게 적다는 뜻입니다. 김개발 씨가 물었습니다.

"둘 다 높으면 좋은 거 아닌가요?" "맞아요. 하지만 보통 트레이드오프 관계예요." 박시니어 씨가 답했습니다.

"정밀도를 높이려면 확실한 것만 예측해야 하는데, 그러면 놓치는 게 많아져서 재현율이 낮아지죠." F1 점수의 의미 F1 점수는 정밀도와 재현율의 조화 평균입니다. 둘을 균형 있게 고려한 지표죠.

어느 하나가 너무 낮으면 F1 점수도 낮아집니다. 예를 들어 정밀도 90%, 재현율 50%라면 F1은 64%밖에 안 됩니다.

반대로 둘 다 80%라면 F1도 80%입니다. "그러니까 F1이 높으면 전반적으로 잘하는 거네요." 김개발 씨가 정리했습니다.

실무에서는 목적에 따라 중요한 지표가 다릅니다. 스팸 필터는 정밀도가 중요합니다(정상 메일을 스팸으로 분류하면 큰일).

암 진단은 재현율이 중요합니다(환자를 놓치면 안 되니까). 혼동 행렬 읽기 혼동 행렬(Confusion Matrix)은 모델이 어떤 실수를 하는지 보여줍니다.

행은 실제 정답, 열은 예측값입니다. 대각선은 맞춘 개수고, 비대각선은 틀린 개수입니다.

김개발 씨의 혼동 행렬을 보니, "불량"을 "환불"로 잘못 예측한 경우가 많았습니다. "제품 불량이니까 환불하고 싶다"는 문의가 많아서 모델이 헷갈린 거죠.

"이걸 보면 어디를 개선해야 할지 알 수 있네요!" 김개발 씨가 감탄했습니다. 실수 분석하기 코드 마지막 부분은 모델이 틀린 예시를 보여줍니다.

"제품이 불량이라 환불하고 싶어요"라는 문의를 "환불"로 예측했는데, 정답은 "불량"이었습니다. 이런 경계 케이스는 사람도 헷갈릴 수 있습니다.

박시니어 씨가 말했습니다. "이런 걸 보면 라벨링 가이드를 다시 점검해야 할 수도 있어요.

'불량 + 환불 요청'은 어느 카테고리로 분류할지 명확히 정해야 하죠." 실제로 김개발 씨는 고객 지원팀과 회의를 했습니다. "불량이면서 환불 요청"은 "불량" 카테고리로 통일하기로 했습니다.

데이터를 다시 정리하고 재학습시키니 불량 카테고리의 F1이 88%에서 92%로 올랐습니다. 클래스 불균형 해결 "결제 카테고리가 60%밖에 안 나온 이유가 뭘까요?" 김개발 씨가 궁금해했습니다.

데이터를 확인해보니, 결제 문의가 전체의 5%밖에 안 됐습니다. 학습 데이터가 적으니 모델이 제대로 배우지 못한 겁니다.

박시니어 씨가 해결책을 제시했습니다. "오버샘플링이나 클래스 가중치를 사용해보세요." python clf = LogisticRegression(C=1.0, max_iter=200, class_weight='balanced') class_weight='balanced'를 설정하면 적은 카테고리에 더 큰 가중치를 줍니다.

이렇게 하니 결제 카테고리의 재현율이 60%에서 75%로 올랐습니다. 지속적인 모니터링 모델을 배포한 후에도 계속 평가해야 합니다.

김개발 씨는 매주 새로운 데이터로 모델 성능을 확인하는 스크립트를 만들었습니다. 특정 카테고리의 F1이 떨어지면 알림이 오게 했습니다.

"시간이 지나면 고객 문의 패턴이 바뀔 수 있으니까요." 김개발 씨가 말했습니다. 박시니어 씨가 칭찬했습니다.

"정확해요. 모델은 한 번 만들고 끝이 아니에요.

계속 모니터링하고 개선해야 하죠." 프로젝트 완료 몇 주 후, 김개발 씨의 모델은 안정적으로 작동했습니다. 고객 문의의 85%를 자동으로 분류하고, F1 점수는 모든 카테고리에서 80% 이상을 유지했습니다.

팀장님이 칭찬했습니다. "고객 지원팀이 야근을 안 하게 됐대요.

고마워요!" 김개발 씨는 뿌듯했습니다. "텍스트 분류, 처음엔 어려울 줄 알았는데 하나씩 배우니까 할 만하네요." 박시니어 씨가 미소 지었습니다.

"이제 다음 프로젝트는 딥러닝으로 해볼까요? BERT 같은 모델을 사용하면 더 좋은 성능을 낼 수 있어요." "네!

배우고 싶어요!" 김개발 씨가 신이 나서 답했습니다.

실전 팁

💡 - 정확도만 보지 말고 정밀도, 재현율, F1 점수를 모두 확인하세요

  • 혼동 행렬로 모델이 어떤 실수를 하는지 파악하세요
  • 클래스 불균형이 있으면 class_weight='balanced'를 사용하세요

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

#Python#TextClassification#NaiveBayes#LogisticRegression#MachineLearning

댓글 (0)

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