다운로드
작성자: admin 작성일시: 2019-01-22 11:19:07 조회수: 6383 다운로드: 115
카테고리: 기타 태그목록:

Inception(GoogLeNet)

2014년 IRSVRC에서 1등을 한 모델이다. 대회 이후 Going Deeper with Convolutions라는 논문에서, Inception이란 이름으로 발표했다. Inception은 이 후 여러 버전이 발표 되었는데 이번에 설명할 것은 Inception v1이다. IRSVRC 대회에 참여할 당시 팀 이름인 GoogLeNet 이라고도 불린다. 다른 버전과의 혼동을 방지 하기 위해서 Inception v1에 대한 설명은 GoogLeNet으로 하겠다.

구글의 가설

딥러닝에서는 대용량 데이터를 학습할 때, 일반적으로 망이 깊고 레이어가 넓을 수록 성능이 좋다는 것이 정설이다. 하지만, 현실적으로는 네트워크를 크게 만들면, 파라미터가 많이 늘어나고, 망이 늘어 날 때마다, 연산량이 지수적으로 많아지며, 과적합, Gradient vanishing 등의 문제 때문에 학습이 매우 힘들다.

이를 해결하기 위한 방안 중 하나로 제시된 것이 Sparse Connectivity 이다. 지금까지 배운 convolution 연산은 Densely 연결 되어 있다. 이를 높은 관련성(correlation)을 가진 노드들 끼리만 연결하도록, 다시 말해 노드들 간의 연결을 sparse 하도록 바꾼다면, 연산량과 파리미터 수가 줄고 따라서 overfitting 또한 개선 될 것이라고 생각했다. Fully connected network에서 사용하는 Dropout과 비슷한 기능을 할 것이라고 본 것이다.
하지만, 실제로는 Dense matrix연산 보다 Sparse Matrix연산이 더 큰 Computational resource를 사용한다. LeNet 시절의 CNN만 하더라도 Sparse 한 CNN 연산을 사용했다. 이 후, 연산을 병렬처리 하기위해서 Dense Connection을 사용했고, Dense Matrix의 연산 기술이 발전했다. 반면, Sparse Matrix연산은 그 만큼 발전하지 못했고, Dense Matrix 연산 보다 비효율적이다. 따라서, 위의 목적을 달성하기 위해서 Sparse connectivity를 사용하는 것은 해결방안이 될 수 없었다.

여기에서, 구글이 고민한 것은 어떻게 노드 간의 연결을 줄이면서(Sparse connectivity), 행렬 연산은 Dense 연산을 하도록 처리하는가 였다. 그리고, 이 고민의 결과가 Inception module 이다.

Inception module

그림 18.4.1 : Inception module

GoogLeNet이 저렇게 깊은 망을 만들고도 학습이 가능했던 것은 Inception Module 덕분이다. 그림 18.4.1 은 Inception module을 자세히 나타낸 것이다. 입력값에 대해, 4가지 종류의 연산을 수행하고, 4개의 결과를 채널 방향으로 합친다. 이러한 Inception module이 모델에 총 9개가 있다. Inception module에 대해 더 자세히 알아보겠다.

Inception module의 4가지 연산은 각 각,

  • 1x1 convolution
  • 1x1 convolution 후, 3x3 convolution
  • 1x1 convolution 후, 5x5 convolution,
  • 3x3 MaxPooling후 1x1 convolution
  • 그리고, 이 결과를 Channel-wise concat(feature map을 쌓는 것)

이 중, 1x1 convolution 연산은 모호하게 여겨질 것이다. 하지만, 1x1 convolution은 Inception module에서 핵심 역할을 한다. 1x1 convolution 연산의 기능은 두가지 이다.

  • 첫번째는 채널의 수를 조절하는 기능이다. 채널의 수를 조정한다는 의미는 다시 말해, 채널 간의 Correlation을 연산한다는 의미라고 할 수 있다. 기존의 convolution 연산은, 예를 들어 3x3의 커널을 이용해 연산을 할 경우, 3x3 크기의 지역 정보와 함께 채널 간의 정보 또한 같이 고려하여 하나의 값으로 나타낸다. 다르게 말하면, 하나의 커널이 2가지의 역할을 모두 수행해야 한다는 것이다. 대신 이전에 1x1 convolution을 사용한다면, 1x1은 채널을 조절하는 역할을 하기 때문에, 최적화 과정에서 채널 간의 특징을 추출 할 것이고, 3x3은 이미지의 지역정보에만 집중하여 특징을 추출하려 할 것이다. 역할을 세분화 해준 것이다. 채널간의 관계정보는 1x1 convolution에 사용되는 파라미터들 끼리, 이미지의 지역 정보는 3x3 convolution에 사용되는 파라미터들 끼리 연결된다는 점에서 노드 간의 연결을 줄였다고 볼 수 있다.

  • 그리고 두번째는 1x1 convolution연산으로 이미지의 채널을 줄여준다면, 3x3과 5x5 convolution 레이어에서의 파라미터 개수를 절약 할 수 있다. 이 덕분에 망을 기존의 CNN 구조들 보다 더욱 깊게 만들고도 파라미터가 그리 크지 않다.

아래의 코드는 Inception module을 구현 한 것 이다.

In [1]:
# Inception 모듈 정의
def inception_module(x, o_1=64, r_3=64, o_3=128, r_5=16, o_5=32, pool=32):
    """
    # Arguments 
    x : 입력이미지
    o_1 : 1x1 convolution 연산 출력값의 채널 수 
    r_3 : 3x3 convolution 이전에 있는 1x1 convolution의 출력값 채널 수
    o_3 : 3x3 convolution 연산 출력값의 채널 수 
    r_5 : 5x5 convolution 이전에 있는 1x1 convolution의 출력값 채널 수 
    o_5 : 5x5 convolution 연산 출력값의 채널 수 
    pool: maxpooling 다음의 1x1 convolution의 출력값 채널 수
    
    # returns
    4 종류의 연산의 결과 값을 채널 방향으로 합친 결과 
    """
    
    x_1 = layers.Conv2D(o_1, 1, padding='same')(x)
    
    x_2 = layers.Conv2D(r_3, 1, padding='same')(x)
    x_2 = layers.Conv2D(o_3, 3, padding='same')(x_2)
    
    x_3 = layers.Conv2D(r_5, 1, padding='same')(x)
    x_3 = layers.Conv2D(o_5, 5, padding='same')(x_3)
    
    x_4 = layers.MaxPooling2D(pool_size=(3, 3), strides=1, padding='same')(x)
    x_4 = layers.Conv2D(pool, 1, padding='same')(x_4)
    
    return layers.concatenate([x_1, x_2, x_3, x_4])

GoogLeNet의 구조

GoogLeNet

유형 입력 크기 출력 크기 커널 크기 스트라이드 활성화함수 inception module
입력 (224,224,3)
Conv (224,224,3) (56,56,96) (7,7) 2 ReLU
maxpool (56,56,256) (27, 27, 256) (3,3) 2
LRN
Conv (56,56,96) (56,56,64) (1,1) 1 ReLU
Conv (56,56,64) (56,56,192) (3,3) 1 ReLU
LRN
maxpool (56,56,192) (28, 28,192) (3,3) 2
Inception module (27,27,192) (28,28,256) ReLU 64,(96,128),(16,32),32
Inception module (27,27,256) (28,28,480) ReLU 128,(128,192),(32,96),64
maxpool (28,28,480) (14,14,480) (3,3) 2
Inception module (14,14,256) (14,14,512) ReLU 192,(96,208),(16,48),64
Inception module (14,14,512) (14,14,512) ReLU 160,(112,224),(24,64),64
Inception module (14,14,512) (14,14,512) ReLU 128,(128,256),(24,64),64
Inception module (14,14,512) (14,14,528) ReLU 112,(144,288),(32,64),64
Inception module (14,14,528) (14,14,832) ReLU 256,(160,320),(32,128),128
maxpool (14,14,832) (7,7,832) (3,3) 2
Inception module (7,7,832) (7,7,832) ReLU 256,(160,320),(32,128),128
Inception module (7,7,832) (7,7,1024) ReLU 384,(192,384),(48,128),128
Average pool (7,7,1024) (1,1,1024) (7,7) 1
Dropout(0.4) (1,1,1024) (1,1,1024)
FCN (1,1,1024) (1,1,1000)
softmax (1,1,1000) (1,1,1000)
표 18.4.3 : GoogLeNet 구조

위 표에서 inception module 열의 의미는, 예를 들어 "64,(96,128),(16,32),32" 는 inception module 그림(18.4.1) 에서 가장 왼쪽의 1x1 convolution 연산의 출력 피쳐맵의 갯수가 64, 두번째 1x1, 3x3 convolution 연산의 출력 피쳐맵의 갯수는 각각 96,128, 세번째 1x1, 5x5 convolution 연산의 출력 피쳐맵의 갯수는 각각 16,32 마지막 maxpooling 후의 1x1 convolution 연산의 출력 피쳐맵의 갯수는 32 란 의미 이다.

모델의 첫번째 레이어를 보면, 일반적인 CNN 모델과 크게 다르지 않다. 11x11의 커널 사이즈와 4의 strides로 convolution 연산을 한다. 이 첫번째 레이어를 Stem layer라고 부른다. 이 첫번째 레이어를 변경했을 때, 학습에 큰 영향을 주었기에 이 부분은 inception v3 까지 유지가 된다. 모델의 마지막에는 피쳐맵의 크기와 같은 커널 사이즈로(이 경우에는 7x7) Average Pooling을 수행하고, fully connected layer와 softmax 함수로 1000개의 클래스에 대한 예측을 수행한다. 파라미터 수를 늘리는 가장 큰 요인 중 하나가 fully connected layer인데 이를 적게 사용함으로써 파라미터 수를 더 절약 할 수 있었다.

표에는 묘사되지 않았지만, 논문을 확인해보면 중간에도 예측하는 부분이 있다는 것을 발견 할 수 있다.(https://arxiv.org/pdf/1409.4842.pdf) 이 부분을 "auxiliary classifiers"라고 부른다. 깊은 망을 구성한 탓에 gradient가 모든 레이어로 잘 흘러가지 않을 수 있다고 생각하여, 모델 중간에 예측하는 부분을 추가하여, 여기에서 나온 loss 값을 최종 loss에 반영하는 트릭을 사용했다. 모델 중간에서도 예측을 함으로써, 일종의 정규화 효과를 기대한 것이다. auxiliary classifier 에서 계산된 loss 값들은 0.3의 가중치와 곱해져, 최종 loss 값에 더해졌다. 그리고, 테스트시에는 이 부분을 사용하지 않았다. GoogLeNet에서는 두 개의 auxiliary classifier가 존재한다.

구현

구현함에 있어, 코드의 간결함을 위해 auxiliary classifiers 부분은 생략하였고, Local response normalization은 Batch normalization으로 대체 하였다.

In [2]:
from keras import layers
from keras.datasets import mnist
from keras.models import Sequential, Model
from keras.optimizers import Adam
from keras.models import load_model
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
Using TensorFlow backend.
In [3]:
img_shape = (224, 224, 3)

input_ = layers.Input(shape=img_shape)
x = layers.Conv2D(64, 7, strides=2, padding='same')(input_)
x = layers.MaxPooling2D(pool_size=(3, 3), strides=2, padding='same')(x)
x = layers.BatchNormalization()(x)
x = layers.Conv2D(64, 1, strides=1)(x)
x = layers.Conv2D(192, 3, strides=1, padding='same')(x)
x = layers.BatchNormalization()(x)
x = layers.MaxPooling2D(pool_size=(3, 3), strides=2, padding='same')(x)
x = inception_module(x, o_1=64, r_3=64, o_3=128, r_5=16, o_5=32, pool=32)
x = inception_module(x, o_1=128, r_3=128, o_3=192, r_5=32, o_5=96, pool=64)
x = layers.MaxPooling2D(pool_size=(3, 3), strides=2, padding='same')(x)
x = inception_module(x, o_1=192, r_3=96, o_3=208, r_5=16, o_5=48, pool=64)
x = inception_module(x, o_1=160, r_3=112, o_3=224, r_5=24, o_5=64, pool=64)
x = inception_module(x, o_1=128, r_3=128, o_3=256, r_5=24, o_5=64, pool=64)
x = inception_module(x, o_1=112, r_3=144, o_3=288, r_5=32, o_5=64, pool=64)
x = inception_module(x, o_1=256, r_3=160, o_3=320, r_5=32, o_5=128, pool=128)
x = layers.MaxPooling2D(pool_size=(3, 3), strides=2, padding='same')(x)
x = inception_module(x, o_1=256, r_3=160, o_3=320, r_5=32, o_5=128, pool=128)
x = inception_module(x, o_1=384, r_3=192, o_3=384, r_5=48, o_5=128, pool=128)
x = layers.AveragePooling2D(pool_size=(7, 7), strides=1)(x)
x = layers.Dropout(0.4)(x)
x = layers.Dense(1000)(x)
output = layers.Activation('softmax')(x)

googlenet = Model(input_, output)
In [4]:
# googlenet.summary()
print("전체 파라미터 수 : {}".format(sum([arr.flatten().shape[0] for arr in googlenet.get_weights()])))
전체 파라미터 수 : 6956536

이전에 학습한 AlexNet은 전체 파리미터의 수가 62,380,904 이었다. GoogLeNet은 훨씬 깊은 모델이면서도, 6,955,768 로 AlexNet의 1/10 정도 밖에 되지 않는다.

Inception V2 & V3

Inception V2와 Inception V3는 이전의 GoogLeNet을 발전시킨 형태로 같은 논문에서 발표가 되었다. 다음 두 가지의 문제를 정의하고 이에 대한 해결책을 제시 했다.

  1. representational bottleneck : 뉴럴넷 안에서 차원을 과도하게 줄였을 때 정보의 양이 크게 줄어드는 현상
  2. factorization : 기존의 convolution 연산에서 사용하는 커널을 개선하면 연산의 복잡도가 더 줄어들 수 있다.

두 가지 문제에 대해 제시한 해결책은 다음과 같다.

factorization

VGGNet에서 보았듯이 3x3 convolution 연산을 두 번 쌓는 것이 5x5 convolution 연산을 한 번 하는 것 보다 더 좋다. InceptionV2와 V3에서는 이 점을 받아들여, 기존의 5x5 convolution 연산을 두 번의 3x3 convolution 연산을 교체 하였다. 다음 그림은 이를 반영한 inception module이다. 이를 편의 상, inception module-A 라고 하겠다.

그림 18.4.2 : Inception module-A

그리고, 기존에 사용하던 3x3 convolution을 1x3, 3x1 convolution 연산으로 바꿨다. 예를 들어, (3,3,3) 형태의 이미지에 대해 convolution 연산을 한다면, 3x3 커널의 경우, 입출력을 같게 하기위해 padding을 사용한 경우, 3x3x3의 파라미터를 사용해 9번의 연산을 해야한다. 곱 연산만 고려했을 때, 243번의 곱을 하게된다. 같은 경우에 1x3, 3x1의 커널을 사용하면 각각 3x3의 파라미터를 사용하고 9번의 연산을 해야한다. 총 162번의 곱을 한다. 다음 그림은 이를 반영한 inception module이다. 이를 inception module-B 라고 하겠다.

그림 18.4.3 : Inception module-B

representational bottleneck

representational bottleneck 문제를 해결하기 위해서 논문의 저자들은 피쳐맵의 크기를 효과적으로 줄이는 방법을 고민 했다. Pooling을 먼저 하는 것이 좋을까? convolution 연산을 먼저 하는 것이 좋을까? 그에 대한 대답은 둘다 비슷하다는 것이다. Pooling을 먼저하면 연산량이 줄어 들겠지만 representational bottleneck이 발생한다. 그렇다고 convolution을 먼저 하면 연산량이 많아진다. 무엇을 먼저 쓰는 것에 대한 뚜렷한 장점이 없다고 생각하여 이를 병렬적으로 수행하고 합치는 방법을 사용한다. 그 방법은 다음 그림과 같다.

그림 18.4.4 : Inception v2,v3의 피쳐맵 축소 방법, Reduction

그리고 마지막 Average pooling 전의 Inception module을 더 넓게 형성 했다. 차원을 줄이면서 발생하는 정보 손실을 막기위한 방법으로 같은 입력값에 대하여 다양한 연산을 하도록 했다. 다음 그림은 이를 반영한 inception module이다. 이를 inception module-C 라고 하겠다.

그림 18.4.5 : Inception module-C

Inception V2, V3의 구조

Inception V2와 V3에서는 방금 소개한 세가지 타입의 Inception module-A,B,C 를 조합하여 모델을 구성하였다.

유형 입력 크기 출력 크기 커널 크기 스트라이드
입력 (299,299,3)
Conv (299,299,3) (149,149,32) (3,3) 2
Conv (149,149,32) (147,147,32) (3,3) 1
Conv (147,147,32) (147,147,64) (3,3) 1
maxpool (147,147,64) (73,73,64) (3,3) 2
Conv (73,73,64) (73,73,80) (3,3) 1
Conv (73,73,80) (71,71,192) (3,3) 1
Conv (73,73,80) (71,71,192) (3,3) 1
maxpool (71,71,192) (35,35,192) (3,3) 2
Inception-A (35,35,192) (35,35,256)
Inception-A (35,35,256) (35,35,288)
Inception-A (35,35,288) (35,35,288)
Reduction (35,35,288) (17,17,768)
Inception-B (17,17,768) (17,17,768)
Inception-B (17,17,768) (17,17,768)
Inception-B (17,17,768) (17,17,768)
Inception-B (17,17,768) (17,17,768)
Reduction (17,17,768) (8,8,1280)
Inception-C (8,8,1280) (8,8,2048)
Inception-C (8,8,2048) (8,8,2048)
Average pool (8,8,2048) (1,1,2048) (7,7) 1
FCN (1,1,2048 (1,1,1000)
softmax (1,1,1000) (1,1,1000)
표 18.4.4 : Inception V3의 구조

표 18.4.4는 Inception-V3의 구조를 표현한 것이다. Inception V2와 Inception V3의 구조는 거의 동일하다. Inception V3는 동일 구조에 몇 가지 기술이 더 추가된 것이다. 세부사항은 다음과 같다.

  • Inception V3에서 추가된 사항

    1. stem layer의 7x7 convolution 연산을 3x3 연산을 세 번 하는 것으로 대체
    2. Optimizer로 "RMSProp"를 사용
    3. auxiliary classifier : 1개만 사용하고, Batch normalization을 적용한다.
    4. Label Smoothing : 기존의 라벨을 $y$라고 했을 때, 다음 과 같이 $y'$를 만들어, 학습에 사용하는 것이다. 아래의 $K$는 예측 클래스의 갯수, $\epsilon$ 하이퍼 파라미터로 논문에서는 $\dfrac{1}{K}$를 사용하도록 추천한다. 정답 데이터에 대한 정규화 작업은 과적합을 방지하는 효과가 있다.

      $$y' = (1 - \epsilon)y + \dfrac{\epsilon}{K}$$

Keras의 Inception V3

In [5]:
from keras.applications.inception_v3 import InceptionV3, decode_predictions

inceptionv3 = InceptionV3(input_shape=(299,299,3))
# inceptionv3.summary()
In [6]:
import cv2
import time 

img = cv2.imread('bird1.jpg', -1)
img = cv2.resize(img, (299, 299))

start = time.time() 
yhat = inceptionv3.predict(img.reshape(-1, 299, 299, 3))
time = time.time() - start
# label_key = np.argmax(yhat)
label = decode_predictions(yhat)
label = label[0][0]

print("테스트 시 소요 시간 : {}".format(time))
print('%s (%.2f%%)' % (label[1], label[2]*100))
img = img[:,:,::-1]
plt.figure(figsize=(11,11))
plt.imshow(img)
plt.axis("off")
plt.show()
테스트 시 소요 시간 : 1.7837135791778564
clog (99.59%)

Inception의 다음 버전으로 Inception V4 와 Inception-ResNet이 있다. 이는 ResNet을 학습한 후 학습 하겠다.

질문/덧글

ok78*** 2019년 11월 13일 8:55 오후

모델의 첫번째 레이어를 보면, 일반적인 CNN 모델과 크게 다르지 않다. 11x11의 커널 사이즈와 4의 strides로 convolution 연산을 한다. 이 첫번째 레이어를 Stem layer라고 부른다.

이부분 질문있습니다. 첫번째 레이어는 11x11이 아니라 7x7 , 2 stride convolution 연산으로 보이는데 stem layer는 inception v4에서 나온게 아닌가요?