-
[모두를 위한 딥러닝 시즌2][Lab 12](2) RNN TensorFlow모두를 위한 딥러닝 시즌2 2021. 11. 17. 16:43
tf keras를 사용해 RNN을 직접 코드로 구현해보자.
두 가지 방법
- 특정 셀을 선언하고 이를 LOOP 하는 방식
- 이 두가지를 결합한 api를 활용하는 방식
입력으로 전달 받는 데이터는 (betch size x sequence length x input dimension)으로 전 처리되어야 한다.
sequence가 1인 데이터를 전처리해서 RNN을 적용하는 코드는 다음과 같다.
hidden size가 2인 RNN을 만들어서 전처리해준 데이터들을 넣어준다.
이때 outputs 변수는 전체 sequence에 해당하는 hidden state 값들을 가지고 있고 states 변수는 마지막 hidden state 값만 가지고 있기 때문에 shape이 다르다.
Unfolding to n sequences
이번엔 sequence가 1이 아닌 데이터로 실습해보자.
Batching input
sequence가 5인 데이터, 3개로 구성된 mini batch를 RNN으로 처리해보자.
sequence들이 모인 mini batch를 만들고 전처리 해준 후 RNN에 넣어준다.
RNN은 다음과 같이 다양하게 활용할 수 있다.
one to many 경우는 특정 이미지를 받아 cation을 생성하는 image caption에 사용된다.
many to many는 Neural Machine Translation, 형태소 분석에 사용되고
many to one은 Sentiment Classification에 사용될 수 있다.
many to one
자연어 처리에서 RNN을 many to one 방식으로 활용하는 예:
문장을 단어 단위로 분해 할 수 있는데 이를 sentence를 word 단위로 Tokenization 했다고 한다.
마지막 Token을 읽었을 때 polarity를 classification 방식으로 활용한다.
sentence를 word 단위로 Tokenization 했을 때 Token인 word는 숫자가 아니기 때문에 RNN으로 처리할 수가 없다.
그래서 Token을 numeric vector로 바꿔주는 Embedding layer가 존재한다.
Embedding layer를 거친 Token을 순서대로 RNN이 읽고, 마지막 Token을 읽었을 때 나온 출력과 정답 간의 loss를 구할 수 있다. 이 loss를 기반으로 Backpropagation을 진행해 RNN을 학습할 수 있다.
각각 word에 그에 대한 긍/부정 값이 주어져 있다. (1은 긍정 0은 부정)
word를 character의 sequence로 간주했을 때 각각의 sequence에 길이가 다르기 때문에 '<pad>' Token을 넣어서 길이를 맞춰준다.
print의 출력을 보면 각각의 Token이 int 인덱스와 mapping이 되어있는 것을 볼 수 있다.
Token dictionary를 기반으로 word를 int 인덱스에 sequence로 변환한다.
그다음 변환한 데이터를 pad_sequence()를 통해서 max_sequence 변수의 값만큼의 길이로 변환한 데이터를 padding 한다.
이제 RNN을 many to one 방식으로 활용할 수 있다.
Embedding layer는 Token을 One Hot 백터로 표현한다.
SimpleRNN은 sequence의 마지막 Token을 input으로 받아 처리한 결과를 리턴한다.
마지막으로 dense를 이용해서 classification 해준다.
모델의 출력과 정답을 비교해주는 Loss 함수를 만들어준다.
이제 모델의 성능을 확인할 수 있다.
Training의 history를 그려 보았을 때 Training Loss가 점점 떨어지는 것을 확인할 수 있다.
many to one stacking
recurrent NN을 여러 개 쌓은 RNN
multi layered RNN/stacked RNN
자연어 처리에서 stacked RNN은 input에 가까운 RNN의 hidden state가 semantic information보다 syntactic information을 더 잘 encoding 하고 있다.
반대로 output에 가까운 RNN의 hidden state는 semantic information을 syntactic informatio보다 더 잘 encoding 하고 있다.
이러한 이유로 자연어 처리에서 stacked RNN은 다양하게 활용되고 있다.
sentence classification을 stacked RNN에서 mant to one방식을 사용해 모델링해보자.
먼저 token dictionary를 만들어준다.
token dictionary를 기반으로 sentence를 int 인덱스의 sequence로 변환시킨다.
sentence를 char로 Tokenization 했기 때문에 sequence의 길이가 이전 실습보다 훨씬 길다.
이렇게 긴 sequence를 다루기 위해서 stacked RNN 구조를 사용해준다.
sentence의 sequence가 다른 문제를 해결하기 위해서 pad_sequences()을 사용해서 길이를 맞춰준다.
이렇게 하면 stacked RNN 구조로 batch 단위의 데이터를 처리해줄 수 있다.
이제 RNN을 두 개 활용하는 stacked RNN을 many to one 방식으로 활용할 수 있다.
이제 Loss function을 만든다.
이 실습에선 classification 문제를 풀고 있기 때문에 cross entropy loss를 계산하는 함수를 활용한다.
다음의 코드로 모델을 training 해준다.
마지막으로 모델의 성능을 확인한다.
train accuracy가 100%이고, epoch이 진행됨에 따라 train loss가 감소하는 걸 확인 할 수 있다.
many to many
자연어 처리에서 형태소 분석과 같은 sequence tagging에 활용될 수 있다.
many to many는 sequence를 구성하고 있는 각각의 token에 대해서 모두 출력을 내주는 구조다.
주어진 sentence를 word 단위로 Tokenization 해준뒤 token으로 이루어진 sequence를 RNN이 각각의 token을 읽고 해당 token이 어떤 품사인지 파악한다.
RNN이 각각의 token을 순서대로 읽을때마다 출력을 내고 이를 정답과 비교하여 각 token마다 loss를 계산한다.
모든 token에 대한 loss의 평균을 구한다. 이를 sequence loss라고 한다.
이 sequence loss로 RNN을 Backpropagation 해준다.
데이터의 sequence를 구성하는 token들 중에서 길이를 맞추기 위해 존재할 뿐인 pad token에 대해서는 loss를 계산하지 않겠다는 masking 개념이 등장한다.
token dictionary를 만들고 정답이 품사의 sequence로 주어져 있기 때문에 품사를 int 인덱스로 mapping하고 있는 dictionary 또한 만들어야 한다.
masking을 위해서 각각의 sentence가 몇 개의 word로 Tokenization 되었는지를 계산한 sentence의 유효한 길이를 계산한다.
padding한 부분에 대한 masking 정보를 담고 있는 masking도 계산해준다.
RNN을 many to many 방식으로 활용하는 모델을 완성해준다.
masking을 생성하고 이를 mini batch loss에 반영하는 형태로 Loss function을 만들어준다.
many to many bidirectional
RNN을 모두 단방향으로만 활용하면 각각의 token을 RNN이 읽을 떄 정보의 불균형이 생긴다.
many to many bidirectional은 sequence를 순서대로 읽는 forward RNN, 역으로 읽는 backward RNN을 두어 문제점을 해결할 수 있다.
token을 forward RNN, backward RNN이 읽고 각각의 RNN을 합친다.
Weight와 bios는 모든 token의 hidden state에서 동일하게 적용한다.
각 token마다 bidirectional RNN의 출력과 정답을 비교하여 loss를 계산하고 masking을 활용하여 데이터 간의 길이를 맞추기 위한 pad token을 제외한 실제 데이터의 유효한 token들에 대해서 loss를 계산한다.
이전 예제와 동일하게 데이터를 전처리 해준다.
이전에 다루었던 예제에 Bidirectional()만 추가해줘서 Bidirectional RNN을 many to many 방식으로 활용할 수 있다.
Training 부분은 이전 예제와 동일하다.
Seq2Seq
sequence의 입력을 받고 sequence의 출력을 하는 모델
Seq2Seq의 대표적인 특징이 Encoder-Decoder 구조이다.
Encoder 부분에서 입력을 받아 C로 요약하고 이것을 Decoder에 넣어 새롭게 RNN을 하여 출력을 얻어낸다.
실제 예시:
Encoder에서 각 신경망의 스텝마다 단어가 하나씩 들어간다. 각 단어는 embedding된 후 벡터로 바뀐 뒤 입력 값으로 사용된다.
Decoder에서는 start라는 특정 token을 사용하여 문장의 시작을 나타낸다. 각 스텝마다 출력이 나오고 이 출력은 다음 스텝의 입력 값으로 사용된다. end라는 특정 token은 문장의 마지막을 나타낸다.
sources의 문장을 targets으로 번역하는 Dataset으로 실습을 진행해보자.
sources vocabulary와 targets vocabulary를 생성해준다.
sources을 전처리하고
targets을 전처리해준다. 이때 입력의 처음과 끝에 시작과 종료 token을 붙여주고 결과의 끝에 종료 token을 붙여준다.
최대 길이를 정해준다.
이제 tf.data를 사용해 Pipeline을 완성해준다.
gru 알고리즘을 활용하여 Encoder-Decoder의 기반을 만들어주자.
Encoder와 Decoder의 class를 생성한다.
Encoder와 Decoder의 차이점은 Decoder에는 마지막에 출력을 위한 fully connected layer가 추가된다는 것이다.
이제 학습을 진행해보자.
Decoder 학습 시에 이전 스텝의 정답 단어들을 다음 스텝의 입력으로 넣어주는 Teacher forcing이 필요하다.
Teacher forcing 예시:
Y predict에 I가 나와야 하지만 a가 나온 후, 다음 입력 값으로 들어가게 되면 예측값이 틀리게 된다.
Y가 어떤 것을 예측하던지 I와 I feel이 들어가게 된다.
출력값 확인:
indexing과 padding을 진행한 후,
encoder의 hidden 값을 decoder의 첫 hidden 값으로 입력을 받는다.
그리고 문장을 시작하는 token을 decoder의 입력으로 하여 예측을 시작한다.
각 값들을 target의 최대 길이만큼 반복하면서 id 값을 예측한다.
id가 eos token으로 예측하면 예측을 중지하고 결과 값을 돌려준다.
예측 결과 확인
'모두를 위한 딥러닝 시즌2' 카테고리의 다른 글
[모두를 위한 딥러닝 시즌2][Lab 12] RNN (0) 2021.11.17 [모두를 위한 딥러닝 시즌2][Lab 11](2) mnist cnn keras sequential/functional/subclassing/ensemble/keras eager (0) 2021.11.16 [모두를 위한 딥러닝 시즌2][Lab 11] Convolution Neural Networks (0) 2021.11.15 [모두를 위한 딥러닝 시즌2][Lab 10] Relu, Weight Initialization, Dropout, Batch Normalization (0) 2021.11.10 [모두를 위한 딥러닝 시즌2][Lab 9] Neural Nets for XOR (0) 2021.11.09