Artificial Intelligence/Deep Learning

다양한 신경망 - 자연어 처리

DaTALK 2021. 12. 11.

1. 서론

* 다양한 신경망을 통해 여러 형태를 처리할 수 있는데, 앞서 배운 것은 신경망 모델을 통해 '이미지를 처리'하는 방법에 대해서 배웠다.

(하단 링크 포스팅 참조)

https://dataworld.tistory.com/51

 

다양한 신경망 - 이미지 처리

1. 서론 * 이미지 처리 기술 예시 : 얼굴 인식 카메라, 화질 개선(Super Resolution), 이미지 자동 태깅 (사진 안의 물체를 분류해서 tagging) * 이미지 처리를 위해 우리는 '데이터 전처리' -> '딥러닝 모델'

dataworld.tistory.com

* 이미지 이외 자연어도 처리가 가능하다. 이번 포스팅에서는 신경망을 통해 자연어를 처리하는 방법 및 실제 코드 실습까지 알아보려고 한다!

 

 

2. 자연어 처리 개론

* 예시로 '기계번역모델' & '음성인식'

- 자연어 주어진 문장을 번역해주거나 자연어 음성을 인식함

 

* 자연어 처리 과정 ( 1) -> 2) -> 3) )

1) 자연어 전처리(preprocessing)

2) 단어 표현(word embedding)

3) 모델 적용하기(modeling)

 

3. 처리과정 1) 자연어 전처리

* 원상태 그대로의 자연어는 전처리 과정이 필요하다

* 크게 3가지의 전처리 방법에 대해서 알아보려고 한다!

- Noise Cancelling

- Tokenizing

- StopWord Removal

 

[1] Noise Cancelling - 오류 교정

* 자연어 문장의 스펠링 체크 & 띄어쓰기 오류 교정

- 표준어에 맞게끔 변경

- slang, 줄임말 등 난이도 있는 작업

 

[2] Tokenizing - 토큰화

* 문장을 토큰(Token)으로 나눔

- 토큰은 어절, 단어 등으로 목적에 따라 다르게 정의

 

(사용자 토큰 정의 방식에 따라 딥러닝으로 묶을 수도 있음)

 

* 자연어 자체가 숫자 데이터가 아니기에 해당 문장을 수치 변환해주어야 함

- 문장 자체를 쪼개고 쪼갠 단위인 토큰을 수치로 넣어 의미를 부여하는 방식

 

[3] StopWord Removal - 불용어 제거

* 불필요한 단어를 의미하는 불용어(StopWord) 제거

 

감탄사 등등

 

* 예를 들어 뉴스기사에서 뉴스 카테고리를 분류할 때 '그러나', '그런데' 등등과 같은 단어는 중요하지 않음

- 따라서 이런 단어들을 불용어라 정의하고 제거함

 

[4] 수치 변화

{1} Bag of Words

* 자연어 데이터에 속해 있는 단어들의 가방

 

* 토큰화의 결과로 만들어진 token들 중 모두 사용되는 token들을 뽑아서 index를 부여

(index의 자체 의미는 없음)

 

반복되는 token은 1개로 간주하여 index mapping

 

{2} Token Sequence

* Bag of Words에서 단어에 해당되는 index로 변환

- 모든 문장의 길이를 맞추기 위해 기준보다 짧은 문장에는 padding을 수행

- 자연어 데이터에서 가장 큰 문장 길이를 갖는 해당 길이를 기준으로 나머지 길이들을 padding으로 맞춰줌

(유독 긴 문장이면 제외하는 경우도 있음)

 

3개로 맞추기 위해 index 4를 padding하여 추가한다.

 

[5] 실습 - 데이터 전처리 <영화 리뷰 긍정/부정 분류 RNN 모델>

{1} 개요

* 영화 리뷰 data를 바탕으로 감정 분석을 하는 모델을 학습시킨다

* 영화 리뷰와 같은 자연어 자료는 곧 단어의 연속적인 배열로서, 시계열 자료이다. 즉, 시계열 자료(연속된 단어)를 이용해 리뷰에 내포된 감정(긍정, 부정)을 예측하는 분류기를 만든다.

 

* dataset은 IMDB 영화 review dataset

- train set 5,000개 & test set 1,000개 준비

- label은 긍정/부정 두 가지

 

* 먼저 자연어 데이터를 RNN 모델의 입력으로 사용할 수 있도록 데이터 전처리를 수행한다.

 

{2} 준비된 자연어 데이터

* 이미 모든 단어를 index mapping하여서 숫자로 정리가 된 상태 (해당 실습 한정)

 

 

* 이미 준비된 bag of words (해당 실습 한정)

 

 

* (실습 한정) data_process.py

- 단어를 가져오고 index mapping & bag of words 생성하는 code

 

import json
import numpy as np
import tensorflow as tf
from keras.datasets import imdb
from keras.preprocessing import sequence

import logging, os
logging.disable(logging.WARNING)
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"

np_load_old = np.load
np.load = lambda *a,**k: np_load_old(*a, allow_pickle=True, **k)

# 데이터를 불러오고 전처리하는 함수입니다.

n_of_training_ex = 5000
n_of_testing_ex = 1000

PATH = "./data/"

def imdb_data_load():

    X_train = np.load(PATH + "X_train.npy")[:n_of_training_ex]
    y_train = np.load(PATH + "y_train.npy")[:n_of_training_ex]
    X_test = np.load(PATH + "X_test.npy")[:n_of_testing_ex]
    y_test = np.load(PATH + "y_test.npy")[:n_of_testing_ex]

    # 단어 사전 불러오기
    with open(PATH+"imdb_word_index.json") as f:
        word_index = json.load(f)
    # 인덱스 -> 단어 방식으로 딕셔너리 설정
    inverted_word_index = dict((i, word) for (word, i) in word_index.items())
    # 인덱스를 바탕으로 문장으로 변환
    decoded_sequence = " ".join(inverted_word_index[i] for i in X_train[0])

    
    print("첫 번째 X_train 데이터 샘플 문장: \n",decoded_sequence)
    print("\n첫 번째 X_train 데이터 샘플 토큰 인덱스 sequence: \n",X_train[0])
    print("첫 번째 X_train 데이터 샘플 토큰 시퀀스 길이: ", len(X_train[0]))
    print("첫 번째 y_train 데이터: ",y_train[0])
    
    return X_train, y_train, X_test, y_test

 

{3} 데이터 전처리 (앞 일부 과정 생략)

* 필요한 부분 import (위 data_process import 포함)

 

import json
import numpy as np
import tensorflow as tf
import data_process
from keras.datasets import imdb
from keras.preprocessing import sequence

 

* 학습용 및 평가용 데이터 불러오고 sample용 출력

 

X_train, y_train, X_test, y_test = data_process.imdb_data_load()

 

* padding: pad_sequences 함수 사용

- 해당 data의 크기를 최대 maxlen까지 맞춘다. 즉 이보다 작으면 이 크기에 맞게 padding을 수행한다는 뜻

- padding인자는 뒤에 붙일 지 , 앞에 붙일 지 결정하는 parameter ('post'는 뒤에 붙인다는 뜻)

 

sequence.pad_sequences(data, maxlen=300, padding='post')

 

- (실습) X 훈련용 데이터 & 실습용 데이터 모두 padding 수행

 

X_train = sequence.pad_sequences(-, maxlen=300, padding='post')
X_test = sequence.pad_sequences(-, maxlen=300, padding='post')

 

* 출력 결과 (훈련용 데이터)

- 첫번째 X_train data sample & 첫 번째 X_train data sample token index sequence, 첫 번째 X_train data sample token sequence length & 첫 번째 y_train data (접은글 펼치기)

: y_train data의 결과로 1이 나왔으며, 이는 positive의 label을 뜻한다.

 

더보기

첫 번째 X_train 데이터 샘플 문장: the as you with out themselves powerful and and their becomes and had and of lot from anyone to have after out atmosphere never more room and it so heart shows to years of every never going and help moments or of every and and movie except her was several of enough more with is now and film as you of and and unfortunately of you than him that with out themselves her get for was and of you movie sometimes movie that with scary but and to story wonderful that in seeing in character to of and and with heart had and they of here that with her serious to have does when from why what have and they is you that isn't one will very to as itself with other and in of seen over and for anyone of and br and to whether from than out themselves history he name half some br of and and was two most of mean for 1 any an and she he should is thought and but of script you not while history he heart to real at and but when from one bit then have two of script their with her and most that with wasn't to with and acting watch an for with and film want an

첫 번째 X_train 데이터 샘플 토큰 인덱스 sequence: [1, 14, 22, 16, 43, 530, 973, 2, 2, 65, 458, 2, 66, 2, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 2, 2, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2, 19, 14, 22, 4, 2, 2, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 2, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2, 2, 16, 480, 66, 2, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 2, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 2, 15, 256, 4, 2, 7, 2, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 2, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2, 56, 26, 141, 6, 194, 2, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 2, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 2, 88, 12, 16, 283, 5, 16, 2, 113, 103, 32, 15, 16, 2, 19, 178, 32]

첫 번째 X_train 데이터 샘플 토큰 시퀀스 길이: 218

첫 번째 y_train 데이터: 1

 

- 패딩 추가한 첫 번째 X_train data sample token index sequence (접은글 펼치기)

: 결과로 뒤에 300 length를 맞추기 위해 0을 추가한 것을 확인할 수 있다

 

더보기

패딩을 추가한 첫 번째 X_train 데이터 샘플 토큰 인덱스 sequence: [ 1 14 22 16 43 530 973 2 2 65 458 2 66 2 4 173 36 256 5 25 100 43 838 112 50 670 2 9 35 480 284 5 150 4 172 112 167 2 336 385 39 4 172 2 2 17 546 38 13 447 4 192 50 16 6 147 2 19 14 22 4 2 2 469 4 22 71 87 12 16 43 530 38 76 15 13 2 4 22 17 515 17 12 16 626 18 2 5 62 386 12 8 316 8 106 5 4 2 2 16 480 66 2 33 4 130 12 16 38 619 5 25 124 51 36 135 48 25 2 33 6 22 12 215 28 77 52 5 14 407 16 82 2 8 4 107 117 2 15 256 4 2 7 2 5 723 36 71 43 530 476 26 400 317 46 7 4 2 2 13 104 88 4 381 15 297 98 32 2 56 26 141 6 194 2 18 4 226 22 21 134 476 26 480 5 144 30 2 18 51 36 28 224 92 25 104 4 226 65 16 38 2 88 12 16 283 5 16 2 113 103 32 15 16 2 19 178 32 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 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 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 0]

 

4. 처리과정 2) 단어 표현(Word Embedding)

[1] 워드 임베딩(Word Embedding) 의미 & 필요성 & 개념

= 단순하게 Bag of Words의 인덱스로 정의된 토큰들에게 의미를 부여하는 방식

-> token의 특징을 설명하기 위해 vector 사용

- vector를 사용하면 vector간의 유사도를 구할 수 있으며 & 연산도 가능

(즉 서로 vector간의 유사도가 높다면 token끼리 의미가 깊다 + 두 vector를 계산해서 한 vector가 나오면 두 단어를 합쳐서 새로운 의미의 단어를 만들어냄)

 

- Bag of Words에서는 각 toekn의 index가 순서대로 되어있는, 의미가 없는 형태였다. 해당 단점을 보완하기 위해 워드 임베딩 사용 

- CNN 모델에서 특징을 추출할 수 있듯이, 자연어에서도 단어들을 추출하여 의미가 있는 값으로 변환

 

 

- Embedding table을 참조하여 해당 index의 vector를 가져온다.

(예를 들어, '어머니'에 해당되는 index 0의 [1,3,0,-2,0,0]을 가져옴)

- 위의 예를 보면, 어머니와 아버지 token vetor 사이의 유사성이 어머니와 친구 token vector 사이의 유사성보다 더 높음을 알 수 있다.

- 유사도는 다양한 metric 사용 가능 (cos 유사도 등등..)

 

[2] 기존 MLP 모델과 자연어 처리의 한계

 

* CNN의 경우 이미지 데이터를 모델의 input으로 집어넣기 위해 2차원 데이터를 1차원 데이터로 변환하였다.

- 예로 6x6 image를 1x36의 input으로 변환 

 

* 자연어 문장을 기존 MLP 모델(다층 퍼셉트론 모델)에 적용시키기에는 한계가 존재. token 간의 순서와 관계를 적용할 수 있는 model은 없을까?

- 예를 들어 각 token으로 이루어진 문장을 model의 input으로 집어넣기 위해서는 각 token내의 여러 숫자들을 넣을 때 이 숫자들을 묶어서 model의 input으로 넣는 방법이 필요하다 & 또한 token간의 관계를 넣기에도 한계

- 해결: RNN 모델

 

5. 처리과정 3) 모델 적용하기(modelling)

[1] 자연어 분류를 위한 순환 신경망 RNN(Recurrent Neural Network)

{1} 개요

* 기존 퍼셉트론 계산과 비슷하게 X 입력 데이터를 받아 Y를 출력

 

 

- X에 한 개의 embedding vector를 input으로 집어넣는다

- RNN의 결과물로 vector 형태나 0~1사이의 값등 원하는 값으로 출력됨

 

{2} 입출력구조

* 출력값을 두 갈래로 나누어 신경망에게 '기억'하는 기능을 부여한다

 

 

- X의 input RNN 결과 Y가 생성되고 X의 기억정보로 생성된 h가 또 다른 vector를 RNN의 input 값으로 집어넣을 때 같이 input으로 들어간다.

- 문장 데이터에 대해서 학습할 때 그 전에 사용되었던 token에 대한 기억이 남아있다.

 

{3} 순환 신경망 기반 자연어 분류 예시

 

실제로는 각 token의 embedding vector가 input으로 들어감

 

- token 차례대로 RNN 모델에 들어가면서 그 이전의 RNN 출력값과 함께 input으로 들어가 최종적으로 Y생성

- 생성된 Y를 마지막 FC를 넣어서 분류 결과 긍정인지, 부정인지 (위 예의 경우 2개의 label) 분류모델을 생성할 수 있다.

- 도중에 생성된 Y가 아니라, 최종적으로 생성된 단 하나의 Y만 고려한다

 

{4} 정리

 

 

- bag of words (token sequence)인 전처리된 결과를 embedding하여 token의 특징을 찾아낸다.

- embedding 결과의 여러 embedding vector가 RNN 모델을 거치고 최종 한 개의 Y 출력

- 출력된 Y가 활성함수를 거쳐 최종적으로 모델의 출력값이 탄생!

 

- 활성함수의 경우 multi-class는 softmax, binary-class는 sigmoid, 회귀는 activation 없이 loss MSE 등등을 이용해서 처리 가능

 

* 사용 예)

- Image captioning

: 주어진 image data에서 CNN을 통해 feature를 분류한다. 이를 RNN에 적용하여 image의 label로 사진을 설명하는 문장(caption)을 출력하게 할 수 있다. (RNN은 sequence 형태의 label로 만들 수 있음 - 앞서 언급한 여러 RNN 모델의 output을 이어 만든 문장 형태)

 

 

- chatbot

: 자연어 문장이 input으로 주어졌을 때 자연어 문장을 출력

 

* 그 외로, RNN의 단점을 보완하는 LSTM, GRU, TRANSFO ~ 등등 발전되고 있다.

 

6. 실습 - 모델 학습 및 예측,평가 <영화 리뷰 긍정/부정 분류 RNN 모델>

(저번 전처리한 데이터를 가지고 이어서 실습)

[1] RNN 모델 구현 (+word embedding)

* 이제, 직접 RNN 모델을 구현해본다.

- 일반적으로 RNN 모델은 입력층으로 Embedding layer를 먼저 쌓고, RNN layer를 몇 개 쌓은 다음, 이후 마지막으로 Dense layer을 쌓는다.

 

* embedding layer

: 들어온 문장을 단어 embedding하는 layer

- 해당 layer를 통해 token별 embedding vector를 생성한다

 

tf.keras.layers.Embedding(input_dim, output_dim, input_length)

 

- input_dim: 들어올 단어의 개수

- output_dim: 결과로 나올 embedding vector의 크기(차원), 즉 특성을 몇 개 짜리로(만들어진 token combination) 만들 것인지를 결정한다.

- input_length: 들어오는 word vector의 크기 (해당 실습의 경우 max_len값을 300으로 설정함)

 

 

 

* RNN layer

 

tf.keras.layers.SimpleRNN(units)

 

- units: layer의 node 수

 

* 실습

- SimpleRNN을 사용하여 RNN layer를 쌓고, node의 수는 5로 설정한다.

- input_dim = 1000: 단어의 개수

- embedding_vector_length는 32로 설정

- 설정한 max_len의 300을 마지막 인자로 설정

-> 총 1,000개의 단어를 조합하여 32개의 word(token) combination으로 만들고 각 token의 길이는 300으로 통일한다.

 

- 5개의 node로 RNN 모델을 돌린다

- 0과 1로 출력되는 label이기에 Dense의 출력 node 수는 1로 설정하며, 긍정/부정 binary 구분 활성함수는 sigmoid를 사용한다

 

max_review_length = 300
embedding_vector_length = 32

model = tf.keras.models.Sequential([
    tf.keras.layers.Embedding(1000, embedding_vector_length, input_length = max_review_length),
    tf.keras.layers.SimpleRNN(@), #5 units
    tf.keras.layers.Dense(1, activation='sigmoid')
    ])
    
print(model.summary())

 

- 모델 info 출력 결과

 

출력결과 - model summary

 

- embedding(Embedding)

: parameter로 총 32,000개(1000x32)의 단어가 쓰였고, shape으로 (embedding_vector_length, max_review_length) = (300,32)

 

- simple_rnn (SimpleRNN)

: 총 5개의 node를 가진 RNN 모델이므로 shape size는 5가 나온다

 

- dense(Dense)

: 최종 결과 1개의 node를 갖는 Dense layer - shape size는 1

 

[2] RNN 모델 학습

* binary 분류로는 loss함수로 binary_crossentropy 함수를 사용한다

* adam optimizer & 정확도 metrics 측정하며

* fit 함수로 모델을 학습한다. - 총 3 epochs 반복, 매 epoch 결과 한 줄씩 출력하는 형태 (verbose 값 2)

 

model.compile(loss = 'binary_crossentropy', optimizer = 'adam', metrics = ['accuracy'])

model_history = model.fit(X_train, y_train, epochs = 3, verbose = 2)

 

학습 출력 결과 (3 epochs)

 

* 총 5,000개의 학습 데이터를 사용하였고

- epoch를 반복할 수록 loss의 값은 점점 감소, accuracy 값은 점점 증가함을 확인할 수 있다

 

[3] RNN 모델 평가 & 예측

* 평가: evaluate() 사용

- feature X, label Y 입력

- loss & metrics 값을 출력한다.

 

model.evaluate(X, Y)

 

* 예측: predict() 사용

- X data의 예측 label 값을 출력한다

 

model.predict(X)

 

* 실습 (epochs는 5로 설정)

 

loss, test_acc = model.evaluate(X_test, y_test, verbose = 0)

predictions = model.predict(X_test)

print('\nTest Loss : {:.4f} | Test Accuracy : {}'.format(loss, test_acc))
print('예측한 Test Data 클래스 : ',1 if predictions[0]>=0.5 else 0)

 

- label 값이 1 or 0만으로 판단되기에 0의 prediction 값이 50%, 즉 0.5 이상이면 0으로 판단되게 하는 코드를 작성하였다.

(sigmoid 함수 특징)

 

평가 & 예측 출력 결과

 

 

 

 

 

 

- 출처 - 2021 NIPA/AI 기본/응용 교육과정

 

'Artificial Intelligence > Deep Learning' 카테고리의 다른 글

다양한 신경망 - 이미지 처리  (0) 2021.12.08
텐서플로우(TensorFlow) (+Keras)  (0) 2021.12.07
퍼셉트론 (perceptron)  (0) 2021.12.06

댓글