Now Loading ...
-
1. PyTorch_Basics
파이토치의 기초 문법과 함수 사용법을 다룬 포스트이다. 계속해서 내용을 업데이트할 예정.
1. Tensor
1.1. Tensor가 무엇인가?
인공지능 분야에서 대부분의 데이터는 벡터(Vector) 로 표현된다. 단어, 이미지, 혹은 특정 측정값에 대한 정보를 나타낼 때, 여러 수치를 나열한 데이터 형태를 사용하는데, 예를 들어 [0.5, 1.2, 1.7]과 같이 1차원 배열 형태가 이에 해당한다.
이러한 벡터가 여러 개 모이면, 같은 형태가 새로운 방향으로 쌓이게 된다. 즉, 기존 벡터의 수치들이 나열된 방향(가로)에 수직한 방향(세로)으로 데이터가 늘어나면서 행렬(Matrix) 이 만들어진다.
그런데 항상 모든 데이터를 동일한 방식으로 표현할 수 있는 것은 아니다. 예를 들어 사진 한 장을 수치들의 집합으로 표현한다고 하면, 단순히 벡터로 펼쳐서 나타낼 수도 있지만, 보다 직관적으로는 가로 × 세로의 픽셀 격자로 이루어진 행렬이며, 각 픽셀은 RGB 세 가지 채널 값을 갖는다. 결국 하나의 이미지는 (높이) × (너비) × (채널 수) 형태의 3차원 구조, 즉 직육면체 모양의 수치 배열로 표현된다.
그렇다면 이런 이미지가 여러 장 있다면 어떻게 될까? 여러 개의 벡터를 쌓아 행렬을 만들었던 것처럼, 3차원 구조의 이미지들을 또 다른 방향으로 쌓으면 4차원 구조가 된다. 이처럼 차원이 높아질수록 데이터의 구조는 점점 복잡해진다.
이 지점에서 텐서(Tensor) 라는 개념이 등장한다. 텐서는 이처럼 임의의 차원(축, axis)을 가지는 수치 배열을 통칭하는 개념으로, 스칼라·벡터·행렬을 모두 텐서의 특수한 경우로 일반화할 수 있다.
개념
차원 (Rank)
예시
스칼라 (Scalar)
0차원
3.14
벡터 (Vector)
1차원
[0.5, 1.2, 1.7]
행렬 (Matrix)
2차원
[[1, 2], [3, 4]]
텐서 (Tensor)
3차원 이상
이미지 배치, 영상 데이터 등
이때, 반드시 3차원 이상의 데이터만을 텐서라고 가리키는 것은 아니다. 벡터는 1D Tensor, 행렬은 2D Tensor라고도 부를 수 있으며, 텐서는 이들을 모두 포괄하는 일반화된 개념이다.
- rank
선형대수학에서 Rank는 행렬의 열(또는 행) 중 선형 독립인 벡터의 수, 즉 기저 벡터의 개수를 의미했었다. 반면 텐서를 다룰 때의 Rank는 의미가 다소 달라지는데, 텐서가 가진 축(axis)의 수, 즉 몇 차원인지를 나타내는 개념으로 사용된다.
이처럼 텐서라는 개념을 통해, 스칼라부터 고차원 데이터까지 다양한 형태의 수치 데이터를 하나의 일관된 자료구조로 표현하고 다룰 수 있게 된다.
1.2 Tensor in PyTorch
- Tensor 생성
다음과 같이 tensor를 정의할 수 있다.
t1 = torch.tensor([0, 1, 2, 3])
t2 = torch.tensor([[1,2,3],
[4,5,6]])
이 외에도 특정 값으로 초기화된 텐서를 생성하는 다양한 방법이 있다.
torch.zeros(2, 3) # 0으로 채워진 2x3 텐서
torch.ones(2, 3) # 1로 채워진 2x3 텐서
torch.full((2, 3), 7) # 7으로 채워진 2x3 텐서
torch.eye(3) # 3x3 단위 행렬(대각선이 1)
torch.rand(2, 3) # 0~1 사이의 균일 분포 난수 텐서
torch.randn(2, 3) # 평균 0, 표준편차 1의 정규 분포 난수 텐서
- rank, 형태 확인
dim() : 텐서의 rank(차원 수)를 반환한다.
shape, size() : 텐서의 크기(shape)를 반환한다.
print(t1.dim()) # rank = 1
print(t1.shape) # torch.Size([4])
print(t2.dim()) # rank = 2
print(t2.shape) # torch.Size([2, 3])
print(t2.size(0)) # 2 (첫 번째 축의 크기)
print(t2.size(1)) # 3 (두 번째 축의 크기)
- Tensor의 자료형 (dtype)
텐서는 담고 있는 값의 자료형에 따라 여러 종류로 나뉜다.
자료형
PyTorch dtype
설명
torch.FloatTensor
torch.float32
32비트 부동 소수점 (가장 널리 사용)
torch.DoubleTensor
torch.float64
64비트 부동 소수점
torch.HalfTensor
torch.float16
16비트 부동 소수점 (경량화 모델에 주로 사용)
torch.IntTensor
torch.int32
32비트 정수
torch.LongTensor
torch.int64
64비트 정수 (인덱스, 레이블에 주로 사용)
torch.BoolTensor
torch.bool
True / False
자료형은 텐서 생성 시 dtype= 인자로 지정하거나, 이후 to() 혹은 type()으로 변환할 수 있다.
t = torch.tensor([1, 2, 3], dtype=torch.float32)
print(t.dtype) # torch.float32
t = t.to(torch.float64)
print(t.dtype) # torch.float64
혹은 텐서 뒤에 원하는 자료형을 붙여 타입 캐스팅이 가능하다.
print(t.float()) # tensor([1., 2., 3.]) → float32
print(t.double()) # tensor([1., 2., 3.]) → float64
print(t.half()) # tensor([1., 2., 3.]) → float16
print(t.long()) # tensor([1, 2, 3]) → int64
print(t.int()) # tensor([1, 2, 3]) → int32
print(t.bool()) # tensor([True, True, True])
딥러닝 모델에서는 대부분 float32를 기본 자료형으로 사용한다.
torch.tensor() => 타입이 인풋에 따라감, torch.Tensor() => 무조건 float
- Tensor와 NumPy 변환
PyTorch 텐서는 NumPy 배열과 상호 변환이 가능하다.
tensor(numpy배열) : numpy배열을 파이토치 tensor로 변환한다. 이때 새 메모리를 할당 받음.
from_numpy(numpy배열) : 똑같이 파이토치 tensor로 변환하다. 원본 NumPy 배열과 메모리를 공유한다. 한쪽을 수정하면 다른 쪽도 영향을 받으니 주의.
# NumPy → Tensor
arr = np.array([1.0, 2.0, 3.0])
t = torch.Tensor(arr)
t2 = torch.from_numpy(arr)
# Tensor → NumPy
arr2 = t.numpy()
print(t) # tensor([1., 2., 3.])
print(t.dtype) # torch.float32
print(t2) # tensor([1., 2., 3.], dtype=torch.float64)
print(arr2) # [1. 2. 3.]
1.3 Tensor 연산
텐서 간의 기본적인 사칙연산은 동일한 shape일 때 element-wise(원소별)로 수행된다.
a = torch.tensor([1.0, 2.0, 3.0])
b = torch.tensor([4.0, 5.0, 6.0])
print(a + b) # tensor([5., 7., 9.])
print(a - b) # tensor([-3., -3., -3.])
print(a * b) # tensor([ 4., 10., 18.])
print(a.mul(b)) # tensor([ 4., 10., 18.]) ==> * 연산자와 같은 역할
print(a / b) # tensor([0.25, 0.40, 0.50])
행렬 곱(Matrix Multiplication)은 @ 연산자 또는 torch.matmul()을 사용한다.
A = torch.tensor([[1.0, 2.0],
[3.0, 4.0]])
B = torch.tensor([[5.0, 6.0],
[7.0, 8.0]])
print(A @ B)
# tensor([[19., 22.],
# [43., 50.]])
1.4 Tensor 조작 메서드
:: sum, mean
A = torch.tensor([[1.0, 2.0],
[3.0, 4.0]])
print(A.sum()) # tensor(10.)
print(A.sum(dim=0)) # tensor([4., 6.])
print(A.mean()) # tensor(2.5000)
print(A.mean(dim=0)) # tensor([2., 3.])
:: max (argmax)
max()로 호출하면 텐서의 최댓값을 반환하고, dim을 파라미터로 같이 전달하면 argmax 정보가 담긴 텐서와 같이 리턴된다.
print(A.max()) # tensor(4.)
print(A.max(dim=0))
# torch.return_types.max(values=tensor([3., 4.]),indices=tensor([1, 1]))
print(A.max(dim=-1))
# torch.return_types.max(values=tensor([2., 4.]),indices=tensor([1, 1]))
print(A.max(dim=0)[1]) # tensor([1, 1])
:: view
view를 호출하는 텐서의 원소의 갯수를 유지하며 shape을 변경한다.
A = torch.tensor([[1, 2, 3],
[4, 5, 6],
[7, 8, 9],
[10, 11,12]])
print(A.shape) # torch.Size([4, 3])
print(A.view(3,-1).shape) # torch.Size([3, 4])
print(A.view(2,2,-1).shape) # torch.Size([2, 2, 3])
print(A.view(-1)) # tensor([ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12])
:: squeeze, unsqueeze
squeeze(): 크기가 1인 차원을 삭제
unsqueeze(dim): 크기가 1인 차원을 추가
A = torch.tensor([[[1, 2, 3],
[4, 5, 6]]])
print(A.shape) # torch.Size([1, 2, 3])
print(A.squeeze().shape) # torch.Size([2, 3])
A = torch.tensor([[[1, 2, 3, 4, 5, 6]]])
print(A.shape) # torch.Size([1, 1, 6])
print(A.squeeze().shape) # torch.Size([6])
print(A.squeeze(0).shape)# torch.Size([1, 6])
A = torch.tensor([1, 2, 3, 4, 5, 6])
print(A.shape) # torch.Size([6])
print(A.unsqueeze(0).shape) # torch.Size([1, 6])
print(A.unsqueeze(1).shape) # torch.Size([6, 1])