파이토치 위키독스 공부하기
07-01 순환 신경망 RNN
RNN은 가장 기본적인 Sequence모델이다.
시퀀스들을 처리하기 위한 모델을 시퀀스 모델이라고 한다.
입력과 출력을 시퀀스 단위로 처리한다.
ex)번역기 : 입력은 문장(단어 시퀀스), 출력은 번역된 문장(단어 시퀀스)
1. 순환 신경망(RNN)
앞서 배운 신경망들안 은닉층에서 활성화 함수 지난 값은 출력층 방향으로만 진행 한다.(Feed Forward 신경망)
RNN은 이와 달리 결과값을 출력층 방향으로도 보내고, 그 다음 다시 은닉층 노드의 다음 계산 입력으로도 보낸다.
x는 입력층
y는 출력층
cell은 RNN 은닉층에서 활성화 함수를 통해 결과 내보내는 역할의 노드. 이전 값을 기억하려고 하는 메모리 역할을 하기 때문에 메모리셀, RNN셀이라고도 한다.
은닉층의 메모리 셀은 이전 시점에서의 은닉층 메모리셀 출력값을 자신의 입력으로 사용한다.(재귀적 활동)
은닉상태hidden state=메모리 셀이 출력층 방향 or 다음 시점 t+1의 자신에게 보내는 값
t 시점 (t=현재 시점) 의 메모리 셀은 t-1시점의 메모리 셀이 보낸 은닉 상태값을 t시점의 은닉 상태 계산을 위한 입력값
RNN에서는 뉴런이라는 단위말고 입력 벡터와 출력벡터를 사용하고 은닉층에서는 은닉 상태라는 표현을 사용한다.
RNN을 뉴런단위로 시각화하면 다음과 같다
위 그림은 입력 벡터 차원4, 은닉 상태 크기2, 출력층 출력 벡터 차원이 2인 RNN이 시점이 2일 때의 모습
(뉴런단위로 보면 입력층 뉴런 수4, 은닉층 뉴런 수2, 출력층 뉴런 수2)
one to many모델
하나의 이미지 입력에 대해 사진 제목(시퀀스 출력) 출력하는 이미지 캡셔닝 작업에 사용
many to one모델
입력문서가 긍정적인지 부정적인지 판별하는 감성 분류, 스팸 메일 분류에 사용
many to many
입력 문장으로 부터 대답문장 출력하는 챗봇, 번역기, 개체명 인식 등...
<RNN 수식 정의>
ht = 현재 시점t에서의 은닉 상태값
Wx = ht를 계산하기 위해 힙력층에서 입력값을 위한 가중치,
Wh = ht-1(이전 시점 은닉 상태값)을 위한 가중치
식으로 표현하면 다음과 같다
자연어 처리에서 RNN입력 xt는 대부분 단어 벡터이다.
이 차원을 d, 은닉상태 크기를 Dh일때 각 벡터와 행렬의 크기는 다음과 같다.
배치 크기가 1, d 와 Dh 두 값 모두 4라고 가정할 대 RNN의 은닉층 연산을 그림으로 표현
결과값인 yt를 계산하기 위한 활성화 함수로는 상황에 따라 다르다
예를들어 이진분류 해야하는 경우면 시그모이드 함수를 사용할 수 있고
다양한 카테고리 중에서 선택해야하는 문제라면 소프트맥스 함수 사용한다.
2. 파이썬으로 RNN 구현
numpy로 RNN층 구현
#07-01 2.
import numpy as np
timesteps = 10 # 시점의 수. (NLP에서는 보통 문장의 길이)
input_size = 4 # 입력의 차원. (NLP에서는 보통 단어 벡터의 차원)
hidden_size = 8 # 은닉 상태의 크기. 메모리 셀의 용량이다.
inputs = np.random.random((timesteps, input_size)) # 입력에 해당되는 2D 텐서
#초기 은닉 상태
hidden_state_t = np.zeros((hidden_size,)) # 0(벡터)으로 초기화. 크기 hidden_size
#초기 가중치와 편향
#입력에 대한 가중치.
Wx = np.random.random((hidden_size, input_size)) # (8, 4)크기의 2D 텐서 (은닉 상태 크기 x 입력 차원)
#은닉 상태에 대한 가중치
Wh = np.random.random((hidden_size, hidden_size)) # (8, 8)크기의 2D 텐서 (은닉 상태 크기 x 은닉 상태 크기)
#편향(bias)
b = np.random.random((hidden_size,)) # (8,)크기의 1D 텐서 (은닉 상태 크기)
#RNN동작#################################
total_hidden_states = []
# 메모리 셀 동작
for input_t in inputs: # 각 시점 입력값 입력
output_t = np.tanh(np.dot(Wx,input_t) + np.dot(Wh,hidden_state_t) + b) # Wx * Xt + Wh * Ht-1 + b(bias)
total_hidden_states.append(list(output_t)) # 각 시점의 은닉 상태의 값을 계속해서 축적
print(np.shape(total_hidden_states)) # 각 시점 t별 메모리 셀의 출력의 크기는 (timestep, output_dim)
hidden_state_t = output_t
total_hidden_states = np.stack(total_hidden_states, axis = 0)
print(total_hidden_states) # (timesteps, output_dim)의 크기. 이 경우 (10, 8)의 크기를 가지는 메모리 셀의 2D 텐서를 출력.
3. 파이토치의 nn.RNN()
nn.RNN()으로 RNN셀 구현
#07-01 3.
import torch
import torch.nn as nn
input_size = 5 # 입력의 크기
hidden_size = 8 # 은닉 상태의 크기
# 입력 텐서 정의 (batch_size, time_steps, input_size)
inputs = torch.Tensor(1, 10, 5)
#RNN셀을 만든다
cell = nn.RNN(input_size, hidden_size, batch_first=True)
#입력 텐서를 RNN셀에 입력
outputs, _status = cell(inputs)
#모든 시점의 은닉상태와 마지막 시점의 은닉상태 반환
4. 깊은 순환 신경망(Deep Recurrent Neural Network)
RNN도 다수의 은닉층을 가질 수 있다.
위의 그림은 순환 신경망에서 은닉층이 1개 더 추가되어 은닉층이 2개인 깊은 순환 신경망 모습이다.
깊은 순환 신경망을 파이토치로 구현하면 num_layers에 값을 전달하여 층을 쌓는다.
층이 2개인 깊은 순환 신경망 코드를 확인해보자
inputs=torch.Tensor(1,10,5)
cell=nn.RNN(input_size=5,hidden_size=8,num_layers=2,batch_first=True)
print(outputs.shape) # 모든 time-step의 hidden_state
print(_status.shape) # (층의 개수, 배치 크기, 은닉 상태의 크기)
#두번째 리턴값 크기가 층1개인 RNN셀과 달라졌다.
5.양방향 순환 신경망 (Bidirectional Recurrent Neural Network)
출력값을 예측할때 이전 시점 데이터뿐 아니라 이후 데이터로도 예측할 수 있다는 아이디어에 기반한다.
예를들어
Exercise is very effective at [ ] belly fat.
1) reducing 2) increasing 3) multiplying
에서 정답은 reducing인데 빈칸의 앞부분만 알아서는 아닌 belly fat을 알아야지 정답을 정하 수 있다.
즉 RNN이 과거 시점의 데이터 참고하여 정답을 예측하지만 향후 시점의 데이터에 힌트가 있을 수도 있다는 것이다.
그래서 이후 시점 데이터도 힌트로 활용하기 위해 고안된것이 양방향 RNN이다.
양방향 RNN은 하나의 출력값을 예측하기 위해 두개의 메모리 셀을 사용한다.
첫번째 메모리 셀은 앞 시점의 은닉 상태(Forward States)를 전달받아 현재 은닉상태를 계산한다.(주황색 메모리셀)
두번째 메모리 셀은 뒤 시점의 은닉 상태(Backward States)를 전달받아 현재 은닉 상태를 계산한다(초록색 메모리셀)
두 값 모두 출력층에서 출력값 예측하기 위해 사용된다.
양방향 RNN도 다수의 은닉층을 가질 수 있다. 아래의 그림은 은닉층이 2개인 깊은 양방향 순환 신경망이다
은닉층을 추가한다고 해서 모델의 성능이 좋아하지는것은 아니지만 은닉층을 추가하면 학습할 수 있는 양이 많아지지만 또한 반대로 훈현데이터도 많이 필요하다
양방향 순환 신경망을 파이토치로 구현하면 nn.RNN()의 bidirectional 값을 True로 하면 된다.
층이 2개인 깊은 순환 신경망이고 양방향일때의 출력을 확인해보자
inputs=torch.Tensor(1,10,5)
cell=nn.RNN(input_size=5,hidden_size=8,num_layers=2,batch_first=True, bidirectional=True)
outputs,_status=cell(inputs)
print(outputs.shape) #(배치 크기, 시퀀스 길이, 은닉상태 크기x2)은닉 상태 크기 값이 2배가 되었다.
print(_status.shape) #(층의 개수 x 2, 배치 크기, 은닉 상태의 크기)
#역방향 기준 첫번째 시점에 해당되는 시점의 출력값을 층의 개수만큼 쌓아 올린 결과값
07-02 장단기 메모리 LSTM
가장 단순한 형태의 RNN을 바닐라 RNN이라고 한고 이의 한계를 극복하기 위한 다양한 변형이 있는데
LSTM이 그 중 하나이다.
1.바닐라 RNN의 한계
짧은 시퀀스에 대해서만 효과를 보인다.
time stpe이 길어질 수록 앞 정보가 뒤로 충분히 전달되지 못한다.
정보량이 손실되어져 가는것이다.
이렇게 되면 중요한 정보가 시점 앞쪽에 위치할 경우 문제가 생기는
장기 의존성 문제(the problem of long term dependencies)가 나타난다.
ex) ''모스크바에 여행을 왔는데 건물도 예쁘고 먹을 것도 맛있었어. 그런데 글쎄 직장 상사한테 전화가 왔어. 어디냐고 묻더라구 그래서 나는 말했지. 저 여행왔는데요. 여기 ___''
여기서 ___ 단어를 예측해야하는데 모스크바다 앞에 위치해 있고 RNN이 기억력이 약하면 단어를 엉뚱하게 예측할것이다.
2. 바닐라 RNN 내부 열어보기
(편향b는 생략됨)
입력 xt와 ht-1가 각각의 가중치와 곱해져 메모리 셀의 입력이 된다.
이를 하이퍼볼릭탄젠트 함수 입력으로 사용하고
이 값은 은닉층의 출력인 은닉 상태가 된다.
3.LSTM(Long Short-Term Memory)
LSTM의 내부를 보자.
LSTM은 은닉층 메모리 셀에 입력 게이트, 망각 게이트, 출력 게이트 추가하여 기억해야할 것들을 정리한다.
LSTM은 은닉상태를 계산하는 식이 RNN보다 복잡하며 cell state라는 값을 추가했다.
(Ct = t시점의 셀 상태)
왼쪽에서 오른쪽으로 가는 굵은 선이 셀 상태이다.
셀상태도 이전 시점 셀 상태가 다음 시점 셀 상태 구하기 위한 입력으로 사용이 가능하다
은닉 상태값과 셀 상태값 구하기 위해 새로 추가 된 3개의 게이트(삭제 게이트, 입력 게이트, 출력 게이트)를 사용하는데
여기에는 공통적으로 시그모이드 함수가 존재한다. 그래서 0과1사이의 값으로 게이트를 조절한다.
σ = 시그모이드 함수
tanh = 하이퍼볼릭탄젠트 함수
Wxi,Wxg,Wxf,Wxo = xt와 함께 각 게이트에서 사용되는 4개의 가중치.
Whi,Whg,Whf,Who = ht−1와 함께 각 게이트에서 사용되는 4개의 가중치.
1) 입력 게이트
=현재 정보 기억 게이트.
it는 0~1사이의 값, gt는 -1~1사이의 값인데
이 값들로 기억할 정보의 양을 정한다.
2) 삭제 게이트
=기억 삭제 게이트.
현재 시점 t의 x값과 t-1의 은닉상태가 시그모이드 함수를 지난다.
그러면 0~1 값이 나오는데 이 값이 삭제 과정을 거친 정보의 양이다.
0에 가까울수록 정보가 많이 삭제된것. 1에 가까울수록 덜 삭제된것.
이 값으로 셀 상태를 구한다.
3) 셀 상태(장기 상태)
셀 상태 Ct(=장기 상태)를 구해보자.
(삭제 게이트에서 일부 기억을 잃은 상태이다.)
입력 게이트에서 구한 it,gt 값들에 대해 원소별 곱이 이번에 선택된 기억할 값이다.
입력 데이터에서 선택된 기억을 삭제 데이터 결과값과 더한 값이 셀 상태이다.
이 값은 다음 t+1 시점의 LSTM 셀로 넘겨진다.
삭제 게이트와 입력 게이트의 영향력을 이해해보자
만약 ft(삭제 게이트 출력값)이 0이라면
Ct-1(이전 시점 셀 상태값)이 현재 시점 셀 상태값 결정하기 위한 영향력이 0이 되면서
오직 입력 게이트 결과만이 Ct(현재 시점 셀 상태값)를 정하게 된다.
이는 삭제 게이트가 완전히 닫히고 입력 게이트를 연 상태를 의미한다.
반대로 it(입력 게이트 값)이 0이면 Ct는 Ct-1 값에만 의존한다.
이는 입력 게이트를 완전히 닫고 삭제 게이트만 연 상태를 의미한다.
결과적으로 삭제 게이트는 이전 시점의 입력을 얼마나 반영할지를 의미하고
입력 게이트는 현재 시점 입력을 얼마나 반영할지를 결정한다.
4) 출력 게이트와 은닉 상태(단기 상태)
출력 게이트는 현재 시점 t의 x값과 이전 시점 t-1의 은닉 상태가 시그모이드 함수를 지난 값이다.
해당 값은 현재 시점 t의 은닉 상태 ㅇ결정하는 일에 쓰인다.
은닉 상태(=단기 상태)는 장기 상태 값이 하이퍼볼릭 탄젠트 함수를 지나 -1~1사이 값이고
이 값은 풀력 게이트 값과 연산되며 값이 걸러지는 효과가 발생한다.
단기 상태의 값은 또한 출력층으로도 향한다.
4. 파이토치 nn.LSTM()
#RNN사용
nn.RNN(input_dim, hidden_size, batch_fisrt=True)
#LSTM사용
nn.LSTM(input_dim, hidden_size, batch_fisrt=True)
07-03 게이트 순환 유닛(GRU)
GRU(gated recurrent unit)는 LSTM의 장기 의존성 문제에 대한 해결책을 유지하면서 은닉상태를 업데이트하는 계산을 줄였다.
1. GRU
GRU는 업데이트 게이트와 리셋 게이트가 존재한다.
LSTM과 성능은 비슷하지만 속도가 더 빠르다. 둘 중 어느 하나가 더 낫다고 단정지을 수는 없다
2. 파이토치의 nn.GRU()
nn.GRU(input_dim, hidden_size, batch_fisrt=True)
07-04 문자 단위 RNN(Char RNN)
다대다 RNN구현해보자(품사 태깅, 개체명 인식 등..)
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np
input_str = 'apple'
label_str = 'pple!'
char_vocab = sorted(list(set(input_str+label_str)))
vocab_size = len(char_vocab)
#하이퍼파라미터 정의
input_size = vocab_size # 입력의 크기는 문자 집합의 크기
hidden_size = 5
output_size = 5
learning_rate = 0.1
#문자에 정수 인덱스 값 부여
#{'!': 0, 'a': 1, 'e': 2, 'l': 3, 'p': 4}
char_to_index = dict((c, i) for i, c in enumerate(char_vocab))
#정수로 부터 문자 얻을 수 있는 index_to_char
index_to_char={}
for key, value in char_to_index.items():
index_to_char[value] = key
#입력 데이터와 레이블 데이터의 각 문자들을 정수로 맵핑
x_data = [char_to_index[c] for c in input_str]
y_data = [char_to_index[c] for c in label_str]
print(x_data) #a,p,p,l,e
print(y_data) #p,p,l,e,!
# 배치 차원 추가
# nn.RNN()은 3차원 텐서를 입력받기 떄문에 배치 차원을 추가해준다.
x_data = [x_data]
y_data = [y_data]
#각 문자들을 원-핫 벡터로 변경
x_one_hot = [np.eye(vocab_size)[x] for x in x_data]
#입력데이터, 레이블데이터 텐서로 변경
X = torch.FloatTensor(x_one_hot)
Y = torch.LongTensor(y_data)
#RNN 모델 구현##################################
class Net(torch.nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(Net, self).__init__()
self.rnn = torch.nn.RNN(input_size, hidden_size, batch_first=True) # RNN 셀 구현
self.fc = torch.nn.Linear(hidden_size, output_size, bias=True) # 출력층 구현
def forward(self, x): # 구현한 RNN 셀과 출력층을 연결
x, _status = self.rnn(x)
x = self.fc(x)
return x
net = Net(input_size, hidden_size, output_size)
outputs=net(X)
criterion = torch.nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), learning_rate)
#TRAIN############################################
for i in range(100):
optimizer.zero_grad()
outputs = net(X)
loss = criterion(outputs.view(-1, input_size), Y.view(-1)) # view를 하는 이유는 Batch 차원 제거를 위해
loss.backward() # 기울기 계산
optimizer.step() # 아까 optimizer 선언 시 넣어둔 파라미터 업데이트
# 모델이 실제 어떻게 예측했는지 확인
result = outputs.data.numpy().argmax(axis=2) # 최종 예측값인 각 time-step 별 5차원 벡터에 대해서 가장 높은 값의 인덱스를 선택
result_str = ''.join([index_to_char[c] for c in np.squeeze(result)])
print(i, "loss: ", loss.item(), "prediction: ", result, "true Y: ", y_data, "prediction str: ", result_str)
07-05 문자 단위 RNN(Char RNN)-더 많은 데이터
더 많은 데이터 문자 단위 RNN 구현
import torch
import torch.nn as nn
import torch.optim as optim
sentence = ("if you want to build a ship, don't drum up people together to "
"collect wood and don't assign them tasks and work, but rather "
"teach them to long for the endless immensity of the sea.")
char_set = list(set(sentence)) # 중복을 제거한 문자 집합
char_dic = {c: i for i, c in enumerate(char_set)} # 각 문자에 정수 인덱스 부여(공백도 하나의 원소)
dic_size=len(char_dic)
#하이퍼파라미터 설정
hidden_size=dic_size
sequence_length=10
learning_rate=0.1
# 데이터 구성
x_data = []
y_data = []
for i in range(0, len(sentence) - sequence_length):
x_str = sentence[i:i + sequence_length]
y_str = sentence[i + 1: i + sequence_length + 1]
print(i, x_str, '->', y_str)
x_data.append([char_dic[c] for c in x_str]) # x str to index
y_data.append([char_dic[c] for c in y_str]) # y str to index
#one hot encoding. change to tensor
x_one_hot = [np.eye(dic_size)[x] for x in x_data]
X = torch.FloatTensor(x_one_hot)
Y = torch.LongTensor(y_data)
#모델 구현. 은닉층 2개
class Net(torch.nn.Module):
def __init__(self, input_dim, hidden_dim, layers): # 현재 hidden_size는 dic_size와 같음.
super(Net, self).__init__()
self.rnn = torch.nn.RNN(input_dim, hidden_dim, num_layers=layers, batch_first=True)
self.fc = torch.nn.Linear(hidden_dim, hidden_dim, bias=True)
def forward(self, x):
x, _status = self.rnn(x)
x = self.fc(x)
return x
net=Net(dic_size,hidden_size,2)
criterion=torch.nn.CrossEntropyLoss()
optimizer=optim.Adam(net.parameters(),learning_rate)
for i in range(100):
optimizer.zero_grad()
outputs = net(X) # (170, 10, 25) 크기를 가진 텐서를 매 에포크마다 모델의 입력으로 사용
loss = criterion(outputs.view(-1, dic_size), Y.view(-1))
loss.backward()
optimizer.step()
# results의 텐서 크기는 (170, 10)
results = outputs.argmax(dim=2)
predict_str = ""
for j, result in enumerate(results):
if j == 0: # 처음에는 예측 결과를 전부 가져오지만
predict_str += ''.join([char_set[t] for t in result])
else: # 그 다음에는 마지막 글자만 반복 추가
predict_str += char_set[result[-1]]
print(i,"==>",end="")
print(predict_str)
'개발 > AI' 카테고리의 다른 글
[논문 리뷰] A joint Sequence Fusion Model for Video Question Answering and Retrieval (1) | 2024.09.25 |
---|---|
[Pytorch] 08. 합성곱 신경망 (0) | 2024.08.06 |
[OpenCV] 컴퓨터 비전과 딥러닝 / Ch05 / 연습 문제 (2) | 2024.08.03 |
[OpenCV] 컴퓨터 비전과 딥러닝 / Ch04 / 연습 문제 (0) | 2024.08.03 |
[Pytorch] 06. 인공 신경망 (1) | 2024.08.02 |