[인공지능사관학교: 자연어분석A반] 학습 내용 보충 - Autograd

Suhyeon Lee·2025년 7월 17일

PyTorch Gradient 관련 설명 (Autograd)

pytorch에서 미분이 어떻게 계산되는지 살펴보기

Autograd 사용 방법

  • 어떤 tensor가 학습에 필요한 tensor라면 backpropagation을 통하여 gradient를 구해야 함
    • 즉, 미분을 해야 함
  • tensor의 gradient를 구할 때에는 다음 조건들이 만족되어야 함
    1. tensor의 옵션을 requires_grad = True 로 설정 (기본 값은 requires_grad = False)
    2. backparopagation을 시작할 지점의 output은 scalar 형태
  • tensor의 gradient를 구하는 방법: backpropagation을 시작할 지점의 tensor에서 .backward() 함수를 호출
  • gradient 값을 확인: requires_grad = True로 생성한 Tensor에서 .grad를 통해 값을 확인

Autograd 살펴보기

  • 자동 미분 (Auto differentitation)을 이용하여 변화도 (Gradient) 계산

예제 1

import torch
x1 = torch.ones(2, 2)
print(x1)
# tensor([[1., 1.],
#         [1., 1.]])

x2 = torch.ones(2, 2, requires_grad = True)
print(x2)
# tensor([[1., 1.],
#         [1., 1.]], requires_grad=True)
  • torch.ones를 이용하여 단순하게 2 x 2 크기의 텐서를 생성
    • 후자는 requires_grad=True 옵션을 주고 생성
  • 후자의 경우 출력 결과에 requires_grad=True가 나타난 것을 볼 수 있는데, 이는 이후 역전파 과정을 수행 후, 해당 텐서의 변화도를 구할 수 있도록 함

예제 2

  • x1과 x2를 이용하여 추가적인 산술 연산을 하면:
import torch
x1 = torch.ones(2, 2)
print(x1)
# tensor([[1., 1.],
#         [1., 1.]])

y1 = x1 + 2
print(y1)
# tensor([[3., 3.],
#         [3., 3.]])

x2 = torch.ones(2, 2, requires_grad=True)
print(x2)
# tensor([[1., 1.],
#         [1., 1.]], requires_grad=True)
y2 = x2 + 2
print(y2)
# tensor([[3., 3.],
#         [3., 3.]], grad_fn=<AddBackward0>)
  • 각 x1과 x2에 덧셈 연산을 수행하여 y1, y2를 생성
    • 코드 결과에 연산 수행 결과와 grad_fn이 <AddBackward0>인 것을 확인할 수 있음
    • grad_fn에는 텐서가 어떤 연산을 하였는 지 연산 정보를 담고 있고, 이 정보는 역전파 과정에 사용될 예정

사칙연산

import torch
x = torch.ones(2, 2, requires_grad=True)
y1 = x + 2
print(y1)
# tensor([[3., 3.],
#         [3., 3.]], grad_fn=<AddBackward0>)

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

y3 = x * 2
print(y3)
# tensor([[2., 2.],
#         [2., 2.]], grad_fn=<MulBackward0>)

y4 = x / 2
print(y4)
# tensor([[0.5000, 0.5000],
#         [0.5000, 0.5000]], grad_fn=<DivBackward0>)
  • 각 텐서에서 나중에 이루어지는 역전파를 위해 기록되는 grad_fn에는 각각 AddBackward0, SubBackward0, MulBackward0, DivBackward0와 같이 저장되어 있는 것을 확인할 수 있음
  • 만약 requiresgrad가 없는 텐서에서 속성을 추가할 때에는 y.requires_grad(True)와 같은 방식으로 속성값을 추가
x = torch.ones(2, 2)
y = x + 2

print(x)
# tensor([[1., 1.],
#         [1., 1.]])
print(y)
# tensor([[3., 3.],
#         [3., 3.]])

y.requires_grad_(True)

print(x)
# tensor([[1., 1.],
#         [1., 1.]])
print(y)
# tensor([[3., 3.],
#         [3., 3.]], requires_grad=True)
  • 다만, 이렇게 별도로 requires_grad를 추가한 경우 앞에서 연산한 이력이 grad_fn으로 자동으로 저장되지는 않음

derivative 기본 예제

  • 다음 식을 x에 대하여 미분한다고 가정:

    f(x)=9x4+2x3+3x2+6x+1f(x)=9x^4+2x^3+3x^2+6x+1
  • 이 때 도함수 df(x)/dxdf(x)/dx를 구하면 x=ax=a 지점의 경사를 구할 수 있음

    df(x)dx=d9x4+2x3+3x2+6x+1dx=36x3+6x2+6x+6\frac{df(x)}{dx}=\frac{d9x^4+2x^3+3x^2+6x+1}{dx}=36x^3+6x^2+6x+6
    • 만약 x=2x=2라고 하면 x=2x=2의 변화율은 36×23+6×22+6×2+6=33036×2^3+6×2^2+6×2+6=330
x = torch.tensor(2.0, requires_grad=True)
y = 9*x**4 + 2*x**3 + 3*x**2 + 6*x + 1
y.backward()
print(x.grad)
# tensor(330.)
  • output인 y가 scalar 라는 점을 유심히 볼 것
    • output이 scalar이어야 backward 연산 가능
    • 만약 backward를 scalar가 아닌 매트릭스에서 진행하려면 y.backward(z)처럼 z를 tensor.backward()의 인자로 넣어주면 연산이 가능해짐
  • input인 x가 scalar가 아니라 (2, 2) shape matrix 형태로 사용:
    f(X)=3(Xij+2)2=14i=01j=013(Xij+2)2f(X) = \overline{3(X_{ij}+2)^{2}} = \frac{1}{4}\sum_{i=0}^{1}\sum_{j=0}^{1}3(X_{ij}+2)^{2}
    df(X)dX=14i=01j=01(6Xij+12)=i=01j=01(1.5Xij+3)=1.5(X00+X01+X10+X11)+34\frac{df(X)}{dX}=\frac{1}{4}\sum_{i=0}^{1}\sum_{j=0}^{1}(6X_{ij}+12)=\sum_{i=0}^{1}\sum_{j=0}^{1}(1.5X_{ij}+3) = 1.5(X_{00}+X_{01}+X_{10}+X_{11})+3·4
  • matrix에서 각 원소의 값에 대하여 미분을 하면 다른 원소는 영향을 주지 않음
    df(X)dX=[1.5(X00)+31.5(X01)+31.5(X10)+31.5(X11)+3]\frac{df(X)}{dX}=\begin{bmatrix}1.5(X_{00})+3&1.5(X_{01})+3\\1.5(X_{10})+3&1.5(X_{11})+3\\ \end{bmatrix}
    • matrix X가 X=[1.02.03.04.0]X=\begin{bmatrix}1.0&2.0\\3.0&4.0\\ \end{bmatrix}일 때: (X=[1.0,2.0;3.0,4.0]이라고도 쓰는 듯)
      df(X)dX=[1.5(X00)+31.5(X01)+31.5(X10)+31.5(X11)+3]=[4.56.07.59.0]\frac{df(X)}{dX}=\begin{bmatrix}1.5(X_{00})+3&1.5(X_{01})+3\\1.5(X_{10})+3&1.5(X_{11})+3\\ \end{bmatrix}=\begin{bmatrix}4.5&6.0\\7.5&9.0\\ \end{bmatrix}
x = torch.tensor([[1.0, 2.0],[3.0, 4.0]], requires_grad = True)
# tensor([[1., 2.],
#         [3., 4.]], requires_grad=True)

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

z = y * y * 3
# tensor([[ 27.,  48.],
#         [ 75., 108.]], grad_fn=<MulBackward0>)

out = z.mean()
# tensor(64.5000, grad_fn=<MeanBackward0>)

out.backward()
print(x.grad)
# tensor([[4.5000, 6.0000],
#         [7.5000, 9.0000]])
  • 실제 값이 할당된 텐서인 x의 경우 backward를 통해 계산된 grad가 저장된 것을 볼 수 있음
x = torch.randn(2, 2, requires_grad=True)
y = x + 2
z = (y * y)
y.backward(z)

print(x.grad)
# tensor([[2.2796, 3.2123],
#         [5.1224, 0.6321]])

print(y.grad)
# None

print(z.grad)
# None
  • 반면 계산 중간 과정인 y, z의 경우 실제 grad가 저장되진 않고 backward 연산에만 참여된 것으로 볼 수 있음
    • 그 결과 y.grad, z.grad는 None으로 출력됨

partial derivative 기본 예제

  • y=x2+z3y=x^2 + z^3이 있다고 가정할 때:
    • 변수 x에 대하여 편미분
      x(x2+z3)=2x\frac{\partial }{\partial x}(x^2 + z^3) = 2x
    • 변수 z에 대하여 편미분
      z(x2+z3)=3z2\frac{\partial }{\partial z}(x^2 + z^3) = 3z^2
  • f(x,z)=f(1,2)f'(x,z)=f'(1,2) 연산
    • 먼저, x에 관하여 연산
      (f(x)=x2)xwhere x=1\frac{\partial (f(x)=x^2)}{\partial x}\quad where\ x=1
      y(1)=2y'(1) = 2
    • 다음으로 z에 관하여 연산
      (f(z)=z3)xwhere z=2\frac{\partial (f(z)=z^3)}{\partial x}\quad where\ z=2
      y(2)=12y'(2) = 12
x = torch.tensor(1.0, requires_grad=True)
z = torch.tensor(2.0, requires_grad=True)
y = x**2 + z**3
y.backward()
print(x.grad, z.grad)
## tensor(2.) tensor(12.)

학습 모드와 평가 모드

  • 앞에서 다룬 모든 예제는 gradient를 구하기 위해서 tensor의 속성을 requires_grad = True로 설정
    • gradient를 구한다는 것은 학습 대상이 되는 weight
  • 반면 학습이 모두 끝나고 학습한 결과를 실행에 옮기는 inference 단계에서는 굳이 학습 모드로 사용할 필요가 없음 → torch.no_grad() 사용
    • torch.no_grad()가 적용된 tensor는 비록 실제 속성은 requires_grad = True 이더라도 gradient를 업데이트 하지 않고, dropout, batchnormalization 등이 적용되지 않음
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
  • with torch.no_grad():에서 requires_grad = True인 tensor가 어떤 연산이 적용될 때, requires_grad = False로 변경된 것을 확인할 수 있음
    • with 문을 벗어나면 다시 requires_grad = True로 원복된 것을 확인 가능
  • 이러한 방식으로 gradient를 업데이트 하는 모드와 그렇지 않은 모드를 구분
profile
2 B R 0 2 B

0개의 댓글