관리 메뉴

Bull

[DL] LSTM (Long Short-Term Memory) | study books 본문

Artificial Intelligence/Deep Learning

[DL] LSTM (Long Short-Term Memory) | study books

Bull_ 2024. 9. 28. 15:06

LSTM (Long Short-Term Memory)

LSTM 특징


  • 1997년 셉 호흐라이터(Sepp Hochreiter)와 유르겐 슈미트후버(Juergen Schmidhuber)가 제안한 알고리즘
  • RNN의 기울기 소실, 기억력 부족 문제를 해결
  • 기존 RNN은 학습 데이터의 크기가 클수록 학습한 정보가 충분히 전달되지 않음
  • tanh, ReLU 등 기울기 소실 혹은 폭주 문제 발생.
  • 메모리 셀과 게이트 구조를 도입하여 이를 해결.

LSTM 구조


https://www.google.com/url?sa=i&url=https%3A%2F%2Fsooftware.io%2Flstm_gru%2F&psig=AOvVaw2r_jzq3k0s4TgAOEx_RT80&ust=1727585011849000&source=images&cd=vfe&opi=89978449&ved=0CBQQjRxqFwoTCIjsjOrp5IgDFQAAAAAdAAAAABAu

 

  • 셀 상태, 망각 게이트, 기억 게이트, 출력 게이트

$$f_t = \sigma (W^{(f)}x x_t+W^{(f)}_h h{t-1} + b^{(f)})$$

1. 망각 게이트 $f$

  • 현재 $x_t$에서 이전 은닉 상태 $h_{t-1}$ 을 받아서 이전 셀 상태를 기억할지 말지 결정한다.
  • $W^{(f)}_x$ : 입력 값 가중치
  • $W^{(f)}_h$ : 은닉 상태 가중치
  • 1에 가까우면 이전 셀을 기억하고 0에 가까우면 정보를 삭제한다.

$$ g_i = tanh(W^{(g)}x x_t + W^{(g)}_x h{t-1} + b^{(g)}) $$
$$ i_i = sigmoid(W^{(i)}x x_t + W^{(i)}_x h{t-1} + b^{(i)}) $$

2. 기억 게이트 $g, i$

  • $g_i$ : -1~1 사이의 값을 가지고 이전 시점의 은닉 상태와 현재 시점 입력 값이 [-1,1] 사이에 존재한다. 하지만 얼마나 기억할 지 제어하기가 어렵다.
  • $i_i$ : 새로운 은닉 상태를 기억한다. [0,1] 값을 가지므로 현재 시점에서 얼마나 많은 정보를 기억할지 결정하는 가중치 역할을 한다. 1에 가까울 수록 많이 기억, 0에 가까울수록 망각.

$$ c_t = f_t \circ c_{t-1} + g_i \circ i_i$$
3. 메모리 셀 $c$

  • 현재 시점의 메모리 셀을 나타낸다. 즉 이전 시점의 메모리 셀을 얼마나 유지할지, 현재 시점의 새로운 정보를 얼마나 받아들일지 결정한다.
  • 아다마르 곱은 원소별 곱셈이다.
  • 이전 은닉상태와 현재 입력값의 결과인 $f$ 와 이전 셀의 연산을 현재 입력값과 이전 은닉 상태를 sig와 tanh 연산을 수행한 것을 다시 아다마르곱을 통해 나온 결과값을 합친다.

 

$$ o_t = \sigma(W^{(o)}x x_t + W^{(o)}_x h{t-1} + b^{(o)}) $$
$$ h_t = o_t \circ tanh(c_t)$$

4. 출력 게이트

 

  • 현재 시점의 은닉 상태를 제어한다. 망각 게이트에서 수식과 동일하며 시그모이드를 사용한다.
  • 최종적으로 은닉 상태는 현재 메모리 셀과 다시 $o_t$와 아다마르 연산을 통해 출력된다.

CODE : LSTM


LSTM class


lstm = nn.LSTM(
    input_size,
    hidden_size,
    num_layers=1,
    bias=False,
    batch_first=True,
    dropout=0,
    bidirectional=False,
    proj_size=0
)
  • proj_size : 투사 크기는 매개 변수를 사용해 장단기 메모리 계층의 출력에 대한 선형 투사 크기를 결정.
    0보다 큰 경우 은닉 상태를 선형 투사를 통해 다른 차원으로 매핑. 출력 차원을 줄이거나 변환가능.
    0이라면 은닉 상태의 차원을 변환하지 않고 그대로 유지.LSTM 피쳐별 크기

 

import torch
from torch import nn


input_size = 128
ouput_size = 256
num_layers = 3
bidirectional = True
proj_size = 64

model = nn.LSTM(
    input_size=input_size,
    hidden_size=ouput_size,
    num_layers=num_layers,
    batch_first=True,
    bidirectional=bidirectional,
    proj_size=proj_size,
)

batch_size = 4
sequence_len = 6

inputs = torch.randn(batch_size, sequence_len, input_size)
# proj_size 출력 차원을 조정함. 문장으로 치면 4단어 출력을 투영하여 3 단어로 이루어진 문장으로 나옴.
h_0 = torch.rand(
    num_layers * (int(bidirectional) + 1),
    batch_size,
    proj_size if proj_size > 0 else ouput_size,
)
c_0 = torch.rand(num_layers * (int(bidirectional) + 1), batch_size, ouput_size)

outputs, (h_n, c_n) = model(inputs, (h_0, c_0))

print(outputs.shape) # torch.Size([4, 6, 128])
print(h_n.shape) # torch.Size([6, 4, 64])
print(c_n.shape) # torch.Size([6, 4, 256])

RNN과 LSTM 임베딩 비교


분류기 : SentenceClassifier

from torch import nn


class SentenceClassifier(nn.Module):
    def __init__(
        self,
        n_vocab,
        hidden_dim,
        embedding_dim,
        n_layers,
        dropout=0.5,
        bidirectional=True,
        model_type="lstm"
    ):
        super().__init__()

        self.embedding = nn.Embedding(
            num_embeddings=n_vocab,
            embedding_dim=embedding_dim,
            padding_idx=0
        )
        if model_type == "rnn":
            self.model = nn.RNN(
                input_size=embedding_dim,
                hidden_size=hidden_dim,
                num_layers=n_layers,
                bidirectional=bidirectional,
                dropout=dropout,
                batch_first=True,
            )
        elif model_type == "lstm":
            self.model = nn.LSTM(
                input_size=embedding_dim,
                hidden_size=hidden_dim,
                num_layers=n_layers,
                bidirectional=bidirectional,
                dropout=dropout,
                batch_first=True,
            )

        if bidirectional:
            self.classifier = nn.Linear(hidden_dim * 2, 1)
        else:
            self.classifier = nn.Linear(hidden_dim, 1)
        self.dropout = nn.Dropout(dropout)

    def forward(self, inputs):
        embeddings = self.embedding(inputs)
        output, _ = self.model(embeddings)
        last_output = output[:, -1, :]
        last_output = self.dropout(last_output)
        logits = self.classifier(last_output)
        return logits
  • Custom class로 rnn과 lstm을 입력 받아 문장을 분류하는 모델이다.
  • output[:, -1, :] : 마지막 시점만 활용한다. 왜냐하면 최종 은닉 상태 이기 때문. 이 은닉 상태가 의미하는 바는 문장 전체를 하나의 벡터로 표현것과 같다.
    [배치 크기, 시퀀스 길이, 출력 크기]
  • 그 뒤에 드랍아웃은 과적합을 방지하기 위해 사용.
  • 마지막으로 양방향을 체크하여 classifier의 출력층을 결정한다.

그 이외 CODE


원본 git source

  • 데이터 셋
  • 토큰화 및 단어 사전 구축
  • 정수 인코딩 및 패딩
  • 데이터 로더 적용
  • 손실함수 최적화 정의
  • 모델 학습 및 테스트

LSTM 정확도

Train Loss 0 : 0.4722405672073364 
Train Loss 500 : 0.32604282127763934 
Train Loss 1000 : 0.3257676584350241 
Train Loss 1500 : 0.3258801227352327 
Train Loss 2000 : 0.3244019852544384 
Train Loss 2500 : 0.3251704181008628 
Val Loss : 0.3984528498622937, Val Accuracy : 0.8176
  1. 현재 모델은 긍/부정을 분류하므로 이진 교차 엔트로피를 적용한다.
    BCEWithLogitLoss : BCELoss, Sigmoid 클래스의 결합된 형태이다.
  2. RMSProp(Root Mean Sqaure Propagation) : 모든 기울기를 누적하지 않고 지수 가중 이동 평균(Exponentially Weighted Moving Average, EWMA)을 사용한다.
    • 기울기 제곱 값이 평균보다 작아진면 학습률이 증가.
    • 그 반대는 학습률 감소.
    • 기울기가 큰 경우 빠른 수렴을 보인다.
    • 작은 경우 더 작은 학습률을 유지하여 안정적인 최적화 수행.

사전 학습 임베딩을 사용한 코드
pretained git source
학습이 완료된 임베딩 가중치를 불러와서 학습을 시킬 수 있다.

 

참고 자료

https://product.kyobobook.co.kr/detail/S000209621433

 

파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습 | 윤대희 - 교보문고

파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습 | 트랜스포머는 딥러닝 분야에서 성능이 우수한 모델로 현대 인공지능 분야의 핵심 기술입니다. 트랜스포머와 비전 트랜스

product.kyobobook.co.kr