본문 바로가기
Python/PyTorch

[PyTorch] CUDA와 자동미분

by _sweep 2022. 2. 23.

이수안컴퓨터연구소파이토치(PyTorch) 기초 영상을 보고 정리한 내용입니다.

 

 

CUDA Tensor

CUDA(Computed Unified Device Architecture)는 NVIDIA의 GPU 개발 툴이다.
파이토치는 .to() 메서드를 통해 텐서를 cpu 또는 gpu 등 어떠한 장치로도 옮길 수 있다.

 

import torch

x = torch.randn(1)
print(x)
print(x.item())
print(x.dtype)

# output
# tensor([-0.3513])
# -0.35131844878196716
# torch.float32

 

스칼라 값을 가지는 텐서를 하나 생성한 뒤 텐서를 살펴보면 위와 같다.

-0.3513... 이라는 값을 가지고 있고 데이터 타입은 float32이다.

 

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

y = torch.ones_like(x, device=device)
x = x.to(device)
z = x + y

print(device)
print(z)
print(z.to('cpu', torch.double))

# output
# cuda
# tensor([0.6487], device='cuda:0')
# tensor([0.6487], dtype=torch.float64)

 

torch.device()를 통해 cuda가 사용가능할 경우 환경을 cuda로 설정한다.

device는 .to() 메서드의 인자로 전해질 수도 있지만 텐서 선언 시 device 옵션에 전해지기도 한다.

 

cuda에서 x와 y를 만들고 이를 더한 z라는 텐서를 생성했을 때 z를 출력하면 device가 cuda로 되어있는 것을 확인할 수 있다.

또 z를 .to() 메서드를 통해 다시 cpu로 가져오면 device는 사라지고 z가 [0.6487]의 값을 가지고 데이터타입이 float64인 텐서임을 확인할 수 있다.

 

 

AUTOGRAD

autograd 패키지는 텐서의 모든 연산에 대해 자동 미분 기능을 제공한다.

이는 코드를 어떻게 작성하여 실행하느냐에 따라 역전파가 정의된다는 뜻이다.

backprop을 위한 미분값을 자동으로 계산하기 위해 autograd 패키지를 사용한다.

 

data는 tensor 형태의 데이터를 의미하며 grad는 data가 거쳐온 layer에 대한 정보가, grad_fn은 미분 값을 계산한 함수에 대한 정보가 저장된다.

 

requires_grad 속성을 True로 설정하면 해당 텐서에서 이루어지는 모든 연산들을 추적하기 시작하며 연산에 따라 grad_fn에 연산 관련 함수 정보가 담긴다.

계산이 완료된 후 .backward()를 호출하면 자동으로 gradient를 계산할 수 있으며 이는 .grad 속성에 누적된다.

 

기록 추적을 중단하려면 .detach()를 호출하여 연산기록으로부터 분리시킨다.

기록 추적을 방지하려면 해당 코드 블럭을 with torch.no_grad(): 로 감싸면 된다.

이는 gradient는 필요없지만 requires_grad=True로 설정되어 학습 가능한 매개변수를 갖는 모델을 평가할 때 유용하다.

 

✔️ 연산 기록 추적

x = torch.ones(3, 3, requires_grad=True)
print(x)

# output
# tensor([[1., 1., 1.],
#         [1., 1., 1.],
#         [1., 1., 1.]], requires_grad=True)

 

3x3의 shape을 모두 1로 채운 텐서 x를 생성했다.

이때 requires_grad 속성을 True로 설정하여 x라는 텐서에서 이루어지는 모든 연산을 추적하도록 했다.

 

y = x + 5
print(y)
print(y.grad_fn)

# output
# tensor([[6., 6., 6.],
#         [6., 6., 6.],
#         [6., 6., 6.]], grad_fn=<AddBackward0>)
# <AddBackward0 object at 0x7fd8f9ae3e50>

 

x에 5를 더한 y라는 텐서를 생성했을 때 y를 출력한 결과는 위와 같다.

추적의 결과로 grad_fn에 AddBackward가 들어있는 것을 확인할 수 있다.

이는 이 텐서에서 일어난 연산이 Add임을 나타낸다.

 

z = y * y * 2
out = z.mean()

print(z)
print(out)

# output
# tensor([[72., 72., 72.],
#         [72., 72., 72.],
#         [72., 72., 72.]], grad_fn=<MulBackward0>)
# tensor(72., grad_fn=<MeanBackward0>)

 

이외에도 mul 연산을 진행했을 때, mean 연산을 진행했을 때 모두 출력해보면 grad_fn에 연산에 관련된 함수가 담기는 것을 확인할 수 있다.

 

✔️ gradient 계산

out.backward()
print(x.grad)

# output
# tensor([[2.6667, 2.6667, 2.6667],
#         [2.6667, 2.6667, 2.6667],
#         [2.6667, 2.6667, 2.6667]])

 

연산이 종료된 후 .backward()를 호출해 gradient를 계산한다.

계산된 gradient는 .grad 속성에 담기게 되므로 이를 출력해보면 위와 같다.

 

✔️ 기록 추적 방지

print(x.requires_grad)
print((x **2).requires_grad)

with torch.no_grad():
  print((x**2).requires_grad)

# output
# True
# True
# False

 

with torch.no_grad(): 로 감싸 기록 추적을 방지했다.

감싸지 않은 부분은 True가 출력되는데 반해 감싸진 부분은 False가 출력되는 것을 확인할 수 있다.

 

✔️ 기록 추적 분리

print(x.requires_grad)
y = x.detach()
print(y.requires_grad)
print(x.eq(y).all())

# output
# True
# False
# tensor(True)

 

.detach()를 이용해 텐서 x와 내용물(content)은 같지만 requires_grad 속성만 다른 새로운 텐서 y를 생성했다.

이때 x와 y의 requires_grad는 각각 True와 False로 다른 값을 가지고 있지만 x.eq(y)로 값을 비교해보면 True의 결과가 나오는 것을 확인할 수 있다.

 

 

자동미분 계산 흐름

자동 미분을 진행하는 흐름을 정리하자면 다음과 같다.

계산 흐름이 \(a\rightarrow b \rightarrow c \rightarrow out\) 과 같다고 할 때 우리가 구해야 할 값은 \(\frac{\partial out}{\partial a}\) 이다.

이 값을 구하기 위해 .backward()를 통해 \(a\leftarrow b \leftarrow c \leftarrow out\) 의 값을 계산하면서 계산된 값을 a.grad에 채워나가면 \(\frac{\partial out}{\partial a}\) 의 값을 얻을 수 있다.

 

a = torch.ones(2, 2, requires_grad=True)
print("a.data: ", a.data)
print("a.grad: ", a.grad)
print("a.grad_fn: ", a.grad_fn)

# output
# a.data:  tensor([[1., 1.],
#         [1., 1.]])
# a.grad:  None
# a.grad_fn:  None

b = a + 2
print(b)

# output
# tensor([[3., 3.],
#         [3., 3.]], grad_fn=<AddBackward0>)

c = b ** 2
print(c)

# output
# tensor([[9., 9.],
#         [9., 9.]], grad_fn=<PowBackward0>)

out = c.sum()
print(out)

# output
# tensor(36., grad_fn=<SumBackward0>)

out.backward()

 

위의 연산을 진행한 후 a, b, c, out에 대해 data, grad, grad_fn을 출력해보면 다음의 결과를 얻을 수 있다.

 

print("a.data: ", a.data)
print("a.grad: ", a.grad)
print("a.grad_fn: ", a.grad_fn)

print("b.data: ", b.data)
print("b.grad: ", b.grad)
print("b.grad_fn: ", b.grad_fn)

print("c.data: ", c.data)
print("c.grad: ", c.grad)
print("c.grad_fn: ", c.grad_fn)

print("out.data: ", out.data)
print("out.grad: ", out.grad)
print("out.grad_fn: ", out.grad_fn)

# output
# a.data:  tensor([[1., 1.],
#         [1., 1.]])
# a.grad:  tensor([[6., 6.],
#         [6., 6.]])
# a.grad_fn:  None

# b.data:  tensor([[3., 3.],
#         [3., 3.]])
# b.grad:  None
# b.grad_fn:  <AddBackward0 object at 0x7fd8f55e12d0>

# c.data:  tensor([[9., 9.],
#         [9., 9.]])
# c.grad:  None
# c.grad_fn:  <PowBackward0 object at 0x7fd8f5e42250>

# out.data:  tensor(36.)
# out.grad:  None
# out.grad_fn:  <SumBackward0 object at 0x7fd8f63b8b50>




🔍 참조

autograd https://tutorials.pytorch.kr/beginner/basics/autogradqs_tutorial.html

 

 

 

 

'Python > PyTorch' 카테고리의 다른 글

[PyTorch] Torchvision과 utils.data  (0) 2022.02.23
[PyTorch] nn과 nn.functional  (0) 2022.02.23
[PyTorch] 텐서의 연산과 조작  (0) 2022.02.22
[PyTorch] 파이토치와 텐서  (0) 2022.02.22

댓글