1. 비지도학습 기반 감성분석 소개
- 감성 어휘 사전 'Lexicon' 기반
- 감성 사전은 긍정, 부정 감성의 정도를 나타내는 감성 지수를 가지고 있음
- NLP의 WordNet 은 시맨틱 (문맥상 의미) 분석을 제공하는 어휘 사전
- WordNet 은 각각의 품사로 구성된 개별 단어를 Synset 이라는 단어의 문맥, 시맨틱 정보를 제공하는 개념을 이용해 표현
- WordNet 예측 성능이 좋지 못하다는 단점으로 인해 실무에서는 VADER, Pattern 등 다른 감성사전을 사용
2. SentiWordNet을 이용한 감성분석
2.1 WordNet Synset 클래스
- wordnet() 클래스를 이용해 'present' 단어에 대한 Synset 객체 확인
import nltk
nltk.download('wordnet')
nltk.download('omw-1.4')
from nltk.corpus import wordnet as wn
term ='present'
#'present' 라는 단어로 wordnet의 synsets 생성
synsets = wn.synsets('present')
print('synsets()반환 type :', type(synsets))
print('synsets()반환 값 개수 :', len(synsets))
print('synsets()반환 값 :', synsets)
[output]
synsets()반환 type : <class 'list'>
synsets()반환 값 개수 : 18
synsets()반환 값 : [Synset('present.n.01'), Synset('present.n.02'),
Synset('present.n.03'), Synset('show.v.01'), Synset('present.v.02'),
Synset('stage.v.01')...
- synsets 객체가 가지는 여러가지 속성 확인 (품사, 정의, 부명제)
for synset in synsets:
print('### Synset name :', synset.name(),'####' )
print('POS : ', synset.lexname())
print('Definition :', synset.definition())
print('Lemmas:', synset.lemma_names())
[output]
### Synset name : present.n.01 ####
POS : noun.time
Definition : the period of time that is happening now; any continuous stretch of time including the moment of speech
Lemmas: ['present', 'nowadays']
### Synset name : present.n.02 ####
POS : noun.possession
Definition : something presented as a gift
Lemmas: ['present']
### Synset name : present.n.03 ####
POS : noun.communication
Definition : a verb tense that expresses actions or states at the time of speaking
Lemmas: ['present', 'present_tense']
### Synset name : show.v.01 ####
POS : verb.perception
Definition : give an exhibition of to an interested audience
Lemmas: ['show', 'demo', 'exhibit', 'present', 'demonstrate']
...
- 어휘 간의 유사도를 나타내는 path_similarity() 메서드
import pandas as pd
#synset 객체를 단어별로 생성
tree = wn.synset('tree.n.01')
lion = wn.synset('lion.n.01')
tiger = wn.synset('tiger.n.02')
cat = wn.synset('cat.n.01')
dog = wn.synset('dog.n.01')
entities = [tree, lion, tiger, cat, dog]
similarities = []
entity_names = [entity.name().split('.')[0] for entity in entities]
#단어별 synset을 반복하면서 다른 단어의 synset과 유사도를 측정
for entity in entities :
similarity = [round(entity.path_similarity(compared_entity),2) for compared_entity in entities]
similarities.append(similarity)
#개별 단어별 synset 과 다른 단어의 synset과의 유사도를 dataframe 형태로 지정
similarity_df = pd.DataFrame(similarities, columns = entity_names, index = entity_names)
similarity_df
2.2 SentiWordNet 의 Senti_Synset() 클래스
- WordNet 모듈이므로 synset()과 비슷하게 senti_synset() 클래스를 리스트 형태로 반환
import nltk
nltk.download('sentiwordnet')
from nltk.corpus import sentiwordnet as swn
senti_synsets = list(swn.senti_synsets('slow'))
print('senti_synsets 반환 type : ', type(senti_synsets))
print('senti_synsets 반환 값 개수 : ', len(senti_synsets))
print('senti_synsets 반환 값 : ', senti_synsets)
[output]
senti_synsets 반환 type : <class 'list'>
senti_synsets 반환 값 개수 : 11
senti_synsets 반환 값 : [SentiSynset('decelerate.v.01'), SentiSynset('slow.v.02'),
SentiSynset('slow.v.03'), SentiSynset('slow.a.01'), ...
- senti_synset 객체는 감성지수, 객관성지수를 가지고 있음
- 단어가 감성적이지 않으면 객관성 지수는 1, 감성 지수는 모두 0이 됨
father = swn.senti_synset('father.n.01')
print('father 긍정감성 지수: ', father.pos_score())
print('father 부정감성 지수: ', father.neg_score())
print('father 객관성 지수: ', father.obj_score())
print('\n')
fabulous = swn.senti_synset('fabulous.a.01')
print('fabulous 긍정감성 지수 :', fabulous.pos_score())
print('fabulous 부정감성 지수: ', fabulous.neg_score())
[output]
father 긍정감성 지수: 0.0
father 부정감성 지수: 0.0
father 객관성 지수: 1.0
fabulous 긍정감성 지수 : 0.875
fabulous 부정감성 지수: 0.125
2.3 SentiWordNet을 이용한 영화 감상평 분석
- 문서(Document)를 문장(Sentence)단위로 분해
- 다시 문장을 단어(Word)단위로 토큰화하고 품사 태깅
- 품사 태깅된 단어 기반으로 synset 객체와 senti_synset 객체를 생성
- Senti_synset에서 긍정/부정 감성지수를 구하고 이를 모두 합산해 특정 임계치 값 이상일 때 긍정 감성, 그렇지 않으면 부정 감성으로 결정
- 품사 태깅을 수행하는 내부 함수 생성
from nltk.corpus import wordnet as wn
#간단한 nltk PennTreebanc Tag 를 기반으로 WordNet 기반의 품사 Tag로 변환
def penn_to_wn(tag):
if tag.startswith('J'):
return wn.ADJ
if tag.startswith('N'):
return wn.NOUN
if tag.startswith('R'):
return wn.ADV
if tag.startswith('V'):
return wn.VERB
- 문서를 문장 → 단어 토큰 → 품사 태깅 후에 SentiSynset 클래스를 생성하고 PolarityScore를 합산하는 함수 생성
from nltk.stem import WordNetLemmatizer
from nltk.corpus import sentiwordnet as swn
from nltk import word_tokenize, sent_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:
#NLTK 기반 품사 태깅 문장 추출
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
#총 count 가 0 이상일 경우 긍정 1, 그렇지 않을 경우 부정 0 반환
if sentiment >=0:
return 1
return 0
- 지도학습 기반의 IMDB 감상평 문서에 swn_polarity() 함수 적용, review_df 에 pred 칼럼을 추가하여 예측성능 평가
import nltk
nltk.download('punkt')
nltk.download('averaged_perceptron_tagger')
review_df['preds'] = review_df['review'].apply(lambda x : swn_polarity(x))
y_target = review_df['sentiment'].values
preds = review_df['preds'].values
# SentiWord 감성분석 예측 성능 평가
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))
[output]
[[7668 4832]
[3636 8864]]
정확도 : 0.6613
정밀도 : 0.6472
재현율 : 0.7091
3. VADER 을 이용한 감성분석
- 소셜미디어의 감성분석 용도로 만들어진 룰 기반의 Lexicion
- SentimentAnalyzer 클래스 이용
- polarity_scores() 메서드는 딕셔너리 형태의 감성지수 반환
- compound score 을 기반으로 부정, 긍정 감성 여부 결정 (보통 0.1 이상이면 긍정, 그 이하면 부정)
from vaderSentiment.vaderSentiment import SentimentIntensityAnalyzer
#IMDB 감상평 데이터 한 개만 감성분석
senti_analyzer = SentimentIntensityAnalyzer()
senti_score = senti_analyzer.polarity_scores(review_df['review'][0])
print(senti_score)
[output]
{'neg': 0.126, 'neu': 0.754, 'pos': 0.12, 'compound': -0.8696}
- 영화 감상평 텍스트와 긍정/부정을 결정하는 임계값을 입력받아 polarity_score()를 호출해 감성 결과를 반환하는 vader_polarity() 함수
- 정확도, 재현율이 SentiWordNet 보다 향상
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))
[output]
[[ 6683 5817]
[ 1792 10708]]
정확도: 0.6956
정밀도: 0.648
재현율: 0.8566
Ref) 파이썬 머신러닝 완벽가이드
'데이터 > 머신러닝' 카테고리의 다른 글
[RecSys] Chapter 9 | 장르 속성을 이용한 영화 콘텐츠 기반 필터링 (0) | 2023.02.12 |
---|---|
[NLP] Chapter 8 | 텍스트 분석 (네이버 영화 평점 감성 분석) (0) | 2023.02.11 |
[NLP] Chapter 8 | 텍스트 분석 (지도학습 기반 감성 분석) (0) | 2023.02.04 |
[NLP] Chapter 8 | 텍스트 분석 (텍스트 분류) (1) | 2023.02.03 |
[NLP] Chapter 8 | 텍스트 분석 (소개 및 기반지식) (1) | 2023.02.02 |