본문 바로가기
Artificial Intelligence/PyTorch

텐서 조작하기(3)

by YUNZEE 2024. 1. 10.
728x90

1. 뷰(view) - 원소의 수를 유지하면서 텐서의 크기 변경

파이토치 텐서의 뷰(view)는 Reshape라는 이름에서 알 수 있듯이, 텐서의 크기를 변경해 주는 역할을 한다.

import numpy as np
import torch

ft라는 이름의 3차원 텐서를 만들었습니다. 크기(shape)를 확인해 보자.

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

#3차원 텐서를 만들었다. ft의 크기를 기억하자
print(ft.shape)

# torch.Size([2, 2, 3])

현재 위 텐서의 크기는(2,2,3)이다.

 

1) 3차원 텐서에서 2차원 텐서로 변경

view([-1, 3])이 가지는 의미는 -1은 첫 번째 차원은 사용자가 잘 모르겠으니 파이토치에 맡기겠다는 의미이고, 3은 두 번째 차원의 길이는 3을 갖도록 하라는 의미이다. 즉, 3차원 -> 2차원 텐서로하되  (?,3)의  크기로 변경하라는 의미이다. 

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

print(ft.view([-1,3]))
print(ft.view([-1,3]).shape)

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

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

정리

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

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

 

변경 전 텐서의 원소의 수는 (2 x 2 x 3) = 12/ 변경 후 텐서의 원소의 수(4x3) = 12

 

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

이번에는 3차원 텐서에서 3차원 텐서로 차원은 유지하되, 크기(shape)를 바꾸는 직업을 해보겠습니다. view로 텐서의 크기를 변경하더라도 원소의 수는 유지되어야 한다. 그렇다면 (2 x 2 x 3) 텐서를 (? x 1 x 3) = 12로 만족하는 숫자는 4입니다.

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

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

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

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

 

3) 스퀴즈(squeeze) - 1인 차원을 제거한다.

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

실습을 위해 임의로(3 x 1)의 크기를 가지는 2차원 텐서를 만든다.

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

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

해당 텐서는 (3x1)의 크기를 가진다. 두 번째 차원이 1이므로 squeeze를 사용하면(3,)의 크기를 가지는 텐서로 변경된다.

print(ft.squeeze())
print(ft.squeeze().shape)

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

위의 결과는 1이었던 두 번째 차원이 제거되면서(3,)의 크기를 가지는 텐서로 변경되어 1차원 벡터가 된 것을 보여준다.

 

4) 언스퀴즈(Unsqueeze) - 특정 위치에 1인 차원을 추가한다.

언스퀴즈는 스퀴즈의 정반대이다. 특정 위치에 1인 차원을 추가할 수 있다.

#(3,)의 크기를 가지는 1인 차원 텐서
ft = torch.Tensor([0,1,2])
print(ft.shape)

# torch.Size([3])

현재 차원이 1개인 1차원 벡터이다. 여기에 첫 번째 차원에 1인 차원을 추가해 보자. 첫 번째 차원의 인덱스를 의미하는 숫자 0을 인자로 넣으면 첫 번째 차원에 1인 차원이 추가된다.

print(ft.unsqueeze(0)) #0은 첫 번째 차원을 의미함
print(ft.unsqueeze(0).shape)

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

위 결과는(3,)의 크기를 가졌던 1차원 벡터가(1,3)의 2차원 텐서로 변경된 것을 보여준다. 방금 한 연산을 앞서 배운 view로도 구현 가능하다. 2차원으로 바꾸고 싶으면서 첫 번째 차원은 1이기를 원한다면 view에서 (1, -1)을 인자로 사용하면 된다.

#2차원으로 바꾸고 싶으면서 첫 번째 차원은 1이기를 원한다면 (1, -1)
print(ft.view(1, -1))
print(ft.view(1, -1).shape)

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

위의 결과는 unsqueeze와 view가 동일한 결과를 만든 것을 보여준다. 이번에는 unsqueeze의 인자로 1을 넣어보겠습니다. 인덱스는 0부터 시작하므로 이는 두 번째 차원에 1을 추가하겠다는 것을 의미한다. 현재 크기는(3,)이었으므로 두 번째 차원에 1인 차원을 추가하면(3,1)의 크기를 가지게 된다. 

여기서 unsqueeze의 인자로 -1을 넣어도 1을 대입한 값과 동일한 결과가 나온다.

#현재의 크기(3,) -> (3,1)로 변함
print(ft.unsqueeze(1))
print(ft.unsqueeze(1).shape)

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

- view(), squeeze(), unsqueeze()는 텐서의 원소 수를 그대로 유지하면서 모양과 차원을 조절한다.

 

5) 타입 캐스팅(Type Casting)

텐서에는 자료형이라는 것이 있다. 각 데이터형 벌로 정의되어 있는데, 예를 들어 32비트의 부동 소수점은 torch.FloatTensor를, 64비트의 부호 있는 정수는 torch.LongTensor를 사용합니다. GPU연산을 위한 자료형도 있습니다.

예를 들어 torch.cuda.FloatTensor가 그 예입니다.

 

그리고 이 자료형을 변환하는 것을 타입 캐스팅이라고 합니다.

#long타입의 It라는 텐서 선언
It = torch.LongTensor([1,2,3,4])
print(It,"\n")
# tensor([1, 2, 3, 4]) 

#텐서에다가.float()를 붙이면 바로 float형으로 타입이 변경됩니다.
print(It.float())
# tensor([1., 2., 3., 4.]

 

#Byte타입의 bt라는 텐서를 만들어보겠습니다.
bt=torch.ByteTensor([True, False, False, True])
print(bt)

# tensor([1, 0, 0, 1], dtype=torch.uint8)​
#여기에 .long()이라고하면 long타입의 텐서로 변경되고 .float()이라고 하면 float타입의 텐서로 변경된다.
print(bt.long())
print(bt.float())

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

 

6) 연결하기 concatenate

이번에는 두 텐서를 연결하는 방법에 대해서 알아보자.

우선(2x2) 크기의 텐서를 두 개 만든다.

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

이제 두 텐서를 torch.cat([ ])를 통해 연결해 보자. 그런데 연결 방법은 한 가지만 있는 것이 아니다. torch.cat은 어느 차원을 늘릴 것인지를 인자로 줄 수 있다. 예를 들어 dim=0은 첫 번째 차원을 늘리라는 의미를 담고 있다.

#dim=0을 인자로 했더니 두 개의(2x2) 텐서가 (4x2) 텐서가 된 것을 볼 수 있다.
print(torch.cat([x,y], dim=0),"\n")
# tensor([[1., 2.],
#         [3., 4.],
#         [5., 6.],
#         [7., 8.]]) 


#dim=1을 인자로 했더니 두 개의(2x2)텐서가 (2x4)텐서가 된 것을 볼 수 있다.
print(torch.cat([x,y], dim=1))
# tensor([[1., 2., 5., 6.],
#         [3., 4., 7., 8.]])

- 딥러닝에서는 주로 모델의 입력 또는 중간 연산에서 두 개의 텐서를 연결하는 경우가 많다. 두 텐서를 연결해서 입력으로 사용하는 것은 두 가지의 정보를 모두 사용한다는 의미를 가지고 있다.

 

7) 스택킹(Stacking)

6번에서 배운 연결을 하는 또 다른 방법으로 스택킹이 있다. 스택킹은 영어로 쌓는다는 의미입니다. 때로는 연결을 하는 것보다 스택킹이 더 편리할 때가 있다. 이는 스택킹이 많은 연산을 포함하고 있기 때문이다.

 

실습을 위해 크기가(2, )로 모두 동일한 3개의 벡터를 만든다.

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

#3개의 벡터를 torch.stack를 활용해서 쌓아주기(3x2)=>(3,2)
print(torch.stack([x,y,z]))

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

스택킹은 많은 연산을 한 번에 축약하고 있습니다. 예를 들어 위 작업은 아래의 코드와 동일한 작업을 합니다.

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

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

x, y, z는 기존에는 전부(2,)의 크기를 가졌습니다. 그런데. unsqueeze(0)을 하므로 3개의 벡터는 전부(1,2)의 크기의 2차원 텐서로 변경됩니다. 여기에 연결(concatenate)을 의미하는 cat을 사용하면(3x2) 텐서가 됩니다.

torch.stack([x, y, z])라는 한 번의 커맨드로 수행했지만, 연결(concatenate)로 이를 구현하려고 하면 꽤 복잡하다.

 

스택킹에 추가적으로 dim을 인자로 줄 수도 있다. 이번에는 dim=1 인자를 주면 두 번째 차원이 증가하도록 쌓으라는 의미로 해석할 수 있다.

#두 번째 차원이 증가하도록 스택킹이 된 결과를 보여줌, (2x3)텐서 확인
print(torch.stack([x,y,z], dim=1))

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

 

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

실습을 위해(2 x 3) 텐서를 만든다.

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

# tensor([[0., 1., 2.],
#         [2., 1., 0.]])
#위 텐서에one_like를 하면 동일한 크기(shape)지만 1으로만 값이 채워진 텐서를 생성
print(torch.ones_like(x),"\n") #입력 텐서와 크기를 동일하게 하면서 값을 1로 채우기

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

#위 텐서에 zeros_like를 하면 동일한 크기(shape)지만 0으로만 값이 채워진 텐서를 생성한다.
print(torch.zeros_like(x))
                                
# tensor([[0., 0., 0.],
#         [0., 0., 0.]])

 

9) In-place Operation(덮어쓰기 연산)

실습을 위해(2 x 2) 텐서를 만들고 x에 저장한다.

곱하기 연산을 한 값과 기존의 값을 출력해 보자.

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

print(x.mul(2.)) #곱하기 2를 수행한 결과를 출력
print(x) #기존의 값 출력

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

첫 번째 출력은 곱하기 2가 수행된 결과를 보여주고, 두 번째 출력은 기존의 값이 그대로 출력된 것을 확인할 수 있다. 곱하기 2를 수행하지만 이를 x에다가 다시 저장하지 않았으니, 곱하기 연산을 하더라도 기존의 갑 x는 변하지 않는 것이 당연하다.

#mul뒤에 _를 붙여보면 바뀌는 결과 확인
print(x.mul_(2.))  # 곱하기 2를 수행한 결과를 변수 x에 값을 저장하면서 결과를 출력
print(x) # 기존의 값 출력

# tensor([[2., 4.],
#         [6., 8.]])
# tensor([[2., 4.],
#         [6., 8.]])

연산 뒤에 _를 붙이면 기존의 값을 덮어쓰기 한다. 기존의 x값이 변경된 것을 확인할 수 있다.

728x90

'Artificial Intelligence > PyTorch' 카테고리의 다른 글

텐서 조작하기(2)  (10) 2024.01.03
텐서 조작하기  (2) 2023.12.29
데이터의 분리  (4) 2023.12.26
파이썬, 아나콘다, jupyter notebook 설치방법  (4) 2023.12.23