안녕하세요. 오랜만에 블로그 포스팅 올려보네요.

 

 

일하다가 torch.cdist라는 함수와 마주치게 되었는데, 마침 구글링을 해보니 한국어 자료가 0개여서 누군가에게 도움이 되고자 이렇게 포스팅을 만들게 되었습니다.

 

 

그럼 시작해보도록 하겠습니다.

 

 

torch.cdist의 공식 document의 주소는 다음과 같습니다.

 

 

https://pytorch.org/docs/stable/generated/torch.cdist.html

 

torch.cdist — PyTorch 1.12 documentation

Shortcuts

pytorch.org

 

해당 함수에 대해서 설명하는 내용을 읽어보면, 'Computes batched the p-norm distance between each pair of the two collections of row vectors'라고 적혀 있습니다.

 

 

쉽게 말씀드리자면, 두 개의 벡터에서 각 행의 pair끼리의 p-norm distance를 구한 것이라는 설명입니다.

 

 

물론 이렇게 말씀드리면 이해하기가 어려우실 수 있으니, 이번에도 예시를 통해서 설명드리도록 하겠습니다.

 

 

p-norm에 대한 설명은 다른 블로그들이나 자료들을 참고해서 이해해주시고 본 포스팅에서는 별도로 설명드리지 않습니다.

 

 

import torch
import numpy as np

 

먼저 예시를 만들기 위한 library 두개를 import 해줍니다. pytorch와 numpy가 필요합니다.

 

 

a = torch.tensor(np.arange(1, 7).reshape(1, 3, 2), dtype=torch.float64)
print(a)

b = torch.tensor(np.arange(0.1, 0.9, 0.1).reshape(1, 4, 2))
print(b)

예시를 위해서 1부터 6까지 구성되는 numpy array를 만들고, (1, 3, 2)로 reshape 한 뒤에 torch tensor로 변환합니다.

 

또, 0.1부터 0.8까지 구성되는 numpy array를 만들고, (1, 4, 2)로 reshape 한 뒤에 torch tensor로 변환합니다.

 

참고로 torch.cdist는 column의 size가 동일한 vector끼리만 연산이 가능합니다. 따라서, 두 vector는 2차원으로 만들어두었습니다.

 

위 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다.

 

 

 

print(torch.cdist(a, b))

 

 

앞에서 선언한 a와 b를 torch.cdist를 이용해서 연산해줍니다.

 

 

이를 이용하면 다음과 같은 결과를 볼 수 있습니다.

 

 

 

 

값들이 꽤나 복잡하죠? 이에 대한 설명을 드리도록 하겠습니다.

 

 

 

앞에서 정의했던 a와 b는 다음과 같은 vector입니다. shape는 각각 Bx3x2와 Bx4x2로 정의하였습니다. B는 Batch size이며, 현재 예시에서는 1로 적용하였습니다. 

 

 

torch.cdist에서는 a 벡터의 row와 b 벡터의 row를 각각 pair로 만들고, 이에 대한 distance를 계산하게 됩니다.

 

 

a 벡터의 첫 번째 row와 b 벡터의 첫 번째 row, a 벡터의 첫 번째 row와 b 벡터의 두 번째 row.... 이런식으로 해서 a 벡터의 세 번째 row와 b 벡터의 네 번째 row까지 모두 pair를 만들면 총 몇 개의 pair가 만들어질까요?

 

 

3x4=12개의 pair가 만들어집니다.

 

 

그래서 위에서 보여드린 document에서는 다음과 같은 내용을 담고 있습니다.

 

 

 

만약 x1이 P 개의 row를 가지고 있고, x2가 R 개의 row를 가지고 있으면, 총 P x R 개의 pair가 만들어지므로, 결과값의 shape가 P x R 이라는 얘기입니다.

 

 

다시 위 예시로 돌아가서, 직접 한 개씩 연산을 진행해보겠습니다.

 

 

 

먼저 a 벡터의 첫 번째 row와 b 벡터의 첫 번째 row를 pair로 만들고, 이 둘 간의 distance를 계산해줍니다.

 

 

이를 output 벡터의 (1, 1) 위치에 적어줍니다.

 

 

 

다음으로는 a 벡터의 첫 번째 row와 b 벡터의 두 번째 row를 pair로 만들고, 이 둘 간의 distance를 계산해줍니다.

 

 

이를 output 벡터의 (1, 2) 위치에 적어줍니다.

 

 

이러한 방식으로 진행하기 때문에, output 벡터의 column 개수는 b 벡터의 row와 같아지게 됩니다. 

 

 

왜냐하면 b 벡터의 row 개수 만큼 계산을 진행해서, 이를 가로로 펴는 방식이기 때문이지요.

 

 

다음으로 a 벡터의 두 번째 row와 pair를 지어볼까요?

 

 

 

 

아무래도 수식이 커지다보니 그림이 작아졌네요.

 

 

a 벡터의 두 번째 row와 b 벡터의 첫 번째 row가 pair가 되어, 계산 결과가 output의 (2, 1) 위치에 오게 됩니다.

 

 

즉, 이러한 방식으로 진행되므로 output 벡터의 row 개수는 a 벡터의 row 개수가 같아지게 됩니다.

 

 

앞에서 설명한 방식을 전부 실행하면 다음과 같은 결과를 볼 수 있습니다.

 

 

 

 

 

 

이번 포스팅에서는 간단하게 torch.cdist 함수가 어떻게 작동하는지 예시를 통해서 준비해보았습니다.

 

 

감사합니다.

 

 

 

 

 

cv2.error:OpenCV(3.4.2) /tmp/build/80754af9/opencv-suite_1535558553474/work/modules/core/src/arithm.cpp:223:error:(-209:sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and type), nor 'array op scalar' nor 'scalar op array' in function 'binary_op'

 

 

Python의 OpenCV(cv2)에서 cv2.bitwise_and()를 사용할 때 발생했던 에러입니다.

 

 

해당 오류가 발생하는 이유는 굉장히 다양해서, 해당 에러에 대한 해결방안을 적은 블로그 글에서 조차 다양한 해결방안을 제시하고 있습니다.

 

 

해당 에러에서의 핵심은 'where arrays have the same size and type'이라고 보여지며, 말 그대로 opencv 연산을 하고자 하는 array의 size와 type이 같아야 한다는 점입니다.

 

 

제 케이스에서는 size가 같았으나 에러가 나서, 변수의 .dtype을 찍어보니 하나는 float32였고, 다른 하나는 float64이였습니다.

 

 

이에 대응하고자 float64인 이미지를 .astype(np.float32)로 처리하여 float32로 맞춰주었더니 해결되었습니다.

 

 

 

 

안녕하세요.

 

 

오늘은 scipy.ndimage.label과 skimage.measure.regionprops를 이용해서 이미지 내에서 threshold 이상의 값을 가지는 위치를 찾는 작업을 해보려고 합니다.

 

 

구글링 해보니 한국어로 된 자료가 검색이 잘 안되어서, 작성해두면 많은 분들께 도움이 되지 않을까 싶어 작성하게 되었습니다.

 

예시 코드들을 첨부해보면서, 하나하나 같이 익혀보겠습니다.

 

그럼 시작해보겠습니다.

 

 

 

이미지 내에서 특정 값 이상을 가지는 위치를 어떻게 하면 찾을 수 있을까?

 

 

 

먼저, 필요한 library를 import 해줍니다.

 

import numpy as np
import scipy
from scipy.ndimage import label
from skimage.measure import regionprops

 

저는 이해를 돕기 위해, 임의로 numpy array를 생성하여 설명을 드리겠습니다.

 

원리만 이해한다면, 일반적으로는 제가 설명드리는 예시보다 훨씬 더 큰 이미지 내에서도 동일한 작업을 수행하실 수 있을 겁니다.

 

(혹시나 pytorch나 tensor flow 등 deep learning framework를 사용하신다면, 대부분 .numpy()를 통해 tensor 형태의 데이터를 numpy array로 전환하실 수 있습니다.)

 

이해를 돕기 위해, 간단하게 (6, 6) 짜리 numpy array를 생성해줍니다.

 

image = np.array([[1, 2, 3, 4, 4, 4],
               [1, 2, 6, 10, 9, 7],
                [2, 3, 11, 15, 16, 7],
               [1, 2, 7, 8, 9, 7],
               [6, 7, 8, 9, 10, 7],
               [4, 4, 4, 4, 4, 4]])

 

아까 import 했던 scipy.ndimage.label을 사용할 시간입니다.

 

제가 하고 싶은 작업은, 해당 image에서 6보다 큰 부위를 찾아내고 싶습니다.

 

따라서, 다음과 같은 코드를 작성해줍니다.

 

labeled, nr_objects = label(image > 6)
print(labeled)

 

여기서 labeled는 입력으로 준 image 중에서 6보다 큰 지점들에 label을 주게 됩니다.

 

print를 해보면 다음과 같이 나오게 됩니다.

 

 

이렇게 하게 되면, labeled 된 지점들만 추출해 위치를 잡을 수 있게 됩니다.

 

그리고 nr_objects의 경우는 1이 됩니다.

 

 

 

이걸 보시고 이런 생각을 하실 수 있을 것 같습니다.

 

만약 특정값보다 큰 지점들이 저렇게 다 연결되어 있는 것이 아니라, 조금씩 조금씩 모여있는 경우는 어떻게 될까?

 

그런 경우도 보여드리겠습니다.

 

먼저, 기존에 사용했던 image의 값을 다음과 같이 변경해줍니다.

 

image = np.array([[1, 2, 3, 7, 7, 7],
               [1, 2, 6, 10, 9, 7],
                [2, 3, 4, 4, 4, 4],
               [1, 2, 7, 8, 9, 7],
               [6, 7, 8, 9, 10, 7],
               [4, 4, 4, 4, 4, 4]])

 

이렇게 설정하고 나서, 다시 label을 찍어보면 다음과 같이 되는 것을 보실 수 있습니다.

 

 

이번에는 오른쪽 상단에 한 구역과 오른쪽 하단에 한 구역으로, 총 두 구역이 잡히게 됩니다.

 

이처럼, 특정 값보다 높은 지역이 모두 연결되는 것이 아니라 분리되어 있을 경우, label이 1, 2,... 등으로 매겨지면서 지점을 찾아주게 됩니다.

 

그리고 이렇게 두 구역이 잡히는 경우, nr_objects는 2가 됩니다. 

 

즉, nr_objects는 구역의 개수를 뜻한다는 것이죠.

 

 

 

자 그럼, 여기까지 진행하면 내가 가지고 있는 이미지에 대해서 특정 값보다 높은 값을 가지는 구역에 대해 labeling 작업이 된 것이죠?

 

다음으로는 그 구간의 좌표와 면적을 구해보겠습니다.

 

아까 구해둔 labeled라는 변수를 이용해서 skimage.measure.regionprops를 이용합니다.

 

# regionprops : Measure properties of labeled image regions.
props = regionprops(labeled)

 

props라는 변수로 regionprops 객체를 저장하였는데요. 

 

이는 다음과 같은 것을 포함합니다.

 

 

RegionProperties의 list이죠. 따라서 이는 type을 찍어보면 list가 됩니다.

 

다음으로는 이 RegionProperties의 property를 사용해서 여러 가지 정보를 얻어보겠습니다.

 

 

 

먼저, 구역이 1개인 기존 케이스에 대해서 적용해보겠습니다.

 

 

props가 list이므로, list 내부에 있는 값들에 대해서 적용하기 위해 for문을 적용해줍니다.

 

b.area는 labeled 된 구역의 pixel 개수를 구해줍니다. 

 

이전 사진에서 확인하실 수 있겠지만, 1로 labeled 된 pixel은 총 16개입니다.

 

 

 

b.bbox_area는 bounding box의 pixel 개수를 구해줍니다.

 

bounding box는 labeled 된 구역을 감싸는 box를 생성하게 되는 것으로, b.area보다는 크거나 같은 값이 나오게 됩니다.

 

 

 

b.image는 bounding box와 같은 사이즈를 가지고 있는 binary region image를 보여줍니다.

 

따라서, False와 True로 표시된 저 값이 실제 bounding box의 크기와 동일하고, bounding box 내에서 labeled 된 데이터와 unlabeled 데이터가 어떻게 구성되어 있는지 확인할 수 있습니다.

 

 

 

b.bbox의 경우는 Bounding box의 정보를 출력해주게 되며, (min_row, min_col, max_row, max_col)의 형태로 출력을 내줍니다. 

 

주의해야 할 사항은, bounding box에 속하는 pixel은 half-open interval [min_row; max_row)와 [min_col; max_col)에 속하게 됩니다.

 

그렇다면, 이 b.bbox를 어떻게 해석해볼 수 있을지 알아봐야겠죠?

 

기본적으로 col은 가로, row는 세로를 뜻하는 것을 기억해주셔야 하고,

 

index는 0부터 시작합니다.

 

 

이전에 labeled 된 데이터를 보여드렸었는데, 이걸 가지고 설명을 드리겠습니다.

 

빨간색row, 파란색col입니다.

 

방금 bounding box는 half-open interval에 속한다는 말씀을 드렸었는데, 먼저 row부터 보겠습니다.

 

row[min_row; max_row) 범위에 속하게 되는데, 해당 image 데이터 기준으로 보자면 bounding box를 그린다고 했을 때 세로 기준으로 1부터 4까지 그려지는 게 맞겠죠? 

 

근데, max_row는 open입니다. 아마 고등학교 수학에서 open(열린 구간)과 close(닫힌 구간)의 개념을 배우셨겠지만, 쉽게 얘기하면 max_row에는 등호가 없습니다.

 

따라서, bounding box 기준에서 봤을 때는 min_row는 1이고, max_row는 5입니다.

 

쉽게 부등호로 표현하자면 1 <=row <5라는 것입니다.

 

 

col도 보겠습니다.

 

마찬가지로 col도 [min_col; max_col) 범위에 속하게 됩니다.

 

해당 image 데이터 기준으로 bounding box는 1부터 5까지 그려지는 게 맞겠죠?

 

그렇다면 bounding box 기준에서 봤을 때 min_col은 1이고, max_col은 6이게 됩니다. 

 

 

 

그래서, 아까 보여드렸던 이 그림에서 b.bbox의 값들이 이렇게 나온 것입니다.

 

 

(1, 1, 5, 6)인 것을 확인하실 수 있죠.

 

 

다음으로는 이 값들을 이용해서 bounding box의 xy starting point와 width, height를 계산해보겠습니다.

 

bounding box를 그리려면 starting point와 width, height가 필요한데, 먼저 b.bbox [1]이 x 좌표의 시작점이 됩니다.

 

그리고 b.bbox [0]이 y 좌표의 시작점이 되지요.

 

 

width는 가로를 의미하므로, max_col과 min_col을 뺀 b.bbox [3] - b.bbox [1]이 되고, 

 

height는 세로를 의미하므로, max_row와 min_row를 뺀 b.bbox [2] - b.bbox [0]이 됩니다.

 

 

이 정보를 이용해서 원하시는 곳에 bounding box를 그리시면 됩니다...!

 

 

아까 label 쪽을 설명하면서 특정값 이상인 구역이 여러 개인 경우에 대해서도 말씀을 드렸는데, for b in props: 코드를 진행하시게 되면 이렇게 여러 개인 경우 bounding box가 여러 개 나오게 됩니다.

 

이를 이용해서 여러 개의 bounding box를 그리시면 되겠습니다.

 

원리는 당연히 동일하므로, 이 부분에 대해서는 별도로 제가 예시를 들어 설명을 해드리지 않아도 될 것 같습니다.

 

 

 

여기까지 해서 scipy.ndimage.label과 skimage.measure.regionprops를 활용해 특정값보다 큰 값을 가지는 이미지의 위치를 잡아내는 방법을 소개해보았습니다.

 

분명 다양한 이미지 task에 사용할 수 있는 방법이라고 생각이 되고, 유용한 자료가 되었으면 좋겠습니다.

 

감사합니다.

 

 

 

 

 

 

 

 

 

 

안녕하세요. 오늘은 torch.nn.ConvTranspose2d 라고 하는 모듈에 대해서 알아보도록 하겠습니다.

 

 

용어로는 Deconvolution이나 fractionally-strided convolution이라고 불리는 작업을 수행합니다.

 

 

해당 작업이 무엇인지를 자세하게 다룬 자료들은 이미 너무 많이 있어 생략하고, 실제로 연산되는 결과를 위주로 살펴보겠습니다.

 

 

이 모듈은 Deep Convolutional GAN을 구현할 때 주로 사용됩니다.

 

 

그럼 시작해보겠습니다.

 

 

 

분석 코드

 

 

import torch
import torch.nn as nn

# sample values.
test_input = torch.Tensor([[[[1, 2, 3], [4, 5, 6]]]])
print("input size: ", test_input.shape)
print("test input: ", test_input)

 

먼저, pytorch를 이용해서 실험해 볼 것이기 때문에 모듈을 불러오고요.

 

input으로 들어갈 tensor를 만들어줍니다.

 

사이즈가 작으면서도 직관으로 이해해보기 쉽도록 다음과 같이 만들었습니다.

 

굳이 4차원으로 만드는 이유는, pytorch에서는 기본적으로 4차원 input만을 지원하기 때문입니다.

 

따라서, 실제로 값이 있는 것은 1부터 6까지로 해서 2 x 3 형태이지만, shape를 맞추기 위해 4차원으로 구성되었다고 보시면 될 것 같습니다.

 

# sample model. It has nn.ConvTranspose2d(1, 3, 4, 1, 0, bias = False)
# First parameter = Channels of input (=1)
# Second parameter = Channels of output (=3)
# Third parameter = Kernel size (=4)
# Fourth parameter = stride (=1)
# fifth parameter = padding (=0)
class sample(nn.Module):
  def __init__(self):
    super(sample, self).__init__()
    self.main = nn.ConvTranspose2d(1, 3, 4, 1, 0, bias = False)

  def forward(self, input):
    return self.main(input)

 

다음으로는 우리가 오늘 살펴보려고 하는 메인인 nn.ConvTranspose2d를 가지고 있는 모델을 하나 만들어줍니다.

 

위에 주석에 달려있듯이, 첫 번째 파라미터는 input의 채널수이고 두 번째 파라미터는 output의 채널 수이며

 

세 번째 파라미터는 kernel size, 네 번째 파라미터는 stride, 다섯 번째 파라미터는 padding입니다.

 

계산을 조금 더 간단하게 하기 위해 bias= False로 놓고 실험해보겠습니다.

 

별도의 신경망 연산이 필요 없으므로, 매우 간단하게 layer 딱 1개만 있는 모델을 만들었습니다.

 

 

 

Model = sample()

# Print model's original parameters.
for name, param in Model.state_dict().items():
  print("name: ", name)
  print("Param: ", param)
  print("Param shape: ", param.shape)

 

만들어진 모델의 기본 weight를 살펴보겠습니다.

 

pytorch가 기본적으로 지원하는 초기화 방법에 따라, weight 값이 결정된 것을 확인할 수 있습니다.

 

그리고 shape는 [1, 3, 4, 4]를 가지게 됩니다. 

 

이는 kernel size를 4로 지정했기 때문에, filter의 사이즈가 4x4 형태가 되어서 그렇고 output의 channel을 3으로 지정하였으므로 결괏값이 3개의 채널로 나와야 해서 3이 생겼다고 보시면 되겠습니다.

 

쉽게 해당 convolution 필터를 가로가 4, 세로가 3, 높이가 4인 직육면체로 생각하시면 편합니다.

 

그림으로 표현해보자면 다음과 같은 것이죠.

 

다음으로는 결과를 조금 더 쉽게 보기 위해서, model의 파라미터를 manually 하게 바꿔보겠습니다.

 

지금은 weight가 소수점으로 되어 있고 매우 복잡하기 때문에, 결과를 직관적으로 이해해볼 수 있기 위한 작업입니다.

 

# I makes 48 values from 0.1 to 4.8 and make (1, 3, 4, 4) shape
np_sam = np.linspace(0.1, 4.8, num = 48)
np_sam_torch = torch.Tensor(np_sam)
sam_tor = np_sam_torch.view(1, 3, 4, 4)


# Modify model's parameters using 4 for loops.
with torch.no_grad():
    batch, channel, width, height = Model.main.weight.shape
    
    for b in range(batch):
        for c in range(channel):
            for w in range(width):
                for h in range(height):
                    Model.main.weight[b][c][w][h] = sam_tor[b][c][w][h]

# Check parameter modification.
print("Model weight: ", Model.main.weight)

 

먼저, np_sam이라는 변수로 np.linspace를 이용해 0.1부터 4.8까지 48개의 값을 만들어줍니다. 이렇게 하면 0.1, 0.2, 0.3 .... 4.8까지 0.1을 간격으로 하는 숫자 48개를 만들 수 있습니다.

 

그리고 이를 torch.Tensor로 바꿔주고, shape를 맞추기 위해 (1, 3, 4, 4)의 형태로 맞춰줍니다.

 

다음으로는 model의 파라미터를 수동으로 바꿔주는 코드입니다.

 

먼저 batch, channel, width, height라는 변수로 model의 weight의 shape를 받습니다.

 

그리고 이를 4중 for문을 이용해서 바꿔줍니다.

 

Model.main.weight[b][c][w][h]의 값을 해당하는 인덱스와 동일한 sam_tor의 위치의 값으로 바꾸는 것이죠.

 

물론 일반적으로 computational cost 때문에 4중 for문을 사용하는 것은 비효율적이긴 하지만, 어쨌든 이번 상황에서는 각 차원별로 값이 작기 때문에 연산 자체가 오래 걸리진 않아 이렇게 코드를 짜보았습니다.

 

그리고 마지막으로는 model의 weight들을 print 해봅니다. 실제로 우리가 원하는 대로 잘 바뀌었는지 봐야겠죠.

 

역시나 원하는 대로 잘 바뀌었네요. 

 

이렇게 값들이 simple해야 우리가 결과를 찍었을 때 해석도 쉬울 것입니다.

 

이것도 아까와 마찬가지로 shape가 (1, 3, 4, 4)가 될 텐데요, 이 또한 직육면체 형태로 이해해 볼 수 있습니다.

 

모든 원소의 값이 다 보이시도록 최대한 길게 그렸는데요. 이처럼 가로가 4, 세로가 3, 높이가 4를 만족하는 직육면체를 생각하시면 됩니다.

 

 

result = Model(test_input)

print("Result shape: ", result.shape)
print("Result: ", result)

다음으로는, 아까 만들어뒀던 (1, 1, 2, 3) 짜리 input을 위에서 보여드린 weight을 이용해서 nn.ConvTranspose2d를 사용해 연산한 결과를 출력합니다.

 

결과가 어떻게 나오는지 보겠습니다.

 

 

뭔가 어떤 결과가 나왔는데, 이것만 봐서는 아직 어떤 식으로 연산이 이루어지는지 알 수 없겠죠?

 

그래서 제가 하나하나 한번 뜯어서 분석해보았습니다.

 

 

 

실제 구동 방법 분석

 

 

이해를 조금 쉽게 하기 위해서, 파라미터와 결과 모두 channel을 1차원으로 가정하고 살펴보겠습니다.

 

먼저, 우리의 input은 다음과 같은 2 x 3 형태의 tensor가 됩니다.

 

이를 우리가 설정한 parameter를 이용해서 단순히 곱해줍니다.

 

따라서 다음과 같은 결과를 볼 수 있습니다.

 

 

다음으로는 다른 input 요소들도 어떻게 계산되는지 보겠습니다.

 

 

 

 

 

 

 

자 그러면 input 1부터 6까지 연산을 완료했다면, 결과가 총 6개가 나오게 되겠죠?

 

나온 6개의 결과를 element-wise addition을 통해서 최종 결과를 뽑아냅니다.

 

 

이것이 아까 프로그램을 돌려서 나온 결과와 일치함을 확인할 수 있습니다.

 

 

 

 

따라서, nn.ConvTranspose2d의 연산 과정을 다음과 같이 정리해볼 수 있습니다.

 

 

1. nn.ConvTranspose2d를 만들 때 input값의 channel 수와 설정한 output channel 수, kernel size를 고려하여 weight를 만들어낸다.

 

우리의 경우에는 output channel을 3으로 지정하였고, kernel size를 4로 지정하였으므로 

 

weight의 크기가 (1, 3, 4, 4)가 됩니다. 

 

만약에 input의 channel 수가 3이고, output channel 수가 7이며 kernel size가 4였다면

 

weight의 크기가 (3, 7, 4, 4)가 될 것입니다.

 

즉, weight의 크기는 (input channel, output channel, kernel size, kernel size)가 됩니다.

 

 

 

2. input의 각 element 별로 weight와 곱해줍니다. 만약 stride가 1보다 크다면, 그 값만큼 이동하면서 만들어줍니다.

 

위에서의 예시는 stride 1인 경우를 봤었죠. input의 각 element 별로 weight와 곱해준 결과를 stride 1로 이동하면서 만들어냈습니다.

 

 

 

 

3. 나온 모든 결괏값을 element-wise 하게 더해서 최종 결과를 냅니다.

 

stride가 kernel size보다 작게 되면 서로 겹치는 부분들이 존재합니다. 그럴 때는 단순히 element-wise하게 더해서 결과를 내주면 됩니다.

 

 

 

마지막으로, batch size가 1보다 클 때의 결과도 한번 정리해보면서 마무리 짓겠습니다.

 

만약 input이 (4, 3, 4, 4)의 사이즈를 가지고 있고, 모델이 nn.ConvTranspose2d(3, 7, 4, 1, 0, biase = False)인 layer를 가지고 있다고 가정한다면 어떻게 될까요?

 

먼저 parameter의 값들부터 확인해보겠습니다.

 

input channel이 3이고, output channel이 7이며, kernel size는 4이고, stride는 1이며 padding은 0인 경우입니다.

 

 

그렇다면 weight는 shape가 어떻게 될까요?

 

 

위에서 얘기한 내용을 적용해보면, (3, 7, 4, 4)가 됨을 알 수 있습니다.

 

 

마지막으로 output의 결과는 어떻게 될까요?

 

 

batch size는 그대로 4가 될 것이고, output channel을 7로 정했으니 channel 수는 7이 되겠죠?

 

 

그리고 stride = 1인 상황이고 padding = 0이니까, input 각각에 대해서 kernel size 4 만큼 확장이 되므로

 

 

이것이 1칸씩 이동하는데 input 사이즈가 4개니까, 총 7이 됩니다. (input이 1개면 4, 2개면 5, 3개면 6, 4개면 7이 되는 것이죠. stride = 1이므로 하나씩 이동한다고 보시면 될 것 같아요.)

 

따라서 output의 결과는 (4, 7, 7, 7)이 됩니다.

 

 

 

지금까지 nn.ConvTranspose2d가 어떻게 작동하는지에 대해서 세세하게 알아봤습니다.

 

예시에서는 조금 더 와닿게 만들기 위해서 batch size가 1일 때를 가정하고 설명하였으나,

 

사실상 batch size는 input와 output에서 변동되는 요소가 아니므로 크게 신경 쓰지 않아도 되는 부분이라고 생각합니다.

 

 

혹시라도 보시면서 더 궁금하신 점이나 이해가 안 되시는 점이 있다면 댓글로 추가적으로 설명을 드릴 수 있을 것 같습니다.

 

 

이번 포스팅은 여기서 마치겠습니다.

 

 

 

 

 

Intro

 

코드를 보다가, A를 거친 점수와 B를 거친 점수를 가지고 순위를 매기는 과정이 있었다.

 

 

A를 거친 점수를 기준으로 오름차순을 한 뒤, 다시 그 결과에 대해 B를 거친 점수를 기준으로 오름차순을 진행해 순위를 매기는 것이었다.

 

 

이렇게 여러 번의 정렬을 수행해야 될 때 어떻게 하는지 알아보도록 하자.

 

 

 

 

 

 

Python에서의 다중 정렬(sorted 이용)

 

 

1. 데이터의 순위를 index로 반환하도록 만드는 경우

 

# 점수를 scores라는 list로 생성
scores = [[1, 2], [6, 5], [5, 4], [5, 8], [7, 1], [10, 5]]

rank = sorted(range(len(scores)), key = lambda x: (scores[x][0], scores[x][1]), reverse = True)
print(rank)

 

먼저 점수를 scores라는 list로 만들어줍니다. 

 

 

[5, 4]와 [5, 8]을 넣어 다중 정렬이 시행되는지를 확인하고자 하였습니다.

 

 

최종 순위를 매길 때는, sorted를 사용해주고, 이때 첫 번째 인자는 반드시 iterable 한 object가 들어가야 합니다.

 

 

안 그러면 TypeError가 발생하니 주의해야 합니다.

 

 

range(len(scores)) 대신, 6을 넣으면 해당 오류를 직접 확인하실 수 있습니다.

 

 

 

 

key라고 하는 인자는, "어떤 것을 기준으로 정렬할까?"를 결정하는 역할을 합니다. 이때, 기준이 여러 개이면 첫 번째부터 순서대로 기준을 적용해줍니다.

 

 

그리고 key는 첫 번째 인자를 input으로 받아서 작동합니다.

 

 

위의 예시에서는, 먼저 range(len(scores))에서 첫 번째로 나오는 0부터 input으로 받게 되며 scores[0][0]을 기준으로 먼저 정렬해줍니다. 다음으로 scores[0][1]을 기준으로 다시 정렬해줍니다.

 

 

그다음으로는 range(len(scores))의 다음 값인 1을 input으로 받고, scores[1][0]을 기준으로 먼저 정렬한 뒤, scores[1][1]을 기준으로 다시 정렬해주겠죠?

 

 

이런 방식으로 range의 마지막 값까지 진행됩니다.

 

 

reverse = True를 지정하면, 내림차순으로 바뀝니다. (reverse = False가 default 값이고, 따로 설정을 하지 않으면 오름차순이 진행됩니다.)  

 

 

 

 

 

위 예시를 실제로 돌리면, 다음과 같은 결과가 나오게 됩니다.

 

[5, 4, 1, 3, 2, 0]

 

먼저 첫 번째 요소를 기준으로 내림차순을 진행합니다.

 

[[1, 2], [6, 5], [5, 4], [5, 8], [7, 1], [10, 5]] 이므로, 순서는 5, 4, 1, (2, 3), 0이 됩니다.

 

첫 번째 요소가 5로 같기 때문에, 2번째와 3번째 값은 순위를 매길 수 없습니다. 따라서 (2, 3)로 표기하였습니다.

 

다음으로, 두 번째 요소를 기준으로 내림차순을 진행합니다.

 

[5, 4], [5, 8] 중에서 [5, 8]이 더 크기 때문에, 더 높은 순위를 받게 됩니다.

 

따라서 정답이 [5, 4, 1, 3, 2, 0]이 되는 것이죠.

 

 

 

2. 데이터의 순위를 데이터 값 자체로 반환하도록 만드는 경우

 

scores = [[1, 2], [6, 5], [5, 4], [5, 8], [7, 1], [10, 5]]

rank = sorted(scores, key = lambda x: (x[0], x[1]), reverse = True)
print(rank)

점수 데이터는 위와 동일하게 사용해주고, 그 대신 sorted를 해주는 대상을 바꿨습니다.

 

아까는 range(len(scores))로 지정하여 scores의 index인 0부터 5까지를 key의 input으로 사용했지만, 이번에는 scores값 자체를 key의 input으로 사용합니다.

 

따라서, 정렬한 결과도 scores값이 직접 나오게 됩니다.

 

print(rank)의 결과는 다음과 같습니다.

 

 

[[10, 5], [7, 1], [6, 5], [5, 8], [5, 4], [1, 2]]

 

첫 번째 예시에서는 score 값들의 index가 나왔다면, 이번에는 score 값 그 자체로 정렬이 되었습니다.

 

 

 

 

 

 

 

결론(Conclusion)

 

 

1. sorted()에는 정렬을 하는 기준을 설정해주는 'key'라는 인자가 존재한다.

 

2. key는 sorted의 첫 번째 인자를 input으로 받는다.

 

3. 만약 key로 두 개를 지정해주면, 첫 번째 값을 기준으로 정렬해주고, 다음으로는 두 번째 값을 기준으로 재 정렬해준다.

 

 

 

참고자료

docs.python.org/ko/3/howto/sorting.html

 

Intro

 

 

받으려고 하는 데이터셋 파일이 .mat(매트랩) 형식으로 되어 있어서, 이를 scipy.io.loadmat을 이용해서 받았다.

(아래에 stackoverflow 출처를 올리겠지만, 질문을 올리신 분과 정확히 똑같은 상황에 처해있었다.)

 

 

받은 파일은 dictionary 형식이였고... bounding box 좌표가 들어있는 key를 찾아서 출력해보았더니

 

사이즈가 (70, ) 였다. 그래서 이걸 보고 아 ~ 70장 사진에 대한 bbox 좌표인가 보다 하고 [0]을 출력해보았다.

 

처음 봤을때는 뭔가 숫자가 엄청 많아서 이게 뭐지? 하고 .shape로 찍어봤는데 출력 값이 () 이였다. 

 

분명 데이터 값이 저렇게 많은데 shape를 찍었을 때 ()으로 나오는 것을 보니 심상치 않았다.

 

그래서 이게 뭐지???? 하고 type()으로 출력해보았더니 결과값이 <class 'numpy.void'> 이였다.

 

numpy.ndarray나 많이 봤지, void는 처음이라....... 많이 당황했다.

 

처음에는 이름만 보고 데이터가 비어있다는 의미인가? 했는데 비어있다면 실제로 데이터 값이 없어야 하니 그건 또 말이 안 되는 것 같았다.

 

따라서 numpy.void가 무엇이고, 이를 어떻게 활용할 수 있는지에 대해서 알아볼 필요성이 생겨 검색을 하다보니 적합한 글을 찾아 이를 정리해보려고 한다.

 

 

 

What is numpy.void?

 

numpy documentation에 따르면(http://docs.scipy.org/doc/numpy/reference/arrays.dtypes.html), numpy.void type은 flexible data type으로 정의된다. 기본적으로, numpy.void type은 변수와 관련해서 이미 정의된 type이 없는 데이터 타입이다. 이미 정의된 type이란, float, uint8, bool, string 등등의 data type을 말하는 것이다.

 

 

void는 더 포괄적이고 융통성 있는 type을 수용하며, void는 이미 정의된 데이터 타입 중 하나로 반드시 결정되지 않는 데이터 타입을 위해 존재한다. 이러한 상황은 대개 struct에서 loading 할 때 마주하게 되는데, 각 element가 다양한 field와 관련 있는 다양한 데이터 타입을 가지고 있는 경우이다. 각 structure element는 다른 데이터 타입의 조합을 가질 수 있으며, 이러한 structure element를 가진 instance를 나타내기 위해 numpy.void를 사용한다.

 

 

documentation에 따르면, 우리가 다른 데이터 타입들에 적용하던 operation을 동일하게 적용할 수 있다. 

 

 

예시를 통해 조금 더 정확하게 알아보도록 하자.

 

import numpy as np
dt = np.dtype([('name', np.str_, 16), ('grades', np.float64, (2,))])

dt는 데이터 타입을 나타내는데, tuple로 구성되고 각 tuple은 (fieldname, datatype, shape)로 구성된다. 

 

 

field가 대체 뭘 얘기하는지 명확하게 써있지 않아서 이것저것 찾아봤는데도 못 찾아서... 대략 유추하기로는 데이터 타입의 element를 나타내는 용어인 듯하다.

 

 

데이터 타입이 두 개의 tuple로 구성되었다는 것은, 결국에 데이터가 2개로 구성되어야 한다는 의미이다.

 

 

즉, 첫 번째 데이터의 fieldname은 'name'이고, 16-bit인 string이 된다.

 

 

두 번째 데이터의 fieldname은 'grades'이고, (1, 2) 형태를 가지는 float 값이 될 것이다.

 

 

 

 

위 데이터 타입을 이용해서, 실제 데이터를 만들어보자.

 

x = np.array([('Sarah', (8.0, 7.0)), ('John', (6.0, 7.0))], dtype=dt)

 

x라는 데이터를 만들었고, 이는 두 개의 elements를 가지고 있다. 

 

각 element는 아까 위에서 정의해준 데이터 타입대로 만들어진 것을 확인할 수 있다.

 

 

 

여기서 주의해야 할 점은, 데이터 x 자체의 타입은 numpy.ndarray를 만족한다는 점이다.

 

 

그 대신, 데이터 x의 elements는 numpy.void를 만족한다. 왜냐하면, 각 element는 'name'과 'grades'라고 하는 다른 데이터 타입 두 개를 모두 포함하기 때문이다.

 

 

따라서,

x[1]

로 입력하게 되면,

 

 

 

('John', [6.0, 7.0])

이라는 두 번째 element를 얻을 수 있으며,

 

type(x[1])

 

이에 대해서 type을 출력하게 만들면,

 

x[1] type:  <class 'numpy.void'>

라고 출력이 된다. 

 

 

 

즉, 내가 처음에 마주했던 numpy.void는 이 데이터 안에 다양한 형태의 데이터 타입이 존재하는 데이터였던 것이다....!

 

 

 

자 그럼 여기서 실제로 numpy.void를 마주했다고 했을 때, 각 데이터 타입의 이름을 알아야만 출력이 가능하다.

 

물론, index를 이용해서 출력할 수도 있긴 하다.

 

예를 들면,

 

print("x[1][0]: ", x[1][0])
print("x[1][1]: ", x[1][1])

으로 출력시키면,

 

 

 

x[1][0]:  John
x[1][1]:  [6. 7.]

이라는 출력 값을 얻게 된다.

 

 

 

index를 이용하지 않고, 데이터 타입의 이름을 이용해서 출력하려면 어떻게 해야 할까?

 

 

 

print(x[1].dtype)

 

해당 numpy.void 데이터에 대해서 .dtype을 하게 되면,

 

 

[('name', '<U16'), ('grades', '<f8', (2,))]

 

이렇게 각 데이터 타입(fieldname, datatype, shape)에 대한 정보를 알 수 있다.

 

 

 

따라서 이 fieldname을 이용해서 출력해보자면,

 

 

print('x[1]["name"]: ', x[1]['name'])
print('x[1]["grades"]: ', x[1]['grades'])

 

이렇게 해서 시키면, 출력이

 

 

 

x[1]["name"]:  John
x[1]["grades"]:  [6. 7.]

 

이런 식으로 출력이 된다. 

 

 

 

 

결론(Conclusion)

 

 

 

1. 'numpy.void'는 다양한 데이터 타입을 포함하고 있는 데이터를 나타내는 데이터 타입이다. 따라서, 데이터 안에 정수형, 실수형, 문자열 등 다양한 형태의 데이터 타입이 존재한다.

 

 

2. numpy.void로 되어 있는 데이터를 핸들링할 때, index를 이용해서 데이터를 뽑을 수도 있지만 만약 정확하게 fieldname을 지정해서 뽑아내고 싶다면 .dtype을 이용해서 각 데이터 타입의 fieldname을 알아낸 뒤 이를 이용해 주자!

 

 

 

 

참고자료

stackoverflow.com/questions/25247190/numpy-void-type-how-to-use-it

 

numpy.void type - how to use it?

I loaded a MATLAB .mat file via scipy.io.loadmat and it gave me a list of numpy.void objects. Can someone tell me what are they, how they can be used and where can I get some reference documentati...

stackoverflow.com

docs.scipy.org/doc/numpy-1.15.1/user/basics.rec.html

 

 

 

 

논문을 읽던 중, ablation study 라고 하는 단어를 종종 마주할 수 있었다.

 

따로 논문에 이게 어떤 것인지에 대해서 설명이 없어서, 이에 대해서 정리해보고자 한다.

 

 

 

In the context of deep learning, what is an ablation study?

(딥러닝에서, ablation study는 무엇인가?)

 

 

의학이나 심리학 연구에서, Ablation Study는 장기, 조직, 혹은 살아있는 유기체의 어떤 부분을 수술적인 제거 후에 이것이 없을때 해당 유기체의 행동을 관찰하는 것을 통해서 장기, 조직, 혹은 살아있는 유기체의 어떤 부분의 역할이나 기능을 실험해보는 방법을 말한다. 이 방법은, experimental ablation이라고도 알려져 있는데, 프랑스의 생리학자 Maria Jean Pierre Flourens이 19세기 초에 개척했다. Flourens은 동물들에게 뇌 제거 수술을 시행하여 신경계의 다른 부분을 제거하고 그들의 행동에 미치는 영향을 관찰했다. 이러한 방법은 다양한 학문에서 사용되어왔으며, 의학이나 심리학, 신경과학의 연구에서 가장 두드러지게 사용되었다.

 

 

 

Machine learning에서, ablation study는 "machine learning system의 building blocks을 제거해서 전체 성능에 미치는 효과에 대한 insight를 얻기 위한 과학적 실험"으로 정의할 수 있다. Dataset의 feature나 model components가 building blocks에 해당한다. (따라서 ablation study라는 용어를 feature ablation이나 model ablation 이라는 용어로도 사용할 수 있다.) 하지만 어떠한 design choice나 system의 module도 ablation study에 포함될 수 있다.

 

 

 

그림 1. Illustration of ablation study

 

 

 

우리는 ablation study가 여러 시도로 구성된 실험이라고 생각할 수 있다. 예를 들어, 각 model ablation trial은 한개 혹은 그 이상의 components가 제거된 모델을 학습하는 것을 포함한다. 유사하게, feature ablation trial은 dataset feature 들의 다른 집합을 사용하여 모델을 학습하는 것을 포함하며, 결과를 확인한다.

 

 

 

그림 1에서, 위에 나타난 표 그림은 feature ablation trial을 나타내고 있으며, 아래 나타난 layer 그림은 layer ablation trial을 나타낸다. 예를 들어, layer ablation trial에서, 우리는 base model의 마지막 hidden layer를 제거하고, 결과 모델을 학습시키고, 이것의 성능을 확인하게 된다.

 

 

 

 

[참고자료]

 

https://www.quora.com/In-the-context-of-deep-learning-what-is-an-ablation-study

 

 

 

 

+ Recent posts