18.2 CNN과 공간 구조

실습 · 연습문제 2개

conv2d를 NumPy로 구현하기 conv2d-forward

딥러닝 라이브러리의 2차원 합성곱(정확히는 상호상관)을 직접 구현한다. 입력 x의 shape은 (C_in, H, W), kernel w의 shape은 (C_out, C_in, kH, kW), bias b의 shape은 (C_out,)이다. 출력 y의 원소는

yo,i,j=bo+∑c=0Cin−1∑u=0kH−1∑v=0kW−1wo,c,u,v  x~c,  is+u,  js+vy_{o,i,j} = b_o + \sum_{c=0}^{C_{\text{in}}-1} \sum_{u=0}^{k_H-1} \sum_{v=0}^{k_W-1} w_{o,c,u,v}\; \tilde{x}_{c,\; i s + u,\; j s + v}

이다. x~\tilde{\mathbf{x}}는 높이·너비 양쪽에 padding칸씩 0을 덧붙인 입력, ss는 stride다(높이와 너비에 같은 값을 쓴다). kernel을 뒤집지 않는다는 점에 주의한다.

함수 conv2d(x, w, b, stride=1, padding=0)를 완성해 y를 반환한다. 출력 shape은 (C_out, H_out, W_out)이고

Hout=⌊H+2p−kHs⌋+1H_{\text{out}} = \left\lfloor \frac{H + 2p - k_H}{s} \right\rfloor + 1

이다(WoutW_{\text{out}}도 같다).

테스트는 다음을 확인한다.

  • 여러 stride·padding·직사각형 kernel에서 출력 shape이 공식과 같다.
  • 출력값이 원소 정의를 그대로 계산한 값과 같다.
  • stride 1, padding 0, 채널 1개일 때 scipy.signal.correlate2d(..., mode="valid")와 같다.

힌트: 출력 위치 (i, j)마다 입력에서 (C_in, kH, kW) 조각을 잘라 w와 곱해 더한다. np.einsum("chw,ochw->o", patch, w)가 채널과 kernel 축을 한 번에 축약한다.

receptive field 계산하기 receptive-field-calc

1차원 합성곱 층을 입력 쪽부터 차례로 쌓는다. 층 ll은 kernel 크기 klk_l, stride sls_l, dilation dld_l을 가진다. 마지막 층 출력 한 칸이 영향을 받을 수 있는 입력의 범위(이론 receptive field)의 길이 rr과, 마지막 층 출력에서 한 칸 옆으로 갈 때 입력에서 이동하는 칸 수(jump) JJ를 구한다.

r=1+∑l=1L(kl−1) dl∏i<lsi,J=∏l=1Lsl.r = 1 + \sum_{l=1}^{L} (k_l - 1)\, d_l \prod_{i<l} s_i, \qquad J = \prod_{l=1}^{L} s_l.

함수 receptive_field(layers)를 완성해 (r, J)를 반환한다. layers는 (k, s, d) 튜플의 리스트이고, 비어 있으면 (1, 1)이다.

테스트는 다음을 확인한다.

  • 3×1 합성곱 다섯 층(stride 1)은 r=11r = 11, J=1J = 1이다.
  • dilation 1, 2, 4, 8인 kernel 2 causal 층 네 개는 r=16r = 16이다(시계열 TCN의 전형적인 구성).
  • 무작위로 만든 층 구성에서, 입력 칸 하나를 바꿔 보며 출력 한 칸이 영향을 받는 입력 범위를 직접 센 값과 같다.

힌트: 앞 층들의 stride 곱을 누적하는 변수를 두고, 층마다 그 값에 (k−1)d(k-1)d를 곱해 rr에 더한다. 누적 stride는 이 층의 (k−1)d(k-1)d를 더한 뒤에 갱신한다.