Residual Connection
Residual Connection 은 Skip Connection 이라고도하는 계층사이를 연결하는 연결방식이다. 네트워크를 더 깊게 만들수록 표현력이 오히려 떨어지는 현상을 보고, 이런일은 optimize 를 잘 수행하지 못하기 때문이라고 생각해서 이전 Layer 보다 학습이 덜되지는 않게 이전 학습된 결과에 이번 Layer에서 더 학습할 잔여 학습이 있다면 이를 학습하는 방식이다. 의미적으로는 이번에 학습할 게 전혀없고 쓸데없는 Layer 라면 최소한 이전 Layer 의 결과를 그대로 출력하는 방식을 의미한다. 그래서 신경망이 깊어질수록 optimize 가 덜 되지는 않도록 한다. 사실 실제에서는 이전 결과를 그대로 출력하는 일 ( Identity mapping ) 은 거의 발생하지 않는다.
계산 및 미분 그래프식
$ x $ : 입력
$ f(x) $ : Residual Block ( 잔여 학습을 수행하는 블럭 )
$ y $ : 출력
$ y = f(x) + x $
$ \frac{\partial y}{\partial x} = \frac{\partial y}{\partial f} \frac{\partial f}{\partial x} + 1 $
$ \frac{\partial L}{\partial x} = \frac{\partial L}{\partial y}\frac{\partial y}{\partial f}\frac{\partial f}{\partial x} + \frac{\partial L}{\partial y} $
Skip Connection 의 Gradient : $ \frac{\partial L}{\partial y} $
Residual Block 의 Gradient : $ \frac{\partial L}{\partial y} \frac{\partial y}{\partial f} \frac{\partial f}{\partial x} $
여기서 $ \frac{\partial y}{\partial f} $ 는 1 이고, Layer F 에 흘러들어가는 Upstream Gradient 는 $ \frac{\partial L}{\partial y} $ 그 자체이다. 이후, Layer F 에서 x 로 가는 Gradient 계산은 그냥 Backprop 계산을 수행하면 됨.
같은 변수 $ x $ 에서 가지친 Gradient 는 합쳐지기 때문에 두 Gradient 를 합하면 Downstream Gradient 가 된다.
코드 구현
Residual Block 은 기존 방식 그대로 Forward 에는 $ x $ 를 입력받아 $ y $ 를 출력하고, 역전파 시에 Upstream gradient $ \frac{\partial L}{\partial y} $ 를 입력받아 $ \frac{\partial L}{\partial x} $ 를 구하면 된다.
Forward 출력은 입력과 Residual Block 출력을 더한 값이 출력이고, Downstream Gradient 는 Upstream Gradient $ \frac{\partial L}{\partial y} $ 과 Residual Block 에서 내려온 Gradient 를 더해주어 Downstream 으로 흘려주면된다.
ResNet 을 구현하기 위해서 병렬 처리가 가능한 Layer 를 정의한다.
저장된 Layer 별로 동일한 입력에 대한 출력을 만들고, 이들을 Concatenate 로 묶어주고 출력한다.
ResNet 에서는 Layer 하나는 $ y = x $ 인 Layer, 다른 하나는 Residual Block 을 수행하는 Layer 으로 지정한다.
class ParallelLayer(Layer):
def __init__(self, *args):
self.layers = args
def build(self, input_shape):
for layer in self.layers:
layer.build(input_shape)
def forward(self, inputs):
x = inputs
branches = []
for layer in self.layers[1:]:
branches.append(layer.forward(inputs))
y = branches[0]
for branch in branches[1:]:
y = np.c_[y, branch]
return y
def backprop(self, dLdy, optimizer):
dLdx = self.layers[0].backprop(dLdy)
for layer in self.layers[1:]:
dLdx += layer.backprop(dLdy, optimizer)
return dLdx
class ResNetLayer(ParallelLayer):
def __init__(self, residual_block):
skip_layer = IdentityLayer()
super(ResNetLayer, self).__init__(skip_layer, residual_block)
'머신러닝 > CustomFramework' 카테고리의 다른 글
Batch Normalization (0) | 2022.04.03 |
---|---|
Dropout (0) | 2022.04.03 |
L1 L2 Regularizer (0) | 2022.04.03 |
Convolution 2D Layer (0) | 2022.03.26 |
Dense (Fully Connect) Layer (0) | 2022.03.21 |