문서 분류의 가장 단순하고 강한 기준선은 텍스트를 TF-IDF 벡터로 바꾸고 선형 분류기를 학습하는 것이다. 형태소 분석이나 거대한 언어 모델부터 시작하지 않아도, 데이터 분할과 평가를 제대로 하면 뉴스 주제·문의 유형·스팸 분류 같은 문제의 첫 결과를 빠르게 만들 수 있다.
기존 노트에는 형태소, 코사인 유사도, 로지스틱 회귀, 혼동 행렬까지 관련 용어를 거의 모두 넣었다. 다시 보니 실제로 모델을 만드는 순서와 각 단계의 선택 이유가 잘 보이지 않았다. 이번에는 데이터 확인 → 분할 → 벡터화 → 학습 → 오류 분석 흐름으로 정리했다.
문서 분류는 어떤 문제인가
입력은 문서이고 출력은 미리 정한 카테고리다. 예를 들어 고객 문의를 결제, 배송, 환불로 나누거나 뉴스를 정치, 경제, 스포츠로 분류할 수 있다.
지도학습이므로 각 문서에는 정답 라벨이 필요하다. 모델보다 먼저 확인할 것은 데이터다.
- 카테고리 정의가 서로 겹치지 않는가
- 라벨을 붙인 사람마다 기준이 같은가
- 클래스별 문서 수가 지나치게 불균형하지 않은가
- 같은 기사나 템플릿 문장이 학습·테스트에 동시에 들어가지 않았는가
- 시간 순서가 중요한 문제인데 무작위로 섞지는 않았는가
“정확도는 보통 70~90%”처럼 일반적인 숫자를 기대값으로 두면 안 된다. 라벨의 난이도, 데이터 중복, 클래스 수와 평가 방식에 따라 점수의 의미가 완전히 달라진다.
텍스트를 숫자로 바꾸는 가장 단순한 방법
Bag-of-Words는 문서에 어떤 토큰이 몇 번 나타났는지를 센다. TF-IDF는 여기에 전체 문서에서 흔한 단어의 가중치를 낮추고, 특정 문서에서 상대적으로 두드러지는 단어의 가중치를 높인다.
직관만 남기면 다음과 같다.
TF-IDF = 문서 안에서의 단어 빈도 × 전체 문서에서의 희귀성
scikit-learn의 TfidfVectorizer는 토큰 수 집계, IDF 가중치와 정규화를 한 번에 처리해 희소 행렬을 만든다. 기본 L2 정규화를 사용하면 두 문서 벡터의 내적이 코사인 유사도와 연결된다.
한국어는 토큰화를 어떻게 할까
한국어 텍스트라고 항상 형태소 분석기가 필수인 것은 아니다. 기준선을 만들 때는 몇 가지 선택을 비교해 볼 수 있다.
| 방식 | 장점 | 주의점 |
|---|---|---|
| 띄어쓰기 기반 word n-gram | 빠르고 설명하기 쉽다. | 조사·어미와 띄어쓰기 변형에 민감하다. |
| character n-gram | 오탈자와 활용형 변화에 비교적 강하다. | 특성 수가 커지고 설명이 어려워질 수 있다. |
| 형태소 분석 | 어간·품사 단위로 다룰 수 있다. | 사전·설치·도메인 용어 관리가 필요하다. |
처음부터 하나를 정답으로 두기보다 동일한 데이터 분할에서 word n-gram, character n-gram, 형태소 토큰을 비교하는 편이 낫다. 형태소 분석을 쓴다면 학습과 예측에서 같은 전처리기와 사전 버전을 사용해야 한다.
데이터 누수를 막는 분할 순서
TF-IDF의 vocabulary와 IDF는 학습 데이터에서만 배워야 한다. 전체 문서로 벡터라이저를 먼저 학습한 뒤 테스트 세트를 나누면 테스트 문서의 단어 분포가 학습 단계에 섞인다.
안전한 순서는 다음과 같다.
- 원문과 라벨을 학습·테스트로 먼저 나눈다.
- 학습 문서로만
TfidfVectorizer.fit을 수행한다. - 같은 벡터라이저로 테스트 문서를
transform한다. - 학습 세트로 분류기를 학습하고 테스트 세트는 마지막 평가에만 쓴다.
Pipeline을 사용하면 이 경계를 코드로 묶을 수 있다.
TF-IDF와 로지스틱 회귀 기준선 만들기
CSV에 text, label 열이 있다고 가정한 최소 예제다.
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
from sklearn.metrics import classification_report, confusion_matrix
df = pd.read_csv("documents.csv").dropna(subset=["text", "label"])
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"],
)
model = Pipeline([
("tfidf", TfidfVectorizer(
ngram_range=(1, 2),
min_df=2,
sublinear_tf=True,
)),
("classifier", LogisticRegression(max_iter=1000)),
])
model.fit(X_train, y_train)
pred = model.predict(X_test)
print(classification_report(y_test, pred))
print(confusion_matrix(y_test, pred))
이 코드는 완성된 정답이 아니라 비교 기준이다. 데이터가 적다면 min_df=2 때문에 중요한 희귀 단어가 사라질 수 있고, 클래스가 크게 불균형하다면 class_weight="balanced"가 후보가 될 수 있다. 설정은 반드시 검증 결과와 오류 사례를 보고 정한다.
정확도만 보면 놓치는 것
클래스 비율이 불균형하면 accuracy가 높아도 필요한 문서를 거의 찾지 못할 수 있다.
- Precision: 해당 클래스로 예측한 문서 중 실제로 맞은 비율
- Recall: 실제 해당 클래스 문서 중 모델이 찾아낸 비율
- F1: precision과 recall의 조화 평균
- Confusion matrix: 어떤 클래스를 어떤 클래스로 잘못 분류하는지 보여 주는 표
다중 클래스에서는 macro F1과 weighted F1의 차이도 본다. macro 평균은 각 클래스를 같은 비중으로 보므로 소수 클래스의 실패가 드러난다. weighted 평균은 문서 수가 많은 클래스의 영향이 더 크다.
R²는 회귀 문제의 지표이므로 문서 카테고리 분류 평가 목록에 섞지 않는다.
점수를 올리기 전에 오류를 읽는다
모델을 바꾸기 전에 오분류 문서를 직접 읽으면 다음 실험이 구체적이 된다.
- 라벨 기준이 애매하면 데이터 정의부터 고친다.
- 도메인 복합어가 잘리면 n-gram이나 사용자 사전을 바꾼다.
- 제목과 본문이 다른 역할을 하면 특성을 분리한다.
- 중복 템플릿이 점수를 부풀리면 그룹 단위로 분할한다.
- 문맥과 어순이 핵심이라면 그때 임베딩이나 Transformer 계열 모델을 비교한다.
고전적인 TF-IDF 모델은 최신 모델보다 단순하지만 빠르고, 중요한 단어를 확인하기 쉬우며, 학습·서빙 비용이 작다. 복잡한 모델이 실제로 얼마나 더 나은지 판단할 기준선으로 가치가 있다. AI 학습 범위 전체는 ML 개념부터 NLP 모델까지의 공부 지도에서 이어서 볼 수 있다.
참고 자료
'배움과 성장 > AI·자동화' 카테고리의 다른 글
| MNIST 손글씨 분류의 학습 흐름: 순전파·교차 엔트로피·역전파 (0) | 2025.06.22 |
|---|---|
| AI 입문 용어 지도: 데이터·학습·모델·RAG·MLOps 연결하기 (6) | 2025.06.22 |
| 선형회귀 기초: 최소제곱·L1·L2 정규화·교차검증 (1) | 2025.06.07 |
| 인공지능을 위한 수학 4: 평균·분산부터 MLE·Bayes 추정까지 (0) | 2025.06.06 |
| AI를 위한 확률과 통계: 확률변수·분포·기댓값의 연결 (0) | 2025.05.19 |
댓글