09_순환신경망 RNN
Python/인공지능

09_순환신경망 RNN

728x90

CNN이 컴퓨터 비전 문제에 주로 사용되는 인공신경망이라면, 순환신경망은 자연어 처리 문제에 주로 사용된다. 조금더 정확히 말하면 시계열 데이터를 다루기에 쵲거화된 인공신경망이다

 

시계열 데이터란, 시간축 중심으로 현재 데이터가 앞,뒤 시간 데이터와 연관 관계를 가지고 있는 데이털르 의미한다. 주식 가격은 시계열 데이터의 예이다.

 

 

RNN은 중간에 순환되는과정이 추가되는 부분 말고는 ANN과 거의 비슷하다.

 

이 구조는 이전상태에 대한 정보를 일종의 메모리 형태로 저장할수 있다는 강력한 강점이 있다. 인간의 언어 같은 경우도 앞뒤 문맥에 의해 결정되므로 RNN을 적용하기 적합하다.

 

 

 

Vanishing Gradient Problem ( 경사도 사라짐 문제 )

 

 

1에서 받은 영향력은 시간이 지나가면서 점점 사라지게 된다. 이로인한 문제점은 결국 RNN은 장기기억력을 가지지 못해 먼 과거의 데이터를 고려해야하는 경우 RNN의 성능이 감소하게 된다.

 

 

LSTM(장/단기 기억 네트워크)

 

이런한 경사도 사라짐 문제를 해결하기 위해 발전된 RNN구조이다.

 

 

LSTM은 인풋게이트, 포겟 게이트, 아웃풋 게이트를 열고 닫으면서 시간 1의 영향력을 오랫동안 가져갈 수 있다!

 

셀에서는 일어나는 연산이 인풋 게이트와 포겟 게이트를 열고 닫아서 현재 시간과 이전 시간의 데이터를 가져갈지 잊어버릴지 결정하게 된다.

 

 

임베딩(Embedding)

 

머신러닝 알고리즘을 사용할때, 그중에서도 자연어 처리 문제를 다룰때 사용되는 기법이다. 머신러닝 알고리즘을 사용할때 데이터를 표현하는 일반적인 방법은 One-hot-Encoding인데, 이 원 핫 인코딩은 데이터의 표현형태가 Sparse하다는 문제가 있다. 

 

이러한 문제를 해결하기 위해 임베딩 개념이 들어왔다. 예를들어 10000 * 1로 되어있는 원핫 데이터를 250 * 250 과 같은 빽빽한 형태로 만들어준다.

 

이러한 과정을통해 연산량도 감소하고 분류가 학습이 잘되어있다면 유의미한 데이터 표현이 가능하게 된다.

 

 

Char-RNN

 

하나의 글자 Character을 RNN의 입력값으로 받고, RNN은 다음에 올 글자를 예측하는 문제

RNN의 타겟 데이터를 인풋 문장에서 한글자씩 뒤로 민 형태로 구성하면 된다. 만약 'HELLO'를 입력하고 싶다면, 

 

HE, EL, LL, LO 로 입력쌍을 만들면 된다.

 

해당 RNN은 가장 대표정인 RNN이다! 문장의 의미론적으로는 말이 안되지만 구조적으로는 그럴듯한 모습을 띄는 것을 알 수 있다. 프로그래밍 언어를 넣은 경우에도 문법적 패턴을 어느정도 그럴듯하게 가져가는것을 확인할 수 있다.

 

 

그럼한번 Char-RNN을 구현해보자.

 

 

# -*- coding: utf-8 -*-
# Char-RNN 예제 - Keras API를 이용한 구현
# Reference : https://github.com/tensorflow/docs/blob/master/site/en/tutorials/text/text_generation.ipynb

from __future__ import absolute_import, division, print_function, unicode_literals

from absl import app
import tensorflow as tf

import numpy as np
import os
import time

# input 데이터와 input 데이터를 한글자씩 뒤로 민 target 데이터를 생성하는 utility 함수를 정의합니다.
def split_input_target(chunk):
  input_text = chunk[:-1]
  target_text = chunk[1:]

  return input_text, target_text

# 학습에 필요한 설정값들을 지정합니다.
data_dir = tf.keras.utils.get_file('shakespeare.txt', 'https://storage.googleapis.com/download.tensorflow.org/data/shakespeare.txt')  # shakespeare
#data_dir = './data/linux/input.txt'  # linux
batch_size = 64      # Training : 64, Sampling : 1
seq_length = 100     # Training : 100, Sampling : 1
embedding_dim = 256  # Embedding 차원
hidden_size = 1024   # 히든 레이어의 노드 개수
num_epochs = 10

 

기본적으로 학습에 필요한 설정값들을 먼저 지정해준다.

 

 

# 학습에 사용할 txt 파일을 읽습니다.
text = open(data_dir, 'rb').read().decode(encoding='utf-8')
# 학습데이터에 포함된 모든 character들을 나타내는 변수인 vocab과
# vocab에 id를 부여해 dict 형태로 만든 char2idx를 선언합니다.
vocab = sorted(set(text))  # 유니크한 character 개수
vocab_size = len(vocab)
print('{} unique characters'.format(vocab_size))
char2idx = {u: i for i, u in enumerate(vocab)}
idx2char = np.array(vocab)

set을 활용해서 text에 들어간 유니크한 character을 가져간다.

 

# 학습 데이터를 character에서 integer로 변환합니다.
text_as_int = np.array([char2idx[c] for c in text])

# split_input_target 함수를 이용해서 input 데이터와 input 데이터를 한글자씩 뒤로 민 target 데이터를 생성합니다.
char_dataset = tf.data.Dataset.from_tensor_slices(text_as_int)
sequences = char_dataset.batch(seq_length+1, drop_remainder=True)
dataset = sequences.map(split_input_target)

# tf.data API를 이용해서 데이터를 섞고 batch 형태로 가져옵니다.
dataset = dataset.shuffle(10000).batch(batch_size, drop_remainder=True)

 

파이썬 딕셔너리를 활용해서 데이터를 다루기 위해서 int형으로 데이터를 변환한 후에 np.array 꼴로 만들어준다.

 

그 후 batch형태로 가져오는데, 한번을 sequences로 묶어서 가져와야 한다.

 

# tf.keras.Model을 이용해서 RNN 모델을 정의합니다.
class RNN(tf.keras.Model):
 def __init__(self, batch_size):
   super(RNN, self).__init__()
   self.embedding_layer = tf.keras.layers.Embedding(vocab_size, embedding_dim,
                                                    batch_input_shape=[batch_size, None])
   self.hidden_layer_1 = tf.keras.layers.LSTM(hidden_size,
                                             return_sequences=True,
                                             stateful=True,
                                             recurrent_initializer='glorot_uniform')
   self.output_layer = tf.keras.layers.Dense(vocab_size)

 def call(self, x):
   embedded_input = self.embedding_layer(x)
   features = self.hidden_layer_1(embedded_input)
   logits = self.output_layer(features)

   return logits
   
    def call(self, x):
   embedded_input = self.embedding_layer(x)
   features = self.hidden_layer_1(embedded_input)
   logits = self.output_layer(features)

   return logits

# sparse cross-entropy 손실 함수를 정의합니다.
def sparse_cross_entropy_loss(labels, logits):
  return tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(labels, logits, from_logits=True))

# 최적화를 위한 Adam 옵티마이저를 정의합니다.
optimizer = tf.keras.optimizers.Adam()

# 최적화를 위한 function을 정의합니다.
@tf.function
def train_step(model, input, target):
  with tf.GradientTape() as tape:
    logits = model(input)
    loss = sparse_cross_entropy_loss(target, logits)
  grads = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(grads, model.trainable_variables))

  return loss

 

다음은 케라스 모델을 이용해서 RNN모델 구조를 만들어준고 손실함수, 옵티마이저, 최적화 함수들을 만들어준다.

 

 

def generate_text(model, start_string):
  num_sampling = 4000  # 생성할 글자(Character)의 개수를 지정합니다.

  # start_sting을 integer 형태로 변환합니다.
  input_eval = [char2idx[s] for s in start_string]
  input_eval = tf.expand_dims(input_eval, 0)

  # 샘플링 결과로 생성된 string을 저장할 배열을 초기화합니다.
  text_generated = []

  # 낮은 temperature 값은 더욱 정확한 텍스트를 생성합니다.
  # 높은 temperature 값은 더욱 다양한 텍스트를 생성합니다.
  temperature = 1.0

  # 여기서 batch size = 1 입니다.
  model.reset_states()
  for i in range(num_sampling):
    predictions = model(input_eval)
    # 불필요한 batch dimension을 삭제합니다.
    predictions = tf.squeeze(predictions, 0)

    # 모델의 예측결과에 기반해서 랜덤 샘플링을 하기위해 categorical distribution을 사용합니다.
    predictions = predictions / temperature
    predicted_id = tf.random.categorical(predictions, num_samples=1)[-1,0].numpy()

    # 예측된 character를 다음 input으로 사용합니다.
    input_eval = tf.expand_dims([predicted_id], 0)
    # 샘플링 결과를 text_generated 배열에 추가합니다.
    text_generated.append(idx2char[predicted_id])

  return (start_string + ''.join(text_generated))

def main(_):
  # Recurrent Neural Networks(RNN) 모델을 선언합니다.
  RNN_model = RNN(batch_size=batch_size)

  # 데이터 구조 파악을 위해서 예제로 임의의 하나의 배치 데이터 에측하고, 예측결과를 출력합니다.
  for input_example_batch, target_example_batch in dataset.take(1):
    example_batch_predictions = RNN_model(input_example_batch)
    print(example_batch_predictions.shape, "# (batch_size, sequence_length, vocab_size)")

  # 모델 정보를 출력합니다.
  RNN_model.summary()

  # checkpoint 데이터를 저장할 경로를 지정합니다.
  checkpoint_dir = './training_checkpoints'
  checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt_{epoch}")

  for epoch in range(num_epochs):
    start = time.time()

    # 매 반복마다 hidden state를 초기화합니다. (최초의 hidden 값은 None입니다.)
    hidden = RNN_model.reset_states()

    for (batch_n, (input, target)) in enumerate(dataset):
      loss = train_step(RNN_model, input, target)

      if batch_n % 100 == 0:
        template = 'Epoch {} Batch {} Loss {}'
        print(template.format(epoch+1, batch_n, loss))

    # 5회 반복마다 파라미터를 checkpoint로 저장합니다.
    if (epoch + 1) % 5 == 0:
      RNN_model.save_weights(checkpoint_prefix.format(epoch=epoch))

    print ('Epoch {} Loss {:.4f}'.format(epoch+1, loss))
    print ('Time taken for 1 epoch {} sec\n'.format(time.time() - start))

  RNN_model.save_weights(checkpoint_prefix.format(epoch=epoch))
  print("트레이닝이 끝났습니다!")

  sampling_RNN_model = RNN(batch_size=1)
  sampling_RNN_model.load_weights(tf.train.latest_checkpoint(checkpoint_dir))
  sampling_RNN_model.build(tf.TensorShape([1, None]))
  sampling_RNN_model.summary()

  # 샘플링을 시작합니다.
  print("샘플링을 시작합니다!")
  print(generate_text(sampling_RNN_model, start_string=u' '))

if __name__ == '__main__':
  # main 함수를 호출합니다.
  app.run(main)

 

그후 main문안에서는 데이터 구조가 정상적인지 한번 체크한 후에 학습을 저장하면서 하고, 샘플링을 출력하는 과정을 겪게 한다.

 

프로그램을 돌려보면 잘 적용되는것을 볼 수 있다.

 

 

 

 

Gradient Clipping

계산과정에서 Gradient가 무한히 커지는 Exploding Gradient Problem이 발생할 수 있다. 이 Gradient Clipping 방법을 이용하면 손쉽게 해결할 수 있다.

 

 

 

Bidirectional Recurrent Neural networs(RNNS)

 

RNN은 이전 time-step을 고려한다. 하지만 미래의 time-step까지 고려한다면 좀 더 좋은 결과를 낼 수 있다. 예를 들어 푸른 하늘에 ㅁㅁ이 떠있다 라면 뒤의 ~이 떠있다 라는 정보까지 고려한다면 좀더 정확한 정보를 추출할 수 있다.

 

 

 

 

 

 

RNN같은 경우 기본적인 형태는 one to one 즉, 하나의 인풋을 받아서 1개의 아웃풋을 출력하는 형태이다. Char-RNN 등은 이 one to one 형태로 구성한 RNN이다.  1개의 time-step을 받아서 여러개의 time-step 아웃풋을 출력하는 one to many 아웃풋도 존재한다...! Image Captioning 등이 이 one to many 형태로 구성한 RNN이다.

 

또한 여러개의 time-step을 넣어 하나의 아웃풋을 출력하는 many to one 형태도 존재한다.

 

 

 

 

 

 

 

728x90

'Python > 인공지능' 카테고리의 다른 글

경사하강법 직접구현  (0) 2022.04.10
10_Text Detection(문자감지)  (0) 2022.03.26
08_개,고양이 구분 인공지능 구현  (0) 2022.03.18
06_CNN ILSVRC,CNN모델들  (0) 2022.03.18
05_CNN 기초  (0) 2022.03.18