학부연구생

Decoder-Only Transformer - Positional Encoding Coding ver.

euphoria24 2025. 2. 14. 13:10

https://www.youtube.com/watch?v=C9QSpl5nmrY&t=1227s

 

 

이 영상의 코드를 보고 공부를 해보았사옵니다~..

 

 

아래의 코드는 PositionalEncoding 클래스를 구현한 것:

# 파이토치의 텐서 연산과 신경망 모듈 사용하기 위해 임포트
# 위치 인코딩은 nn.Module을 상속받아 신경망 모듈로 동작
import torch
import torch.nn as nn

class PositionalEncoding(nn.Module):

    # 임베딩 차원 기본값 2, 최대 문장 길이 기본값 6으로 설정
    def __init__(self, d_model=2, max_len=6):
        # nn.Module의 부모 클래스 초기화(필수)
        super().__init__()

        # max_len * d_model 크기의 pe 행렬을 0으로 초기화
        # 최종적으로 이 행렬은 위치 정보를 포함한 값으로 채워짐
        pe = torch.zeros(max_len, d_model)

        # position: (max_len, 1) 형태의 위치 인덱스를 생성 (0, 1, 2, ..., max_len-1)
        # unsqueeze(1): 차원을 추가하여 (max_len,) -> (max_len, 1)로 변환
        position = torch.arange(start=0, end=max_len, step=1).float().unsqueeze(1)
        
        # embedding_index: 짝수 인덱스 (0, 2, 4, ...)을 추출하여 div_term을 계산하는 데 사용
        embedding_index = torch.arange(start=0, end=d_model, step=2).float()

        # 10000^(index/d_model)를 계산 -> 논문에서 사용된 스케일링 공식
        # div_term은 위치 값을 변환하는 비율을 정의하는 역할
        div_term = 1 / torch.tensor(10000.0) ** (embedding_index / d_model)

        # 짝수 차원(0, 2, 4, ...)에 대해 sin(position * div_term)을 계산하여 채움
        pe[:, 0::2] = torch.sin(position * div_term)

        # 홀수 차원(1, 3, 5, ...)에 대해 cos(position * div_term)을 계산하여 채움
        if d_model > 1:  # d_model이 1일 경우 cos 부분을 건너뜀
            pe[:, 1::2] = torch.cos(position * div_term)

        # register_buffer로 저장 (학습하지 않는 텐서)
        self.register_buffer('pe', pe)

    # 입력에 pe를 더하여 위치 정보 반영
    def forward(self, word_embeddings):
        return word_embeddings + self.pe[:word_embeddings.size(0), :]

 

위치 인코딩은 순서 정보가 없는 word embedding단어의 위치 정보를 추가하는 역할을 한다.

 

 

d_model(임베딩 차원)이란,

단어를 수치적으로 표현하는 벡터의 차원을 의미한다.

즉, 한 단어(또는 토큰)가 얼마나 많은 숫자로 표현되는지를 결정하는 값이다.

 

2025.02.14 - [학부연구생] - Decoder-Only Transformer 모델 설명

 

Decoder-Only Transformer 모델 설명

디코더만 있는 트랜스포머의 대표적 예시: GPT 시리즈-> 입력(프롬포트)만 보고 다음 단어를 예측하는 방식 1. 입력 토큰화2. 위치 인코딩 추가3. 멀티 헤드 셀프 어텐션 적용4. 피드 포월드 네트워

euphoria24.tistory.com

 

자세한 내용은 이전 블로그에 적어놓으니 참고 부탁.

 

4줄 코드에서 pe는 행렬이다. 처음에 이 행렬은 0으로 가득차 있다.

예를 들어, max_len=3 이고 d_model=2 일 때 행렬 pe는 

tensor( [ [0., 0.][0., 0.][0., 0.] ] )

 

5줄 코드에서 position는 열행렬이다. 이는 pos로 각 토큰의 위치를 나타낸다.

 

>깨알 설명<

  • 더보기
    짝수 차원(2i)에서는 sin을 사용PE(pos,2i)PE(pos, 2i) = sin을 적용하여 값 계산
  • 더보기
    홀수 차원(2i+1)에서는 cos을 사용PE(pos,2i+1)PE(pos, 2i+1) = cos을 적용하여 값 계산
  • 더보기
    분모 → 이는 위치(pos) 값이 큰 경우에도 적절한 스케일을 유지하도록 조정하는 역할을 한다.

0 ~ max_len 사이의 수를 생성하기 위해 torch.arange 사용한다.

float는 부동소수점인지 확인하고, unsqueeze(1)는 숫자 시퀀스를 열행렬로 압축한다.

 

예를 들어, max_len=3 이면 

tensor( [ [0.], [1.], [2.] ] ) 를 가진다.

 

6줄 코드에서는 torch를 사용하기 전에 각 단어 임베딩에 대한 인덱스(i*2) 를 나타내는 행 행렬 임베딩 위치를 생성한다.

 

>깨알 설명<

더보기

step이란? (arange(start, end, step))

숫자를 생성할 때 증가하는 간격을 의미한다.

숫자가 클수록 수학 연산을 줄일 수 있음.

 

float를 사용하여 부동소수점인지 확인한 후,

d_model이 2라면, 임베딩 인덱스가 단일 값 0이 된다. -> tensor ( [ 0. ] )

d_model이 6이면, 임베딩 인덱스가 세 개의 값이 생성된다. -> tensor ( [ [0.], [2.], [4.] ] )

 

이제 각각의 값은 위치 pos를 나타내고, 이는 분모로 나눠지기에,

7줄 코드에서 div_term에 이 값이 저장된다.

 

8줄 코드에서 pe[:, 0::2] 는 0번부터 시작하여 2씩 증가 (짝수 차원 선택) 라는 의미이고,

9줄 코드에서 pe[:, 1::2] 는 1번부터 시작하여 2씩 증가 (홀수 차원 선택) 라는 의미이다.

 

즉,

짝수 차원 선택 시, d_model=6일 때 선택되는 차원 → [0, 2, 4]

홀수 차원 선택 시, d_model=6일 때 선택되는 차원 → [1, 3, 5]

 

 

궁극적으로!!

max_len=3 과 d_model=2 를 사용하여 코드를 돌리면,,

 

 

이런 결과가 나온다.

첫 번째 열 0.0000, 0.8415, 0.9093 은 sin 함수에서 나온 값이고,

두 번째 열 1.0000, 0.5403, -0.4161 은 cos 함수에서 나온 값이다.

 

10줄 코드에서 register_buffer() 르 사용하여 pe가 GPU로 움직이는 것을 보장한다.

 

11줄 코드에서 self.pe[:word_embeddings.size(0), :] 는 입력된 문장 길이만큼의 위치 인코딩을 가져온다.

그리고 word_embeddings + self.pe[...] 는 단어 벡터에 위치 정보를 더하여 순서를 반영한다.

 

이렇게 하면 transformer가 단어의 의미 뿐만 아니라 문장 내 위치 정보도 학습할 수 있다!!!

 

그럼 빠잉~