뮤트 개발일지

AIFFEL 아이펠 29일차 본문

AIFFEL

AIFFEL 아이펠 29일차

박뮤트 2022. 2. 10. 18:38

딥러닝 레이어의 이해

Embedding 레이어와 RNN 레이어를 배워볼 것

 

희소 표현 Sparse Representation

사과, 바나나, 배를 컴퓨터에게 알려줄 때 첫 번째 요소로 모양(0: 둥글다, 1: 길쭉하다)을 나타내고, 두 번째 요소로 색상(0: 빨강, 1: 노랑)을 나타내면 사과: [0, 0], 바나나: [1, 1], 배: [0, 1]로 표현할 수 있을 것이다. 이렇게 벡터의 특정 차원에 단어 혹은 의미를 직접 매핑하는 방식을 희소 표현이라고 한다.

 

분산 표현 Distributed Representation

단어를 고정차원(예를 들어 256차원)의 벡터로 표현해보자. 그러나 어떤 차원이 특정한 의미를 가진다고 가정하지는 않고, 유사한 맥락에서 나타나는 단어는 그 의미도 비슷하다라는 가정을 해보자. 이것을 분포 가설distribution hypothesis라고 한다. 여기서 맥락이란 단순하게는 단어 좌우에 출현하는 다른 단어이다.

- 나는 밥을 먹는다.

- 나는 떡을 먹는다.

- 나는 _을 먹는다.

우리가 할 수 있는 것은 유사한 맥락에 나타난 단어들끼리는 두 단어 벡터 사이의 거리를 가깝게 하고, 그렇지 않은 단어들끼리는 멀어지도록 조정하는 것이다. 이런 방식으로 얻어지는 단어 벡터를 단어의 분산 표현이라고 한다. 벡터의 특정 차원이 특정 의미를 담고 있는 것이 아니라 의미가 벡터의 여러 차원에 분산되어 있으리라고 여기게 된다.

Embedding 레이어는 단어의 분산 표현을 구현하기 위한 레이어이다. "단어 n개를 쓰고 k차원으로 표현하라."라고 전달하면 컴퓨터가 알아서 n x k 형태의 분산 표현 사전을 만든다. 이것이 곧 weight이고 파라미터이다. 그리고 수많은 데이터를 통해 적합한 파라미터를 찾는다.

 

Embedding 레이어

Embedding 레이어(룩업 테이블lookup table이라고도 함)는 입력으로 들어온 단어를 분산 표현으로 연결해주는 역할을 한다. 단어가 룩업 테이블에 매핑되는 부분을 이해하기 위해서는 원-핫 인코딩을 먼저 알아야한다. 아래 링크 참조

https://www.kakaobrain.com/blog/6

 

카카오브레인

Unthinkable question makes impactful answer.

www.kakaobrain.com

원-핫 인코딩 그 자체는 단어에 순번을 매겨서 표현하는 방식에 지나지 않지만 Embedding 레이어와 결합하여 유용하게 사용할 수 있다.

 

여기서 잠깐, 원-핫 인코딩에 Linear 레이어를 적용하면 어떻게 될까?

import tensorflow as tf

vocab = {      # 사용할 단어 사전 정의
    "i": 0,
    "need": 1,
    "some": 2,
    "more": 3,
    "coffee": 4,
    "cake": 5,
    "cat": 6,
    "dog": 7
}

sentence = "i i i i need some more coffee coffee coffee"
# 위 sentence
_input = [vocab[w] for w in sentence.split()]  # [0, 0, 0, 0, 1, 2, 3, 4, 4, 4]

vocab_size = len(vocab)   # 8

one_hot = tf.one_hot(_input, vocab_size)
print(one_hot.numpy())    # 원-핫 인코딩 벡터를 출력
[[1. 0. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0.]
 [1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0. 0. 0.]]
distribution_size = 2   # 보기 좋게 2차원으로 분산 표현하도록 하죠!
linear = tf.keras.layers.Dense(units=distribution_size, use_bias=False)
one_hot_linear = linear(one_hot)

print("Linear Weight")
print(linear.weights[0].numpy())

print("\nOne-Hot Linear Result")
print(one_hot_linear.numpy())
Linear Weight
[[ 0.39116013  0.44643486]
 [-0.4685021   0.19086939]
 [ 0.12963527 -0.4503246 ]
 [-0.56285644  0.2066915 ]
 [-0.58632463 -0.12145126]
 [-0.73680854  0.5313132 ]
 [-0.09248024 -0.36496076]
 [-0.6848488  -0.41761476]]

One-Hot Linear Result
[[ 0.39116013  0.44643486]
 [ 0.39116013  0.44643486]
 [ 0.39116013  0.44643486]
 [ 0.39116013  0.44643486]
 [-0.4685021   0.19086939]
 [ 0.12963527 -0.4503246 ]
 [-0.56285644  0.2066915 ]
 [-0.58632463 -0.12145126]
 [-0.58632463 -0.12145126]
 [-0.58632463 -0.12145126]]

Linear 레이어의 Weight에서 단어 인덱스 배열 [0, 0, 0, 0, 1, 2, 3, 4, 4, 4]에 해당하는 행만 읽어온다.

각 단어를 원-핫 인코딩해서 Linear 연산을 하는 것이 파란선이라고 볼 수 있다. 원-핫 인코딩을 위한 단어 사전을 구축하고 단어를 사전의 인덱스로 변환해주면 Embedding 레이어를 사용할 수 있다는 것이다.

 

Embedding 레이어 선언

some_words = tf.constant([[3, 57, 35]])
# 3번 단어 / 57번 단어 / 35번 단어로 이루어진 한 문장입니다.

print("Embedding을 진행할 문장:", some_words.shape)
embedding_layer = tf.keras.layers.Embedding(input_dim=64, output_dim=100)
# 총 64개의 단어를 포함한 Embedding 레이어를 선언할 것이고,
# 각 단어는 100차원으로 분산 표현 할 것입니다.

print("Embedding된 문장:", embedding_layer(some_words).shape)
print("Embedding Layer의 Weight 형태:", embedding_layer.weights[0].shape)
Embedding을 진행할 문장: (1, 3)
Embedding된 문장: (1, 3, 100)
Embedding Layer의 Weight 형태: (64, 100)

* 주의사항: 딥러닝은 미분을 기반으로 동작하는데, Embedding 레이어는 단어를 대응시켜줄 뿐이라 미분이 불가능하다. Embedding 레이어는 입력에 직접 연결되게 사용해야 한다.

 

RNN

문장, 영상, 음성은 이미지 데이터와는 달리 순차적인Sequential 특성을 가진다.

데이터의 나열 사이에 연관성이 없어도 순차적인 데이터라고 할 수 있지만, 인공지능이 예측을 하기 위해서는 요소 간 연관성이 있어야 한다. 이런 순차 데이터를 처리하기 위해 고안 된 것이 RNN(Recurrent Neural Network)이다.

https://towardsdatascience.com/illustrated-guide-to-recurrent-neural-networks-79e5eb8049c9

 

Illustrated Guide to Recurrent Neural Networks

Understanding the Intuition

towardsdatascience.com

* RNN의 입력으로 들어가는 모든 단어만큼 Weight를 만드는 것이 아니다. (입력의 차원, 출력의 차원)에 해당하는 단 하나의 Weight를 순차적으로 업데이트하는 것이 RNN이다. 그렇다보니 한 문장을 읽고 처리해도 여러 번의 연산이 필요해 느리다는 단점이 있다.

위의 그림을 코드로 나타내보자.

sentence = "What time is it ?"
dic = {
    "is": 0,
    "it": 1,
    "What": 2,
    "time": 3,
    "?": 4
}

print("RNN에 입력할 문장:", sentence)

sentence_tensor = tf.constant([[dic[word] for word in sentence.split()]])

print("Embedding을 위해 단어 매핑:", sentence_tensor.numpy())
print("입력 문장 데이터 형태:", sentence_tensor.shape)

embedding_layer = tf.keras.layers.Embedding(input_dim=len(dic), output_dim=100)
emb_out = embedding_layer(sentence_tensor)

print("\nEmbedding 결과:", emb_out.shape)
print("Embedding Layer의 Weight 형태:", embedding_layer.weights[0].shape)

rnn_seq_layer = \
tf.keras.layers.SimpleRNN(units=64, return_sequences=True, use_bias=False)
rnn_seq_out = rnn_seq_layer(emb_out)

print("\nRNN 결과 (모든 Step Output):", rnn_seq_out.shape)
print("RNN Layer의 Weight 형태:", rnn_seq_layer.weights[0].shape)

rnn_fin_layer = tf.keras.layers.SimpleRNN(units=64, use_bias=False)
rnn_fin_out = rnn_fin_layer(emb_out)

print("\nRNN 결과 (최종 Step Output):", rnn_fin_out.shape)
print("RNN Layer의 Weight 형태:", rnn_fin_layer.weights[0].shape)
RNN에 입력할 문장: What time is it ?
Embedding을 위해 단어 매핑: [[2 3 0 1 4]]
입력 문장 데이터 형태: (1, 5)

Embedding 결과: (1, 5, 100)
Embedding Layer의 Weight 형태: (5, 100)

RNN 결과 (모든 Step Output): (1, 5, 64)
RNN Layer의 Weight 형태: (100, 64)

RNN 결과 (최종 Step Output): (1, 64)
RNN Layer의 Weight 형태: (100, 64)

위의 코드를 아래와 같이 바꿀 수도 있다.

lstm_seq_layer = tf.keras.layers.LSTM(units=64, return_sequences=True, use_bias=False)
lstm_seq_out = lstm_seq_layer(emb_out)

print("\nLSTM 결과 (모든 Step Output):", lstm_seq_out.shape)
print("LSTM Layer의 Weight 형태:", lstm_seq_layer.weights[0].shape)

lstm_fin_layer = tf.keras.layers.LSTM(units=64, use_bias=False)
lstm_fin_out = lstm_fin_layer(emb_out)

print("\nLSTM 결과 (최종 Step Output):", lstm_fin_out.shape)
print("LSTM Layer의 Weight 형태:", lstm_fin_layer.weights[0].shape)
WARNING:tensorflow:Layer lstm_2 will not use cuDNN kernels since it doesn't meet the criteria. It will use a generic GPU kernel as fallback when running on GPU.

LSTM 결과 (모든 Step Output): (1, 5, 64)
LSTM Layer의 Weight 형태: (100, 256)
WARNING:tensorflow:Layer lstm_3 will not use cuDNN kernels since it doesn't meet the criteria. It will use a generic GPU kernel as fallback when running on GPU.

LSTM 결과 (최종 Step Output): (1, 64)
LSTM Layer의 Weight 형태: (100, 256)

 

LSTM(Long Short-Term Memory)

기울기 소실 문제를 해결하기 위해 고안된 RNN 레이어이다.

RNN 네트워크 구조 특성상, 입력된 문장이 길수록 초기에 입력된 단어들의 미분값이 너무 작아지거나(vanishing gradient) 커지는 현상(exploding gradient)이 발생한다. 전자의 경우, 가중치 업데이터가 잘 안돼 학습이 거의 이뤄지지 않고, 후자의 경우, 가중치 업데이터가 너무 커 학습이 불안정해진다.

LSTM은 기본적인 바닐라 RNN보다 4배가 큰 Weight를 가지고 있다. (4배 깊은 RNN이라고 하기보다, 4종류의 서로 다른 Weight를 가진 RNN이라고 이해하는 것이 좋다.) 각 Weight들은 Gate라는 구조에 포함되어 어떤 정보를 기억하고, 어떤 정보를 다음 스텝에 전달할지 등을 결정한다.

Cell state: 긴 문장이 들어와도 Cell state를 통해 오래된 기억 또한 큰 손실 없이 저장한다.

그리고 Gate들이 Cell state에 정보를 추가하거나 빼주는 역할을 한다.

 

LSTM이 갖는 3개의 레이어

- Forget Gate Layer: cell state의 기존 정보를 얼마나 잊어버릴지 결정하는 gate

- Input Gate Layer: 새롭게 만들어진 cell state를 기존 cell state에 얼마나 반영할지 결정하는 gate

- Output Gate Layer: 새롭게 만들어진 cell state를 새로운 hidden state에 얼마나 반영할지 결정하는 gate

 

양방향(Bidirectional) RNN: 진행 방향이 반대인 RNN 2개를 겹쳐놓은 형태

사용하고자 하는 레이어를 tf.keras.layers.Bidirectional()로 감싸주면 된다.

번역기를 만들 때 많이 사용한다!

import tensorflow as tf

sentence = "What time is it ?"
dic = {
    "is": 0,
    "it": 1,
    "What": 2,
    "time": 3,
    "?": 4
}

sentence_tensor = tf.constant([[dic[word] for word in sentence.split()]])

embedding_layer = tf.keras.layers.Embedding(input_dim=len(dic), output_dim=100)
emb_out = embedding_layer(sentence_tensor)

print("입력 문장 데이터 형태:", emb_out.shape)

bi_rnn = \
tf.keras.layers.Bidirectional(
    tf.keras.layers.SimpleRNN(units=64, use_bias=False, return_sequences=True)
)
bi_out = bi_rnn(emb_out)

print("Bidirectional RNN 결과 (최종 Step Output):", bi_out.shape)
입력 문장 데이터 형태: (1, 5, 100)
Bidirectional RNN 결과 (최종 Step Output): (1, 5, 128)

순방향 Weight와 역방향 Weight를 각각 정의하므로, RNN의 2배 크기 Weight가 정의된다.

units를 64로 정의하고, 입력은 Embedding을 포함해 (1, 5, 100), 그리고 양방향에 대한 Weight를 거쳐나오니 출력은 (1, 5, 128)가 된다.

'AIFFEL' 카테고리의 다른 글

AIFFEL 아이펠 31일차  (0) 2022.02.16
AIFFEL 아이펠 30일차  (0) 2022.02.11
AIFFEL 아이펠 28일차  (0) 2022.02.10
AIFFEL 아이펠 27일차  (0) 2022.02.10
AIFFEL 아이펠 26일차  (0) 2022.02.04