Level 7: Transformer & LLM 원리
🤖

Level 7

Positional Encoding

위치 정보의 필요성, Sinusoidal Encoding

40분
Positional Encoding 강의 영상
강의 영상 보기 (새 탭에서 재생)YouTube

📓Google Colab에서 실습하기

이 레슨은 PyTorch/GPU가 필요합니다. 노트북을 다운로드 후 Google Colab에서 열어주세요.

학습 내용

Positional Encoding - 순서의 비밀

학습 목표

이 레슨을 완료하면:

  • Transformer에 위치 정보가 필요한 이유를 설명할 수 있다
  • 사인/코사인 기반 Positional Encoding의 원리를 이해한다
  • 왜 사인/코사인 함수가 위치 표현에 적합한지 설명할 수 있다
  • numpy와 matplotlib로 Positional Encoding을 직접 생성하고 시각화할 수 있다
  • 학습 가능한 위치 임베딩과의 차이를 이해한다

핵심 메시지

"Transformer는 칠판에 적힌 글을 한눈에 보지만, 줄 번호가 없으면 순서를 모른다." Positional Encoding은 각 단어에 고유한 "줄 번호 도장"을 찍어주는 것입니다.


왜 위치 정보가 필요한가?

비유: 줄 번호 없는 칠판

비유: Transformer는 칠판에 적힌 모든 단어를 한꺼번에 봅니다. 하지만 줄 번호가 없으면 어떤 단어가 먼저 나왔는지 알 수 없습니다.

줄 번호가 없는 칠판: "물었다" "개를" "사람이"

이것은 아래 두 문장 중 어느 것일까요? A: "사람이 개를 물었다" B: "개를 사람이 물었다"

단어 집합은 동일하지만, 순서에 따라 의미가 완전히 달라집니다!

RNN vs Transformer의 위치 인식

모델처리 방식위치 정보
RNN단어를 순서대로 하나씩 처리자연스럽게 "이것은 3번째 단어"라는 정보가 포함됨<br/>별도의 위치 정보가 필요 없음
Transformer모든 단어를 동시에 처리Self-Attention은 순서를 전혀 고려하지 않음<br/>"나는 너를 좋아한다"와 "너를 나는 좋아한다"가 동일하게 처리됨<br/>반드시 위치 정보를 별도로 추가해야 함!

위치 정보 없이는 어떤 문제가 생기나?

위치 정보가 없으면 Self-Attention은 "Bag of Words"와 같아집니다:

"고양이가 쥐를 쫓았다" = "쥐를 고양이가 쫓았다" = "쫓았다 고양이가 쥐를"

모두 같은 단어 집합이므로 같은 결과가 나옵니다. 하지만 자연어에서 순서는 의미에 결정적인 영향을 줍니다!


간단한 해결책은 왜 안 될까?

시도 1: 정수 인덱스 사용

위치 = [0, 1, 2, 3, 4, ...]

문제점:

  • 문장이 길어지면 값이 무한히 커짐 (0, 1, 2, ..., 10000)
  • 단어 임베딩 벡터의 값은 보통 -1 ~ 1 범위
  • 위치 값이 10000이면 임베딩 정보를 압도해 버림
  • 학습이 매우 불안정해짐

시도 2: 정규화 (0~1로 맞추기)

5단어 문장: [0.0, 0.25, 0.5, 0.75, 1.0] 10단어 문장: [0.0, 0.11, 0.22, 0.33, ...]

문제점:

  • 같은 "2번째 위치"가 문장 길이에 따라 다른 값을 가짐
  • 5단어 문장의 2번째: 0.25
  • 10단어 문장의 2번째: 0.11
  • 모델이 일관된 위치 패턴을 학습하기 어려움

좋은 위치 인코딩의 조건

조건설명
1️⃣ 제한된 범위값의 범위가 제한적이어야 함 (임베딩과 비슷한 스케일)
2️⃣ 고유성각 위치마다 고유한 값을 가져야 함
3️⃣ 일관성문장 길이에 상관없이 같은 위치는 같은 값이어야 함
4️⃣ 상대 거리상대적 거리도 표현할 수 있으면 좋음
5️⃣ 일반화학습하지 않은 긴 문장에도 적용 가능해야 함

Sinusoidal Positional Encoding

핵심 아이디어

사인(sin)과 코사인(cos) 함수를 서로 다른 주파수로 사용하여, 각 위치를 고유한 벡터로 표현합니다.

수학적 공식

PE(pos,2i)=sin(pos100002i/dmodel)\text{PE}(\text{pos}, 2i) = \sin\left(\frac{\text{pos}}{10000^{2i/d_{\text{model}}}}\right)

PE(pos,2i+1)=cos(pos100002i/dmodel)\text{PE}(\text{pos}, 2i+1) = \cos\left(\frac{\text{pos}}{10000^{2i/d_{\text{model}}}}\right)

변수 설명:

  • pos\text{pos}: 단어의 위치 (0, 1, 2, 3, ...)
  • ii: 차원 인덱스 (0, 1, 2, ..., dmodel/21d_{\text{model}}/2 - 1)
  • dmodeld_{\text{model}}: 임베딩 차원 (예: 512)

규칙:

  • 짝수 차원(0, 2, 4, ...): sin 사용
  • 홀수 차원(1, 3, 5, ...): cos 사용

직관적 이해: 이진수와의 유사성

비유: 이진수를 떠올려 보세요. 각 자릿수(비트)는 서로 다른 주기로 0과 1을 반복합니다.

이진수 카운팅:
위치  bit3  bit2  bit1  bit0
  0     0     0     0     0
  1     0     0     0     1    ← bit0: 매번 변함
  2     0     0     1     0    ← bit1: 2번마다 변함
  3     0     0     1     1
  4     0     1     0     0    ← bit2: 4번마다 변함
  5     0     1     0     1
  6     0     1     1     0
  7     0     1     1     1

Positional Encoding도 비슷합니다:

  • 낮은 차원: 빠르게 변하는 sin/cos (bit0처럼)
  • 높은 차원: 느리게 변하는 sin/cos (bit3처럼)
  • 이 조합으로 각 위치가 고유한 패턴을 가짐!

주파수별 변화

차원함수변화 속도
차원 0-1sin(pos/1), cos(pos/1)매우 빠른 변화 ⚡
차원 2-3sin(pos/10), cos(pos/10)중간 변화 ⚙️
차원 4-5sin(pos/100), cos(pos/100)느린 변화 🐌
차원 6-7sin(pos/1000), cos(pos/1000)매우 느린 변화 🐢
.........

각 위치는 이 모든 주파수의 값을 조합한 고유한 벡터를 가집니다. 마치 각 위치에 고유한 "지문"을 부여하는 것과 같습니다!


실행해보기: Positional Encoding 생성과 시각화

python
import numpy as np import matplotlib.pyplot as plt def positional_encoding(max_len, d_model): """Sinusoidal Positional Encoding 생성""" PE = np.zeros((max_len, d_model)) for pos in range(max_len): for i in range(0, d_model, 2): freq = 1.0 / (10000 ** (i / d_model)) PE[pos, i] = np.sin(pos * freq) if i + 1 < d_model: PE[pos, i+1] = np.cos(pos * freq) return PE max_len = 50 d_model = 64 PE = positional_encoding(max_len, d_model) print(f"Positional Encoding 크기: {PE.shape}") print(f" - {max_len}개 위치 x {d_model}차원") print(f" - 값 범위: [{PE.min():.3f}, {PE.max():.3f}]") print(f" - sin/cos이므로 항상 [-1, 1] 범위!") print() for pos in range(3): print(f"위치 {pos}의 처음 8차원: {np.round(PE[pos, :8], 3)}") print() fig, axes = plt.subplots(2, 2, figsize=(14, 10)) ax1 = axes[0, 0] im = ax1.imshow(PE, aspect="auto", cmap="RdBu_r") ax1.set_xlabel("Dimension (i)") ax1.set_ylabel("Position (pos)") ax1.set_title("Positional Encoding Heatmap") plt.colorbar(im, ax=ax1) ax2 = axes[0, 1] positions = np.arange(max_len) for d in [0, 4, 8, 16, 32]: if d < d_model: ax2.plot(positions, PE[:, d], label=f"dim {d}") ax2.set_xlabel("Position") ax2.set_ylabel("Value") ax2.set_title("Dimension-wise Patterns (different frequencies)") ax2.legend() ax2.grid(True, alpha=0.3) ax3 = axes[1, 0] similarity = PE @ PE.T im3 = ax3.imshow(similarity, cmap="viridis") ax3.set_xlabel("Position") ax3.set_ylabel("Position") ax3.set_title("Position Similarity (dot product)") plt.colorbar(im3, ax=ax3) ax4 = axes[1, 1] for p in [0, 1, 2, 10, 25, 49]: ax4.plot(PE[p, :32], label=f"pos {p}", alpha=0.7) ax4.set_xlabel("Dimension") ax4.set_ylabel("Value") ax4.set_title("PE vectors at different positions (first 32 dims)") ax4.legend() ax4.grid(True, alpha=0.3) plt.tight_layout() plt.savefig("positional_encoding.png", dpi=100, bbox_inches="tight") plt.show() print("Positional Encoding 시각화 완료!") print() print("===== 위치 간 유사도 분석 =====") print() ref_pos = 0 print(f"위치 {ref_pos}과 다른 위치들의 유사도 (내적):") for p in [1, 2, 5, 10, 20, 49]: sim = np.dot(PE[ref_pos], PE[p]) print(f" 위치 {ref_pos} vs 위치 {p:>2}: {sim:>8.3f} (거리: {p - ref_pos})") print() print("가까운 위치일수록 유사도가 높고,") print("먼 위치일수록 유사도가 낮아지는 경향을 보입니다.") print("이것이 모델이 상대적 거리를 학습할 수 있게 해줍니다!")

Sinusoidal Encoding의 장점

1. 값이 항상 -1 ~ 1 사이

sin과 cos의 출력 범위: [-1, 1] 단어 임베딩의 값 범위: 보통 [-1, 1] 근처

→ 위치 정보와 단어 의미 정보의 스케일이 비슷 → 단순히 더해도(+) 한쪽이 다른 쪽을 압도하지 않음

final_input = word_embedding + positional_encoding (의미 정보) + (위치 정보)

2. 상대적 위치 표현 가능

수학적 특성: PE(pos + k)는 PE(pos)의 선형 변환으로 표현 가능

이것이 의미하는 바: "3칸 뒤의 단어"라는 상대적 관계를 Attention이 학습할 수 있음

언어에서 상대 위치가 중요한 예:

  • 형용사는 보통 명사 바로 앞에 옴 (상대 거리 = 1)
  • 주어와 동사 사이 거리는 다양하지만 관계가 있음

3. 길이 일반화 가능

학습 시: 최대 512 토큰까지만 봄 추론 시: 513번째 위치의 PE(513, i) = sin(513 / 10000^(2i/d_model)) → 수학적으로 자연스럽게 계산 가능!

학습하지 않은 긴 문장에도 적용할 수 있음 (다만 성능은 학습 범위를 크게 벗어나면 저하될 수 있음)


Positional Encoding 적용 방법

입력 처리 전체 과정

1. 토큰화:
   "나는 사과를 먹었다" → [토큰1, 토큰2, 토큰3]

2. 단어 임베딩:
   토큰1 → [0.2, -0.5, 0.8, ...]  (d_model 차원 벡터)
   토큰2 → [0.1, 0.3, -0.2, ...]
   토큰3 → [-0.4, 0.7, 0.1, ...]

3. Positional Encoding 더하기:
   위치0의 PE → [0.0, 1.0, 0.0, ...]
   위치1의 PE → [0.84, 0.54, 0.01, ...]
   위치2의 PE → [0.91, -0.42, 0.02, ...]

4. 최종 입력 = 임베딩 + PE:
   토큰1 → [0.2+0.0, -0.5+1.0, 0.8+0.0, ...]
   토큰2 → [0.1+0.84, 0.3+0.54, -0.2+0.01, ...]
   토큰3 → [-0.4+0.91, 0.7-0.42, 0.1+0.02, ...]

→ 이제 각 토큰이 "의미 + 위치" 정보를 모두 담고 있습니다!

Sinusoidal vs 학습 가능한 위치 임베딩

두 가지 방식 비교

특성Sinusoidal (원본 Transformer)Learned (BERT, GPT)
파라미터없음 (수학 공식으로 고정)max_len x d_model 개
학습학습 불필요학습으로 최적화
길이 일반화학습 범위 밖도 가능학습 범위 내로 제한
성능좋음비슷 (약간 더 나을 수도)
상대 위치수학적으로 보장학습으로 습득

학습 가능한 위치 임베딩 (Learned Positional Embedding):

  • 위치별 벡터를 처음에 랜덤 초기화
  • 학습 과정에서 최적의 위치 표현을 자동으로 배움
  • BERT: 최대 512 위치, GPT-2: 최대 1024 위치

최신 기법 (RoPE - Rotary Position Embedding):

  • LLaMA, ChatGPT 등 최신 모델에서 사용
  • 회전 행렬을 이용한 상대적 위치 인코딩
  • 길이 일반화 성능이 매우 좋음

PyTorch로 보는 Positional Encoding (참고용)

import torch
import math

class PositionalEncoding(torch.nn.Module):
    def __init__(self, d_model, max_len=5000):
        super().__init__()
        pe = torch.zeros(max_len, d_model)
        position = torch.arange(0, max_len).unsqueeze(1).float()
        div_term = torch.exp(
            torch.arange(0, d_model, 2).float() *
            (-math.log(10000.0) / d_model)
        )
        pe[:, 0::2] = torch.sin(position * div_term)
        pe[:, 1::2] = torch.cos(position * div_term)
        pe = pe.unsqueeze(0)
        self.register_buffer("pe", pe)

    def forward(self, x):
        return x + self.pe[:, :x.size(1), :]

핵심 요약

개념설명비유
위치 정보 필요성Transformer는 순서를 모름줄 번호 없는 칠판
Sinusoidal PEsin/cos로 위치를 벡터로 변환각 위치의 고유 지문
다양한 주파수낮은 차원은 빠르게, 높은 차원은 느리게 변화이진수의 비트와 유사
값 범위항상 [-1, 1]임베딩과 같은 스케일
상대 위치선형 변환으로 표현 가능거리 관계 학습 가능
적용 방법임베딩에 단순히 더함의미 + 위치 결합

핵심 공식

PE(pos,2i)=sin(pos/100002i/dmodel)PE(pos, 2i) = sin(pos / 10000^{2i/d_{model}})

PE(pos,2i+1)=cos(pos/100002i/dmodel)PE(pos, 2i+1) = cos(pos / 10000^{2i/d_{model}})

ext최종입력=extWordEmbedding+extPositionalEncoding ext{최종 입력} = ext{Word Embedding} + ext{Positional Encoding}


학습 체크리스트

  • Transformer에 위치 정보가 필요한 이유를 설명할 수 있다
  • 정수 인덱스나 정규화가 왜 좋은 해결책이 아닌지 이해했다
  • sin/cos 공식에서 pos, i, d_model 각각의 의미를 안다
  • 다양한 주파수가 고유한 위치 패턴을 만드는 원리를 이해했다
  • matplotlib 시각화에서 위치별 패턴 차이를 확인했다
  • Sinusoidal과 Learned 방식의 장단점을 비교할 수 있다
  • Positional Encoding이 임베딩에 더해지는 과정을 설명할 수 있다

다음 레슨: Transformer 전체 구조를 조립합니다. Encoder-Decoder 아키텍처, Layer Normalization, Residual Connection을 배웁니다!

레슨 정보

레벨
Level 7: Transformer & LLM 원리
예상 소요 시간
40분
참고 영상
YouTube 링크

💡실습 환경 안내

이 레벨은 PyTorch/GPU가 필요하여 Google Colab 사용을 권장합니다.

Colab은 무료 GPU를 제공하여 PyTorch, CNN, Transformer 등을 실행할 수 있습니다.