본문 바로가기
파이썬 프로그래밍/Numpy 딥러닝

30. [CNN기초] 2차원 배열 합성곱 - image to column 구현

by Majestyblue 2023. 1. 6.

1. 기존 합성곱 방식의 문제점

이전시간에 1차원 배열에 대한 합성곱 연산을 실시하였다. 이번 시간부터 2차원 배열(이미지)에 대해 실시해 보도록 하겠다.

 원리는 간단한데, 1차원을 2차원으로 확장시켰으므로 1차원 합성곱을 2번 한다고 생각하면 쉽다.

문제는, for문 중첩이 너무 심하다는 것이다. 예를 들어 최소 아래와 같은 for문 중첩이 발생한다.

 

for input in inputs:
    for fmap_h in range(fmap_height):
        for fmap_w in range(fmap_width):
        a = 0
            for filter_h in range(W.shape[0]):
                for filter_w in range(W.shape[1]):
                    ...
                    ...
                    ...

 

이건 못참는다(...) 나 안해! 

 

코드 작성 난해함은 둘째 치더라도 컴퓨터 수행 작업에서 너무 많은 시간을 잡아먹는다.

좀 더 쉬운 방법은 없을까?

3 × 3 이미지를 2 × 2 필터로 합성곱을 하면 2 × 2의 특성맵이 나오는 연산을 통해 좋은 방법이 있는지 살펴보자.

 

 

 

 

 

 

뭔가 보이는가? 합성곱 역시 x와 w의 곱이므로 입력과 필터를 잘 조작하면 선형 연산인 행렬곱으로 바꿀 수 있을 것 같다. 

아래 그림을 다시 보자

 

 

이 연산 결과는 합성곱 연산과 동일하다! 

이와 같이 이미지를 열(Column)을 바꾸어 선형 연산으로 합성곱을 구현하는 방법을 image to column 이라고 하고 약어로 im2col 이라고 적는다. 시간 절약 등 많은 이점이 있지만, 선형 연산의 역전파로 쉽게 구현이 가능하다!

 

 

 

 

2. image to column 구현 

 우리가 필요한 것은 이와 같이 이미지를 열의 형태로 변환하여야 한다.

 아래 상황은 image size = 2 × 2, filter size = 2 × 2 , padding = 0, stride = 1인 상황이다. 

 

 

1) 합성곱 필터 크기로 분리하기

 아래 그림과 같이 필터 크기로 나누어 슬라이스 해야 한다. 슬라이스된 배열은 필터와 합성곱을 하여 출력이 된다는 점을 다시 한번 상기하자. 

 index 번호를 잘 살펴보자. index번호는 배열 슬라이싱 할 때 사용된다. 어떤 규칙성이 있지 않을까?  output height, output width와 어떤 관련이 있을까?

 슬라이싱 크기를 살펴보자. 0이상 2미만, 1이상 3미만 임을 보면 크기가 2임을 알 수 있다. 이것은 무엇을 의미할까?

 

 

 

 

index 번호와 output height, output width 관계를 a, b, c, d 별로 살펴보면 좋다.

배열의 슬라이싱은 시작점으로부터 범위를 정함에 주목하자

 

정리하자면, 슬라이싱의 범위는 필터의 크기를 의미하고, 슬라이싱의 각 시작점은 출력(특성맵) 크기를 height, width의 정수로 나눈 값이라고 생각하면 된다. 

 

 

이를 이중 for문으로 나타내면 다음과 같다.

순서대로 a → b → c → d 로 작업한다. 

for o_h in range(output_height):
  for o_w in range(output_width):
    a = X[o_h : o_h + weight_height, o_w:o_w + weight_width]

 

 

 

 

아래 X 배열에 대해 2 × 2로 추출하면 다음과 같은 결과를 얻는다.

 

 

 

 

 

 이를 평평하게 펴고(np.reshape), 차곡차고 쌓아서(np.concatenate) 행과 열을 바꾸어 준다(np.transpose(, (1, 0)))

 

 

 

이를 수행하는 함수를 코드로 작성하면 아래와 같다.

def im2col(input, output_height, output_width, filter_height, filter_width):
  count = 0
  for o_h in range(output_height):
    for o_w in range(output_width):
      a = input[o_h : o_h + filter_height, o_w:o_w + filter_width]
      out = np.reshape(a, (1, -1))
      if count == 0:
        outs = out.copy()
      else:
        outs = np.concatenate((outs, out), axis=0)
      count += 1

  return np.transpose(outs, (1, 0))

 

 

위의 입력 예제 X로 확인해 보자.

im2col_image = im2col(X, 2, 2, 2, 2)
print(im2col_image)

 

 

출력된 image to column 을 자세히 살펴보자, 세로 길이는 가중치 개수이고 가로 길이는 특성맵 출력 개수이다.

 

 

 

 

 

 

 

 

3. 2차원 합성곱 구현하기

 이와 같이 2차원 합성곱은 이미지를 열로 바꾸는 image to column (im2col)로 변환하고, 필터를 일렬로 편 다음 행렬곱을 해주면 된다.

 

마지막으로 원래 출력 예정이였던 이미지 크기(output_height, output_width)로 변경하면 된다.

 

이를 코드로 구현해 보자.

def Conv2D(input, output_height, output_width, filter_height, filter_width, weight, bias):
  im2col_input = im2col(input, output_height, output_width, filter_height, filter_width)
  conv = np.dot(weight.reshape(1, -1),im2col_input) + bias
  return conv.reshape(output_height, output_width)

 

 

 

 

결과가 맞는지 함수 실행결과와 손으로 계산한 결과를 비교하여 확인해 보자