파이토치

[파이토치] 텐서 이해하기

mhiiii 2024. 11. 7. 14:24
728x90

벡터, 행렬, 텐서


 

스칼라 : 차원이 없는 값, 숫자가 1개

벡터 : 1차원으로 구성된 값, 숫자가 여러개 나열 됨 (그림에서 1d-tensor)

행렬 : 2차원으로 구성된 값 (2d-tensor)

텐서 : 3차원 이상부터 텐서라고 부르지만 벡터를 1차원 텐서, 행렬을 2차원 텐서로 부름 

 

2D Tensor

딥러닝에서는 2차원 텐서를 batch size * dimension으로 표현함

즉, 행렬에서 행의 크기가 batch size, 열의 크기가 dim.

 

더보기

데이터 하나의 크기가 256이라면, 벡터 하나에 숫자가 256개 존재한다는 의미

 

만약 훈련 데이터가 총 3000개라면, 전체 훈련 데이터의 개수는 3000 * 256개

batch size = 64이면, 컴퓨터는 한번에 64 * 256개의 데이터를 처리

그럼 컴퓨터는 총 47번(3000/64 = 46.875)의 처리를 하게 됨

 

3D Tensor

이미지나 영상 처리에서는 가로, 세로를 가진 이미지를 가지기 때문에, 위와 같은 3차원 텐서가 됨

 

자연어 처리 분야에서는 (batch size, 문장 길이, 단어 벡터의 차원)의 3차원 텐서를 사용

 

1D with PyTorch

파이토치로 1차원 텐서 벡터 t 생성

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

 

print(t.dim())  # rank. 즉, 차원
print(t.shape)  # shape
print(t.size()) # shape

아래와 같은 결과가 나옴

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

 

2D with PyTorch

t = torch.FloatTensor([[1., 2., 3.],
                       [4., 5., 6.],
                       [7., 8., 9.],
                       [10., 11., 12.]
                      ])
print(t.dim())  # rank. 즉, 차원
print(t.size()) # shape
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)

행렬의 덧셈과 뺄셈에서는 두 행렬의 크기가 같아야 함

행렬의 곱셈에서는 한 행렬의 마지막 차원과 다른 행렬의 첫번째 차원이 일치해야 함

 

그러나, 파이토치에서는 자동으로 크기를 맞춰서 연산을 수행하게 만드는 브로드캐스팅 기능이 존재

 

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


tensor([[4., 5.]])
# 2 x 1 Vector + 1 x 2 Vector
m1 = torch.FloatTensor([[1, 2]])
m2 = torch.FloatTensor([[3], [4]]) # [3] -> [3,3] , [4] -> [4, 4]
print(m1 + m2)


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

 

브로드캐스팅은 자동으로 수행되므로 사용자는 나중에 원하는 결과가 나오지 않았더라도 어디서 문제가 발생했는지 찾기가 굉장히 어려움

 

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

행렬로 곱셈을 하는 방법은 크게 두 가지

바로 행렬 곱셈(.matmul)과 원소 별 곱셈(.mul)

 

원소 별 곱셈 (element-wise 곱셈)

더보기

동일한 크기의 행렬이 동일한 위치에 있는 원소끼리 곱하는 것

 

아래는 서로 다른 크기의 행렬이 브로드캐스팅이 된 후에 element-wise 곱셈이 수행되는 예시

*이나 mul()을 이용해 사용

m1 = torch.FloatTensor([[1, 2], [3, 4]])
m2 = torch.FloatTensor([[1], [2]]) # [1] -> [1, 1] , [2] -> [2, 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.]])

 

평균 구하기

 

1차원 평균

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

 

2차원 평균

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

4개의 원소의 평균인 2.5가 나옴

 

2차원 평균을 구할 때, 차원을 인자로 주는 경우

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

dim=0이라는 것은 첫번째 차원을 의미

dim을 인자로 준다는 것은 해당 차원(행)을 제거한다는 것

다시 말해 '열'만 남기겠다는 것 -> (1,2)의 행렬 크기를 가지게 됨 

 

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

각 행(row)에 대해 열(column) 방향으로 평균을 계산

열의 차원이 제거되어야 하므로 (2, 2)의 크기에서 (2, 1)의 크기가 됨 

하지만 (2 × 1)은 결국 1차원이므로 (1 × 2)와 같이 표현되면서 위와 같이 [1.5, 3.5]로 출력

 

덧셈

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

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

 

View

원소의 수를 유지하면서 텐서의 크기 변경

파이토치 텐서의 뷰(View)는 넘파이에서의 리쉐이프(Reshape)와 같은 역할

 

t = np.array([[[0, 1, 2],
               [3, 4, 5]],
              [[6, 7, 8],
               [9, 10, 11]]])
ft = torch.FloatTensor(t)
print(ft.shape) # torch.Size([2, 2, 3])

1) 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을 가지도록 하라는 의미

내부적으로 크기 변환은 다음과 같이 이루어 짐. (2, 2, 3) -> (2 × 2, 3) -> (4, 3)

 

더보기

view의 규칙

  • view는 기본적으로 변경 전과 변경 후의 텐서 안의 원소의 개수가 유지되어야 합니다.
  • 파이토치의 view는 사이즈가 -1로 설정되면 다른 차원으로부터 해당 값을 유추합니다.

2) 3차원 텐서의 크기 변경

3차원 텐서에서 3차원 텐서로 차원은 유지하되, 크기(shape)를 바꾸는 작업

 

(2 × 2 × 3) 텐서를 (? × 1 × 3) 텐서로 변경하라고 하면 ?는 몇 차원?

?는 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])

 

Squeeze

스퀴즈는 차원이 1인 경우에는 해당 차원을 제거

ft = torch.FloatTensor([[0], [1], [2]])
print(ft)
print(ft.shape)
tensor([[0.],
        [1.],
        [2.]])
torch.Size([3, 1])
print(ft.squeeze())
print(ft.squeeze().shape)
tensor([0., 1., 2.])
torch.Size([3])

 1이었던 두번째 차원이 제거되면서 (3,)의 크기를 가지는 텐서로 변경되어 1차원 벡터가 된 것

 

Unsqueeze

특정 위치에 1인 차원을 추가

ft = torch.Tensor([0, 1, 2])
print(ft.shape)
torch.Size([3])

현재는 차원이 1개인 1차원 벡터

 

첫번째 차원에 1인 차원을 추가

print(ft.unsqueeze(0)) # 인덱스가 0부터 시작하므로 0은 첫번째 차원을 의미한다.
print(ft.unsqueeze(0).shape)
tensor([[0., 1., 2.]])
torch.Size([1, 3])

 

Concat

두 텐서를 연결하는 방법

 

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

tensor([[1., 2.],
        [3., 4.],
        [5., 6.],
        [7., 8.]])

dim=0을 인자로 했더니 두 개의 (2 × 2) 텐서가 (4 × 2) 텐서가 됨

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

tensor([[1., 2., 5., 6.],
        [3., 4., 7., 8.]])

dim=1을 인자로 했더니 두 개의 (2 × 2) 텐서가 (2 × 4) 텐서가 됨

 

Stacking

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

print(torch.stack([x, y, z]))

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

위 stack 방법은 아래와 같음

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

x, y, z는 기존에는 전부 (2,)의 크기를 가짐

그런데 .unsqueeze(0)을 하므로서 3개의 벡터는 전부 (1, 2)의 크기의 2차원 텐서로 변경

 

위에서는 torch.stack([x, y, z])라는 한 번의 커맨드로 수행했지만,

연결(concatenate)로 이를 구현하려고 했더니 꽤 복잡해짐

 

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

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

 

ones_like와 zeros_like - 0으로 채워진 텐서와 1로 채워진 텐서

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

tensor([[0., 1., 2.],
        [2., 1., 0.]])
print(torch.ones_like(x)) # 입력 텐서와 크기를 동일하게 하면서 값을 1로 채우기

tensor([[1., 1., 1.],
        [1., 1., 1.]])

 

Reference

https://wikidocs.net/52460

728x90