[AI] PyTorch : 1. Tensors

NewPlus·2022년 6월 21일
0

PyTorch

목록 보기
1/11

2022.06.21 연구실 공부(PyTorch). 'PyTorch로 시작하는 딥 러닝 입문'< 이 글은 이 책의 내용을 요약 정리한 것임.>(내 저작물이 아니고 저 위 링크에 있는 것이 원본임)
02-02 Tensor Manipulation

Summary

  • Vector, Matrix and Tensor
  • Numpy Review
  • PyTorch Tensor Allocation
  • Maxtrix Multiplication
  • Other Basic Ops

1. Vector, Matrix and Tensor

1-1. Base units

  • Scala(스칼라) : 차원이 없는 경우
  • Vector(벡터) : 1차원으로 구성된 값 -> 1D Tensor 라고도 함
  • Matrix(행렬) : 2차원으로 구성된 값 -> 2D Tensor 라고도 함
  • Tensor(텐서) : 3차원으로 구성된 값 -> 3D Tensor 라고도 함
  • 4차원부터는 Tensor(3D)를 위로 쌓아 올린 모습으로 생각(5D는 4D의 모임 등)

1-2. 2D Tensor(Matrix)

  • 크기를 |t|라 할때, |t| = batch size(행) * dimension(열)으로 표현

1-3. 3D Tensor(Typical Computer Vision)

  • |t| = (batch size, width, height)
  • 이미지(width, height)가 여러 장(batch size) 존재하므로 3차원 텐서가 된다.

1-4. 3D Tensor(Typical Natural Language Processing)

  • |t| = (batch size, length, dim)
  • batch size(여러 단어 및 문장), 문장 길이(length), 단어 벡터의 차원(dimension)가 존재하므로 3차원 텐서가 된다.

1-4-1. NLP 3D Tensor Examples

  • 예를 들어 다음과 같은 훈련데이터가 있다고 하자.
[[I love Korea], [I love America], [I hate Korea], [I hate America]]
  • 컴퓨터에게 입력시키기 위해 단어 단위로 나눠준다.
[['I', 'love', 'Korea'], ['I', 'love', 'America'], ['I', 'hate', 'Korea'], ['I', 'hate', 'America']]
  • 이 input data는 4 * 3 크기인 2D Tensor이다.
  • 이 단어를 벡터 값으로 나타낸다.(컴퓨터는 텍스트보다 숫자를 더 잘 처리함)
'I' = [0.1, 0.2, 0.9]
'Korea' = [0.3, 0.5, 0.1]
'America' = [0.3, 0.5, 0.2]
'love' = [0.7, 0.6, 0.5]
'hate' = [0.5, 0.6, 0.7]
  • 이것을 훈련데이터로 재구성하면 다음과 같다.(4 3 3 크기인 3D Tensor)
[[[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.7, 0.6, 0.5]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.7, 0.6, 0.5]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.5, 0.6, 0.7]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.5, 0.6, 0.7]]]
  • batch size가 2라면
첫번째 배치 #1
[[[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.7, 0.6, 0.5]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.7, 0.6, 0.5]]]

두번째 배치 #2
[[[0.1, 0.2, 0.9], [0.3, 0.5, 0.1], [0.5, 0.6, 0.7]],
 [[0.1, 0.2, 0.9], [0.3, 0.5, 0.2], [0.5, 0.6, 0.7]]]
  • 각 배치의 Tensor size는 (2 3 3)이므로 이는 곧 (batch size, 문장 길이, 단어 벡터의 차원)의 크기임.

2. Numpy Review

  • 넘파이(Numpy)는 Python의 수학 라이브러리로 다음과 같이 임포함.
import numpy as np

2-1. 1D with Numpy

  • 파이썬 리스트를 np.array()로 감싸는 형태로 Tensor를 만든다.
t = np.array([0., 1., 2., 3., 4., 5., 6.])
  • 넘파이 텐서의 벡터 차원과 크기 출력
print('Rank of t: ', t.ndim)
print('Shape of t: ', t.shape)
  • .ndim : 몇 차원인지를 출력함
  • .shape : 크기를 출력함
  • , : Tensor의 크기(shape)를 표시하는데 사용함.(Ex : (2 * 3) = (2, 3) = (2 × 3))
  • 벡터 원소에 접근 : index로 접근
print('t[0] t[1] t[-1] = ', t[0], t[1], t[-1])
  • Slicing(슬라이싱) : 시작번호:끝번호 식으로 범위 지정을 통해 추출(단, 끝 번호에 해당하는 것은 포함하지 않는다!)
print('t[2:5] t[4:-1]  = ', t[2:5], t[4:-1])
t[2:5] t[4:-1]  =  [2. 3. 4.] [4. 5.]

2-2. 2D with Numpy

  • 파이썬 2차원 리스트를 np.array()로 감싸는 형태로 2D Tensor를 만든다.
t = np.array([[1., 2., 3.], [4., 5., 6.], [7., 8., 9.], [10., 11., 12.]])
print(t)
[[ 1.  2.  3.]
 [ 4.  5.  6.]
 [ 7.  8.  9.]
 [10. 11. 12.]]
  • 2차원 넘파이 텐서의 벡터 차원과 크기 출력
print('Rank  of t: ', t.ndim)
print('Shape of t: ', t.shape)
Rank  of t:  2
Shape of t:  (4, 3)

3. PyTorch Tensor Allocation

  • PyTorch는 Numpy와 매우 유사하지만 좀 더 나음
  • PyTorch는 다음과 같이 import함
import torch

3-1. 1D with PyTorch

t = torch.FloatTensor([0., 1., 2., 3., 4., 5., 6.])
  • 텐서의 차원과 크기 보기
print(t.dim())  # rank. 즉, 차원
print(t.shape)  # shape
print(t.size()) # shape
  • .dim() : 현재 텐서의 차원을 보여줌
  • .shape .size() : 현재 텐서의 크기를 보여줌
  • index와 슬라이싱 : Numpy와 같음

3-2. 2D with PyTorch

t = torch.FloatTensor([[1., 2., 3.],
                       [4., 5., 6.],
                       [7., 8., 9.],
                       [10., 11., 12.]
                      ])
tensor([[ 1.,  2.,  3.],
        [ 4.,  5.,  6.],
        [ 7.,  8.,  9.],
        [10., 11., 12.]])
  • 텐서의 차원과 크기 보기
print(t.dim())  # rank. 즉, 차원
print(t.size()) # shape
  • .dim() : 현재 텐서의 차원을 보여줌
  • .shape .size() : 현재 텐서의 크기를 보여줌
print(t[:, 1]) # 첫번째 차원을 전체 선택한 상황에서 두번째 차원의 첫번째 것만 가져온다.
print(t[:, 1].size()) # ↑ 위의 경우의 크기
  • 텐서가 2차원인 경우, 첫번째 코드는 첫번째 차원을 전체 선택한 후, 두번째 차원의 첫번째(:, 1) 것만 가져옴
  • 두번째 코드는 (:, 1)의 사이즈를 가져온다.
print(t[:, :-1]) # 첫번째 차원을 전체 선택한 상황에서 두번째 차원에서는 맨 마지막에서 첫번째를 제외하고 다 가져온다.
  • 첫번째 차원을 전체 선택한 상황(:, )에서 두번째 차원에서는 맨 마지막에서 첫번째를 제외(:, :-1)하고 다 가져오는 코드이다.

3-3. Broadcasting

  • 행렬(Matrix)에서는 덧셈과 뺄셈 시, 두 행렬의 크기가 같아야 함.
  • 또한 곱셈 시, 앞 행렬의 마지막 차원과 뒷 행렬의 첫번째 차원이 일치해야 함.
  • 파이토치(PyTorch)에서는 자동으로 크기를 맞춰서 연산을 수행하게 만드는 브로드캐스팅(broadcasting)이라는 기능을 제공함.
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([3]) # [3] -> [3, 3]
print(m1 + m2)
tensor([[4., 5.]])
  • 이처럼 덧셈 시, 크기가 안맞을 경우 자동으로 크기를 조정함(위 예시에서는 (1,2)를 (1,)과 더하게 하였으므로 (1,)를 (1,2)로 브로드캐스팅하여 크기를 조정하였음)
# 2 x 1 Vector + 1 x 2 Vector
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([[3], [4]])
print(m1 + m2)
tensor([4., 5.],
       [5., 6.]])
  • 여기서 덧셈 시, 크기가 안 맞지만 (2,2)로 브로드캐스팅하여 연산을 시킴.
  • 그러나 자동으로 실행되는 기능이므로 사용자 입장에서 굉장히 주의해서 사용해야함.
  • 특히 사용자는 크기가 맞다고 착각하여 연산을 진행시켰는데 나중에 원하는 결과가 나오지 않아 디버깅을 할 때 오류를 찾기가 굉장히 어려움.

4. Maxtrix Multiplication

4-1. Maxtrix Multiplication vs. Multiplication

  • 행렬에서의 곱셈은 행렬 곱셈(.matmul)과 원소별 곱셈(.mul) 두 가지가 있음.
  • m1.matmul(m2) : 행렬 곱셈
m1 = torch.FloatTensor([[1, 2], [3, 4]])
m2 = torch.FloatTensor([[1], [2]])
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1
print(m1.matmul(m2)) # 2 x 1
Shape of Matrix 1:  torch.Size([2, 2])
Shape of Matrix 2:  torch.Size([2, 1])
tensor([[ 5.],
        [11.]])
  • m1 * m2 혹은 m1.mul(m2) : 원소별 곱셈 혹은 element-wise 곱셈
m1 = torch.FloatTensor([[1, 2], [3, 4]])
m2 = torch.FloatTensor([[1], [2]])
print('Shape of Matrix 1: ', m1.shape) # 2 x 2
print('Shape of Matrix 2: ', m2.shape) # 2 x 1
print(m1 * m2) # 2 x 2
print(m1.mul(m2))
Shape of Matrix 1:  torch.Size([2, 2])
Shape of Matrix 2:  torch.Size([2, 1])
tensor([[1., 2.],
        [6., 8.]])
tensor([[1., 2.],
        [6., 8.]])

5. Other Basic Ops

5-1. Mean(평균)

  • 1차원의 경우
t = torch.FloatTensor([1, 2])
print(t.mean())
tensor(1.5000)
  • 2차원의 경우
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
print(t.mean())
tensor([[1., 2.],
        [3., 4.]])
tensor(2.5000)
  • 4개 원소의 평균인 2.5가 나옴
  • 차원(dimension)을 인자(dim)으로 주는 경우
print(t.mean(dim=0))
tensor([2., 3.])
  • dim=0 : 첫번째 차원, 행렬의 '행'을 의미하는데 dim=0은 곧 행렬에서 '열'만을 남긴다는 의미가 된다. 기존 행렬의 크기는 (2,2) 였지만 이 코드 이후에는 (1,2)가 되므로 이는 (2,)와 같으며 벡터이다.
  • dim을 지정하는 것은 지정한 차원을 제거하여 연산을 수행한다는 의미임.

5-2. Sum(덧셈)

  • 평균(Mean)과 연산 방법이나 인자가 의미하는 바는 정확히 동일. 단지 평균이 아닌 덧셈을 함.
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
tensor([[1., 2.],
        [3., 4.]])
  • dim을 지정하는 것은 지정한 차원을 제거하여 연산을 수행한다는 의미임.
print(t.sum()) # 단순히 원소 전체의 덧셈을 수행
print(t.sum(dim=0)) # 행을 제거
print(t.sum(dim=1)) # 열을 제거
print(t.sum(dim=-1)) # 열을 제거
tensor(10.)
tensor([4., 6.])
tensor([3., 7.])
tensor([3., 7.])

5-3. 최대(Max)와 아그맥스(ArgMax)

  • 최대(Max)는 원소의 최대값을 리턴하고, 아그맥스(ArgMax)는 최대값을 가진 인덱스를 리턴
  • 다음 텐서 선언
t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
tensor([[1., 2.],
        [3., 4.]])
  • .max() : 행렬의 최대 원소 값을 리턴한다.
print(t.max()) # Returns one value: max
tensor(4.)
  • 원소 중 최대값인 4를 리턴함
  • 인자(dim)로 0을 준 경우
print(t.max(dim=0)) # Returns two values: max and argmax
(tensor([3., 4.]), tensor([1, 1]))
  • 인자를 준 경우에는 위 처럼 max에 해당하는 값과 argmax에 해당하는 값(인덱스 값)을 동시에 전달한다.
  • dim=0의 경우 행의 차원을 제거한다는 의미 -> (1,2) Tensor를 만든다. -> 각 열에서 가장 max값을 리턴 -> 3, 4가 리턴
  • argmax의 경우 -> dim=0을 통해 행의 차원 제거 -> (1,2) Tensor를 만든다. -> 각 열에서 가장 max값의 index를 리턴 -> 1, 1이 리턴
  • 만약 max나 argmax를 따로따로 가져오고 싶은 경우
print('Max: ', t.max(dim=0)[0])
print('Argmax: ', t.max(dim=0)[1])
Max:  tensor([3., 4.])
Argmax:  tensor([1, 1])

5-4. 뷰(View)

  • 다음과 같은 텐서를 하나 만든다.
t = np.array([[[0, 1, 2],
               [3, 4, 5]],
              [[6, 7, 8],
               [9, 10, 11]]])
ft = torch.FloatTensor(t)
  • 이는 곧 ft라는 이름의 3차원 텐서임.(크기는 (2, 2, 3)임.)
print(ft.shape)
torch.Size([2, 2, 3])

  • 3차원에서 2차원 텐서로 변경
print(ft.view([-1, 3])) # ft라는 텐서를 (?, 3)의 크기로 변경
print(ft.view([-1, 3]).shape)
tensor([[ 0.,  1.,  2.],
        [ 3.,  4.,  5.],
        [ 6.,  7.,  8.],
        [ 9., 10., 11.]])
torch.Size([4, 3])
  • -1은 사용자가 잘 모르는 차원이니 파이토치에게 위임한다는 의미

  • 3은 두번째 차원의 길이는 3을 가지도록 하라는 의미

  • 결과적으로 (4, 3)의 크기를 가지는 텐서를 얻었음.

  • 구체적인 과정 : (2, 2, 3) -> (2 × 2, 3) -> (4, 3) (둘 다 원소의 개수는 12개임)

  • view는 기본적으로 변경 전과 변경 후의 텐서 안의 원소의 개수가 유지되어야 한다!

  • 파이토치의 view는 사이즈가 -1로 설정되면 다른 차원으로부터 해당 값을 유추한다!

  • 3차원 텐서의 크기 변경(차원은 유지하되, 크기(shape)를 바꾸는 작업)

  • view로 크기를 변경한다면 원소의 수는 유지되어야 하므로 (2 × 2 × 3) 텐서를 (? × 1 × 3) 텐서로 변경하라고 하면 ?는 4가 된다.

  • 구체적인 과정 : (2 × 2 × 3) = (? × 1 × 3) = 12를 만족하는 ?는 4이다.

print(ft.view([-1, 1, 3]))
print(ft.view([-1, 1, 3]).shape)
tensor([[[ 0.,  1.,  2.]],
        [[ 3.,  4.,  5.]],
        [[ 6.,  7.,  8.]],
        [[ 9., 10., 11.]]])
torch.Size([4, 1, 3])

5-5. 스퀴즈(Squeeze)

  • 스퀴즈는 차원이 1인 경우에는 해당 차원을 제거.
  • 임의로 다음과 같은 텐서 선언
ft = torch.FloatTensor([[0], [1], [2]])
print(ft)
print(ft.shape)
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])
  • 텐서의 크기는 (3, 1)이고 두번째 차원이 1이므로 squeeze를 사용하면 (3,)의 크기를 가지는 텐서로 변경
print(ft.squeeze())
print(ft.squeeze().shape)
tensor([0., 1., 2.])
torch.Size([3])
  • 결과는 1이었던 두번째 차원이 제거되면서 (3,)의 크기를 가지는 텐서로 변경되어 1차원 벡터가 된 것을 알 수 있음.

5-6. 언스퀴즈(Unsqueeze)

  • 스퀴즈와 정반대로 특정 위치에 1인 차원을 추가
  • 임의로 (3,)의 크기를 가지는 1인 차원 텐서 선언
ft = torch.Tensor([0, 1, 2])
print(ft.shape)
torch.Size([3])
  • 첫번째 차원의 인덱스를 의미하는 숫자 0을 인자로 넣으면 첫번째 차원에 1인 차원이 추가
print(ft.unsqueeze(0)) # 인덱스가 0부터 시작하므로 0은 첫번째 차원을 의미한다.
print(ft.unsqueeze(0).shape)
tensor([[0., 1., 2.]])
torch.Size([1, 3])
  • 이는 곧 (,3)<1차원> -> (1, 3)<2차원>이 된 것과 같다. -> View로도 가능함.
print(ft.view(1, -1))
print(ft.view(1, -1).shape)
tensor([[0., 1., 2.]])
torch.Size([1, 3])
  • 결론적으로 view(), squeeze(), unsqueeze()는 텐서의 원소 수를 그대로 유지하면서 모양과 차원을 조절

5-7. 타입 캐스팅(Type Casting)

  • 자료형(Data Type)

  • 이러한 자료형을 변환하는 것을 타입 캐스팅이라 한다.
  • 아래와 같이 실습용으로 long타입의 lt라는 텐서를 선언
lt = torch.LongTensor([1, 2, 3, 4])
print(lt)
  • 텐서에다가 .float()를 붙이면 바로 float형으로 타입이 변경
print(lt.float())
tensor([1., 2., 3., 4.])

5-8. 연결하기(concatenate)

  • (2, 2) 크기의 텐서를 두 개 선언
x = torch.FloatTensor([[1, 2], [3, 4]])
y = torch.FloatTensor([[5, 6], [7, 8]])
  • torch.cat([]) : 두 텐서를 연결한다, dim을 통해 어느 차원을 늘릴지 인자로 줄 수 있다.
  • dim=0 : 첫번째 차원을 늘리라는 의미
print(torch.cat([x, y], dim=0))
tensor([[1., 2.],
        [3., 4.],
        [5., 6.],
        [7., 8.]])
  • dim=0을 인자로 했더니 두 개의 (2 × 2) 텐서가 (4 × 2) 텐서가 됨
  • dim=1을 인자로 준 경우
print(torch.cat([x, y], dim=1))
tensor([[1., 2., 5., 6.],
        [3., 4., 7., 8.]])
  • dim=1을 인자로 했더니 두 개의 (2 × 2) 텐서가 (2 × 4) 텐서가 됨.
  • 딥러닝에서의 연결 : 두 텐서를 연결해서 입력으로 사용하는 것은 두 가지의 정보를 모두 사용한다는 의미

5-9. 스택킹(Stacking)

  • 연결(concatenate)을 하는 또 다른 방법
  • 실습용으로 크기가 (2,)로 모두 동일한 3개의 벡터를 선언
x = torch.FloatTensor([1, 4])
y = torch.FloatTensor([2, 5])
z = torch.FloatTensor([3, 6])
  • 3개의 벡터를 모두 스택킹
print(torch.stack([x, y, z]))
tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])
  • 3개의 벡터가 순차적으로 쌓여 (3 × 2) 텐서가 됨.
  • 위의 코드는 곧 아래와 같다.
print(torch.cat([x.unsqueeze(0), y.unsqueeze(0), z.unsqueeze(0)], dim=0))
  • 위 코드의 구체적인 과정 : (2,) -> unsqueeze(0) -> (1, 2) -> cat -> (3, 2)
  • 위 코드(concatenate)보다는 스태킹이 훨씬 간편하고 이해하기 쉬움.
  • 추가로 dim을 인자로 전달 가능.
  • dim=1 인자로 주는 경우 : 두번째 차원이 증가하도록 쌓으라는 의미
print(torch.stack([x, y, z], dim=1))
tensor([[1., 2., 3.],
        [4., 5., 6.]])
  • 결과적으로 두번째 차원이 증가하도록 스택킹 -> (2 × 3) 텐서

5-10. ones_like와 zeros_like

  • 실습을 위해 (2, 3) 텐서 선언
x = torch.FloatTensor([[0, 1, 2], [2, 1, 0]])
print(x)
tensor([[0., 1., 2.],
        [2., 1., 0.]])
  • ones_like를 하면 동일한 크기(shape)지만 1으로만 값이 채워진 텐서
print(torch.ones_like(x)) # 입력 텐서와 크기를 동일하게 하면서 값을 1로 채우기
tensor([[1., 1., 1.],
        [1., 1., 1.]])
  • zeros_like를 하면 동일한 크기(shape)지만 0으로만 값이 채워진 텐서
print(torch.zeros_like(x)) # 입력 텐서와 크기를 동일하게 하면서 값을 1로 채우기
tensor([[0., 0., 0.],
        [0., 0., 0.]])

5-11. In-place Operation (덮어쓰기 연산)

  • 실습을 위해 (2 × 2) 텐서 선언
x = torch.FloatTensor([[1, 2], [3, 4]])
  • 단순히 연산(예를 들어, 곱하기 연산)을 한 경우에는 원래 값에 변동이 없다. 그러나 덮어쓰기 연산을 통해 원래 값에 연산 후 값을 덮어쓰기하여 저장할 수 있다.
  • 연산 뒤에 _ 를 붙이면 기존의 값을 덮어쓰기한다.
print(x.mul_(2.))  # 곱하기 2를 수행한 결과를 변수 x에 값을 저장하면서 결과를 출력
print(x) # 기존의 값 출력
tensor([[2., 4.],
        [6., 8.]])
tensor([[2., 4.],
        [6., 8.]])

출처 : 'PyTorch로 시작하는 딥 러닝 입문' <이 책의 내용을 요약 정리한 것임.>

profile
매일 매일 새로워지는 나 자신을 꿈꾸며

0개의 댓글