Posts 네이버 영화 평점을 이용한 감성 분석
Post
Cancel

네이버 영화 평점을 이용한 감성 분석

1. 네이버 영화


1.1 네이버 영화 평점

https://movie.naver.com/movie/point/af/list.nhn

  • 관객이 영화를 관람 후 리뷰와 함께 평점을 0점 ~ 10점 까지의 점수를 남김
  • 영화의 리뷰와 함께 평점을 크롤링해와서 감성분석에 사용


1.2 Data Load

1
2
3
4
import pandas as pd

train_df = pd.read_csv('https://raw.githubusercontent.com/hmkim312/datas/main/navermoviereview/ratings_train.txt', sep ='\t')
train_df.tail()
iddocumentlabel
1499956222902인간이 문제지.. 소는 뭔죄인가..0
1499968549745평점이 너무 낮아서...1
1499979311800이게 뭐요? 한국인은 거들먹거리고 필리핀 혼혈은 착하다?0
1499982376369청춘 영화의 최고봉.방황과 우울했던 날들의 자화상1
1499999619869한국 영화 최초로 수간하는 내용이 담긴 영화0
  • id: 리뷰한 관객의 id 고유값
  • document: 실제 리뷰
  • label: 감정 (0: 부정, 1: 긍정)
  • 총 200K의 감정분석(20만)
  • ratings.txt: 전체 20만개의 리뷰
  • ratings_test.txt: 5만개의 테스트용 리뷰
  • ratings_train.txt: 15만개의 훈련용 리뷰
  • 모든 리뷰는 140자 미만
  • 100k(10만) 부정 리뷰 (평점이 0점 ~ 4점)
  • 100K(10만) 긍정 리뷰 (평점이 9점 ~ 10점)
  • 평점이 5점 ~ 8점은 중립리뷰점수로 로 제외시킴


1.3 데이터의 분포

1
train_df['label'].value_counts()
1
2
3
0    75173
1    74827
Name: label, dtype: int64
  • 긍정과 부정이 각 7만5천개정도로 비슷한 분포를 보임


1.4 숫자 및 Null 데이터를 공백으로 변환

1
2
3
4
5
6
7
8
import re

train_df = train_df.fillna(' ')
train_df['document'] = train_df['document'].apply(lambda x : re.sub(r"\d+", " ", x))

test_df = pd.read_csv('https://raw.githubusercontent.com/hmkim312/datas/main/navermoviereview/ratings_test.txt', sep = '\t')
test_df = test_df.fillna(' ')
test_df['document'] = test_df['document'].apply(lambda x : re.sub(r"\d+", " ", x))
  • 리뷰에 숫자와 Null값은 공백으로 바꾸었음


1.5 형태소 분석

1
2
3
4
5
6
7
from konlpy.tag import Okt

okt = Okt()

def tw_tokenizer(text):
    tokens_ko = okt.morphs(text)
    return tokens_ko
  • KoNLPy를 사용하여 형태소 분석을 하기위해 함수를 생성함


1.6 TFidf 사용

1
2
3
4
5
from sklearn.feature_extraction.text import TfidfVectorizer

tfidf_vect = TfidfVectorizer(tokenizer= tw_tokenizer, ngram_range=(1,2), min_df=3, max_df=0.9)
tfidf_vect.fit(train_df['document'])
tfidf_matrix_train = tfidf_vect.transform(train_df['document'])
1
2
/opt/anaconda3/lib/python3.8/site-packages/sklearn/feature_extraction/text.py:484: UserWarning: The parameter 'token_pattern' will not be used since 'tokenizer' is not None'
  warnings.warn("The parameter 'token_pattern' will not be used"
  • min_df: 최소 빈도값을 설정해주는 파라미터
    • DF는 특정 단어가 나타나는 ‘문서의 수’를 의미, 단어의 수가 아님.
    • min_df를 설정하여 해당 값보다 작은 DF를 가진 단어들은 사전 (vocabulary_)에서 제외함
    • float은 %, int는 갯수를 의미함 (ex - 0.01 = 문서에 1%미만으로 나타나는 단어 무시, 10 = 문서에 10개 미만으로 나타나는 단어 무시)
  • max_df : 최대 빈도값을 설정해주는 파라미터
    • DF는 특정 단어가 나타나는 ‘문서의 수’를 의미, 단어의 수가 아님.
    • max_df를 설정하여 해당 값보다 큰 DF를 가진 단어들은 사전 (vocabulary_)에서 제외함
    • float은 %, int는 갯수를 의미함 (ex - 0.80 = 문서에 80%이상으로 나타나는 단어 무시, 10 = 문서에 10개 이상으로 나타나는 단어 무시)
  • analyzer : 학습단위를 결정하는 파라미터
    • word : 학습의 단위를 단어로 설정 (ex - one, two, …)
    • char : 학습의 단위를 글자로 설정(ex - a, b, c, d …)
  • sublinear_tf : TF (단어빈도) 값의 스무딩(smoothing) 여부를 결정하는 파라미터 (Boolean type)
    • TF를 1 + log(TF)으로 변경
    • TF의 아웃라이어가 심할경우 사용
  • n-ngram_range : 단어의 묶음의 범위 설정 파라미터
    • ngram_range = (1, 1) : 단어의 묶음을 1개부터 1개까지 설정 (one, two, …)
    • ngram_range = (1, 2) : 단어의 묶음을 1개부터 2개까지 설정 (go back, good time, one, two, …)
  • max_feature : tf-idf vector의 최대 feature를 설정하는 파라미터
    • feature : 컬럼 혹은 열, 전체 feature의 갯수를 제한함


1.7 LGBM 사용

1
2
3
4
5
6
7
8
from lightgbm import LGBMClassifier
import time

start_time = time.time()

lgbm_clf = LGBMClassifier(n_estimators= 400, n_jobs=-1)
lgbm_clf.fit(tfidf_matrix_train, train_df['label'])
print(f'fit time : {time.time() - start_time}')
1
fit time : 44.02427792549133
  • LGBM을 사용하니 생각보다 오래 걸리지 않음


1.8 Accuracy

1
2
3
4
5
6
from sklearn.metrics import accuracy_score

tfidf_matrix_test = tfidf_vect.transform(test_df['document'])
preds = lgbm_clf.predict(tfidf_matrix_test)

accuracy_score(test_df['label'], preds)
1
0.82958
  • 감성분석 후 test accuracy도 나쁘지 않게 나오는듯 하다


1.9 실제 문장 테스트

1
test_df['document'][100]
1
'걸작은 몇안되고 졸작들만 넘쳐난다.'
1
lgbm_clf.predict(tfidf_vect.transform([test_df['document'][100]]))
1
array([0])
  • Test 100번째 데이터의 리뷰를 보고, 감성분석의 결과 0(부정)으로 나오는것을 보니, 나쁘지않은것 같다
  • transform을 할때 리스트로 감싸주어야 한다.


1.10 감성 분류 적용

1
2
3
4
5
text = '여태 보았던 영화중에 제일 재미없네요'
if lgbm_clf.predict(tfidf_vect.transform([text])) == 0:
    print(f'"{text}" -> 부정일 가능성이 {round(lgbm_clf.predict_proba(tfidf_vect.transform([text]))[0][0],2)}% 입니다.')
else:
    print(f'"{text}" -> 긍정일 가능성이 {round(lgbm_clf.predict_proba(tfidf_vect.transform([text]))[0][1],2)}% 입니다.')
1
"여태 보았던 영화중에 제일 재미없네요" -> 부정일 가능성이 0.83% 입니다.
1
2
3
4
5
text = '시원하고 통쾌한 액션 최고였어요'
if lgbm_clf.predict(tfidf_vect.transform([text])) == 0:
    print(f'"{text}" -> 부정일 가능성이 {round(lgbm_clf.predict_proba(tfidf_vect.transform([text]))[0][0],2)}% 입니다.')
else:
    print(f'"{text}" -> 긍정일 가능성이 {round(lgbm_clf.predict_proba(tfidf_vect.transform([text]))[0][1],2)}% 입니다.')
1
"시원하고 통쾌한 액션 최고였어요" -> 긍정일 가능성이 0.96% 입니다.
  • 아무 리뷰나 작성하여 보았고, 긍정과 부정을 잘 가져온다.


1.11 단어 빈도수 및 WordCloud를 보기 위한 함수 생성

1
2
3
4
5
6
7
8
twitter = Okt()
def tw_tokenizer_nouns(text):
    tokens_ko = twitter.nouns(text)
    return tokens_ko

nouns = []
for i in range(0,len(train_df['document'])):
    nouns.extend(tw_tokenizer_nouns(train_df['document'][i]))
  • 함수를 사용하여 명사만 추출함


1.12 단어 빈도수 시각화

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import nltk
from wordcloud import WordCloud, STOPWORDS
stopwords = list(STOPWORDS) + (["것", "점", "말", "거", "때", "그", "내", "왜",
                                "나", "이", "뭐", "듯", "걸", "수", "더", "좀", "볼", 
                                "임", "개", "년", "암", "또", "안", "분", "중", "꼭"])

ko = nltk.Text(nouns, name='Naver_movie_reviews')
data = ko.vocab().most_common(5000)

ko = [data for data in ko if data not in stopwords]
ko = nltk.Text(ko, name='Naver_movie_reviews')
plt.figure(figsize=(24, 8))
ko.plot(50)  # 상위 50개
plt.show()

  • 언급된 명사들 중 무의미한것들만 제외(stopwords)하고 그래프로 시각화함
  • 역시나 영화 리뷰라서 영화라는 단어가 언급이 제일 많이됨


1.13 Word Cloud

1
2
3
4
5
6
7
8
9
data = ko.vocab().most_common(5000)
wordcloud = WordCloud(font_path='AppleGothic',
                      relative_scaling=0.2,
                      background_color='white',
                      stopwords=stopwords).generate_from_frequencies(dict(data))
plt.figure(figsize=(24, 8))
plt.imshow(wordcloud)
plt.axis('off')
plt.show()

This post is licensed under CC BY 4.0 by the author.