꺼내먹는지식 준

Faster R CNN 에 나오는 numpy 활용법 본문

카테고리 없음

Faster R CNN 에 나오는 numpy 활용법

알 수 없는 사용자 2022. 3. 26. 13:31

Faster R CNN 소스 코드를 읽으면서 numpy 의 용례를 많이 찾아볼 수 있다. 

 

본 글은 numpy 에 익숙하지 않은 사람 대상으로, Faster R CNN 소스코드를 읽을 때 만날 수 있는 여러 시행착오를 줄이기 위해 작성

 

 

Numpy Where

 

np.where 의 official doc 

https://numpy.org/doc/stable/reference/generated/numpy.where.html

 

numpy.where — NumPy v1.22 Manual

Return elements chosen from x or y depending on condition. Note When only condition is provided, this function is a shorthand for np.asarray(condition).nonzero(). Using nonzero directly should be preferred, as it behaves correctly for subclasses. The rest

numpy.org

 

official doc 을 읽는 것도 좋지만, 직관적으로 내용을 설명해보자면 이렇다. 

 

np.where 자체는 주어진 np.array에서 True index 를 반환한다. 

예를 들면, 

np.where(np.array([True, False, False]))

(array([0]),)

 

활용 

np.where(a==b)

 

직관적으로 설명해보자면 a==b를 먼저 수행 한 후, 내부에서 나온 결과에 따라 True False 를 원소로 갖는 np array를 반환 

 

다음과 같은 경우가 있다고 하자. 

 a = np.arange(200).reshape(20,10)
 b = np.range(10,20,-1)
 
a.shape 
(20, 10)
b.shape
(10,)

각 np array의 원소를 출력해보면 다음과 같다. 

print(a)
array([[  0,   1,   2,   3,   4,   5,   6,   7,   8,   9],
       [ 10,  11,  12,  13,  14,  15,  16,  17,  18,  19],
       [ 20,  21,  22,  23,  24,  25,  26,  27,  28,  29],
       [ 30,  31,  32,  33,  34,  35,  36,  37,  38,  39],
       [ 40,  41,  42,  43,  44,  45,  46,  47,  48,  49],
       [ 50,  51,  52,  53,  54,  55,  56,  57,  58,  59],
       [ 60,  61,  62,  63,  64,  65,  66,  67,  68,  69],
       [ 70,  71,  72,  73,  74,  75,  76,  77,  78,  79],
       [ 80,  81,  82,  83,  84,  85,  86,  87,  88,  89],
       [ 90,  91,  92,  93,  94,  95,  96,  97,  98,  99],
       [100, 101, 102, 103, 104, 105, 106, 107, 108, 109],
       [110, 111, 112, 113, 114, 115, 116, 117, 118, 119],
       [120, 121, 122, 123, 124, 125, 126, 127, 128, 129],
       [130, 131, 132, 133, 134, 135, 136, 137, 138, 139],
       [140, 141, 142, 143, 144, 145, 146, 147, 148, 149],
       [150, 151, 152, 153, 154, 155, 156, 157, 158, 159],
       [160, 161, 162, 163, 164, 165, 166, 167, 168, 169],
       [170, 171, 172, 173, 174, 175, 176, 177, 178, 179],
       [180, 181, 182, 183, 184, 185, 186, 187, 188, 189],
       [190, 191, 192, 193, 194, 195, 196, 197, 198, 199]])

print(b)
array([20, 19, 18, 17, 16, 15, 14, 13, 12, 11])

 

이 부분에서 numpy array 에 익숙하지 않은 사람들은 a == b 가 크기가 맞지 않아 애초에 성립이 불가능하다고 생각하기 쉽다. 

하지만, 이 부분에서 numpy 는 자동으로 

for i in range(a):
	a == b

를 수행한다. 

이에 따라 a 와 b를 비교하면, 

 a == b
array([[False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False,  True, False, False, False,
        False],
       [ True, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False]])

다음과 같은 결과를 얻을 수 있다. 

그 결과 np.array(a==b) 는 

np.where( a == b
array([[False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False,  True, False, False, False,
        False],
       [ True, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False],
       [False, False, False, False, False, False, False, False, False,
        False]]))

가 된다고 할 수 있다. 

 

이에 따라 True 인 항의 index를 반환하면 

(array([1, 2]), array([5, 0]))

다음의 결과를 얻는다. 

 

※추가 정보 

np.where(condition, if True: ,  else: )

직관적으로 작성해보았다. 

np.where의 결과가 True 이면 두번째 항의 값으로, False이면 3번째 항의 값으로 설정할 수도 있다. 

 

이 경우 index 가 아닌 value 를 return 한다. 

np.where(a==b, a, -1)

array([[-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, 15, -1, -1, -1, -1],
       [20, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1],
       [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1]])

다음의 결과로 관측할 수 있는 건 True 인 경우 a 로, False 경우 -1 로 원소를 치환 한 것을 알 수 있다. 

 

numpy meshgrid 

numpy meshgrid 를 처음 사용해보고 황당했다. 

a = np.arange(5)
b = np.arange(20,15,-1)

print(a)
array([0, 1, 2, 3, 4])
print(b)
array([20, 19, 18, 17, 16])

다음의 간단한 두 numpy array로 np.meshgrid 를 수행하면, 

np.meshgrid(a,b)

[array([[0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4]]), 
       array([[20, 20, 20, 20, 20],
       [19, 19, 19, 19, 19],
       [18, 18, 18, 18, 18],
       [17, 17, 17, 17, 17],
       [16, 16, 16, 16, 16]])]

다음의 황당한 결과를 얻는다. 

즉 np.meshgrid 의 첫번째 파라미터의 numpy array는 원소 개수만큼 numpy array를 단순 복사해주고, 

 

두번째 파라미터의 numpy array는 원소개수만큼, numpy array의 각 원소를 복사하여, 각 원소별로 하나의 numpy array를 생성한다. 

작성해놓고도 말이 어렵다. 위의 예제를 통해 이해하자. 

 

결론적으로 이걸 왜 할까에 대해 직관적인 이해가 바로 되지 않아서 고민했었다. 

그러던 중 이게 얼마나 편리한 기능인지 깨달았다. 

x = np. meshgrid(a,b)[0]

y = np. meshgrid(a,b)[1]

우리는 아주 종종 

 

$[(x_1,y_1), (x_2,y_1), (x_3, y_1)] \\ [(x_1,y_2), (x_2,y_2), (x_3, y_2)] \\ [(x_1,y_3), (x_2,y_3), (x_3, y_3)]$

 

다음의 형태로 tensor 를 생성해야 할 때가 있다. 

이때 for 문을 돌리며 생성하는 과정이 좀 짜친다는 생각을 늘 했었다. 

이를 mesh.grid 를 사용하면 간단하게 처리가 가능하다. 

stack_result = np.stack((x.ravel(), y.ravel(),
                      ), axis=1)

· .ravel() 은 1차원으로 tensor 를 평탄화 

np.stack((x.ravel(),y.ravel()), axis=1)

array([[ 0, 20],
       [ 1, 20],
       [ 2, 20],
       [ 3, 20],
       [ 4, 20],
       [ 0, 19],
       [ 1, 19],
       [ 2, 19],
       [ 3, 19],
       [ 4, 19],
       [ 0, 18],
       [ 1, 18],
       [ 2, 18],
       [ 3, 18],
       [ 4, 18],
       [ 0, 17],
       [ 1, 17],
       [ 2, 17],
       [ 3, 17],
       [ 4, 17],
       [ 0, 16],
       [ 1, 16],
       [ 2, 16],
       [ 3, 16],
       [ 4, 16]])

다음과 같이 사용할 수 있다. 너무 편리한 기능이다. 하지만 이 정도 경지에 오르려면 numpy 를 수족처럼 사용해야 할 듯 싶다. 

 

numpy clip

np.clip(array, min, max)

 

np clip을 어디에 사용할까? 엄청난 용례가 있다. 

faster R CNN 논문에서 bounding box 가 사진 이미지 크기를 벗어나는 일은 자주 발생한다. 

이 때, clip을 통해 img 크기를 벗어난 tensor 의 크기를 clip해준다. 

 

코드내에서 출력값이 너무 길어서 간단한 예제로 대채해보자. 

a = np.arange(20).reshape(2,5,2)

np.clip(a,2,8)

array([[[2, 2],
        [2, 3],
        [4, 5],
        [6, 7],
        [8, 8]],

       [[8, 8],
        [8, 8],
        [8, 8],
        [8, 8],
        [8, 8]]])

다음과 같이 사용될 수 있다. 

 

numpy Maximum

간단하게는 앞뒤 list를 비교하고 더 큰 값을 return 해준다. 

※numpy 함수들은 parameter로 list 를 주면 자동으로 numpy array로 치환해서 계산 

np.maximum([2, 3, 4], [1, 5, 2])
array([2, 5, 4])
np.maximum([2, 3, 4], [[1, 5, 2], [1,1,9]])

array([[2, 5, 4],
       [2, 3, 9]])

모든 과정을 통해 살펴본 numpy 는 마지막 차원만 동일하면 연산 수행이 가능하다. 

np.maximum([2, 3, 4], [[[1, 5, 2], [1,1,9]], [[1, 5, 2], [1,1,9]]] )

array([[[2, 5, 4],
        [2, 3, 9]],

       [[2, 5, 4],
        [2, 3, 9]]])

 

※항상 실수하기 좋은 추가 정보

 

Numpy shape[0] vs [0]

a.shape
(1, 2, 5, 2)

다음 형태의 numpy array가 있다고 할 때, 

a.shape[0]

a[0]

두 결과는 당연히 다르다. 

첫번째는 단순히 첫번째 차원이 크기를 받아오는 거고, 

 

두번째는 첫번째 차원을 기준으로 index 중 1개만 선택하여 차원수를 줄이는 것이다. 

a[0].shape
(2, 5, 2)

차원수를 줄일 때 다음과 같이 많이 활용된다. 

np.squeeze(a) == a[0]
array([[[ True,  True],
        [ True,  True],
        [ True,  True],
        [ True,  True],
        [ True,  True]],

       [[ True,  True],
        [ True,  True],
        [ True,  True],
        [ True,  True],
        [ True,  True]]])

squeeze와 동일한 기능이다. 

사실 readability를 위해 squeeze를 사용하는 것이 더 나아보인다. 

Comments