[NLP]NSMC데이터 + 전처리 + WordCloud + SVM + LSTM
네이버 영화 리뷰 데이터인 NSMC데이터를 사용해서 NLP를 연습해 보겠습니다.
초보자의 입장에서 사용한 코드이므로 참고하시길 바랍니다.
감사합니다.
import re
from konlpy.tag import Mecab
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import sys
sys.version
'3.8.8 (default, Apr 13 2021, 15:08:03) [MSC v.1916 64 bit (AMD64)]'
파이썬 3.8버젼을 이용해서 작성하였습니다. 윈도우 사용자들은 구글링을 하셔서 메캡 설치 방법을 찾아서 다운로드 받으시면 됩니다.(간단해요!)
데이터 불러오기
- 영화리뷰 데이터인 NSMC 데이터를 사용해보겠습니다
rating_train = pd.read_csv('./nsmc-master/nsmc-master/ratings_train.txt', sep = "\t", engine='python', encoding = "utf-8")
rating_test = pd.read_csv('./nsmc-master/nsmc-master/ratings_test.txt', sep = "\t", engine='python', encoding = "utf-8")
rating_train.head()
id | document | label | |
---|---|---|---|
0 | 9976970 | 아 더빙.. 진짜 짜증나네요 목소리 | 0 |
1 | 3819312 | 흠...포스터보고 초딩영화줄....오버연기조차 가볍지 않구나 | 1 |
2 | 10265843 | 너무재밓었다그래서보는것을추천한다 | 0 |
3 | 9045019 | 교도소 이야기구먼 ..솔직히 재미는 없다..평점 조정 | 0 |
4 | 6483659 | 사이몬페그의 익살스런 연기가 돋보였던 영화!스파이더맨에서 늙어보이기만 했던 커스틴 ... | 1 |
print(len(rating_train))
print(len(rating_test))
150000
50000
총 20만개의 데이터가 있음을 확인할 수 있다.
전처리
- 부호 제거, 토큰화, 불용어 제거
rating_test['document'].astype('str')
0 []
2 ['평점', '나쁘', '짜리', '더더욱', '잖아']
3 ['지루', '은데', '완전', '막장']
4 ['어도', '텐데', '나와서', '심기', '불편']
5 ['음악', '최고', '음악', '영화']
Name: document, dtype: object
#특수문자 및 영어 없애기
def del_special(text):
text = re.sub("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]", "", text) #한글을 제외한 모든 건 삭제
return text
rating_train['document'] = rating_train['document'].astype('str').apply(del_special)
rating_test['document'] = rating_test['document'].astype('str').apply(del_special)
# 결측치 제거
rating_train = rating_train[rating_train['document'].str.len() >= 1]
rating_test = rating_test[rating_test['document'].str.len() >= 1]
%%time
# 토큰화 및 불용어(stopwords) 제거
# mecab 객체 생성
mecab = Mecab(dicpath=r"C:\mecab\mecab-ko-dic")
SW = list(pd.read_csv('./stopwords-ko.txt',header=None).iloc[:,0])
def token_and_stopwords(text):
return [word for word in mecab.morphs(text) if word not in SW and len(word) > 1]
rating_train['document'] = rating_train['document'].apply(token_and_stopwords)
rating_test['document'] = rating_test['document'].apply(token_and_stopwords)
Wall time: 57.3 s
전처리를 끝낸 후의 데이터 확인
rating_train.tail()
id | document | label | |
---|---|---|---|
149995 | 6222902 | [인간, 문제지, 인가] | 0 |
149996 | 8549745 | [평점, 너무, 아서] | 1 |
149997 | 9311800 | [이게, 한국인, 거들먹거리, 필리핀, 혼혈, 착하] | 0 |
149998 | 2376369 | [청춘, 영화, 최고봉, 방황, 우울, 자화상] | 1 |
149999 | 9619869 | [한국, 영화, 최초, 수간, 내용, 담긴, 영화] | 0 |
nltk 라이브러리를 활용해서 전처리 한 정보 집계하기
import nltk
text = nltk.Text([word for text in rating_train['document'] for word in text])
text.vocab().most_common(10)
[('영화', 57601),
('는데', 11464),
('너무', 11013),
('정말', 9779),
('재밌', 8988),
('네요', 8979),
('진짜', 8327),
('ㅋㅋ', 7233),
('연기', 6826),
('최고', 6579)]
Wordcloud
from wordcloud import WordCloud
data = text.vocab().most_common(100)
wordcloud = WordCloud(font_path='C:/Windows/Fonts/malgun.ttf', # font_path : 윈도우는 .ttf, macOS는 .otf
relative_scaling = 0.2,
max_font_size=60,
background_color='white',
).generate_from_frequencies(dict(data))
plt.figure(figsize=(12,4))
plt.imshow(wordcloud)
plt.axis("off")
plt.show()
SVM을 이용한 예측
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.pipeline import Pipeline
from sklearn.svm import SVC
rating_train.head()
id | document | label | |
---|---|---|---|
0 | 9976970 | [진짜, 짜증, 네요, 목소리] | 0 |
1 | 3819312 | [포스터, 보고, 초딩, 영화, 오버, 연기, 가볍, 구나] | 1 |
2 | 10265843 | [너무, 밓었다그래서보는것을추천한다] | 0 |
3 | 9045019 | [교도소, 이야기, 구먼, 솔직히, 재미, 평점, 조정] | 0 |
4 | 6483659 | [사이몬페그, 익살, 스런, 연기, 돋보였, 영화, 스파이더맨, 보이, 커스틴, 던... | 1 |
def list_to_str(text):
return " ".join(text)
# 데이터 구성
train_x = [list_to_str(text) for text in rating_train['document']]
train_y = np.array(rating_train['label'])
test_x = [list_to_str(text) for text in rating_test['document']]
test_y = np.array(rating_test['label'])
# 모델 파이프라인 구축
model1 = Pipeline([
('vect' , CountVectorizer()), #Bag of Word
('clf' , SVC())
])
model2 = Pipeline([
('vect' , TfidfVectorizer()), #TF-IDF
('clf' , SVC())
])
print(len(train_x))
print(len(train_y))
149186
149186
컴퓨터가 너무 안 좋아서 안 돌아가는 관계로 ㅜㅜ 앞부분 10000개만 학습시켜 보겠습니다.
train_x_sample = train_x[:10000]
train_y_sample = train_y[:10000]
test_x_sample = test_x[:5000]
test_y_sample = test_y[:5000]
%%time
model1.fit(train_x_sample, train_y_sample)
model2.fit(train_x_sample, train_y_sample)
Wall time: 19.6 s
Pipeline(steps=[('vect', TfidfVectorizer()), ('clf', SVC())])
print(f"countervectorizer + SVM : {model1.score(test_x_sample,test_y_sample)}")
print(f"TF-IDF vectorizer + SVM : {model2.score(test_x_sample,test_y_sample)}")
countervectorizer + SVM : 0.7726
TF-IDF vectorizer + SVM : 0.7844
대략 70 후반대의 정확도를 보여줍니다. 데이터를 더 학습시키면 좋아질 것으로 예상됩니다.
pred = model1.predict(test_x_sample)
결과 확인
- 1 : 긍정
- 0 : 부정
pd.DataFrame({"text":test_x_sample[10:20], "pred":pred[10:20]})
text | pred | |
---|---|---|
0 | 한국 독립영화 한계 그렇게 아버지 된다 비교 | 0 |
1 | 청춘 아름답 아름다움 이성 흔들 는다 찰나 아름다움 포착 섬세 아름다운 수채화 퀴어 영화 | 1 |
2 | 보이 반전 영화 흡인력 사라지 | 0 |
3 | 스토리 연출 연기 비주얼 영화 기본 영화 영화 김문옥 감독 영화 경력 인데 조무래기... | 0 |
4 | 소위 문가 라는 평점 | 0 |
5 | 최고 | 1 |
6 | 발연기 도저히 진짜 이렇게 연기 라곤 상상 못했 | 0 |
7 | 나이스 | 0 |
8 | 재미 우려먹 챔프 방송 더라 ㅋㅋㅋ ㅋㅋㅋ ㅋㅋㅋ ㅋㅋ | 0 |
9 | 금요일 나이트메어 시리즈 가장 시리즈 양산 해냈 레이저 시리즈 작가 상상력 돋보이 ... | 1 |
LSTM을 이용한 예측
토큰화 및 정수 인코딩
text = nltk.Text([word for text in rating_train['document'] for word in text])
pd.Series(text.tokens).value_counts()
영화 57601
는데 11464
너무 11013
정말 9779
재밌 8988
...
초면 1
부네 1
스무고개 1
잡힙니다 1
겁대가리 1
Length: 47803, dtype: int64
sum(pd.Series(text.tokens).value_counts() >=2)
26827
총 47803개의 토큰들 중 전체 문서중에서 최소 2번 이상 등장하는 토큰은 약 27000개 정도. 이것을 단어 집합의 크기로 정하자
import tensorflow as tf
from tensorflow.keras.preprocessing.text import Tokenizer
# Keras가 인식할 수 있도록 토큰화
tokenizer = Tokenizer(num_words=27000)
tokenizer.fit_on_texts(train_x)
# LSTM의 input으로 넣기 위해 변환
x_train = tokenizer.texts_to_sequences(train_x)
x_test = tokenizer.texts_to_sequences(test_x)
#train_y는 그대로
#test_y는 그대로
print(x_train[0:2])
[[7, 94, 6, 405], [267, 273, 369, 1, 1046, 9, 581, 190]]
이런식으로 토큰화 및 정수 인코딩을 실행한 것을 확인할 수 있습니다.
패딩
- 리뷰 데이터에서 길이가 30 이하인 데이터가 전체의 99.7%로 대부분을 차지함 그러므로 최대 길이는 30으로 정하겠습니다.
- ex) [30, 22, 123, 444]라는 리뷰 데이터의 길이 : 4
sum([len(txt) <= 30 for txt in x_train]) / len(x_train)
0.9968696794605392
plt.figure(figsize=(4,3))
sns.histplot([len(txt) for txt in x_train])
plt.show()
# 리뷰 데이터의 크기를 맞춰주기 위한 zero padding
from tensorflow.keras.preprocessing.sequence import pad_sequences
x_train = pad_sequences(x_train, value=0, padding='pre', maxlen=30)
x_test = pad_sequences(x_test, value=0, padding='pre', maxlen=30)
print(x_train[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 0 7 94 6 405]
최종적으로 인코딩 및 패딩을 완료한 데이터의 모습입니다.
# 학습 가능한 형태로 최종 변환.
train_ds = tf.data.Dataset.from_tensor_slices((x_train, train_y)).shuffle(10000).batch(64)
test_ds = tf.data.Dataset.from_tensor_slices((x_test, test_y)).batch(64)
모델 만들어서 예측하기
from tensorflow.keras.layers import Embedding, Dense, LSTM
from tensorflow.keras.models import Sequential
model = Sequential()
model.add(Embedding(input_dim=27000, #Size of the vocabulary
output_dim=100)) #Dimension of the dense embedding.
model.add(LSTM(units=128, #dimensionality of the output space
dropout=0.2, #overfitting 방지
recurrent_dropout=0.2))
model.add(Dense(1, activation='sigmoid'))
노트북이 안 좋은 관계로 epoch는 5번만 해보겠습니다.
%%time
# 모델 컴파일
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# Early Stopping Callback(val_loss가 계속 올라가면 stop)
ES = tf.keras.callbacks.EarlyStopping(monitor="val_loss", patience=10, verbose=1)
history = model.fit(train_ds, validation_data=test_ds, epochs=5, callbacks=[ES])
Epoch 1/3
2332/2332 [==============================] - 305s 129ms/step - loss: 0.4221 - accuracy: 0.7978 - val_loss: 0.3952 - val_accuracy: 0.8113
Epoch 2/3
2332/2332 [==============================] - 303s 130ms/step - loss: 0.3459 - accuracy: 0.8422 - val_loss: 0.4168 - val_accuracy: 0.8145
Epoch 3/3
2332/2332 [==============================] - 298s 128ms/step - loss: 0.2976 - accuracy: 0.8659 - val_loss: 0.4467 - val_accuracy: 0.8054
모델 평가
score, acc = model.evaluate(x_test,test_y, batch_size=64)
777/777 [==============================] - 12s 16ms/step - loss: 0.4467 - accuracy: 0.8054
LSTM을 활용하면 0.8정도의 퍼포먼스를 보여주고 있습니다.
모델 저장
model.save_weights("nsmc_lstm")