논문을 상세히 번역하고 한단어씩 해석해주는 포스팅은 많다.
나는 논문을 누구나 알아듣도록 쉽고 간결하게 전달하고자 한다.
Inception v1
저자의 의도
이 당시에는 깊은 네트워크에 대한 아이디어 창출이 가장 핫했다.
저자는 gradient vanishing이 없는 더 깊은 네트워크를 만들고자 했다.
특히 Hebbian principle이라는 뉴로사이언스 기법을 활용하고자 했다.
또한 다양한 필터를 사용하되, 가장 효율적인 방법을 찾고자 했다.
기존 문제점
CNN의 네트워크 구조는 깊어질수록 gradient vanishing이 나타날 수 밖에 없다.
그 이유는 간단하게 설명하면 2가지 인데,
1. 활성화 함수에 미분이 0인 지점이 있다.
2. 퍼포먼스를 고려해 반드시 역전파 알고리즘을 사용한다.
나는 여기에 이상한 점을 느껴 chatGPT와 30분 동안 토론을 했지만
결국엔 그냥 받아들여야 하는 문제라고 생각했다.
(그래도 수긍이 안되면 힌튼 교수님이 forward-forward 알고리즘 논문을 발표했으니 그걸 보자...)
또다른 문제는, 입력 이미지의 사이즈가 일정하지 않으면 정확도가 급격히 떨어진다.
모델을 학습시킬 때랑 사용할 때 입력 이미지의 사이즈를 잘 맞춰줘야 한다는 것.
그리고 여러 종류의 필터를 사용하기 위해서는 여러 종류의 커널을 사용해야 한다.
여러 커널을 쓰는 것은 당연한 내용이지만, 이렇게 하면 메모리가 작살난다.
해결 아이디어
1. Inception module
인셉션 모듈은 다양한 크기의 필터를 함께 사용하기 위해 고안된 효율지향 블럭이다.
기본적인 컨셉은 1x1, 3x3, 5x5, maxpooling 4개의 레이어에 모두 통과시키고 concatenate한다.
각 레이어는 역할을 가지고 있는데,
1x1 : 채널간 선형 결합을 진행하여 채널 수가 줄어든 원본 정보를 전달한다.
3x3, 5x5 : 작은 크기, 큰 크기의 특징을 추출한다.
maxpooling : 컨볼루션 레이어에 비해 대상의 위치 이동에 영향을 받지 않고 정보를 전달한다.
그런데 딱봐도 겁나 무겁게 생겼다.
그래서 그림의 오른쪽 처럼 1x1을 사용해 차원축소를 해준다.
계산량도 감소하고 복잡도도 감소하고 병목 현상도 예방해준다.
이 빌딩 블럭을 기본으로 해서 전체 구조인 GoogLeNet을 설계할 예정이다.
2. Auxiliary classifier
전체 GoogLeNet 구조를 보고 있으면 중간 중간에 가지가 뻗어 나가 끊긴다.
이게 이 논문의 핵심이다. 인셉션 모듈보다 이 방법이 퍼포먼스를 크게 향상시켰을 것이다.
중간 지점에 해당하는 gradient와 적은 수의 파라미터로 역전파를 진행하게 유도한다.
결론적으로 gradient vanishing이 완화되는 효과를 가져온다.
네트워크는 깊어질수록 gradient vanishing이 가중화될건데,
끝까지 학습이 된게 아니라 소실이 덜 된 그라디언트를 전달하는 방법이다.
전체 구조를 보면 총 3개의 auxiliary classifier가 있는데
각각 0.3, 0.4, 0.5의 가중치를 부여해 역전파에 사용된다.
3. Konbs and Levers
논문에서는 그리 대단하게 표현되고 있지 않지만
엔지니어 였던 나에게 눈에띄는 아이디어였다.
인셉션 모듈 내에서 필터수와 차원수를 튜닝하는 도구인데
이게 있으면 실험을 더 빠르고 효율적으로 진행할 수 있다.
노브는 필터의 개수와 차원 수를 조절해주고,
레버는 입력 데이터의 채널 수와 차원 수를 조절해준다.
실험 인터페이스에서 노브와 레버만 돌려주면
빠르고 정확하게 실험할 수 있다. 혁신 그자체....
논문 구현
import torch
import torch.nn as nn
import torch.nn.functional as F
class InceptionModule(nn.Module):
def __init__(self, in_channels, out_1x1, reduce_3x3, out_3x3, reduce_5x5, out_5x5, out_pool):
super(InceptionModule, self).__init__()
# 1x1 conv branch
self.branch1 = nn.Sequential(
nn.Conv2d(in_channels, out_1x1, kernel_size=1),
nn.BatchNorm2d(out_1x1),
nn.ReLU(inplace=True)
)
# 3x3 conv branch
self.branch2 = nn.Sequential(
nn.Conv2d(in_channels, reduce_3x3, kernel_size=1),
nn.BatchNorm2d(reduce_3x3),
nn.ReLU(inplace=True),
nn.Conv2d(reduce_3x3, out_3x3, kernel_size=3, padding=1),
nn.BatchNorm2d(out_3x3),
nn.ReLU(inplace=True)
)
# 5x5 conv branch
self.branch3 = nn.Sequential(
nn.Conv2d(in_channels, reduce_5x5, kernel_size=1),
nn.BatchNorm2d(reduce_5x5),
nn.ReLU(inplace=True),
nn.Conv2d(reduce_5x5, out_5x5, kernel_size=5, padding=2),
nn.BatchNorm2d(out_5x5),
nn.ReLU(inplace=True)
)
# maxpool branch
self.branch4 = nn.Sequential(
nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
nn.Conv2d(in_channels, out_pool, kernel_size=1),
nn.BatchNorm2d(out_pool),
nn.ReLU(inplace=True)
)
def forward(self, x):
out1 = self.branch1(x)
out2 = self.branch2(x)
out3 = self.branch3(x)
out4 = self.branch4(x)
outputs = [out1, out2, out3, out4]
return torch.cat(outputs, 1)
블럭 자체는 아주 간단하다.
4개의 브런치로 쪼개지면서 차원축소 및 각 레이어를 통과하고 나온다.
그리고 마지막에 concatenate해주면 된다.
끝.
'논문리뷰' 카테고리의 다른 글
[논문 리뷰] Vision Transformer(ViT) 요약, 코드, 구현 (0) | 2023.03.22 |
---|---|
[논문 리뷰] UNet 요약, 코드, 구현 (0) | 2023.03.20 |
[논문 리뷰] VGGNet 요약, 코드, 구현 (0) | 2023.03.15 |
[논문 리뷰] Transformer (Attention Is All You Need) 요약, 코드, 구현 (0) | 2023.03.14 |
[논문 리뷰] ResNet 요약, 코드, 구현 (0) | 2023.03.07 |