PyTorch autograd (자동미분)
https://tutorials.pytorch.kr/beginner/blitz/autograd_tutorial.html#computational-graph
torch.autograd 에 대한 간단한 소개
torch.autograd 는 신경망 학습을 지원하는 PyTorch의 자동 미분 엔진입니다. 이 단원에서는 autograd가 신경망 학습을 어떻게 돕는지에 대한 개념적 이해를 할 수 있습니다. 배경(Background): 신경망(NN; Neur
tutorials.pytorch.kr
autograd (자동미분)이란?
torch.autograd는 신경망 학습을 지원하는 pyTorch의 자동 미분 엔진이다.
NN(Neural Network)의 학습방법
순전파(Forward Propagation): 순전파 단계에서 신경망은 함수들을 통해 가지고 있는 가중치와 입력데이터를 활용하여 정답값을 추측한다
역전파(Backward Propagation): 역전파 단계에서 신경망은 추측한 값에서 발생한 오류에 비례하여 매개변수들을 적절히 조절한다. 출력으로부터 역방향으로 이동하면서 오류에 대한 함수의 매개변수의 미분값을 수집하고 경사하강법을 사용하여 최적화한다.
ex. PyTorch에서의 NN학습
import torch, torchvision
# 모델 설정, 입력 데이터 정의
model = torchvision.models.resnet18(pretrained=True)
data = torch.rand(1, 3, 64, 64)
labels = torch.rand(1, 1000)
# 순전파 단계(forward pass)
prediction = model(data)
# 손실함수를 통해 loss값 정의
loss = (prediction - labels).sum()
# 역전파 단계(backward pass)
loss.backward()
# 역전파 단계에서 Autograd가 매개변수(parameter)의 .grad 속성(attribute)에,
# 모델의 각 매개변수에 대한 변화도(gradient)를 계산하고 저장한다
# optimizer 정의
optim = torch.optim.SGD(model.parameters(), lr=1e-2, momentum=0.9)
# optimizer를 통해 경사하강법(gradient descent)으로 파라미터 조정
optim.step()
Autograd에서의 미분
$$Q = 3a^3 - b^2$$
$$\frac{\partial Q}{\partial a} = 9a^2, \frac{\partial Q}{\partial b} = -2b$$
autograd를 통해 위의 미분을 계산하면,
import torch
# requires_grad = True => autograd에게 이 텐서의 모든 연산을 추적해야 한다고 알려줌
a = torch.tensor([2., 3.], requires_grad=True)
b = torch.tensor([6., 4.], requires_grad=True)
Q = 3*a**3 - b**2
# external_grad
external_grad = torch.tensor([1., 1.])
Q.backward(gradient=external_grad)
# 수학적 미분값과 autograd를 통한 미분값이 같은지 확인
print(9*a**2 == a.grad)
print(-2*b == b.grad)
#tensor([True, True])
#tensor([True, True])
$\frac{\partial Q}{\partial Q} = 1$이므로 계산 차원에 맞추어 external_grad에 [1,1] Tensor를 넣어주었다
- autograd 벡터 미적분의 수학적 해석
연산 그래프에서의 Autograd에서의 미분
- c = a*b
requires_grad = True인 Tensor a와 requires_grad = False인 Tensor b를 torch.Mul()연산하여 Tesnor C를 만들었다.
Tesnor C의 계산 요소중 하나인 Tensor a가 requires_grad = True이므로, Tesnor C도 자동으로 requires_grad = True가 된다.
Tensor A와 Tensor B의 torch.Mul()연산을 하면서 ctx.save_for_backward에는 추적해야하는 Tensor의 정보가 포함되고, C.grad_fn에는 torch.Mul()연산을 하였다는 MulBackward가 들어간다
ctx는 c.backward()가 호출된 시점에 MulBackward(torch.Mul()의 autograd Backward연산)에서 chainrule에 사용된다.
(grad_fn = 각 변수를 생성한 함수)
C.grad_fn의 next_function = {(Accumulate ,0),(None,0)}인 이유는 Tensor a는 추적되지만, Tensor b는 추적을 필요로 하지 않기 때문이다.
이러한 과정을 통해 a.grad(loss 값에 대한 a의 편미분)은 3이 된다
- e = c *d, c = a*b
e에서 c와 d로 가는 autograd연산에서 Mulbackward를 수행한 후에 나온 c의 편미분값은 c가 leaf_node가 아니므로 기본적으로는 c.grad에 값이 저장되는 것이 아니라 바로 c를 이루는 Mulbackward연산의 input 값으로 들어간다.
하지만 c.retian_grad()를 해주면, c.grad에 c의 편미분값을 저장할 수 있다
평가 모드에서의 Autograd & detach()
학습 모드에서는 가중치 조정이 필요하므로 requires_grad = True 가 유용하나, inference 단계에서는 각 텐서에대해 추적할 필요가 없다.
그러므로 torch.no_grad()를 사용하면 각 Tensor가 freeze되어 가중치 추적 및 업데이트가 이루어지지 않는다.
x = torch.tensor(1.0, requires_grad = True)
print(x.requires_grad)
# True
with torch.no_grad():
print(x.requires_grad)
print((x**2).requires_grad)
# True
# False
print(x.requires_grad)
# True
detach(): Tensor를 연산 기록으로부터 분리하여 Tensor 을 추적하는 것을 방지한다
with torch.no_grad()로 grad를 사용하지 않는 방법과 detach()로 하는 방법이 차이가 있나?
X. 단 torch.no_grad()는 범위 내의 requires_grad를 모두 False로 만든다.
modelA = nn.Linear(10, 10)
modelB = nn.Linear(10, 10)
x = torch.randn(1, 10)
a = modelA(x)
b = modelB(a.detach())
b.mean().backward()
print(modelA.weight.grad) # A는 detach()로 추적을 방지하였으므로 None이 나옴
print(modelB.weight.grad) # B는 추척되므로 값이 나옴