트랜스포머 학습용 플래시카드 20장. 등장 배경부터 핵심 메커니즘, 응용까지 단계별 카드. 카드를 클릭하면 답이 보이고, 난이도 평가로 진도를 추적할 수 있습니다.
🤖 트랜스포머 마스터 카드 (20장)
카드를 클릭해 답을 확인하세요. 답을 본 후 “쉬움/보통/어려움”으로 평가하면 진도가 기록돼요.
Transformer의 등장 배경.
Attention으로 해결하지 못한것
- 장기 의존성 문제 (Long-term Dependency Problem): RNN의 순차적 정보 전달 구조, 문장이 길어질수록 앞쪽 정보가 뒤로 전달되는 과정에서 소실됨. LSTM으로 개선되었기는하나 여전히 이전 정보가 희석되고 한계가 있음. Attention은 디코더가 인코더의 모든 hidden state를 참조할수 있게 해주지만, hidden state 자체가 이미 RNN을 통해 만든 값들임.
- 병렬 처리의 어려움 (Parallelization Difficulty): RNN은 구조적으로 현재 스텝의 계산이 이전 스텝의 결과에 의존적임. Hidden state에서 h2를 계산하려면 h1이 필요하고 h3를 계산하려면 h2가 필요함. Attention 자체는 병렬계산이 가능한 알고리즘이지만, Transformer 이전의 attention은 RNN 위에서 쓰는 부가기능이었기때문에 한계가 존재했음.
Transformer의 핵심 개념들.
- Positional Encoding
- Attention
- Scaled dot product Attention
- Multihead Attention
- FFNN
- Add&Norm
Positional Encoding (PE): 위치기반 인코딩
PE가 없다면 아래 두 문장을 구분할수 있을까?
나는 학생 이다 / 이다 학생 나는
[답변] Transformer는 모든 토큰을 동시에 처리해. RNN처럼 순서대로 읽지 않기 때문에, Positional Encoding이 없으면 아래 두 문장은 완전히 동일하게 인식돼.
"나는 학생 이다" → {나는, 학생, 이다} ← 그냥 단어 집합
"이다 학생 나는" → {이다, 학생, 나는} ← 똑같은 단어 집합
PE가 하는 일: 각 토큰의 임베딩 벡터에 위치 정보를 담은 벡터를 더해줘.
입력 = 단어 임베딩 + 위치 임베딩
예를 들면 이렇게 돼:
"나는 학생 이다"
나는 → [단어벡터] + [위치1 벡터]
학생 → [단어벡터] + [위치2 벡터]
이다 → [단어벡터] + [위치3 벡터]
"이다 학생 나는"
이다 → [단어벡터] + [위치1 벡터] ← !!
학생 → [단어벡터] + [위치2 벡터]
나는 → [단어벡터] + [위치3 벡터] ← !!
핵심 원리 한 줄 요약
같은 단어라도 위치가 다르면 → 더해지는 벡터가 다르고 → 최종 입력값이 달라진다
그래서 모델은 단순히 “어떤 단어가 있냐”가 아니라 “어떤 단어가 몇 번째에 있냐” 를 함께 인식할 수 있어.
PE 설계에 반영된 전제들과 해소 방법
전제가 왜 필요한가?
위치 벡터를 단어 임베딩에 더하는(+) 방식을 쓰기 때문에, 잘못 설계하면 위치 정보가 단어 의미 정보를 덮어쓰거나 방해할 수 있어. 그래서 PE는 아래 3가지 전제를 만족해야 해.
전제 1. 위치마다 고유한 값이어야 한다 (Unique)
문제: 위치 벡터가 중복되면 모델이 두 위치를 구별할 수 없어.
1번 위치 PE = 3번 위치 PE → 모델 입장에서 두 토큰이 같은 위치
해소: sin/cos 함수를 다양한 주파수로 조합해서 사용해. 각 차원마다 진동 속도가 다른 sin/cos 값을 쌓으면, 모든 위치가 고유한 벡터 패턴을 가지게 돼. 이진수에서 각 자릿수가 다른 속도로 바뀌는 것과 같은 원리야.
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))
전제 2. 값이 너무 커지면 안 된다 (Bounded)
문제: 위치 벡터의 값이 단어 임베딩보다 훨씬 크면, 모델은 위치 정보만 보고 단어 의미는 무시하게 돼.
단어 임베딩 값: 0.3, 0.7, -0.2 … 위치 벡터 값: 1000, 5000, 3000 … → 임베딩 정보가 묻혀버림
해소: sin/cos 함수는 모든 값이 [-1, 1] 범위 안에 있어서, 위치 신호가 단어 임베딩을 압도하지 않아. 반면 단순히 위치 인덱스(1, 2, 3…)를 그대로 더하면 문장이 길어질수록 위치 값이 무한히 커지는 문제가 생겨. Mesuvash
전제 3. 가까운 위치는 비슷한 벡터여야 한다 (Smooth)
문제: 인접한 위치의 PE가 완전히 다른 값이라면, 모델이 “1번과 2번 위치는 가깝다”는 것을 학습하기 어려워.
해소: sin/cos의 주기적 특성 덕분에 가까운 위치는 비슷한 인코딩 값을 가지게 되어, 모델이 자연스럽게 위치 간 근접성을 학습할 수 있어. 또한 삼각함수의 덧셈정리 덕분에, 임의의 위치 k만큼 떨어진 관계를 선형 변환으로 표현할 수 있어서 상대적 위치 관계도 파악 가능해. Mesuvash
정리
| 전제 | 문제 상황 | sin/cos로 해소한 방법 |
|---|---|---|
| Unique (고유성) | 위치 중복 → 구별 불가 | 다양한 주파수 조합으로 모든 위치가 고유한 패턴 가짐 |
| Bounded (유계성) | 값이 너무 큼 → 임베딩 정보 훼손 | sin/cos는 항상 [-1, 1] 이내 |
| Smooth (연속성) | 인접 위치가 너무 다름 → 근접성 학습 불가 | 주기함수라 인접 위치 간 값 변화가 부드러움 |
이 세 조건을 동시에 만족하는 함수로 sin/cos가 선택된 거야. 실제로 Transformer 논문에서 learnable한 PE도 시도해봤지만 성능이 거의 동일했고, 입력 길이에 대한 일반화 측면에서 sinusoidal을 최종 선택했다고 해.
Attention
트랜스포머 이전의 Dot proudct Attention의 과정.
전체 흐름을 3단계로 나눠서 보면 이해하기 쉬워.
① 인코더 — 입력 문장의 각 토큰을 RNN이 순서대로 처리해서 hidden state(h₁, h₂, h₃)를 생성해. 각 hᵢ는 해당 토큰까지의 문맥 정보를 담고 있어.
② Attention — 디코더의 현재 상태(s)와 인코더의 각 hᵢ를 비교해서 점수를 계산하고, Softmax로 가중치(α)를 뽑아. 그 가중치로 hᵢ들을 가중합해서 context vector(c) 를 만들어. 선 굵기가 attention 가중치를 나타내는데, h₂가 가장 관련 있다고 판단된 예시야.
③ 디코더 — context vector와 이전 decoder state를 합쳐서 RNN이 다음 출력 토큰을 생성해.
오른쪽 하단 점선 박스가 핵심인데, Attention을 썼어도 RNN 구조 자체는 그대로라서 순차 처리와 h₁ 손상 문제가 남아있는 게 Transformer 등장의 배경이야.
Q, K, V가 뭔지 — 도서관 비유로 먼저 이해하기
Attention을 도서관 검색 시스템으로 생각해봐.
- Q (Query) — “내가 지금 찾고 싶은 것”. 검색창에 입력하는 검색어야. 현재 처리 중인 토큰이 “나는 무엇에 집중해야 하지?”라고 던지는 질문.
- K (Key) — “각 책의 색인 태그”. 모든 토큰이 자신을 설명하는 라벨을 달고 있어. Query가 어떤 Key와 잘 맞는지를 비교해.
- V (Value) — “책의 실제 내용”. Key가 매칭됐을 때 실제로 가져오는 정보야.
그리고 Q · Kᵀ는 Query와 모든 Key를 내적(dot product)해서 유사도 점수를 계산하는 것이야. 두 벡터의 내적이 크다 = 방향이 비슷하다 = 관련이 높다는 뜻이야.
Q K V Attention 인터랙티브 도식 — 토큰을 클릭하면 해당 토큰의 Query가 다른 Key와 유사도를 계산하는 과정을 시각화합니다
공식 맨 아래 / √dk가 있는데, 이건 벡터 차원이 커질수록 내적값이 폭발적으로 커져서 Softmax가 한 값으로 쏠리는 걸 방지하기 위한 정규화야.
Scaled dot product Attention (기존 attention과의 차이)
기존 Attention은 Q, K, V가 이렇게 나옴
- Q = 디코더의 현재 hidden state
- K = 인코더의 각 hidden state
- V = 인코더의 각 hidden state (K와 동일한 값)
즉 Q, K, V가 RNN이 만들어낸 hidden state 그 자체였어. 별도의 변환 없이 바로 사용한 거야.
Scaled Dot-Product Attention(Transformer)에서는 근본적으로 달라져. 입력 토큰의 임베딩 벡터 하나에서 Wq, Wk, Wv 세 개의 별도 가중치 행렬을 곱해서 Q, K, V를 각각 따로 만들어. 같은 토큰이라도 Q로 쓸 때와 K로 쓸 때, V로 쓸 때 서로 다른 벡터가 되는 거야.
2. 하나의 토큰이 3개로 나뉜다는 의미
토큰 임베딩 벡터 x가 있을 때:
Q = x · Wq ← "나는 무엇을 찾고 있나?" (질문자 역할)
K = x · Wk ← "나는 어떤 정보를 갖고 있나?" (색인 역할)
V = x · Wv ← "나의 실제 내용은 무엇인가?" (정보 역할)
같은 토큰 x에서 세 가지 역할이 분리되는 거야. Wq, Wk, Wv는 학습으로 최적화되는 파라미터이기 때문에, 모델이 "어떤 방식으로 질문하고, 어떤 방식으로 매칭하고, 어떤 정보를 전달할지"를 스스로 학습하게 돼.
3. Self-Attention vs Encoder-Decoder Attention
도식에서 핵심 차이가 보이지? Self-Attention은 Q, K, V가 전부 같은 시퀀스에서 나와서 "자기 문장 내부의 단어들이 서로를 참조"하는 거고, Encoder-Decoder Attention은 Q만 디코더에서, K와 V는 인코더에서 나와서 "번역 대상 문장을 보면서 출력을 생성"하는 구조야.
4. Scaling(√dk로 나누기)의 의미
Scaling의 원리는 이래. 벡터의 차원(dk)이 커질수록 Q와 K의 내적값이 자연히 커지는데, 이 큰 값이 그대로 Softmax에 들어가면 지수함수 특성상 가장 큰 값 쪽으로 확률이 거의 1로 쏠려버려. 이 상태에서 역전파를 하면 기울기가 거의 0이 되어서(기울기 소실) 학습이 제대로 안 돼.
√dk로 나누는 건 이 폭발적인 스케일을 내적 차원에 맞게 정규화해주는 거야. 예를 들어 dk=64이면 √64 = 8로 나눠서 점수를 안정된 범위로 가져오는 거지.
전체를 한 줄로 정리하면
| 항목 | 기존 Attention | Scaled Dot-Product |
|---|---|---|
| Q, K, V 출처 | RNN hidden state 그대로 | 임베딩에 Wq/Wk/Wv 곱해서 생성 |
| 토큰 → 3개 분리 | 없음 | 역할(질문/색인/정보)을 명시적 분리 |
| Self vs Cross | Cross만 존재 | 둘 다 동일 연산, 입력만 다름 |
| Scaling | 없음 | ÷√dk 로 기울기 소실 방지 |
self attention 하고, Encoder-Decoder Attention 둘다 transformer에서 사용하는거같은데, self attention 은 decoder only or encoder only 중에 하나인가?
Single-Head Attention vs Multi-Head Attention
Self-Attention은 "무엇을", Multi-Head Attention은 "어떻게 더 잘" 하는지에 대한 개념이야.
Self-Attention은 Q, K, V를 사용해서 토큰 간 관계를 계산하는 연산 방식 자체고, Multi-Head Attention은 그 Self-Attention을 h개의 헤드로 나눠서 병렬로 여러 번 돌린 뒤 결과를 합치는 구조적 확장이야.
헤드를 여러 개 쓰는 이유는, 하나의 Attention만으로는 한 가지 관점밖에 못 배우기 때문이야. 예를 들어 "나는 어제 학교에서 공부했다"라는 문장에서 단 하나의 Attention은 문법 관계에 집중하거나 의미 관계에 집중하거나 둘 중 하나밖에 못 해. 헤드를 8개, 16개로 늘리면 각 헤드가 서로 다른 Wq, Wk, Wv를 학습해서 각자 다른 언어적 패턴을 담당하게 돼.
그래서 Transformer에서 실제로 쓰이는 건 Multi-Head Self-Attention이야. Self-Attention(연산 방식) + Multi-Head(구조)가 합쳐진 형태인 거지.
단일 토큰 벡터를 multihead로 구성하는 방법
차원 분할 방식(d_model -> h개로 나누기)
d
독립적 학습 파라미터 방식 (각 head가 독립적 가중치 학습)
각 헤드가 원본 d_model 전체 차원에 접근하는 독립적인 Wq, Wk, Wv를 가져. 헤드가 h개면 파라미터 행렬도 h세트가 따로 존재.
입력 x (d_model=512)
↓
Head 1: x · Wq¹(512×512), x · Wk¹(512×512), x · Wv¹(512×512)
Head 2: x · Wq²(512×512), x · Wk²(512×512), x · Wv²(512×512)
...
Head 8: x · Wq⁸(512×512), x · Wk⁸(512×512), x · Wv⁸(512×512)
각 헤드가 전체 벡터를 보고 독립적으로 뭘 집중할지 결정해. 파라미터 수는 h × 3 × d_model²으로 헤드 수에 비례해서 증가해.
차원 분할 방식 (Transformer 논문의 실제 선택)
d_model을 헤드 수로 쪼개서 각 헤드에 할당해. d_model=512, h=8이면 각 헤드는 dk=64 차원만 담당해.
입력 x (d_model=512)
↓
Head 1: x · Wq¹(512×64), x · Wk¹(512×64), x · Wv¹(512×64)
Head 2: x · Wq²(512×64), x · Wk²(512×64), x · Wv²(512×64)
...
Head 8: x · Wq⁸(512×64), x · Wk⁸(512×64), x · Wv⁸(512×64)
각 헤드가 더 작은 부분 공간에서 작동해. 파라미터 수는 h × 3 × d_model × dk = 3 × d_model²로 헤드 수에 무관하게 일정해.
핵심 차이를 한 문장으로 요약하면, 독립 파라미터 방식은 헤드마다 각자 512차원 전체를 보고, 차원 분할 방식은 512차원을 헤드 수로 나눠서 각자 64차원씩 담당하는 것이야.
Transformer 논문("Attention is All You Need")이 차원 분할을 선택한 이유는 파라미터 수가 헤드 수에 상관없이 일정하게 유지되기 때문이야. 독립 파라미터 방식으로 헤드를 8개 쓰면 파라미터가 8배로 늘어나지만, 차원 분할 방식은 헤드가 몇 개든 3 × d_model²로 고정돼. 계산량과 메모리를 효율적으로 유지하면서 다양한 관점을 동시에 학습할 수 있는 거지.
다만 차원 분할도 완전히 독립적인 파라미터를 가지긴 해. 각 헤드의 Wq, Wk, Wv가 서로 다른 별개의 행렬이야. 차이는 그 행렬의 크기가 d_model × dk(작은 부분공간)냐, d_model × d_model(전체 공간)이냐인 거야.
N21, N2N, N2M 정리
N21 — 문장 전체를 보고 답 하나를 내는 것. "이 리뷰가 긍정이야 부정이야?" 처럼 전체 입력을 하나의 결론으로 압축하는 태스크야.
N2N — 토큰 하나하나에 대응하는 답을 내는 것. "각 단어가 무슨 품사야?" 처럼 입력과 출력 개수가 딱 맞아. Transformer 인코더 내부의 Self-Attention이 이 방식이야.
N2M — 입력과 출력 길이가 서로 달라도 되는 것. 번역이 대표적인 예야. 한국어 3단어를 넣었는데 영어로는 4단어가 나올 수도 있잖아. Transformer 전체 구조(인코더 + 디코더)가 이걸 가능하게 하는 거야.
Transformer가 이전 RNN 기반 모델들보다 혁신적이었던 이유 중 하나가 N2M을 훨씬 잘 처리할 수 있게 된 거야. RNN은 길이가 길어질수록 앞쪽 정보가 흐려졌지만, Transformer는 Self-Attention으로 거리와 상관없이 모든 토큰을 직접 참조할 수 있으니까.
트랜스포머 관련 링크 (3 가지)
https://cpm0722.github.io/pytorch-implementation/transformer
찾아봐야할 내용
PCA, 공분산 행렬