etc 함수

In-place Operation

한국 말로는 덮어쓰기 연산입니다.

 

tc_1 = torch.FloatTensor([1., 2.])
tc_2 = torch.FloatTensor([3.])

우리는 tc_1과 tc_2 결과 값을, 새로운 변수 (tc_add, tc_mul)에 저장하여 사용하였지만...

tc_add = tc_1.add(tc_2)
tc_mul = tc_1.mul(tc_2)
print(tc_add)
print(tc_mul)
tensor([4., 5.])
tensor([3., 6.])

in-place 연산을 사용하면 새로운 변수를 지정하지 않아도 기존 변수에 값이 바뀝니다.

tc_1.add_(tc_2)
print(tc_1)
tensor([4., 5.])
tc_1.mul_(tc_2)
print(tc_1)
tensor([12., 15.])

결과값을 확인하면, 기존 변수 (tc_1, tc_2) 들이 결과 값에 따라 업데이트되는 것을 확인할 수 있습니다.

예로 들어, 선형 회귀 구현 시 w와 b의 값들을 업데이트할 때, in-place 연산을 써주게 된다면, 새로운 변수를 사용하는 것보다

속도도 빠르고 코드도 간편해진다는 장점이 있습니다.

 

Max

파이썬 함수와 마찬가지로, 해당 원소중 가장 큰 값을 추출하는 함수입니다.

tc_1d = torch.FloatTensor([1.,2.])
tc_2d = torch.FloatTensor([[1., 2.], [3., 4.]])
print(tc_2d)
tensor([[1., 2.],
        [3., 4.]])
print(tc_1d.max())
print(tc_2d.max())
print(tc_2d.max(dim=0)[0])
print(tc_2d.max(dim=1)[0])

첫 번째 : 전체 원소들 중 가장 높은 값 (1차원)

두 번째 : 전체 원소들 중 가장 높은 값 (2차원)

세 번째 :  dim=0 -> 세로획을 그어서 각각을 비교하여 높은 값을 추출

네 번째 :  dim=1 -> 가로 획을 그어서 각각을 비교하여 높은 값을 추출

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

예로 들어 소프트맥스 사용 시, 여러개중 하나를 추출할 때, max 함수 사용시 간단하게 코드 구현 가능

 

View (=reshape(np) )

넘파이에서 reshape와 같습니다. 원소를 유지하면서 텐서의 크기만 바꿔주는 기능입니다.

텐서를 배우는 이유이며 여러분들이 데이터를 다룰 때, 가장 헷갈리고 어려우며..

파이토치 내에서 가장 중요한 부분이라고 강조드립니다.

 

먼저 3차원 배열을 다음과 같이 tc로 선언하였습니다.

tc = torch.FloatTensor([[[1., 2.], [3., 4.]], [[1., 2.], [3., 4.]]])
print(tc)
print(tc.shape)
tensor([[[1., 2.],
         [3., 4.]],

        [[1., 2.],
         [3., 4.]]])
torch.Size([2, 2, 2])
tc = tc.view([4, 2])
print(tc)
print(tc.shape)

우리는 view 함수를 통하여 [2,2,2]의 3차원 텐서를 [4,2]의 2차원 텐서로 바꿔 달라는 의미입니다.

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

사실 예제 데이터는 그리 많지는 않지만, 데이터 양이 많아지고 크기가 커지면 일일이 원하는 크기로 바꾸기는 힘듭니다.

예로 들어, 100만 개 이상의 3차원 텐서를 2차원으로 바꿀 때, 2개의 원소만 갖도록 바꾸려 합니다.

그러면, tc.view([데이터 개수/2, 2]) 이런 식으로 지정을 해주어야 할 것입니다.

그러면 불필요한 계산을 한 번 더 해주어야 하는데... 이럴 땐, (데이터 개수/2 ) 대신에 -1이라는 값을 두면 파이토치가 알아서 계산을 해줍니다.

tc = tc.view([-1, 2])
print(tc)
print(tc.shape)
tensor([[1., 2.],
        [3., 4.],
        [1., 2.],
        [3., 4.]])
torch.Size([4, 2])

이런 식으로, -1을 사용해주게 된다면, view 함수를 통해서 쉽게 배열의 구조를 바꿔줄 수 있습니다.

다음은 2개의 리스트로 변환해주되 각 리스트 원소는 몇 개인지는 파이토치가 대신 계산해달라는 코드입니다.

tc = tc.view([2, -1])
print(tc)
print(tc.shape)
tensor([[1., 2., 3., 4.],
        [1., 2., 3., 4.]])
torch.Size([2, 4])

다음 코드는, 3차원 내에서 구조만 바꿔주는 예시 코드입니다.

기존 2,2,2 size를 갖는 tc 함수에서 (?, 1, 2)의 구조로 바꿔달라는 코드입니다.

tc = tc.view([-1, 1, 2])
print(tc)
print(tc.shape)
tensor([[[1., 2.]],

        [[3., 4.]],

        [[1., 2.]],

        [[3., 4.]]])
torch.Size([4, 1, 2])

위와 같이 기존 2,2,2 size를 갖는 tc 함수에서 ( 1,?, 2)의 구조로 바꿔달라는 코드입니다.

tc = tc.view([1, -1, 2])
print(tc)
print(tc.shape)
tensor([[[1., 2.],
         [3., 4.],
         [1., 2.],
         [3., 4.]]])
torch.Size([1, 4, 2])

 

Squeeze

쥐어 짜다라의 의미를 갖고 있는 squeeze는 1인 차원을 축소시키는 함수입니다.

우리는 먼저 (3,1)의 size를 갖는 tc 변수를 만들어 줍니다.

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

그리고, squeeze 함수를 통해서, 1인 차원을 제거해보도록 하겠습니다.

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

 

다음과 같이 (3,1)의 size에서 (3)의 사이즈로 바뀐 것을 확인할 수 있습니다.

 

 Unsqueeze

squeeze는 1인 차원을 축소하였고, 반대로 unsqueeze는 특정 위치에 1인 차원을 추가해주는 함수입니다.

먼저 (3)의 size를 갖는 변수 tc를 선언을 해주었습니다.

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

우리는 이러한 원소에 1인 차원을 추가해줄 것이기 때문에, 결과적으로 (1,3) 또는 (3,1)의 형태로 바꿔줄 수 있습니다.

(1,3)을 만들어 주기 위해서는 0번째 위치에 1을 추가를 해주는 것입니다.

tc_unsqueeze = tc.unsqueeze(0)
print(tc_unsqueeze)
print(tc_unsqueeze.shape)
tensor([[1., 2., 3.]])
torch.Size([1, 3])

그럼 다음과 같이 (1,3)의 형태를 갖는 텐서를 만들었습니다.

 

똑같이, (3,1)을 만들어 주기 위해서는 1번째 위치에 1을 추가를 해줍니다.

tc_unsqueeze = tc.unsqueeze(1)
print(tc_unsqueeze)
print(tc_unsqueeze.shape)
tensor([[1.],
        [2.],
        [3.]])
torch.Size([3, 1])

그럼 다음과 같이 (3,1)의 형태를 갖는 텐서를 만들게 됩니다.

* unsqueeze의 함수를 굳이 사용 안 하시고 view함수로도 1의 차원을 추가해줄 수 있습니다.

 

Concate

concate 함수는 두 개 이상의 텐서를 연결해줄 때 사용합니다.

tc1 = torch.FloatTensor([[1.,2.], [3.,4.]])
tc2 = torch.FloatTensor([[5.,6.], [7.,8.]])
tensor([[1., 2.],
        [3., 4.]])
tensor([[5., 6.],
        [7., 8.]])
tc_cat_0 = torch.cat([tc1, tc2], dim = 0)
tc_cat_1 = torch.cat([tc1, tc2], dim = 1)

첫 번째 :  dim=0 -> 차원을 늘려서 연결한다.

두 번째 :  dim=1 -> 차원을 유지한 체 원소들만 연결한다.

 

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

딥러닝에서, 모델 입력이나 중간 연산을 할 때, 자주 사용됩니다. 예로 들어 x_1, x_2, x_3 변수를 갖고 있는데 수식의 x는 하나로만 세팅이 되어있으시다면, concate 함수를 통해서 하나의 변수로 사용할 수 있습니다.

Stacking

concate의 또 다른 방법으로써, 데이터를 연결을 해주는데, 쌓는 개념이라 보시면 됩니다.

tc1 = torch.FloatTensor([1.,2.])
tc2 = torch.FloatTensor([2.,3.])
tc3 = torch.FloatTensor([4.,5.])
tc_stack_0 = torch.stack([tc1, tc2, tc3], dim = 0)
tc_stack_1 = torch.stack([tc1, tc2, tc3], dim = 1)

첫 번째 :  dim=0 -> 차원을 늘려서 쌓는다.

두 번째 :  dim=1 -> 차원을 유지한 체 원소들만 쌓는다.

print(tc_stack_0)
print(tc_stack_1)
tensor([[1., 2.],
        [2., 3.],
        [4., 5.]])
tensor([[1., 2., 4.],
        [2., 3., 5.]])

 

Like

넘파이에서도 마찬가지로, 0 행렬(텐서)이나 1 행렬(텐서)을 만들 때, 우리는 다음과 같이 만들 수 있습니다.

tc_zero = torch.zeros(2,2)
tc_one = torch.ones(2,2)
print(tc_zero)
print(tc_one)
tensor([[0., 0.],
        [0., 0.]])
tensor([[1., 1.],
        [1., 1.]])

그러나, 특정 텐서 값에 크기에 맞는 0 행렬(텐서)이나 1 행렬(텐서)을 만들고 싶을 때는 Like 함수를 사용하면 됩니다.

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

다음과 같이 tc1이라는 텐서가 있고 tc1 형태를 갖고 있는 0 행렬(텐서)과 1 행렬(텐서)을 만들어 보겠습니다.

tc_one_ = torch.ones_like(tc1)
tc_zero_ = torch.zeros_like(tc1)
print(tc_zero_)
print(tc_one_)
tensor([[0., 0.],
        [0., 0.]])
tensor([[1., 1.],
        [1., 1.]])

다음과 같이, 0 텐서와 1 텐서를 간단하게 만들었습니다. 복잡한 연산을 만들었을 경우, 이에 맞는 0텐서 또는 1텐서가 사용되어지는 경우가 생길 수 있습니다. 똑같은 크기의 데이터가 들어온다면 미리 0텐서와 1텐서를 만들면 되지만, 연산에 따라 크기가 변경되는 수식에서는 사용 할 수가 없을 것입니다. 이 때, like 함수를 사용하면 수식에 맞추어 0텐서 또는 1텐서를 만들어 줄 수 있습니다.

반응형

+ Recent posts