본문 바로가기

그 땐 AI했지/그 땐 DeepLearning했지

[TAVE/study] ch02 파이토치 기초 | 02 텐서 조작하기①

728x90

참고자료: https://wikidocs.net/book/2788

벡터, 행렬 그리고 텐서

1️⃣백터, 행렬, 텐서 이해하기

  • 스칼라: 차원이 없는 값
  • 벡터: 1차원으로 구성된 값
  • 행렬(Matrix): 2차원으로 구성된 값
  • 텐서(Tensor): 3차원으로 구성된 값

👉🏻데이터 사이언스 분야 한정으로 3차원 이상의 텐서는 그냥 다차원 행렬 또는 배열로 간주할 수 있다. 또 벡터나 행렬을 그냥 1차원 텐서, 2차원 텐서라고 부르기도 한다.

 

2️⃣PyTorch Tensor Shape Convention

👉🏻다루고 있는 텐서의 크기를 고려하는게 중요한데 행렬과 텐서의 크기 표현 방식을 알아보자!

 

✅2D Tensor(2차원 텐서)

✅3D Tensor - 비전 분야에서의 3차원 텐서

👉🏻일반적인 자연어 처리보다 비전분야(이미지, 영상 처리)를 다루면 더 복잡한 텐서를 다룬다.

 

✅3D Tensor - NLP 분야에서의 3차원 텐서

👉🏻batch size, 문장 길이, 단어 벡터의 차원 이라는 3차원 텐서를 사용한다.

 

넘파이로 텐서 만들기(벡터와 행렬 만들기)
import numpy as np

👉🏻먼저 Numpy를 import해준다.

 

1️⃣1D with Numpy

t = np.array([0., 1., 2., 3., 4., 5., 6.])
#파이썬으로 설명하면 List를 생성해서 np.array로 1차원 array로 변환함.
print(t)

#[0. 1. 2. 3. 4. 5. 6.]

👉🏻Numpy로 1차원 텐서인 벡터를 만든다.

print('Rank of t: ', t.ndim)
print('Shape of t:', t.shape)

#Rank of t:  1
#Shape of t: (7,)

👉🏻1차원 텐서인 벡터의 차원과 크기를 출력한다.

  • .ndim: 몇 차원인지 출력한다. → 현재는 백터라 1차원(1차원 벡터, 2차원 행렬, 3차원 텐서)
  • .shape: 크기를 출력한다. (7,)은 (1, 7)을 의미한다. (1 x 7)

✅Numpy 기초

print('t[0] t[1] t[-1] = ', t[0], t[1], t[-1])
#t[0] t[1] t[-1] =  0.0 1.0 6.0

👉🏻인덱스로 원소에 접근할 수 있다.

print('t[2:5] t[4:-1]  = ', t[2:5], t[4:-1])
print('t[:2] t[3:]     = ', t[:2], t[3:])
#t[2:5] t[4:-1]  =  [2. 3. 4.] [4. 5.]
#t[:2] t[3:]     =  [0. 1.] [3. 4. 5. 6.]

👉🏻[시작 번호 : 끝 번호]로 범위를 지정해 원소를 가져올 수 있다.

 

2️⃣2D with Numpy

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)

👉🏻차원과 모양은 다음과 같다.

 

파이토치 텐서 선언하기
import torch

👉🏻먼저 torch를 import해준다.

 

1️⃣1D with PyTorch

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

👉🏻1차원 텐서인 백터를 만들었다.

print(t.dim())
print(t.shape)
print(t.size())

#1
#torch.Size([7])
#torch.Size([7])

👉🏻차원과 크기를 출력했다. shape, size 둘 다 크기를 출력한다.

print(t[0], t[1], t[-1]) 
print(t[2:5], t[4:-1])    
print(t[:2], t[3:])

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

👉🏻같은 방법으로 원소에 접근할 수 있다.

 

2️⃣2D with PyTorch

t = torch.FloatTensor([[1., 2., 3.],
                       [4., 5., 6.],
                       [7., 8., 9.],
                       [10., 11., 12.]
                      ])
print(t)

#tensor([[ 1.,  2.,  3.],
#        [ 4.,  5.,  6.],
#        [ 7.,  8.,  9.],
#        [10., 11., 12.]])

👉🏻2차원 행렬

print(t.dim()) 
print(t.size())

#2
#torch.Size([4, 3])
print(t[:, 1])
print(t[:, 1].size())

#tensor([ 2.,  5.,  8., 11.])
#torch.Size([4])

👉🏻 첫번째 차원을 전체 선택한 상황에서 두번째 차원의 첫번째 것만 가져온다.

print(t[:, :-1]) 

#tensor([[ 1.,  2.],
#        [ 4.,  5.],
#        [ 7.,  8.],
#        [10., 11.]])

👉🏻첫번째 차원을 전체 선택한 상황에서 두번째 차원에서는 맨 마지막에서 첫번째를 제외하고 다 가져온다.

 

브로드캐스팅(Broadcasting)

👉🏻행렬의 덧셈과 뺄셈을 할 때는 두 행렬의 크기가 같아야 한다. 그리고 곱셈을 할 때는 A행렬의 마지막 차원과 B행렬의 첫번째 차원일 일치해야 한다. 하지만 딥러닝을 할 때 크기가 다른 행렬 또는 텐서의 사칙 연산이 필요한 순간이 온다. 이를 위해 파이토치에서는 자동으로 크기를 맞춰 연산을 수행하는 브로드캐스팅 기능을 제공한다.

 

1️⃣같은 크기일 때 연산

m1 = torch.FloatTensor([[3, 3]])
m2 = torch.FloatTensor([[2, 2]])
print(m1 + m2)

#tensor([[5., 5.]])

👉🏻둘 다 크기가 (1, 2)이므로 문제없이 덧셈이 가능하다.

 

2️⃣다른 크기일 때 연산

# Vector + scalar
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([3])
print(m1 + m2)

#tensor([[4., 5.]])

👉🏻m1의 크기는 (1, 2)이지만 m2의 크기는 (1, )이므로 파이토치가 m2를 (1, 2)로 아래와 같이 변경하여 연산을 수행한다.

  • [3] -> [3, 3]
# 2 x 1 Vector + 1 x 2 Vector
n1 = torch.FloatTensor([[1, 2]])
n2 = torch.FloatTensor([[3], [4]])
print(n1 + n2)

#tensor([[4., 5.],
#       [5., 6.]])

👉🏻n1은 (1, 2), n2는 (2, 1)이기 때문에 두 벡터의 크기를 (2, 2)로 변경하여 덧셈한다.

[1, 2]
==> [[1, 2],
     [1, 2]]
[3]
[4]
==> [[3, 3],
     [4, 4]]

👉🏻변경 과정

 

자주 사용하는 기능

1️⃣행렬 곱셈과 곱셈의 차이(Matrix Multiplication Vs. Multiplication)

  • .matmul: 행렬 곱셈
  • .mul: 원소 별 곱셈
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.]])

👉🏻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.]])

👉🏻서로 다른 크기의 행렬이 브로드캐스팅된 후에 element-wise 곱셈을 수행했다. 이는 * 또는 mul()을 통해 수행한다.

# 브로드캐스팅 과정
[1]
[2]
==> [[1, 1],
     [2, 2]]

 

2️⃣평균(Mean)

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

#tensor(1.5000)

👉🏻1차원 벡터의 평균을 구했다.

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

#tensor([[1., 2.],
#        [3., 4.]])
#tensor(2.5000)

👉🏻2차원 행렬의 평균을 구했다. 4개의 원소의 평균이 나온다.

print(t.mean(dim=0))
#tensor([2., 3.])

# 실제 연산 과정
t.mean(dim=0)은 입력에서 첫번째 차원을 제거한다.

[[1., 2.],
 [3., 4.]]

1과 3의 평균을 구하고, 2와 4의 평균을 구한다.
결과 ==> [2., 3.]

👉🏻차원을 인자로 주었다. dim=0이라는 것은 첫 번째 차원(행)을 의미한다. 인자로 dim을 준다면 해당 차원을 제거한다는 의미가 된다. 그러므로 열만 남겨서 평균을 구한다.

print(t.mean(dim=1))
tensor([1.5000, 3.5000])

# 실제 연산 결과는 (2 × 1)
[1. 5]
[3. 5]

👉🏻두번째 차원을 제거하면 서 열이 제거된 텐서가 되어야 한다.

 

3️⃣덧셈(Sum)

👉🏻평균과 연산방법, 인자가 의미하는 바가 똑같고 덧셈으로 바뀌었을 뿐이다.

t = torch.FloatTensor([[1, 2], [3, 4]])
print(t)
print(t.sum()) # 단순히 원소 전체의 덧셈을 수행
print(t.sum(dim=0)) # 행을 제거
print(t.sum(dim=1)) # 열을 제거
print(t.sum(dim=-1)) # 열을 제거

#tensor([[1., 2.],
#        [3., 4.]])
#tensor(10.)
#tensor([4., 6.])
#tensor([3., 7.])
#tensor([3., 7.])

 

4️⃣최대(Max)와 아그맥스(ArgMax)

👉🏻Max는 원소의 최대값을 리턴하고 ArgMax는 최대값을 가진 인덱스를 리턴한다.

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

#tensor([[1., 2.],
#        [3., 4.]])
#tensor(4.)

👉🏻최대값 4를 리턴한다.

print(t.max(dim=0))
(tensor([3., 4.]), tensor([1, 1]))

# [1, 1]의 의미
#[[1, 2],
# [3, 4]]
#첫번째 열에서 0번 인덱스는 1, 1번 인덱스는 3입니다.
#두번째 열에서 0번 인덱스는 2, 1번 인덱스는 4입니다.
#다시 말해 3과 4의 인덱스는 [1, 1]입니다.

👉🏻인자로 dim=0을 주어 첫번째 차원을 제거했다. [3, 4]뿐만 아니라 [1, 1]도 함께 리턴되는데 이는 max에 dim 인자를 주면 argmax도 함께 리턴하는 특징때문이다.

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

#Max:  tensor([3., 4.])
#Argmax:  tensor([1, 1])

👉🏻두 개를 함께 리턴받는 것이 아니라 max 또는 argmax만 리턴받고 싶다면 다음과 같이 리턴값에도 인덱스를 부여하면 된다.

728x90