새소식

반응형
컴퓨터공학 (Computer Science)/┗ 인공신경망 | Neural Network

딥러닝 코딩을 위한 배경지식 - 텐서 조작 (Tensor Manipulation)

  • -
반응형

1. Introduction

텐서(Tensor)는 연산을 용이하게 하기 위해, 벡터를 모아둔 단위라고 정의하기는 하지만, 컴퓨터 공학에서는 사실상 3차원 행렬로 통용된다.

물론, 3차원 행렬, 2차원 텐서라고 사용되기도 하지만 사용자가 그 의미를 모르는 경우는 거의 없다. 이런 단위는 다음과 같이 정리된다.

  • 0차원 스칼라 (Scalar): 정수
  • 1차원 벡터 (Vector): 리스트
  • 2차원 행렬 (Maxtrix): 2차원 행렬
  • 3차원 텐서 (Tensor): 3차원 행렬

일반적으로 2차원, 3차원 행렬을 가지고 딥러닝을 수행할 때, 이 행렬의 row 크기를 batch size라고 일반적으로 부른다.

2. Practice

2.1 Basic

Pytorch에서 텐서를 다루는 방법은 Numpy의 array와 완전히 동일하다. 다음과 같은 예제를 보자.

import torch
t = torch.FloatTensor([0, 1, 2, 3, 4, 5, 6])
print(t)

print(t.dim())              # rank
print(t.shape)              # shape
print(t.size())             # shape
print(t[0], t[1], t[-1])    # Element
print(t[2:5], t[4:-1])      # Slicing
print(t[:2], t[3:])         # Slicing
print(t[::2])               # Slicing

이 예제는 다음과 같은 결과를 리턴한다.

#Terminal
tensor([0., 1., 2., 3., 4., 5., 6.])
1
torch.Size([7])
torch.Size([7])
tensor(0.) tensor(1.) tensor(6.)
tensor([2., 3., 4.]) tensor([4., 5.])
tensor([0., 1.]) tensor([3., 4., 5., 6.])
tensor([0., 2., 4., 6.])

2.2 Broadcasting

텐서의 연산에서는 Broadcasting이란 것이 있는데, 연산중에 행렬의 크기가 맞지 않으면 연산에 맞게 행렬을 확장한다. 다음과 같은 예제를 보자.

print('-------------')
print('Hadamard Mul vs Matrix Mul')
print('-------------')
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

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))

첫번째 결과는 일반 행렬곱이고 두번째 결과는 아다마르(Hadamard) 곱이라고 불리는 그냥 같은 위치의 값끼리 곱하는 행렬의 곱셈이다.

#Terminal
-------------
Hadamard Mul vs Matrix Mul
-------------
Shape of Matrix 1:  torch.Size([2, 2])
Shape of Matrix 2:  torch.Size([2, 1])
tensor([[ 5.],
        [11.]])
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.]])

원래 같으면 두 행렬의 크기가 다르기 때문에 아다마르 곱이 불가능하나, Broadcasting에 의해 $\begin{bmatrix} 1 \\ 2 \end{bmatrix}$가 $\begin{bmatrix} 1 & 1 \\ 2 & 2\end{bmatrix}$로 Broadcasting된 것을 볼 수 있다.

2.3 Raw & Column Calculation

Numpy와 마찬가지로 Raw별, Coulum별 연산을 지원한다.

t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)

print(t.sum())
print(t.sum(dim=0))
print(t.sum(dim=1))
print(t.mean())
print(t.mean(dim=0)) 
print(t.mean(dim=1))

출력은 다음과 같다.

#Terminal
tensor([[1., 2.],
        [3., 4.]])			# 기본출력
tensor(10.)				# 전체합
tensor([4., 6.])			# dim=0 : column별 합
tensor([3., 7.])			# dim=1 : raw별 합
tensor(2.5000)				# 전체 평균
tensor([2., 3.])			# dim=0 : column별 평균
tensor([1.5000, 3.5000])		# dim=1 : raw별 평균

당연하겠지만, 차원 정보를 안 줄 경우 디폴트는 행렬 전체에 대해 연산을 수행한다.

차원이 0일 경우, column별로, 차원이 1일 경우, raw별로 연산을 수행한다. 이것은 Numpy때부터 지원하는 표준이다.

2.4 Max

당연히 Max도 지원하지만 Pytorch에서는 차원에 대해 Max를 출력할 경우, value와 함께 그 value의 index도 함께 리턴한다.

print(t.max()) 

print("-----------------------")
print(t.max(dim=0))
print("-----------------------")
print('Max: ', t.max(dim=0)[0])
print('MaxIdx: ', t.max(dim=0)[1])
print("-----------------------")
print(t.max(dim=1))

출력은 다음과 같다.

#Terminal
tensor(4.)			# 전체 최대 값
-----------------------
torch.return_types.max(
values=tensor([3., 4.]),	# col별 최대 값과 그 인덱스
indices=tensor([1, 1]))
-----------------------
Max:  tensor([3., 4.])		# col별 최대 값
MaxIdx:  tensor([1, 1])		# col별 최대 값의 인덱스
-----------------------
torch.return_types.max(
values=tensor([2., 4.]),	# raw별 최대 값과 그 인덱스
indices=tensor([1, 1]))

2.5 View

View 메서드는 Numpy의 reshape와 같다. 역시 표준과 마찬가지로 -1을 주면 자동으로 남은 수를 채운다.

import numpy as np

t = np.array([[[0, 1, 2],		#2,2,3 사이즈의 행렬
               [3, 4, 5]],

              [[6, 7, 8],
               [9, 10, 11]]])
ft = torch.FloatTensor(t)

print(ft.view([-1, 1, 3]))		# ?,1,3으로 변화
print(ft.view([-1, 1, 3]).shape)

출력은 다음과 같다.

#Terminal
tensor([[[ 0.,  1.,  2.]],

        [[ 3.,  4.,  5.]],

        [[ 6.,  7.,  8.]],

        [[ 9., 10., 11.]]])
torch.Size([4, 1, 3])

이렇다 보니 가장 자주 쓰는 방법은 행렬은 열 또는 행벡터로 바꾸는 것.

2.6 Squeeze & Unsqueeze

차원을 조절하는 메서드이다. squeeze로 크기가 1인 차원을 없애고 unsqueeze로 차원을 추가한다.

ft = torch.FloatTensor([[0], [1], [2]])

squeezed = ft.squeeze()
print(squeezed)
print(squeezed.shape)
print("-----------------------")
print(squeezed.unsqueeze(1))
print(squeezed.unsqueeze(1).shape)

출력은 다음과 같다.

#Terminal
tensor([0., 1., 2.])
torch.Size([3])
-----------------------
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])

2.7 Type casting

Pytorch에서도 Numpy처럼 행렬의 타입 캐스팅은 전체 원소에 적용된다.

Long, Float, Byte(사실상 boolean)의 3가지 타입이 존재한다.

lt = torch.LongTensor([1, 2, 3, 4])
print(lt)
print(lt.float())

print("---------------------------")
bt = torch.ByteTensor([True, False, False, True])
print(bt)
print(bt.long())
print(bt.float())

print("---------------------------")
mask = (lt == 3)
print(mask)

출력은 다음과 같다.

#Terminal
tensor([1, 2, 3, 4])
tensor([1., 2., 3., 4.])
---------------------------
tensor([1, 0, 0, 1], dtype=torch.uint8)
tensor([1, 0, 0, 1])
tensor([1., 0., 0., 1.])
---------------------------
tensor([False, False,  True, False])

2.8 Concatenate & Stack

Concatenate는 글자 그대로 텐서간의 병합이다.

x = torch.FloatTensor([[1, 2], [3, 4]])
y = torch.FloatTensor([[5, 6], [7, 8]])

print(torch.cat([x, y], dim=0))

print("-------------------------")
print(torch.cat([x, y], dim=1))

출력은 다음과 같다.

#Terminal
tensor([[1., 2.],			#col 방향으로 합침
        [3., 4.],
        [5., 6.],
        [7., 8.]])
-------------------------
tensor([[1., 2., 5., 6.],		#raw 방향으로 합침
        [3., 4., 7., 8.]])

Stack은 차원을 늘리면서 Concatenate를 하는 메서드이다.

x = torch.FloatTensor([1, 4])
y = torch.FloatTensor([2, 5])
z = torch.FloatTensor([3, 6])
print(torch.stack([x, y, z]))

print("----------------------------")
print(torch.cat([x.unsqueeze(0), y.unsqueeze(0), z.unsqueeze(0)], dim=0)) #print(torch.stack([x, y, z]))와 같음

print("----------------------------")
print(torch.stack([x, y, z], dim=1))

출력은 다음과 같다.

#Terminal
tensor([[1., 4.],
        [2., 5.],
        [3., 6.]])
----------------------------
tensor([[1., 4.],			
        [2., 5.],
        [3., 6.]])
----------------------------
tensor([[1., 2., 3.],
        [4., 5., 6.]])

2.9 Initialization

당연히 기본 초기화 메서드도 지원한다.

a = torch.ones(3,2)
b = torch.zeros(3,2)
print(a)
print(b)
print("--------------------------")
x = torch.FloatTensor([[0, 1, 2], [2, 1, 0]])
print(torch.ones_like(x))
print(torch.zeros_like(x))

출력은 다음과 같다.

#Terminal
tensor([[1., 1.],
        [1., 1.],
        [1., 1.]])
tensor([[0., 0.],
        [0., 0.],
        [0., 0.]])
--------------------------
tensor([[1., 1., 1.],
        [1., 1., 1.]])
tensor([[0., 0., 0.],
        [0., 0., 0.]])

2.10 In-Place Operation

일반적인 연산의 결과는 다른 변수를 할당하여 저장하지 않으면 후에 결과값은 사라지게 된다. 하지만 In-Place Operation을 결과를 해당 변수에 Update한다. 쉽게 말해, a = a + 1 같은 느낌이다.

x = torch.FloatTensor([[0, 1, 2], [2, 1, 0]])
print(x.mul(2.)) #x값은 변하지 않음.
print(x)
print("---------------------")
print(x.mul_(2.)) #mul함수의 리턴값이 변수 x에 저장됨.
print(x)

출력은 다음과 같다.

tensor([[0., 2., 4.],
        [4., 2., 0.]])
tensor([[0., 1., 2.],
        [2., 1., 0.]])
---------------------
tensor([[0., 2., 4.],
        [4., 2., 0.]])
tensor([[0., 2., 4.],
        [4., 2., 0.]])
반응형
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.