본문 바로가기
파이썬 프로그래밍/Numpy 딥러닝

38.[RNN기초] 자연어 데이터는 어떻게 접근해야 할까?

by Majestyblue 2023. 9. 11.

드디어 연구를 끝냈다!

1. 들어가며

이전까지 학습한 내용의 데이터는 ( 데이터 갯수, 데이터 특징 )으로 구성된 csv 자료였거나 ( 데이터 갯수, 가로, 세로, 채널 )로 구성된 이미지 파일이었다. 

 

이번엔 인공신경망에 자언어 데이터를 입력할 수 없을까? 자연어란 인간이 사용하는 언어라고 쉽게 생각하자.

예를 들어 "as soon as", "as as soon", "soon as as" 세 개의 문장에 대해 문법적으로 어떤 것이 의미 있는지 구별할 수 있는 인공신경망을 제작한다고 하자

 

"as soon as" → 1

"as as soon" → 0

"soon as as" → 0

 

"as soon as"는 문법적으로 올바르고 영어에서 널리 사용된다. 이것은 어떤 특정한 사건이나 상태가 발생한 직후 또는 직후에 어떤 일이 발생할 것임을 나타낼 때 사용된다. 예를 들어, "집에 도착하면 바로 전화할게요.(I'll call you as soon as I get home. )" 이 문장에서 "as soon as"은 전화를 거는 행위와 집에 도착한 직후에 전화가 발생할 것임을 나타내는 이벤트를 연결한다. "As soon as possible" 로도 많이 사용하잖아?

 

그러나 "as as soon", "soon as as"는 전혀 문법적으로 맞지 않고 일상생활에서 사용하지 않는다. (영어는 내 전공이 아니여서 직접 영어선생님께 여쭈어 보았는데 그런말이 있을 수 있냐면서 크게 놀라신건 비밀)

이처럼 언어 데이터는 '순서'가 매우 중요하다고 할 수 있다.

 

 

 

2. 전처리하기

가. 토크나이징(Tokenizing)

위 데이터를 컴퓨터가 이해할 수 있도록 전처리를 해야 한다. 어떻게 전처리해야 할까? 이전에 배웠던 내용을 떠올려 보면, 데이터에는 항상 '특성(feature)'가 존재하였다. 먼저 단어 자체를 특성으로 만들어 보자. "as", "soon"과 같이 2개의 단어로 문장을 생성하므로 특성은 2개로 설정할 수 있다.

 

이처럼 언어를 '의미 있는 단위'로 나눌 수 있는데 이를 "토크나이징(Tokenizing)"이라고 한다. "토큰(Token)"이란 의미가 있는 문장의 최소 단위를 뜻한다. 쉽게 생각해 영어는 단어 단위로 토큰을 설정할 수 있다. 다만 한국어는 어마무시하게 복잡한데 여기선 다루지 않겠다.(한국어 언어 모델 연구가 힘든 이유...)

 

위 예시에서 토큰은 2개이며 "as", "soon" 으로 설정하고자 한다.

 

 

나. 워드 임베딩(Word Embedding)

설정한 토큰을 컴퓨터가 이해할 수 있는 자료로 변환해야 한다. 이 과정을 워드 임베딩(Word Embedding)이라고 하며 정말 많은 방법이 존재하지만, 여기서는 기존에 사용한 원 핫 인코딩(one hot encoding)을 사용하겠다.

 

"as" → [1, 0]

"soon" → [0, 1]

 

 

다. 입력 데이터 생성: 직렬로 이어 붙이기

특성을 선택하였으니 데이터를 생성해 보자. 데이터는 어떤 형태여야 할까? 자연스럽게 떠오른 생각은, 예를 들어 "as soon as" 처럼 [1, 0] [0, 1], [1, 0] → [1, 0, 0, 1, 1, 0] 으로 만들어 볼 수 있지 않을까? 즉, 직렬로 이어 붙이는 방법이다.

 

import numpy as np
from itertools import *
np.random.seed(230907)

dataset = ["as", "soon", "as"]
datalist = list(set(permutations(dataset, 3)))
# permutations는 중복이 허용됨. 중복을 제거하기 위해 set 함수를 이용함
print(datalist)

출력결과

[('as', 'as', 'soon'), ('soon', 'as', 'as'), ('as', 'soon', 'as')]

 

def serial_prep(x):
  total_prep = np.array([])
  for sentence in x:
    prep = np.array([])
    for word in sentence:
      if word == 'as':
        word = np.array([1, 0])
      elif word == 'soon':
        word = np.array([0, 1])
      prep = np.concatenate([prep, word])
    if total_prep.size == 0:
      total_prep = np.concatenate([total_prep, prep])
    else:
      total_prep = np.vstack([total_prep, prep])
  return total_prep

input = serial_prep(datalist)
print(input)

 

출력결과

[ [1. 0. 1. 0. 0. 1.]

  [0. 1. 1. 0. 1. 0.]

  [1. 0. 0. 1. 1. 0.] ]

 

 

이렇게 데이터를 설정한다면 그냥 특성이 6개인 데이터셋이 되어 버려 순서의 의미가 없어져버린다. 이게 무슨 뜻일까? 이해를 위해 5. 로지스틱 회귀 구현하기(실습) 에서 했던 "평균 득점(avg_scroe)", "리바운드 횟수(rebound)", "어시스트 횟수(asist)"에 따른 "드래프트 여부(draft)" 를 다시 살펴보자.

 

(12, 4)의 데이터를 입력 데이터(input) (12, 3), 목표 데이터(target) (12, 1)로 나누어 전처리 하였었다. 이 때 입력 데이터의 특성은 avg_score, rebound, asist였고 목표 데이터 특성은 draft 였다. 

 

흔히 wx + b로 불리우는 완전 연결(Fully connected) 기반 인공신경망 모델(DNN)은 입력 특성과 목표 특성간의 "관계"를 찾는 것이다. 예를 들어 평균 득점이 드래프트 여부와 정비례 하는지 아닌지 정량적으로 찾는 것이기 때문에 특성값들의 배열 순서에는 의미가 없다! 만약 입력 데이터 특성의 열을 바꾸어 asist, avg_score, rebound 로 입력해도 훈련에는 아무런 상관이 없다! 

 

하지만 우리가 처리하고자 하는 언어 데이터는 [1, 0]과 [0, 1]의 순서가 중요한데 신경망은 이를 이해할 수 있을까? 위의 데이터셋을 DNN에 입력하여 훈련하면 적은 데이터이므로 잘 훈련될 것이다. 그러나 토큰 수가 많아질 수록 잘 이해하지 못할 가능성이 높다. 다른 방법이 필요하다. 

 

 

 

라. 입력 데이터 생성: 병렬로 이어 붙이기

직렬로 쌓는 것이 아닌 병렬로 쌓는(stack)다면 어떨까? "as soon as"일 때 입력 데이터는 아래와 같다.

[ [ [1. 0.]

    [0. 1.]

    [1, 0] ] ] 

 

3차원 데이터(a, b, c)가 생성되는데 a는 문장의 개수(batch)가 될 것이고 b는 문장이 토큰을 몇 개 사용하였는지 알 수 있다. as soon as는 3개의 토큰으로 구성되어 있다는 것을 확인하자. c는 토큰의 특성이다. 

위 개념을 다시 정의하면 아래와 같다.

a : 데이터 개수, batch

b : 시간 순서, time steps → as, soon, as 의 순서를 가진다는 뜻이다.

c : 단어 길이, sequence length → "as soon as"는 2개의 단어로 구성되어 있다. 문장의 특성이라고 쉽게 생각하자.

→ (23.10.25.) 뭔가 설명이 좀 부족한 것 같다.

 

즉, 언어 데이터는 (batch, time steps, sequence length)로 구성되어 있다. 매우 중요하므로 꼭 기억하자!

→ (23.10.25.) (batch, sequence length(글자수), vocab size(토큰 수)) 가 더 적절한 표현이려나?

 

def parallel_prep(x):
  count = 0
  for sentence in x:
    prep = np.array([])
    for word in sentence:
      if word == 'as':
        word = np.array([1, 0])
      elif word == 'soon':
        word = np.array([0, 1])
      if prep.size == 0:
        prep = np.concatenate([prep, word])
      else:
        prep = np.vstack([prep, word])
    if count == 0:
      total_prep = prep.copy()
    else:
      total_prep = np.vstack([total_prep, prep])
    count = count + 1
  total_prep = np.reshape(total_prep, (-1, 3, 2)) # (batch, time steps, sequence length)
  return total_prep

input_RNN = parallel_prep(datalist)
print(input_RNN)
print(input_RNN.shape)

출력결과는 순서대로 ('as', 'as', 'soon'), ('soon', 'as', 'as'), ('as', 'soon', 'as')

[ [ [1. 0.]

    [1. 0.]

    [0. 1.] ]

  [ [0. 1.]

    [1. 0.]

    [1. 0.] ]

  [ [1. 0.]

    [0. 1.]

    [1. 0.] ] ]

(3, 3, 2)

 

 

 

마. 목표값(target) 생성

목표값은 심플하게 as soon as면 1, 그 외에는 0으로 설정하면 된다. 위의 입력 데이터 전처리에 대해 아래와 같이 설정하자.

target = np.array([[0],
                   [0],
                   [1]])

 

 

 

이제 준비는 끝났다. 다음 시간에 위의 입력 데이터를 처리하기 위한 인공신경망은 어떻게 되어야 하는지 알아보자.