논문을 상세히 번역하고 한단어씩 해석해주는 포스팅은 많다.
나는 논문을 누구나 알아듣도록 쉽고 간결하게 전달하고자 한다.
ResNet
저자의 의도
일반적인 구조의 CNN에서는 레이어를 늘리면 늘릴수록 정확도가 올라가지 않는다.
이유는 비밀이다. 논문을 읽으면 다 나와있다. (밑에 기존 문제점에 적어놨다.)
저자는 레이어를 계속 늘려도 정확도가 계속해서 좋아지는 구조를 만들고 싶다.
기존 문제점
일반적인 구조의 CNN은 unreferenced function을 사용한다. (일반적인 F(x))
쉽게 풀어서 이야기 하면, 함수를 지나고 나면 원래 데이터의 identity가 제대로 reference되지 않는다는 의미.
해결 아이디어
1. Residual learning(Shortcut)
unreferenced function이 아니라, residual function을 사용하자.
unreferenced function, F(x) = (w1 * x1) + w0 는 원래 근본 정보를 잃는다.
반면 residual function, H(x) = ((w1 * x1) + w0) + x 는 마지막에 x를 더해줘 원래 근본 정보를 잃지 않는다.
저자는 이 작업을 2개 레이어 마다 반복해서 하고자 하고
이를 빌딩 블럭이라고 칭한다.
2. Bottleneck
각 빌딩 블럭을 보틀넥 블럭 구조로 수정하자.
여기서 두가지 이득을 볼 수 있다.
3x3, 64 레이어로 들어가는 데이터를 주목하자.
왼쪽 일반 블럭에서는 3x3 커널사이즈의 필터 64개가 들어온다.
오른쪽 보틀넥 블럭에서는 1x1 커널사이즈의 필터 64개가 들어온다.
전체적인 연산량에서는 크게 영향이 없다. 오히려 증가한다.
하지만 이 3x3 64 레이어가 연산하는 양이 약 2/3으로 줄어들고,
메모리도 다량 아낄 수 있다. 말그대로 효율적이다.
앞서 말한 x. 즉, identity를 주목하자.
왼쪽 일반 블럭에서는 차원 증폭을 위해 projection이 필요하다.
오른쪽 보틀넥 블럭에서는 projection 없이 그대로 더하면 된다.
이 연산만큼이 이득.
3. 이게 성립하는 이유는?
보통 다른 사람들이 간과하는 이 점을 눈여겨 보길 바란다.
그렇다면 이런 작업이 왜 성립할까?
딥러닝 겁나 똑똑한거 아니었나? 레이어 추가한다고 정확도가 떨어져?
이 두 기법의 효과가 성립하는 이유는 다음과 같다.
첫째 gradient descent가 발생한다.
딥러닝 모델은 기본적으로 통계 모델이다.
optimizer는 gradient를 계산하고, 그 값을 back propagation 시킨다.
그런데 레이어가 깊어지면 깊어질수록 이 gradient 계산하는 수식이 반복된다.
반복되는 수식에 의해 우리가 전달하고자 하는 값보다 훨씬 약해진 값을 전달한다.
이런 현상은 residual connection이 어느정도 해결해 줄 수 있다.
둘째 데이터 사이언스에서 가장 중요한건 '데이터'다.
아무리 날고 기는 기법을 적용해도 데이터의 질이 좋아야 정확한 예측이 가능하다.
딥러닝에서 레이어가 깊어지면 깊어질수록 데이터는 희석된다. (뭔가 첫째와 비슷한 내용)
각 은닉층이 아무리 좋은 feature를 찾아냈다고 한들
가장 근본이 되는 데이터보다 질이 좋을 수 없다.
다음 그림을 보자.
범블비다.
(범블비가 뭔데 틀딱아...)
우리는 이 자동차를 다른 사물과 구분할 때 뭘 볼까?
바퀴가 있다. 앞유리창이 있다. 자동차 모양이다(?) 등등일 것이다.
하지만 컴퓨터는 이런 feature를 사용해서 자동차인지 인식한다.
high level feature를 보면 실제로 바퀴를 찾는 듯한 그림이 보인다.
인간과 유사한 작업을 하는 것이다.
그런데 이 레이어가 깊어지면 깊어질수록 딥러닝이는 더 기괴한 짓을 한다.
예를 들어 바퀴의 주름 하나하나를 세는 것과 같은 별 이상한 짓을 한다.
즉 feature만 보고 예측하다 보니... 본분을 잊고 이상한 짓을 하는 것.
근데 이때 앞에서 부터 원본 정보를 계속 넣어서 상기시켜주면
딥러닝이가 정신을 차릴 수 있다는 개념이다.
즉 가장 근본의 데이터가 가장 질좋은 데이터이기 때문이다.
하지만 이것도 한계가 있다.
실제로 논문에서 152레이어가 넘어가면 효과가 떨어지기 시작한다.
코드 구현
나는 이미지넷이 아니라 Cifar10으로 resnet 학습하는 코드 전체를 구현했다.
근데 그런건 뭐... 궁금하면 들어가서 읽자. (전체 코드 링크)
사실 중요한건 Residual learning, bottleneck 2개만 보면 된다.
1. Residual learning(Shortcut)
class BasicBlock(nn.Module):
expand = 1
def __init__(self, in_size, out_size, stride=1):
super(BasicBlock, self).__init__()
self.cnn1 = nn.Conv2d(in_size, out_size, 3, stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_size)
self.cnn2 = nn.Conv2d(out_size, out_size, 3, padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_size)
self.identity = nn.Sequential()
if stride != 1:
self.identity = nn.Sequential(
nn.Conv2d(in_size, out_size, 1, stride=stride, bias=False),
nn.BatchNorm2d(out_size)
)
def forward(self, x):
i = self.identity(x)
x = self.bn1(self.cnn1(x))
x = F.relu(x)
x = self.bn2(self.cnn2(x))
x = F.relu(x + i)
return x
별거 없다.
그냥 그림이랑 똑같이 conv 레이어 2개를 깔고
포워드 하면서 마지막 렐루 직전에 identity 를 더해주면 끝.
(stride 조건문은 resnet 전체를 구현할 때 텐서 크기가 안맞는 이유 때문에 사용한다. 무시하자.)
2. Bottleneck
class BottleNeckBlock(nn.Module):
expand = 4
def __init__(self, in_size, out_size, stride=1):
super(BottleNeckBlock, self).__init__()
self.cnn1 = nn.Conv2d(in_size, out_size, 1, stride=stride, bias=False)
self.bn1 = nn.BatchNorm2d(out_size)
self.cnn2 = nn.Conv2d(out_size, out_size, 3, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_size)
self.cnn3 = nn.Conv2d(out_size, out_size * self.expand, 1, bias=False)
self.bn1 = nn.BatchNorm2d(out_size * self.expand)
self.identity = nn.Sequential()
if stride != 1 or in_size != out_size * self.expand:
self.identity = nn.Sequential(
nn.Conv2d(in_size, out_size * self.expand, 1, stride=stride, bias=False),
nn.BatchNorm2d(out_size * self.expand)
)
def forward(self, x):
i = self.identity(x)
x = self.bn1(self.cnn1(x))
x = F.relu(x)
x = self.bn2(self.cnn2(x))
x = F.relu(x)
x = self.bn3(self.cnn3(x))
x = F.relu(x + i)
return x
이것도 별거 없다.
그냥 2개의 conv 레이어가 3개의 conv 레이어로 바뀌면 된다.
그리고 차원이 안맞을 터이니 self.expand를 설정하고 필요할때마다 꺼내서 곱해준다.
끝이다.
'논문리뷰' 카테고리의 다른 글
[논문 리뷰] Vision Transformer(ViT) 요약, 코드, 구현 (0) | 2023.03.22 |
---|---|
[논문 리뷰] UNet 요약, 코드, 구현 (0) | 2023.03.20 |
[논문 리뷰] Inception v1 요약, 코드, 구현 (0) | 2023.03.16 |
[논문 리뷰] VGGNet 요약, 코드, 구현 (0) | 2023.03.15 |
[논문 리뷰] Transformer (Attention Is All You Need) 요약, 코드, 구현 (0) | 2023.03.14 |