Skip to main content

Autograd (자동 미분)

학습 목표

  • PyTorch Autograd 엔진의 동작 원리를 이해한다
  • requires_grad, .backward(), .grad의 역할을 설명할 수 있다
  • torch.no_grad() 컨텍스트의 용도와 중요성을 안다
  • 계산 그래프의 동적 생성과 해제 메커니즘을 이해한다

왜 중요한가

Autograd는 PyTorch의 핵심 엔진입니다. 텐서에 대한 모든 연산을 기록하여 자동으로 기울기를 계산합니다. 사용자가 미분 공식을 직접 구현할 필요 없이, .backward() 한 번의 호출로 모든 파라미터의 기울기를 얻을 수 있습니다.

requires_grad

requires_grad=True로 설정된 텐서에 대한 연산은 모두 계산 그래프에 기록됩니다.
import torch

# 기울기 추적 활성화
w = torch.tensor([2.0, 3.0], requires_grad=True)
b = torch.tensor([1.0], requires_grad=True)

# 연산 수행 (계산 그래프 자동 구성)
x = torch.tensor([4.0, 5.0])
y = w @ x + b       # y = 2*4 + 3*5 + 1 = 24
loss = y ** 2        # loss = 576

print(f"y = {y.item()}")
print(f"loss = {loss.item()}")
print(f"y의 grad_fn: {y.grad_fn}")      # AddBackward
print(f"loss의 grad_fn: {loss.grad_fn}")  # PowBackward

backward()

.backward()를 호출하면 계산 그래프를 역순으로 순회하며 기울기를 계산합니다.
# 역전파 수행
loss.backward()

# 기울기 확인
print(f"∂loss/∂w = {w.grad}")  # [384., 480.] = 2*y*x = 2*24*[4,5]
print(f"∂loss/∂b = {b.grad}")  # [48.] = 2*y*1 = 2*24

기울기 누적 주의

.backward()를 여러 번 호출하면 기울기가 누적됩니다. 매 학습 스텝마다 .zero_grad()로 초기화해야 합니다.
# 기울기 누적 문제 시연
w = torch.tensor([1.0], requires_grad=True)

for i in range(3):
    y = (w * 2) ** 2
    y.backward()
    print(f"Step {i}: w.grad = {w.grad}")
    # Step 0: 8.0
    # Step 1: 16.0 (누적!)
    # Step 2: 24.0 (누적!)

# 올바른 사용: 매번 초기화
w = torch.tensor([1.0], requires_grad=True)
for i in range(3):
    if w.grad is not None:
        w.grad.zero_()         # 기울기 초기화
    y = (w * 2) ** 2
    y.backward()
    print(f"Step {i}: w.grad = {w.grad}")
    # 매 스텝 8.0

torch.no_grad()

추론(Inference) 시에는 기울기 계산이 불필요합니다. torch.no_grad() 컨텍스트를 사용하면 계산 그래프가 생성되지 않아 메모리를 절약하고 속도를 높일 수 있습니다.
model = torch.nn.Linear(10, 1)
x = torch.randn(32, 10)

# 추론 시: no_grad 사용
with torch.no_grad():
    output = model(x)
    print(f"output.requires_grad: {output.requires_grad}")  # False

# 동일한 효과: @torch.inference_mode() (더 빠름, PyTorch 1.9+)
@torch.inference_mode()
def predict(model, x):
    return model(x)
추론 성능이 중요하다면 torch.no_grad() 대신 torch.inference_mode()를 사용하세요. 내부적으로 더 공격적인 최적화를 수행하여 약 5~10% 빠릅니다.

detach()

계산 그래프에서 텐서를 분리합니다. 기울기 흐름이 차단되어 해당 텐서 이전의 연산은 역전파에 포함되지 않습니다.
a = torch.tensor([2.0], requires_grad=True)
b = a * 3
c = b.detach()   # 계산 그래프에서 분리
d = c * 4

# d에서 backward 호출해도 a로 기울기가 전파되지 않음
# d.backward()  # RuntimeError: requires_grad=False

# 활용: GAN의 Discriminator 학습 시 Generator 기울기 차단
# fake_output = discriminator(generator(z).detach())

계산 그래프의 동적 생성

PyTorch는 **동적 계산 그래프(Dynamic Computational Graph)**를 사용합니다. 매 순전파 시 새 그래프가 생성되고, .backward() 후 해제됩니다.
w = torch.tensor([1.0], requires_grad=True)

# 조건에 따라 다른 연산 수행 가능
for i in range(3):
    if i % 2 == 0:
        y = w * 2    # 짝수 스텝: 2w
    else:
        y = w ** 3   # 홀수 스텝: w³

    y.backward()
    print(f"Step {i}: grad = {w.grad}")
    w.grad.zero_()
이러한 동적 그래프 덕분에 Python의 if, for, while 등 제어문을 자유롭게 사용할 수 있습니다.

실전 패턴: 학습 스텝

import torch
import torch.nn as nn

model = nn.Linear(10, 1)
optimizer = torch.optim.SGD(model.parameters(), lr=0.01)
criterion = nn.MSELoss()

x = torch.randn(32, 10)
target = torch.randn(32, 1)

# 한 스텝의 학습 과정
optimizer.zero_grad()              # 1. 기울기 초기화
output = model(x)                  # 2. 순전파
loss = criterion(output, target)   # 3. 손실 계산
loss.backward()                    # 4. 역전파 (기울기 계산)
optimizer.step()                   # 5. 가중치 업데이트

print(f"Loss: {loss.item():.4f}")
기본적으로 .backward() 호출 후 계산 그래프가 해제됩니다. 같은 그래프에서 .backward()를 여러 번 호출해야 할 때 retain_graph=True를 사용합니다. GAN 학습에서 같은 순전파 결과를 Generator와 Discriminator 업데이트에 모두 사용할 때가 대표적입니다.
텐서의 기울기가 계산될 때 호출되는 콜백 함수를 등록합니다. 기울기 디버깅, 기울기 클리핑, 중간 층의 기울기 시각화 등에 활용합니다.

체크리스트

  • requires_grad=True의 역할을 이해한다
  • .backward() 후 기울기가 누적되는 것을 알고 .zero_grad()를 사용한다
  • torch.no_grad() / torch.inference_mode()의 사용 시점을 안다
  • .detach()로 기울기 흐름을 차단하는 방법을 이해한다

다음 문서