[파이썬 머신러닝 가이드] 텍스트 분석 - 감성 분석

 

감성 분석

: 문서의 주관적인 감성/의견/감정/기분 등을 파악하기 위한 방법으로 소셜미디어, 여론조사, 온라인 리뷰, 피드백 등 다양한 분야에서 활용되고 있음.

: 문서 내 텍스트가 나타내는 여러가지 주관적인 단어와 문맥을 기반으로 감성 수치를 계산하는 방법 이용.

: 긍정 감성 지수와 부정 감성 지수로 구성되며 이들 지수를 합산해 긍정감성 또는 부정감성을 결정함.

 

  • 지도학습의 방식

: 학습 데이터와 타깃레이블 값을 기반으로 감성분석 학습을 진행한 뒤 이를 기반으로 다른 데이터의 감성 분석을 예측하는 방법으로 일반적인 텍스트 기반의 분류와 거의 동일.

 

  • 비지도 학습의 방식

‘Lexicon’이라는 일종의 감성 어휘사전 이용, 감성 분석을 위한 용어와 문맥에 대한 다양한 정보를 가지고 있으며, 이를 이용해 문서의 긍정적, 부정적 감성여부를 파악함.

 

  • 지도학습 기반 감성분석 실습 - IMDB 영화평
import pandas as pd

review_df = pd.read_csv('./labeledTrainData.tsv', header=0, sep = '\t', quoting =3) #탭(\t) 문자로 분리된 파일. 인자로 sep 명시.

import re
#<br> html 태그는 replace 함수로 공백으로 변환
review_df['review'] = review_df['review'].str.replace('<br />', ' ')

#파이썬의 정규 표현식 모듈인 re를 이용해 영어 문자열이 아닌 문자는 모두 공백으로 변환
review_df['review'] = review_df['review'].apply(lambda x : re.sub("[^a-zA-Z]", " ", x) )

# 이제 결정 값 클래스인 sentiment 칼럼 별도로 추출해서 결정값 데이터 세트 만들고, 원본 데이터 세트에서 id와 sentiment 컬럼을 삭제해 피처 데이터 세트 생성하면 됨.
# 이후는 앞에 텍스트 분석과 동일

 

**정규표현식 짚고 넘어가기

 

  • 비지도학습 기반 감성 분석 소개

많은 감성 분석용 데이터는 결정된 레이블 값을 가지고 있지 않음.

 

* 감성 사전

: 긍정 또는 부정 감성의 정도를 의미하는 수치를 가지고 있으며 이를 감성 지수(Polarity Score)라고 함.

이 감성 지수는 단어이 위치나 주변 단어, 문맥, POS(품사) 등을 참고해 결정됨 - NLTK 패키지

 

  • NLP 패키지의 WordNet

: 다양한 상황에서 같은 어휘라도 다르게 사용되는 어휘의 시맨틱 정보를 제공하며,

이를 위해 각각의 품사로 구성된 개별 단어를 Synset이라는 개념을 이용해 표현.

Synset은 단순한 하나의 단어가 아니라, 그 단어가 가지는 문맥, 시맨틱 정보를 제공하는 WordNet의 핵심 개념!

but, 예측 성능 안좋음.

 

- SentiWordNet

: synset 별로 긍정감성지수, 부정감성지수, 객관성 지수와 같은 3가지 감성점수를 할당함. 객관성 지수는 긍정/부정 감성 지수와 완전히 반대되는 개념으로 단어가 감성과 관계없이 얼마나 객관적인지 수치로 나타냄. 문장별로 단어들의 긍정 감성지수와 부정감성지수를 합산하여 최종 감성지수를 계산.

+ VADER, Pattern

 

  • SentiWordNet을 이용한 영화 감상평 감성 분석
  1. 문서를 문장 단위로 분해
  2. 다시 문장을 단어 단위로 토큰화하고 품사 태깅
  3. 품사 태깅된 단어 기반으로 synset 객체와 senti_synset 객체를 생성
  4. senti_synset에서 긍정감성/부정감성 지수를 구하고 이를 모두 합산해 특정 임계치 값 이상일 때 긍정 감성으로, 그렇지 않을 때는 부정 감성으로 결정
from nltk.corpus import wordnet as wn

# 간단한 NTLK PennTreebank Tag를 기반으로 WordNet기반의 품사 Tag로 변환
def penn_to_wn(tag):
    if tag.startswith('J'):
        return wn.ADJ
    elif tag.startswith('N'):
        return wn.NOUN
    elif tag.startswith('R'):
        return wn.ADV
    elif tag.startswith('V'):
        return wn.VERB
    return
from nltk.stem import WordNetLemmatizer
from nltk.corpus import sentiwordnet as swn
from nltk import sent_tokenize, word_tokenize, pos_tag

def swn_polarity(text):
    # 감성 지수 초기화 
    sentiment = 0.0
    tokens_count = 0
    
    lemmatizer = WordNetLemmatizer()
    raw_sentences = sent_tokenize(text)
    # 분해된 문장별로 단어 토큰 -> 품사 태깅 후에 SentiSynset 생성 -> 감성 지수 합산 
    for raw_sentence in raw_sentences:
        # NTLK 기반의 품사 태깅 문장 추출  
        tagged_sentence = pos_tag(word_tokenize(raw_sentence))
        for word , tag in tagged_sentence:
            
            # WordNet 기반 품사 태깅과 어근 추출
            wn_tag = penn_to_wn(tag)
            if wn_tag not in (wn.NOUN , wn.ADJ, wn.ADV):
                continue                   
            lemma = lemmatizer.lemmatize(word, pos=wn_tag)
            if not lemma:
                continue
            # 어근을 추출한 단어와 WordNet 기반 품사 태깅을 입력해 Synset 객체를 생성. 
            synsets = wn.synsets(lemma , pos=wn_tag)
            if not synsets:
                continue
            # sentiwordnet의 감성 단어 분석으로 감성 synset 추출
            # 모든 단어에 대해 긍정 감성 지수는 +로 부정 감성 지수는 -로 합산해 감성 지수 계산. 
            synset = synsets[0]
            swn_synset = swn.senti_synset(synset.name())
            sentiment += (swn_synset.pos_score() - swn_synset.neg_score())           
            tokens_count += 1
    
    if not tokens_count:
        return 0
    
    # 총 score가 0 이상일 경우 긍정(Positive) 1, 그렇지 않을 경우 부정(Negative) 0 반환
    if sentiment >= 0 :
        return 1
    
    return 0
review_df['preds'] = review_df['review'].apply( lambda x : swn_polarity(x) )
y_target = review_df['sentiment'].values
preds = review_df['preds'].values

from sklearn.metrics import accuracy_score, confusion_matrix, precision_score 
from sklearn.metrics import recall_score, f1_score, roc_auc_score
import numpy as np

print(confusion_matrix( y_target, preds))
print("정확도:", np.round(accuracy_score(y_target , preds), 4))
print("정밀도:", np.round(precision_score(y_target , preds),4))
print("재현율:", np.round(recall_score(y_target, preds), 4))

 

  • VADER를 이용한 감성분석
  1. SentimentIntensityAnalyzer 객체를 생성한 뒤에 문서별로 polarity_scores() 메서드를 호출해 감성점수를 구한다.
  2. 해당 문서의 감성 점수가 특정 임계값 이상이면 긍정, 그렇지 않으면 부정으로 판단함. (SentimentIntensityAnalyzer 객체의 polarity_scores() 메서드는 딕셔너리 형태의 감성점수 반환, compound가 neg,neu, pos score 를 적절히 조합해 -1에서 1 사이의 감성지수를 표현한 값임. Compound score 를 기반으로, 보통 0.1 이상이면 긍정 감성, 그 이하이면 부정 감성으로 판단)
def vader_polarity(review,threshold=0.1): #입력 파라미터로 영화 감상평 텍스트와, 긍정/부정을 결정하는 임계값을 가짐.
    analyzer = SentimentIntensityAnalyzer()
    scores = analyzer.polarity_scores(review) #감성결과 반환
    
    # compound 값에 기반하여 threshold 입력값보다 크면 1, 그렇지 않으면 0을 반환 
    agg_score = scores['compound']
    final_sentiment = 1 if agg_score >= threshold else 0
    return final_sentiment

# apply lambda 식을 이용하여 레코드별로 vader_polarity( )를 수행하고 결과를 'vader_preds'에 저장
review_df['vader_preds'] = review_df['review'].apply( lambda x : vader_polarity(x, 0.1) ) #함수 호출하여 각 문서별로 감성결과를 새로운 칼럼으로 저장.
y_target = review_df['sentiment'].values
vader_preds = review_df['vader_preds'].values

print(confusion_matrix( y_target, vader_preds))
print("정확도:", np.round(accuracy_score(y_target , vader_preds),4))
print("정밀도:", np.round(precision_score(y_target , vader_preds),4))
print("재현율:", np.round(recall_score(y_target, vader_preds),4))