배운 내용
- sklearn.datasets.fetch_20newsgroups
- CountVectorizer
- TfidfVectorizer
- Pipeline
본문
사이킷런이 내부에 가지고 있는 예제 데이터인 20 뉴스그룹 데이터 세트를 텍스트 분류에 적용해 보겠습니다. 텍스트 분류는 학습 데이터를 학습해 모델을 생성한 후 이 학습 모델을 이용해 다른 문서의 분류를 예측하는 것입니다.
사이킷런의 fetch_20newsgroups() API를 이용해 뉴스그룹의 분류를 수행해 보겠습니다. 텍스트를 피처 벡터화로 변환하면 일반적으로 희소 행렬 형태가 됩니다. 그리고 이러한 희소 행렬에 분류를 효과적으로 처리할 수 있는 알고리즘은 로지스틱 회귀, 선형 서포트 벡터 머신, 나이브 베이즈 등입니다. 이 중 로지스틱 회귀를 이용해 분류를 수행해보겠습니다.
텍스트 분류를 수행할 때, 텍스트 정규화를 한 뒤 피처 벡터화를 합니다. 그리고 적합한 머신러닝 알고리즘으로 분류를 학습/예측/평가합니다. 이번 절에서는 카운트 기반과 TF-IDF 기반의 벡터화를 적용해 예측 성능을 비교하겠습니다.
그리고 사이킷런의 Pipeline 객체를 통해 피처 벡터 파라미터와 GridSearchCV 기반의 하이퍼 파라미터 튜닝을 한꺼번에 수행하는 방법을 소개하겠습니다.
텍스트 정규화
fetch_20newsgroups()는 인터넷에서 먼저 데이터를 내려받은 후 메모리로 데이터를 로딩합니다.
from sklearn.datasets import fetch_20newsgroups
news_data = fetch_20newsgroups(subset='all', random_state=156)
사이킷런의 데이터 세트 예제는 파이선 딕셔너리와 유사한 Bunch 객체를 반환합니다. key 값을 확인해 보겠습니다.
print(news_data.keys())
'''
dict_keys(['data', 'filenames', 'target_names', 'target', 'DESCR'])
'''
filenames는 fetch_20newsgroups() API가 인터넷에서 내려 받아 로컬 컴퓨터에 저장하는 디렉터리와 파일명을 지칭합니다. 다음으로 Target 클래스가 어떻게 구성돼 있는지 확인해보겠습니다.
import pandas as pd
print('target 클래스의 값과 분포도\n', pd.Series(news_data.target).value_counts().sort_index())
print('target 클래스의 이름들\n', news_data.target_names)
'''
target 클래스의 값과 분포도
0 799
1 973
2 985
...
18 775
19 628
dtype: int64
target 클래스의 이름들
['alt.atheism', 'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware',
'comp.sys.mac.hardware', 'comp.windows.x', 'misc.forsale', 'rec.autos', 'rec.motorcycles',
'rec.sport.baseball', 'rec.sport.hockey', 'sci.crypt', 'sci.electronics', 'sci.med', 'sci.space',
'soc.religion.christian', 'talk.politics.guns', 'talk.politics.mideast', 'talk.politics.misc',
'talk.religion.misc']
'''
Target 클래스 값은 0 ~ 19까지 20개로 구성돼 있으며, 0 : alt.atheism, 1 : comp.graphics .. 로 구성돼 있습니다. 개별 데이터가 텍스트로 어떻게 구성돼 있는지 데이터를 한 개만 추출해 값을 확인해보겠습니다.
print(news_data.data[0])
'''
From: egreen@east.sun.com (Ed Green - Pixel Cruncher)
Subject: Re: Observation re: helmets
Organization: Sun Microsystems, RTP, NC
Lines: 21
Distribution: world
Reply-To: egreen@east.sun.com
NNTP-Posting-Host: laser.east.sun.com
In article 211353@mavenry.altcit.eskimo.com, maven@mavenry.altcit.eskimo.com (Norman Hamer) writes:
>
> The question for the day is re: passenger helmets, if you don't know for
>certain who's gonna ride with you (like say you meet them at a .... church
>meeting, yeah, that's the ticket)... What are some guidelines? Should I just
>pick up another shoei in my size to have a backup helmet (XL), or should I
>maybe get an inexpensive one of a smaller size to accomodate my likely
>passenger?
If your primary concern is protecting the passenger in the event of a
crash, have him or her fitted for a helmet that is their size. If your
primary concern is complying with stupid helmet laws, carry a real big
spare (you can put a big or small head in a big helmet, but not in a
small one).
---
Ed Green, former Ninjaite |I was drinking last night with a biker,
Ed.Green@East.Sun.COM |and I showed him a picture of you. I said,
DoD #0111 (919)460-8302 |"Go on, get to know her, you'll like her!"
(The Grateful Dead) --> |It seemed like the least I could do...
'''
출력을 보면 제목, 작성자, 소속, 이메일 등의 정보가 있습니다. 이 중 내용을 제외한 제목 등의 정보는 제거하겠습니다. 이러한 정보들은 Target 클래스 값과 유사한 데이터를 가지고 있어 왠만한 ML 알고리즘을 적용해도 상당히 높은 예측 성능을 나타내기 때문입니다. remove 파라미터를 이용하면 뉴스그룹 기사의 헤더(Header), 푸터(Footer) 등을 제거할 수 있습니다. 또한 subset 파라미터를 이용해 학습 데이터, 테스트 데이터 세트를 분리해 내려받을 수 있습니다.
from sklearn.datasets import fetch_20newsgroups
# subset='train'으로 학습용 데이터만 추출, remove=('headers', 'footers', 'quotes')로 내용만 추출
train_news = fetch_20newsgroups(subset='train', remove=('headers', 'footers', 'quotes'),\
random_state=156)
X_train = train_news.data
y_train = train_news.target
# subset='test'으로 테스트 데이터만 추출, remove=('headers', 'footers', 'quotes')로 내용만 추출
test_news = fetch_20newsgroups(subset='test', remove=('headers', 'footers', 'quotes'),\
random_state=156)
X_test = test_news.data
y_test = test_news.target
print('학습 데이터 크기 {0}, 테스트 데이터 크기 {1}'.format(len(train_news.data),\
len(test_news.data)))
'''
학습 데이터 크기 11314, 테스트 데이터 크기 7532
'''
피처 벡터화 변환과 머신러닝 모델 학습/예측/평가
학습 데이터는 11314개의 뉴스그룹 문서가 리스트 형태로 주어지고, 테스트 데이터는 7532개의 문서가 리스트 형태로 주어졌습니다.
Count 기반 피처 벡터화 CountVectorizer
CountVectorizer를 이용해 학습 데이터의 텍스트를 피처 벡터화하겠습니다. 테스트 데이터 역시 피처 벡터화를 수행하는데, 반드시 학습 데이터로 학습한 CountVectorizer 객체를 이용해 변환(transform)해야 한다는 것입니다. 그래야만 학습시 설정된 CountVectorizer의 피처 개수와 테스트 데이터를 CountVectorizer로 변환할 피처 개수가 같아집니다.
from sklearn.feature_extraction.text import CountVectorizer
# Count Vectorization으로 피처 벡터화 변환 수행.
cnt_vect = CountVectorizer()
cnt_vect.fit(X_train)
X_train_cnt_vect = cnt_vect.transform(X_train)
# 학습 데이터로 fit()된 CountVectorizer를 이용해 테스트 데이터를 피처 벡터화 변환 수행.
X_test_cnt_vect = cnt_vect.transform(X_test)
print('학습 데이터 텍스트의 CountVectorizer Shape:', X_train_cnt_vect.shape)
'''
학습 데이터 텍스트의 CountVectorizer Shape: (11314, 101631)
'''
11314 개의 문서를 피처 벡터화한 결과 101631개의 피처(단어)가 만들어졌습니다. 피처 벡터화된 데이터에 로지스틱 회귀를 적용해 뉴스그룹에 대한 분류를 예측해 보겠습니다.
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# LogisticRegression을 이용해 학습/예측/평가 수행.
lr_clf = LogisticRegression()
lr_clf.fit(X_train_cnt_vect, y_train)
pred = lr_clf.predict(X_test_cnt_vect)
print('CountVectorized Logistic Regression의 예측 정확도는 {0:.3f}'.format(\
accuracy_score(y_test, pred)))
'''
CountVectorized Logistic Regression의 예측 정확도는 0.608
'''
TF-IDF 기반 피처 벡터화 TfidVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
# 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('TF-IDF Logistic Regression의 예측 정확도는 {0:.3f}'.format(accuracy_score(\
y_test, pred)))
'''
TF-IDF Logistic Regression의 예측 정확도는 0.674
'''
정확도가 단순 Count 기반보다 높습니다. 일반적으로 문서 내에 텍스트가 많고, 많은 문서를 가지는 텍스트 분석에서 TF-IDF 벡터화가 Count 벡터화보다 좋은 결과를 도출합니다.
텍스트 분석에서 머신러닝 모델의 성능을 향상시키는 방법은 2가지가 있습니다.
- 최적의 ML 알고리즘 선택
- 최상의 피처 전처리를 수행
이번엔 TfidfVectorizer 클래스의 스톱 워드를 기존 'None'에서 'english'로 변경하고, ngram_range는 (1, 1)에서 (1, 2)로, max_df = 300으로 변경한 뒤 예측 성능을 측정하겠습니다.
# stop words 필터링을 추가하고 ngram을 기본 (1, 1)에서 (1, 2)로 변경해 피처 벡터화 적용.
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()
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.692
'''
이번에는 GridSearchCV를 이용해 로지스틱 회귀의 하이퍼 파라미터 최적화를 수행해보겠습니다. C 파라미터만 최적화한후 그 값을 적용해 테스트 데이터로 예측 성능을 평가하겠습니다.
from sklearn.model_selection import GridSearchCV
# 최적 C값 도출 튜닝 수행. CV는 3폴드 세트로 설정.
params = { 'C':[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('Logistic Regression best C parameter :', grid_cv_lr.best_parms_)
# 최적 C값으로 학습된 grid_cv로 예측 및 정확도 평가.
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 4 candidates, totalling 12 fits
Logistic Regression best C parameter : {'C': 10}
TF-IDF Vectorized Logistic Regression의 예측 정확도는 0.701
'''
사이킷런 파이프라인(Pipeline) 사용 및 GridSearchCV와의 결합
사이킷런의 Pipeline 클래스를 이용하면 피처 벡터화와 ML 알고리즘 학습/예측을 위한 코드 작성을 한 번에 진행할 수 있습니다. 머신러닝에서 Pipeline은 수도관에서 물이 흐르듯 한꺼번에 데이터 가공, 변환 등의 전처리와 알고리즘 적용을 스트림 기반으로 처리한다는 의미입니다.
Pipeline을 이용하면 데이터 전처리, 머신러닝 학습 과정을 통일된 API 기반에서 처리할 수 있어 더 직관적인 ML 모델 코드를 생성할 수 있습니다. 또한, 대용량 데이터의 피처 벡터화 결과를 별도 데이터로 저장하지 않고, 스트림 기반에서 바로 머신러닝 알고리즘 데이터로 입력할 수 있기 때문에 수행 시간을 절약할 수 있습니다.
일반적으로 사이킷런의 Pipeline은 텍스트 피처 벡터화뿐만 아니라 모든 데이터 전처리 작업과 Estimator를 결합할 수 있습니다. 예를 들어 스케일링 또는 벡터 정규화, PCA 등의 변환 작업과 분류, 회귀 등의 Estimator를 한 번에 결합하는 것입니다.
Pipeline
다음은 위에서 텍스트 분류 예제 코드를 Pipeline을 이용해 다시 작성한 코드입니다. Pipeline 객체는 아래와 같이 선언합니다.
pipeline = Pipeline([('tfidf_vect', TfidfVectorizer(stop_words='english')),\
('lr_clf', LogisticRegression(random_state=156))])
TfidfVectorizer 객체를 'tfidf_vect'라는 객체 변수 명으로, LogisticRegression 객체를 lr_clf라는 객체 변수 명으로 생성하고, 이 두 객체를 파이프라인으로 연결하는 Pipeline 객체를 pipeline으로 생성합니다.
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
# TfidfVectorizer 객체를 tfidf_vect로, LogisticRegression 객체를 lr_clf로 생성하는 Pipeline 생성
pipeline = Pipeline([
('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1, 2), max_df=300)),\
('lr_clf', LogisticRegression(C=10))
])
# 별도의 TfidfVectorizer 객체의 fit(), transform()과 LogisticRegression의 fit(), predict()가
# 필요없음.
# pipeline의 fit()과 predict()만으로 한꺼번에 피처 벡터화와 ML 학습/예측이 가능.
pipeline.fit(X_train, y_train)
pred = pipeline.predict(X_test)
print('Pipeline을 통한 Logistic Regression의 예측 정확도는 {0:.3f}'.format(\
accuracy_score(y_test, pred)))
'''
Pipeline을 통한 Logistic Regression의 예측 정확도는 0.701
'''
사이킷런은 GridSearchCV 클래스의 생성 파라미터로 Pipeline을 입력할 수 있습니다. 이렇게 하면, 피처 벡터화를 위한 파라미터, ML 알고리즘의 하이퍼 파라미터를 모두 한번에 GridSearchCV로 최적화할 수 있습니다.
GridSearchCV에 Estimator가 아닌 Pipeline을 입력할 경우 param_grid에 딕셔너리 형태의 Key와 Value 값을 가지며, 하이퍼 파라미터 명이 객체 변수명과 결합돼 제공됩니다.
Pipeline + GridSearchCV를 적용할 때는 튜닝에 너무 많이 시간이 소모되는 단점이 있습니다. 아래 예제를 통해 피처 벡터화, ML 알고리즘의 파라미터를 튜닝해서 예측 정확도를 출력해보겠습니다.
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
('tfidf_vect', TfidfVectorizer(stop_words='english')),
('lr_clf', LogisticRegression())
])
# Pipeline에 기술된 각각의 객체 변수에 언더바(__) 2개를 연달아 붙여 GridSearchCV에 사용될
# 파라미터/하이퍼 파라미터 이름과 값을 설정.
params = {
'tfidf_vect__ngram_range':[(1, 1), (1, 2), (1, 3)],
'tfidf_vect__max_df':[100, 300, 700],
'lr_clf__C':[1, 5, 10]
}
# GridSearchCV의 생성자에 Estimator가 아닌 Pipeline 객체 입력
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_pipeline.predict(X_test)
print('Pipeline을 통한 Logistic Regression의 예측 정확도는 {0:.3f}'.format(\
accuracy_score(y_test, pred)))
'''
{'lf_clf__C': 10, 'tfidf_vect__max_df': 700, 'tfidf_vect__ngram__range: (1, 2)} 0.75524129397207
Pipeline을 통한 Logistic Regression의 예측 정확도는 0.701
'''
정확도가 크게 개선되지는 않았습니다. 하지만, 한 번의 fit()과 predict()만으로 성능 테스트를 할 수 있습니다.
궁금한 점
- LR 알고리즘 말고도 서포트 벡터 머신, 나이브 베이즈 알고리즘도 희소 행렬 기반의 텍스트 분류에 사용해보기.
출처: 파이썬 머신러닝 완벽 가이드(권철민)
사진 출처:
'파이썬 머신 러닝 완벽 가이드' 카테고리의 다른 글
추천 시스템 (0) | 2021.12.11 |
---|---|
텍스트 분석 실습 - 캐글 Mercari Price Suggestion Challenge (0) | 2021.12.10 |
텍스트 분석 (0) | 2021.12.03 |
군집화 실습 - 고객 세그먼테이션 (0) | 2021.12.02 |
군집화 (0) | 2021.11.28 |