왜 이어 붙이지(concat) 않고 더하는가? 이어 붙이면 차원이 늘어나 뒤의 모든 층이 커진다. 더하면 차원이 그대로다. “정보가 섞여 버리지 않나?” 싶지만 — \(d\)가 수백 차원이면 학습이 알아서 의미 성분과 위치 성분을 다른 방향에 나누어 담는다. 실제로 학습된 위치 임베딩을 보면 서로 다른 위치끼리 뚜렷이 구별되는 패턴이 나타난다.
노트
원 논문은 학습이 필요 없는 sin/cos 공식으로 위치 벡터를 만들었다. 지금은 학습형 위치 임베딩(GPT 계열)이나 회전형(RoPE) 방식이 더 흔하다. 이 수업에서는 학습형만 다룬다 — 구현이 nn.Embedding 한 줄이고, 원리는 “위치마다 다른 벡터를 더한다”로 같다.
2. 깊이 — 잔차 연결과 LayerNorm
블록을 12개, 96개 쌓으려 한다. Ch04에서 배운 문제가 바로 앞을 막는다 — 층이 깊어지면 그래디언트가 앞쪽까지 도달하지 못한다.
잔차 연결 — Ch07에서 이미 만난 해법
잔차 연결 (Residual Connection)
어떤 층 \(F\) 를 통과한 결과에 입력을 그대로 더한다.
\[\text{출력} = F(x) + x\]
Ch07의 ResNet에서 본 그 구조 그대로다. 우회로를 따라 그래디언트가 곱셈 없이 통과하므로 깊은 층까지 살아서 도달한다. Transformer는 이것을 블록마다 두 번 쓴다 — 어텐션 뒤에 한 번, FFN 뒤에 한 번.
덧셈이 되려면 모양이 같아야 한다. Ch11에서 Multi-Head의 입출력 모양이 \((T, d)\) 로 보존된다고 강조한 이유가 여기서 드러난다.
LayerNorm — 이 장의 유일한 새 계산
정의: 층 정규화 (Layer Normalization)
토큰 하나의 \(d\)개 성분을 평균 0, 표준편차 1로 맞춘 뒤, 학습되는 \(\gamma, \beta\)로 다시 조정한다.
블랙박스가 아니다. \(d\)와 층 수만 알면 크기를 계산할 수 있는 구조물이다. 이 구조가 커진 것이 지금의 모든 LLM이다.
모델
\(d\)
층 수
파라미터
GPT-2 small
768
12
124M
GPT-2 large
1,280
36
774M
GPT-3
12,288
96
175B
\(12d^2 N\) 이라는 식 하나로 세 줄이 전부 설명된다 — \(d\)를 키우면 제곱으로, 층을 늘리면 비례해서 커진다.
6. 이 블록으로 무엇을 하는가 — 언어모델
구조는 완성됐다. 이제 출력층에 무엇을 붙이고, 무엇을 목표로 학습시킬 것인가를 정할 차례다.
LLM의 목표는 단 하나: 다음 토큰 예측
빈칸 하나 맞히기
다음 문장의 빈칸에 무엇이 올지 맞혀 보자.
“나는 어제 학교에 ___”
“갔다”가 가장 그럴듯하고, “왔다”도 가능하다. “고양이”는 문법적으로 이상하다. 여러분의 머릿속에서 방금 일어난 일 — 문맥을 보고 다음 단어의 그럴듯함에 점수를 매기는 것 — 이것이 ChatGPT 같은 거대 언어모델(LLM)이 하는 일의 전부다. 수천억 개의 파라미터, 수조 개의 학습 토큰, 그 모든 규모가 향하는 목표는 단 하나, 이 빈칸 맞히기다.
정의: 언어모델 (Language Model)
문맥(지금까지의 토큰들)이 주어졌을 때, 다음 토큰의 확률분포를 출력하는 모델.
\[P(\text{다음 토큰} \mid \text{문맥})\]
이 확률분포에서 토큰을 하나 뽑아 문맥에 붙이고, 다시 다음 토큰을 예측하는 일을 반복하면 문장이 생성된다. LLM(Large Language Model)은 이것을 아주 큰 규모로 하는 언어모델이다.
이것은 여러분이 아는 문제다
“다음 토큰 예측”이라는 말을 이 수업의 언어로 번역하면 이렇게 된다:
클래스가 어휘 전체인 다중 분류 문제.
Ch03에서 3개 클래스 분류를 다뤘다. 출력층에 노드 3개를 두고, Softmax로 확률로 바꾸고, 교차 엔트로피로 손실을 계산했다. LLM은 클래스 수가 3개가 아니라 어휘 크기(수만 개) 일 뿐, 구조가 똑같다:
Ch03의 3-클래스 분류
LLM의 다음 토큰 예측
입력
특성 벡터
문맥 (토큰열)
출력층 노드 수
3개
어휘 크기 (예: GPT-3는 50,257개)
확률 변환
Softmax (Ch03)
Softmax (같은 함수)
손실 함수
교차 엔트로피 (Ch03)
교차 엔트로피 (같은 함수)
정답
클래스 라벨
실제로 다음에 온 토큰
정답 토큰의 확률을 \(\hat{y}_{\text{정답}}\)이라 하면 손실은 Ch03 그대로 \(\mathcal{L} = -\log(\hat{y}_{\text{정답}})\)이다.
여러분은 이미 LLM의 손실 함수를 3주차에 손으로 계산했다. 문맥을 벡터로 바꾸는 내부(임베딩은 Ch10, 어텐션은 Ch11)도 이미 배웠으니, LLM에서 여러분이 계산할 수 없는 부분은 사실상 없다 — 크기가 클 뿐이다.
import pandas as pdimport numpy as nptokens = ['갔다', '왔다', '있다', '먹었다', '고양이']z = [2.0, 1.0, 0.5, -0.5, -1.5]exp_z = [np.exp(zi) for zi in z]sum_exp =sum(exp_z)probs = [e / sum_exp for e in exp_z]rows = []for t, zi, e, p inzip(tokens, z, exp_z, probs): rows.append({'토큰': t,'로짓 $z_k$': zi,'$e^{z_k}$': f'{e:.4f}','확률 $\\hat{y}_k$': f'{p:.4f}','확률 (%)': f'{p*100:.1f}%', })rows.append({'토큰': '**합계**','로짓 $z_k$': '','$e^{z_k}$': f'{sum_exp:.4f}','확률 $\\hat{y}_k$': f'{sum(probs):.4f}','확률 (%)': '100.0%',})pd.DataFrame(rows).to_html(index=False, border=0, escape=False)
표 5: 다음 토큰 확률 손계산 — 문맥: ‘나는 어제 학교에’
토큰
로짓 \(z_k\)
\(e^{z_k}\)
확률 \(\\hat{y}_k\)
확률 (%)
갔다
2.0
7.3891
0.5871
58.7%
왔다
1.0
2.7183
0.2160
21.6%
있다
0.5
1.6487
0.1310
13.1%
먹었다
-0.5
0.6065
0.0482
4.8%
고양이
-1.5
0.2231
0.0177
1.8%
합계
12.5857
1.0000
100.0%
“갔다”가 58.7%로 가장 그럴듯하고, “고양이”는 1.8%다. 학습 데이터에서 실제 다음 토큰이 “갔다”였다면, 손실은 Ch03 그대로 \(\mathcal{L} = -\log(0.5871) = 0.5326\). 실제 LLM의 학습은 이 계산을 수조 개의 빈칸에 대해 반복하며 경사하강법(Ch03~04)으로 손실을 줄이는 것이다.
Temperature: 확률분포의 뾰족함을 조절하는 손잡이
정의
ChatGPT API의 설정 화면에서 보게 될 temperature는 새로운 수학이 아니라, 방금 쓴 Softmax에 나누기 하나를 추가한 것이다.
확률분포에서 실제 토큰을 고르는 방식도 두 갈래다. 그리디(greedy) 디코딩은 매번 확률 1등 토큰만 택한다 — 같은 입력에 항상 같은 출력이 나오지만 뻔하고 반복적인 문장이 되기 쉽다. 샘플링은 확률분포대로 주사위를 던져 뽑는다 — “갔다”가 58.7%면 100번 중 59번쯤 “갔다”가 나오고 나머지는 다른 토큰이 나온다. ChatGPT가 같은 질문에 매번 조금씩 다른 답을 내놓는 이유가 바로 이 샘플링이고, temperature는 그 주사위의 모양을 조절하는 손잡이다.
인터랙티브 — Temperature 손잡이를 직접 돌려 보세요
로짓 5개는 위 손계산과 같은 값으로 고정되어 있어요. T 슬라이더를 움직이면 Softmax 확률 막대가 실시간으로 변합니다. T를 0.1까지 내리면 어떤 일이 생기는지, 3까지 올리면 균등분포(점선, 20%)에 얼마나 가까워지는지 확인해 보세요.