Pytorch의 모든 것

( 참고 : https://wikidocs.net/book/2788 )

Contents

[ Basics ]

  1. 1차원 텐서

  2. 2차원 텐서

  3. 브로드캐스팅 (Broadcasting)

  4. 행렬 곱셈 & 곱셈

  5. 평균

  6. 덧셈

  7. Max & Argmax

  8. view

  9. squeeze

  10. unsqueeze

  11. Type Casting

  12. Concatenate

  13. Stack

  14. torch.ones_like & torch.zeros_like

  15. Linear Regression (naive)

  16. Linear Regression (nn.Module)

  17. Linear Regression (Class)

  18. Mini Batch & Data Loader

  19. Custom Dataset

  20. Linear Regression (with Custom Dataset)

  21. Softmax

  22. One-hot Encoding ( scatter_ )

  23. Softmax의 loss function 구하기

  24. Softmax Regression (nn.Module)

  25. Softmax Regression (Class)

  26. MNIST Classification ( Single NN )

  27. MNIST Classification ( MLP 1 )

  28. MNIST Classification ( MLP 2 )

  29. Batch Normalization & Layer Normalization

    [ Vision & NLP ]

  30. basics of CNN

  31. NLP의 basic

  32. 네이버 영화 리뷰 전처리

  33. Torchtext (1) 영어

  34. Torchtext (2) 한글

  35. Torchtext의 batch_first

  36. nn.Embedding()

  37. Pre-trained Word Embedding

  38. Pre-trained Word Embedding 종류들

  39. RNN basic

  40. Pytorch RNN ( LSTM )

  41. Pytorch CharRNN ( 텍스트 생성 )

  42. Pytorch Word RNN ( 텍스트 생성 )

  43. Pytorch Sentiment Classification (다대일)

  44. Pytorch Sequence Labeling (다대다)


1. 1차원 텐서

  • torch.FloatTensor([ ~ ])

  • x.size() ( = x.shape )

t = torch.FloatTensor([0., 1., 2., 3., 4., 5., 6.])

print(t.dim())  # rank. 즉, 차원
print(t.shape)  # shape
print(t.size()) # shape
#---------------------------------
1
torch.Size([7])
torch.Size([7])

2. 2차원 텐서

t = torch.FloatTensor([[1., 2., 3.],
                       [4., 5., 6.],
                       [7., 8., 9.],
                       [10., 11., 12.]
                      ])

print(t.dim())  # rank. 즉, 차원
print(t.size()) # shape
#---------------------------------
2
torch.Size([4, 3])

3. 브로드캐스팅 (Broadcasting)

  • 불가피하게 크기가 다른 행렬 또는 텐서에 대해서 사칙 연산을 수행할 필요
  • Pytorch에서는 자동으로 크기를 맞춰서 연산을 수행하게 만드는 브로드캐스팅이라는 기능을 제공

1) 일반적인 경우

m1 = torch.FloatTensor([[3, 3]])
m2 = torch.FloatTensor([[2, 2]])
print(m1 + m2)
#---------------------------------
tensor([[5., 5.]])


2) Vector + Scalar

m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([3]) # [3] -> [3, 3]
print(m1 + m2)
#--------------------------
tensor([4., 5.],
       [5., 6.]])


4. 행렬 곱셈 & 곱셈

  • 행렬 곱셈(.matmul) vs 원소 별 곱셈(.mul)
m1 = torch.FloatTensor([[1, 2], [3, 4]])
m2 = torch.FloatTensor([[1], [2]])
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1
#--------------------------
Shape of Matrix 1:  torch.Size([2, 2])
Shape of Matrix 2:  torch.Size([2, 1])

[ matrix multiplication ]

print(m1.matmul(m2)) # 2 x 1
#--------------------------
tensor([[ 5.],
        [11.]])

[ multiplication ]

print(m1 * m2) # 2 x 2
print(m1.mul(m2))
#--------------------------
tensor([[1., 2.],
        [6., 8.]])
tensor([[1., 2.],
        [6., 8.]])


5. 평균

[ 1차원 벡터 ]

t = torch.FloatTensor([1, 2])
#--------------------------
print(t.mean())
tensor(1.5000)


[ 2차원 행렬 ]

  • dim=0 : 1번째(index=0) 차원을 의미
    • (기존) (2x2) \(\rightarrow\) (mean) (1x2)
    • (행x열) \(\rightarrow\) (열,)
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
print(t.mean())
print(t.mean(dim=0)) # (행,열)에서 '행(0)' 제거 ( = 열 별로 평균)
print(t.mean(dim=1)) # (행,열)에서 '열(1)' 제거 ( = 행 별로 평균)
#--------------------------
tensor([[1., 2.],
        [3., 4.]])
tensor(2.5000)
tensor([2., 3.])
tensor([1.5000, 3.5000])


6. 덧셈

t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
print(t.sum())      
print(t.sum(dim=0)) # (행,열)에서 '행(0)' 제거 ( = 열 별로 덧셈)
print(t.sum(dim=1)) # (행,열)에서 '열(1)' 제거 ( = 행 별로 덧셈)
print(t.sum(dim=-1)) # (행,열)에서 '열(-1)' 제거 ( = 행 별로 덧셈)
#--------------------------
tensor([[1., 2.],
        [3., 4.]])
tensor(10.)
tensor([4., 6.])
tensor([3., 7.])
tensor([3., 7.])


7. Max & Argmax

  • dim을 설정하면, max & argmax 두 가지 value 모두 반환
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
print(t.max()) 
print(t.max(dim=0)) # (행,열)에서 '행(0)' 제거 ( = 열 별로 최대)
print(t.max(dim=1)) # (행,열)에서 '열(1)' 제거 ( = 행 별로 최대)
print(t.max(dim=-1)) # (행,열)에서 '열(-1)' 제거 ( = 행 별로 최대)
#--------------------------
tensor([[1., 2.],
        [3., 4.]])
tensor(4.)
(tensor([3., 4.]), tensor([1, 1]))
(tensor([2., 4.]), tensor([1, 1]))
(tensor([2., 4.]), tensor([1, 1]))


8. view

t = np.array([[[0, 1, 2],
               [3, 4, 5]],
              [[6, 7, 8],
               [9, 10, 11]]])
ft = torch.FloatTensor(t)
print(ft.shape)
#--------------------------
torch.Size([2, 2, 3])


[ 3차원 \(\rightarrow\) 2차원 ]

  • x.view([-1,n]) : (?,n)의 크기로 바꾸기
print(ft.view([-1, 3])) # ft라는 텐서를 (?, 3)의 크기로 변경
print(ft.view([-1, 3]).shape)
#--------------------------
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.],
        [ 6.,  7.,  8.],
        [ 9., 10., 11.]])
torch.Size([4, 3]) # (2,2,3) -> (2x2,3)


[ 3차원 \(\rightarrow\) 3차원 ]

  • 3차원 텐서에서 3차원 텐서로 차원은 유지하되, 크기(shape)를 바꾸는 작업
print(ft.view([-1, 1, 3])) # ft라는 텐서를 (?, 1, 3)의 크기로 변경
print(ft.view([-1, 1, 3]).shape)
#--------------------------
tensor([[[ 0.,  1.,  2.]],
        [[ 3.,  4.,  5.]],
        [[ 6.,  7.,  8.]],
        [[ 9., 10., 11.]]])
torch.Size([4, 1, 3])


9. squeeze

  • 스퀴즈(Squeeze) :1인 차원을 제거한다
ft = torch.FloatTensor([[0], [1], [2]])
print(ft)
print(ft.shape)
print(ft.squeeze())
print(ft.squeeze().shape)
#--------------------------
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])
tensor([0., 1., 2.])
torch.Size([3]) # [3,1] -> [3]


10. unsqueeze

  • 언스퀴즈(Unsqueeze) : 특정 위치에 1인 차원을 추가한다.
ft = torch.Tensor([0, 1, 2])
print(ft.shape)
print(ft.unsqueeze(0)) # 첫번째 차원(=0)을 의미한다.
print(ft.unsqueeze(0).shape)
#--------------------------
torch.Size([3])
tensor([[0., 1., 2.]])
torch.Size([1, 3]) # [3] -> [1,3]
  • 방금 한 연산을 앞서 배운 view로도 구현 가능
  • 2차원으로 바꾸고 싶으면서 & 첫번째 차원은 1이기를 원한다면…
print(ft.view(1, -1))
print(ft.view(1, -1).shape)
#--------------------------
tensor([[0., 1., 2.]])
torch.Size([1, 3])
  • unsqueeze의 인자로 1을 넣으면, 2번째 차원에 차원 추가
print(ft.unsqueeze(1))
print(ft.unsqueeze(1).shape)
#--------------------------
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])


11. Type Casting

  • CPU : torch.xxTensor

  • GPU : torch.cuda.xxTensor

.

lt = torch.LongTensor([1, 2, 3, 4])
print(lt.float())
#--------------------------
tensor([1., 2., 3., 4.])
bt = torch.ByteTensor([True, False, False, True])
print(bt)
print(bt.long())
print(bt.float())
#--------------------------
tensor([1, 0, 0, 1], dtype=torch.uint8)
tensor([1, 0, 0, 1])
tensor([1., 0., 0., 1.])


12. Concatnate

  • torch.cat([ ])
x = torch.FloatTensor([[1, 2], [3, 4]])
y = torch.FloatTensor([[5, 6], [7, 8]])

print(torch.cat([x, y], dim=0)) # (2,2)&(2,2) -> (4,2)
print(torch.cat([x, y], dim=1)) # (2,2)&(2,2) -> (2,4)
#--------------------------
tensor([[1., 2.],
        [3., 4.],
        [5., 6.],
        [7., 8.]])
tensor([[1., 2., 5., 6.],
        [3., 4., 7., 8.]])


13. Stack

( 참고 : https://stackoverflow.com/questions/54307225/whats-the-difference-between-torch-stack-and-torch-cat-functions )

  • torch.cat([ ]) : Concatenates sequence of tensors along a new dimension.
  • torch.stack([]) : Concatenates the given sequence of seq tensors in the given dimension.


So if A and B are of shape (3, 4)

  • torch.cat([A, B], dim=0) will be of shape (6, 4)
  • torch.stack([A, B], dim=0) will be of shape (2, 3, 4)


x = torch.FloatTensor([1, 4]) # (2)
y = torch.FloatTensor([2, 5]) # (2)
z = torch.FloatTensor([3, 6]) # (2)

print(torch.cat([x.unsqueeze(0), y.unsqueeze(0), z.unsqueeze(0)], dim=0)) # (3,2)
print(torch.stack([x, y, z])) # (3,2)
print(torch.stack([x, y, z], dim=1)) # (2,3)
#--------------------------
tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])
tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])
tensor([[1., 2., 3.],
        [4., 5., 6.]])


14. torch.ones_like & torch.zeros_like

x = torch.FloatTensor([[0, 1, 2], [2, 1, 0]])

print(x)
print(torch.ones_like(x))
print(torch.zeros_like(x))
#--------------------------
tensor([[0., 1., 2.],
        [2., 1., 0.]])
tensor([[1., 1., 1.],
        [1., 1., 1.]])
tensor([[0., 0., 0.],
        [0., 0., 0.]])


15. Linear Regression (naive)

import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
torch.manual_seed(1)
x_train = torch.FloatTensor([[1], [2], [3]])
y_train = torch.FloatTensor([[2], [4], [6]])

# 가중치 0으로 초기화 & requires_grad 통해 학습 대상으로 설정!
W = torch.zeros(1, requires_grad=True) 
b = torch.zeros(1, requires_grad=True)

# prediction
y_pred = x_train * W + b

# loss & optimizer
loss = torch.mean((y_pred - y_train) ** 2) 
optimizer = optim.SGD([W, b], lr=0.01)

# z/b/s
optimizer.zero_grad() 
loss.backward() 
optimizer.step() 


[ Summary ]

  • [z/b/x] 매 epoch마다, backward & optimize하기 이전에 zero_grad() 해주기
nb_epochs=1999

for epoch in range(nb_epochs + 1):

    y_pred = x_train * W + b
    loss = torch.mean((y_pred - y_train) ** 2)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 100 == 0:
        print('Epoch {:4d}/{} W: {:.3f}, b: {:.3f} Cost: {:.6f}'.format(
            epoch, nb_epochs, W.item(), b.item(), loss.item()))


16. Linear Regression (nn.Module)

  • 이미 구현되어져 제공되고 있는 함수들을 사용
    • model : nn.Linear()
    • loss : nn.functional.mse_loss()
import torch
import torch.nn as nn
import torch.nn.functional as F
torch.manual_seed(1)


x_train = torch.FloatTensor([[73, 80, 75],
                             [93, 88, 93],
                             [89, 91, 90],
                             [96, 98, 100],
                             [73, 66, 70]])
y_train = torch.FloatTensor([[152], [185], [180], [196], [142]])


model = nn.Linear(3,1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01) 

nb_epochs = 2000
for epoch in range(nb_epochs+1):

    y_pred = model(x_train)
    loss = F.mse_loss(y_pred, y_train) 

    optimizer.zero_grad()
    loss.backward() # backward 연산
    optimizer.step()

    if epoch % 100 == 0:
      print('Epoch {:4d}/{} Cost: {:.6f}'.format(
          epoch, nb_epochs, cost.item()))


new_var =  torch.FloatTensor([[73, 80, 75]]) 
pred_y = model(new_var) 

print(pred_y) 
print(list(model.parameters()))
#------------------------------------------
tensor([[151.2305]], grad_fn=<AddmmBackward>)
[Parameter containing:
tensor([[0.9778, 0.4539, 0.5768]], requires_grad=True), Parameter containing:
tensor([0.2802], requires_grad=True)]


17. Linear Regression (Class)

class MultivariateLinearRegressionModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(3, 1) 

    def forward(self, x):
        return self.linear(x)


model = MultivariateLinearRegressionModel()
optimizer = torch.optim.SGD(model.parameters(), lr=1e-5) 


18. Mini Batch & Data Loader

  • dataset=TensorDataset(x_train, y_train)
  • DataLoader(dataset, batch_size=2, shuffle=True)
from torch.utils.data import TensorDataset
from torch.utils.data import DataLoader 


x_train  =  torch.FloatTensor([[73,  80,  75], 
                               [93,  88,  93], 
                               [89,  91,  90], 
                               [96,  98,  100],   
                               [73,  66,  70]])  
y_train  =  torch.FloatTensor([[152],  [185],  [180],  [196],  [142]])


dataset = TensorDataset(x_train, y_train)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)


for epoch in range(nb_epochs + 1):
  for batch_idx, samples in enumerate(dataloader):
  	xxxxx


19. Custom Dataset

  • torch.utils.data.Dataset을 상속받아 직접 커스텀 데이터셋(Custom Dataset)을 만들기
  • 아래의 method들을 override 하여 Custom Dataset 만들기


class CustomDataset(torch.utils.data.Dataset): 
  def __init__(self):
  # 데이터셋의 전처리를 해주는 부분

  def __len__(self):
  # 데이터셋의 길이. 즉, 총 샘플의 수를 적어주는 부분

  def __getitem__(self, idx): 
  # 데이터셋에서 특정 1개의 샘플을 가져오는 함수


20. Linear Regression (with Custom Dataset)

class CustomDataset(Dataset): 
  def __init__(self):
    self.x_data = [[73, 80, 75],
                   [93, 88, 93],
                   [89, 91, 90],
                   [96, 98, 100],
                   [73, 66, 70]]
    self.y_data = [[152], [185], [180], [196], [142]]

  def __len__(self): 
    return len(self.x_data)

  def __getitem__(self, idx): 
    x = torch.FloatTensor(self.x_data[idx])
    y = torch.FloatTensor(self.y_data[idx])
    return x, y


dataset = CustomDataset()
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)


21. Softmax

  • F.softmax(xxx, dim=0)
z = torch.rand(3, 5, requires_grad=True)
y_pred = F.softmax(z, dim=1) # (행,열)에서 "열" 기준이므로, "행"의 합이 1

print(y_pred)
#--------------------------
tensor([[0.2645, 0.1639, 0.1855, 0.2585, 0.1277],
        [0.2430, 0.1624, 0.2322, 0.1930, 0.1694],
        [0.2226, 0.1986, 0.2326, 0.1594, 0.1868]], grad_fn=<SoftmaxBackward>)


22. One-hot Encoding ( scatter_ )

y = torch.randint(5, (3,)).long()
print(y)
#--------------------------
tensor([0, 2, 1])


[ One-hot Encoding 하기 ]

y_one_hot = torch.zeros_like(y_pred) # 크기 : (3x5) 
y_one_hot.scatter_(1, y.unsqueeze(1), 1)
print(y_one_hot)
#--------------------------
tensor([[1., 0., 0., 0., 0.],
        [0., 0., 1., 0., 0.],
        [0., 1., 0., 0., 0.]])


23. Softmax의 loss function 구하기

F.log_softmax()

  • F.softmax() + torch.log() = F.log_softmax()
a=torch.log(F.softmax(z, dim=1))
b=F.log_softmax(z, dim=1)

a==b


F.cross_entropy()

  • F.log_softmax() + F.nll_loss() = F.cross_entropy()
a=(y_one_hot * -torch.log(F.softmax(z, dim=1))).sum(dim=1).mean()
b=(y_one_hot * - F.log_softmax(z, dim=1)).sum(dim=1).mean()
c=F.nll_loss(F.log_softmax(z, dim=1), y)
d=F.cross_entropy(z, y)

a==b==c==d


24. Softmax Regression (nn.Module)

x_train = [[1, 2, 1, 1],
           [2, 1, 3, 2],
           [3, 1, 3, 4],
           [4, 1, 5, 5],
           [1, 7, 5, 5],
           [1, 2, 5, 6],
           [1, 6, 6, 6],
           [1, 7, 7, 7]]
y_train = [2, 2, 2, 1, 1, 1, 0, 0]
x_train = torch.FloatTensor(x_train)
y_train = torch.LongTensor(y_train)


  • One-hot 인코딩하기
y_one_hot = torch.zeros(8, 3)
y_one_hot.scatter_(1, y_train.unsqueeze(1), 1)


model = nn.Linear(4, 3)
optimizer = optim.SGD(model.parameters(), lr=0.1)


nb_epochs = 1000
for epoch in range(nb_epochs + 1):
    y_pred = model(x_train)
    loss = F.cross_entropy(y_pred, y_train)

    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if epoch % 100 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, nb_epochs, cost.item()))


25. Softmax Regression (Class)

class SoftmaxClassifierModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.linear = nn.Linear(4, 3) # Output이 3!

    def forward(self, x):
        return self.linear(x)


model = SoftmaxClassifierModel()
optimizer = optim.SGD(model.parameters(), lr=0.1)


26. MNIST Classification ( Single NN )

import torch
import torchvision.datasets as dsets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import matplotlib.pyplot as plt
import random


  • GPU 연산이 가능하다면 GPU를사용
USE_CUDA = torch.cuda.is_available() 
device = torch.device("cuda" if USE_CUDA else "cpu") 


  • Random Seed 고정
random.seed(777)
torch.manual_seed(777)

if device == 'cuda':
    torch.cuda.manual_seed_all(777)


  • Hyperparameter
training_epochs = 15
batch_size = 100


  • Dataset

    • drop_last =True

    • 1000 = 128 x 7개의 batch + 104

      ( 이 104개를 버리기! 상대적으로 과대평가 되는 것 방지 )

mnist_train = dsets.MNIST(root='MNIST_data/',
                          train=True,
                          transform=transforms.ToTensor(),
                          download=True)

mnist_test = dsets.MNIST(root='MNIST_data/',
                         train=False,
                         transform=transforms.ToTensor(),
                         download=True)
                         
data_loader = DataLoader(dataset=mnist_train,
                         batch_size=batch_size,
                         shuffle=True,
                         drop_last=True) # 마지막 batch를 버리기


  • model ( to() : 연산을 어디서 수행할지 결정 )
linear = nn.Linear(784, 10, bias=True).to(device)


  • Loss Function & Optimizer
    • nn.CrossEntropyLoss() : 내부적으로 softmax 이미 포함
      • torch.nn.functional.cross_entropy()와 동일
criterion = nn.CrossEntropyLoss().to(device)
optimizer = torch.optim.SGD(linear.parameters(), lr=0.1)


  • Train

    • 여기서 y는 one-hot encoding 되지 않은 label이다 ( 0 ~9 중 하나 )

      ( nn.CrossEntropyLoss()가 알아서 내재잭으로 one-hot encoding 해준 뒤 연산 )

for epoch in range(training_epochs): 
    avg_cost = 0
    total_batch = len(data_loader) # ( = 전체 data 개수 / batch 1개 당 개수 )

    for X, Y in data_loader:
        X = X.view(-1, 28 * 28).to(device) # (100,784)
        Y = Y.to(device) 
        #------------------------------
        y_pred = linear(X)
        loss = criterion(y_pred, Y)
		#------------------------------
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
		#------------------------------
        avg_cost += cost / total_batch

    print('Epoch:', '%04d' % (epoch + 1), 'cost =', '{:.9f}'.format(avg_cost))


  • Test
    • torch.no_grad() : gradient 계산 안함
with torch.no_grad(): 
    X_test = mnist_test.test_data.view(-1, 28 * 28).float().to(device)
    Y_test = mnist_test.test_labels.to(device)

    y_pred = linear(X_test)
    prediction = (torch.argmax(y_pred, 1) == Y_test)
    accuracy = prediction.float().mean()
    print('Accuracy:', accuracy.item())


27. MNIST Classification ( MLP 1 )

import torch
import torch.nn as nn
from torch import optim


from sklearn.datasets import load_digits
digits = load_digits() # 1,979개의 이미지 데이터 로드

X = digits.data 
Y = digits.target 

X = torch.tensor(X, dtype=torch.float32)
Y = torch.tensor(Y, dtype=torch.int64)


model = nn.Sequential(
    nn.Linear(64, 32),
    nn.ReLU(),
    nn.Linear(32, 16),
    nn.ReLU(),
    nn.Linear(16, 10) 
)


loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters())


losses = []

for epoch in range(100):
    y_pred = model(X) 
    loss = loss_fn(y_pred, Y)
    #---------------------------
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    #---------------------------

    if epoch % 10 == 0:
        print('Epoch {:4d}/{} Cost: {:.6f}'.format(
            epoch, 100, loss.item()))

    losses.append(loss.item())


28. MNIST Classification ( MLP 2 )

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=1/7, random_state=0)

X_train = torch.Tensor(X_train)
X_test = torch.Tensor(X_test)
y_train = torch.LongTensor(y_train)
y_test = torch.LongTensor(y_test)


ds_train = TensorDataset(X_train, y_train)
ds_test = TensorDataset(X_test, y_test)

loader_train = DataLoader(ds_train, batch_size=64, shuffle=True)
loader_test = DataLoader(ds_test, batch_size=64, shuffle=False)


from torch import nn

model = nn.Sequential()
model.add_module('fc1', nn.Linear(28*28*1, 100))
model.add_module('relu1', nn.ReLU())
model.add_module('fc2', nn.Linear(100, 100))
model.add_module('relu2', nn.ReLU())
model.add_module('fc3', nn.Linear(100, 10))


loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01, weight_decay=1e-6)


train 함수

def train(epoch):
    model.train()  
    for data, targets in loader_train:
        outputs = model(data)  
        loss = loss_fn(outputs, targets) 
        #-------------------------
        optimizer.zero_grad()  
        loss.backward()  
        optimizer.step() 
    print("epoch{}:완료\n".format(epoch))


test 함수

def test():
    model.eval()  
    num_correct = 0

    with torch.no_grad():        
        for X, y in loader_test:
            y_pred = model(X) 
            _, y_pred_class = torch.max(y_pred.data, 1) ( value, value index )
            num_correct += predicted.eq(y.data.view_as(y_pred_class)).sum() 

    data_num = len(loader_test.dataset)
    print('\n테스트 데이터에서 예측 정확도: {}/{} ({:.0f}%)\n'.format(num_correct,
                                                   data_num, 100. * num_correct / data_num))


  • train
num_epoch=10

for epoch in range(num_epoch):
    train(epoch)

test()


29. Batch Normalization & Layer Normalization

(1) Batch Normalization

.


(2) Layer Normalization

.


30. basics of CNN

(1) Conv2d

  • input channel = 1
  • output channel = 32
  • kernel size = 3 ( = 3x3 )
  • padding = 1
conv1 = nn.Conv2d(1, 32, 3, padding=1)
print(conv1)
#-----------------------------------------
Conv2d(1, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))


(2) MaxPool2d

  • 정수 하나를 인자로 넣으면, kernel size & stride 모두 해당 값
pool = nn.MaxPool2d(2)
print(pool)	
#-----------------------------------------
MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)


(3) Basic Model & MNIST

import torch
import torchvision.datasets as dsets
import torchvision.transforms as transforms
import torch.nn.init


device = 'cuda' if torch.cuda.is_available() else 'cpu'

torch.manual_seed(777)
if device == 'cuda':
    torch.cuda.manual_seed_all(777)


  • hyperparameter
learning_rate = 0.001
training_epochs = 15
batch_size = 100


  • dataset & dataloader
mnist_train = dsets.MNIST(root='MNIST_data/', 
                          train=True,
                          transform=transforms.ToTensor(),
                          download=True)

mnist_test = dsets.MNIST(root='MNIST_data/', 
                         train=False, 
                         transform=transforms.ToTensor(),
                         download=True)
                         
data_loader = torch.utils.data.DataLoader(dataset=mnist_train,
                                          batch_size=batch_size,
                                          shuffle=True,
                                          drop_last=True)


  • CNN
    • 기본 구조 : [ (1) Convolution - (2) ReLU - (3) Max Pooling ] x n + [ FC Layer ]
    • input : ( batch size 개수, h=28, w=28, c=1 )
class CNN(torch.nn.Module):

    def __init__(self):
        super(CNN, self).__init__()
        self.layer1 = torch.nn.Sequential(
            torch.nn.Conv2d(1, 32, kernel_size=3, stride=1, padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2, stride=2))

        self.layer2 = torch.nn.Sequential(
            torch.nn.Conv2d(32, 64, kernel_size=3, stride=1, padding=1),
            torch.nn.ReLU(),
            torch.nn.MaxPool2d(kernel_size=2, stride=2))

        self.fc = torch.nn.Linear(7 * 7 * 64, 10, bias=True)
        torch.nn.init.xavier_uniform_(self.fc.weight)

    def forward(self, x):
        x = self.layer1(x)
        x = self.layer2(x)
        x = x.view(x.size(0), -1)  # Flatten
        out = self.fc(x)
        return out


model = CNN().to(device)


  • Loss Function & Optimizer
criterion = torch.nn.CrossEntropyLoss().to(device)  
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)


  • Train
total_batch = len(data_loader)

for epoch in range(training_epochs):
    avg_cost = 0

    for X, Y in data_loader: 
        X = X.to(device)
        Y = Y.to(device) # One-hot encoding 되지 "않은" 상태
		#---------------------------
        y_pred = model(X)
		loss = criterion(y_pred, Y)
		#---------------------------
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        avg_cost += cost / total_batch

    print('[Epoch: {:>4}] cost = {:>.9}'.format(epoch + 1, avg_cost))


  • Test
with torch.no_grad():
    X_test = mnist_test.test_data.view(len(mnist_test), 1, 28, 28).float().to(device)
    Y_test = mnist_test.test_labels.to(device)

    y_pred = model(X_test)
    prediction = (torch.argmax(y_pred, 1) == Y_test)
    accuracy = prediction.float().mean()
    print('Accuracy:', accuracy.item())


31. NLP의 basic

tokenize

  • 주어진 텍스트를 단어/문자 단위로 나누는 것
  • 영어 package : spacy, nltk
  • 한글 package :
  • 띄어쓰기 기준 : split 함수


1) 영어 : spaCy

  • 예시 문장
english_text = "A Dog Run back corner near spare bedrooms"


  • package & tokenizer 불러오기
import spacy
spacy_en = spacy.load('en')
eng_tokenizer = spacy_en.tokenizer


  • tokenize하는 함수
def tokenize(text):
    return [x.text for x in eng_tokenizer(text)]


  • 결과
tokenize(english_text)
#---------------------------------------------------
['A', 'Dog', 'Run', 'back', 'corner', 'near', 'spare', 'bedrooms']


2) 영어 : nltk

  • package & tokenizer 불러오기
import nltk
nltk.download('punkt')
from nltk.tokenize import word_tokenize


  • 결과
word_tokenize(english_text)
#---------------------------------------------------
['A', 'Dog', 'Run', 'back', 'corner', 'near', 'spare', 'bedrooms']


3) 띄어쓰기로 tokenize

english_text.split()
#---------------------------------------------------
['A', 'Dog', 'Run', 'back', 'corner', 'near', 'spare', 'bedrooms']


4) 한글 : mecab

  • 예시 문장
korean_text = "사과의 놀라운 효능이라는 글을 봤어. 그래서 오늘 사과를 먹으려고 했는데 사과가 썩어서 슈퍼에 가서 사과랑 오렌지 사왔어"


  • Mecab : 형태소(morpheme) 분석기

    ( 한국어는 일반적으로 띄어쓰기가 아닌, “형태소” 단위로 분석 )

!git clone https://github.com/SOMJANG/Mecab-ko-for-Google-Colab.git

%cd Mecab-ko-for-Google-Colab

!bash install_mecab-ko_on_colab190912.sh


from konlpy.tag import Mecab
tokenizer = Mecab()


tokenizer.morphs(korean_text)
#------------------------------------------------
['사과', '의', '놀라운', '효능', '이', '라는', '글', '을', '봤', '어', '.', '그래서', '오늘', '사과', '를', '먹', '으려고', '했', '는데', '사과', '가', '썩', '어서', '슈퍼', '에', '가', '서', '사과', '랑', '오렌지', '사', '왔', '어']


32. 네이버 영화 리뷰 전처리

  • 1) pakcage 불러오기
import urllib.request
import pandas as pd
from konlpy.tag import Mecab
from nltk import FreqDist
import numpy as np


  • 2) data 다운로드
urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings.txt", filename="ratings.txt")

data = pd.read_table('ratings.txt')


전처리 ( 규불 )

( “규”칙 제거 - “불”용어 제거 - )

  • 3-1) 한글, 공백 제외하고 전부제거

    ( = 영어, 숫자, 특수문자 제거 )

    • ”[\^ 여기에 포함시키고 싶은 규칙만 남기기 ]”
      • 남길 것 1 : ㄱ-ㅎ
      • 남길 것 2 : ㅏ-ㅣ
      • 남길 것 3 : 가-힣
data['document'] = data['document'].str.replace("[^ㄱ-ㅎㅏ-ㅣ가-힣 ]","")


  • 3-2) 불용어 제거
    • step 1) 불용어 list 생성
    • step 2) Mecab() 형태소 분석기 사용하여 모든 문장 tokenize
    • step 3) 각 문장 별로 for loop 돌면서, tokenize한 뒤, 불용어에 속하지 않으면 추가
stopwords=['의','가','이','은','들','는','좀','잘','걍','과','도','를','으로','자','에','와','한','하다']
tokenizer = Mecab()
morphs_tokenizer = tokenizer.morphs
tokenized=[]
for sentence in data['document']:
    temp = morphs_tokenizer(sentence) # 토큰화
    temp = [word for word in temp if not word in stopwords] 
    tokenized.append(temp)


  • 4) Vocabulary 만들기
    • FreqDist로 등장하는 모든 (고유 단어 & 등장 빈도) 확인하기
    • 등장 빈도 상위 Top N개의 “N”을 설정해주기 ( 여기서는 N = 500 )
from nltk import FreqDist
vocab = FreqDist(np.hstack(tokenized))
vocab_size = 500
vocab = vocab.most_common(vocab_size)


33. Torchtext (1) 영어

pip install torchtext

[ Torchtext가 제공하는 기능들 ]

  1. File Load
  2. Tokenizing (토크나이징)
  3. Vocabulary 생성
  4. Integer Encoding (정수 인코딩)
  5. Vector Representation ( 단어를 고유한 embedding vector로 )
    1. random initialized vector
    2. pre-trained vector
  6. Batch ( + Padding )
# from torchtext.data import TabularDataset ( before )
from torchtext.legacy.data import TabularDataset


  • 1) data 다운받기 & 불러오기 ( IMDB dataset )
urllib.request.urlretrieve("https://raw.githubusercontent.com/LawrenceDuan/IMDb-Review-Analysis/master/IMDb_Reviews.csv", filename="IMDb_Reviews.csv")

df = pd.read_csv('IMDb_Reviews.csv', encoding='latin1')


  • 2) train / test 나누기
train_df = df[:25000]
test_df = df[25000:]

train_df.to_csv("train_data.csv", index=False)
test_df.to_csv("test_data.csv", index=False)


  • 3) Field 정의하기 ( 일종의 객체라고 생각하면 됨 )
    • 역할 : 각종 텍스트 전처리
    • 2개의 Field를 정의한다 ( Text(X) & Label(Y) )

[ 인자 소개 (default)]

  • sequential : sequence 데이터 여부 (True)
  • use_vocab : vocabulary 집합 만들 지 여부 (True)
  • tokenize : 어떤 tokenize 함수를 사용 할지 (string.split)
  • lower : 영어 텍스트 소문자화 여부 (False)
  • batch_first : batch의 “dimiension”을 맨 앞으로 할 지 (False)
  • is_target : y값인지의 여부 (False)
  • fix_length : 최대 허용 길이
from torchtext import data

TEXT = data.Field(sequential=True,
                  use_vocab=True,
                  tokenize=str.split,
                  lower=True,
                  batch_first=True,
                  fix_length=20)
                  
LABEL = data.Field(sequential=False,
                   use_vocab=False,
                   batch_first=False,
                   is_target=True)


  • 4) Dataset 생성

    ( TabularDataset : data를 불러오면서, field에서 정의한 방법대로 tokenize )

[ 인자 소개 ]

  • path : file이 위치한 경로
  • format : data의 format
  • fields : 위에서 정의한 field를 pair ( = (A,B) )로써 지정
    • A : field의 호칭
    • B : 위에서 생성한 field
  • skip_header : data의 first row를 skip
from torchtext.data import TabularDataset
train_data, test_data = TabularDataset.splits(
    path='.', 
    train='train_data.csv', test='test_data.csv',   
    format='csv',
    fields=[('text', TEXT), ('label', LABEL)], 
    skip_header=True)


  • 5) Data 확인하기
print('훈련 샘플의 개수 : {}'.format(len(train_data)))
print('테스트 샘플의 개수 : {}'.format(len(test_data)))
#------------------------------------------------------------
훈련 샘플의 개수 : 25000
테스트 샘플의 개수 : 25000


( 특정 idx의 data 확인하기 )

vars(train_data[0])
#------------------------------------------------------------
{'text': ['my', 'family', 'and', 'i', 'normally', 'do', 'not', 'watch', 'local', 'movies', 'for', 'the', 'simple', 'reason', 
         ... 중략 ...
         'movie?', 'congratulations', 'to', 'star', 'cinema!!', 'way', 'to', 'go,', 'jericho', 'and', 'claudine!!'],
'label': '1'}


  • 6) Vocabulary 생성
    • 위의 1)~4)의 과정으로, tokenize는 완료되었다
    • 이제 vocabulary를 생성해보자

[ 인자 소개 ]

  • min_freq : vocabulary 사전에 등록되기 위한 최소 등장 횟수
  • max_size : vocabulary 사전의 최대 크기
TEXT.build_vocab(train_data, min_freq=10, max_size=10000)

print(len(TEXT.vocab)))
#----------------------------------------------
10002


10,000개가 아니라, 10,0002개인 이유는?

  • 10,000개 : 일반적인 vocabulary
  • 2개 : special token, <unk>와 <pad>
    • <unk> : 단어 집합에 없는 단어 ( 최소 등장횟수 자격요건 충족 X )
    • <pad> : 패딩


  • 7) Data Loader 생성 ( Iterator )
    • 인자 1) dataset
    • 인자 2) batch_size
from torchtext.data import Iterator

batch_size = 5
train_loader = Iterator(dataset=train_data, batch_size = batch_size)
test_loader = Iterator(dataset=test_data, batch_size = batch_size)	


( 25000개의 단어 / 배치당 5개 = 5000개의 배치 )

len(train_loader),len(test_loader)
#-----------------------------------
5000,5000 


( 첫 번째 mini-batch 뽑아내기 )

  • 총 5개의 data가 있음을 알 수 있다 ( \(\because\) batch size = 5 )
  • 그 중 첫번째 data를 상세히 확인해보면 뒷부분에 padding (<pad>, index로는 1)이 됨을 알 수 있다.
batch = next(iter(train_loader)) 

print(batch.text)
print(batch.text[0])
#------------------------------------
tensor([[ 248,   39,    0,    0,   55, 7701,    0,  174,  701,   34,    3,  403,
            8,    0, 1480,    0, 2595, 1499,    0,    9],
        [  50,    9,   82, 2294,    2,    0,   26,    6, 1130,   44,   10,  265,
           54,   28,  450,   16,   55, 5506,   18,   94],
        [  10,    7, 5302,    2,  119,   25,  206,    0,    0,   95,  309, 1578,
            3, 8885,  269, 1373,    0,    8,    0,    4],
        [   2,  256,  122,    5,   10,    0,    0, 3588,    0,    0, 4750,   19,
          386,    0,    0,  996,  135,   68,    0,    2],
        [  10,   14,   61,    3,    0,    5,    3, 3536,    9,  202,   11,   42,
         3589,  182, 1193,   19, 2608,  269,    4,  641]])
         
tensor([ 248,   39,    0,    0,   55, 7701,    0,  174,  701,   34,    3,  403,
           8,    0, 1480,    0, 2595, 1499,    0,    9,  388, 5068,    6,   73,
           ... 중략 ...
           1,    1,    1,    1,    1,    1,    1,    1,    1,    1,    1,    1,
           1,    1,    1,    1,    1,    1,    1,    1,    1,    1,    1,    1])


34. Torchtext (2) 한글

( Mecab은 위에 나온대로 설치가 되었다는 가정 하에 )

from konlpy.tag import Mecab


  • 1) dataset 다운로드 & 불러오기
urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_train.txt", filename="ratings_train.txt")
urllib.request.urlretrieve("https://raw.githubusercontent.com/e9t/nsmc/master/ratings_test.txt", filename="ratings_test.txt")

train_df = pd.read_table('ratings_train.txt') # 15만개 
test_df = pd.read_table('ratings_test.txt')  # 5만개


  • 2) Field 정의하기

( 주의 : 전처리를 위한 객체를 마련한 것일 뿐, 아직 전처리를 수행한 것은 아니다 )

from torchtext import data 

( 총 3개의 field를 정의한다 )

  • 1) ID ( 필수적 X )
  • 2) TEXT
  • 3) LABEL
tokenizer = Mecab()
morphs_tokenizer = tokenizer.morphs

# 필드 정의
ID = data.Field(sequential = False,
                use_vocab = False) 

TEXT = data.Field(sequential=True,
                  use_vocab=True,
                  tokenize=morphs_tokenizer,
                  lower=True,
                  batch_first=True,
                  fix_length=20)

LABEL = data.Field(sequential=False,
                   use_vocab=False,
                   is_target=True)


  • 3) Dataset 만들기
    • data 불러온 뒤, 위에서 정의한 field대로 전처리(+tokenize)
from torchtext.data import TabularDataset

train_data, test_data = TabularDataset.splits(
    path='.', 
    train='ratings_train.txt', test='ratings_test.txt', 
    format='tsv',
    fields=[('id', ID), ('text', TEXT), ('label', LABEL)], 
    skip_header=True)
len(train_data),len(test_data)
#------------------------------------
150000,50000
vars(train_data[0])
#-------------------------------------
{'id': '9976970', 'text': ['아', '더', '빙', '.', '.', '진짜', '짜증', '나', '네요', '목소리'], 'label': '0'}


  • 4) Vocabulary 생성
TEXT.build_vocab(train_data, min_freq=10, max_size=10000)


  • 5) Dataloader 생성 ( Iterator )
from torchtext.data import Iterator

batch_size = 5
train_loader = Iterator(dataset=train_data, batch_size = batch_size)
test_loader = Iterator(dataset=test_data, batch_size = batch_size)


( 30000,10000 = 15만/5 , 5만/5 )

len(train_loader),len(test_loader)
#------------------------------------
30000,10000


35. Torchtext의 batch_first

batch_first =True

  • 따라서, batch size=5가 먼저 나와서

    \(\rightarrow\) 1개의 minibatch의 차원은 (5,20)

batch = next(iter(train_loader)) # 1번째 mini-batch
print(batch.text)
#-----------------------------
tensor([[  31,  191,   24,  133,  445,  115,   42,   10,  149,    2, 3581, 6601,
            0,   12,  172,   74,  358,  806,    6,  425],
        [   9,   98,   12,   10,   20,    7,  157, 2520,  285,   11, 1384,   46,
          921, 4255,   16,   10,    0,  702,   82,    5],
        [   9,  323,  148,   10,   25,   17,  110, 3109,   80,   44,  291, 4427,
            3,  778, 3286,   17,    0,    2, 1308,  193],
        [  10,    7,   49, 8950,   18,  189,  184,    5,    2, 1890,   17,   10,
            0,  118,   24,   62,  141,    2,  162,   16],
        [   9,  574, 4312, 1147,   64, 2621,    3,  283,  499,   16,   21,  138,
            0,    5,    0, 5994,    2, 1462,   12,    2]])
print(batch.text.shape)
#-----------------------------
torch.Size([5, 20])


batch_first = False

따라서, batch size=5가 뒤에 나와서

\(\rightarrow\) 1개의 minibatch의 차원은 (20,5)

batch = next(iter(train_loader)) # 1번째 mini-batch
print(batch.text)
#-----------------------------
tensor([[  31,    9,    9,   10,    9],
        [ 191,   98,  323,    7,  574],
        [  24,   12,  148,   49, 4312],
        [ 133,   10,   10, 8950, 1147],
        [ 445,   20,   25,   18,   64],
        [ 115,    7,   17,  189, 2621],
        [  42,  157,  110,  184,    3],
        [  10, 2520, 3109,    5,  283],
        [ 149,  285,   80,    2,  499],
        [   2,   11,   44, 1890,   16],
        [3581, 1384,  291,   17,   21],
        [6601,   46, 4427,   10,  138],
        [   0,  921,    3,    0,    0],
        [  12, 4255,  778,  118,    5],
        [ 172,   16, 3286,   24,    0],
        [  74,   10,   17,   62, 5994],
        [ 358,    0,    0,  141,    2],
        [ 806,  702,    2,    2, 1462],
        [   6,   82, 1308,  162,   12],
        [ 425,    5,  193,   16,    2]])
print(batch.text.shape)
#-----------------------------
torch.Size([20,5])


36. nn.Embedding()

Embedding vector를 사용하는 2가지 방법

  • 1) Embedding Layer를 만든 뒤, 학습하기

    \(\rightarrow\) nn.Embedding()

  • 2) pre-traeind Word Embedding Vector 사용하기


알아야 할 것들

  • Embedding Layer의 input이 되기 위해선, 모든 단어들이 Integer Encoding (정수 인코딩)이 되어 있어야!

    ( 단어 \(\rightarrow\) 단어 정수 idx\(\rightarrow\) Embedding Layer 통과 \(\rightarrow\) Dense Vector )

..


  • 1) data 준비하기 & Integer Encoding
train_data = 'you need to know how to code'

vocab_list = set(train_data.split()) 

vocab = {tkn: i+2 for i, tkn in enumerate(vocab_list)}
vocab['<unk>'] = 0
vocab['<pad>'] = 1


  • 2) Embedding Table 만들기 ( 아직 학습되지 않은 상태 )

[ 인자 소개 ]

  • num_embeddings : input dimension의 크기 ( = vocabulary 고유 단어 개수 )

  • embedding_dim : output dimension의 크기

    ( 원하는 embedding vector의 dimension )

  • padding_idx : <pad>의 인덱스를 알려줌

import torch.nn as nn
embedding_layer = nn.Embedding(num_embeddings = len(vocab), 
                               embedding_dim = 3,
                               padding_idx = 1)
embedding_layer.weight
#------------------------------------------------------------
Parameter containing:
tensor([[-0.1778, -1.9974, -1.2478],
        [ 0.0000,  0.0000,  0.0000],
        [ 1.0921,  0.0416, -0.7896],
        [ 0.0960, -0.6029,  0.3721],
        [ 0.2780, -0.4300, -1.9770],
        [ 0.0727,  0.5782, -3.2617],
        [-0.0173, -0.7092,  0.9121],
        [-0.4817, -1.1222,  2.2774]], requires_grad=True)


37. Pre-trained Word Embedding

nn.Embedding()을 사용하는 것보다,

다른 data로 이미 사전 훈련되어 있는 Embedding vector를 사용하는 것이 더 나을 수도!


  • 1) 사전학습된 weight 불러오기 ( = Embedding vector )

    ( 확인으로 수행한 것일 뿐, 꼭 이렇게 불러올 필요는 없다 )

from gensim.models import KeyedVectors
pretrained_w2v = KeyedVectors.load_word2vec_format('eng_w2v')


  • 2) 데이터 불러오기
from torchtext import data, datasets

TEXT = data.Field(sequential=True, batch_first=True, lower=True)
LABEL = data.Field(sequential=False, batch_first=True)
trainset, testset = datasets.IMDB.splits(TEXT, LABEL)
  • 3) 사전 학습된 weight를 초기값으로 설정하기
    • build_vocab(vectors=xxx)
    • nn.Embedding.from_pretrained
from torchtext.vocab import Vectors
pretrained_vectors = Vectors(name="eng_w2v")

TEXT.build_vocab(trainset, 
                 vectors=pretrained_vectors, 
                 max_size=10000, 
                 min_freq=10) 

embedding_layer = nn.Embedding.from_pretrained(TEXT.vocab.vectors, freeze=False)

( example : “this”의 Embedding Vector는? )

TEXT.vocab.vectors[10]
#------------------------------------------------------------------------
tensor([-0.4860,  2.4053, -0.8451, -0.6362, -0.0984,  0.9017, -1.8017, -2.4730,
         ... 중략 ...
         0.0685,  2.3219, -1.2140, -1.2776])
embedding_layer(torch.LongTensor([10])
#------------------------------------------------------------------------	
tensor([[-0.4860,  2.4053, -0.8451, -0.6362, -0.0984,  0.9017, -1.8017, -2.4730,
          ... 중략 ...
          0.0685,  2.3219, -1.2140, -1.2776]], grad_fn=<EmbeddingBackward>)


38. Pre-trained Word Embedding 종류들

  • fasttext.en.300d
  • fasttext.simple.300d
  • glove.42B.300d
  • glove.840B.300d
  • glove.twitter.27B.25d
  • glove.twitter.27B.50d
  • glove.twitter.27B.100d
  • glove.twitter.27B.200d
  • glove.6B.50d
  • glove.6B.100d
  • glove.6B.200d
  • glove.6B.300d


39. RNN basic

.


\(\begin{aligned} &h_{t}=\tanh \left(W_{x} x_{t}+W_{h} h_{t-1}+b\right) \\ &y_{t}=f\left(W_{y} h_{t}+b\right) \end{aligned}\).


[ 차원 정리 ]

\(\begin{aligned} &x_{t}:(d \times 1) \\ &W_{x}:\left(D_{h} \times d\right) \\ &W_{h}:\left(D_{h} \times D_{h}\right) \\ &h_{t-1}:\left(D_{h} \times 1\right) \\ &b:\left(D_{h} \times 1\right) \end{aligned}\).


40. Pytorch RNN ( LSTM )

LSTM은 , nn.RNN 대신 nn.LSTM만 사용하면 전부 동일하다!

import torch
import torch.nn as nn

input_size = 5  # d
hidden_size = 8 # Dh


Example

  • 크기: (batch_size, time_steps, input_size)
inputs = torch.Tensor(1, 10, 5)


RNN

cell_shallow = nn.RNN(input_size, hidden_size, batch_first=True)
cell_deep = nn.RNN(input_size, hidden_size,  num_layers = 2, batch_first=True)
cell_deep_bi = nn.RNN(input_size, hidden_size,  num_layers = 2, 
                      batch_first=True,bidirectional = True)


Result

y, h = cell_shallow(inputs)
print(y.shape)
print(h.shape)
#-------------------
torch.Size([1, 10, 8]) # 모든 time step에서의 output ( hidden state들 )
torch.Size([1, 1, 8])  # 마지막 time step에서의 output ( hidden state )
y, h = cell_deep(inputs)
print(y.shape)
print(h.shape)
#-------------------
torch.Size([1, 10, 8]) # 모든 time step에서의 output ( hidden state들 ) ... 불변
torch.Size([2, 1, 8])  # 마지막 time step에서의 output ( hidden state ) ... 1x(2)
y, h = cell_deep_bi(inputs)
print(y.shape)
print(h.shape)
#-------------------
torch.Size([1, 10, 16]) # 모든 time step에서의 output ( hidden state들 ) .. 16=8x2
torch.Size([4, 1, 8])  # 마지막 time step에서의 output ( hidden state ) ... 1x(2x2)


41. Pytorch CharRNN ( 텍스트 생성 )

  • 1) import packages
import torch
import torch.nn as nn
import torch.optim as optim
import numpy as np


  • 2) dataset
input_str = 'apple'
label_str = 'pple!'

vocab = sorted(list(set(input_str+label_str)))
vocab_size = len(char_vocab)


  • 3) hyperparameter
input_size = vocab_size 
hidden_size = 5
output_size = 5

learning_rate = 0.1


  • 4) Integer Encoding ( + inverse 버전)
char_to_index = dict((char, idx) for idx, char in enumerate(vocab))

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)
print(y_data)
#---------------------------------------
[1, 4, 4, 3, 2] # a, p, p, l, e에 해당
[4, 4, 3, 2, 0] # p, p, l, e, !에 해당


( Pytorch는 3차원 tensor를 input으로 받기 때문에 ( 맨 앞에 batch size) , batch dimension을 추가해준다 & one-hot encoding해준다 )

x_data = [x_data]
y_data = [y_data]

x_one_hot = [np.eye(vocab_size)[x] for x in x_data]
print(x_one_hot)
#---------------------------------------
[array([[0., 1., 0., 0., 0.],
       [0., 0., 0., 0., 1.],
       [0., 0., 0., 0., 1.],
       [0., 0., 0., 1., 0.],
       [0., 0., 1., 0., 0.]])]


X = torch.FloatTensor(x_one_hot)
Y = torch.LongTensor(y_data)
X.shape,Y.shape
#---------------------------------------
torch.Size([1, 5, 5]), torch.Size([1, 5])


  • 5) 모델 생성
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)
        self.fc = torch.nn.Linear(hidden_size, output_size, bias=True) 

    def forward(self, x): 
        x, _status = self.rnn(x)
        x = self.fc(x)
        return x
net = Net(input_size, hidden_size, output_size)
outputs = net(X)

print(outputs.shape)
print(outputs.view(-1, input_size).shape)
#-----------------------------
torch.Size([1, 5, 5]) # 3차원 ( batch 차원 O ) 
torch.Size([5, 5])    # 2차원 ( batch 차원 X )

위에서 size(5,5)의 의미?

  • 앞의 5 : time step
  • 뒤의 5 : 출력의 dimension


  • 2) loss function & optimizer
loss_fn = torch.nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), learning_rate)


  • 3) train
    • view : Batch 차원 제거 위해
for i in range(100):
    outputs = net(X)
    loss = loss_fn(outputs.view(-1, input_size), Y.view(-1)) 
    #----------------------
    # zbs
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
	#----------------------
    print(i, "loss: ", loss.item())


42. Pytorch Word RNN ( 텍스트 생성 )

RNN의 입력 단위가 “문자” 가 아닌 “단어” 로

\(\rightarrow\) pytorch의 nn.Embedding()을 사용하기!


  • 1) dataset
sentence = "Repeat is the best medicine for memory".split()
vocab = list(set(sentence))
print(vocab)
#----------------------------------------------
['best', 'memory', 'the', 'is', 'for', 'medicine', 'Repeat']


  • 2) Integer Encoding
word2index = {tkn: i for i, tkn in enumerate(vocab, 1)}  
word2index['<unk>']=0
index2word = {v: k for k, v in word2index.items()}


  • 3) build_data 함수
    • word \(\rightarrow\) integer 인코딩
    • X,y반환
    • unsqueeze(0)하는 이유 : batch 차원 추가 위해
    • 정수 encoding이기 때문에, torch.LongTensor를 사용
def build_data(sentence, word2index):
    encoded = [word2index[token] for token in sentence]
    X, Y = encoded[:-1], encoded[1:] 
    X = torch.LongTensor(input_seq).unsqueeze(0) 
    Y = torch.LongTensor(label_seq).unsqueeze(0) 
    return X, Y
X, Y = build_data(sentence, word2index)


  • 4) hyperparameter
vocab_size = len(word2index)  # vocabulary 개수 ( 고유 단어 개수 + 1)
input_size = 5  
hidden_size = 20 


  • 5) 모델
class Net(nn.Module):
    def __init__(self, vocab_size, input_size, hidden_size, batch_first=True):
        super(Net, self).__init__()
        self.embedding_layer = nn.Embedding(num_embeddings=vocab_size, 
                                            embedding_dim=input_size)
        self.rnn_layer = nn.RNN(input_size, hidden_size,
                                batch_first=batch_first)
        self.linear = nn.Linear(hidden_size, vocab_size)

    def forward(self, x):
        y = self.embedding_layer(x)
        y, h = self.rnn_layer(output)
        y = self.linear(y)
        return y.view(-1, y.size(2))


model = Net(vocab_size, input_size, hidden_size, batch_first=True)

loss_fn = nn.CrossEntropyLoss()
optimizer = optim.Adam(params=model.parameters())


  • 6) 정수 list \(\rightarrow\) 단어 list로 변환해주는 함수
decode = lambda y: [index2word.get(x) for x in y]


  • 7) train
for step in range(201):
    Y_pred = model(X)
    loss = loss_fn(Y_pred, Y.view(-1))
    
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

    if step % 40 == 0:
        print("[{:02d}/201] {:.4f} ".format(step+1, loss))
        pred = output.softmax(-1).argmax(-1).tolist()
        print(" ".join(["Repeat"] + decode(pred)))
        print()
[01/201] 2.0184 
Repeat the the the the medicine best

[41/201] 1.3917 
Repeat is the best medicine for memory

[81/201] 0.7013 
Repeat is the best medicine for memory

[121/201] 0.2992 
Repeat is the best medicine for memory

[161/201] 0.1552 
Repeat is the best medicine for memory

[201/201] 0.0964 
Repeat is the best medicine for memory


43. Pytorch Sentiment Classification (다대일)

  • 1) import packages
import os
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchtext import data, datasets
import random

SEED = 5
random.seed(SEED)
torch.manual_seed(SEED)


  • 2) hyperparameters
BATCH_SIZE = 64
lr = 0.001
EPOCHS = 10

USE_CUDA = torch.cuda.is_available()
DEVICE = torch.device("cuda" if USE_CUDA else "cpu")


  • 3) Field 정의하기

    • torchtext.dataField클래스 사용

      ( 전처리를 위한 객체 사용 )

TEXT = data.Field(sequential=True, batch_first=True, lower=True)
LABEL = data.Field(sequential=False, batch_first=True)


  • 4) dataset 불러오기

    • torchtext.datasets에서 제공하는 IMDB 데이터셋 아용

    • default 비율 = (8:2)

trainset, testset = datasets.IMDB.splits(TEXT, LABEL)


( 첫 번째 sample 확인해보기 )

vars(trainset[0])
#------------------------------------------------------------
{'text': ['if', 'you', 'like', 'jamie', 'foxx,(alvin', 'sanders),"date', 'from', 'hell",\'01,', 'you', 'will', 'love', 'his', 'acting', 'as', 'a', 'guy', 'who', 'never', 'gets', 'an', 'even', 'break', 'in', 'life', 'and', 'winds', 'up', 'messing', 'around', 'with', 'shrimp,', '(jumbo', 'size)', 'and', 'at', 'the', 'same', 'time', 'lots', 'of', 'gold', 'bars.', 'alvin', 'sanders', 'has', 'plenty', 'of', 'fbi', 'eyes', 'watching', 'him', 'and', 'winds', 'up', 'getting', 'hit', 'by', 'a', 'brick', 'in', 'the', 'jaw,', 'and', 'david', 'morse,(edgar', 'clenteen),', '"hack"', "'02", 'tv', 'series,', 'decides', 'to', 'zero', 'in', 'on', 'poor', 'alvin', 'and', 'use', 'him', 'as', 'a', 'so', 'called', 'fish', 'hook', 'to', 'attract', 'the', 'criminals.', 'there', 'is', 'lots', 'of', 'laughs,', 'drama,', 'cold', 'blood', 'killings', 'and', 'excellent', 'film', 'locations', 'and', 'plenty', 'of', 'expensive', 'cars', 'being', 'sent', 'to', 'the', 'junk', 'yard.', 'jamie', 'foxx', 'and', 'david', 'morse', 'were', 'outstanding', 'actors', 'in', 'this', 'film', 'and', 'it', 'was', 'great', 'entertainment', 'through', 'out', 'the', 'entire', 'picture.'],
'label': 'pos'}


  • 5) Vocabulary 만들기
TEXT.build_vocab(trainset, min_freq=5) # 단어 집합 생성
LABEL.build_vocab(trainset)

vocab_size = len(TEXT.vocab)
n_classes = 2


  • 6) Data Loader 생성
    • train : val : test = 0.8x0.8 : 0.8x0.2 : 0.2
trainset, valset = trainset.split(split_ratio=0.8)

( 단어를 index로 대체하여 사용하는 BucketIterator )

train_iter, val_iter, test_iter = data.BucketIterator.splits(
    (trainset, valset, testset), 
    batch_size=BATCH_SIZE,
    shuffle=True, repeat=False)

( 크기 확인 )

print('훈련 데이터의 미니 배치의 개수 : {}'.format(len(train_iter)))
print('테스트 데이터의 미니 배치의 개수 : {}'.format(len(test_iter)))
print('검증 데이터의 미니 배치의 개수 : {}'.format(len(val_iter)))
#----------------------------------------------------------------
훈련 데이터의 미니 배치의 개수 : 313
테스트 데이터의 미니 배치의 개수 : 391
검증 데이터의 미니 배치의 개수 : 79


  • 7) 첫 번째 minibatch 확인하기
    • size = ( batch size x 해당 배치 내에서 최대 길이 )
    • “해당 배치 내에서 최대 길이”이므로, batch별로 크기가 다를 수 있다
# 1번째
batch = next(iter(train_iter))
print(batch.text.shape)
#----------------------------------------------------------------
torch.Size([64, 968])
# 2번째
batch = next(iter(train_iter))
print(batch.text.shape)
#----------------------------------------------------------------
torch.Size([64, 873])


  • 8) 모델
    • _init_state : 특정 state를 0으로 초기화
class GRU(nn.Module):
    def __init__(self, n_layers, hidden_dim, n_vocab, embed_dim, n_classes, dropout_p=0.2):
        super(GRU, self).__init__()
        self.n_layers = n_layers
        self.hidden_dim = hidden_dim
        self.embed = nn.Embedding(n_vocab, embed_dim)
        self.dropout = nn.Dropout(dropout_p)
        self.gru = nn.GRU(embed_dim, self.hidden_dim,
                          num_layers=self.n_layers,
                          batch_first=True)
        self.out = nn.Linear(self.hidden_dim, n_classes)

    def forward(self, x):
        x = self.embed(x)
        h_0 = self._init_state(batch_size=x.size(0))
        x, _ = self.gru(x, h_0)  # GRU output의 shape : (batch size, seq length, hidden dim)
        h_t = x[:,-1,:] # 마지막 time step것만 가져오기
        h_t=self.dropout(h_t)
        logit = self.out(h_t)  # (배치 batch, hidden dim) -> (배치 batch, output dim)
        return logit

    def _init_state(self, batch_size=1):
        weight = next(self.parameters()).data
        return weight.new(self.n_layers, batch_size, self.hidden_dim).zero_()
model = GRU(1, 256, vocab_size, 128, n_classes, 0.5).to(DEVICE)
optimizer = torch.optim.Adam(model.parameters(), lr=lr)


  • 9) train & evaluate 함수
def train(model, optimizer, train_iter):
    model.train()
    for _, batch in enumerate(train_iter):
        x, y = batch.text.to(DEVICE), batch.label.to(DEVICE)
        y.data.sub_(1)  # label을 0/1로 치환
        #-------------------------------
        logit = model(x)
        loss = F.cross_entropy(logit, y)
        #-------------------------------
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
def evaluate(model, val_iter):
    model.eval()
    corrects, total_loss = 0, 0
    for batch in val_iter:
        x, y = batch.text.to(DEVICE), batch.label.to(DEVICE)
        y.data.sub_(1) 
        #-------------------------------
        logit = model(x)
        loss = F.cross_entropy(logit, y, reduction='sum')
        #-------------------------------
        total_loss += loss.item()
        corrects += (logit.max(1)[1].view(y.size()).data == y.data).sum()
    size = len(val_iter.dataset) # batch의 개수
    avg_loss = total_loss / size
    avg_accuracy = 100.0 * corrects / size
    return avg_loss, avg_accuracy


  • 10) 학습하기 & best weight 저장하기
best_val_loss = None

for e in range(1, EPOCHS+1):
    # (1) train
    train(model, optimizer, train_iter)
    
    # (2) evaluate
    val_loss, val_accuracy = evaluate(model, val_iter)
    print("[Epoch: %d] val loss : %5.2f | val accuracy : %5.2f" % (e, val_loss, val_accuracy))

	# (3) save models
    if not best_val_loss or val_loss < best_val_loss:
        if not os.path.isdir("snapshot"):
            os.makedirs("snapshot")
        torch.save(model.state_dict(), './snapshot/txtclassification.pt')
        best_val_loss = val_loss


  • 11) best weight 불러오기
# model = GRU(1, 256, vocab_size, 128, n_classes, 0.5).to(DEVICE)
model.load_state_dict(torch.load('./snapshot/txtclassification.pt'))

test_loss, test_acc = evaluate(model, test_iter)


44. Pytorch Sequence Labeling (다대다)

  • 입력 시퀀스 \(\mathrm{X}=\left[x_{1}, x_{2}, x_{3}, \ldots, x_{n}\right]\)
  • 레이블 시퀀스 \(\mathrm{y}=\left[y_{1}, y_{2}, y_{3}\right.\) \(\left.\ldots, y_{n}\right]\) 를 각각 부여

.

task : 품사 태깅(PoS tagging)

  • 1) packages 불러오기
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchtext import data
from torchtext import datasets
import time
import random

SEED = 1234
random.seed(SEED)
torch.manual_seed(SEED)

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')


  • 2) Field 정의하기
TEXT = data.Field(lower = True)
UD_TAGS = data.Field(unk_token = None)
PTB_TAGS = data.Field(unk_token = None)


  • 3) Dataset 만들기
    • torchtext.datasets에서 제공하는 UDPOS 데이터셋
    • train/valid/test로 나눈다
fields = (("text", TEXT), ("udtags", UD_TAGS), ("ptbtags", PTB_TAGS))

train_data, valid_data, test_data = datasets.UDPOS.splits(fields)
print(f"훈련 샘플의 개수 : {len(train_data)}")
print(f"검증 샘플의 개수 : {len(valid_data)}")
print(f"테스트 샘플의 개수 : {len(test_data)}")
#-----------------------------------------
훈련 샘플의 개수 : 12543
검증 샘플의 개수 : 2002
테스트 샘플의 개수 : 2077


  • 4) label ( = 품사 ) 확인하기
vars(train_data.examples[0])['udtags']
#----------------------------------------------------
['PROPN', 'PUNCT', 'PROPN', 'PUNCT', 'ADJ', 'NOUN', 'VERB', 'PROPN', 'PROPN', 'PROPN', 'PUNCT', 'PROPN', 'PUNCT', 'DET', 'NOUN', 'ADP', 'DET', 'NOUN', 'ADP', 'DET', 'NOUN', 'ADP', 'PROPN', 'PUNCT', 'ADP', 'DET', 'ADJ', 'NOUN', 'PUNCT']


  • 5) Vocabulary 만들기
    • pre-trained Glove 사용
TEXT.build_vocab(train_data, min_freq = 5, vectors = "glove.6B.100d")
UD_TAGS.build_vocab(train_data)
PTB_TAGS.build_vocab(train_data)


  • 6) Data Loader 만들기
BATCH_SIZE = 64

train_iterator, valid_iterator, test_iterator = data.BucketIterator.splits(
    (train_data, valid_data, test_data), 
    batch_size = BATCH_SIZE,
    device = device)


  • 7) 첫 번째 batch 확인
    • size = (46,64)
      • 46 : 해당 batch내의 최대 문장 길이
      • 64 : batch_first=False이므로, 두 번째 차원이 batch size(=64)이다
batch = next(iter(train_iterator))
batch.text.shape
#----------------------------------------
torch.Size([46, 64])
batch.text
#----------------------------------------
tensor([[ 732,  167,    2,  ...,    2,   59,  668],
        [  16,  196,  133,  ..., 2991,   46,    1],
        [   1,   29,   48,  ..., 1582,   12,    1],
        ...,
        [   1,    1,    1,  ...,    1,    1,    1],
        [   1,    1,    1,  ...,    1,    1,    1],
        [   1,    1,    1,  ...,    1,    1,    1]], device='cuda:0')


  • 8) Model
class RNNPOSTagger(nn.Module):
    def __init__(self, vocab_size, embedding_dim, hidden_dim, output_dim, n_layers, bidirectional, dropout): 
        super().__init__()

        self.embedding = nn.Embedding(vocab_size, embedding_dim)
        self.rnn = nn.LSTM(embedding_dim, hidden_dim, num_layers = n_layers, bidirectional = bidirectional)
        self.fc = nn.Linear(hidden_dim * 2 if bidirectional else hidden_dim, output_dim)        
        self.dropout = nn.Dropout(dropout)

    def forward(self, text):
        # (크기) text = [sent len, batch size]
        
        # (크기) embedded = [sent len, batch size, emb dim]
        embedded = self.dropout(self.embedding(text))

        # (크기) output = [sent len, batch size, hid dim * n directions]
        outputs, (hidden, cell) = self.rnn(embedded)

        # (크기) predictions = [sent len, batch size, output dim]
        predictions = self.fc(self.dropout(outputs))

        return predictions


  • 9) Hyperparameters
INPUT_DIM = len(TEXT.vocab) # vocabulary의 개수
EMBEDDING_DIM = 100
HIDDEN_DIM = 128
OUTPUT_DIM = len(UD_TAGS.vocab)

N_LAYERS = 2
BIDIRECTIONAL = True

DROPOUT = 0.25
model = RNNPOSTagger(INPUT_DIM, 
                     EMBEDDING_DIM, 
                     HIDDEN_DIM, 
                     OUTPUT_DIM, 
                     N_LAYERS, 
                     BIDIRECTIONAL, 
                     DROPOUT)


  • 10) pre-trained embedding 사용하기
pretrained_embeddings = TEXT.vocab.vectors
model.embedding.weight.data.copy_(pretrained_embeddings)

# unknown(0) & padding(1) 토큰 추가하기
UNK_IDX = TEXT.vocab.stoi[TEXT.unk_token]
PAD_IDX = TEXT.vocab.stoi[TEXT.pad_token]
model.embedding.weight.data[UNK_IDX] = torch.zeros(EMBEDDING_DIM) # 0번 임베딩 벡터에는 0값을 채운다.
model.embedding.weight.data[PAD_IDX] = torch.zeros(EMBEDDING_DIM) #


  • 11) Loss Function & Optimizer
    • padding token은 loss 계산에서 제외!
loss_fn = nn.CrossEntropyLoss(ignore_index = TAG_PAD_IDX)
optimizer = optim.Adam(model.parameters())
model = model.to(device)
loss_fn = loss_fn.to(device)


  • 12) example prediction
    • size (46,64,18)
      • 46 : 1번째 batch의 sequence 길이 ( = 최대 문장 길이 )
      • 64 : batch size
      • 18 : num_classes
prediction = model(batch.text)

print(prediction.shape)
#------------------------------------
torch.Size([46, 64, 18])
prediction = prediction.view(-1, prediction.shape[-1])

print(prediction.shape)
print(batch.udtags.view(-1).shape)
#------------------------------------
torch.Size([2944, 18])
torch.Size([2944])


  • 13) accuracy 계산 함수
def categorical_accuracy(y_pred, y_real_idx, tag_pad_idx):
    y_pred_idx = y_pred.argmax(dim = 1, keepdim = True)
    mask = (y_real != tag_pad_idx).nonzero()
    y_pred_idx=y_pred_idx[mask]
    y_real_idx=y_real_idx[mask]
    correct = y_pred_idx.squeeze(1).eq(y_real_idx)
    return correct.sum() / torch.FloatTensor([y_real_idx.shape[0]])


  • 14) train & evaluation 함수

[size]

  • text = [sent len, batch size]
  • predictions = [sent len, batch size, output dim] \(\rightarrow\) predictions = [sent len * batch size, output dim]

  • tags = [sent len, batch size] \(\rightarrow\) [sent len * batch_size]
def train(model, iterator, optimizer, loss_fn, tag_pad_idx):
    epoch_loss = 0
    epoch_acc = 0
    
    model.train()
    for batch in iterator:
        X = batch.text
        Y = batch.udtags
		#-----------------------------------------
        Y_pred = model(X)
        Y_pred = Y_pred.view(-1, Y_pred.shape[-1])
        Y = Y.view(-1) 
        loss = loss_fn(Y_pred, Y)
		#-----------------------------------------
        optimizer.zero_grad()
		loss.backward()
		optimizer.step()
        #-----------------------------------------
        acc = categorical_accuracy(Y_pred, Y, tag_pad_idx)
        epoch_loss += loss.item()
        epoch_acc += acc.item()

    return epoch_loss / len(iterator), epoch_acc / len(iterator)
def evaluate(model, iterator, loss_fn, tag_pad_idx):
    epoch_loss = 0
    epoch_acc = 0

    model.eval()
    with torch.no_grad():

        for batch in iterator:
            X = batch.text
	        Y = batch.udtags
			#-----------------------------------------
            Y_pred = model(X)
            Y_pred = Y_pred.view(-1, Y_pred.shape[-1])
            Y = Y.view(-1)
            loss = loss_fn(predictions, tags)
			#-----------------------------------------
            # NO UPDATE
            #-----------------------------------------
            acc = categorical_accuracy(Y_pred, Y, tag_pad_idx)
            epoch_loss += loss.item()
            epoch_acc += acc.item()

    return epoch_loss / len(iterator), epoch_acc / len(iterator)


  • 15) 학습하기
N_EPOCHS = 10

best_valid_loss = float('inf')

for epoch in range(N_EPOCHS):
    train_loss, train_acc = train(model, train_iterator, optimizer, loss_fn, TAG_PAD_IDX)
    valid_loss, valid_acc = evaluate(model, valid_iterator, loss_fn, TAG_PAD_IDX)

    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss
        torch.save(model.state_dict(), 'tut1-model.pt')

    print(f'Epoch: {epoch+1:02}')
    print(f'\tTrain Loss: {train_loss:.3f} | Train Acc: {train_acc*100:.2f}%')
    print(f'\t Val. Loss: {valid_loss:.3f} |  Val. Acc: {valid_acc*100:.2f}%')