Link

실습: PyTorch AutoGrad 소개

딥러닝은 대부분 경사하강법gradient descent을 통해 학습되므로, 미분이 필수입니다. 따라서 요새 나오는 딥러닝 프레임워크들은 모두 자동 미분 기능을 제공합니다. 이런 자동 미분 기능이 없던 시절에는 일일히 손으로 미분 수식을 계산한 후, C++로 구현해야 했습니다. 다행히 Theano 부터 자동 미분 기능이 제공되기 시작하였고, 이제는 당연한 것처럼 느껴집니다.

파이토치에서도 AutoGrad라는 자동 미분 기능을 제공합니다. 이를 위해서 파이토치는 requires_grad 속성이 True인 텐서의 연산을 추적하기 위한 계산 그래프computation graph를 구축하고, backward 함수가 호출되면 이 그래프를 따라서 미분을 자동으로 수행하고 계산된 그래디언트를 채워놓습니다.

다음 코드와 같이 우리는 텐서의 requires_grad 속성을 True로 만들 수 있습니다. 해당 속성의 디폴트 값은 False 입니다.

>>> x = torch.FloatTensor([[1, 2],
...                        [3, 4]]).requires_grad_(True)

이렇게 requires_grad 속성이 True인 텐서가 있을 때, 이 텐서가 들어간 연산의 결과가 담긴 텐서도 자동으로 requires_grad 속성 값을 True로 갖게 됩니다. 그럼 다음 코드와 같이 여러가지 연산을 수행하였을 때, 결과 텐서들은 모두 requires_grad 속성 값을 True로 갖게 됩니다.

>>> x1 = x + 2
>>> print(x1)
tensor([[3., 4.],
        [5., 6.]], grad_fn=<AddBackward0>)

>>> x2 = x - 2
>>> print(x2)
tensor([[-1.,  0.],
        [ 1.,  2.]], grad_fn=<SubBackward0>)

>>> x3 = x1 * x2
>>> print(x3)
tensor([[-3.,  0.],
        [ 5., 12.]], grad_fn=<MulBackward0>)

>>> y = x3.sum()
>>> print(y)
tensor(14., grad_fn=<SumBackward0>)

앞서 코드의 실행 결과에서 눈여겨 보아야 할 점은, 생성된 결과 텐서들이 모두 grad_fn 속성을 갖는다는 점입니다. 예를 들어 텐서 x1이 덧셈 연산의 결과물이기 때문에, x1의 grad_fn 속성은 AddBackward0 임을 볼 수 있습니다. 텐서 y는 sum 함수를 썼으므로 스칼라scalar 값이 되었습니다. 그럼 여기세 다음과 같이 backward 함수를 호출합니다.

y.backward()

그럼 앞서 x, x1, x2, x3, y 모두 grad 속성에 그래디언트 값이 계산되어 저장되었을 것입니다. 이것을 수식으로 나타내면 다음과 같습니다.

\[\begin{gathered} x=\begin{bmatrix} x_{(1,1)} & x_{(1,2)} \\ x_{(2,1)} & x_{(2,2)} \end{bmatrix} \\ \\ x_1=x+2 \\ x_2=x-2 \\ \\ \begin{aligned} x_3&=x_1\times{x_2} \\ &=(x+2)(x-2) \\ &=x^2-4 \end{aligned} \\ \\ \begin{aligned} y&=\text{sum}(x_3) \\ &=x_{3,(1,1)}+x_{3,(1,2)}+x_{3,(2,1)}+x_{3,(2,2)} \end{aligned} \end{gathered}\]

이렇게 구한 y를 다시 x로 미분하면 다음과 같습니다.

\[\begin{gathered} \text{x.grad}=\begin{bmatrix} \frac{\partial{y}}{\partial{x_{(1,1)}}} & \frac{\partial{y}}{\partial{x_{(1,2)}}} \\ \frac{\partial{y}}{\partial{x_{(2,1)}}} & \frac{\partial{y}}{\partial{x_{(2,2)}}} \end{bmatrix} \\ \\ \frac{dy}{dx}=2x \end{gathered}\]

그럼 실제 파이토치로 미분한 값과 같은지 비교해보도록 하겠습니다.

>>> print(x.grad)
tensor([[2., 4.],
        [6., 8.]])

참고로 혹시 이 연산에 사용된 텐서들을 배열과 같은 곳에 저장할 경우 메모리 누수의 원인이 되기 때문에 주의해야합니다. 파이썬은 가비지 컬렉터garbage collector가 자동으로 메모리 관리를 수행하게 되는데, 예를 들어 만약 x3를 배열 등에 저장하고 놓지 않을 경우, x3 뿐만 아니라 계산 그래프에 등록된 텐서들 모두 메모리에서 해제되지 않기 때문입니다. 따라서 이 경우에는 detach 함수를 통해 그래프로부터 떼어낼 수 있습니다.

>>> x3.detach_()