본문 바로가기
AI 빅데이터/후려치는 데이터분석과 AI 알고리즘

[자연어처리] 간단하게 텍스트 감성 분류하기

by 마고커 2020. 7. 13.


CNN으로 SA(Sentimental Analysis)하는 건 김윤박사의 논문으로 유명해졌는데, 비전공자로써 알고리즘 이해하는 건 쉽지 않았다. 간단히 DNN 네트워크를 구성하고 감성분류하는 내용을 Keras로 따라해 보기로 했다. 유명한 아래 책의 4장에 있는 내용을 살짝만 바꿨다.

 

 

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

본 서는 기존 자연어 처리 서적과는 다른 세 가지 특징을 가지고 있다. 첫째, 자연어 처리에 활용되는 개념적인 설명에서 끝나는 것이 아니라 모델 구현에 집중한다. 그뿐만 아니라 상용 서비스�

www.yes24.com

EDA하는 과정도 자세히 나와 있지만, 후려치는 AI알고리즘이니

 

데이터 읽기 -> 간단한 전처리 -> 토큰화 -> 임베딩으로 포함한 모델링 -> 테스트 부분으로만 볼 생각이다.

책과 마찬가지로 네이버 영화 리뷰 데이터를 사용할 것이고, 아래에서 가져올 수 있다. ratings_train.txt로 학습할 예정이다.

 

https://github.com/e9t/nsmc

 

1) 데이터 읽기

 

import numpy as np
import pandas as pd
import os

DATA_IN_PATH='./nsmc/'

train_data = pd.read_csv(DATA_IN_PATH+'ratings_train.txt', header=0, delimiter='\t', quoting=3)
train_data.head()

 

 

document가 영화평, label의 '1'이 긍정, '0'이 부정인데, 세번째 데이터처럼 학습데이터가 잘못되어 있기도 하다. 

 

2) 간단한 전처리

 

전처리를 잘 해야 성능이 좋게 나오지만, 여기서는 간단히 한글만 뽑아내고, konlpy 패키지의 Okt를 이용해 어간만 분리해 낸다.

 

from konlpy.tag import Okt
import re

// 감탄사, 조사들은 제거할 수 있도록 정의
stop_words = set(['은', '는', '이', '가', '하', '아', '것', '들', '의', '있', '되', '수', '보', '주', '등', '한'])

okt=Okt()

def preprocessing(review, okt, remove_stopwords = False, stop_words = []):
    // 문장에서 한글만 뽑아냄
    review_text = re.sub("[^가-힣ㄱ-하-ㅣ\\s]", "", review)
    
    // okt 라이브러리로 단어의 어간별로 분리
    word_review = okt.morphs(review_text, stem=True)
    
    // 감탄사와 조사 제거
    if remove_stopwords:
        word_review = [token for token in word_review if token not in stop_words]
        
    return word_review

 

위의 함수에 아래의 예문을 대입했을 때의 결과다.

 

'아 더빙 진짜 짜증나네요 목소리'

==>

['더빙', '진짜', '짜증나다', '목소리']

 

전체 데이터세트의 document에 위의 과정을 적용한다.

 

clean_train_review = []

for review in train_data['document']:
    if type(review) == str:
        clean_train_review.append(preprocessing(review, okt, True, stop_words))
    else:
        clean_train_review.append([])

 

제대로 바뀌었는 지 결과를 확인해 본다.

 

clean_train_review[:4]

[['더빙', '진짜', '짜증나다', '목소리'],
 ['흠', '포스터', '보고', '초딩', '영화', '줄', '오버', '연기', '조차', '가볍다', '않다'],
 ['너', '무재', '밓었', '다그', '래서', '보다', '추천', '다'],
 ['교도소', '이야기', '구먼', '솔직하다', '재미', '없다', '평점', '조정']]

 

3) 토큰화

 

자연어를 그대로 컴퓨터가 해석할 수 없으니 토큰화 과정을 거쳐야 한다. 일종의 단어 사전을 만들고 각 문장의 단어들이 몇번째 위치해 있는 지 숫자로 바꾸는 과정이다. DNN을 활용하기 위해서는 입력 데이터의 길이가 같아야 하므로, 한 문장의 최대 길이를 8로 맞추고 단어의 수가 모자란 문장은 '0'으로 padding  해 준다.

 

from tensorflow.python.keras.preprocessing.sequence import pad_sequences
from tensorflow.python.keras.preprocessing.text import Tokenizer

tokenizer = Tokenizer()

// 전체 순서를 만들고
tokenizer.fit_on_texts(clean_train_review)

// 단어의 위치를 숫자로 표시
train_sequence = tokenizer.texts_to_sequences(clean_train_review)

MAX_SEQUENCE_LENGTH = 8

// 최대 문장 길이는 8로 padding
train_inputs = pad_sequences(train_sequence, maxlen = MAX_SEQUENCE_LENGTH, padding='post')

// train label 만들기
train_labels = np.array(train_data['label'])

 

제대로 토큰화가 이루어졌는 지 확인해 보자

 

train_inputs[:4]

array([[  466,    20,   266,   666,     0,     0,     0,     0],
       [  606,     1,   220,  1461,    30,   971,   682,    24],
       [  395,  2458, 25061,  2325,  5682,     2,   227,    13],
       [ 6508,   110,  8143,   225,    61,     8,    31,  3623]],
      dtype=int32)

 

이제 학습할 준비는 모두 끝났다.

 

4) 모델링

 

모델링은 메모리를 엄청 차지하는 sparse한 형태의 one-hot-encoding 방법이 아니라, 주변 단어들이 나타날 확률을 나타내는 word embedding 방식을 적용한 후에 수행시킨다. word embedding에 대해서는 인터넷 찾아보면 되는데, word2vec, fasttext, glove 등의 방법이 있다고.. 간단한 원리만 알아 설명할 수는 없다.

 

// 10%는 Eval Data로 사용
TEST_SPLIT = 0.1
RNG_SEED = 13371447

// 데이터를 train과 eval로 분리
from sklearn.model_selection import train_test_split
input_train, input_eval, label_train, label_eval = train_test_split(train_inputs, train_labels, test_size=TEST_SPLIT, random_state=RNG_SEED)

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Flatten, Dense, Embedding, Conv1D

model = Sequential()

// Word Embedding
word_size = len(tokenizer.word_index)+1
model.add(Embedding(word_size, 128, input_length=MAX_SEQUENCE_LENGTH))

// DNN Training, Activation은 relu
model.add(Flatten())
model.add(Dense(1, activation='relu'))
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])

// epoch을 5로 해서 간단히 학습
model.fit(input_train, label_train, epochs=5)

 

분리해 놓았던 Eval Data로 테스트 해보면 간단히 작성했지만, 73% 이상의 정답율을 보인다.

 

model.evaluate(input_eval, label_eval)

469/469 [==============================] - 0s 588us/step - loss: 1.2454 - accuracy: 0.7359

 

5) 테스트

 

이제 임의의 테스트 데이터를 전처리 해서 결과를 본다.

 

def predict_review(sentence, model):
    // 테스트 문장을 전처리
    test_prepro = preprocessing(sentence, okt, True, stop_words)
    test_review = []
    test_review.append(test_prepro)
    
    // 전처리된 문장을 토큰화
    test_token = tokenizer.texts_to_sequences(test_review)
    test_seq = pad_sequences(test_token, maxlen = MAX_SEQUENCE_LENGTH, padding='post')
    ret = model.predict(test_seq)
    
    return ret
    
// 테스트
predict_review('앞으로 정준기 픽은 믿고 거른다.', model)

==> array([[0.5451854]], dtype=float32)

predict_review('이 감독 영화는 안본다', model)

==> array([[0.2593701]], dtype=float32)

 

사실 성능이 좋아보이지는 않는다. 명확하게 이야기한 것은 잘 구분해 내는 편이나 흔히 쓰지 않는 표현은 애매하게 판단한다. 충분히 학습데이터를 확보하거나 전처리에 좀 더 신경을 써야 하기도 하고, 간단히 신경망구조만 가져간 것을 다른 네트워크로 대체하면 좀 더 좋아질 수 있을 듯 하다. 

 

다만, 여기서는 자연어처리가 전체적으로 이루어지는 방법을 본 정도로 이해하면 된다.

 

※ Disclaimer: 본인은 AI알고리즘을 연구하지 않습니다. 의미 부분만 대충 파악하고 싶어서 정리하는 것이고 내용에 다소 때로는 상당히 오류가 있을 수 있으며, 아주아주 단편적 내용만 담고 있습니다. 참고만 하시고 각자 열심히 공부하시길 바랍니다 :D



댓글