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()
id | document | label | |
---|---|---|---|
149995 | 6222902 | 인간이 문제지.. 소는 뭔죄인가.. | 0 |
149996 | 8549745 | 평점이 너무 낮아서... | 1 |
149997 | 9311800 | 이게 뭐요? 한국인은 거들먹거리고 필리핀 혼혈은 착하다? | 0 |
149998 | 2376369 | 청춘 영화의 최고봉.방황과 우울했던 날들의 자화상 | 1 |
149999 | 9619869 | 한국 영화 최초로 수간하는 내용이 담긴 영화 | 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()