이번 포스팅에서는 sklearn의 내장된 데이터와 Kaggle 데이터를 활용해 뉴스 기사 데이터들의 카테고리를 분석하는 텍스트 분류, 그리고 Review의 감성(긍정, 부정)을 분류해보는 실습내용에 대해 소개하려고 한다. 텍스트 데이터 분석, 모델링 과정 프로세스에 대해서는 여기를 참고하자. 목차는 다음과 같다.
1. 뉴스 카테고리 분류하기
2. 리뷰 감성 분류하기
1. 뉴스 카테고리 분류하기
네이버, 다음, 그리고 여러 종류의 언론사 각각의 홈페이지를 방문해 보면 다양한 카테고리의 뉴스가 존재하는 것을 알 수 있다. 그리고 각 뉴스 기사의 카테고리를 지정하는 기준은 해당 뉴스의 내용이 기반이 된다. 그렇다면 뉴스 내용의 텍스트를 기반으로 해당 뉴스의 카테고리를 예측할 수 있지 않을까? 이러한 예시 이외에 대표적인 텍스트 분류 예시로는 자동으로 스팸메일을 분류해주는 경우가 되겠다.
이제 뉴스 기사 데이터를 불러와 실습해보자. 해당 실습은 Supervised Learning이며 주어진 데이터셋에 각 뉴스 기사에 매핑되는 카테고리 label이 존재하고 이를 기반으로 모델을 학습시키고 테스트해보자. 단, 내장된 데이터이기에 텍스트 클렌징, 정규화 과정은 되어있는 상태이다.
# 데이터 로드
from sklearn.datasets import fetch_20newsgroups
# subset인자에 all로 train, test데이터 모두 가져오기
news_data = fetch_20newsgroups(subset='all', random_state=42)
# 데이터 분할
from sklearn.datasets import fetch_20newsgroups
#remove인자에 있는 부분은 해당 기사의 일부분을 제거한 것이다. 왜냐하면 오로지 기사의 body(본문)에 해당하는 텍스트들로만 학습시키려 했기 때문
train_data = fetch_20newsgroups(subset='train',
remove=('headers','footers','quotes'),
random_state=12)
X_train = train_data.data
y_train = train_data.target
test_data = fetch_20newsgroups(subset='test',
remove=('headers','footers','quotes'),
random_state=12)
X_test = test_data.data
y_test = test_data.target
print(f"학습 데이터 크기: {len(X_train)}\n 테스트 데이터 크기: {len(X_test)}")
이제 데이터셋을 분할 했으니 텍스트를 Feature Vectorizer화 시키고 모델에 학습시켜보자. 먼저 단어 발생 빈도수에만 기반한 Count Vectorizer를 사용해보자.
# 텍스트 데이터 feature Vectorizer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
cnt_vect = CountVectorizer()
# CountVecorizer도 한번 fit 하면 검증, 테스트 데이터에도 똑같은 것으로 해야함!
# 다시 한 번 fit하게 되면 벡터화되는 feature 개수가 달라짐!
X_train_vec = cnt_vect.fit_transform(X_train)
X_test_vec = cnt_vect.transform(X_test)
# 로지스틱 리그레션으로 모델링하기
lr_clf = LogisticRegression()
lr_clf.fit(X_train_vec, y_train)
y_pred = lr_clf.predict(X_test_vec)
acc = accuracy_score(y_test, y_pred)
print(f"분류 정확도 : {acc : .4f}")
결과값은 다음과 같다.
다음은 Tf-idf 방법을 사용해 텍스트를 Feature Vectorizer 시켜보자.
# Tf-idf로 feature vectorizer 시킨 후 로지스틱리그레션 해보기
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
tf_idf = TfidfVectorizer()
X_train_tf = tf_idf.fit_transform(X_train)
X_test_tf = tf_idf.transform(X_test)
lr_clf = LogisticRegression()
lr_clf.fit(X_train_tf, y_train)
y_pred = lr_clf.predict(X_test_tf)
acc = accuracy_score(y_test, y_pred)
print(f"Tf-idf 벡터화 후 정확도 :{acc : .4f}")
결과값은 다음과 같다. Tf-idf를 사용했을 때 더 높은 성능을 보였다. 하지만 이러한 성능 향상은 어느 데이터셋에 일반화시킬 순 없으며 오히려 Count Vectorizer일 때 성능이 더 높은 경우도 있음을 알아두자. 뭐든지 No free lunch Theorem 이다!
다음은 Count Vectorizer와 Tf-idf Vectorizer에서 공통적으로 추가해줄 수 있는 하이퍼파라미터에 대해 알아보려고 한다. 각 파라미터에 대한 설명은 다음과 같다.
- max_df : 너무 높은 빈도수를 갖는 feature(단어)를 제거한다. 정수와 부동소수점 입력이 가능하다. 만약 100을 입력하면 100개 이하로 발생하는 단어만 추출하고 0.95를 입력한다면 빈도수 상위 0.05의 단어들을 제외하고 추출한다.
- min_df : 너무 낮은 빈도수를 갖는 feature를 제거한다. 정수와 부동소수점 입력이 가능하다. 만약 2를 입력하면 전체문서에 걸쳐 2번 이하의 빈도수인 단어를 제거하고 0.02를 입력하면 하위 0.02이하의 빈도수 단어를 제거한다.
- max_features : feature 벡터화 시킬 단어 개수를 제한하는 최댓값이다.
- stop_words : 'english'로 지정하면 영어의 불용어를 추출하지 않는다.
- ngram_range : 튜플형태로 범위를 지정한다. 예를 들어 (1,2)이면 1gram, 2gram을 각각 수행해서 추출한다. 만약 2gram만 수행하고 싶다면 (2,2)로 지정하면 된다.
- analyzer : feature 벡터화 시킬 단위를 지정한다. 디폴트값은 'word'(단어)이다.
- token_pattern : 토큰화를 수행하는 패턴을 지정한다. 디폴트는 word(단어)인데 디폴트값을 변경할 경우는 많이 없다고 한다. 추가적으로 만약 토큰화시키면서 어근을 추출하고 싶다면 외부함수를 해당 인자로 넣어주어 어근 추출도 동시에 수행이 가능하다.
- lower_case : 모든 문자를 소문자로 변경한다. 디폴트 값은 True다.
2. 리뷰 감성 분류하기
리뷰란, 어떤 컨텐츠에 남기는 코멘트이다. 우리가 유튜브 영상에 다는 댓글, 뉴스기사에 다는 댓글 등 이런것들이 모두 리뷰이다. 이러한 리뷰에는 작성자의 감성이 들어있다. 즉, 긍정적인 감정, 부정적인 감정 아니면 객관적인(중립적인) 감정이 리뷰에 들어있다. 그렇다면 이러한 리뷰의 감성을 분류해보는 실습을 해보자. 감성을 분류하는 방법에는 크게 2가지 방법이 있다.
2-1. 지도학습에 기반한 감성 분석
지도학습, 그야말로 각 텍스트(여기선 리뷰)에 특정 감성이 매핑되어 있는 데이터셋으로 학습을 시켜 감성을 분류하는 것이다. 따라서 위에서 실습해본 뉴스 카테고리 분류 문제처럼 주어진 데이터셋을 갖고 활용하면 된다.
# Pipeline을 통해서 텍스트를 벡터화시키고 모델 학습시키기!
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.metrics import accuracy_score, roc_auc_score
import re
import pandas as pd
import numpy as np
# tsv 파일도 csv호출함수로 불러올 수 있음!
# headers=0이면 헤더값(칼럼이름) 출력, 1이면 헤더값 없이 바로 출력
# quoting=3 이면 ""(큰따음포) 인용구는 무시하고 출력함!
review_df = pd.read_csv('labeledTrainData.tsv', encoding='utf-8',
header=0, sep='\t', quoting=3)
# 정규표현식으로 HTML태그와 숫자 삭제하기
# Series의 replace함수로 HTML태그 없애주기
review_df['review'] = review_df['review'].str.replace('<br />',' ')
# re.sub('패턴','뭘로바꿀지',바꿀문자열)
review_df['review'] = review_df['review'].apply(lambda x : re.sub('[^a-zA-Z]',' ',x))
# 학습, 테스트 데이터 분리
target = review_df['sentiment']
feature = review_df['review']
X_train, X_test, y_train, y_test = train_test_split(feature,
target,
test_size=0.3,
random_state=42)
# Vectorizer인자로는 불용어 제거, ngram범위 설정해주기
pipeline = Pipeline([('cnt_vect',CountVectorizer(stop_words='english',
ngram_range=(1,2))),
('lr_clf', LogisticRegression(C=10))])
# 위에서 설정해준 Pipleline으로 데이터 학습시킬 때는 벡터화시키기 전의 원본 데이터
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
# '1' label로 예측될 확률 추출 for roc_auc_score
y_pred_proba = pipeline.predict_proba(X_test)[:,1]
acc = accuracy_score(y_test, y_pred)
auc = roc_auc_score(y_test, y_pred_proba)
print(f"정확도:{acc : .3f}\nAUC score:{auc : .3f}")
결과값은 다음과 같다. Tf-idf 사용한 예제코드는 생략하겠다.
2-2. 비지도 학습기반 감성분석
다음은 감성어휘 사전을 이용해 비지도 학습에 기반한 방법이다. 여기서 감성어휘 사전이란, 어떤 단어가 긍정, 부정, 중립의 의미를 얼마나 지니고 있는지 백과사전 형태(?)처럼 만들어 놓은 것을 말한다. 이를 기반으로 주어진 텍스트의 감성을 분석한다.
감성 어휘 사전 기반의 감성분석에는 SentiWordNet, VADER, Pattern 등 방법이 존재한다. 이번 포스팅에서는 VADER에 대한 예시만 다룰 예정이다. 참고로 Pattern은 파이썬 3.x 버전부터는 호환이 되지 않는다고 한다.
VADER는 소셜 미디어의 감성 분석 용도로 만들어졌다. NLTK에서 제공해주는 SentimentIntensityAnalyzer 객체를 통해 쉽게 감성 분석을 제공한다. 해당 클래스는 polarity_score() 메서드를 호출해 긍정, 부정, 중립 감성 지수를 구하고 이 3가지 지수를 적절히 조합해 계산한 총 감성 지수까지 계산하게 된다. 또한 총 감성 지수는 -1에서 1사이의 값으로 표현된다.(총 감성 지수는 compound라고 되어 있는 key값의 value이다.)
위에서 계산된 compound(총 감성 지수) 값이 사전에 설정한 임계값을 기준으로 임계값보다 높으면 긍정 감성, 낮으면 부정감성으로 판단한다. 보통 임계값은 0.1로 설정하며 경우에 따라 임계값을 조정해 예측 성능을 조절하기도 한다.
이제 VADER를 활용한 감성 분석을 구현해보자.
from nltk.sentiment.vader import SentimentIntensityAnalyzer
senti_analyzer = SentimentIntensityAnalyzer()
# polarity_score로 하나의 텍스트에 대해 각 부정/객관/긍정 그리고 총 합한 감성지수 출력(-1~1)
senti_score = senti_analyzer.polarity_scores(review_df['review'][0])
print(senti_score)
해당 코드를 출력하면 아래와 같은 dictionary 형태의 데이터가 보이는데, 여기서 compound의 value값이 총 감성 지수이다.
이제 임계치를 설정 후 compound값이 임계치보다 높으면 긍정(1), 낮으면 부정(0)으로 예측해보자.
from sklearn.metrics import accuracy_score, confusion_matrix, precision_score
from sklearn.metrics import recall_score, f1_score
# 임계치설정(보통 0.1)을 통해 compound(총 감성지수)가 임계치값보다 높으면 긍정, 낮으면 부정으로 분석
def get_sentiment(review, threshold):
analyzer = SentimentIntensityAnalyzer()
scores = analyzer.polarity_scores(review)
compound_score = scores['compound']
final_sentiment = 1 if compound_score >= threshold else 0
return final_sentiment
# 각 텍스트 데이터에 위에서 설정한 감성 label 얻는 함수 적용하기
# 임계값은 0.1로 설정
review_df['vader_pred'] = review_df['review'].apply(lambda x : get_sentiment(x, 0.1))
# 원본 데이터에서 주어진 정답 label과 VADER로 예측한 label 비교
y_target = review_df['sentiment']
y_pred = review_df['vader_pred']
print(confusion_matrix(y_target, y_pred))
print("정확도 :", accuracy_score(y_target, y_pred))
print("정밀도 :", precision_score(y_target, y_pred))
print("재현율 :", recall_score(y_target, y_pred))
print("F1 score :", f1_score(y_target, y_pred))
코드 생김새로 따졌을 때, 지도학습에 기반한 감성분석과의 차이점은 특정 머신러닝 모델을 선정해주지 않는다는 점이다.
'Data Science > 추천시스템과 NLP' 카테고리의 다른 글
[NLP] Collaborative Filtering(Recommendation) (0) | 2020.08.26 |
---|---|
[NLP] Contents-based Recommendation(컨텐츠 기반 추천) (3) | 2020.08.26 |
[NLP] 문서 군집화(Clustering)와 문서간 유사도(Similarity) 측정하기 (0) | 2020.08.20 |
[NLP] LDA를 활용한 Topic Modeling 구현하기 (0) | 2020.08.18 |
[NLP] 텍스트를 이용한 머신러닝 프로세스에 대한 이해 (0) | 2020.08.17 |