콘텐츠로 이동

Autograd 기초

딥러닝 학습의 핵심은 손실 함수를 최소화하기 위해 파라미터를 업데이트하는 것입니다. 이를 위해 각 파라미터에 대한 손실의 미분값(그래디언트)이 필요합니다.

수식으로 표현하면:

L=f(w)\mathcal{L} = f(\mathbf{w})

여기서 L\mathcal{L} 은 손실(loss), w\mathbf{w} 는 모델 파라미터입니다. 경사하강법(Gradient Descent)으로 파라미터를 업데이트합니다:

wwηLw\mathbf{w} \leftarrow \mathbf{w} - \eta \cdot \frac{\partial \mathcal{L}}{\partial \mathbf{w}}

여기서 η\eta 는 학습률(learning rate)입니다.

파라미터가 수백만 개인 신경망에서 이 미분을 손으로 계산하는 것은 불가능합니다. PyTorch의 Autograd 는 연산 과정을 자동으로 기록하고, 역전파(backpropagation) 알고리즘을 통해 모든 그래디언트를 자동으로 계산합니다.


텐서를 생성할 때 requires_grad=True 를 지정하면, 해당 텐서를 사용하는 모든 연산이 추적됩니다.

import torch
# 추적 활성화
x = torch.tensor(3.0, requires_grad=True)
print(x) # tensor(3., requires_grad=True)
print(x.requires_grad) # True
# 추적 비활성화 (기본값)
y = torch.tensor(3.0)
print(y.requires_grad) # False
# 기존 텐서에 추적 활성화
z = torch.tensor([1.0, 2.0, 3.0])
z.requires_grad_(True) # in-place 설정
print(z.requires_grad) # True

requires_grad=True 인 텐서와의 연산 결과는 자동으로 추적됩니다.

a = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(3.0, requires_grad=True)
c = a * b + a # c = a*b + a
print(c.requires_grad) # True — 자동으로 상속됨
print(c.grad_fn) # <AddBackward0 object> — 역전파 함수 참조

PyTorch는 연산할 때마다 동적 계산 그래프 를 구성합니다. 이 그래프는 순전파(forward pass) 중에 만들어지고, backward() 호출 시 역으로 순회하며 그래디언트를 계산합니다.

x = 3.0 (requires_grad=True)
y = x² (PowBackward)
z = y + 2x (AddBackward)
loss = z (스칼라 출력)
y = x² + 2x 계산 그래프 (x = 3.0)
x값: 3.0값: 9.02x값: 6.0x²+2x값: 15.0loss값: 15.0
x = torch.tensor(3.0, requires_grad=True)
y = x ** 2 # y = x², dy/dx = 2x
z = y + 2 * x # z = x² + 2x, dz/dx = 2x + 2
print(f"y = {y.item()}") # 9.0
print(f"z = {z.item()}") # 15.0
# 각 노드의 grad_fn 확인
print(f"y.grad_fn: {y.grad_fn}") # <PowBackward0>
print(f"z.grad_fn: {z.grad_fn}") # <AddBackward0>

스칼라 텐서에 .backward() 를 호출하면 계산 그래프를 역으로 순회하며 모든 requires_grad=True 텐서의 .grad 를 채웁니다.

x = torch.tensor(3.0, requires_grad=True)
loss = x ** 2 + 2 * x + 1 # loss = x² + 2x + 1
# 역전파 실행
loss.backward()
# 그래디언트 확인: d(loss)/dx = 2x + 2 = 2(3) + 2 = 8
print(x.grad) # tensor(8.)

여러 변수가 있는 경우:

a = torch.tensor(2.0, requires_grad=True)
b = torch.tensor(3.0, requires_grad=True)
# loss = a²b + b³
loss = a ** 2 * b + b ** 3
loss.backward()
# d(loss)/da = 2ab = 2(2)(3) = 12
print(f"a.grad = {a.grad}") # tensor(12.)
# d(loss)/db = a² + 3b² = 4 + 27 = 31
print(f"b.grad = {b.grad}") # tensor(31.)

.backward() 호출 후, requires_grad=True리프 노드(leaf node) 텐서에만 .grad 가 채워집니다.

x = torch.tensor(2.0, requires_grad=True)
y = x * 3 # 중간 노드 (non-leaf)
z = y ** 2 # 스칼라
z.backward()
print(f"x.grad = {x.grad}") # tensor(24.) — x=2: dz/dx = 6x·3 = 12·2 = 24
print(f"y.grad = {y.grad}") # None — 중간 노드는 기본적으로 저장 안 됨
# 중간 노드 그래디언트가 필요하면 retain_grad() 사용
x2 = torch.tensor(2.0, requires_grad=True)
y2 = x2 * 3
y2.retain_grad() # 중간 노드 그래디언트 보존 요청
z2 = y2 ** 2
z2.backward()
print(f"y2.grad = {y2.grad}") # tensor(12.) — dz/dy = 2y = 2(6) = 12

개념설명
requires_grad=True텐서의 연산을 추적하도록 설정
grad_fn텐서를 만든 연산(역전파 함수) 참조
.backward()계산 그래프를 역순으로 순회하며 그래디언트 계산
.grad리프 노드에 축적되는 그래디언트 값
retain_grad()중간 노드의 그래디언트도 보존

다음 장에서는 그래디언트 누적 문제, zero_grad(), torch.no_grad() 등 실전에서 필수인 개념들을 다룹니다.