[Python] 머신러닝 완벽가이드 - 08. 텍스트 분석[텍스트 분류]

Updated:

파이썬 머신러닝 완벽가이드 교재를 토대로 공부한 내용입니다.

실습과정에서 필요에 따라 내용의 누락 및 추가, 수정사항이 있습니다.


기본 세팅

import numpy as np
import pandas as pd

import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

import warnings
%matplotlib inline
%config InlineBackend.figure_format = 'retina'

mpl.rc('font', family='NanumGothic') # 폰트 설정
mpl.rc('axes', unicode_minus=False) # 유니코드에서 음수 부호 설정

# 차트 스타일 설정
sns.set(font="NanumGothic", rc={"axes.unicode_minus":False}, style='darkgrid')
plt.rc("figure", figsize=(10,8))

warnings.filterwarnings("ignore")

2. 텍스트 분류 실습

사이킷런 내부 데이터 20 뉴스그룹 데이터 셋을 이용해 어떤 뉴스그룹인지 텍스트 분류를 적용해보자.

2.1 텍스트 정규화

from sklearn.datasets import fetch_20newsgroups

news_data = fetch_20newsgroups(subset='all',random_state=156)
  • fetch_20newsgroups()를 이용해 데이터를 로컬 컴퓨터로 내려받고 불러온다.
news_data.keys()
dict_keys(['data', 'filenames', 'target_names', 'target', 'DESCR'])
print("target 클래스의 값과 분포도")
print(pd.Series(news_data.target).value_counts().sort_index())
target 클래스의 값과 분포도
0     799
1     973
2     985
3     982
4     963
5     988
6     975
7     990
8     996
9     994
10    999
11    991
12    984
13    990
14    987
15    997
16    910
17    940
18    775
19    628
dtype: int64
print("target 클래스의 이름")
print(news_data.target_names)
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개로 구성되어 있다.
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과 유사한 데이터를 가진 경우가 많기에 내용을 제외한 정보는 제거한다.

  • 순수한 텍스트만으로 어떤 뉴스그룹에 속하는지 확인한다.

# train set, 내용 외 정보 제거
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 set, 내용 외 정보 제거
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)} , 테스트 데이터 크기 {len(test_news.data)}')
<class 'list'>
학습 데이터 크기 11314 , 테스트 데이터 크기 7532
  • 데이터를 불러올 때 remove로 헤더, 푸터 등의 정보를 제거하여 불러왔다.

  • subset을 이용해서 train, test도 분리하였다.

  • 11,314개의 뉴스그룹 문서가 리스트 형태로 train, 7,532개가 test로 주어졌다.

2.2 피처 벡터화

from sklearn.feature_extraction.text import CountVectorizer

# Count Vectorization: train
cnt_vect = CountVectorizer()
cnt_vect.fit(X_train)
X_train_cnt_vect = cnt_vect.transform(X_train)

# Count Vectorization: test
X_test_cnt_vect = cnt_vect.transform(X_test)

print(f"학습 데이터 Text의 CountVectorizer Shape: {X_train_cnt_vect.shape}")
학습 데이터 Text의 CountVectorizer Shape: (11314, 101631)
  • 피처 벡터화로 피처를 추출한 결과 101,631개의 피처(단어)가 만들어졌다.

  • 피처 벡터화시 반드시 train으로 학습한 벡터화 객체로 test를 벡터화 하여야한다.

2.3 ML 학습/예측/평가

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)
lr_acc = accuracy_score(y_test, pred)

print(f"CountVectorized Logistic Regression 예측 정확도: {lr_acc:.3f}")
CountVectorized Logistic Regression 예측 정확도: 0.604
  • Count 벡터화 후 로지스틱 회귀분석의 예측 정확도는 약 0.604로 나타난다.
from sklearn.feature_extraction.text import TfidfVectorizer

# TF-IDF Vectorization: train
tfidf_vect = TfidfVectorizer()
tfidf_vect.fit(X_train)
X_train_tfidf_vect = tfidf_vect.transform(X_train)

# TF-IDF Vectorization: test
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)
lr_acc = accuracy_score(y_test, pred)

print(f"TF-IDF Logistic Regression 예측 정확도: {lr_acc:.3f}")
TF-IDF Logistic Regression 예측 정확도: 0.674
  • 이번엔 TF-IDF 벡터화 후 로지스틱 회귀분석을 진행하였다.

  • 예측 정확도가 0.674로 Count 벡터화에 비해 높게 나타났다.

  • 일반적으로 문서 내에 텍스트가 많고, 문서가 많은 경우 TF-IDF 벡터화가 더 좋은 결과를 도출한다.

2.4 파라미터 조정

2.4.1 피처 벡터화 파라미터

# 피처 벡터화의 파라미터 조정
# TF-IDF Vectorization: train
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)

# TF-IDF Vectorization: test
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)
lr_acc = accuracy_score(y_test, pred)

print(f"TF-IDF 파라미터 조정 후 예측 정확도: {lr_acc:.3f}")
TF-IDF 파라미터 조정 후 예측 정확도: 0.692
  • 피처 벡터화에서 stop_words로 영어를 지정하여 영어 스톱 워드는 제외한다.

  • ngram_range로 단어를 2개씩 묶어서 피처로 추출한다.

  • max_df로 전체 문서를 통틀어 300번 이상 존재하는 단어는 제외한다.

  • 앞서 파라미터를 디폴트로 설정하였을 때에 비해 예측 정확도가 증가하였다.

2.4.2 로지스틱 하이퍼 파라미터

from sklearn.model_selection import GridSearchCV

# 최적 하이퍼 파라미터: C
params = {
    "C":[0.01, 0.1, 1, 5, 10]
}

# GridSearchCV
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_ )

# 최적 C 값으로 학습된 grid_cv로 예측/평가
pred = grid_cv_lr.predict(X_test_tfidf_vect)
lr_acc = accuracy_score(y_test, pred)

print(f"로지스틱 최적 하이퍼 파라미터 적용 후 예측 정확도: {lr_acc:.3f}")
Fitting 3 folds for each of 5 candidates, totalling 15 fits


[Parallel(n_jobs=1)]: Using backend SequentialBackend with 1 concurrent workers.
[Parallel(n_jobs=1)]: Done  15 out of  15 | elapsed: 69.0min finished


Logistic Regression best C parameter : {'C': 10}
로지스틱 최적 하이퍼 파라미터 적용 후 예측 정확도: 0.701
  • GridSearchCV를 이용하여 로지스틱 최적 하이퍼 파라미터를 찾았다.

  • 예측 정확도는 0.701으로 조금 상승하였다.

  • 수행 시간이 매우 오래 걸린다.

2.4.3 pipeline 사용

##### from sklearn.pipeline import Pipeline

# TF-IDF Vectorization 객체, LogisticRegression 객체 파이프라인 생성
pipeline = Pipeline([
    ('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1,2), max_df=300)),
    ('lr_clf', LogisticRegression(C=10))
])

# TF-IDF Vectorization, LogisticRegression 한번에 수행
pipeline.fit(X_train, y_train)
pred = pipeline.predict(X_test)
lr_acc = accuracy_score(y_test, pred)

print(f"Pipeline을 통한 Logistic Regression 예측 정확도: {lr_acc:.3f}")
Pipeline을 통한 Logistic Regression 예측 정확도: 0.701
  • pipeline()은 전처리와 ML 학습을 한번에 수행 가능하다.

  • 피처 벡터화 결과를 별도 데이터로 저장하지 않고 바로 ML 데이터로 입력되어 수행시간 절약이 가능하다.

from sklearn.pipeline import Pipeline

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

# 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]
}

# GridSearchCV: 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_pipe.predict(X_test)
lr_acc = accuracy_score(y_test, pred)

print(f'Pipeline을 통한 Logistic Regression 의 예측 정확도: {lr_acc:.3f}')
  • pipeline()으로 GridSearchCV의 하이퍼 파라미터 튜닝이 가능하다.

  • 파라미터는 각 객체명 + __ + 파라미터로 입력한다.

  • GridSearchCV 외에도 5.4.6 최적 정규화의 다항회귀의 차수 결정 등에서 이러한 방법을 적용했었다.

  • 텍스트 분류에는 로지스틱 외에도 서포트 벡터머신이나 나이브 베이즈 알고리즘을 자주 사용한다.

  • 작업 시간이 너무 오래 걸려서 포기했다.. 다음엔 그냥 코랩으로 돌려야겠다.

Leave a comment