728x90
참고 출처 : https://wikidocs.net/44249
6) 네이버 영화 리뷰 감성 분류하기(Naver Movie Review Sentiment Analysis)
이번에 사용할 데이터는 네이버 영화 리뷰 데이터입니다. 총 200,000개 리뷰로 구성된 데이터로 영화 리뷰에 대한 텍스트와 해당 리뷰가 긍정인 경우 1, 부정인 경우 0을 ...
wikidocs.net
불용어처리, 모델적용 오래걸린다. > 결과값만 보도록한다

colab 에서 konlpy를 사용하여 실행한다.
!pip install konlpy
필요한 것 import
import numpy as np
import matplotlib.pyplot as plt
import re
from konlpy.tag import Okt
from keras.preprocessing.text import Tokenizer
from keras.preprocessing.sequence import pad_sequences
import pandas as pd
데이터 불러오기 (라벨링 완료된 데이터이다)
train_data = pd.read_table('https://raw.githubusercontent.com/pykwon/python/master/testdata_utf8/ratings_train.txt')
test_data = pd.read_table('https://raw.githubusercontent.com/pykwon/python/master/testdata_utf8/ratings_test.txt')
print(set(test_data.label)) # {0, 1} 부정 , 긍정
train 데이터를 전처리 해준다.
# 중복 데이터 확인 후 삭제
print(train_data.document.nunique()) # 146182
train_data.drop_duplicates(subset=['document'], inplace=True)
# null 확인 후 해당 행 삭제
print(train_data.isnull().sum()) # document 1
train_data = train_data.dropna(how='any')
# 한글과 공백만 데이터로 처리
train_data.document = train_data.document.str.replace("[^가-힣 ]", "")
train_data.document = train_data.document.str.replace('^ +','') # 하나 이상의 공백 데이터를 empty value로 변경
train_data.document.replace('',np.nan, inplace=True)
# document 열 중 NaN인 행 삭제
train_data = train_data.dropna(how='any')
test 데이터도 전처리 해준다
test_data.drop_duplicates(subset=['document'], inplace=True)
test_data.document = test_data.document.str.replace("[^가-힣 ]", "")
test_data.document = test_data.document.str.replace('^ +','')
test_data.document.replace('',np.nan, inplace=True)
test_data = test_data.dropna(how='any')
형태소분석, 불용어 제외
# 불용어(stopwords) - 문장에 자주 등장하는 자주 등장하는 단어이지만 문맥에 영향을 주지 않는 단어들
stopwords = ['와','하다','한','은','를','는','의','좀','잘','과','으로','에는','에','하는','이지만','조차','아','그러나','그리고','그래서']
# 형태소 분석
okt = Okt()
# train data
x_train = []
for sentence in train_data['document']:
imsi = []
imsi = okt.morphs(sentence, stem=True) # 형태소단위로 텍스트를 나눠준다. step=True : 어간추출
imsi = [word for word in imsi if not word in stopwords] # 불용어 제거
x_train.append(imsi)
print(x_train[:3])
# test data
x_test = []
for sentence in test_data['document']:
imsi = []
imsi = okt.morphs(sentence, stem=True)
imsi = [word for word in imsi if not word in stopwords]
x_test.append(imsi)
print(x_test[:3])
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
[['더빙', '진짜', '짜증나다', '목소리'], ['흠', '포스터', '보고', '초딩', '영화', '줄', '오버', '연기', '가볍다', '않다'], ['너', '무재', '밓었', '다그', '래서', '보다', '추천', '다']]
[['굳다'], ['뭐', '야', '이', '평점', '들', '나쁘다', '않다', '점', '짜다', '리', '더', '더욱', '아니다'], ['지루하다', '않다', '완전', '막장', '임', '돈', '주다', '보기']]
워드 임베딩
# word embedding : 정수 인코딩
tok = Tokenizer()
tok.fit_on_texts(x_train)
print(tok.word_index)
# 등장 빈도수가 3회 미만인 단어의 비중 확인
threshold = 3
total_cnt = len(tok.word_index)
rare_cnt = 0
total_freq = 0
rare_freq = 0
for key, value in tok.word_counts.items():
total_freq = total_freq + value
if value < threshold:
rare_cnt = rare_cnt + 1
rare_freq = rare_freq + value
print('단어 집합 크기 : ', total_cnt)
print('희귀 단어 수 : ', rare_cnt)
print('단어 집합 크기 :', (rare_cnt / total_cnt) * 100)
print('전체 등장 빈도에서 희귀단어 비율 :', (rare_freq / total_freq) * 100)
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
{'이': 1, '영화': 2, '보다': 3, '가': 4, '을': 5, ...
단어 집합 크기 : 43069
희귀 단어 수 : 23850
단어 집합 크기 : 55.37625670435812
전체 등장 빈도에서 희귀단어 비율 : 1.7389526217056424
OOV란
https://acdongpgm.tistory.com/223
[NLP] . OOV 를 해결하는 방법 - 1. BPE(Byte Pair Encoding)
컴퓨터가 자연어를 이해하는 기술은 크게 발전했다. 그 이유는 자연어의 근본적인 문제였던 OOV문제를 해결했다는 점에서 큰 역할을 했다고 본다. 사실 해결이라고 보긴 어렵고 완화가 더 맞는
acdongpgm.tistory.com
희귀단어(2이하의 단어)제거 후 토큰화
# 희귀단어 비율이 1.7389이므로 희귀단어 갯수는 제거 (2글자 이하 단어)
vocab_size = total_cnt - rare_cnt + 2 # +2 하는 이유는 pad와 oov 토큰을 사용할 예정.
print('단어사전크기:', vocab_size) # 19221
# 토큰화 되어 있지 않은 경우 (단어 사전에 등록되지 않는 단어)에는 oov(out of vocabulary)로 처리. oov는 보통 1로 할당
tok = Tokenizer(vocab_size, oov_token='OOV')
tok.fit_on_texts(x_train)
x_train = tok.texts_to_sequences(x_train)
x_test = tok.texts_to_sequences(x_test)
print(x_train[:3])
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
단어사전크기: 19221
[[448, 21, 256, 651], [921, 450, 46, 594, 3, 211, 1427, 29, 666, 24], [380, 2404, 1, 2224, 5608, 4, 219, 14]]
label 보관, 빈 샘플 제거
# label 별도 보관
y_train = np.array(train_data['label'])
y_test = np.array(test_data['label'])
print(y_train[:3])
# 빈 샘플(empty sample) 제거
# 전체 데이터에서 빈도수가 낮은 단어가 삭제되었다는 것은 빈도수가 낮은 단어만으로 구성되었던 샘플들은 빈(empty) 샘플이 되었다는 것을 의미합니다.
# 빈 샘플들은 어떤 레이블이 붙어있던 의미가 없으므로 빈 샘플들을 제거해주는 작업을 하겠습니다.
# 각 샘플들의 길이를 확인해서 길이가 0인 샘플들의 인덱스를 받아오겠습니다
drop_train = [index for index, sentence in enumerate(x_train) if len(sentence) < 1]
print(drop_train)
# 빈 샘플들을 제거
X_train = np.delete(x_train, drop_train, axis=0)
y_train = np.delete(y_train, drop_train, axis=0)
print(len(X_train))
print(len(y_train))
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
[0 1 0]
전체 샘플 중 길이가 max_len 이하인 샘플의 비율이 몇 %인지 확인하는 함수를 만듭니다.
def below_threshold_len(max_len, nested_list):
count = 0
for sentence in nested_list:
if(len(sentence) <= max_len):
count = count + 1
print('전체 샘플 중 길이가 %s 이하인 샘플의 비율: %s'%(max_len, (count / len(nested_list))*100))
max_len = 30
below_threshold_len(max_len, X_train)
# 전체 훈련 데이터 중 약 94%의 리뷰가 30이하의 길이를 가지는 것을 확인했습니다. 모든 샘플의 길이를 30으로 맞추겠습니다.
x_train = pad_sequences(x_train, maxlen=max_len)
x_test = pad_sequences(x_test, maxlen=max_len)
print(x_train[:10])
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
전체 샘플 중 길이가 30 이하인 샘플의 비율: 93.19852941176471
[[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 0 448 21 256 651]
[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 921 450 46 594
3 211 1427 29 666 24]
[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 380 2404
1 2224 5608 4 219 14]
[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 6416 105
7631 214 59 9 31 3551]
[ 0 0 0 0 0 0 0 0 0 0 0 0
1006 1 34 9060 29 5 819 3 2538 26 1089 237
14101 1 5 1059 249 237]
[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 709 5609 979 1366 424 141 1670 1605 11443 222
3 124 1068 7 50 241]
[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0
0 210 307 6 316 474]
[ 121 1562 2 361 118 223 15 790 22 569 566 511
468 3073 8038 19 1367 1367 2 42 278 7 9 29
40 45 19 695 1053 70]
[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 94 2
9 59 11 361 97 3]
[ 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 1510 31 2 198 528 85 19 387
1418 354 658 13 5610 11]]
모델적용
# model
from keras.layers import Embedding, Dense, LSTM
from keras.models import Sequential
from keras.models import load_model
from keras.callbacks import EarlyStopping, ModelCheckpoint
embedding_dim = 100
hidden_units = 128
model = Sequential()
model.add(Embedding(vocab_size, embedding_dim))
model.add(LSTM(hidden_units, activation='tanh'))
model.add(Dense(128, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
es = EarlyStopping(monitor='val_loss', mode='min', verbose=1, patience=4)
mc = ModelCheckpoint('best_model.h5', monitor='val_acc', mode='max', verbose=1, save_best_only=True)
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['acc'])
history = model.fit(x_train, y_train, epochs=15, callbacks=[es, mc], batch_size=64, validation_split=0.2)
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
Epoch 9/15
1816/1816 [==============================] - ETA: 0s - loss: 0.1952 - acc: 0.9239
Epoch 9: val_acc did not improve from 0.85735
1816/1816 [==============================] - 155s 85ms/step - loss: 0.1952 - acc: 0.9239 - val_loss: 0.3895 - val_acc: 0.8467
Epoch 9: early stopping
예측 함수 만들기
def sentiment_predict(new_sentence):
new_sentence = re.sub(r'[^ㄱ-ㅎㅏ-ㅣ가-힣 ]','', new_sentence)
new_sentence = okt.morphs(new_sentence, stem=True) # 토큰화
new_sentence = [word for word in new_sentence if not word in stopwords] # 불용어 제거
encoded = tok.texts_to_sequences([new_sentence]) # 정수 인코딩
pad_new = pad_sequences(encoded, maxlen = max_len) # 패딩
score = float(model.predict(pad_new)) # 예측
if(score > 0.5):
print("{:.2f}% 확률로 긍정 리뷰입니다.\n".format(score * 100))
else:
print("{:.2f}% 확률로 부정 리뷰입니다.\n".format((1 - score) * 100))
예측해보기
sentiment_predict('이 영화 개꿀잼 ㅋㅋㅋ')
sentiment_predict('이 영화 핵노잼 ㅠㅠ')
sentiment_predict('이딴게 영화냐 ㅉㅉ')
sentiment_predict('감독 뭐하는 놈이냐?')
sentiment_predict('와 개쩐다 정말 세계관 최강자들의 영화다')
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
92.77% 확률로 긍정 리뷰입니다.
98.74% 확률로 부정 리뷰입니다.
99.94% 확률로 부정 리뷰입니다.
99.77% 확률로 부정 리뷰입니다.
58.45% 확률로 부정 리뷰입니다.
728x90
'데이터분석 > 예시코드' 카테고리의 다른 글
RNN : seq2seq로 영어를 한국어로 번역 (0) | 2022.06.07 |
---|---|
LSTM으로 주식을 예측 (1) | 2022.06.03 |
웹 스크래핑 : 네이버 영화 평점 (0) | 2022.06.01 |
웹 스크래핑 : 기초 (0) | 2022.05.29 |
RNN을 이용한 텍스트 생성 (0) | 2022.05.27 |