Programming

CountVectorizer부터 Pipeline/GridSearchCV까지: 뉴스그룹 분류로 배우는 피처 벡터화 + 희소행렬(COO/CSR)

Lucas.Kim 2026. 2. 14. 13:25
반응형

텍스트 분석에서 가장 중요한 전환점은 “텍스트를 숫자 벡터로 바꾸는 것(피처 벡터화)” 입니다.
이번 글에서는 아래 흐름을 코드 실행 순서 그대로 따라가며 정리합니다.

  • 사전 데이터 가공 → 토큰화 → 텍스트 정규화 → 피처 벡터화(CountVectorizer/TF-IDF)
  • 텍스트 벡터의 본질: 희소행렬(Sparse Matrix)
  • 희소행렬 저장 형식: COO vs CSR
  • 20 Newsgroups 분류 실습: 벡터화 → 학습/예측/평가 → Pipeline → GridSearchCV 최적화

주의: 코드 블록은 사용자가 공유한 순서를 유지하며, 각 코드에 “무슨 코드인지/왜 쓰는지/목적” 주석을 상세히 추가했습니다.
또한 중간에 출력 로그(결과)도 학습 포인트가 되므로 글 흐름상 필요한 설명을 함께 덧붙였습니다.

1) 카운트 벡터라이저(CountVectorizer)를 이용한 피처 벡터화

텍스트 분석의 기본 전처리 파이프라인은 다음과 같습니다.

  • 사전 데이터 가공: 원본 텍스트에서 학습 가능한 형태로 준비합니다.
  • 토큰화(Tokenization): 문장/단어/형태소 단위로 쪼갭니다.
  • 텍스트 정규화(Normalization): 불용어 제거, 소문자화, 표제어/어근 처리 등으로 일관성을 맞춥니다.
  • 피처 벡터화(Vectorization): 텍스트를 머신러닝이 이해할 수 있는 숫자 벡터(행렬) 로 바꿉니다.

여기서 CountVectorizer는 BoW(Bag of Words) 기반으로,

  • “단어가 몇 번 등장했는지(빈도)”를 세어서 벡터로 만듭니다.

2) 희소행렬(Sparse Matrix)의 이해

2-1. 왜 희소행렬이 필요한가요?

BoW/CountVectorizer/TF-IDF로 벡터화를 하면 보통 이런 일이 생깁니다.

  • 문서 개수는 수천~수만 개
  • 단어 사전(Feature) 크기는 수만~수십만 개
  • 그런데 문서 하나에 실제로 등장하는 단어는 일부뿐이라서
    벡터 대부분이 0 입니다.

즉, 텍스트 벡터는 거의 항상 희소행렬이 됩니다.

✅ 문제점(밀집행렬 Dense로 들고 있으면)

  • 0이 너무 많아 메모리 낭비가 극심합니다.
  • 연산 시에도 0이 많은 배열을 계속 훑게 되어 시간도 오래 걸립니다.

그래서 텍스트 벡터는 보통 scipy.sparse의 희소행렬로 저장합니다.

2-2. COO 형식이란?

  • COO는 Coordinate(좌표) 방식입니다.
  • “0이 아닌 값”만 따로 저장하고,
  • 그 값의 (행, 열) 위치를 함께 저장합니다.

즉, “값 + 좌표”만 저장하는 구조라서 0을 저장하지 않습니다.

2-3. CSR 형식이란? (사용자 메모의 COR는 보통 CSR을 의미합니다)

  • CSR은 Compressed Sparse Row 입니다.
  • COO의 단점 중 하나는 좌표 정보가 반복될 수 있다는 점인데,
  • CSR은 “행(Row) 기준으로 압축”하여 더 효율적으로 저장/연산합니다.

✅ 실무/사이킷런에서 가장 흔히 보는 희소행렬 형식이 CSR입니다.

3) 희소행렬 실습: COO 형식

아래 코드는 “밀집행렬(Dense)”을 COO 방식으로 표현하고, 다시 Dense로 복원하는 예시입니다.

import numpy as np

# ✅ (목적) 0이 많은 행렬(텍스트 벡터와 유사한 형태)을 예시로 만들기 위한 Dense 행렬입니다.
dense = np.array([[3,0,1],
                  [0,2,0]])

from scipy import sparse

# ✅ (목적) COO는 0이 아닌 값만 저장합니다. 따라서 '0이 아닌 값'만 data 배열로 준비합니다.
# - (0,0)=3
# - (0,2)=1
# - (1,1)=2
data = np.array([3,1,2])

# ✅ (목적) 위 data 값들이 존재하는 "행 좌표"와 "열 좌표"를 각각 저장합니다.
# - 3은 (row=0, col=0)
# - 1은 (row=0, col=2)
# - 2는 (row=1, col=1)
row_pos = np.array([0,0,1])
col_pos = np.array([0,2,1])

# ✅ (핵심) COO 희소행렬 생성
# - (data, (row_pos, col_pos)) 형태로 "값 + 좌표"를 넘겨 COO 행렬을 만듭니다.
sparse_coo = sparse.coo_matrix((data, (row_pos,col_pos)))

print(type(sparse_coo))
print(sparse_coo)

# ✅ (목적) 희소행렬을 다시 Dense(일반 numpy 배열)로 복원하여 같은 값인지 확인합니다.
dense01=sparse_coo.toarray()
print(type(dense01),"\n", dense01)

<class 'scipy.sparse._coo.coo_matrix'>
<COOrdinate sparse matrix of dtype 'int64'
	with 3 stored elements and shape (2, 3)>
  Coords	Values
  (0, 0)	3
  (0, 2)	1
  (1, 1)	2
<class 'numpy.ndarray'> 
 [[3 0 1]
 [0 2 0]]

✅ 결과 해석

  • with 3 stored elements 는 0이 아닌 값이 3개만 저장되었다는 뜻입니다.
  • Dense로 복원해도 원래 행렬과 동일합니다.

4) 희소행렬 실습: CSR 형식

COO → CSR 변환/생성 과정을 이해하기 위한 예시입니다.

#희소행렬 - COR형식
from scipy import sparse

# ✅ (목적) 0이 많은 Dense 행렬을 준비합니다. 텍스트 벡터는 보통 이런 형태가 됩니다.
dense2 = np.array([[0,0,1,0,0,5],
             [1,4,0,3,2,5],
             [0,6,0,3,0,0],
             [2,0,0,0,0,0],
             [0,0,0,7,0,8],
             [1,0,0,0,0,0]])

# ✅ (목적) COO는 0이 아닌 값만 data 배열에 넣습니다.
data2 = np.array([1, 5, 1, 4, 3, 2, 5, 6, 3, 2, 7, 8, 1])

# ✅ (목적) 0이 아닌 값들의 좌표(행, 열)를 저장합니다.
row_pos = np.array([0, 0, 1, 1, 1, 1, 1, 2, 2, 3, 4, 4, 5])
col_pos = np.array([2, 5, 0, 1, 3, 4, 5, 1, 3, 0, 3, 5, 0])

# ✅ (1) COO 형식으로 변환
sparse_coo = sparse.coo_matrix((data2, (row_pos,col_pos)))

# ✅ (2) CSR 형식에서 중요한 개념: "행 포인터(row index pointer)"
# - 각 행이 data 배열의 어디서부터 시작하는지 가리키는 인덱스입니다.
row_pos_ind = np.array([0, 2, 7, 9, 10, 12, 13])

# ✅ (3) CSR 형식으로 변환
# csr_matrix((data, col_index, row_pointer)) 형태를 사용합니다.
sparse_csr = sparse.csr_matrix((data2, col_pos, row_pos_ind))

print('COO 변환된 데이터가 제대로 되었는지 출력 확인')
print(sparse_coo.toarray())
print('CSR 변환된 데이터가 제대로 되었는지 출력 확인')
print(sparse_csr.toarray())

print(sparse_csr)


dense3 = np.array([[0,0,1,0,0,5],
             [1,4,0,3,2,5],
             [0,6,0,3,0,0],
             [2,0,0,0,0,0],
             [0,0,0,7,0,8],
             [1,0,0,0,0,0]])

# ✅ (추가 확인) Dense에서 바로 COO/CSR로 바꾸는 간단한 방법도 있습니다.
coo = sparse.coo_matrix(dense3)
csr = sparse.csr_matrix(dense3)

COO 변환된 데이터가 제대로 되었는지 출력 확인
[[0 0 1 0 0 5]
 [1 4 0 3 2 5]
 [0 6 0 3 0 0]
 [2 0 0 0 0 0]
 [0 0 0 7 0 8]
 [1 0 0 0 0 0]]
CSR 변환된 데이터가 제대로 되었는지 출력 확인
[[0 0 1 0 0 5]
 [1 4 0 3 2 5]
 [0 6 0 3 0 0]
 [2 0 0 0 0 0]
 [0 0 0 7 0 8]
 [1 0 0 0 0 0]]
<Compressed Sparse Row sparse matrix of dtype 'int64'
	with 13 stored elements and shape (6, 6)>
  Coords	Values
  (0, 2)	1
  (0, 5)	5
  (1, 0)	1
  (1, 1)	4
  (1, 3)	3
  (1, 4)	2
  (1, 5)	5
  (2, 1)	6
...
  (3, 0)	2
  (4, 3)	7
  (4, 5)	8
  (5, 0)	1

✅ 결과 해석

  • COO와 CSR 모두 Dense로 복원했을 때 원본과 동일합니다.
  • CSR 출력에서 with 13 stored elements 는 0이 아닌 값 13개만 저장한다는 의미입니다.

5) 20 Newsgroups 분류 실습 (CountVectorizer → TF-IDF → 튜닝 → Pipeline)

5-1. 데이터 로딩과 구성 확인

# 데이터 로딩과 데이터 구성확인
from sklearn.datasets import fetch_20newsgroups

# ✅ (목적) 20개 뉴스 그룹(카테고리)의 텍스트 데이터를 불러옵니다.
news_data =fetch_20newsgroups(subset='all',
                              random_state=156)

print(news_data.keys())
LightGBM과 Ridge를 ensemble한 최종 rmsle 값: 0.4470553194860604
['/Users/donghwigim/nltk_data', '/Users/donghwigim/Desktop/MyDev/myMl/lectvenv/nltk_data', '/Users/donghwigim/Desktop/MyDev/myMl/lectvenv/share/nltk_data', '/Users/donghwigim/Desktop/MyDev/myMl/lectvenv/lib/nltk_data', '/usr/share/nltk_data', '/usr/local/share/nltk_data', '/usr/lib/nltk_data', '/usr/local/lib/nltk_data', '/Users/donghwigim/nltk_data']
/Users/donghwigim/nltk_data/tokenizers/punkt
/Users/donghwigim/nltk_data/tokenizers/punkt/english.pickle
[nltk_data] Downloading package punkt_tab to
[nltk_data]     /Users/donghwigim/nltk_data...
[nltk_data]   Package punkt_tab is already up-to-date!
True
...
dict_keys(['data', 'filenames', 'target_names', 'target', 'DESCR'])

 

✅ 포인트

  • data: 문서 텍스트 리스트
  • target: 각 문서의 정답 레이블(0~19)
  • target_names: 레이블 이름(카테고리명)

또한 중간 로그(예: nltk_data 경로, punkt 등)는 토큰화/정규화 과정에서 NLTK 리소스를 잘 찾는지 확인하는 흔한 디버깅 출력입니다.

5-2. 클래스 분포/카테고리 확인, 샘플 문서 보기

import pandas as pd

print(f'target 클래스 값, 분포도\n{pd.Series(news_data.target).value_counts().sort_index()}')
print(f'target 클래스 이름 : {news_data.target_names}')

#학습 테스트용 데이터 생성
print(news_data.data[0]) #첫번쨰 뉴스 데이터

✅ 포인트

  • 분류 문제에서는 먼저 클래스 불균형이 심한지 확인하는 습관이 중요합니다.
  • 실제 텍스트가 어떤 스타일(메일/뉴스/포럼 등)인지 보면 전처리 방향이 잡힙니다.

5-3. Train/Test 분리

아래 코드에서 train/test 로딩 블록이 2번 반복되지만, 사용자가 공유한 순서를 유지하기 위해 그대로 두고, “동일 동작”임을 주석으로만 표기합니다.

from sklearn.datasets import fetch_20newsgroups


#트레인 > subset = train
train_news = fetch_20newsgroups(subset='train',
                                remove=('headers','footers', 'quotes'),
                                random_state=156)

# ✅ (목적) X_train에는 원문 텍스트(list), y_train에는 정답 레이블(int)이 들어갑니다.
X_train = train_news.data
y_train = train_news.target
print(type(X_train))

#테스트 > subset = test
test_news = fetch_20newsgroups(subset='test',
                                remove=('headers','footers', 'quotes'),
                                random_state=156)

# ✅ (목적) 테스트도 동일하게 구성합니다.
X_test = test_news.data
y_test = test_news.target
print(f'학습 데이터 크기 : {len(train_news.data)}\n테스트 데이터 크기 : {len(test_news.data)}')
from sklearn.datasets import fetch_20newsgroups


# ✅ (참고) 위와 동일한 블록이 한 번 더 반복되어 있습니다. (동일한 데이터 분리)
train_news = fetch_20newsgroups(subset='train',
                                remove=('headers','footers', 'quotes'),
                                random_state=156)
X_train = train_news.data
y_train = train_news.target
print(type(X_train))

test_news = fetch_20newsgroups(subset='test',
                                remove=('headers','footers', 'quotes'),
                                random_state=156)
X_test = test_news.data
y_test = test_news.target
print(f'학습 데이터 크기 : {len(train_news.data)}\n테스트 데이터 크기 : {len(test_news.data)}')

5-4. CountVectorizer로 벡터화 + 로지스틱 회귀 분류

여기서 가장 중요한 규칙이 있습니다.

벡터라이저는 “훈련 데이터로 fit”하고, 테스트는 transform만 해야 합니다.

  • 테스트에서 fit() 또는 fit_transform() 하면
    단어 사전(feature space)이 바뀌어서 학습 모델과 입력 차원이 달라질 수 있습니다.
피처 벡터화 변환과 머신러닝 모델 학습/예측/평가
주의: 학습 데이터에 대해 fit( )된 CountVectorizer를 이용해서 테스트 데이터를 피처 벡터화 해야함.

테스트 데이터에서 다시 CountVectorizer의 fit_transform()을 수행하거나 fit()을 수행 하면 안됨.
이는 이렇게 테스트 데이터에서 fit()을 수행하게 되면 기존 학습된 모델에서 가지는 feature의 갯수가 달라지기 때문임.

from sklearn.feature_extraction.text import CountVectorizer

# ✅ (목적) CountVectorizer는 단어 빈도 기반 BoW 벡터를 생성합니다.
cnt_vect = CountVectorizer()

# ✅ (핵심) 훈련 데이터로 단어 사전(vocabulary)을 학습합니다.
cnt_vect.fit(X_train)

# ✅ (목적) 훈련 데이터를 "단어 빈도 행렬"로 변환합니다. (희소행렬 형태로 나옵니다)
X_train_cnt_vect = cnt_vect.transform(X_train)

# ✅ (핵심) 테스트 데이터는 fit이 아니라 transform만 수행합니다.
X_test_cnt_vect = cnt_vect.transform(X_test)
print(f'학습데이터의 text Count Vectorizer Shape : {X_train_cnt_vect.shape}')


# 학습데이터의 text Count Vectorizer Shape : (11314, 101631)
X_test_cnt_vect.shape
(7532, 101631)

✅ Shape 해석

  • (11314, 101631)
    • 문서 11314개
    • 단어 사전(피처) 101,631개
    • 즉, 엄청 큰 행렬이며 대부분 0 → 그래서 CSR 희소행렬이 기본입니다.

이제 모델 학습을 합니다.

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')

# ✅ (목적) 로지스틱 회귀는 텍스트 분류에서 가장 기본이면서도 강력한 베이스라인 모델입니다.
# ✅ (팁) solver='liblinear'는 비교적 가볍게 돌아가서 예제에서 자주 씁니다.
lr_clf = LogisticRegression(solver='liblinear')

# ✅ (학습) 벡터화된 학습 데이터로 모델 학습
lr_clf.fit(X_train_cnt_vect , y_train)

# ✅ (예측) 벡터화된 테스트 데이터로 분류 예측
pred = lr_clf.predict(X_test_cnt_vect)

# ✅ (평가) 정확도(accuracy)로 성능 확인
print('CountVectorized Logistic Regression 의 예측 정확도는 {0:.3f}'.format(accuracy_score(y_test,pred)))

# CountVectorized Logistic Regression 의 예측 정확도는 0.617
CountVectorized Logistic Regression 의 예측 정확도는 0.617

5-5. TF-IDF로 개선하기 (Count → TF-IDF)

CountVectorizer는 단어 빈도만 보지만, TF-IDF는 “흔한 단어의 영향력을 낮추고 중요한 단어를 부각”합니다.

  • TF: 문서 내 등장 빈도
  • IDF: 전체 문서에서 희귀할수록 가중치 증가
# TF-IDF 피처변환과 머신러닝 학습/예측/평가
from sklearn.feature_extraction.text import TfidfVectorizer

# ✅ (목적) TF-IDF는 문서 대표성이 높은 단어에 가중치를 더 줍니다.
tfidf_vect = TfidfVectorizer()

# ✅ (핵심) 훈련 데이터로 IDF를 학습(fit)하고, 훈련/테스트를 transform합니다.
tfidf_vect.fit(X_train)
X_train_tfidf_vect = tfidf_vect.transform(X_train)
X_test_tfidf_vect = tfidf_vect.transform(X_test)

# LogisticRegression을 이용하여 학습/예측/평가 수행. 
lr_clf = LogisticRegression(solver='liblinear')
lr_clf.fit(X_train_tfidf_vect , y_train)
pred = lr_clf.predict(X_test_tfidf_vect)
print('TF-IDF Logistic Regression 의 예측 정확도는 {0:.3f}'.format(accuracy_score(y_test ,pred)))

# TF-IDF Logistic Regression 의 예측 정확도는 0.678
TF-IDF Logistic Regression 의 예측 정확도는 0.678

✅ Count(0.617) → TF-IDF(0.678)로 상승했습니다.
텍스트 분류에서 TF-IDF가 기본 베이스라인으로 자주 쓰이는 이유입니다.


5-6. stop_words + ngram + max_df로 추가 튜닝

여기서는 “불용어 제거 + n-gram(1,2) + 너무 흔한 단어 제거(max_df)”를 적용합니다.

  • stop_words='english' : 흔한 불용어 제거
  • ngram_range=(1,2) : unigram + bigram 같이 사용
  • max_df=300 : “너무 자주 등장하는 단어”를 상한으로 잘라냄(설정 방식에 따라 의미가 달라질 수 있으므로, 실험/검증이 중요합니다)
#top words 필터링을 추가하고 ngram을 기본(1,1)에서 (1,2)로 변경하여 피처 벡터화
# stop words 필터링을 추가하고 ngram을 기본(1,1)에서 (1,2)로 변경하여 Feature Vectorization 적용.
tfidf_vect = TfidfVectorizer(stop_words='english', ngram_range=(1,2), max_df=300)
tfidf_vect.fit(X_train)
X_train_tfidf_vect = tfidf_vect.transform(X_train)
X_test_tfidf_vect = tfidf_vect.transform(X_test)

lr_clf = LogisticRegression(solver='liblinear')
lr_clf.fit(X_train_tfidf_vect , y_train)
pred = lr_clf.predict(X_test_tfidf_vect)
print('TF-IDF Vectorized Logistic Regression 의 예측 정확도는 {0:.3f}'.format(accuracy_score(y_test ,pred)))

# TF-IDF Vectorized Logistic Regression 의 예측 정확도는 0.690
TF-IDF Vectorized Logistic Regression 의 예측 정확도는 0.690

✅ 0.678 → 0.690로 또 상승했습니다.
특히 bigram은 “new york”, “space shuttle” 같은 의미 있는 단어 조합을 잡는 데 도움 됩니다.


5-7. GridSearchCV로 LogisticRegression의 C 튜닝

로지스틱 회귀의 C는 규제 강도와 관련된 하이퍼파라미터입니다.

  • C가 커질수록 규제가 약해지고(더 복잡해짐)
  • C가 작을수록 규제가 강해집니다(더 단순해짐)
#GridSearchCV로 LogisticRegression C 하이퍼 파라미터 튜닝

from sklearn.model_selection import GridSearchCV

# ✅ (목적) 여러 C 후보 중 교차검증으로 성능이 가장 좋은 값을 자동 탐색합니다.
params = { 'C':[0.01, 0.1, 1, 5, 10]}

# ✅ (주의) verbose=1은 진행 로그를 보기 위함입니다.
grid_cv_lr = GridSearchCV(lr_clf ,param_grid=params , cv=3 , scoring='accuracy' , verbose=1 )
grid_cv_lr.fit(X_train_tfidf_vect , y_train)
print('Logistic Regression best C parameter :',grid_cv_lr.best_params_ )

# ✅ (평가) 최적 파라미터로 학습된 모델로 테스트 예측
pred = grid_cv_lr.predict(X_test_tfidf_vect)
print('TF-IDF Vectorized Logistic Regression 의 예측 정확도는 {0:.3f}'.format(accuracy_score(y_test ,pred)))

# Fitting 3 folds for each of 5 candidates, totalling 15 fits
# Logistic Regression best C parameter : {'C': 10}
# TF-IDF Vectorized Logistic Regression 의 예측 정확도는 0.704

✅ 0.690 → 0.704로 추가 개선되었습니다.

5-8. Pipeline으로 “벡터화 + 학습”을 한 번에 묶기

Pipeline의 장점은 명확합니다.

  • 전처리/벡터화/학습이 한 덩어리로 관리됨
  • 실수(테스트에 fit하는 등)를 줄여줌
  • GridSearchCV와 결합하면 매우 강력함
# 사이킷런 파이프라인 사용 및 GridSearchCV 결합
from sklearn.pipeline import Pipeline

# ✅ (목적) tfidf_vect(벡터화) → lr_clf(분류기) 순서로 “한 번에 실행”되도록 묶습니다.
pipeline = Pipeline([
    ('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1,2), max_df=300)),
    ('lr_clf', LogisticRegression(solver='liblinear', C=10))
])

# ✅ (핵심) pipeline.fit(X_train, y_train)만 하면,
# - 내부에서 tfidf_vect.fit_transform(X_train)
# - 그리고 lr_clf.fit(벡터화 결과, y_train)
# 이 자동으로 실행됩니다.
pipeline.fit(X_train, y_train)

# ✅ (예측) pipeline.predict(X_test)만 하면,
# - 내부에서 tfidf_vect.transform(X_test)
# - 그리고 lr_clf.predict(벡터화 결과)
# 가 자동 실행됩니다.
pred = pipeline.predict(X_test)
print('Pipeline을 통한 Logistic Regression 의 예측 정확도는 {0:.3f}'.format(accuracy_score(y_test ,pred)))

# Pipeline을 통한 Logistic Regression 의 예측 정확도는 0.704

5-9. Pipeline + GridSearchCV (가장 실전적인 형태)

Pipeline의 각 단계 하이퍼파라미터를 조정하려면
단계이름__파라미터명 형태로 GridSearchCV에 넣습니다.

from sklearn.pipeline import Pipeline

pipeline = Pipeline([
    ('tfidf_vect', TfidfVectorizer(stop_words='english')),
    ('lr_clf', LogisticRegression(solver='liblinear'))
])

# ✅ (핵심) pipeline 단계별 파라미터 지정법: 단계이름__파라미터명
params = { 'tfidf_vect__ngram_range': [(1,1), (1,2), (1,3)],
           'tfidf_vect__max_df': [100, 300, 700],
           'lr_clf__C': [1,5,10]
}

# ✅ (목적) 전체 조합을 교차검증으로 평가하여 최적 조합을 찾습니다.
grid_cv_pipe = GridSearchCV(pipeline, param_grid=params, cv=3 , scoring='accuracy',verbose=1)
grid_cv_pipe.fit(X_train , y_train)
print(grid_cv_pipe.best_params_ , grid_cv_pipe.best_score_)

pred = grid_cv_pipe.predict(X_test)
print('Pipeline을 통한 Logistic Regression 의 예측 정확도는 {0:.3f}'.format(accuracy_score(y_test ,pred)))

# Fitting 3 folds for each of 27 candidates, totalling 81 fits
# {'lr_clf__C': 10, 'tfidf_vect__max_df': 700, 'tfidf_vect__ngram_range': (1, 2)} 0.7550828826229531
# Pipeline을 통한 Logistic Regression 의 예측 정확도는 0.702

✅ 결과 해석(초보자가 헷갈리는 포인트)

  • best_score_는 교차검증 평균 성능(훈련 기반) 입니다.
  • 마지막 테스트 정확도는 완전히 분리된 테스트셋에서의 성능이라 값이 다를 수 있습니다.
  • 교차검증에서 너무 좋아 보이는데 테스트가 덜 나오면
    → 데이터 특성, 파라미터 탐색 범위, 전처리 옵션(예: max_df), 모델의 일반화 등을 점검해야 합니다.

 

이번 글에서 가장 중요한 결론은 3가지입니다.

  • 텍스트 벡터는 기본적으로 희소행렬이며, Dense로 처리하면 메모리/연산이 비효율적입니다.
  • 희소행렬 포맷 중 실무/사이킷런에서 가장 흔한 것은 CSR(Compressed Sparse Row) 입니다.
  • 텍스트 분류 성능은
    • Count → TF-IDF로 바꾸고
    • stopwords/ngram/max_df 등 벡터화 파라미터를 튜닝하고
    • Pipeline + GridSearchCV로 “전처리~학습”을 한 번에 최적화할 때
      안정적으로 개선됩니다.
반응형