참고: 전에 작성했던 CNN에 대한 설명 (Tensorflow)
import torch
import torch.nn as nn
import torch.nn.functional as F
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
: number of input features
: number of output features
: convolution kernel size
: convolution padding size
: convolution stride size
class CNN(nn.Module):
def __init__(self, in_channels=1, num_classes=10): # gray scale
super().__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels=8,
kernel_size=3, # ────┐
stride=1, # default ─┤─> same output size
padding=1) # default ┘ 위 식에 따라서 출력 사이즈가 결정된다
self.pool = nn.MaxPool2d(kernel_size=2, stride=2) # -> half, 마찬가지로 위 식에 따라서 절반으로 줄어든다.
# 왜 Conv 층을 안 쓰고 Pooling을 쓰냐? 패딩 안하면 사이즈 줄어드는거 똑같은데
# -> 풀링 층은 학습을 안하기 때문에 속도면에서 이득
# -> 학습 파라미터를 줄여줘서 모델의 일반화 성능을 높일 수 있다.
# -> 또한 주변 특성을 합치는 과정으로 위치에 의한 편향을 줄일 수 있다.
self.conv2 = nn.Conv2d(in_channels=8, out_channels=16,
kernel_size=3,
stride=1,
padding=1)
self.fc1 = nn.Linear(16*7*7, num_classes)
def forward(self, x):
x = F.relu(self.conv1(x)) # [batch_size, in_channels, 28, 28]
x = self.pool(x) # [batch_size, 8, 14, 14]
x = F.relu(self.conv2(x)) # [batch_size, 16, 14, 14]
x = self.pool(x) # [batch_size, 16, 7, 7]
x = x.reshape(x.shape[0], -1) # [batch_size, 16*7*7]
x = self.fc1(x) # [batch_size, num_classes]
return x
model = CNN()
x = torch.randn(64, 1, 28, 28)
print(x.shape)
print(model(x).shape)
class RNN(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, num_classes):
super().__init__()
self.hidden_size = hidden_size
self.num_layers = num_layers
self.rnn = nn.RNN(input_size, # 타임 스탭(시퀀스)마다 특성의 수
hidden_size, # 은닉층 특성 수
num_layers, # 순환 층 수 (stacked)
batch_first=True) # [batch_size, sequence, feature]
# GRU를 사용하는 경우
self.gru = nn.GRU(input_size, hidden_size, num_layers, batch_first=True)
# LSTM을 사용하는 경우
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True)
# 양방향일 경우
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, bidirectional=True)
# 모든 hidden state를 사용하는 경우
self.fc = nn.Linear(hidden_size * sequence_length, num_classes)
# 가장 마지막 hidden state만 사용하는 경우
# self.fc = nn.Linear(hidden_size, num_classes)
# 양방향일 경우
# self.fc = nn.Linear(hidden_size*2, num_classes)
def forward(self, x):
# 초기 은닉층 설정
h0 = torch.zeros(self.num_layers, # D * num_layers (양방향일 경우 D=2)
x.size(0), # batch_size
self.hidden_size).to(device)
# LSTM을 사용하는 경우 (양방향일 경우 self.num_layers*2)
# c0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(device)
# Forward propagation
out, hidden_state = self.rnn(x, h0)
# GRU을 사용하는 경우
# out, hidden_state = self.gru(x, h0)
# LSTM을 사용하는 경우
# out, (hidden_state, cell_state) = self.lstm(x, (h0, c0))
# 모든 hidden state를 사용하는 경우
out = out.reshape(out.shape[0], -1)
out = self.fc(out)
# 가장 마지막 hidden state만 사용하는 경우
# out = self.fc(out[:, -1, :]) # [batch_size, last hidden state, features]
return out
model = RNN(input_size=100,
hidden_size=256,
num_layers=2,
num_classes=10).to(device)
x = torch.randn(64, 128, 100).to(device) # [batch_size, sequence length, features]
print(x.shape)
print(model(x).shape)
RNN 대신 GRU를 사용하는 경우는 RNN과 마찬가지로 nn.GRU
도 입력과 hidden state
를 입력받아 nn.RNN
만 nn.GRU
로 바꿔주면 된다.
LSTM는 입력과 hidden state
, cell state
를 입력받고 출력한다. 때문에 순전파 과정을 작성할 때 lstm에 두 번째 인자로 초기 hidden state와 cell state를 Tuple
형태로 넘겨줘야 한다. 반환되는 값 역시 모델 출력과 튜플 형태의 hidden state와 cell state이다.
양방향 LSTM을 사용할 경우에는 bidirectional=True
를 인자로 넘겨준다. 분류를 위한 완전 연결 층도 입력 특성 수를 두 배 늘려준다. hidden state와 cell state도 첫 번째 인자를 두 배로 늘려줘야 한다.
참고: 전에 작성했던 자연어 처리 기초 (정규식, 임베딩, RNN, LSTM, seq2seq (Tensorflow))