AI 부트캠프 - 18일차

Cookie Baking·2024년 10월 25일

AI 부트 캠프 TIL

목록 보기
16/42

사실 이전 LSTM 모델에서 에폭을 5로 줄여서 학습을 시켜보았는데
모든걸 부정적으로 보는 모델이 생성되었다

개선할 수 있는 방법은 없을까???

떠오른 방법과 부트 캠프가 추천하는 방법은 아래와 같다.

  • optimzer 수정 (SGD -> Adam)
  • 손실함수 수정
  • 모델 수정 (LSTM -> GRU)
  • 모델 합체 (CNN + LSTM, LSTM + Attention)

부트캠프 추천

  • 레이블 데이터를 변경 (기준이 좀 더 포괄적인 것으로)

낮은 예측률을 보이는 LSTM 모델 개선기 - 1

자 그러면 떠오른 방법의 예상되는 성능 향상률을 생각해보자

  1. 모델 수정 (LSTM -> GRU)
    솔직히 모델이 크게 달라지는 것이 아니기 때문에 크게 성능 향상을 기대하지는 않음
class GRUModel(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
        super(GRUModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        self.gru = nn.GRU(embed_dim, hidden_dim, batch_first=True)  # GRU로 변경
        self.fc = nn.Linear(hidden_dim, output_dim)

    def forward(self, text):
        embedded = self.embedding(text)
        output, hidden = self.gru(embedded)  # GRU는 hidden 상태만 반환
        return self.fc(hidden[-1])  # 마지막 타임스텝의 출력을 사용

2-1. 손실함수 수정, optimzer 수정

  • 손실함수의 경우
    해당 문제에서는 1, 2, 3, 4, 5 값을 분류하는 문제이기 때문에 MSE가 아닌 SGD가 더 적합하다고 판단함

라벨 스무딩을 이용해줄 생각임
라벨 스무딩이란?

정답이 클래스 2라면 정답 레이블은 [0, 0, 1]처럼 나타납니다. 하지만 라벨 스무딩을 적용하면 이 레이블이 [0.1, 0.1, 0.8]처럼 정답 클래스가 1이 아닌 소프트한 확률 분포로 변환됩니다.

즉, 과적합되는 것을 방지해주는 것이라고 생각하면 된다.

- optimzer 수정:

1. SGD는

배치 데이터의 무작위 샘플을 선택하여 손실 함수를 최소화하는 방향으로 가중치를 업데이트함
매 반복마다 (미니배치 또는 전체 배치) 기울기를 계산하고, 학습률(learning rate)과 기울기를 곱하여 가중치를 조정함

특징 :
학습률이 고정되어 있으며, 기울기의 변동에 민감합니다.
수렴 속도가 느릴 수 있으며, 지역 최적점에 빠지기 쉬운 경향이 있습니다.

2. Adam는
SGD의 확장으로, 각 파라미터에 대해 개별적인 학습률을 사용함
즉, 기울기뿐만 아니라 기울기의 이동 평균(모멘텀)과 제곱 기울기의 이동 평균(스케일)도 고려함

특징 :
각 파라미터에 대한 학습률이 적응적으로 변경되므로,
기울기의 크기가 다를 때 더 안정적인 업데이트가 가능합니다.
학습률의 감소가 자동으로 조정되므로, 초기 학습률이 상대적으로 커도 효과적으로 수렴할 수 있습니다.
일반적으로 빠르고 효율적으로 수렴하며, 지역 최적점에 빠질 위험이 적습니다.

-> Adam으로 바꿔보기 결론

optimizer = optim.Adam(model.parameters(), lr=0.01)

3. 레이블 데이터를 변경 (기준이 좀 더 포괄적인 것으로)

  • 리뷰를 바탕으로 sentiment를 계산하는 방식임
  • 문장에서 직접 단어를 짜를 필요도 없고 정수형 변환을 해줄 필요도 없다 -> 라이브러리가 다 해줌
  • 다만 실제 회원이 준 score과의 예측이 아닌 해당 라이브러리로 예측한 sentiment를 예측한다는 찝찝한 면이 존재함 (단어별로 가중치를 주는 지, 그 내부를 확인할 수 없는 느낌?)

아래의 라이브러리와 변환 함수를 이용한 방법임

# 텍스트 전처리와 자연어 처리를 위한 라이브러리
import nltk
from textblob import TextBlob

# 토픽 모델링을 위한 라이브러리
import gensim
from gensim import corpora
from gensim.utils import simple_preprocess

# 감성 분석을 위한 함수
def get_sentiment(text):
    return TextBlob(text).sentiment.polarity

4. 모델 변경 (LSTM -> LSTM + CNN)

  • LSTM 모델을 사용하는 이유 ? : 순차적 종속성을 모델링하기 위함
  • CNN 모델을 사용하는 이유 ? : 특징 추출

-> 시너지를 내겠다는 논문 발견

import torch
import torch.nn as nn
import torch.nn.functional as F

class CNNLSTMModel(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim, kernel_sizes, num_filters):
        super(CNNLSTMModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        
        # CNN 레이어
        self.convs = nn.ModuleList([
            nn.Conv2d(1, num_filters, (ks, embed_dim)) for ks in kernel_sizes
        ])
        
        self.lstm = nn.LSTM(num_filters * len(kernel_sizes), hidden_dim, batch_first=True)
        self.fc = nn.Linear(hidden_dim, output_dim)

    def forward(self, text):
        # 텍스트를 임베딩으로 변환
        embedded = self.embedding(text)  # shape: (batch_size, seq_len, embed_dim)
        embedded = embedded.unsqueeze(1)  # shape: (batch_size, 1, seq_len, embed_dim)
        
        # CNN 레이어 통과
        conv_outputs = [F.relu(conv(embedded)).squeeze(3) for conv in self.convs]  # 각 conv 레이어 통과
        pooled_outputs = [F.max_pool1d(output, output.size(2)).squeeze(2) for output in conv_outputs]  # max pooling
        cnn_output = torch.cat(pooled_outputs, 1)  # shape: (batch_size, num_filters * len(kernel_sizes))

        # LSTM에 입력으로 사용
        cnn_output = cnn_output.unsqueeze(1)  # LSTM의 입력 형태 맞추기
        lstm_out, (hidden, cell) = self.lstm(cnn_output)  # LSTM 통과
        
        return self.fc(hidden[-1])  # 마지막 타임스텝의 출력을 사용

5. Attention 알고리즘 적용 (LSTM -> LSTM + Attention 알고리즘)

Attention 개념 복습
먼저 인코더와 디코더 사이에 층이 하나 생깁니다.
새로 삽입된 층에는 각 셀로부터 계산된 스코어들이 모입니다. 이 스코어를 이용해 소프트맥스 함수를 사용해서 어텐션 가중치를 만듭니다. 이 가중치를 이용해 입력 값 중 어떤 셀을 중점적으로 볼지 결정합니다.
예를 들어 첫 번째 출력 단어인 ‘당신께’ 자리에 가장 적절한 단어는 4번째 셀 ‘you’라는 것을 학습하는 것이지요. 이러한 방식으로 매 출력마다 모든 입력 값을 두루 활용하게 하는 것이 어텐션입니다.

필요한 이유?
RNN은 여러 개의 입력 값이 있을 때 이를 바로 처리하는 것이 아니라 잠시 가지고 있는 것이라고 했습니다. 입력된 값끼리 서로 관련이 있다면 이를 모두 받아 두어야 적절한 출력 값을 만들 수 있겠지요.
-> RNN은 오래된 정보의 경우에는 그 중요도가 희미해지는 문제가 있기 때문에 Attention 알고리즘을 적용해 중요한 정보에 가중치를 주자라는 아이디어임

Attention 클래스

  • 구성 요소 : 쿼리 벡터, 키 벡터, 스코어
class Attention(nn.Module):
    def __init__(self, hidden_dim):
        super(Attention, self).__init__()
        # 쿼리 벡터
        self.Wa = nn.Linear(hidden_dim, hidden_dim)
        # 키 벡터
        self.Ua = nn.Linear(hidden_dim, hidden_dim)
        # 쿼리와 키 벡터로 생성된 Score
        self.Va = nn.Linear(hidden_dim, 1)

    def forward(self, lstm_output):
        # lstm_output: (batch_size, seq_len, hidden_dim)
        batch_size, seq_len, _ = lstm_output.size()
        hidden = lstm_output[:, -1, :]  # 마지막 타임스텝의 hidden state

        # Score 계산
        score = self.Va(torch.tanh(self.Wa(lstm_output) + self.Ua(hidden).unsqueeze(1)))
        attention_weights = torch.softmax(score, dim=1)  # 가중치 계산

        # Context vector 생성
        context_vector = torch.sum(attention_weights * lstm_output, dim=1)
        
        return context_vector
  • 적용
class LSTMModel(nn.Module):
    def __init__(self, vocab_size, embed_dim, hidden_dim, output_dim):
        super(LSTMModel, self).__init__()
        self.embedding = nn.Embedding(vocab_size, embed_dim)
        self.lstm = nn.LSTM(embed_dim, hidden_dim, batch_first=True)
        self.attention = Attention(hidden_dim)  # Attention 모듈 추가
        self.fc = nn.Linear(hidden_dim, output_dim)

    def forward(self, text):
        embedded = self.embedding(text)
        output, (hidden, cell) = self.lstm(embedded)  # unsqueeze(0) 제거
        context_vector = self.attention(output)  # Attention을 통해 context vector를 생성
        return self.fc(context_vector)  # 최종 예측을 위한 fully connected layer

결과

  • Attention 알고리즘을 결합하니 성능이 눈에 띄게 올라감

회고

  • 모델 목적 파악 / 텍스트 데이터 변환 과정 이해 (1.5일) # 완료
  • 여러 모델 만들어보기 # 진행중
  • 모델 성능 최적화 # 진행중

텍스트 데이터 변환 과정에 대한 기본적인 이해
(입력값이 list여야 한다, Torch 변환이 필요한 경우) 가 시간이 꽤나 걸렸지만 확실한 이해가 더 우선이기 때문에 이해를 완전히 하도록 계속 복기해야겠다.


유용했던 라이브러리

제대로 데이터 로더를 거치는지가 궁금했음
-> tqdm을 사용해보자

0개의 댓글