본문 바로가기

코딩/PyTorch

[Introduction to PyTorch] Autograd

What Do We Need Autograd for?

Tensor가 거쳐가는 모든 계산 과정을 기록해 chain rule과 backpropagation에 사용

 

Simple Example

requires_grad=True로 설정한 tensor는 autograd를 사용

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
b = torch.sin(a)

print(b)
  • Output
    tensor([ 0.0000e+00,  2.5882e-01,  5.0000e-01,  7.0711e-01,  8.6603e-01,
             9.6593e-01,  1.0000e+00,  9.6593e-01,  8.6603e-01,  7.0711e-01,
             5.0000e-01,  2.5882e-01, -8.7423e-08, -2.5882e-01, -5.0000e-01,
            -7.0711e-01, -8.6603e-01, -9.6593e-01, -1.0000e+00, -9.6593e-01,
            -8.6603e-01, -7.0711e-01, -5.0000e-01, -2.5882e-01,  1.7485e-07],
           grad_fn=<SinBackward0>)
    
  • 이전에 사용된 함수가 sin이라는 것을 기록

 

c = 2 * b

d = c + 1

 

grad_fn 속성을 통해 계산 기록을 모두 확인할 수 있음

print('d:')
print(d.grad_fn)
print(d.grad_fn.next_functions)
print(d.grad_fn.next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions)
print(d.grad_fn.next_functions[0][0].next_functions[0][0].next_functions[0][0].next_functions)
print('\\nc:')
print(c.grad_fn)
print('\\nb:')
print(b.grad_fn)
print('\\na:')
print(a.grad_fn)
  • Output
d:
<AddBackward0 object at 0x7f784ef4ded0>
((<MulBackward0 object at 0x7f784ef4d4e0>, 0), (None, 0))
((<SinBackward0 object at 0x7f784ef4d4e0>, 0), (None, 0))
((<AccumulateGrad object at 0x7f784ef4ded0>, 0),)
()

c:
<MulBackward0 object at 0x7f784ef4d4e0>

b:
<SinBackward0 object at 0x7f784ef4d4e0>

a:
None

 

실제 계산은 tensor에 backward() 메소드를 부르고 input의 grad 속성으로 기울기를 확인

out.backward()

plt.plot(a.detach(), a.grad.detach())
# 2 cos(a) 함수

 

Autograd in Training

먼저 간단한 모델을 정의

BATCH_SIZE = 16
DIM_IN = 1000
HIDDEN_SIZE = 100
DIM_OUT = 10

class TinyModel(torch.nn.Module):

    def __init__(self):
        super(TinyModel, self).__init__()

        self.layer1 = torch.nn.Linear(DIM_IN, HIDDEN_SIZE)
        self.relu = torch.nn.ReLU()
        self.layer2 = torch.nn.Linear(HIDDEN_SIZE, DIM_OUT)

    def forward(self, x):
        x = self.layer1(x)
        x = self.relu(x)
        x = self.layer2(x)
        return x

some_input = torch.randn(BATCH_SIZE, DIM_IN, requires_grad=False)
ideal_output = torch.randn(BATCH_SIZE, DIM_OUT, requires_grad=False)

model = TinyModel()
  • requires_grad=True를 명시하지 않아도 torch.nn.Module의 subclass에서는 모두 gradient를 기록한다 가정

 

backward() 전에는 grad 값이 None

print(model.layer2.weight[0][0:10]) # just a small slice
# tensor([ 0.0920,  0.0916,  0.0121,  0.0083, -0.0055,  0.0367,  0.0221, -0.0276,
#         -0.0086,  0.0157], grad_fn=<SliceBackward0>)

print(model.layer2.weight.grad)
# None

 

backward() 후에는 grad 값이 기울기 값으로 변경됨. (단 weight 값은 변화 x)

optimizer = torch.optim.SGD(model.parameters(), lr=0.001)

prediction = model(some_input)

loss = (ideal_output - prediction).pow(2).sum()

loss.backward()
print(model.layer2.weight[0][0:10])
# tensor([ 0.0920,  0.0916,  0.0121,  0.0083, -0.0055,  0.0367,  0.0221, -0.0276,
#         -0.0086,  0.0157], grad_fn=<SliceBackward0>)
         
print(model.layer2.weight.grad[0][0:10])
# tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
#          0.1732, -5.3835])

 

실제 grad를 weight에 적용하려면 optimizer.step()을 호출

optimizer.step()
print(model.layer2.weight[0][0:10])
# tensor([ 0.0791,  0.0886,  0.0098,  0.0064, -0.0106,  0.0293,  0.0186, -0.0300,
#         -0.0088,  0.0211], grad_fn=<SliceBackward0>)

print(model.layer2.weight.grad[0][0:10])
# tensor([12.8997,  2.9572,  2.3021,  1.8887,  5.0710,  7.3192,  3.5169,  2.4319,
#          0.1732, -5.3835])

optimizer.step() 후 optimizer.zero_grad()를 호출해 grad를 0으로 초기화

  • 그러지 않으면 grad 값이 계속 축적됨

 

Turning AutoGrad Off and On

간단히 requires_grad 속성을 직접 변경 가능

a = torch.ones(2, 3, requires_grad=True)

b1 = 2 * a

a.requires_grad = False
b2 = 2 * a

 

일시적으로 grad 기록을 끄려면 torch.no_grad() 사용

a = torch.ones(2, 3, requires_grad=True) * 2
b = torch.ones(2, 3, requires_grad=True) * 3

c1 = a + b
print(c1)

with torch.no_grad():
    c2 = a + b

print(c2)

c3 = a * b
print(c3)
  • Output
tensor([[5., 5., 5.],
        [5., 5., 5.]], grad_fn=<AddBackward0>)
tensor([[5., 5., 5.],
        [5., 5., 5.]])
tensor([[6., 6., 6.],
        [6., 6., 6.]], grad_fn=<MulBackward0>)

 

 

함수나 메소드 decorator로도 사용 가능

def add_tensors1(x, y):
    return x + y

@torch.no_grad()
def add_tensors2(x, y):
    return x + y

 

 

만약 복사본을 만들 때 기록도 복사하고 싶지 않다면 detach() 메소드를 사용

x = torch.rand(5, requires_grad=True)
y = x.detach()

 

Autograd and In-place Operations

주의 점으로 in-place 작업을 requires_grad=True인 tensor에 적용하면 잘못된 결과가 나올 수 있다

a = torch.linspace(0., 2. * math.pi, steps=25, requires_grad=True)
torch.sin_(a)

# RUNTIME ERROR!

 

Autograd Profiler

Autograd가 사용되는 모든 계산 기록을 가지고 있기 때문에 각 계산이 얼마나 걸리고 얼마나 많은지 profiling 할 때 유용

# 예시

device = torch.device('cpu')
run_on_gpu = False
if torch.cuda.is_available():
    device = torch.device('cuda')
    run_on_gpu = True

x = torch.randn(2, 3, requires_grad=True)
y = torch.rand(2, 3, requires_grad=True)
z = torch.ones(2, 3, requires_grad=True)

with torch.autograd.profiler.profile(use_cuda=run_on_gpu) as prf:
    for _ in range(1000):
        z = (z / x) * y

print(prf.key_averages().table(sort_by='self_cpu_time_total'))