Updated:

PyTorch Documentation: torch.autograd provides classes and functions implementing automatic differentiation of arbitrary scalar valued functions.

import torch

1. torch.autograd

torch.autograd는 PyTorch에서 제공해주는 자동 미분 연산 패키지이다.

딥러닝의 학습 과정은 cost function을 최소화 하는 parameter를 수정해나가는 과정이라고 할 수 있겠다. parameter를 수정을 하기 위해서는 cost function을 parameter로 미분한 gradient value값이 필요한데 이 gradient value를 자동 계산해주는 것이 torch.autograd 패키지이다. 따라서 torch.autograd는 딥러닝 학습에 있어 매우 필요하고 중요한 패키지라고 볼 수 있다.

torch.autograd는 ‘자동’ 미분 연산 패키지이기 때문에 사용하는 방법도 아주 간단하다.

2. requires_grad=True

gradient value 계산이 필요한 Tensor를 생성할 때 requires_grad=True를 추가하면 된다. requires_grad=True는 ‘미분 계산시 이 Tensor를 이용해서 한다’ 라는 의미이다. 따라서 위 keyword를 추가한다고해서 곧바로 계산이 이루어지는 것이 아니다. 실제적인 계산은 미분의 대상이 되는 function에 backward() method 수행할 때 진행된다.

그렇다면 계산된 미분 값은 어디에 담길까? ‘01.Tensor와 연산’에서 잠깐 소개한 적이 있는데 Tensor.grad에 미분된 계산 값이 담긴다. Tensor.grad는 처음에는 None값이었다가 backward() 메소드로 계산이 수행되면 계산된 gradient 값이 담긴 Tensor가 된다. 이후 Tensor.grad에 대해 별다른 초기화 과정이 없다면 backward()연산 수행시 gradient value가 Tensor.grad에 대해 누적된다.

autograd는 float형 Tensor에만 적용이 되기 때문에 int형 tensor의 경우 float으로 형변환이 필요하다.

t = torch.tensor([[1,2],[3,4]], dtype=torch.float,requires_grad=True)

t
tensor([[1., 2.],
        [3., 4.]], requires_grad=True)

생성시 선언하지 않아도 requires_grad 속성을 이용하여 True로 만들어줄 수 있다.

requires_grad_() method로도 해당 Tensor를 requires_grad=True로 만들어 줄 수 있다.

t = torch.FloatTensor([1.0,2.0])
t.requires_grad = True

t
tensor([1., 2.], requires_grad=True)
t = torch.FloatTensor([1.0,2.0])
t.requires_grad_()

t
tensor([1., 2.], requires_grad=True)

3. backward()

현재 Tensor에 미분을 수행하는 method이다.

backward()를 수행하려면 해당 Tensor가 requires_grad=True인 Tensor로 이루어져있어야한다.

backward() 연산시 Tensor.grad를 초기화(0 또는 None)할 필요가 있다.

backward()는 Tensor.grad에 누적으로 값을 더하기 때문에 만약 Tensor.grad에 값이 담겨있을 경우 이 값에 gradient 값이 더해져 원하려던 값이 나오지 않을 수도 있다.

3.1. 다항함수 미분

x = torch.tensor([4.0],requires_grad=True) # 이 tensor를 이용해 미분 수행
print(f"x.grad: {x.grad}")

y = x**2
y.backward() # y tensor에 대해 x tensor로 미분 수행

print(f"x.grad:{x.grad}")
x.grad: None
x.grad:tensor([8.])

위 예제는 $y = x^2$을 $x$로 미분한 값을 찾는 예제이다. $\frac{dy}{dx}=2x$ 이고 $x=4$ 이므로 x.grad에는 8이 담기게 된다.

x.grad를 보면 알 수 있겠지만 backward()메소드를 실행하기 전까지는 None이 담긴 것을 알 수 있다. 그 이유는 앞서도 언급했지만 backward() 실행시 실제적인 미분 연산이 일어나기 때문이다.

y에 대해 backward()를 수행하기 때문에 미분의 대상은 y가 되고 y에서 requires_grad=True로 구성된 Tensor는 x밖에 없기 때문에 x로 미분하게 된다. 그래서 미분한 값이 x.grad에 담겨진다.

3.2. 편미분

x = torch.tensor([4.0],requires_grad=True) # 이 tensor를 이용해 미분 수행
y = torch.tensor([2.0],requires_grad=True) # 이 tensor를 이용해 미분 수행

z = x**2 + y**2
z.backward() # y tensor에 대해 x tensor로 미분 수행

print(f"x.grad:{x.grad}")
print(f"y.grad:{y.grad}")
x.grad:tensor([8.])
y.grad:tensor([4.])

$z = x^2 + y^2$를 미분하는 예제이다.

zrequires_grad=True 속성을 가진 Tensor xy로 이루어져있기 때문에 z에 대해 xy로 편미분이 수행된다.

$\frac{\partial{z}}{\partial{x}}=2x$, $\frac{\partial{z}}{\partial{y}}=2y$ 이기 때문에 x.grad에 8이 담기고, y.grad에 4가 담기게 된다.

3.3 합성 함수 미분

autograd에서 합성 함수 미분도 수행할 수 있다. chain rule이 적용된다.

x = torch.tensor([4.0],requires_grad=True)

y = 2*(x**2)

z = y**2

z.backward()

print(f"x.grad:{x.grad}")
print(f"y.grad:{y.grad}")
x.grad:tensor([1024.])
y.grad:None

$z=y^2$이지만 $y = 2x^2$이기 때문에 zx에 관한 함수로도 볼 수 있다.

따라서 z.backward() 실행시 $\frac{dz}{dx}$이 수행된다.

z는 합성함수이기 때문에 chain-rule이 적용된다.

$ \frac{dz}{dx} = \frac{dz}{dy}\frac{dy}{dx}$

$\frac{dz}{dy} = 2y, \, \frac{dy}{dx} = 4x$

$\frac{dz}{dx} = 2y \times 4x$

$\frac{dz}{dx} = 2 \times 32 \times 4 \times 4 = 2^{10} = 1024$

최종적으로 x.grad에 1024가 담기게된다.

yrequires_grad=True가 아니기 때문에 실질적으로 미분하는 것이 아니어서 z.backward()를 하였어도 y.grad에는 None이 그대로 유지된다.

Leave a comment