Convolution의 역전파: 커널과 입력의 기울기 유도하기
Convolution layer의 커널과 입력 기울기를 chain rule로 유도하고, cross-correlation과 full convolution의 관계 및 커널을 뒤집는 이유를 설명한다.
Convolution layer의 역전파에서는 두 가지 기울기를 계산한다. 커널에 대한 기울기는 가중치를 학습하는 데 사용하고, 입력에 대한 기울기는 앞선 layer로 전달한다.
두 기울기는 같은 순전파 식에서 출발하지만 서로 다른 연산으로 나타난다. 커널의 기울기는 입력과 출력 기울기의 cross-correlation, 입력의 기울기는 출력 기울기와 커널의 full convolution으로 계산할 수 있다. 각 결과를 chain rule로 유도하고, 커널을 뒤집는 이유까지 살펴보자.
순전파와 표기
입력 $x$의 크기를 $H\times W$, 커널 $k$의 크기를 $k_1\times k_2$라고 하자. 여기서는 실수 배열, 단일 입력 채널과 단일 커널을 다룬다. Stride와 dilation은 1, padding은 0으로 가정하고 bias는 생략한다. 커널은 입력보다 크지 않다고 가정한다.
출력 $y$의 크기는 다음과 같다.
\[H_{\mathrm{out}}=H-k_1+1,\qquad W_{\mathrm{out}}=W-k_2+1.\]출력의 각 원소는 입력의 일부 영역과 커널을 원소별로 곱한 뒤 더해 계산한다.
\[y(r,c) = \sum_{a=0}^{k_1-1} \sum_{b=0}^{k_2-1} x(r+a,c+b)\,k(a,b),\]여기서 출력 좌표의 범위는
\[0\le r<H_{\mathrm{out}},\qquad 0\le c<W_{\mathrm{out}}\]이다.
이 식은 커널을 그대로 이동시키므로 수학적으로는 cross-correlation이다. 딥러닝에서는 이 연산을 관례적으로 convolution이라고 부른다. PyTorch의 Conv2d 문서도 순전파 연산을 cross-correlation으로 정의한다.
손실 $L$은 출력 $y$를 통해 입력과 커널에 의존한다고 가정한다. 뒤쪽 layer에서 전달받는 값은 출력에 대한 손실의 기울기다.
\[\frac{\partial L}{\partial y(r,c)}.\]코드에서 흔히 사용하는 dout이 바로 이 기울기 배열이다. 역전파의 목표는 이 값으로부터 다음 두 기울기를 계산하는 것이다.
커널에 대한 기울기
커널의 한 원소 $k(a’,b’)$를 고정해 생각해 보자. 이 원소는 커널이 이동하는 모든 출력 위치에서 재사용된다. 따라서 손실에 대한 영향을 계산하려면 모든 출력 위치에서 발생한 기여를 더해야 한다.
Chain rule을 적용하면
\[\frac{\partial L}{\partial k(a',b')} = \sum_{r=0}^{H_{\mathrm{out}}-1} \sum_{c=0}^{W_{\mathrm{out}}-1} \frac{\partial L}{\partial y(r,c)} \frac{\partial y(r,c)}{\partial k(a',b')}.\]출력 $y$는 배열이므로, chain rule에서도 각 출력 원소를 거치는 기여를 합산해야 한다.
순전파 식에서 $k(a’,b’)$가 포함된 항은
\[x(r+a',c+b')\,k(a',b')\]이므로,
\[\frac{\partial y(r,c)}{\partial k(a',b')} = x(r+a',c+b').\]이를 대입하면 커널의 기울기를 얻는다.
\[\boxed{ \frac{\partial L}{\partial k(a',b')} = \sum_{r=0}^{H_{\mathrm{out}}-1} \sum_{c=0}^{W_{\mathrm{out}}-1} \frac{\partial L}{\partial y(r,c)}\,x(r+a',c+b') }\]각 출력 위치에서 해당 커널 원소와 곱했던 입력값에 출력 기울기를 곱하고, 그 결과를 모두 더하는 식이다. 같은 가중치를 여러 위치에서 공유하므로 기울기도 여러 위치의 기여를 누적한다.
Cross-correlation으로 해석하기
실수 배열 $u,v$의 cross-correlation을 다음과 같이 정의하자. 합은 $v$의 모든 원소에 대해 취한다.
\[(u\star v)(i,j) = \sum_r\sum_c u(i+r,j+c)\,v(r,c).\]그러면 커널 기울기의 식은 그대로
\[\frac{\partial L}{\partial k} =x\star\frac{\partial L}{\partial y}\]가 된다. 입력 $x$ 위에서 출력 기울기 $\frac{\partial L}{\partial y}$를 뒤집지 않고 이동시키며, 겹치는 원소끼리 곱해 더하는 연산이다.
여기서는 출력 기울기 배열이 입력 안에 완전히 들어오는 위치만 계산하므로 valid cross-correlation을 사용한다. 결과의 크기도 커널 크기와 일치한다.
\[\begin{aligned} H-H_{\mathrm{out}}+1 &= k_1,\\ W-W_{\mathrm{out}}+1 &= k_2. \end{aligned}\]입력에 대한 기울기
이번에는 입력의 한 원소 $x(r’,c’)$를 고정하자. 이 입력값은 자신을 포함하는 여러 입력 영역에서 사용된다. 따라서 입력 기울기도 관련된 모든 출력 위치의 기여를 더해야 한다.
\[\frac{\partial L}{\partial x(r',c')} = \sum_{r=0}^{H_{\mathrm{out}}-1} \sum_{c=0}^{W_{\mathrm{out}}-1} \frac{\partial L}{\partial y(r,c)} \frac{\partial y(r,c)}{\partial x(r',c')}.\]순전파 식을 다시 보면,
\[y(r,c) = \sum_{a=0}^{k_1-1} \sum_{b=0}^{k_2-1} x(r+a,c+b)\,k(a,b).\]이 합에서 $x(r’,c’)$에 해당하는 항은
\[r+a=r',\qquad c+b=c'\]를 만족해야 한다. 즉,
\[a=r'-r,\qquad b=c'-c\]인 항이다. 해당 좌표가 커널 범위 안에 있을 때 미분값은 그 위치의 커널 원소가 된다.
\[\frac{\partial y(r,c)}{\partial x(r',c')} = \begin{cases} k(r'-r,c'-c), & \text{if }0\le r'-r<k_1\text{ and }0\le c'-c<k_2,\\[4pt] 0,&\text{otherwise}. \end{cases}\]이제 합의 인덱스를 출력 좌표 $(r,c)$에서 커널 좌표 $(a,b)$로 바꾸자.
\[r=r'-a,\qquad c=c'-b.\]유효한 출력 위치에 대해서는
\[\frac{\partial y(r'-a,c'-b)}{\partial x(r',c')} =k(a,b)\]이다. 출력 좌표를 이렇게 선택하면 $x(r’,c’)$와 곱해지는 커널 원소가 정확히 $k(a,b)$가 된다.
다만 입력 가장자리에서는 $r’-a$나 $c’-b$가 출력 범위를 벗어날 수 있다. 이하 식에서는 출력 범위 밖의 $\frac{\partial L}{\partial y(r,c)}$를 계산상 0으로 취급한다. 이는 실제로 존재하지 않는 출력 원소까지 미분한다는 뜻이 아니라, 유효한 출력만 합에 기여하도록 하는 zero extension이다.
그러면 모든 입력 좌표에 대해 성립하는 식을 얻는다.
\[\boxed{ \frac{\partial L}{\partial x(r',c')} = \sum_{a=0}^{k_1-1} \sum_{b=0}^{k_2-1} \frac{\partial L}{\partial y(r'-a,c'-b)}\,k(a,b) }\]이 식에서 출력 기울기의 인덱스에는 커널 좌표가 빼기로 들어간다. 이 형태가 convolution의 핵심이다.
Stamp 방식: 기여를 입력 위치에 찍어 누적하기
입력 기울기는 슬라이딩 연산 없이도 계산할 수 있다. 입력과 같은 크기의 배열을 0으로 초기화하고, 각 출력 위치 $(r,c)$에서 다음 값을 해당 입력 위치에 더한다.
\[\frac{\partial L}{\partial x(r+a,c+b)} \mathrel{+}= \frac{\partial L}{\partial y(r,c)}\,k(a,b).\]여기서 $\mathrel{+}=$는 그 위치에 기여를 누적한다는 뜻이다. 입력 기울기 배열을 코드에서 dx라고 부르면, 이 누적은 dx[r:r+k1, c:c+k2] += dout[r,c] * k에 해당한다. 즉 dout의 한 원소를 커널 전체에 곱하고, 그 결과를 dx의 $(r,c)$부터 stamp처럼 찍어 더한다. 모든 출력 위치에서 반복하며 겹치는 부분은 더한다. 덮어쓰면 앞서 전달된 기울기가 사라지므로 반드시 +=로 누적해야 한다.
이때 커널은 순전파와 같은 방향으로 사용한다. Stamp 방식에서는 원래 크기의 dout과 커널을 그대로 사용하므로 명시적으로 padding을 추가할 필요가 없다.
왜 full convolution인가?
Convolution은 stamp로 누적하거나 커널을 슬라이딩하며 계산할 수 있다. 여기서 full은 계산 방법이 아니라 출력 범위를 뜻한다. 두 배열이 일부만 겹치는 가장자리까지 포함해 모든 겹침 위치의 결과를 계산하는 것이 full convolution이다.
두 배열의 convolution은 다음 형태로 정의할 수 있다. 합은 $v$의 모든 원소에 대해 취하고, 각 배열은 정의된 범위 밖에서 0으로 취급한다.
\[(u*v)(i,j) = \sum_a\sum_b u(i-a,j-b)\,v(a,b).\]입력 기울기의 식과 비교하면
\[\frac{\partial L}{\partial x} =\frac{\partial L}{\partial y}*k\]임을 알 수 있다. 앞의 stamp 방식은 이 convolution을 각 출력 위치의 기여로 나누어 누적한 것이다.
이때 출력 기울기와 커널이 일부만 겹치는 위치까지 계산해야 한다. 입력 가장자리의 원소도 순전파에 참여했으므로, 그 위치로 돌아가는 기울기가 필요하기 때문이다. 따라서 연산은 full convolution이다.
결과의 크기는
\[\begin{aligned} H_{\mathrm{out}}+k_1-1 &= H,\\ W_{\mathrm{out}}+k_2-1 &= W \end{aligned}\]으로, 입력 크기와 일치한다.
Cross-correlation으로 계산하려면 커널을 뒤집는다
앞에서 정의한 순전파 연산은 cross-correlation이다. 이 연산으로 입력 기울기를 계산하려면 커널을 높이와 너비 방향으로 모두 뒤집어야 한다.
뒤집은 커널을
\[k_{\mathrm{flip}}(a,b) = k(k_1-1-a,k_2-1-b)\]라고 하자. 이는 커널을 $180^\circ$ 회전한 것과 같다.
입력 기울기 식에서 합의 인덱스를 뒤집으면
\[\frac{\partial L}{\partial x(r',c')} = \sum_{a=0}^{k_1-1} \sum_{b=0}^{k_2-1} \frac{\partial L}{\partial y\bigl( r'-(k_1-1)+a,\, c'-(k_2-1)+b \bigr)} \,k_{\mathrm{flip}}(a,b).\]이 식은 dout의 위아래에 각각 $k_1-1$, 좌우에 각각 $k_2-1$만큼 zero padding을 적용하고, 뒤집은 커널로 valid cross-correlation을 수행하는 연산이다.
Padding이 필요한 이유는 가장자리의 기울기도 계산해야 하기 때문이다. Padding 없이 커널이 dout 안에 완전히 들어오는 위치만 계산하면 결과 크기는 $(H_{\mathrm{out}}-k_1+1)\times(W_{\mathrm{out}}-k_2+1)$이 된다. 이 크기로는 원래 입력 전체의 기울기를 얻을 수 없고, dout이 커널보다 작으면 valid 연산 자체가 불가능하다.
위아래에 $k_1-1$만큼 padding을 넣으면 valid cross-correlation 결과의 높이는
\[H_{\mathrm{out}}+2(k_1-1)-k_1+1 =H_{\mathrm{out}}+k_1-1 =H\]이 된다. 너비도 같은 계산으로 $W$가 된다. 여기의 padding은 역전파를 계산하기 위한 것으로, 순전파에서 padding을 0으로 두었던 가정과 함께 성립한다.
따라서 입력 기울기를 계산하는 두 표현은 동등하다.
\[\begin{aligned} \frac{\partial L}{\partial x} &=\operatorname{full\_conv}\left(\frac{\partial L}{\partial y},k\right)\\[6pt] &=\operatorname{valid\_corr}\left( \operatorname{pad}\left(\frac{\partial L}{\partial y}\right),k_{\mathrm{flip}} \right). \end{aligned}\]수학적 convolution 식에서는 원래 커널 $k$를 사용한다. 이를 cross-correlation 연산으로 구현할 때 커널을 뒤집는다. Full convolution을 지원하는 연산은 범위 밖을 0으로 처리하므로 호출 전에 직접 padding을 넣을 필요는 없다. Padding을 명시적으로 넣는 것은 기존의 valid cross-correlation으로 같은 결과를 계산하려는 경우다.
슬라이딩 한 번과 stamp 한 번은 어떻게 대응할까?
Convolution을 슬라이딩으로 계산하면 한 위치에서 내적을 구해 dx의 원소 하나를 완성한다. 반면 stamp 한 번은 dout의 한 원소에 커널 전체를 곱해 dx의 여러 위치에 기여를 더한다. 두 방식이 같다는 것은 모든 위치를 처리한 뒤 얻는 최종 배열이 같다는 뜻이다.
대응 관계를 보기 위해 잠시 1차원 예제를 사용하자.
dout = [1, 2, 3]
k = [4, 5]
Full convolution을 슬라이딩으로 계산하려면 dout의 양쪽에 0을 한 칸씩 채우고, 뒤집은 커널 [5, 4]로 각 위치의 내적을 구한다.
padded dout = [0, 1, 2, 3, 0]
dx[0] = [0, 1] · [5, 4] = 4
dx[1] = [1, 2] · [5, 4] = 13
dx[2] = [2, 3] · [5, 4] = 22
dx[3] = [3, 0] · [5, 4] = 15
Stamp로 계산할 때는 dx를 0으로 초기화한 뒤, dout의 각 값에 원래 커널 [4, 5]를 곱해 해당 위치부터 +=로 누적한다. 아래 표의 각 행은 stamp 한 번이 더하는 기여다.
dout[0] = 1: [4, 5, 0, 0]
dout[1] = 2: [0, 8, 10, 0]
dout[2] = 3: [0, 0, 12, 15]
--------------
sum dx: [4, 13, 22, 15]
슬라이딩 한 위치에서 계산하는 값은 stamp 표의 한 열을 모두 더한 값이다. 예를 들어 dx[1]은 슬라이딩에서는 $1\cdot5+2\cdot4=13$이라는 내적 한 번으로 구한다. Stamp에서는 첫 번째 stamp가 5를, 두 번째 stamp가 8을 더해서 같은 13을 만든다.
2차원에서도 같은 관계가 성립한다. 슬라이딩은 dx의 한 좌표에 들어올 기여를 모아 계산하고, stamp는 dout[r,c] * k를 여러 입력 좌표에 나누어 누적한다.
작은 예제로 확인하기
다음 입력과 커널을 사용하자.
\[x= \begin{bmatrix} 1&2&3\\ 4&5&6\\ 7&8&9 \end{bmatrix}, \qquad k= \begin{bmatrix} 1&2\\ 3&4 \end{bmatrix}.\]순전파 결과는
\[y= \begin{bmatrix} 37&47\\ 67&77 \end{bmatrix}\]이다. 예를 들어 왼쪽 위 출력은
\[y(0,0) = 1\cdot1+2\cdot2+4\cdot3+5\cdot4 = 37\]로 계산한다.
기울기의 흐름을 확인하기 위해 손실을 다음과 같이 정하자.
\[L=y(0,0)+2y(1,1).\]그러면 출력 기울기는
\[\frac{\partial L}{\partial y} = \begin{bmatrix} 1&0\\ 0&2 \end{bmatrix}.\]커널 기울기
출력 기울기가 0이 아닌 위치는 $(0,0)$과 $(1,1)$이다. 커널 기울기는 각 위치에서 사용한 입력 영역에 해당 출력 기울기를 곱해 더하면 된다.
\[\begin{aligned} \frac{\partial L}{\partial k} &= 1\cdot \begin{bmatrix} 1&2\\ 4&5 \end{bmatrix} + 2\cdot \begin{bmatrix} 5&6\\ 8&9 \end{bmatrix}\\[6pt] &= \begin{bmatrix} 11&14\\ 20&23 \end{bmatrix}. \end{aligned}\]예를 들어 $k(0,0)$는 $y(0,0)$를 계산할 때 $x(0,0)=1$과, $y(1,1)$를 계산할 때 $x(1,1)=5$와 곱해진다. 따라서
\[\frac{\partial L}{\partial k(0,0)} = 1\cdot1+2\cdot5 = 11\]이다.
입력 기울기
각 출력은 자신을 계산할 때 사용한 입력 영역으로 기울기를 돌려보낸다. 이때 각 입력으로 전달하는 값은 출력 기울기에 해당 커널 원소를 곱한 값이다.
$y(0,0)$의 기울기 1은 왼쪽 위 입력 영역에 $k$를 더하고, $y(1,1)$의 기울기 2는 오른쪽 아래 입력 영역에 $2k$를 더한다.
\[\begin{aligned} \frac{\partial L}{\partial x} &= \begin{bmatrix} 1&2&0\\ 3&4&0\\ 0&0&0 \end{bmatrix} + \begin{bmatrix} 0&0&0\\ 0&2&4\\ 0&6&8 \end{bmatrix}\\[6pt] &= \begin{bmatrix} 1&2&0\\ 3&6&4\\ 0&6&8 \end{bmatrix}. \end{aligned}\]중앙 입력 $x(1,1)$에는 두 출력에서 기울기가 전달된다. $y(0,0)$에서는 $k(1,1)=4$, $y(1,1)$에서는 $k(0,0)=1$과 곱해졌으므로,
\[\frac{\partial L}{\partial x(1,1)} = 1\cdot4+2\cdot1 = 6\]이다.
같은 결과를 패딩과 슬라이딩으로 계산하기
이 예제에서 커널은 $2\times2$이므로 dout의 네 방향에 각각 한 칸씩 0을 채운다. 슬라이딩에 사용할 커널은 $180^\circ$ 회전한다.
이 $4\times4$ 배열 위에서 뒤집은 $2\times2$ 커널로 valid cross-correlation을 하면 입력과 같은 $3\times3$ 크기의 기울기를 얻는다. 예를 들어 왼쪽 위에서는
\[\frac{\partial L}{\partial x(0,0)} =0\cdot4+0\cdot3+0\cdot2+1\cdot1 =1\]이고, 중앙에서는
\[\frac{\partial L}{\partial x(1,1)} =1\cdot4+0\cdot3+0\cdot2+2\cdot1 =6\]이다. Stamp로 누적했던 값과 같다. Padding 없이 valid cross-correlation만 하면 중앙의 값 6 하나만 얻고, 나머지 입력 위치의 기울기는 계산하지 못한다.
Stamp 방식은 각 출력이 입력으로 보내는 기여를 찍어서 더한다. 슬라이딩 방식은 각 입력 위치에서 받을 기여를 모아 더한다. 같은 기여를 서로 다른 순서로 합산하므로 두 방식의 결과는 동일하다.