Python

Pytorch의 tensor GPU 연산하기(Autograd 이해하기)

qlsenddl 2020. 11. 7. 16:54
728x90

 PyTorch는 Deep Learning Framework 중 하나로 주로 research 분야에서 많이 사용되는 것으로 알려져있다. PyTorch에서는 tensor를 사용하는데 이 tensor는 PyTorch의 기본적인 data structure라고 생각하면 된다.

 tensor는 numpy와 비슷하게 n 차원 배열을 다룬다. 하지만 Deep Learning의 특성 상 계산량이 많아 보통 GPU에서 계산해야 하는데, numpy와 다르게 PyTorch의 tensor는 GPU에서 계산할 수 있기 때문에 Deep Learning Model을 사용할 때, 이 tensor를 사용한다.


 여기에서는 PyTorch의 tensor를 GPU에 올려서 계산하고 Deep Learning Model을 만들기 위해 사용되는 기본적인 GPU 연산들(back propagation 등)을 알아본다.


1. PyTorch tensor를 GPU에 올리기

device: 해당 tensor가 CPU, GPU 중 어디에 있는지 확인할 때 사용한다.

ex1> CPU에 있는 경우

print(a.device)

>> cpu

print(a)

>> tensor([1, 2, 3, 4])


ex2> GPU에 있는 경우

print(a.device)

>> cuda:0

print(a)

>> tensor([1, 2, 3, 4], device='cuda:0')

-> GPU에 있는 경우 그냥 tensor를 출력해도 cuda에 올라가 있는 것이 표시


cuda.is_available(): GPU(cuda)를 사용할 수 있는지 확인할 때 사용한다.

ex>

print(torch.cuda.is_available())

>> True


to('cuda'): tensor를 GPU에 올릴 때 사용한다.

to('cpu'): tensor를 다시 CPU에서 계산할 때 사용한다.

ex>

a = torch.tensor([1, 2, 3, 4])

print(a.device)

>> cpu

a = a.to('cuda')

print(a.device)

>> cuda:0

a = a.to('cpu') print(a.device)

>> cpu


-> 위 내용들을 포함해서 보통 다음과 같은 문법으로 tensor를 GPU에 올리는 작업을 한다.

ex1>

a = torch.tensor([1, 2, 3, 4])

if torch.cuda.is_available():

    device = 'cuda'

else:

    device = 'cpu'

a = a.to(device)


ex2>

a = torch.tensor([1, 2, 3, 4])

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

a = a.to(device)


2. Autograd

Autograd는 tensor에 대한 미분 계산을 도와주는 pytorch 패키지다. AI model을 학습할 때 parameter에 대한 gradient가 필요한데, 그 gradient를 계산할 때 주로 사용되므로 AI 학습에 핵심적인 패키지 중 하나라고 할 수 있다.


requires_grad: True로 설정 시 모든 연산들을 기억(track all operation) <참고: tensor 내 element는 int가 아닌 float 자료형이어야>

retain_grad(): 해당 변수에 대한 gradient 연산을 기억 -> 이것을 하지 않으면 중간 변수에 대한 gradient는 저장 안됨

backward(): 기억한 연산들에 대해서 gradient 계산 -> grad attribute(속성)에 gradient 정보 축적

detach(): 계산 정보를 더 이상 기억하지 않음(stop tracking history)

ex>

 다음과 같은 연산이 있다고 하자.



 이 연산에 대해서 z에 대한 미분 연산을 하면 다음과 같다.



 이 내용을 Python code 결과로 확인하면 다음과 같다.


--- Python Code ---

w = torch.tensor([[1., 2.], [3., 2.]], requires_grad=True)

print(w)

>> tensor([[1., 2.],

[3., 2.]], requires_grad=True)

-> tensor에 requires_grad가 True인 것이 표시되는 것 확인

x = w**3

print(x)

>> tensor([[ 1., 8.],

              [27.,  8.]], grad_fn=<PowBackward0>)

y = x + 3

print(y)

>> tensor([[ 4., 11.],

              [30., 11.]], grad_fn=<AddBackward0>)

z = y.mean()

print(z)

>> tensor(14., grad_fn=<MeanBackward0>)


x.retain_grad() y.retain_grad()


z.backward() print(y.grad)

>> tensor([[0.2500, 0.2500],

              [0.2500, 0.2500]])

print(x.grad)

>> tensor([[0.2500, 0.2500],

              [0.2500, 0.2500]])

print(w.grad)

>> tensor([[0.7500, 3.0000],

              [6.7500, 3.0000]])

-> x.retain_grad(), y.retain_grad()를 하지 않으면 x.grad, y.grad를 하는 경우 다음과 같은 에러 메시지가 발생한다.

RuntimeError: Trying to backward through the graph a second time, but the buffers have already been freed. Specify retain_graph=True when calling backward the first time.


torch.no_grad(): torch.no_grad()를 사용하면 연산을 하는 동안 그 history를 기억하지 않는다. 그러므로 memory를 덜 사용한다. 때문에 gradient 계산이 필요없는 경우 즉, training이 아닌 inference를 하는 경우 사용한다. 더 적은 memory를 사용하기 때문에 더 빠른 계산이 가능해진다.

ex>

with torch.no_grad():

    w = torch.tensor([[1., 2.], [3., 2.]], requires_grad=True)

    x = w**3

    y = x + 3

    z = y.mean()

print(z)

>> tensor(14.)

-> 이전 예시와 다르게 grad_fn에 대한 정보가 없는 것 확인

z.backward()

>> RuntimeError: element 0 of tensors does not require grad and does not have a grad_fn

728x90