ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [PyTorch] RNN 모듈 알아보기
    AI-ML 2023. 12. 29. 06:31
    728x90
    반응형

     

    - 목차

     

    들어가며.

    PyTorch 에는 내장된 RNN 모듈이 존재합니다.

    torch.nn.RNN 클래스를 사용하여 손쉽게 RNN 모듈을 사용 가능합니다.

    다만 PyTorch 의 내장 RNN 모듈이 사용법이 간단히 와닿지 않는 경향이 있어서 이번 글을 통해 그 사용법에 대해서 명확히 하려고 합니다.

     

    기본적인 사용법 알아보기.

    RNN 모듈은 기본적으로 아래와 같이 생성합니다.

    RNN 모듈은 필수적으로 input_size 와 hidden_size 값을 입력으로 받아야합니다.

    import torch.nn as nn
    rnn = nn.RNN(input_size=5, hidden_size=10)

     

    input_size 는 입력 텐서의 차원의 크기가 됩니다.

    예를 들어서, NLP 모델에서 단어 하나하나가 입력으로 사용된다고 가정해보겠습니다.

    그리고 사용된 모든 단어의 수가 10 일 때에 One Hot Encoding 을 적용한다면 input_size 의 값은 10이 됩니다.

    또한 주가를 예측하는 모델에서 "시가", "종가" 2개의 Feature 를 사용한다고 한다면, 이는 input_size 가 2가 됩니다.

     

    일반적으로 아래와 같은 방식으로 사용될 수 있는데요.

    sequence, batch, feature, hidden 등의 헷갈리는 용어들을 정리하는 시간을 가져보도록 하겠습니다.

    import torch
    import torch.nn as nn
    
    
    input_size = 5
    batch_size = 1
    sequence_length = 4
    hidden_size = 32
    
    rnn = nn.RNN(input_size=input_size, hidden_size=hidden_size)
    
    input_tensor = torch.ones(sequence_length, batch_size, input_size)
    hidden_tensor = torch.zeros(1, batch_size, hidden_size)
    
    output, hidden = rnn(input_tensor, hidden_tensor)

     

     

    hidden_size.

    RNN 은 내부적인 Memory Cell 을 구현하기 위해서 hidden state 를 사용합니다.

    아래의 이미지와 같은 방식으로 hidden state 는 업데이트되어 갑니다.

    업데이트의 과정에서 X0, X1, ..., Xn 에 해당하는 모든 Input Data 에 영향을 받게 되는되요.

    이러한 과정을 거치면서 hidden state 는 Memory Cell 로써 역할을 수행할 수 있게 됩니다.

     

    $$ H_{1} = tanh( W_{xh} \times X_{1} + W_{hh} \times H_{0} )$$

     

    hidden_size 는 RNN 모듈을 생성할 때에 명시적으로 그리고 필수적으로 선언해야하는 파라미터입니다.

    아래와 같은 형식으로 hidden_size 가 필요하며, hidden_size 와 input_size 를 설정한 RNN 모듈은 내부적인 weight 와 bias 를 확인할 수 있습니다.

    import torch.nn as nn
    
    hidden_size = 32
    input_size = 5
    rnn = nn.RNN(input_size=input_size, hidden_size=hidden_size)
    
    print(f'rnn.weight_hh_l0: {rnn.weight_hh_l0.shape}')
    print(f'rnn.weight_ih_l0: {rnn.weight_ih_l0.shape}')
    print(f'rnn.bias_ih_l0: {rnn.bias_ih_l0.shape}')
    print(f'rnn.bias_ih_l0: {rnn.bias_ih_l0.shape}')
    rnn.weight_hh_l0: torch.Size([32, 32])
    rnn.weight_ih_l0: torch.Size([32, 5])
    rnn.bias_ih_l0: torch.Size([32])
    rnn.bias_ih_l0: torch.Size([32])

     

    input_size.

    input_size 또한 hidden_size 와 같이 PyTorch RNN 모듈이 필수적으로 입력받는 파라미터입니다.

    input_size 와 hidden_size 를 통해서 RNN 에서 사용하는 Weight 를 생성할 수 있습니다.

    생성된 RNN 모델은 내부적으로 weight_ih_l0 이라는 Weight Tensor 를 가집니다.

    이름이 의미하는 바는 0번째 Layer 중 Input -> Hidden 으로 변형하기 위한 Weight Tensor 를 뜻합니다.

     

    $$ H_{1} = tanh( W_{xh} \times X_{1} + W_{hh} \times H_{0} )$$

    RNN 는 새로운 Output Tensor 와 Hidden State 를 생성하기 위해서 Input 과 Hidden State 의 연산을 반복적으로 수행합니다.

    Input 과 Hidden State 는 서로 Shape 가 동일하지 않기 때문에 Input Size 를 Hidden Size 로 변경하는 Matrix 연산이 필요하며,

    RNN 은 input_size 와 hidden_size 를 인자로 받아서 Weight 를 생성합니다.

     

     

    sequence length.

    RNN 이 여타 Neural Network 와의 차별점은 순환 신경망이라는 점입니다.

    즉, 신경망 내부적으로 Loop 가 존재하고 Hidden State 를 계속 업데이트하게 됩니다.

    출처 : https://www.datacamp.com/tutorial/tutorial-for-recurrent-neural-network

     

    그럼 이러한 순환 연산은 몇 차례가 수행될까요 ?

    무한히 수행되는 것은 아닐겁니다.

    순환 횟수는 바로 Sequence Length 에 의해서 결정되는데요.

    이 Sequence 가 의미하는 바는

    1. 문장을 구성하는 단어의 갯수

    2. 시계열 데이터를 구성하는 Window 의 크기

    와 같습니다.

     

    예를 들어, 문장의 Sentiment Analysis 를 위해서 10개의 단어들로 구성된 문장은 Sequence Length 가 10이  됩니다.

    그리고 주가 예측을 위해서 5일치의 주가 정보를 통해서 다음 날의 주가를 예측한다면, 이때의 Sequence Length 는 5가 됩니다.

    이처럼 Sequence Length 만큼 RNN 는 순환하며 Hidden State 를 업데이트합니다.

     

    이러한 Sequence Length 는 PyTorch RNN 에 사용되는 명시적인 파라미터는 아닙니다.

    대신 입력 텐서가 Sequence Length 만큼의 데이터를 포함하고 있어야합니다.

    예를 들어, 아래와 같이 Sequence Length 가 5 인 입력 텐서는 RNN 모듈에 의해서 torch.Size([5, 1, 32]) 인 텐서를 출력합니다.

    또한 Sequence Length 가 10인 입력 텐서는 RNN 모듈에 의해서 orch.Size([10, 1, 32]) 인 텐서를 출력합니다.

    input_size = 5
    batch_size = 1
    sequence_length = 4
    
    import torch.nn as nn
    
    hidden_size = 32
    input_size = 5
    rnn = nn.RNN(input_size=input_size, hidden_size=hidden_size)
    
    sequence_length = 5
    input_tensor = torch.ones(sequence_length, batch_size, input_size)
    hidden_tensor = torch.zeros(1, batch_size, hidden_size)
    output, hidden = rnn(input_tensor, hidden_tensor)
    print(output.shape)
    # torch.Size([5, 1, 32])
    
    
    sequence_length = 10
    input_tensor = torch.ones(sequence_length, batch_size, input_size)
    hidden_tensor = torch.zeros(1, batch_size, hidden_size)
    output, hidden = rnn(input_tensor, hidden_tensor)
    print(output.shape)
    # torch.Size([10, 1, 32])

     

    즉, 입력 텐서가 100인 Sequence Length 를 가진다면 RNN 는 100 회의 순환 연산을 수행합니다.

    Sequence Length 는 입력 텐서에 영향을 받는 요소이며,

    RNN 은 입력 텐서의 Sequence Length 에 따라서 순환하게 됩니다.

     

    Many to One RNN Model.

    만약 Many-to-One RNN Model 을 사용하게 된다면,

    RNN 의 출력 텐서에서 가장 마지막 Index 의 Tensor 를 사용하면 됩니다.

    이게 무슨의미냐하면,

    보통 입력 텐서는 Seq_num x Batch Size x Input Size 에 해당하는 3차원 텐서로 구성됩니다.

    그리고 출력 텐서는 Seq_num x Batch Size x Hidden Size 에 해당하는 3차원 텐서로 구성되는데요.

    Sequence 의 N 번째 입력 텐서의 출력 텐서는 출력 텐서의 N 번째 텐서에 해당합니다.

     

    예를 들어, "I Love You" 라는 문장을 Input Tensor 로 표현하면

    3개의 단어로 구성된 문장으므로 Sequence Length 는 3이며,

    1개의 문장이기에 Batch Size 는 1 입니다.

    그리고 Input Size 는 편의상 OneHot Encoding 한다는 가정하에 3 이라고 하겠습니다.

     

    이때에 "I" 에 해당하는 출력은 Output Tensor 의 1번째 요소가 되며,

    "Love" 에 해당하는 출력은 Output Tensor 의 2번째 요소가 됩니다.

    출처 : https://goodboychan.github.io/python/deep_learning/tensorflow-keras/2020/12/06/01-RNN-Many-to-one.html

     

    batch size.

    batch size 또한 Sequence Length 와 마찬가지로 RNN 을 구성하는 명시적인 요소는 아닙니다.

    Input Tensor 에 의해서 결정되는 요소이구요.

    일반적으로 입력 텐서의 Shape 는 (Sequence Size x Batch Size x Input Size) 와 같은 3차원으로 구성됩니다.

    그 중에서 Batch Size 는 일반적인 신경망에서 사용하는 그 Batch Size 의 개념과 동일합니다.

    한번의 Forward Propagation Step 에서 처리할 수 있는 데이터의 양을 의미하죠.

    RNN 에서 중요한 Batch Size 의 포인트는 Initial Hidden State 를 구성할 때에도 Batch Size 가 사용된다는 점일 것입니다.

    만약에 Input Tensor 의 Shape 가 5 x 3 x 9 라고 한다면,

    이는 Sequence Size 가 5 이고, Batch Size 가 3 그리고 Input Feature 의 Size 가 9 일 것입니다.

    이 때의 Initial Hidden State 는 1 x Batch Size x Hidden Size 가 됩니다.

    즉, Hidden State 에도 Batch Size 가 적용된다는 점에 유의하시면 좋을 것 같네요.

     

    num_layers.

    PyTorch 의 RNN 은 num_layers 라는 파라미터를 가집니다.

    이는 Hidden Layer 의 크기가 됩니다.

    아래의 이미지는 num_layers 의 값이 2인 상태의 RNN 구조입니다.

    Hidden Layer 의 크기가 2가 되고, 첫번째 Layer 의 결과가 두번째 Layer 의 입력값으로 사용됩니다.

     

     

     

     

    PyTorch RNN 사용해보기.

    아래의 예시는 아주 간단한 Sentiment Analysis 를 위한 RNN 예시입니다.

    I Love You 와 I Hate You 에 대한 Sentiment Analysis 을 수행하는 간단한 예시입니다.

    Batch Size 는 1, Hidden Size 는 32 으로 설정하였고

    문장을 구성하는 단어의 수가 3이므로 자연히 Sequence Length 는 3이 됩니다.

     

    import numpy as np
    import torch
    import torch.nn as nn
    
    
    class EmotionRNN(nn.Module):
    
        def __init__(self, input_size, hidden_size, output_size):
            super(EmotionRNN, self).__init__()
    
            self.rnn = nn.RNN(input_size, hidden_size)
            self.dense = nn.Linear(hidden_size, output_size)
            self.softmax = nn.Softmax(dim=1)
    
        def forward(self, input_tensor, hidden_tensor):
            rnn_output, _ = self.rnn(input_tensor, hidden_tensor)
            last_one = rnn_output[-1]
            output = self.dense(last_one)
            logit = self.softmax(output)
            return logit
    
    
    dataset = [
        ["I Love You", "Good"],
        ["I Hate You", "Bad"],
    ]
    
    word_to_index_map = {
        "I": 0,
        "Love": 1,
        "Hate": 2,
        "You": 3,
    }
    
    label_to_index_map = {
        "Good": 0,
        "Bad": 1
    }
    
    n_epoch = 10
    input_size = 4
    output_size = 2
    hidden_size = 32
    model = EmotionRNN(input_size, hidden_size, output_size)
    criterion = nn.CrossEntropyLoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    
    for epoch in range(n_epoch):
        for data in dataset:
            sentence = data[0]
            target = data[1]
    
            sentence_to_index = [word_to_index_map[word] for word in sentence.split()]
            sentence_to_one_hot_vector = [np.eye(4)[index] for index in sentence_to_index]
            input_tensor = torch.tensor(sentence_to_one_hot_vector, dtype=torch.float)
            input_tensor = input_tensor.reshape(3, 1, 4)
    
            target_to_index = label_to_index_map[target]
            target_to_one_hot_vector = np.eye(2)[target_to_index]
            target_tensor = torch.tensor(target_to_one_hot_vector, dtype=torch.float)
            target_tensor = target_tensor.reshape(1, -1)
    
            hidden_state = torch.zeros(1, 1, hidden_size, dtype=torch.float)
            prediction = model(input_tensor, hidden_state)
    
            loss = criterion(prediction, target_tensor)
            loss.backward()
            optimizer.step()
    
            print(loss.item())

     

     

     

     

     

    반응형
Designed by Tistory.