

딥러닝 학습 사이클의 큰 그림부터 PyTorch 코드 패턴까지, 입문자가 꼭 알아야 할 핵심을 담았습니다.
딥러닝의 학습 과정은 사실 인간이 공부하는 방식과 똑같습니다.
| 단계 | 인간 학생 | 컴퓨터 (딥러닝) |
|---|---|---|
| 1 | 문제집 풀기 | 순전파 (Forward) - 데이터 보고 예측 |
| 2 | 채점하기 | 손실 계산 (Loss) - 정답과 비교 |
| 3 | 피드백 확인 | 오차 측정 - 얼마나 틀렸는지 수치화 |
| 4 | 다시 풀기 | 역전파/업데이트 - 파라미터 수정 |
"우리가 시험장에서 문제를 푸는 것도 예측입니다.
ChatGPT의 답변도 마찬가지로 확률값일 뿐, 절대적인 사실이 아닙니다."
딥러닝은 이런 예측을 반복적으로 개선하는 알고리즘입니다. 그래서 "정답을 맞춘다"가 아니라 "정답에 가까워진다"가 더 정확한 표현입니다.
optimizer.zero_grad() # 1. 기울기 초기화
output = model(input) # 2. 순전파
loss = criterion(output, target) # 3. 손실 계산
loss.backward() # 4. 역전파
optimizer.step() # 5. 파라미터 갱신
평가 단계에서는 기울기 계산이 불필요하므로 다음 패턴을 사용합니다.
model.eval()
with torch.no_grad():
output = model(test_input)
손실은 "내 예측이 정답에서 얼마나 멀리 떨어져 있는가"를 숫자로 표현한 값입니다.
정답(5) - 예측(4) = 손실(1)
학습의 목표는 단 하나, 이 손실을 0에 가깝게 만드는 것입니다.
가장 기본적인 손실 함수인 MSE를 PyTorch로 구현해봅시다.

import torch
def mse(Yp, Y):
loss = ((Yp - Y) ** 2).mean()
return loss
# 테스트
Yp = torch.tensor([1.0, 2.5, 3.8])
Y = torch.tensor([1.2, 2.0, 4.0])
print(f"MSE: {mse(Yp, Y):.4f}") # 0.1100
계산 과정:
[-0.2, 0.5, -0.2][0.04, 0.25, 0.04]0.33 / 3 = 0.11텐서를 사용하면 for 루프 없이 한 줄로 끝납니다. 이것이 NumPy/PyTorch의 강력함입니다.
역전파는 손실에서 시작해 거꾸로 거슬러 올라가며 각 파라미터가 오차에 얼마나 기여했는지 계산하는 과정입니다.
[순전파] x → f₁ → f₂ → ... → fₗ → y (예측)
↓
loss 계산
↓
[역전파] x ← f₁ ← f₂ ← ... ← fₗ ← loss
(기울기를 거꾸로 전파)
PyTorch에서는 단 한 줄로 끝납니다.
loss.backward() # 모든 파라미터의 .grad에 미분값 자동 저장
새 파라미터 = 현재 파라미터 - (학습률 × 기울기)
W_new = W - (lr × grad)
왜 빼는가? 기울기는 손실이 증가하는 방향이므로, 그 반대 방향으로 가야 손실이 줄어듭니다.
| 학습률이 클 때 | 학습률이 작을 때 |
|---|---|
| 최적점을 지나쳐 발산 | 너무 느려서 도달 못 함 |
| 고속도로에서 출구를 놓침 | 영원히 도착 못 함 |
수동 방식 (개념 이해용):
loss.backward()
with torch.no_grad():
W -= lr * W.grad
B -= lr * B.grad
W.grad.zero_()
B.grad.zero_()
옵티마이저 방식 (실무):
optimizer = optim.SGD([W, B], lr=0.001)
# 학습 루프
loss.backward()
optimizer.step() # 업데이트
optimizer.zero_grad() # 초기화
모델 파라미터가 수백만 개일 때 수동 업데이트는 불가능합니다. 옵티마이저는 이를 한 줄로 해결해줍니다.
| 옵티마이저 | 특징 |
|---|---|
| SGD | 가장 기본 |
| Adam | 실무에서 가장 많이 사용 |
| AdamW | Transformer 등에서 사용 |
처음엔 Adam을 쓰면 대부분 무난합니다.
import numpy as np
x = np.array([5, 7, 9])
y = x # 참조만 복사!
x[0] = 100
print(y) # [100, 7, 9] - y도 함께 변함!
y = x는 값을 복사하는 게 아니라 같은 메모리 주소를 가리키게 만듭니다.
x = np.array([5, 7, 9])
y = x.copy() # 새 메모리에 값 복사
x[0] = 100
print(y) # [5, 7, 9] - 원본 유지!
데이터 전처리 과정에서 원본을 훼손하면 재현 불가능한 버그가 발생합니다.
원본 데이터는 무조건 사수해야 합니다. 비교를 위해서라도 따로 보관하세요.
x → [Layer1] → [Layer2] → ... → [LayerL] → y
수식으로 표현하면 다음과 같습니다.

def f1(x): return x ** 2 # 제곱
def f2(x): return 2 * x # 2배
def f3(x): return x + 2 # +2 더하기
x = 3
y = f3(f2(f1(x))) # 3 → 9 → 18 → 20
print(y) # 20
이게 바로 순전파(Forward Propagation)의 본질입니다. 입력이 여러 함수를 거치며 변형되어 최종 출력이 나오는 과정이죠.
한 번에 복잡한 함수를 만들기는 어렵습니다. 대신 단순한 레이어를 쌓아서 복잡한 표현력을 만들어내는 것이 딥러닝의 핵심 전략입니다.
미분은 학습의 나침반입니다. "어느 방향으로 가야 손실이 줄어드는가?"를 알려주죠.
가중치 ⬆
📍 (현재 위치)
╱
╱ ← 미분값 양수 → 반대로(왼쪽) 가야 손실 감소
╱
🎯 (최적점)

def fdiff(f):
def diff(x):
h = 1e-6
return (f(x+h) - f(x-h)) / (2*h)
return diff
f = lambda x: 2*x**2 + 2
f_prime = fdiff(f)
print(f_prime(3.0)) # 12.0000...
수치 미분을 직접 구현할 필요 없이, PyTorch가 자동으로 해줍니다.
import torch
x = torch.tensor(3.0, requires_grad=True) # 추적 ON
y = 2 * x**2 + 2
y.backward() # 자동 미분
print(x.grad) # tensor(12.)
핵심 코드: requires_grad=True 의 s를 절대 빠뜨리지 마세요.
import torch
# 데이터
X = torch.tensor([-5., 5., 0., 2., -2.])
Y = torch.tensor([-6.7, 10.3, -3.3, 5.0, -5.3])
# 파라미터
W = torch.tensor(1.0, requires_grad=True)
B = torch.tensor(1.0, requires_grad=True)
lr = 0.001
# 예측 & 손실
def pred(X): return W * X + B
def mse(Yp, Y): return ((Yp - Y)**2).mean()
# 1회 반복
Yp = pred(X)
loss = mse(Yp, Y)
loss.backward()
with torch.no_grad():
W -= lr * W.grad
B -= lr * B.grad
W.grad.zero_()
B.grad.zero_()
| 요소 | 의미 | 비유 |
|---|---|---|
| Class (클래스) | 설계도 | 붕어빵 틀 |
| Instance (인스턴스) | 실체 | 실제 붕어빵 |
| Attribute (속성) | 데이터/상태 | self.weights |
| Method (메서드) | 동작/함수 | forward() |
class Point:
def __init__(self, x, y): # 생성자 (재료 준비)
self.x = x
self.y = y
def move(self, dx, dy): # 메서드
self.x += dx
self.y += dy
def coord(self):
return (self.x, self.y)
p = Point(2, 3)
p.move(1, -1)
print(p.coord()) # (3, 2)
__call__ 매직 메서드의 비밀class H:
def __call__(self, x):
return 2*x + 2
h = H()
print(h(3)) # 8 - 인스턴스를 함수처럼 호출!
PyTorch에서 model.forward(x)가 아닌 model(x)로 쓰는 이유가 바로 이 __call__ 덕분입니다.
import torch.nn as nn
class MyModel(nn.Module):
def __init__(self):
super().__init__() # 부모 클래스 초기화 (필수!)
self.layer = nn.Linear(10, 1)
def forward(self, x):
return self.layer(x)
# 사용
model = MyModel()
output = model(input) # forward 자동 호출
클래스를 모르면 PyTorch 코드를 절대 읽을 수 없습니다. 모든 LLM, 로봇 코드가 이 구조 위에서 동작합니다.
requires_grad=True는 정확히 뭘 하나요?"이 텐서의 변화를 추적해줘"라고 PyTorch에게 알려주는 옵션입니다.
TrueFalse (기본값)loss.backward()는 왜 loss에서 호출하나요?우리가 줄이고 싶은 것이 바로 loss이기 때문입니다. "loss를 줄이려면 각 파라미터를 어디로 옮겨야 해?"를 묻는 과정입니다.
loss.backward() # loss → ... → W로 역추적
print(W.grad) # 자동으로 미분값이 채워짐
optimizer.zero_grad()를 왜 매번 호출하나요?PyTorch는 기울기를 누적하기 때문입니다. 초기화하지 않으면 이전 배치의 기울기가 쌓여서 학습이 폭주합니다.
# 올바른 패턴
for batch in loader:
optimizer.zero_grad() # 매번 초기화
pred = model(batch)
loss = criterion(pred, target)
loss.backward()
optimizer.step()
model(x)와 model.forward(x)는 같은 건가요?결과는 같지만 반드시 model(x)를 써야 합니다.
model(x)는 __call__을 통해 다음을 추가로 실행합니다:
forward()만 직접 부르면 이런 기능이 모두 무시됩니다.
model.eval()과 torch.no_grad()는 같은 건가요?다릅니다. 둘 다 평가 시 사용하지만 역할이 다릅니다.
| 구분 | model.eval() |
torch.no_grad() |
|---|---|---|
| 역할 | 레이어 동작 변경 | 기울기 계산 OFF |
| 영향 | Dropout, BatchNorm | 메모리/속도 |
평가 시엔 함께 사용합니다.
model.eval()
with torch.no_grad():
output = model(test_input)
super().__init__()은 왜 쓰나요?부모 클래스(nn.Module)의 초기화 코드를 실행하기 위해서입니다. 이걸 안 하면 파라미터 등록, 훅 설정 등이 작동하지 않아 모델이 제대로 동작하지 않습니다.
class MyModel(nn.Module):
def __init__(self):
super().__init__() # 무조건 첫 줄!
...
미분값(기울기)은 "여기서 어느 쪽이 오르막인지"를 알려주는 신호입니다.
공식 W_new = W - lr × grad를 보면:
결국 항상 내리막길로 이동하게 됩니다. 그래서 손실이 점점 작아지는 거죠.
[1] 데이터 입력 (Input)
↓
[2] 순전파 (Forward)
output = model(input)
↓
[3] 손실 계산 (Loss)
loss = criterion(output, target)
↓
[4] 역전파 (Backward)
loss.backward()
↓
[5] 파라미터 업데이트
optimizer.step()
↓
[6] 기울기 초기화
optimizer.zero_grad()
↓
(반복)
이 사이클을 수백, 수천 번 반복하면서 모델은 점점 정답에 가까워집니다.
🤔 [1] - [6]까지 한 글자도 빠짐없이 외워야 하나요?
결론부터: 아니요! 😌
암기는 필요 없습니다. 흐름과 의미만 이해하면 됩니다.
✅ 진짜 외워야 할 것 (이건 자동으로 외워짐)
핵심 5단계 흐름
순전파 → 손실 → 역전파 → 업데이트 → 초기화
(Forward → Loss → Backward → Step → Zero)
이게 끝입니다. 함수 이름은 코드 짜다 보면 자연스럽게 손에 익어요.
requires_grad=True의 역할을 안다__call__ 메서드의 역할을 안다모든 항목에 체크할 수 있다면 1차시 기초는 완벽합니다.
다음 차시 예고: NumPy와 PyTorch 텐서 심화. 4차원 텐서가 등장합니다.