반응형

이번에 할 자연어처리 포스팅은 감정분류입니다 :)

감정분류는 어떤 텍스트가 있을 때, 그 텍스트가 긍정인지, 부정인지 분류해 주는 것을 말합니다.

 

이번에 사용할 데이터는 감정분류 대표 예제인 네이버 영화 리뷰  데이터를 가지고

감정분류를 위한 전처리, 모델링, 학습, 예측까지 진행해 보도록 하겠습니다.

 

데이터 준비

https://github.com/e9t/nsmc

 

GitHub - e9t/nsmc: Naver sentiment movie corpus

Naver sentiment movie corpus. Contribute to e9t/nsmc development by creating an account on GitHub.

github.com

위의 링크에 들어가서 

1. ratings.txt - 전체 리뷰데이터(20만건)

2. ratings_train.txt - 학습데이터(15만건)

3. ratings_test.txt - 평가데이터(5만건)

위의 세 파일을 다운받아 주세요.

그 후 DATA 라는 폴더를 만들어 그 안에 파일들이 위치하도록 해줍니다.

 

데이터 분석하기

감정분류에 사용할 데이터가 어떻게 이루어져있는지 살펴보도록 하겠습니다.

어떤 데이터고 길이는 어느정도 되는지, 많이 나온 단어 수 등등을 분석하면서 추후 모델링에 대한 좋은 아이디어가 생길 수도 있기 때문입니다.

import numpy as np
import pandas as pd
import os
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud

DATA_PATH = '/content/sample_data/DATA/' #데이터경로 설정
print('파일 크기: ')
for file in os.listdir(DATA_PATH):
  if 'txt' in file:
    print(file.ljust(30)+str(round(os.path.getsize(DATA_PATH+ file) / 100000,2))+'MB')

파일 크기는 각각 위의 사진과 같습니다.

train은 전체 데이터의 75%, test는 25%정도 됩니다.

학습할 데이터를 확인해 보도록 하겠습니다.

#트레인 파일 불러오기
train_data = pd.read_csv(DATA_PATH + 'ratings_train.txt',header = 0, delimiter = '\t', quoting=3)
train_data.head()

train파일에서는 id, document(리뷰), label(긍정인지 부정인지) 이렇게 3가지 정보를 나타내는 컬럼이 있습니다.

print('학습데이터 전체 개수: {}'.format(len(train_data)))

학습데이터는 그전에 언급했듯이 15만건이 있습니다. 총 15만개의 행과 3개의 열로 이루어져 있습니다.

리뷰 길이들을 확인해 보겠습니다.

#리뷰 전체길이 확인
train_length = train_data['document'].astype(str).apply(len)
train_length.head()

앞에 5행만 대표적으로 출력했을 때 각각 리뷰의 길이는 위와 같습니다.

#리뷰 통계 정보
print('리뷰 길이 최댓값: {}'.format(np.max(train_length)))
print('리뷰 길이 최솟값: {}'.format(np.min(train_length)))
print('리뷰 길이 평균값: {:.2f}'.format(np.mean(train_length)))
print('리뷰 길이 표준편차: {:.2f}'.format(np.std(train_length)))
print('리뷰 길이 중간값: {}'.format(np.median(train_length)))
print('리뷰 길이 제1사분위: {}'.format(np.percentile(train_length,25)))
print('리뷰 길이 제3사분위: {}'.format(np.percentile(train_length,75)))

리뷰 길이에 대해 간단한 통계 분석을 해보았을 때, 리뷰는 평균적으로 35.24의 길이를 가집니다.

 

이번에는 리뷰에서 어떤 단어가 가장 많이 나오는지 빈도분석을 워드클라우드(wordcloud)로 해 보겠습니다.

워드클라우드를 만들기 위한 전처리로 str타입이 아닌 데이터를 모두 제거해 줍니다.

# 문자열 아닌 데이터 모두 제거
train_review = [review for review in train_data['document'] if type(review) is str]
train_review

한글을 시각화할 것이기 때문에 한글 폰트(.ttf파일)를 다운받아 DATA파일에 위치해 주세요.

그 후 워드클라우드 시각화를 진행해 줍니다.

# 한글 폰트 설정(.ttf파일 다운로드 후 실행)
wordcloud = WordCloud(DATA_PATH+'폰트.ttf').generate(' '.join(train_review))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()

영화, 진짜, 정말, 그냥, 너무 등등의 단어가 많이 나옴을 확인 가능합니다.

학습데이터의 긍정리뷰 및 부정리뷰가 얼만큼 되는지도 확인해 보겠습니다.

#긍정 1, 부정 0
print('긍정 리뷰 갯수: {}'.format(train_data['label'].value_counts()[1]))
print('부정 리뷰 갯수: {}'.format(train_data['label'].value_counts()[0]))

각각 74827개(1: 긍정), 75173개(0: 부정)가 있습니다.

대략적인 분석을 마치도록 하겠습니다.

이번에는 데이터를 전처리하여 학습할 수 있는 데이터로 만들어 주도록 하겠습니다.

데이터 전처리

데이터 전처리는 5단계로 이루어져 있습니다.

1. 정규화로 한국어만 남기기

2. 형태소 분석기로 어간 추출하기

3. 불용어 제거하기

4. 문자를 인덱스벡터로 전환하기

5. 패딩처리하기

한국어 텍스트를 전처리할 때는 konlpy를 사용하여 형태소 분석 등을 해주겠습니다.

!pip install konlpy

konlpy가 미설치 되어 있다면 위의 코드를 실행해 주세요.

그 후 학습데이터의 리뷰데이터만 뽑아 보겠습니다.

import numpy as np
import pandas as pd
import re
import json
from konlpy.tag import Okt
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer

DATA_PATH = '/content/sample_data/DATA/' # 데이터 경로 설정
train_data = pd.read_csv(DATA_PATH+'ratings_train.txt', header = 0, delimiter='\t', quoting=3)

train_data['document'][:5]

자 그럼 본격적으로 전처리를 시작해 보겠습니다.

1. 전처리를 도와주는 함수 만들기
#전처리 함수 만들기
def preprocessing(review, okt, remove_stopwords = False, stop_words =[]):
  #함수인자설명
  # review: 전처리할 텍스트
  # okt: okt객체를 반복적으로 생성하지 않고 미리 생성 후 인자로 받음
  # remove_stopword: 불용어를 제거할지 여부 선택. 기본값 False
  # stop_words: 불용어 사전은 사용자가 직접 입력, 기본값 빈 리스트

  # 1. 한글 및 공백 제외한 문자 모두 제거
  review_text = re.sub('[^가-힣ㄱ-ㅎㅏ-ㅣ\\s]','',review)
  
  #2. okt 객체를 활용하여 형태소 단어로 나눔
  word_review = okt.morphs(review_text,stem=True)

  if remove_stopwords:
    #3. 불용어 제거(선택)
    word_review = [token for token in word_review if not token in stop_words]
  return word_review
2. 전체 학습데이터 및 평가데이터 리뷰 전처리하기
# 전체 텍스트 전처리
stop_words = ['은','는','이','가','하','아','것','들','의','있','되','수','보','주','등','한']
okt = Okt()
clean_train_review = []

for review in train_data['document']:
  # 리뷰가 문자열인 경우만 전처리 진행
  if type(review) == str:
    clean_train_review.append(preprocessing(review,okt,remove_stopwords=True,stop_words= stop_words))
  else:
    clean_train_review.append([]) #str이 아닌 행은 빈칸으로 놔두기

clean_train_review[:4]

#테스트 리뷰도 동일하게 전처리
test_data = pd.read_csv(DATA_PATH + 'ratings_test.txt', header = 0, delimiter='\t', quoting=3)

clean_test_review = []
for review in test_data['document']:
  if type(review) == str:
    clean_test_review.append(preprocessing(review, okt, remove_stopwords=True, stop_words=stop_words))
  else:
    clean_test_review.append([])
3. 문자로 되어있는 리뷰데이터를 인덱스 벡터로 변환

학습데이터 리뷰로 단어 사전을 생성하여 리뷰데이터를 인덱스로 바꾸어 주도록 하겠습니다.

라벨데이터(긍정, 분석 감정데이터, 정답 데이터)는 벡터화 해줍니다.

# 인덱스 벡터 변환 후 일정 길이 넘어가거나 모자라는 리뷰 패딩처리
tokenizer = Tokenizer()
tokenizer.fit_on_texts(clean_train_review)
train_sequences = tokenizer.texts_to_sequences(clean_train_review)
test_sequences = tokenizer.texts_to_sequences(clean_test_review)

word_vocab = tokenizer.word_index #단어사전형태
MAX_SEQUENCE_LENGTH = 8 #문장 최대 길이

#학습 데이터
train_inputs = pad_sequences(train_sequences,maxlen=MAX_SEQUENCE_LENGTH,padding='post')

#학습 데이터 라벨 벡터화
train_labels = np.array(train_data['label'])

#평가 데이터 
test_inputs = pad_sequences(test_sequences,maxlen=MAX_SEQUENCE_LENGTH,padding='post')
#평가 데이터 라벨 벡터화
test_labels = np.array(test_data['label'])
4. 전처리 완료된 데이터 넘파이 파일로 저장

이후 여기서 만들어준 데이터들을 학습시 사용이 용이하도록 넘파이 파일로 만들어 저장해주도록 하겠습니다.

DEFAULT_PATH  = '/content/sample_data/' # 경로지정
DATA_PATH = 'CLEAN_DATA/' #.npy파일 저장 경로지정
TRAIN_INPUT_DATA = 'nsmc_train_input.npy'
TRAIN_LABEL_DATA = 'nsmc_train_label.npy'
TEST_INPUT_DATA = 'nsmc_test_input.npy'
TEST_LABEL_DATA = 'nsmc_test_label.npy'
DATA_CONFIGS = 'data_configs.json'

data_configs={}
data_configs['vocab'] = word_vocab
data_configs['vocab_size'] = len(word_vocab) + 1

#전처리한 데이터들 파일로저장
import os

if not os.path.exists(DEFAULT_PATH + DATA_PATH):
  os.makedirs(DEFAULT_PATH+DATA_PATH)

#전처리 학습데이터 넘파이로 저장
np.save(open(DEFAULT_PATH+DATA_PATH+TRAIN_INPUT_DATA,'wb'),train_inputs)
np.save(open(DEFAULT_PATH+DATA_PATH+TRAIN_LABEL_DATA,'wb'),train_labels)
#전처리 테스트데이터 넘파이로 저장
np.save(open(DEFAULT_PATH+DATA_PATH+TEST_INPUT_DATA,'wb'),test_inputs)
np.save(open(DEFAULT_PATH+DATA_PATH+TEST_LABEL_DATA,'wb'),test_labels)

#데이터 사전 json으로 저장
json.dump(data_configs,open(DEFAULT_PATH + DATA_PATH + DATA_CONFIGS,'w'),ensure_ascii=False)

저는 CLEAN_DATA라는 폴더에 저장해 주었습니다.

학습하기
1. 학습데이터 및 전처리 데이터 불러오기
# 학습 데이터 불러오기
import tensorflow as tf
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint
from tensorflow.keras import layers
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import os
import json
from tqdm import tqdm

#전처리 데이터 불러오기
DATA_PATH = '/content/sample_data/CLEAN_DATA/'
DATA_OUT = '/content/sample_data/DATA_OUT/'
INPUT_TRAIN_DATA = 'nsmc_train_input.npy'
LABEL_TRAIN_DATA = 'nsmc_train_label.npy'
DATA_CONFIGS = 'data_configs.json'

train_input = np.load(open(DATA_PATH + INPUT_TRAIN_DATA,'rb'))
train_input = pad_sequences(train_input,maxlen=train_input.shape[1])
train_label = np.load(open(DATA_PATH + LABEL_TRAIN_DATA,'rb'))
prepro_configs = json.load(open(DATA_PATH+DATA_CONFIGS,'r'))
2. 파라미터 세팅하기
model_name= 'cnn_classifier_kr'
BATCH_SIZE = 512
NUM_EPOCHS = 10
VALID_SPLIT = 0.1
MAX_LEN = train_input.shape[1]

kargs={'model_name': model_name, 'vocab_size':prepro_configs['vocab_size'],'embbeding_size':128, 'num_filters':100,'dropout_rate':0.5, 'hidden_dimension':250,'output_dimension':1}
3. 모델 함수 만들기

학습 모델은 CNN분류 모델로 학습을 진행하도록 하겠습니다.

class CNNClassifier(tf.keras.Model):

  def __init__(self, **kargs):
    super(CNNClassifier, self).__init__(name=kargs['model_name'])
    self.embedding = layers.Embedding(input_dim=kargs['vocab_size'], output_dim=kargs['embbeding_size'])
    self.conv_list = [layers.Conv1D(filters=kargs['num_filters'], kernel_size=kernel_size, padding='valid',activation = tf.keras.activations.relu,
                                    kernel_constraint = tf.keras.constraints.MaxNorm(max_value=3)) for kernel_size in [3,4,5]]
    self.pooling = layers.GlobalMaxPooling1D()
    self.dropout = layers.Dropout(kargs['dropout_rate'])
    self.fc1 = layers.Dense(units=kargs['hidden_dimension'],
                            activation = tf.keras.activations.relu,
                            kernel_constraint=tf.keras.constraints.MaxNorm(max_value=3.))
    self.fc2 = layers.Dense(units=kargs['output_dimension'],
                            activation=tf.keras.activations.sigmoid,
                            kernel_constraint= tf.keras.constraints.MaxNorm(max_value=3.))
    

  def call(self,x):
    x = self.embedding(x)
    x = self.dropout(x)
    x = tf.concat([self.pooling(conv(x)) for conv in self.conv_list], axis = 1)
    x = self.fc1(x)
    x = self.fc2(x)
    return x
4. 학습하기

에포크는 10으로 주어 학습을 진행하고, 검증 정확도가 그전보다 낮아지면 학습을 멈추도록 설계하였습니다.

from tensorflow.keras.models import save_model

model = CNNClassifier(**kargs)
model.compile(optimizer=tf.keras.optimizers.Adam(),
              loss = tf.keras.losses.BinaryCrossentropy(),
              metrics = [tf.keras.metrics.BinaryAccuracy(name='accuracy')])

#검증 정확도를 통한 EarlyStopping 기능 및 모델 저장 방식 지정
earlystop_callback = EarlyStopping(monitor='val_accuracy', min_delta=0.0001, patience=2)
checkpoint_path = DATA_OUT + model_name +'\weights.h5'
checkpoint_dir = os.path.dirname(checkpoint_path)

if os.path.exists(checkpoint_dir):
  print("{} -- Folder already exists \n".format(checkpoint_dir))
else:
  os.makedirs(checkpoint_dir, exist_ok=True)
  print("{} -- Folder create complete \n".format(checkpoint_dir))

cp_callback = ModelCheckpoint(
    checkpoint_path, monitor = 'val_accuracy', verbose=1, save_best_only = True,
    save_weights_only=True
)

history = model.fit(train_input, train_label, batch_size=BATCH_SIZE, epochs = NUM_EPOCHS,
                    validation_split=VALID_SPLIT, callbacks=[earlystop_callback, cp_callback])
# 모델 저장하기
save_model(model,'모델 저장할 폴더 경로')

 

평가하기

위에서 학습한 모델이 저장이 되었고, 그 저장된 모델을 가지고 평가데이터를 넣어 얼마나 정답을 맞추는지 검증해 보도록 하겠습니다.

INPUT_TEST_DATA = 'nsmc_test_input.npy'
LABEL_TEST_DATA = 'nsmc_test_label.npy'
SAVE_FILE_NM = 'weights.h5'

test_input = np.load(open(DATA_PATH+INPUT_TEST_DATA,'rb'))
test_input = pad_sequences(test_input,maxlen=test_input.shape[1])
test_label_data = np.load(open(DATA_PATH + LABEL_TEST_DATA, 'rb'))

model.load_weights('모델저장위치/weights.h5')
model.evaluate(test_input, test_label_data)
 

약 82.6%의 정확도를 보입니다.

예측하기

마지막으로, 저장된 학습모델을 가지고 새로운 문장이 있을 때 그 문장이 긍정인지 부정인지 예측해 보겠습니다.

import numpy as np
import pandas as pd
import re
import json
from konlpy.tag import Okt
from tensorflow.keras.preprocessing.sequence import pad_sequences
from tensorflow.keras.preprocessing.text import Tokenizer
okt = Okt()
tokenizer  = Tokenizer()

DATA_CONFIGS = 'data_configs.json'
prepro_configs = json.load(open('/content/sample_data/CLEAN_DATA/'+DATA_CONFIGS,'r'))
prepro_configs['vocab'] = word_vocab

tokenizer.fit_on_texts(word_vocab)

MAX_LENGTH = 8 #문장최대길이

sentence = input('감성분석할 문장을 입력해 주세요.: ')
sentence = re.sub(r'[^ㄱ-ㅎㅏ-ㅣ가-힣\\s ]','', sentence)
stopwords = ['은','는','이','가','하','아','것','들','의','있','되','수','보','주','등','한'] # 불용어 추가할 것이 있으면 이곳에 추가
sentence = okt.morphs(sentence, stem=True) # 토큰화
sentence = [word for word in sentence if not word in stopwords] # 불용어 제거
vector  = tokenizer.texts_to_sequences(sentence)
pad_new = pad_sequences(vector, maxlen = MAX_LENGTH) # 패딩

model.load_weights('/content/sample_data/DATA_OUT/cnn_classifier_kr\weights.h5') #모델 불러오기
predictions = model.predict(pad_new)
predictions = float(predictions.squeeze(-1)[1])

if(predictions > 0.5):
  print("{:.2f}% 확률로 긍정 리뷰입니다.\n".format(predictions * 100))
else:
  print("{:.2f}% 확률로 부정 리뷰입니다.\n".format((1 - predictions) * 100))

이렇게 직접 리뷰 문장을 입력하여 긍정리뷰인지 부정리뷰인지 확인이 가능합니다 :)

전체 코드

https://colab.research.google.com/drive/1PkuR9r9WrAXHBq3TLePCiOEgYwuFx2lO?usp=sharing

 

영화리뷰 감정분류(한국어).ipynb

Colaboratory notebook

colab.research.google.com

학습된 모델파일

https://drive.google.com/drive/folders/13FqdLc-8BbtMptJZgtaN390Ot96J8I4y?usp=sharing 

my_models.zip파일입니다.

 

Python감정분석_NLP - Google Drive

이 폴더에 파일이 없습니다.이 폴더에 파일을 추가하려면 로그인하세요.

drive.google.com

Github

https://github.com/ElenaLim/NLP_EmotionA.git

 

GitHub - ElenaLim/NLP_EmotionA: this repository is for Korean NLP. 한국어 영화리뷰 감정분석

this repository is for Korean NLP. 한국어 영화리뷰 감정분석. Contribute to ElenaLim/NLP_EmotionA development by creating an account on GitHub.

github.com

전처리, 학습, 검증, 예측의 각 파트가 분리된 .py 파일이 있습니다.

참고자료

책 - 텐서플로2와 머신러닝으로 시작하는 자연어처리

마무리

오늘 감정분석 대표적인 영화리뷰 데이터로 감정분류를 해 보았는데

개인적으로는 예측 부분이 가장 재밌는것 같습니다 ㅎㅎ

다른 오픈데이터도 있다면 시간될 때 다른 모델, 데이터로 감정 분류를 진행해 보도록 하겠습니다 :-)

++22.06.09 : 모델 저장부분 추가 및 학습된 모델 업로드

++22.09.07: 전처리, 학습, 검증, 예측 파트로 분리된 각각의 .py파일 추가(깃허브 링크)

반응형

+ Recent posts