수업 순서로 이동
실습 4 · GPT 모델 · 00 · 수업의 큰 그림
실습 4는 모델을 만들고, 실습 5는 그 모델을 학습합니다.
Day 1에서는 텍스트를 토큰으로 바꾸고, 다음 토큰을 맞히는 Dataset을 만들었으며, Attention으로 문맥을 살펴봤습니다. Day 2 오전 실습 4는 그 부품을 조립해 GPT의 전체 순전파를 만듭니다. 실습 5는 모델의 정답률을 높이기 위해 Loss를 측정하고 가중치를 고칩니다.
코드 또는 흐름을 따라갑니다
입력 문장 → token IDs → 임베딩 → Attention/FFN 블록 → 모든 토큰에 대한 logits → 다음 토큰 생성
↓ (정답 토큰과 비교)
Cross Entropy Loss → backward → optimizer.step
왜 이렇게 작성하나요?한 문장으로 답하면, **실습 4는 예측하는 장치를 정의하고**, **실습 5는 정답을 이용해 그 장치를 수정하는 과정**입니다. 모델을 만든 것과 학습한 것은 다릅니다. Chapter 4의 모델은 설정에 따라 초기화한 상태이므로, 학습 이전 출력이 자연스러운 문장일 필요는 없습니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
강사가 수업 끝에 다시 강조한 Chapter 4 복습 기준- LayerNorm: 정규화 구조와 학습 가능한 scale·shift를 확인합니다. GELU 수식 자체는 상대적으로 낮은 우선순위로 설명했습니다.
- TransformerBlock: LayerNorm → Attention → 잔차 연결 → LayerNorm → FFN → 잔차 연결의 흐름을 설명합니다.
- GPTModel: token/position embedding → 여러 TransformerBlock → final_norm → out_head의 순서를 설명합니다. out_head의 마지막 출력 차원은
vocab_size입니다. - 추론:
no_grad → 마지막 위치 logits → argmax → cat 순서를 복원합니다.
강사는 시험지를 보지 못했다고 말했습니다. 따라서 이 목록은 강사의 복습 권고이지 확정 출제표가 아닙니다.
근거 · 실습4 전사 00:21~00:39, 16:48~19:04 · 실습5 13:17~40:42
실습 4 · Chapter 2·3 핵심 복습 · 00-1 · Dataset / MultiHeadAttention
Chapter 4의 GPT를 이해하려면 입력·타깃과 Q/K/V의 흐름부터 연결해야 합니다.
강사는 실습 4의 01:23~05:47에 Chapter 2·3 코드를 다시 설명했습니다. Dataset은 길이가 같은 입력과 타깃을 만들고, 타깃만 입력보다 토큰 하나 뒤로 이동합니다. DataLoader는 batch_size로 묶음 크기를 정하고, shuffle로 순서를 섞으며, drop_last로 마지막 작은 배치의 제외 여부를 정합니다.
Chapter 2 · 한 칸 뒤가 정답인 이유
input_chunk = token_ids[i:i + max_length]
target_chunk = token_ids[i + 1:i + max_length + 1]
# 예: input=[1,2,3,4] → target=[2,3,4,5]
# 둘의 길이는 모두 4입니다.
Chapter 3의 MultiHeadAttention은 토큰 벡터를 Q/K/V로 투영합니다. view는 여러 Head로 묶어 볼 차원을 만들고, transpose는 행렬곱에 필요한 축 순서를 바꿉니다. Q와 K의 유사도에 미래 위치 마스크를 적용한 후 Softmax를 계산합니다. 마지막으로 확률 가중치를 V에 곱해 문맥 벡터를 만듭니다.
Chapter 3 · 실제 main 코드의 forward 핵심 부분
b, num_tokens, d_in = x.shape
keys = self.W_key(x)
queries = self.W_query(x)
values = self.W_value(x)
keys = keys.view(b, num_tokens, self.num_heads, self.head_dim)
queries = queries.view(b, num_tokens, self.num_heads, self.head_dim)
values = values.view(b, num_tokens, self.num_heads, self.head_dim)
keys = keys.transpose(1, 2)
queries = queries.transpose(1, 2)
values = values.transpose(1, 2)
attn_scores = queries @ keys.transpose(2, 3)
mask_bool = self.mask.bool()[:num_tokens, :num_tokens]
attn_scores.masked_fill_(mask_bool, -torch.inf)
attn_weights = torch.softmax(attn_scores / keys.shape[-1]**0.5, dim=-1)
attn_weights = self.dropout(attn_weights)
context_vec = (attn_weights @ values).transpose(1, 2)
context_vec = context_vec.contiguous().view(b, num_tokens, self.d_out)
context_vec = self.out_proj(context_vec)
왜 이 순서인가요?미래 토큰을 -inf로 가린 뒤 Softmax를 적용하면 그 위치의 가중치는 0이 됩니다. keys.shape[-1]**0.5는 한 Head의 차원 크기에 대한 제곱근으로 점수를 조절합니다. 여러 Head의 결과를 합친 뒤 out_proj가 정보를 혼합합니다. GPT의 잔차 연결에서 두 텐서를 더하려면 최종 입력·출력 차원은 같아야 합니다.
복습용 질문 3개 · 원본 ????와 별도
- 입력 토큰이 4개라면 타깃은 몇 개이며 왜 시작 위치가 다를까요?
view와 transpose는 왜 모두 필요할까요?masked_fill_(-torch.inf)와 out_proj의 역할은 무엇일까요?
답: ① 타깃도 4개입니다. 다음 토큰을 예측하므로 한 칸 밀립니다. ② Head별 차원을 만들고 행렬곱에 맞는 축 순서로 바꿉니다. ③ 미래 정보를 차단하고, 합쳐진 Head 정보를 다시 섞습니다.
구분: 이 질문은 AI 보충 복습입니다. Chapter 4의 이 두 이전 장 코드에는 실제 ????가 없습니다. 실제 빈칸 34개는 실전 코드 탭에서 풉니다.
근거 · 실습4 전사 01:23~05:47 · Chapter 4 main Cells 0·1 (Dataset / MultiHeadAttention, 빈칸 없음)
실습 4 · GPT 모델 · 01 · LayerNorm
입력값의 크기를 정돈한 후 다음 계산으로 넘깁니다.
입력 벡터의 값이 지나치게 커지거나 한쪽으로 치우치면 다음 레이어가 다루기 어렵습니다. LayerNorm은 마지막 차원에서 평균을 빼고 분산을 사용해 크기를 조절합니다. 이 정규화 결과에 학습 가능한 scale과 shift를 적용합니다. 강사는 GELU의 복잡한 수식 암기보다 실제 빈칸과 연결되는 LayerNorm 구조를 설명했습니다.
코드 또는 흐름을 따라갑니다
mean = x.mean(dim=-1, keepdim=True)
var = x.var(dim=-1, keepdim=True, unbiased=False)
norm_x = (x - mean) / torch.sqrt(var + self.eps)
return self.scale * norm_x + self.shift
왜 이렇게 작성하나요?`mean`은 중심을 맞추고, `var`는 얼마나 퍼져 있는지 나타냅니다. `eps`는 0으로 나누는 상황을 완화합니다. `scale`, `shift`는 모델이 학습으로 조절합니다. 입력 shape를 바꾸는 연산이 아니므로 블록의 잔차 연결과도 잘 맞습니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습4 원본 Chapter 4 Cell 2 · 전사 초반 LayerNorm 설명
실습 4 · GPT 모델 · 02 · FeedForward
Attention으로 모은 정보를 토큰별로 가공합니다.
Attention은 여러 토큰 사이의 관계를 참고해 정보를 섞습니다. 그다음 FeedForward는 각 토큰에 동일한 네트워크를 독립적으로 적용해 정보를 가공합니다. 이것이 Attention과 FFN을 모두 사용하는 이유입니다. 입력 차원으로 다시 돌아와야 원래 입력과 잔차 연결로 더할 수 있습니다.
코드 또는 흐름을 따라갑니다
nn.Linear(emb_dim, 4 * emb_dim)
GELU()
nn.Linear(4 * emb_dim, emb_dim)
왜 이렇게 작성하나요?예를 들어 `emb_dim=768`이면 `768 → 3072 → 768`입니다. `4`는 헤드 수가 아닙니다. FFN의 중간 확장 배수입니다. `GELU()`는 비선형 활성화 함수입니다. 이 세 줄은 하나의 `nn.Sequential` 안에 들어갑니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습4 원본 Chapter 4 Cell 2 · 전사 FFN 설명
실습 4 · GPT 모델 · 03 · TransformerBlock
Attention과 FFN을 잔차 연결로 묶은 한 층입니다.
블록의 `forward()`는 두 단계입니다. 먼저 입력을 저장하고 LayerNorm을 거쳐 Attention에 통과시킵니다. 결과를 저장한 입력에 더합니다. 그다음에는 변경된 x를 다시 저장하고 LayerNorm과 FeedForward를 거쳐 더합니다. 강사는 두 개의 정규화와 두 번의 shortcut을 순서대로 설명했습니다.
코드 또는 흐름을 따라갑니다
shortcut = x
x = self.norm1(x)
x = self.att(x)
x = self.drop_shortcut(x)
x = x + shortcut
shortcut = x
x = self.norm2(x)
x = self.ff(x)
x = self.drop_shortcut(x)
x = x + shortcut
왜 이렇게 작성하나요?`self.att`는 위에서 만든 MultiHeadAttention입니다. `self.ff`는 FeedForward입니다. `shortcut`은 직전 입력을 보존합니다. `x + shortcut`은 정보 흐름과 기울기 전달을 돕습니다. 강의는 Pre-LayerNorm 순서로 진행했습니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습4 전사 09:18~10:13 · Chapter 4 Cell 3
실습 4 · GPT 모델 · 04 · GPTModel
토큰 정보와 위치 정보를 더한 뒤 블록을 반복해서 통과시킵니다.
GPT는 토큰 ID만 보고 단어의 위치를 자동으로 알 수 없습니다. 토큰 임베딩과 위치 임베딩을 더해 각 위치의 입력 벡터를 만듭니다. 그런 뒤 설정의 `n_layers`만큼 TransformerBlock을 순서대로 통과시킵니다. 마지막 LayerNorm과 출력 헤드가 다음 토큰 후보별 점수(logits)를 만듭니다.
코드 또는 흐름을 따라갑니다
tok_embeds = self.tok_emb(in_idx)
pos_embeds = self.pos_emb(torch.arange(seq_len, device=in_idx.device))
x = tok_embeds + pos_embeds
x = self.drop_emb(x)
x = self.trf_blocks(x)
x = self.final_norm(x)
logits = self.out_head(x)
왜 이렇게 작성하나요?`vocab_size`는 가능한 토큰 종류 수이므로 토큰 임베딩의 행 수와 출력 logits의 마지막 차원에 들어갑니다. `context_length`는 위치 임베딩의 최대 위치 수입니다. `n_heads=12`는 한 Attention 모듈 내 병렬 관점 수입니다. `n_layers=12`는 TransformerBlock이 12개라는 뜻입니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습4 전사 10:13~11:38, 13:56 · Chapter 4 Cell 3, 5
실습 4 · GPT 모델 · 05 · Tensor shape
shape를 따라가면 왜 마지막 위치가 중요한지 알 수 있습니다.
모델 입력은 2차원 `(배치, 토큰 수)`입니다. 임베딩을 거치면 각 토큰이 벡터로 바뀌어 3차원이 됩니다. Attention 내부에서는 헤드 축을 추가해 여러 관점으로 나누지만, 결과는 다시 `emb_dim`으로 합쳐집니다. 마지막 출력은 토큰마다 전체 어휘 후보 수만큼 점수를 담습니다.
코드 또는 흐름을 따라갑니다
입력 토큰 ID (B, T)
토큰 + 위치 임베딩 (B, T, 768)
Q/K/V 헤드 분리 (B, 12, T, 64)
Attention 합친 결과 (B, T, 768)
최종 logits (B, T, 50257)
마지막 위치 선택 (B, 50257)
왜 이렇게 작성하나요?B는 문장 묶음의 수, T는 현재 입력의 토큰 수, V는 어휘 크기입니다. `768 / 12 = 64`이므로 각 헤드가 64차원을 담당합니다. 다음 토큰 하나를 만들려면 가장 마지막 입력 위치가 예측한 `(B,V)` 점수만 사용합니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습4 전사 12:00~12:47 · Chapter 4 Cells 1, 3, 4, 5
실습 4 · GPT 모델 · 06 · generate_text_simple
한 토큰을 고르고 문장 뒤에 붙이는 과정을 반복합니다.
이미 만들어진 모델에 입력을 넣어 결과를 얻는 것은 추론입니다. 생성 함수는 마지막 `context_size`개 토큰을 모델에 전달합니다. 모델은 각 입력 위치의 logits를 반환하지만, 지금 이어 쓸 토큰 하나는 마지막 위치의 점수로 고릅니다. `argmax`로 가장 큰 logit의 토큰 ID를 선택하고 `cat`으로 입력 뒤에 붙입니다.
코드 또는 흐름을 따라갑니다
idx_cond = idx[:, -context_size:]
with torch.no_grad():
logits = model(idx_cond)
logits = logits[:, -1, :]
idx_next = torch.argmax(logits, dim=-1, keepdim=True)
idx = torch.cat((idx, idx_next), dim=1)
왜 이렇게 작성하나요?`-context_size:`는 끝에서 최대 문맥 길이만큼 남깁니다. `[:, -1, :]`는 모든 배치에서 마지막 위치의 후보 점수만 선택합니다. `keepdim=True`여야 `idx_next`가 `(B,1)`이 되어 `(B,T)`인 idx와 `dim=1`로 결합할 수 있습니다. `no_grad()`는 기울기 추적을 생략합니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습4 전사 12:00~13:28 · Chapter 4 Cell 4
실습 4 · GPT 모델 · 07 · 모델 설정과 초기 상태
설정값은 암기보다 각 값이 어디에 쓰이는지 이해합니다.
강사는 GPT-2 Small 설정으로 토큰 수, 문맥 길이, 임베딩 차원, 헤드와 블록 개수를 연결했습니다. 모델을 `GPTModel(cfg)`로 만들었다고 곧바로 학습된 GPT-2의 성능이 생기지 않습니다. 가중치가 초기화된 모델을 사용하기 때문입니다. `model.eval()`은 평가/추론 모드로 동작을 바꾸지만 학습을 수행하지 않습니다.
코드 또는 흐름을 따라갑니다
GPT_CONFIG_124M = {
"vocab_size": 50257, "context_length": 1024,
"emb_dim": 768, "n_heads": 12, "n_layers": 12,
"drop_rate": 0.1, "qkv_bias": False
}
model = GPTModel(GPT_CONFIG_124M)
model.eval()
왜 이렇게 작성하나요?`eval()`은 Dropout 등을 평가 동작으로 바꿉니다. `no_grad()`는 역전파 그래프 계산을 비활성화합니다. 두 개는 같은 기능이 아닙니다. Chapter 5에서는 훈련 예시의 `context_length`가 256으로 줄어듭니다. 따라서 두 챕터의 설정을 섞어서 답하면 안 됩니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습4 전사 13:56~15:57 · Chapter 4 Cell 5; Chapter 5 Cell 5
실습 5 · 사전학습 · 08 · 학습/검증/시험 데이터
실습 5에서는 모델을 무엇으로 고치고 무엇으로 평가하는지 구분합니다.
강사는 학습 데이터를 훈련·검증·시험으로 나누는 이유를 먼저 설명했습니다. Train은 가중치 업데이트에 사용합니다. Validation은 학습 도중 보지 않은 데이터에서 성능을 점검합니다. Test는 학습 종료 후 최종 성능을 평가하는 목적으로 남겨둡니다. 학습 Loss가 내려가더라도 검증 Loss가 올라가면 과적합 가능성을 확인해야 합니다.
코드 또는 흐름을 따라갑니다
텍스트 데이터
├─ Train → loss.backward() → optimizer.step()
├─ Val → 검증 손실 확인 (가중치 업데이트 X)
└─ Test → 학습 완료 후 최종 평가
왜 이렇게 작성하나요?강의에서 7:2:1을 일반적인 분할 사례로 언급했습니다. 특정 비율은 상황에 따라 달라집니다. 실제 Chapter 5 노트북의 학습용 설정과 일반 이론 설명을 구분하세요. 검증 Loss 상승은 과적합을 의심하게 하지만, 무조건 즉시 과적합이라고 단정하는 것은 아닙니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습5 전사 13:17~15:42
실습 5 · 사전학습 · 09 · Cross Entropy Loss
모델이 정답 토큰에 얼마나 낮은 점수를 줬는지 벌점을 매깁니다.
텍스트 생성의 정답은 숫자 하나가 아니라 수많은 토큰 후보 중 하나입니다. 강사는 MSE처럼 숫자 간 거리를 보는 상황과 구별하여 Cross Entropy를 설명했습니다. 모델은 모든 어휘 토큰에 logits를 출력하고, Cross Entropy는 정답 토큰에 배정한 확률이 낮을수록 큰 손실을 줍니다.
코드 또는 흐름을 따라갑니다
정답 토큰 = 사과
모델이 정답에 준 확률 p = 0.9 → -ln(0.9) ≈ 0.105
모델이 정답에 준 확률 p = 0.1 → -ln(0.1) ≈ 2.303
정답 확률이 낮아질수록 Loss가 커집니다.
왜 이렇게 작성하나요?PyTorch의 `torch.nn.functional.cross_entropy()`는 logits를 직접 받습니다. 이 함수 앞에서 별도로 softmax를 적용하지 않습니다. 정답 레이블은 토큰 ID입니다. Loss는 다음 위치의 정답 토큰을 더 높은 점수로 예측하도록 학습을 유도합니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습5 전사 16:07~21:14, 30:22~34:09
실습 5 · 사전학습 · 10 · calc_loss_batch
3차원 logits를 Cross Entropy가 다룰 수 있는 행으로 펴줍니다.
한 배치의 모델 출력은 `(B,T,V)`로, 각 위치마다 V개의 토큰 후보 점수가 있습니다. Cross Entropy가 이 예제에서 받는 형태는 `(B×T,V)`의 점수 행렬과 `(B×T)`의 정답 ID 벡터입니다. 그래서 `flatten(0,1)`로 배치와 시간 축을 합칩니다. 타깃도 flatten해서 한 줄로 만듭니다.
코드 또는 흐름을 따라갑니다
logits = model(input_batch) # (B,T,V)
loss = torch.nn.functional.cross_entropy(
logits.flatten(0, 1), # (B*T,V)
target_batch.flatten() # (B*T,)
)
왜 이렇게 작성하나요?예: B=2, T=4이면 모델은 총 8개 위치를 예측합니다. logits가 `(2,4,50257)`이라면 `(8,50257)`로, target이 `(2,4)`라면 `(8,)`로 바꿉니다. `flatten(0,1)`과 `flatten()`은 같은 코드가 아니라 대상 축이 다릅니다. 입력·정답·모델을 같은 device에 두는 것도 필요합니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습5 전사 30:22~34:44 · Chapter 5 Cell 1
실습 5 · 사전학습 · 11 · train_model_simple
훈련은 다섯 줄을 한 덩어리로 기억합니다.
모델을 학습 모드로 바꾸고 배치를 하나씩 가져옵니다. 이전 배치의 기울기가 남아 있지 않도록 초기화합니다. 순전파로 Loss를 구하고 `backward()`로 각 가중치의 기울기를 계산합니다. 마지막 `step()`에서 Optimizer가 실제 가중치를 수정합니다. 이것을 배치와 에포크에 걸쳐 반복합니다.
코드 또는 흐름을 따라갑니다
model.train()
for input_batch, target_batch in train_loader:
optimizer.zero_grad()
loss = calc_loss_batch(input_batch, target_batch, model, device)
loss.backward()
optimizer.step()
왜 이렇게 작성하나요?`model.train()`은 파라미터를 직접 업데이트하지 않습니다. `zero_grad()`는 갱신도 손실 계산도 아닙니다. `backward()`만으로도 모델의 값은 바뀌지 않습니다. `optimizer.step()`에서 갱신합니다. 이 역할을 구별하면 함수 빈칸을 외우지 않아도 복원할 수 있습니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습5 전사 35:17~37:04 · Chapter 5 Cell 2
실습 5 · 사전학습 · 12 · Batch/Step/Epoch와 Optimizer
학습 반복 횟수와 업데이트 단위를 구별합니다.
Batch는 한 번에 처리하는 데이터 묶음입니다. Step은 일반적인 학습 루프에서 한 배치를 처리하고 Optimizer 업데이트를 한 번 수행하는 단위입니다. Epoch는 학습 데이터 전체를 한 번 훑는 과정입니다. 강사는 학습률과 Adam류 Optimizer가 가중치를 어떻게 바꾸는지 설명했습니다.
코드 또는 흐름을 따라갑니다
데이터 100개, batch_size=10, drop_last=False
1 epoch ≈ 10 batches → 10 optimizer steps
3 epochs ≈ 30 optimizer steps
optimizer = torch.optim.AdamW(
model.parameters(), lr=settings["learning_rate"],
weight_decay=settings["weight_decay"]
)
왜 이렇게 작성하나요?위 숫자는 개념 설명용 예시입니다. `model.parameters()`는 Optimizer가 갱신할 파라미터를 제공합니다. `lr`은 학습률의 인자명입니다. `weight_decay`는 정규화 설정입니다. 실제 스텝 수는 `drop_last`, 데이터 수, 기울기 누적 같은 조건에 따라 바뀝니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습5 전사 21:32~29:26, 38:27~39:20 · Chapter 5 Cell 4
실습 5 · 사전학습 · 13 · 학습과 평가의 차이
검증할 때는 파라미터를 고치지 않고 성능을 확인합니다.
`evaluate_model()`은 `model.eval()`로 바꾼 뒤 `no_grad()`에서 훈련/검증 손실을 계산합니다. 이 루틴에는 `loss.backward()`와 `optimizer.step()`이 없습니다. 그다음 코드가 훈련을 계속할 수 있도록 `model.train()`으로 복귀합니다. 학습 중 샘플 문장을 생성할 때도 추론 모드로 잠시 전환합니다.
코드 또는 흐름을 따라갑니다
model.eval()
with torch.no_grad():
train_loss = calc_loss_loader(train_loader, model, device, num_batches=eval_iter)
val_loss = calc_loss_loader(val_loader, model, device, num_batches=eval_iter)
model.train()
왜 이렇게 작성하나요?`eval()`과 `no_grad()`는 함께 쓰이지만 역할이 다릅니다. `eval()`은 Dropout 등의 동작을 전환합니다. `no_grad()`는 연산 그래프 추적을 생략합니다. `train_loss`를 계산한다고 그 순간 모델을 훈련하는 것은 아닙니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · Chapter 5 Cell 4 · 실습5 학습·검증 전사
실습 5 · 사전학습 · 14 · generate()와 토큰 변환
모델의 출력에서 다음 토큰을 선택하고 텍스트로 복원합니다.
학습이 끝나면 입력 문장을 `tokenizer.encode()`로 토큰 ID로 만들고 `unsqueeze(0)`으로 배치 축을 추가합니다. 모델은 생성 함수에서 마지막 위치 logits를 뽑고 `argmax`로 다음 토큰을 선택합니다. 마지막에는 `squeeze(0)`으로 배치 축을 제거하고 `tokenizer.decode()`로 사람이 읽을 문장으로 돌립니다.
코드 또는 흐름을 따라갑니다
encoded = tokenizer.encode(text)
encoded_tensor = torch.tensor(encoded).unsqueeze(0)
# [T] → [1,T]
logits = logits[:, -1, :]
idx_next = torch.argmax(logits, dim=-1, keepdim=True)
idx = torch.cat((idx, idx_next), dim=1)
flat = token_ids.squeeze(0)
text = tokenizer.decode(flat.tolist())
왜 이렇게 작성하나요?Chapter 5의 `generate()`는 설명 문자열에 Temperature/Top-k가 등장하지만 본문은 greedy `argmax` 방식입니다. 강사는 Top-k/Temperature 구현이 시험에 포함되지 않는다고 명시했습니다. 또한 원본의 `if idx_next == eos_id:`는 `eos_id=None` 기본 설정에서 의도치 않은 동작이 없는지 실행 환경에서 검증할 필요가 있습니다. 설명과 실제 코드를 구분합니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습5 전사 00:11~01:58, 37:04~37:56 · Chapter 5 Cells 3, 4
실습 5 · 사전학습 · 15 · 저장과 로드
학습된 모델을 다시 사용할 수 있게 가중치를 보관합니다.
훈련이 끝난 모델의 학습 결과는 주로 가중치에 담깁니다. `state_dict()`는 가중치 등의 상태를 사전 형식으로 반환합니다. 새 모델을 같은 설정으로 만들고 저장된 값을 `load_state_dict()`로 채웁니다. 학습·추론 장치와 모델이 같은 위치에 있도록 `to(device)`로 이동시켜야 합니다.
코드 또는 흐름을 따라갑니다
torch.save(model.state_dict(), "outputs/model.pth")
model = GPTModel(GPT_CONFIG_124M)
model.load_state_dict(torch.load("outputs/model.pth", weights_only=True))
model.to(device)
왜 이렇게 작성하나요?`torch.save()`는 파일을 씁니다. `load_state_dict()`는 이미 만든 모델 객체에 값들을 채웁니다. `to(device)`는 CPU/GPU 장치를 맞춥니다. 강사는 Loss 계산, 학습 루프, Optimizer 선언과 함께 저장·로드를 기억하라고 강조했습니다.
시험에서 연결되는 질문
코드 빈칸을 볼 때 이전 단계의 출력과 다음 단계가 요구하는 입력을 먼저 설명해 보세요. 암기보다 원본 함수의 역할과 텐서 차원으로 정답을 고르는 것이 목적입니다.
동일한 함수 이름이 실제 원본 ????에 등장한다면 실전 코드 모드에서 직접 복원할 수 있습니다.
근거 · 실습5 전사 38:27~40:42 · Chapter 5 Cell 5