![순환신경망(RNN)](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcflcUt%2Fbtq8729L4Kc%2FtNIOLkaxawKVEvatD0dMJ0%2Fimg.jpg)
-time series-
이때까지 인공신경망에 사용한 데이터는 각 샘플이 독립적이였다고 가정하였습니다. 이런 가정 때문에 우리는 에포크마다 전체 샘플을 섞은 후에 모델 훈련을 진행 할 수 있었습니다. 하지만 우리가 다루는 데이터가 모두 독립이라는 보장은 없습니다. 예를들어 한시간전 주식의 가격과 지금의 주식 각격은 독립이라고 말할 수있을까요 ? 즉 한시간적 가격이 지금의 가격에 아무런 영향을 미치지 않았을까요? 확신할 순 없지만 두 가격은 깊은 연관이 있을 것입니다. 이처럼 일정 시간 간격으로 배치된 데이터를 시계열 데이터(time series)라 합니다.
시계열 데이터를 포함하여 샘플에 순서가 있는 데이터를 일반적으로 순차 데이터(sequential data)라 부릅니다. 대표적인 순차 데이터의 예가 텍스트 입니다. 당연하게도 글의 앞뒤가 맞아여 우리가 제대로 이해할 수 있는 문장이 탄생하는 것이겠지요. 예를들어 다음과 같이 3개의 단어로 이루어진 순차 데이터가 있다고 합시다. Do it Deeplearning 이 데이터의 처리 단위가 단어라면 총 타임 스텝은 3입니다. 만약 처리 단위가 글자라면 총 타임 스템은 18입니다.(띄워쓰기 포함)
순환 신경망에서는 층이나 뉴런을 셀(cell)이라고 부릅니다. 또한 셀의 출력을 은닉 상태(hidden state)라 부릅니다.
순환층의 셀에서 입력과 이전 타임 스텝의 은닉 상태를 통해 어떤 계산이 일어날까요? 그림을 통해 살펴보겠습니다.
입력 X와 이전 타임 스텝의 은닉 상태 H(p)에 곱해지는 2개의 가중치 W(x),W(h) 그리고 절편 b를 함께 표시하였습니다.
-순환 신경망의 정방향 계산-
-정방향 계산에 필요한 입력과 가중치의 구조-
만약 입력 데이터 X의 크기가 ( m(샘플개수) , nf(특성개수) )라면 입력에 곱해지는 가중치 W(1x)의 크기는 (nf(특성 개수) , nc(셀의 개수) )
이 두개가 곱해진 XW(1x)의 크기는 (m,nc)입니다.
XW(1x)의 크기가 (m,nc) 이므로 Z1 , H 그리고 이전 은닉 상태인 H(p)의 크기도 (m,nc)일 것 입니다. 두행렬을 곱한 H(p)W(1h)의 크기도 (m,nc) 일 것 입니다. 즉, 이를통해 H(p)와 곱해지는 가중치 W(1h)의 크기는 (nc,nc)가 됨을 알 수 있습니다.
이제 순환층을 통과한 뒤 출력층을 살펴 보겠습니다.
출력층으로 전달 되는 H의 크기가 (m,nc)이므로 이와 곱해지는 가중치 W2의 크기는 (nc,n_classes)가 될것 입니다. 이번에는 이진분류를 다루므로 n_classes는 1이 되겠지요. 즉 , 최종 결과 HW(2) 의 크기는 (m,1)이 될 것 입니다.
자 이제 순환층과 출력층에서의 가중치에대한 크기를 알아보았는데 절편의 크기는 어떻게 될까요 ? 간단합니다.!
절편은 각 층의 셀마다 하나씩 필요하므로 , b1의 크기는 (nc,1)이고 b2의 크기는 (n_classes,)입니다.
- 순환 신경망의 역방향 계산 -
미분의 연쇄 법칙을 사용해 차근차근 알아봅시다. 먼저 전체적인 구조를 그림으로 살펴보면 ,
- 가중치 W2에 대한 손실함수의 도함수 -
출력층의 가중치 W2에 대해 손실함수 L을 미분하기 위해 연쇄법칙을 이용하면 도함수는 다음과 같습니다.
H에 대한 Z2의 도함수를 알아 봅시다.
Z1에 대한 H의 도함수를 구합시다.
먼저 tanh의 구조를 알아야 됩니다. 그림과 같습니다.
그럼 지금까지 구했던 도함수를 순환신경망에 표시하여 한눈에 알보겠습니다.
지금까지의 내용을 바탕으로 Z1에 대한 손실 함수 L의 도함수는 연쇄 법칙을 적용하여 다음과 같이 쓸 수 있습니다.
자 이제 이전 타임 스템의 은닉상태에대해 알아 봅시다.
가중치 W(1h)에 대한 Z1의 도함수를 구해봅시다.
안타깝게도 이렇게 도함수를 구하면 안됩니다. 그이유는 Hp가 W(1h)에 관한 수식이기 때문입니다.
다음은 현재 타임 스템의 은닉 상태와 이전 타임 스텝의 은닉 상태를 포함한 순환 신경망의 계산 과정을 동시에 나타낸 것입니다.
그림을 보면 이전 타임 스텝의 은닉상태도 W(1h)에 의해 영향을 받고 있다는 것을 알 수 있습니다.
즉, Hp는 이전 타입 스텝의 입력 Xp와 두 스텝 이전의 은닉상태 Hpp에 의해 계산됩니다. 그래서 수식을 다시 써보면 다음과 같습니다.
조금더 살펴 봅시다.
위 식을 일반화 해보면 다음과 같습니다.
즉, 가중치 W(1h)와 tanh함수의 도함수인 1-H**2 이 곱해지면서 더해짐을 알 수 있습니다.
이제 가중치 W(1x)에 대한 Z1의 도함수를 구해봅시다.
이제 감이 오시나요? 똑같습니다. Hp도 W1x에 관한 함수입니다. 그래서 위와 똑같은 방식으로 식을 전개해 일반화 시키면 다음과 같습니다.
자 이제 마지막으로 b1에 대한 Z1도함수를 구해봅시다.
자 이렇게해서 순환신경망에서 역전파를 구현하기 위한 공식을 소개해 봤습니다.
※정리
지금까지 출력층의 가중치에 대한 도함수를 구하고, 그다음 순환 셀 직전 까지의 역전파된 그레디언트를 구했습니다. 마지막에 순환셀의 가중치에 대한 Z1의 도함수까지 구해봤습니다. 가중치 W(1x),W(1h),b1을 업데이트 하기 위한 최종 그레디언트는 다음과 같이 구현됩니다.
자 이제 순환신경망을 만들고 텍스트를 분류해봅시다.!
훈련세트와 검증세트를 준비합니다.
1.텐서플로에서 IMDB 데이터 세트 불러오기
import numpy as np
from tensorflow.keras.datasets import imdb
(x_train_all , y_train_all),(x_test,y_test) = imdb.load_data(skip_top=20,num_words=100)
(nym_words 매개변수는 훈련에 사용할 단어의 개수를 지정합니다.)
2.훈련세트의 크기 확인
print(x_train_all.shape,y_train_all.shape)
===============================
(25000,) (25000,)
3. 훈련세트의 샘플 확인하기
print(x_train_all[0])
==========================
[2, 2, 22, 2, 43, 2, 2, 2, 2, 65, 2, 2, 66, 2, 2, 2, 36, 2, 2, 25, 2, 43, 2, 2, 50, 2, --]
4. 훈련세트에서 2 제외하기
> 숫자 2는 어휘 사전에 없는 단어. 추가로 0과 1은 각각 패딩과 글의 시작을 나타내는 데 사용. 이숫자들을 제외하고 훈련세트 만듭니다.
for i in range(len(x_train_all)):
x_train_all[i] = [w for w in x_train_all[i] if w > 2]
print(x_train_all[0])
===========================================
[22, 43, 65, 66, 36, 25, 43, 50, 35, 39, 38, 50, 22, 22, 71, 87, 43, 38, 76, 22, 62,
5.어휘 사전 내려받기
> 훈련 세트를 쉽게 이해할 수 있게 영단어로 바꾸어 봅시다.
word_to_index = imdb.get_word_index()
word_to_index['movie']
===============
17
6.훈련세트의 정수를 영단어로 변환하기
index_to_word = {word_to_index[k]: k for k in word_to_index}
for w in x_train_all[0]:
print(index_to_word[w-3],end=' ')
==================================
film just story really they you just there an from so there film film were great just so much f
7. 훈련 샘플의 길이 확인하기
print(len(x_train_all[0]),len(x_train_all[1]))
==================
59 32
샘플의 길이가 다르면 모델을 제대로 훈련시킬 수 없습니다.
8.훈련 세트의 타깃 데이터 확인하기
print(y_train_all[:10])
=======================
[1 0 0 1 0 0 1 0 1 0]
9.검증 세트를 준비합니다.
> permutation()을 이용하여 섞은뒤 훈련세트 검증세트 분리
np.random.seed(777)
random_index = np.random.permutation(25000)
x_train = x_train_all[random_index[:20000]]
y_train = y_train_all[random_index[:20000]]
x_val = x_train_all[random_index[20000:]]
y_val = y_train_all[random_index[20000:]]
샘플의 길이 맞추기
> 샘플의 왼쪽에 0을 추가하여 길이를 맞춘다.
1. 텐서플로로 샘플의 길이 맞추기
> 최대 길이를 100으로 설정하여 길이가 동일한 2개의 배열 만든다.
from tensorflow.keras.preprocessing import sequence
maxlen= 100
x_train_seq = sequence.pad_sequences(x_train,maxlen=maxlen)
x_val_seq = sequence.pad_sequences(x_val,maxlen = maxlen)
2. 길이 조정한 뒤 샘플 확인
print(x_train_seq.shape,x_val_seq.shape)
================================
(20000, 100) (5000, 100)
샘플 원-핫 인코딩 하기
> 훈련데이터를 준비하기 위한 마지막 작업은 정수데이터를 원-핫 인코딩 하는 것입니다.
1. 텐서플로로 원-핫 인코딩하기
from tensorflow.keras.utils import to_categorical
x_train_onehot = to_categorical(x_train_seq)
x_val_onehot = to_categorical(x_val_seq)
2.원-핫 인코딩으로 변환한 변수의 크기를 확인
> 20,000개의 샘플이 100차원으로 원-핫 인코딩됨을 알 수 있습니다.
print(x_train_onehot.shape)
=====================
(20000, 100, 100)
순환 신경망 클래스 구현하기
1. __init__() 메서드 수정하기
> 은닉층의 개수 대신 셀 개수를 입력 받는다. 셀에 필요한 가중치를 추가선언 합니다. 또한 타입 스텝을 거슬러 그레디언트를 전파하려면 순환층의 활성화 출력을 모두 가지고 있어야 하므로 변수 h를 선언합니다.
class RecurrentNetwork:
def __init__(self, n_cells=10, batch_size=32, learning_rate=0.1):
self.n_cells = n_cells # 셀 개수
self.batch_size = batch_size # 배치 크기
self.w1h = None # 은닉 상태에 대한 가중치
self.w1x = None # 입력에 대한 가중치
self.b1 = None # 순환층의 절편
self.w2 = None # 출력층의 가중치
self.b2 = None # 출력층의 절편
self.h = None # 순환층의 활성화 출력
self.losses = [] # 훈련 손실
self.val_losses = [] # 검증 손실
self.lr = learning_rate # 학습률
2. 직교 행렬 방식으로 가중치 초기화 하기
> 합성곱 신경망에서 글로럿 초기화 방식으로 가중치를 초기화하며 가중치 초기화의 중요성에대해 알아 보았습니다. 순환신경망에서는 직교행렬 초기화(orthogonal initialization)라는 방식을 사용합니다. 이 방식은 순환 셀에서 은닉 상태를 위한 가중치가 반복해서 곱해질 때 너무 커지거나 작아지지 않도록 만들어 줍니다.
def init_weights(self, n_features, n_classes):
orth_init = tf.initializers.Orthogonal()
glorot_init = tf.initializers.GlorotUniform()
self.w1h = orth_init((self.n_cells, self.n_cells)).numpy() # (셀 개수, 셀 개수)
self.w1x = glorot_init((n_features, self.n_cells)).numpy() # (특성 개수, 셀 개수)
self.b1 = np.zeros(self.n_cells) # 은닉층의 크기
self.w2 = glorot_init((self.n_cells, n_classes)).numpy() # (셀 개수, 클래스 개수)
self.b2 = np.zeros(n_classes)
3.정방향 계산 구현.
> 각 타임 스텝의 은닉 상태를 저장하기 위해 변수 h를 초기화 합니다. 역전파 과정을 진행할 때 이전 타임 스텝의 은닉상태를 사용합니다. 첫번재 타임 스텝의 이전 은닉 상태는 없으므로 변수 h의 첫번째 요소에 0으로 채워진 배열을 추가합니다.
> 그 다음 스왑 함수를 이용해 입력x의 첫 번째 배치 차원과 두번째 타임 스텝 차원을 바꿉니다.
차원을 바꾸는 이유는 정방향 계산을 할때는 한 샘플으 모든 타임 스텝을 처리하고 그 다음에 샘플을 처리하는 방식이 아닙니다. 미니 배치 안에 있는 모든 샘플의 첫번째 타임스텝을 한번에 처리하고 두번째 타임스텝을 처리합니다. 이 과정을 손쉽게 구현하기위해 배치차원과 타입스탭 차원을 바꾸었습니다.
> 마지막으로 각 샘플의 모든 타임스탭에 대한 정방향 계산을 수행합니다. 셀에서 계산된 은닉상태는 변수 h에 순서대로 추가.
def forpass(self, x):
self.h = [np.zeros((x.shape[0], self.n_cells))] # 은닉 상태를 초기화합니다.
# 배치 차원과 타임 스텝 차원을 바꿉니다.
seq = np.swapaxes(x, 0, 1)
# 순환 층의 선형 식을 계산합니다.
for x in seq:
z1 = np.dot(x, self.w1x) + np.dot(self.h[-1], self.w1h) + self.b1
h = np.tanh(z1) # 활성화 함수를 적용합니다.
self.h.append(h) # 역전파를 위해 은닉 상태 저장합니다.
z2 = np.dot(h, self.w2) + self.b2 # 출력층의 선형 식을 계산합니다.
return z2
4.역방향 계산 구현하기
def backprop(self, x, err):
m = len(x) # 샘플 개수
# 출력층의 가중치와 절편에 대한 그래디언트를 계산합니다.
w2_grad = np.dot(self.h[-1].T, err) / m
b2_grad = np.sum(err) / m
# 배치 차원과 타임 스텝 차원을 바꿉니다.
seq = np.swapaxes(x, 0, 1)
w1h_grad = w1x_grad = b1_grad = 0
# 셀 직전까지 그래디언트를 계산합니다.
err_to_cell = np.dot(err, self.w2.T) * (1 - self.h[-1] ** 2)
# 모든 타임 스텝을 거슬러가면서 그래디언트를 전파합니다.
for x, h in zip(seq[::-1][:10], self.h[:-1][::-1][:10]):
w1h_grad += np.dot(h.T, err_to_cell)
w1x_grad += np.dot(x.T, err_to_cell)
b1_grad += np.sum(err_to_cell, axis=0)
# 이전 타임 스텝의 셀 직전까지 그래디언트를 계산합니다.
err_to_cell = np.dot(err_to_cell, self.w1h) * (1 - h ** 2)
w1h_grad /= m
w1x_grad /= m
b1_grad /= m
return w1h_grad, w1x_grad, b1_grad, w2_grad, b2_grad
순환 신경망 모델 훈련시키기
> 준비한 데이터세트에 순환신경망 클래스 적용
1. 순환 신경망 모델 훈련시키기
> 셀 개수는 32개 , 배치 크기는 32개 , 학습률 0.01 , 에포크 횟수는 20
rn = RecurrentNetwork(n_cells=32, batch_size=32, learning_rate=0.01)
rn.fit(x_train_onehot, y_train, epochs=20, x_val=x_val_onehot, y_val=y_val)
2.훈련 , 검증 세트에 대한 손실 그래프 그리기
plt.plot(rn.losses)
plt.plot(rn.val_losses)
plt.show()
3. 검증세트 정확도 평가
rn.score(x_val_onehot, y_val)
===================
0.6872
텐서플로로 순환 신경망 구현하기
> 텐서플로에서 가장 기본적인 순환층은 SimpleRNN클래스입니다.
1. 순환 신경망에 필요한 클래스 임포트하기
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, SimpleRNN
2.모델 만들기
> 사용할 셀 개수 지정하고 Sequential 모델의 첫 번째 층이므로 입력 차원을 지정. 타임 스텝의 길이가 100이고 원-핫 인코딩 크기가 100이므로 입력 크기는 (100,100) 이다. 이진분류이므로 1개의 유닛을 가진 Dense층을 마지막에 추가합니다
model = Sequential()
model.add(SimpleRNN(32, input_shape=(100, 100)))
model.add(Dense(1, activation='sigmoid'))
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
simple_rnn (SimpleRNN) (None, 32) 4256
_________________________________________________________________
dense (Dense) (None, 1) 33
=================================================================
Total params: 4,289
Trainable params: 4,289
Non-trainable params: 0
summary메서드를 통해 출력된 결과를 살펴보면 입력은 원-핫 인코딩된 100차원 벡터이고 셀 개수가 32개 이므로 W(1x) 행렬 요소의 개수는 (100X32)가 된다. 또한 W(1h)행렬의 요소 개수는 (32X32)가 될것 입니다. 마지막으로 셀마다 하나씩 총 32개의 절편이 있기때문에 순환층에 필요한 전체 파라미터의 개수는 4256개 입니다.
3. 모델 컴파일하고 훈련시키기
> 기본적인 확률적 경사하강법 적용시키고 이진 분류이므로 손실함수는 binary_crossentropy를 쓰겠습니다.
model.compile(optimizer='sgd', loss='binary_crossentropy', metrics=['accuracy'])
history = model.fit(x_train_onehot, y_train, epochs=20, batch_size=32,
validation_data=(x_val_onehot, y_val))
4. 훈련 검증 세트에 대한 손실 그래프와 정확도 그래프 그리기
5. 검증 세트 정확도 평가하기
loss, accuracy = model.evaluate(x_val_onehot, y_val, verbose=0)
print(accuracy)
==================
0.68978998142
성능이 좋아지긴 했지만 아직 기대에 미치지 못하는거 같습니다. 좀더 향성된 성능을 위해 임베딩을 알아보겠습니다.
성능이 기대에 못미치는 가중 큰 이유는 텍스트 데이터를 원-핫 인코딩으로 전처리 하기 때문입니다. 이게 어떤 문제를 일으킬까요 ? 원-핫 인코딩을 사용하면 입력데이터 크기와 사용할 수 있는 영단어의 수가 제한되는 문제가 있습니다. 또한 이 방식은 단어 사이의 관계를 잘 표현하지 못합니다. 이런 문제점을 보완하기 위해 단어 임베딩을 사용합니다. 단어 임베딩은 단어를 고정된 길이의 실수 벡터로 임베딩합니다.
-임베딩층으로 순환 신경망 모델 성능 높이기-
1.Embedding 클래스 임포트하기
from tensorflow.keras.layers import Embedding
2. 훈련데이터 준비하기
(x_train_all, y_train_all), (x_test, y_test) = imdb.load_data(skip_top=20, num_words=1000)
for i in range(len(x_train_all)):
x_train_all[i] = [w for w in x_train_all[i] if w > 2]
x_train = x_train_all[random_index[:20000]]
y_train = y_train_all[random_index[:20000]]
x_val = x_train_all[random_index[20000:]]
y_val = y_train_all[random_index[20000:]]
3.샘플 길이 맞추기
maxlen=100
x_train_seq = sequence.pad_sequences(x_train, maxlen=maxlen)
x_val_seq = sequence.pad_sequences(x_val, maxlen=maxlen)
4.모델 만들기
model_ebd = Sequential()
model_ebd.add(Embedding(1000, 32))
model_ebd.add(SimpleRNN(8))
model_ebd.add(Dense(1, activation='sigmoid'))
model_ebd.summary()
Model: "sequential_1"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
embedding (Embedding) (None, None, 32) 32000
_________________________________________________________________
simple_rnn_1 (SimpleRNN) (None, 8) 328
_________________________________________________________________
dense_1 (Dense) (None, 1) 9
=================================================================
Total params: 32,337
Trainable params: 32,337
Non-trainable params: 0
________________________
5.모델 컴파일 후 훈련시키기
model_ebd.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
history = model_ebd.fit(x_train_seq, y_train, epochs=10, batch_size=32,
validation_data=(x_val_seq, y_val))
6.검증 세트 정확도 평가하기
loss, accuracy = model_ebd.evaluate(x_val_seq, y_val, verbose=0)
print(accuracy)
==========
0.8003
'DNN > 딥러닝' 카테고리의 다른 글
Do it 딥러닝을 읽고.. (0) | 2021.07.22 |
---|---|
합성곱 신경망 (CNN) (0) | 2021.07.07 |
다중 분류 신경망 (0) | 2021.07.06 |
다층 신경망 (0) | 2021.07.06 |
훈련 노하우 (0) | 2021.07.05 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!