텍스트 분석이란?
이전 내용
[텍스트 분석에서 머신러닝 모델의 성능을 향상시키는 중요한 방법]
- 최적의 ML 알고리즘 선택
- 최상의 피처 전처리 수행
▶ 텍스트 정규화나 Count/TF-IDF 기반 피처 벡터화를 어떻게 효과적으로 적용했는지가 텍스트 기반의 머신러닝 성능에 큰 영향을 미칠 수 있다.
텍스트 분류 실습 - 20 뉴스그룹 분류
◆ 텍스트 분류:
특정 문서의 분류를 학습 데이터를 통해 학습해 모델을 생성한 뒤 이 학습 모델을 이용해 다른 문서의 분류를 예측하는 것.
사이킷런은 fetch_20newsgroups() API를 이용해 뉴스그룹의 분류를 수행해 볼 수 있는 예제 데이터를 제공한다.
[사이킷런 예제 데이터 '20 뉴스그룹 데이터 세트'를 이용해 텍스트 분류 실습 수행]
- 텍스트 정규화
- 텍스트를 피처 벡터화로 변환 (희소 행렬 형태) ▶ 카운트 기반, TF-IDF 기반 벡터화
- 피처 벡터화를 위한 파라미터와 하이퍼 파라미터 튜닝 ▶ GridSearchCV 기반의 하이퍼 파라미터 튜닝
- 희소 행렬에 분류를 효과적으로 잘 처리할 수 있는 알고리즘을 이용한 분류 수행
- 적합한 머신러닝 알고리즘을 적용해 분류를 학습/예측/평가
※ 희소 행렬에 분류를 효과적으로 잘 처리할 수 있는 알고리즘에는 로지스틱 회귀, 선형 서포트 벡터 머신(SVM), 나이브 베이즈 등이 있다.
이번 실습에서는 로지스틱 회귀를 이용하여 분류를 수행한다.
또한, 피처 벡터화를 위한 파라미터와 하이퍼 파라미터 튜닝에는 GridSearchCV 기반의 하이퍼 파라미터 튜닝과 사이킷런의 Pipeline 객체를 통해 피처 벡터화 파라미터와 GridSearchCV 기반의 하이퍼 파라미터 튜닝을 한꺼번에 수행.
텍스트 정규화
fetch_20newsgroups() 는 인터넷에서 로컬 컴퓨터로 데이터를 먼저 내려받은 후에 메모리로 데이터를 수행하며, 사이킷런의 다른 데이터 세트 예제와 같이 파이썬 딕셔너리와 유사한 Bunch 객체를 반환한다.
from sklearn.datasets import fetch_20newsgroups
new_data = fetch_20newsgroups(subset='all', random_state=156)
print(new_data.keys())
▶ fetch_20newsgropus() API 역시 load_xxx() API와 유사한 Key 값을 가지고 있다.
filenames 라는 key 이름은 해당 API가 인터넷에서 내려받아 로컬 컴퓨터에 저장하는 디렉터리와 파일명을 지칭한다.
[Target 클래스 구성 확인]
import pandas as pd
print('target 클래스의 값과 분포도 \n', pd.Series(new_data.target).value_counts().sort_index())
print('target 클래스의 이름들 \n', new_data.target_names)
▶ Target 클래스의 값은 0부터 19까지 20개로 구성돼 있다.
개별 데이터가 텍스트로 어떻게 구성돼 있는지 데이터를 한 개만 추출해 값을 확인해 본다.
print(new_data.data[0])
▶ 텍스트 데이터를 확인해 보면 뉴스그룹 기사의 내용뿐만 아니라 뉴스그룹 제목, 작성자, 소속, 이메일 등의 다양한 정보를 가지고 있다.
[내용을 제외한 다른 정보 제거]
텍스트 데이터 중 내용을 제외하고 제목 등의 다른 정보는 제거한다.
왜냐하면 제목, 소속, 이메일 주소 등의 헤더와 푸터 정보들은 뉴스그룹 분류의 Target 클래스값과 유사한 데이터를 가지고 있는 경우가 많기 때문이며, 이 피처들을 포함하게 되면 웬만한 ML 알고리즘을 적용해도 상당히 높은 예측 성능을 나타내기 때문이다.
따라서 이들 정보를 포함하지 않고 순수한 텍스트만으로 구성된 기사 내용으로 어떤 뉴스그룹에 속하는지 분류할 것이다.
remove 파라미터를 이용하면 뉴스그룹 기사의 헤더, 푸터 등을 제거할 수 있으며, fetch_20newsgroups() 는 subset 파라미터를 이용해 학습 데이터 세트와 테스트 데이터 세트를 분리해 내려받을 수 있다.
from sklearn.datasets import fetch_20newsgroups
# subset='train' 으로 학습용 데이터만 추출, remove 파라미터로 내용만 추출
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 파라미터로 내용만 추출
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개의 문서가 리스트 형태로 주어졌다.
[CountVectorizer를 이용해 학습 데이터의 텍스트 벡터 피처화]
테스트 데이터로 CountVectorizer 적용 시에는 반드시 학습 데이터를 이용해 fit() 이 수행된 CountVectorizer 객체를 이용해 테스트 데이터를 변환(transform) 해야 한다. 그래야만 학습 시 설정된 CountVectorizer 의 피처 개수와 테스트 데이터를 CountVectorizer로 변환할 피처 개수가 같아진다. 테스트 데이터의 피처 벡터화는 학습 데이터에 사용된 CountVectorizer 객체 변수인 cnt_vect.transform()을 이용해 변환한다. (fit_transform() 사용하지 않도록 유의)
from sklearn.feature_extraction.text import CountVectorizer
# Count Vectorizer으로 피처 벡터화 변환 수행
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로 피처를 추출한 결과 11314개의 문서에서 피처 (단어)가 101631개로 만들어졌다.
[피처 벡터화된 데이터에 로지스틱 회귀 적용해 분류 예측]
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
import warnings
warnings.filterwarnings('ignore')
# LogisticRegression 이용하여 학습/예측/평가 수행
lr_clf = LogisticRegression(solver='liblinear')
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)))
[TF-IDF 기반으로 벡터화를 변경해 예측 모델 수행]
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(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가 단순 카운트 기반보다 훨씬 높은 예측 정확도를 제공한다.
[TF-IDF 벡터화에 다양한 파라미터 적용]
- TfidfVectorizer 클래스의 스톱 워드를 기존 'None'에서 'english'로 변경
- n_gram_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(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)))
[GridSearchCV를 이용해 로지스틱 회귀의 하이퍼 파라미터 최적화 수행]
로지스틱 회귀와 C 파라미터만 변경하면서 최적의 C값을 찾은 뒤 이 C값으로 학습된 모델에서 테스트 데이터로 예측해 성능 평가
from sklearn.model_selection import GridSearchCV
# 최적 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('Logistic Regression best C Parameter:', grid_cv_lr.best_params_)
# 최적 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)))
▶ 로지스틱 회귀의 C가 10일 때 GridSearchCV 의 교차 검증 테스트 세트에서 가장 좋은 예측 성능을 나타냈으며, 이를 테스트 데이터 세트에 적용해 약 0.704로 이전보다 약간 향상된 성능 수치가 되었다.
사이킷런 파이프라인(Pipeline) 사용 및 GridSearchCV와의 결합
사이킷런의 Pipeline 클래스를 이용하면 피처 벡터화와 ML 알고리즘 학습/예측을 위한 코드 작성을 한 번에 진행할 수 있다.
일반적으로 사이킷런 파이프라인은 텍스트 기반의 피처 벡터화뿐만 아니라 모든 데이터 전처리 작업과 Estimator를 결합할 수 있다.
(스케일링/벡터 정규화, PCA 등의 변환 작업, Estimator - 분류, 회귀 등 을 한 번에 결합)
◆ 머신러닝에서 Pipeline
데이터의 가공, 변환 등의 전처리와 알고리즘 적용을 마치 '수도관(Pipe)에서 물이 흐르듯' 한꺼번에 스트림 기반으로 처리한다는 의미.
Pipeline을 이용하면
- 데이터의 전처리와 머신러닝 학습 과정을 통일된 API 기반에서 처리할 수 있어 더 직관적인 ML 모델 코드를 생성할 수 있다.
- 대용량 데이터의 피처 벡터화 결과를 별도 데이터로 저장하지 않고 스트림 기반에서 바로 머신러닝 알고리즘의 데이터로 입력할 수 있기 때문에 수행시간을 절약할 수 있다.
[Pipeline 객체 선언]
pipeline = Pipeline([('tfidf_vect', TfidfVectorizer(stop_words='english')),
('lr_clf', LogisticRegression(random_state=156))])
▶ TfidfVectorizer 객체를 tfidf_vect 라는 객체 변수명으로,
LogisticRegression 객체를 lr_clf 라는 객체 변수명으로 생성한 뒤 이 두 개의 객체를 파이프라인으로 연결하는 Pipeline 객체 pipeline 을 생성한다는 의미.
[Pipeline 방식을 적용한 머신러닝 코드]
from sklearn.pipeline import Pipeline
pipeline = Pipeline([
('tfidf_vect', TfidfVectorizer(stop_words='english', ngram_range=(1,2), max_df=300)),
('lr_clf', LogisticRegression(solver='liblinear', C=10))
])
# 별도의 TfidfVectorizer 객체의 fit(), transform()과 LogisticRegression의 fit(), predict() 불필요
# pipeline의 ift()과 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)))
▶ 기존 TfidfVetorizer의 학습 데이터와 테스트 데이터에 대한 fit()과 transform() 수행을 통한 피처 벡터화와 LogisticRegression의 fit()과 predict() 수행을 통한 머신러닝 모델의 학습과 예측이 Pipeline의 fit()과 predict()로 통일돼 수행됨을 알 수 있다.
[Pipeline 기반 GridSearchCV 하이퍼 파라미터 최적화 진행]
사이킷런은 GridSearchCV 클래스의 생성 파라미터로 Pipeline을 입력해 Pipeline 기반에서도 하이퍼 파라미터 튜닝을 GridSearchCV 방식으로 진행할 수 있게 지원하여 피처 벡터화를 위한 파라미터와 ML 알고리즘의 하이퍼 파라미터를 모두 한 번에 GridSearchCV 를 이용해 최적화할 수 있다.
◆ Pipeline + GridSearchCV 를 적용할 때 유의할 점
모두의 파라미터를 최적화하려면 너무 많은 튜닝 시간이 소모된다.
피처 벡터화에 사용되는 파라미터와 GridSearchCV 하이퍼 파라미터를 합치면 최적화를 위한 너무 많은 경우의 수가 발생하기 쉽다.
※ 하단의 코드는 Pipeline + GridSearchCV 기반으로 하이퍼 파라미터 튜닝을 적용해 27개의 파라미터 경우의 수 * 3개의 CV로 총 81번의 학습과 검증을 수행하기에 오랜 시간이 걸린다.
필자 역시 하단의 코드를 입력하여 수행했으나, 2시간 이상이 걸려 수행을 중지하였다.
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의 생성자에 Esitmator가 아닌 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 = gird_cv_pipe.predict(X_test)
print('Pipeline을 통한 Logistic Regression의 예측 정확도는 {0:.3f}'.format(accuracy_score(y_test, pred)))
- GridSearchCV에 Estimator가 아닌 Pipeline을 입력할 경우에는 param_grid 입력값 설정이 기존과 조금 다른데, 딕셔너리 형태의 Key와 Value 값을 가지며, Value를 리스트 형태로 입력하는 것은 동일하다.
단, Key 값을 살펴보면 'tfidf_vect_ngram_range'와 같이 하이퍼 파라미터명이 객체 변수명과 결합돼 제공된다.
▶ Pipeline을 GridSearchCV에 인자로 입력하면 GridSearchCV는 Pipeline을 구성하는 피처 벡터화 객체의 파라미터와 Estimator 객체의 하이퍼 파라미터를 각각 구별할 수 있어야 하는데, 이때 개별 객체 명과 파라미터명/하이퍼 파라미터명을 결합해 Key 값으로 할당하는 것이다.
만약 TfidfVectorizer 객체 변수인 tfidf_vect의 ngram_range 파라미터 값을 변화시키면서 최적화하기를 원한다면 객체 변수명인 tfidf_vect에 언더바(_)2개를 연달아 붙인 뒤 파라미터명인 ngram_range를 결합해 'tfidf_vect_ngram_range'를 Key값으로 할당하는 것이다.
[참고]
저자의 내용에 따르면, 상단의 코드를 수행했을 때
TfidfVectorizer 객체의 max_df 파라미터가 700, ngram_range 파라미터가 (1,2)로 피처 벡터화된 데이터 세트에 LogisticRegression 의 C 하이퍼 파라미터에 10을 적용해 예측 분류를 수행할 때 가장 좋은 검증 세트 성능 수치가 도출되었다고 한다.
또한, 최적화한 파라미터를 기반으로 테스트 데이터 세트에 대해 예측했을 때의 정확도는 약 0.702로 크게 개선은 되지 않았다고 한다.
다음 내용
[출처]
파이썬 머신러닝 완벽 가이드
'[파이썬 Projects] > <파이썬 머신러닝>' 카테고리의 다른 글
[머신러닝] 텍스트 분석: 토픽 모델링 (4) | 2024.10.31 |
---|---|
[머신러닝] 텍스트 분석: 감성 분석 (3) | 2024.10.30 |
[머신러닝] 텍스트 분석: BOW(Bag of Words) (1) | 2024.10.30 |
[머신러닝] 텍스트 분석: 텍스트 정규화 (0) | 2024.10.29 |
[머신러닝] 텍스트 분석 (0) | 2024.10.28 |