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} " )
retain_graph=True는 언제 사용하나요?
기본적으로 .backward() 호출 후 계산 그래프가 해제됩니다. 같은 그래프에서 .backward()를 여러 번 호출해야 할 때 retain_graph=True를 사용합니다. GAN 학습에서 같은 순전파 결과를 Generator와 Discriminator 업데이트에 모두 사용할 때가 대표적입니다.
텐서의 기울기가 계산될 때 호출되는 콜백 함수를 등록합니다. 기울기 디버깅, 기울기 클리핑, 중간 층의 기울기 시각화 등에 활용합니다.
체크리스트
다음 문서