Byn's Research Note

AI based Mixed Reality, Human-Computer Interaction

↓ My Web CV & Portfolio 자세히보기

카테고리 없음

AI Programming [1] : 하버드 트랜스포머 (Harvard Transformer)

JaehyeonByun 2024. 11. 29. 17:26

 

Transformer는 문장이나 텍스트를 처리하는 데 사용되는 모델로, 주로 Encoder Decoder라는 두 큰 구성 요소로 나뉜다. Encoder는 입력된 문장을 이해하고 중요한 정보를 추출하는 역할을 하며, Decoder는 그 정보를 바탕으로 새로운 문장을 생성한다. 각 Encoder와 Decoder는 여러 층으로 이루어져 있고, 각 층은 Multi-Head Attention으로 입력의 중요 부분을 분석하고, Normalization으로 데이터의 변동성을 줄이며, Residual Connection으로 각 층의 출력을 더 원활하게 연결하며, Feed Forward network로 데이터를 처리한다. 입력된 단어는 Embedding이라는 과정을 통해 숫자 벡터로 변환되어 컴퓨터가 이해할 수 있도록 하고, Positional Encoding이 추가되어 단어의 순서를 기억할 수 있게 한다. 출력 부분은 Linear layer Softmax 함수를 사용해 최종적으로 단어를 예측하고, 이를 통해 문장을 생성한다. 쉽게 말해, Transformer는 한 문장을 다른 언어로 번역할 때, 먼저 입력 문장의 의미를 잘 이해하고(Encoder), 그 이해한 내용을 바탕으로 새로운 문장을 만드는(Decoder) 방식으로 작동한다.

 

Python 코드로 Transformer의 각 구성 요소를 구현하면서 이해할 수 있다. 예를 들어, Multi-Head Attention은 한 문장에서 여러 부분을 동시에 분석해 문맥을 파악하는 데 사용된다. 코드 실행 중 발생할 수 있는 에러는 디버깅을 통해 문제를 찾고 해결할 수 있다. 하버드에서 제작한 Transformer 코드를 참고하면 코드 작성 시 발생할 수 있는 문제를 수정하고, 각 구성 요소가 어떻게 작동하는지를 쉽게 확인할 수 있다. 이를 통해 Transformer의 동작 원리를 깊이 이해하고 실제로 구현할 수 있게 된다.

 

 

Harvard Transformer Code는 Transformer의 복잡한 구조를 코드로 구현한 예시이다. 이 코드는 다양한 구성 요소를 포함하며, Transformer 모델의 학습과 실행에 필요한 모든 기능을 제공한다. 각 구성 요소의 역할을 간단한 예시를 통해 설명하겠다.

  • run_epoch: 모델을 학습시키는 기능을 수행한다. 예를 들어, 한 번의 학습 주기가 끝나면 모델이 데이터로부터 더 나은 결과를 학습하도록 돕는다.
  • make_model: Transformer 모델을 생성하는 함수이다. 마치 새로운 로봇을 조립할 때 각 부품을 모아 하나의 로봇을 만드는 것처럼, Encoder와 Decoder를 포함하는 모델을 만든다.
  • NoamOpt: 학습 과정에서 모델의 가중치를 업데이트하기 위해 사용하는 옵티마이저이다. 학습이 시작될 때는 천천히 학습하다가 점점 더 빠르게 학습하도록 돕는다.
  • data_gen: 학습에 사용할 데이터를 생성하는 함수이다. 예를 들어, 문장 번역 모델을 훈련할 때 임의의 문장과 그 번역 결과를 만들어 낸다.
  • Batch: 데이터를 배치 단위로 묶어주는 기능을 한다. 마치 식사 시간에 여러 접시를 준비해 한 번에 여러 사람이 먹는 것처럼, 모델이 한 번에 여러 데이터를 처리할 수 있도록 한다.
  • subsequent_mask: Decoder가 미래의 단어를 참조하지 않도록 mask를 생성한다. 번역할 때, 다음 단어를 예측할 때는 아직 번역되지 않은 단어를 보지 못하도록 막는 역할을 한다.
  • Embeddings: 단어를 숫자 벡터로 변환하여 모델이 이해할 수 있게 한다. 예를 들어, "고양이"라는 단어는 컴퓨터가 숫자로 된 벡터로 변환해 인식할 수 있도록 한다.
  • EncoderDecoder: Encoder와 Decoder를 포함하는 구조로, 입력을 받아서 출력을 생성하는 역할을 한다.
  • Encoder: 입력 문장을 처리하여 중요한 정보를 추출하는 부분이다. 예를 들어, "고양이가 공원에 간다"는 문장에서 '고양이'와 '간다'는 단어의 관계를 파악한다.
  • Decoder: Encoder의 출력을 받아서 새로운 출력을 생성하는 부분이다. 번역할 때, '고양이'는 'cat'으로 변환된다.
  • EncoderLayer: Encoder를 구성하는 작은 단위이다. 각각의 Layer가 입력을 처리하고 정보를 추출하는 역할을 한다.
  • DecoderLayer: Decoder를 구성하는 작은 단위이다. 입력된 정보를 바탕으로 다음 단어를 예측하는 역할을 한다.
  • clones: 같은 구조의 Layer를 여러 개 복사하여 사용한다. 예를 들어, Encoder와 Decoder는 여러 층을 쌓아 올려 구성된다.
  • LayerNorm: 각 Layer의 출력을 정규화하여 학습을 더 안정적으로 만든다.
  • SublayerConnection: 각 Layer에 Residual Connection을 추가하여 깊은 네트워크에서 발생할 수 있는 문제를 해결한다. 예를 들어, 길이가 긴 문장을 처리할 때 정보 손실을 방지한다.
  • MultiHeadedAttention: 입력의 여러 부분을 동시에 분석하여 문맥을 이해하는 데 사용된다. 예를 들어, "고양이와 개가 공원에 간다"는 문장에서 '고양이'와 '개'의 관계를 동시에 분석한다.
  • attention: 입력의 특정 부분에 집중하여 중요한 정보를 추출한다. 주어진 문장에서 특정 단어에 집중해 문맥을 파악할 때 사용된다.
  • PositionwiseFeedForward: 각 단어의 정보를 독립적으로 처리하는 부분이다. 예를 들어, 단어 '고양이'에 대해 특정 연산을 수행하여 출력을 생성한다.
  • PositionalEncoding: 단어의 순서를 기억할 수 있게 하는 부분이다. 예를 들어, 문장에서 '고양이'와 '간다'의 순서를 인식할 수 있도록 돕는다.
  • SimpleLossCompute: 모델이 예측한 값과 실제 값을 비교하여 오차를 계산하고, 이를 통해 가중치를 조정하는 과정인 backpropagation을 수행한다.
  • loss: 모델이 얼마나 잘못 예측했는지를 나타내는 함수이다. 예를 들어, '고양이'를 'dog'라고 예측했을 때 손실이 발생한다.
  • Generator: Transformer의 최종 출력인 Linear layer와 Softmax 함수를 사용해 최종 단어를 예측한다. 예를 들어, '고양이'라는 단어의 다음 단어가 '공원'일 확률을 계산한다.
  • LabelSmoothing: 정답을 조금 흐리게 만들어 모델이 과도하게 자신감을 가지지 않도록 한다. 예를 들어, '고양이'라는 단어의 정답이 '고양이'일 확률을 1에서 약간 줄여서 모델이 더 일반화된 학습을 하도록 돕는다.
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
import math, copy, time
from torch.autograd import Variable
import matplotlib.pyplot as plt
import seaborn
seaborn.set_context(context="talk")
%matplotlib inline

 

Python에서는 다양한 기능을 위해 외부 라이브러리를 사용할 수 있다. 예를 들어, 데이터 처리나 수학 연산을 위해 NumPy, 딥러닝을 위해 PyTorch, 수학적 계산을 위해 math 라이브러리를 자주 사용한다. 이런 라이브러리들은 코드 상단에 import 문을 통해 불러올 수 있다. 예를 들어, import numpy as np라고 작성하면 NumPy 라이브러리를 np라는 이름으로 사용할 수 있다.

 

그러나 만약 코드를 실행했을 때 ModuleNotFoundError와 같은 오류가 발생한다면, 해당 라이브러리가 현재 가상환경에 설치되어 있지 않은 것이다. 이런 경우에는 라이브러리를 설치해줘야 한다. 설치는 간단하다. Python의 패키지 관리자 pip를 사용하여 설치할 수 있다. 예를 들어, NumPy를 설치하고 싶다면 코드 셀에 다음과 같이 입력하면 된다:

 

pip install은 Jupyter Notebook이나 Google Colab 환경에서 명령어를 실행하는 방식으로, 가상환경에 원하는 라이브러리를 설치할 수 있게 해준다. 이 코드를 실행하면 Python은 인터넷에서 해당 라이브러리를 다운로드하여 설치하고, 이후에는 코드 상단에서 import로 불러올 수 있다

class EncoderDecoder(nn.Module):
  def __init__(self, encoder, decoder, src_embed, tgt_embed, generator):
    super(EncoderDecoder, self).__init__()
    self.encoder = encoder
    self.decoder = decoder
    self.src_embed = src_embed
    self.tgt_embed = tgt_embed
    self.generator = generator

  def forward(self, src, tgt, src_mask, tgt_mask):
    return self.decode(self.encode(src, src_mask), src_mask, tgt, tgt_mask)

  def encode(self, src, src_mask):
      return self.encoder(self.src_embed(src), src_mask)

  def decode(self, memory, src_mask, tgt, tgt_mask):
      return self.decoder(self.tgt_embed(tgt), memory, src_mask, tgt_mask)

 

Encoder와 Decoder는 Transformer 모델의 핵심적인 구성 요소로, 번역과 같은 자연어 처리 작업에서 입력 문장을 처리하고 출력 문장을 생성하는 역할을 한다. 이들은 Python 코드로 EncoderDecoder라는 클래스를 통해 설정된다. 이 클래스 안에는 encode decode라는 함수가 있어 각각 Encoder와 Decoder를 만들고 데이터를 처리한다. 예를 들어, src는 번역하려는 입력 문장을 가리키고, tgt는 출력 문장(번역 결과)을 가리킨다. 이때 src_embed는 입력 문장을 숫자 벡터로 변환한 임베딩을 나타내고, src_mask는 입력 데이터의 특정 부분을 마스킹해주는 역할을 한다. Decoder에서도 tgt_embed tgt_mask가 같은 방식으로 사용된다. 마지막으로, forward 함수는 Encoder와 Decoder를 연결해주는 역할을 한다. 즉, 입력된 문장이 Encoder를 통해 처리되고, 그 결과가 Decoder로 전달되어 최종적으로 번역된 문장을 생성하게 된다. 예를 들어, "고양이가 공원에 간다"라는 입력 문장이 주어졌을 때, Encoder는 문장의 의미를 이해하고, Decoder는 이를 영어로 번역해 "The cat goes to the park"라는 출력 문장을 생성하는 과정이 이루어진다.

 

class Generator(nn.Module):
  def __init__ (self, d_model, vocab):
    super(Generator, self).__init__()
    self.proj = nn.Linear(d_model, vocab)

  def forward(self, x):
    return F.log_softmax(self.proj(x), dim=-1)

 

Transformer의 마지막 부분에서는 생성된 단어의 확률을 계산하기 위해 Linear layer Softmax 함수가 필요하다. 이 과정은 nn.Linear() 함수를 사용해 구현되며, Linear layer는 모델의 출력 차원을 조정하여 각 단어의 점수를 계산한다. 예를 들어, 어휘 사전에 있는 단어의 수만큼 출력 차원을 설정하여 각 단어에 대한 예측 점수를 생성한다. 그다음, Softmax 함수는 이 점수들을 확률로 변환해준다. 이를 위해 F.log_softmax() 함수를 사용하면, 각 단어의 확률을 로그 형태로 계산할 수 있어 계산 안정성이 높아진다. Softmax 함수는 입력값 중 가장 큰 값을 기준으로 상대적인 확률을 부여하므로, 모델은 가장 높은 확률을 가진 단어를 선택해 다음 단어를 예측하게 된다. 예를 들어, 번역 모델에서 "고양이"라는 단어가 출력될 확률이 가장 높다면, Softmax를 통과한 후 해당 단어가 다음 출력으로 선택되는 방식이다.

 

def clones(module, N):
  return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])

 

Neural network layer와 같은 module을 복제할 수 있는 함수입니다. 여기서는 copy.deepcopy()를 사용하여 복제를 진행합니다. 이 함수는 완벽히 다른 객체로 복제를 진행하여 유용하게 사용할 수 있습니다. 그럼 일반적인 copy와는 어떤 차이가 있을까요?

a = [1, 2, 3]
b = a

a[0] = 100
print(a)
print(b)

 

예를 들어 위와 같이 list a를 먼저 정의하고, b를 a로 정의했을 때 a = [100, 2, 3], b =  [100, 2, 3]로 a와 b가 서로 연결되어 있어, 둘 중 하나를 변환하면 다른 객체 역시 동일하게 바뀝니다.
 

class Encoder(nn.Module):
    "Core encoder is a stack of N layers"

    def __init__(self, layer, N):
        super(Encoder, self).__init__()
        self.layers = clones(layer, N)
        self.norm = LayerNorm(layer.size)

    def forward(self, x, mask):
        "Pass the input (and mask) through each layer in turn."
        for layer in self.layers:
            x = layer(x, mask)
        return self.norm(x)

 

Transformer의 Encoder는 여러 개의 Encoder Layer로 구성되어 있으며, 이 레이어들은 입력 문장을 처리하고 중요한 정보를 추출하는 역할을 한다. 코드 상에서는 먼저 EncoderLayer를 생성한 후, clone 함수를 사용해 원하는 만큼의 레이어를 복사하여 N개의 레이어를 만든다. 이렇게 여러 개의 레이어가 쌓이면, 모델은 입력 데이터의 다양한 특징을 단계별로 학습할 수 있다.

 

forward 함수는 입력 데이터가 Encoder를 통과하는 과정을 정의한다. 입력된 문장은 각 레이어를 순차적으로 통과하며, 각 레이어는 Multi-Head Attention과 Feed Forward network 등을 포함하여 정보를 처리한다. 마지막으로, 모든 레이어를 통과한 후에는 LayerNorm을 적용해 출력을 정규화한다. 정규화는 데이터를 일정한 범위로 유지시켜 학습이 안정적으로 이루어지도록 돕는다.

 

예를 들어, "고양이가 공원에 간다"는 문장을 입력으로 할 때, Encoder는 이 문장을 여러 Encoder Layer를 거쳐 분석하고 각 단어 간의 관계를 파악한다. 최종적으로 LayerNorm을 통해 전체 출력의 스케일을 조정하여 안정적인 출력을 제공한다.

class LayerNorm(nn.Module):
    "Construct a layernorm module (See citation for details)."

    def __init__(self, features, eps=1e-6):
        super(LayerNorm, self).__init__()
        self.a_2 = nn.Parameter(torch.ones(features))
        self.b_2 = nn.Parameter(torch.zeros(features))
        self.eps = eps

    def forward(self, x):
        mean = x.mean(-1, keepdim=True) # 텐서 x의 마지막차원(-1)을 따라 평균을 구하고 차원을 유지하는 함수
        std = x.std(-1, keepdim=True)
        return self.a_2 * (x - mean) / (std + self.eps) + self.b_2