양방향 LSTM(Bi-LSTM)의 핵심 개념은 정방향으로만 학습을 진행하는 대신, 마지막 노드에서 뒤에서 앞으로(역방향) 실행되는 다른 LSTM을 추가하는 것이다. Bi-LSTM은 일반 LSTM 대비 역방향으로 정보를 전달하는 hidden layer를 추가해서 이러한 정보를 보다 유연하게 처리한다. (유연하게 = 성능이 좋다 아니겠습니까?)
결론적으로, 각 시점에서 hidden state가 이전 시점과 미래 시점의 정보를 모두 갖는 효과가 있기 때문에 모델을 전체 시계열 데이터로부터 학습할 수 있도록 하려는 경우 유용하다.
(코드도 LSTM과 매우 유사하고, hidden layer 부분만 조금 다르다)
# Load Data
tensor_mode = torchvision.transforms.ToTensor()
trainset = torchvision.datasets.MNIST(root="./data", train=True, transform=tensor_mode, download=True)
testset = torchvision.datasets.MNIST(root="./data", train=False, transform=tensor_mode, download=True)
trainloader = DataLoader(trainset, batch_size=128, shuffle=True)
testloader = DataLoader(testset, batch_size=128, shuffle=False)
class BiLSTM(nn.Module):
def __init__(self, input_size, hidden_size, num_layers, seq_length, num_classes, device):
super(BiLSTM, self).__init__()
self.device = device
self.hidden_size = hidden_size
self.num_layers = num_layers
self.seq_length = seq_length
self.lstm = nn.LSTM(input_size, hidden_size, num_layers, batch_first=True, bidirectional=True)
# bidirectional 옵션만 사용하면 끝 !!
self.fc = nn.Linear(seq_length*hidden_size * 2, num_classes)
def forward(self, x): # layer의 개수도 두 배가 됨
h0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).to(self.device)
c0 = torch.zeros(self.num_layers * 2, x.size(0), self.hidden_size).to(self.device)
out, _ = self.lstm(x, (h0, c0))
#out = self.fc(out[:, -1, :])
out = out.reshape(-1,self.seq_length*self.hidden_size * 2) # 배열을 1열로 만들어주기
out = self.fc(out)
return out
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
sequence_length = trainset.data.size(1) # (28, 이미지의 가로축)
input_size = trainset.data.size(2) # (28, 이미지의 세로축)
num_layers = 2
hidden_size = 12
num_classes = 10
trainset.data.size()
# result
torch.Size([60000, 28, 28])
model = BiLSTM(input_size, hidden_size, num_layers, sequence_length, num_classes, device)
model = model.to(device)
criterion = nn.CrossEntropyLoss() # 분류 문제
optimizer = optim.Adam(model.parameters(), lr=5e-3)
for epoch in range(11):
correct = 0
total = 0
for data in trainloader:
optimizer.zero_grad()
inputs, labels = data[0].to(device).squeeze(1), data[1].to(device)
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
_, predicted = torch.max(outputs.detach(), 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('[%d] train acc: %.2f' %(epoch, 100*correct/total))
def accuracy(dataloader):
correct = 0
total = 0
with torch.no_grad():
model.eval()
for data in dataloader:
inputs, labels = data[0].to(device).squeeze(1), data[1].to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
acc = 100*correct/total
model.train()
return acc
train_acc = accuracy(trainloader)
test_acc = accuracy(testloader)
print("Train Acc: %.1f, Test Acc: %.1f" %(train_acc, test_acc))
# result
Train Acc: 99.5, Test Acc: 98.6