자동 미분
자동 미분 (Autograd mechanics)
신경망을 학습시킬 때 결국 필요한 건 "가중치를 얼마나 움직여야 손실이 줄어드는지"예요. PyTorch의 torch.autograd 는 순전파 때 수행한 연산을 그래프로 기록해 두었다가 역전파 때 이 그래프를 따라 기울기를 계산해 줘요. 모든 것을 손으로 미분하지 않아도 되게 만드는 자동 미분의 원리를 가볍게 훑어볼게요.
autograd가 연산 이력을 기록하는 방식
torch.Tensor 는 이 그래프의 진입점이에요. 순전파가 끝나면 뒤에서 역전파로 기울기를 계산할 수 있도록 계산 그래프가 만들어져 있고, 텐서는 이 그래프를 참조해요. 연산 자체는 프레임워크가 처리하지만, 그 구조를 이해하면 디버깅이 훨씬 쉬워져요.
저장된 텐서 (Saved tensors)
커스텀 torch.autograd.Function 을 정의할 때 순전파 중간에 save_for_backward() 로 텐서를 저장해 두고, 역전파에서 saved_tensors 로 꺼내 쓸 수 있어요. PyTorch가 정의한 연산(예: torch.pow())은 필요한 텐서를 자동으로 저장해 주죠. 어떤 텐서가 저장되는지는 grad_fn 의 _saved 접두사 속성을 보면 확인할 수 있어요.
x = torch.randn(5, requires_grad=True)
y = x.pow(2)
print(x.equal(y.grad_fn._saved_self)) # True
print(x is y.grad_fn._saved_self) # True
반드시 같은 객체가 저장되는 건 아니에요. 참조 사이클(reference cycle)을 막기 위해 PyTorch는 텐서를 저장할 때 packing 하고, 읽을 때 unpacking 해 다른 텐서 객체로 돌려줄 수 있어요. y.grad_fn._saved_result 로 얻는 텐서는 y 와 다른 객체지만 같은 저장소를 공유해요. 이 pack/unpack 동작은 saved tensor 훅으로 제어할 수 있어요.
기울기 계산을 끄는 방법
- 리프 텐서가 손실 기울기 계산 목적으로 필요한 중간 텐서(리프가 아닌 텐서)를 다룰 때는
requires_grad설정이 어떤 역할을 하는지 명확히 이해하는 게 좋아요. 리프가 아닌 텐서(즉grad_fn이 있는 텐서)는 역전파 그래프와 연결돼 있어서, 그 기울기가 리프 텐서의 기울기를 구하는 데 중간 결과로 필요해요. - 지역적으로 기울기 계산을 끄고 싶으면
torch.no_grad()같은 컨텍스트나 텐서의requires_grad를 조절해요.
Backward 훅 실행
torch.Tensor.register_hook() 로 텐서에 backward 훅을 등록하면 역전파 중 기울기에 접근해 수정할 수 있어요. 역전파가 끝나 텐서의 grad 필드가 갱신된 뒤에만 실행되는 post-accumulate-grad 훅은 리프 텐서에만 등록할 수 있고, 리프가 아닌 텐서에서 backward(retain_graph=True) 를 호출해도 오류가 나요.