본문 바로가기

NPL with ML

텍스트 분류 실전 예제 - 뉴스그룹 분류

반응형

[추천시스템 구축을 위한 텍스트 분석 이해 #2 - BOW]에서 설명했듯 텍스트를 피처 벡터화로 변환하면 일반적으로 희소 행렬 형태가 됩니다. 그리고 이러한 희소 행렬에 분류를 효과적으로 잘 처리할 수 있는 알고리즘은 로지스틱 회귀, 선형 서포트 벡터 머신, 나이브 베이즈 등입니다. 

 

텍스트를 기반으로 분류를 수행할 때는 먼저 텍스트를 정규화한 뒤 피처 벡터화를 적용합니다. 이 후 적합한 머신러닝 알고리즘을 적용해 분류를 학습/예측/평가합니다.

 

실습을 위해 사이킷런의 fetch_20newsgroups() API를 이용해 뉴스그룹의 분류를 수행해 볼 수 있는 예제 데이터를 Download합니다.

 

from sklearn.datasets import fetch_20newsgroups
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
from sklearn.model_selection import GridSearchCV
from sklearn.pipeline import Pipeline
import pandas as pd

news_data = fetch_20newsgroups(subset='all', random_state=156)
# print(news_data.keys())
# print('target 클래스의 값과 분포도\n', pd.Series(news_data.target).value_counts().sort_index())
# print('target Class''s feature name\n', news_data.target_names)
# print(news_data.DESCR)
# print(news_data.data[0])


train_news = fetch_20newsgroups(subset='train', remove=('headers','footers','quotes'),random_state=156)
X_train = train_news.data
y_train = train_news.target

test_news = fetch_20newsgroups(subset='test', remove=('headers','footers','quotes'),random_state=156)
X_test = test_news.data
y_test = test_news.target

print('test''s size {0}, train''s size {1}'.format(len(test_news.data), len(train_news.data)))

 

다음은 CountVectorizer을 이용해 학습 데이터의 텍스트를 피처 벡터하하겠습니다. 테스트 데이터 역시 피처 벡터화를 수행하는데, 한 가지 반드시 유의해야 할 점은 테스트 데이터에서 CountVectorizer를 적용할 때는 반드시 학습 데이터를 이용해 fit()이 수행된 CountVectorizer객체를 이용해 테스트 데이터를 변환(tranform)해야 한다는 것입니다. 그래야만 학습 시 설정된 CountVectorizer의 피처 개수와 테스트 데이터를 CountVectorizer로 변환할 피처 개수가 같아집니다. 테스트 데이터의 피처 벡터화는 학습 데이터에 사용된 CountVectorizer객체 변수인 cnt_vect.traform()을 이용해 반환합니다.

 

테스트 데이터의 피처 벡터화 시 fit_transform()을 사용하면 안 된다는 점도 중요.  fit_tranfor()의 경우 fit을 하고 tranform을 하기 때문에 데이타 개수가 달라지게 됩니다.

#Count Vectorization으로 train 데이타를 피처 벡터화 변환 수행
cnt_vect = CountVectorizer()
cnt_vect.fit(X_train)
X_train_cnt_vect = cnt_vect.transform(X_train)
print('CountVectorizer Train Size', X_train_cnt_vect.shape)

#Train Data로 fit()된 ConVectorizer를 이용해 테스트 데이터를 Feature Vector화 변환 수행
X_test_cnt_vect = cnt_vect.transform(X_test)
print('CountVectorizer Test Size', X_test_cnt_vect.shape)

 

결과를 확인하면 11314개 문서에서, 101631개 단어로 분류가 되었다는 뜻입니다.

CountVectorizer Train Size (11314, 101631)

 

이렇게 피처 벡터화된 데이터에 로지스틱 회귀를 적용해 뉴스그룹에 대한 분류를 예측해 보겠습니다.

#LogisticRegression을 이용해 학습/예측/평가 수행
lr_clf = LogisticRegression(solver='lbfgs', max_iter=100)
lr_clf.fit(X_train_cnt_vect, y_train)
pred = lr_clf.predict(X_test_cnt_vect)
print('CountVectorized LogisticRegression Accuracy', accuracy_score(y_test, pred))

 

결과를 보면 60%의 예측 정확도를 보입니다.

CountVectorized LogisticRegression Accuracy 0.6078066914498141

 

이번에는 TF-IDF기반으로 벡터화를 변경해 예측 모델을 수행하겠습니다.

#TF-IDF 벡터화를 적용해 학습 데이터 세트와 테스트 데이터 세트 변환
tfidf_vect = TfidfVectorizer()
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()
lr_clf.fit(X_train_tfidf_vect, y_train)
pred = lr_clf.predict(X_test_tfidf_vect)
print('CountVectorized LogisticRegression Accuracy', accuracy_score(y_test, pred))

 

결과는 67% 정확도를 보입니다. Counter기반보다 높은 예측 정확도를 제공합니다.

CountVectorized LogisticRegression Accuracy 0.6736590546999469

 

일반적으로 문서 내에 텍스트가 많고 많은 문서를 가지는 텍스트 분석에서 카운트 벡터보다는 TF-IDF벡터화가 좋은 예측 결과를 도출합니다. 

 

텍스트 분석에서 머신러닝 모델의 성능을 향상시키는 중요한 2가지 방법은 최적의 ML알고리즘을 선택하는 것과 최상의 피처 전처리를 수행하는 것입니다. 

 

아래는 TfidfVectorizer에 기본 파라미터가 아닌 값을 변경했을 때 내용입니다.

#TF-IDF 벡터화를 적용해 학습 데이터 세트와 테스트 데이터 세트 변환
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)

#LogisticRegression을 이용해 학습/예측/평가 수행
lr_clf = LogisticRegression()
lr_clf.fit(X_train_tfidf_vect, y_train)
pred = lr_clf.predict(X_test_tfidf_vect)
print('CountVectorized LogisticRegression Accuracy', accuracy_score(y_test, pred))

 

아래와 같이 69%의 정확도를 보입니다. 적절한 파라미터를 찾는것이 중요합니다.

CountVectorized LogisticRegression Accuracy 0.6922464152947424

 

GridSearchCV를 이용해 로지스틱 회귀의 파라미터 최적화를 수행해 보겠습니다. 로지스틱 회귀의 C파라미터만 변경하면서 최적의 C값을 찾은 뒤 이 C값으로 학습된 모델에서 테스트 데이터로 예측해 성능을 평가하겠습니다.

#최적 C값 도출 튜닝 수행, CV 3 촐드 세트로 진행
params = {'C':[0.01,0.1,1,5,10]}
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('LogisticRegression Best C Parameter :', grid_cv_lr.best_score_)

#최적 C값으로 학습된 grid_cv로 예측 및 정확도 평가
pred=grid_cv_lr.predict(X_test_tfidf_vect)
print('TF-IDF Vectorized LogisticRegression :', accuracy_score(y_test, pred))

 

결과를 확인하면 C가 10일때 ..

 

사이킷런의 Pipeline클래스를 이용하면 피처 벡터화와 ML알고리즘 학습/예측을 위한 코드 작성을 한 번에 진해알 수 있습니다. 일반적으로 머신러닝에서 Pipeline이란 데이터의 가공, 변환 등의 전처리와 알고리즘 적용을 마치 수도관(Pipe)에서 물이 흐르듯 한꺼번에 스트림 기반으로 처리한다는 의미입니다.

 

이렇게 하면 데이터의 전처리와 머신러닝 학습 과정을 통일된 API기반에서 처리할 수 있어 더 직관적인 ML 모델 코드를 생성할 수 있습니다. 또한 대용량 데이터의 피처 벡터화 결과를 별도 데이터로 저장하지 않고 스트림 기반에서 바로 머신러닝 알고리즘의 데이터로 입력할 수 있기 대문에 수행 시간을 절약할 수 있습니다. 

pipline = Pipeline([
    ('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1,2), max_df=300)),
    ('lr_clf', LogisticRegression(c=10))
])
pipline.fit(X_train, y_train)
pred = pipline.predict(X_test)
print(accuracy_score(y_test,pred))

 

반응형