반응형

www.analyticsvidhya.com의 블로그 내용을 정리함

Understanding Inception Network from Scratch

Introduction

    딥러닝은 전세계에서 더욱더 많은 연구 논문이 나오면서 빠르게 성장동력을 얻고 있다. 이들 논문은 의심할 여지 없이 많은 정보를 포함하고 있지만, 때때로 분석하기 어렵다. 그리고 이해하기 위해서는 해당 논문을 여러번 살펴봐야만 한다.(그리고 아마 다른 관계된 논문까지도)

    이는 우리같은 학자가 아닌 사람들에게는 겁나는 작업이다.

    연구논문을 살펴보고 그 배후의 중요부분을 해석하는 작업을 찾고 현업이 보유해야 하는 모든 딥러닝 주요기술 처럼 코드를 구현한다. 특히 연구 아이디어를 구현하는 것은 저자가 생각한 과정을 끌어내고 또한 이 아이디어를 실제 산업 어플리케이션으로 변환하는데 도움이 된다.

    따라서, 이 글에서 작성 동기는 아래와 같이 두가지이다.

  1. 딥러닝 논문을 이해할 수 있는 개념으로 세분화하여 독자가 최첨단 연구와 나란히 유지할 수 있도록 함.
  2. 연구 아이디어를 스스로의 코드로 변환하기 위한 학습.

    이 글은 딥러닝의 기본을 잘 알고 있다고 가정하고 진행한다. 만약 그렇지 않거나 아래 글을 먼저 학습하자.


Table of Contents

  • "Going Deeper with Convolutions" 논문 요약
    • 논문의 목적
    • 제시된 구조(architecture) 상세
    • 훈련 방법론
  • 케라스로 GoogLeNet 구현

"Going Deeper with Convolutions" 논문 요약

    이 글은 인셉션 네트워크가 나왔던 특징 아이디어인 "Going deeper with convolutions" 논문에 초점을 맞춘다. 인셉션 네트워크는 이미지 인식과 탐지 문제에 대한 최신 딥러닝 구조(또는 모델)로 간주되었다.

    이는 이미지 인식과 탐지 알고리즘을 밴치마킹하기 위해 평판이 좋은 플랫폼인 2014년 이미지넷 Visual Recognition Challenge에서 획기적인 성능을 내놓았다. 이와 함깨 획기적이고 강렬한 아이디어로 새로운 딥러닝 구조의 생성에 많은 연구를 시작했다.

    앞서 언급된 논문에서 주요 아이디어와 제기된 제안을 살펴보고 그 기술을 파악해 볼 것이다.

논문의 목적

    더 나은 딥러닝 모델을 만들기 위한 간단하지만 강력한 방법이 있다. 단지 깊이 면에서 즉 레이어의 수 또는 각 레이어의 뉴런의 수 어느 쪽이든 더 큰 모델을 만들면 된다.하지만 예상되는 것과 같이 이는 때때로 복잡한 문제를 만든다.

  • 더 큰 모델은 더 오버피팅하기 쉽다.
  • 파라미터 수가 증가한다는 것은 계산 리소스가 더 필요하다는 의미이다.

    이를 해결하기 위해서는 논문에서 제시한 것과 같이 특히 합성곱 레이어 내부에서 fully connected 네트워크 구조를 sparsely connected 네트워크 구조로 교체하는 것으로 움직이는 것이다. 이 아이디어는 아래 이미지로 개념화할 수 있다.


This paper proposes a new idea of creating deep architectures. This approach lets you maintain the “computational budget”, while increasing the depth and width of the network. Sounds too good to be true! This is how the conceptualized idea looks:

Let us look at the proposed architecture in a bit more detail.

제시된 구조(architecture) 상세

    논문은 GoogLeNet 또는 Inception V1으로 불리는 새로운 형태의 구조를 제안한다. 이는 기본적으로 27 레이어 깊이인 CNN이다. 아래는 모델의 summary이다.


    위 이미지에 인셉션 레이어가 있는 것에 주의하자. 이는 실제로 논문의 접근 방식의 메인 아이디어이다. 인셉션 레이어는 sparsely connected 구조의 핵심 개념이다.


    논문에서 인셉션이 무엇인가에 대해 아래와 같이 설명한다.

(인셉션 레이어)는 다음 단계의 입력을 형성하는 단일 출력 벡터로 연결되어진 출력 필터 뱅크를 가진 모든 레이어(이름하여 1 X 1, 3 X 3, 5 X 5 합성곱레이어)의 조합이다.

    위에 언급된 레이어와 함께, 원래의 인셉션 레이어에는 두개의 주요 추가물(add-on)이 있다.

  • 다른 레이어를 적용하기 전의 1 X 1 합성곱 레이어, 이는 주로 차원 축소를 위해 사용한다.
  • 병렬 맥스 풀링 레이어, 이는 인셉션 레이어에 다른 선택(option)을 제공한다.

    인셉션 레이어 구조(structure)의 중요성을 이해하기 위해, 저자는 인간 학습(human learning)으로부터 Hebbian principle을 요구한다. 여기서는 함께 동작하고 함께 묶여있는 뉴런(neurons that fire together, wire together)를 이야기 한다. 저자는 심층 신경망 모델에서 연속적인 레이어를 생성할 때, 하나는 이전 레이어의 학습에 주의를 기울어야 한다는 것을 제안했다.

    예를 들어, 심층 신경망 몰델이 얼굴의 각 부분에 초점을 맞추어 학습되었다고 해보자. 네트워크의 다음 레이어는 그곳에 존재하는 다른 객체를 구분하기 위해 이미지에서 얼굴 전체에 초점을 맞출 것이다. 실제로 이것을 하기 위해서, 레이어는 다른 객체를 탐지하기 위한 적절한 필터 크기를 가져야만 한다.



    이는 인셉션 레이어가 전면으로 나온다. 이는 내부 레이어가 어떤 필터 크기가 필요한 정보를 학습하는데 관련되는지를 고를 수 있게한다. 따라서 이미지내 얼굴의 크기가 다르더라도(아래 사진과 같이), 레이어는 적절하게 얼굴을 인식한다. 첫번째 이미지에서는 아마도 더 큰 필터 크기를 갖는 반면 두번째 이미지에서는 더 작은 것을 갖을 것이다.



    모든 사양을 포함한 전체 구조(architecture)는 아래와 같다.




훈련 방법론

    이 구조가 이미지 인식과 탐지 대회에 저자가 참여하여 만들어진 것에 주목하자. 따라서 논문에서 설명된 꽤 많은 멋을 위해 덧붙힌 부가 기능(bell and whistles)이 있다. 이는 아래 항목을 포함한다.

  • 모델을 훈련하기 위해 사용된 하드웨어
  • 훈련 데이터셋을 생성하기 위한 데이터 증강(data augmentation) 기술
  • 최적화 기술과 학습률 일정(learning rate schedule)같은 신경망 하이퍼 파라미터들
  • 모델 훈련을 위해 필요한 보조(auxiliary) 훈련
  • 최종 제출을 구축하기 위해 사용한 앙상블(ensemble) 기술

    이 중에서, 저자에 의해 수행된 보조 훈련은 본질적으로 흥미롭고 참신하다. 그래서 우선은 그 부분에 초점을 맞춘다. 나머지 기술에 대한 상세한 내용은 논문 또는 이후 구현에서 확인할 수 있다.

    네트워크 중간부분이 소실(dying out)을 방지하기 위해, 저자는 두개의 보조 분류기(이미지에서 보라색 박스)를 도입했다. 두개의 인셉션 모듈의 출력에 softmax를 적용했고, 동일한 레이블에 대해 보조 손실(auxiliary loss)를 계산했다. 총 손실 함수는 보조 손실과 실제 손실의 가중치가 적용된 합이다. 논문에서는 가중치 값을 각 보조 손실에 대해 0.3이었다.



케라스로 GoogLeNet 구현

    이제 케라스와 CIFAR-10 데이터셋을 사용하여 구현해보자.



    CIFAR-10은 유명한 이미지 분류 데이터셋이다. 이 데이터셋은 10가지 분류로 60,00개의 이미지로 구성된다.(각 분류는 위 이미지에서 열로써 나타난다.). 데이터셋은 50,000개의 훈련 이미지와 10,000개의 테스트 이미지로 나뉜다.

    우선 모든 필요한 라이브러리와 모듈을 아래와 같이 import한다.


import keras
from keras.layers.core import Layer
import keras.backend as K
import tensorflow as tf
from keras.datasets import cifar10

from keras.models import Model
from keras.layers import Conv2D, MaxPool2D,  \
    Dropout, Dense, Input, concatenate,      \
    GlobalAveragePooling2D, AveragePooling2D,\
    Flatten

import cv2 
import numpy as np 
from keras.datasets import cifar10 
from keras import backend as K 
from keras.utils import np_utils

import math 
from keras.optimizers import SGD 
from keras.callbacks import LearningRateScheduler

    데이터셋을 로드하고 전처리 작업을 한다. 이는 딥러닝 모델을 훈련시키기 전 중요한 작업이다.


num_classes = 10

def load_cifar10_data(img_rows, img_cols):

    # Load cifar10 training and validation sets
    (X_train, Y_train), (X_valid, Y_valid) = cifar10.load_data()

    # Resize training images
    X_train = np.array([cv2.resize(img, (img_rows,img_cols)) for img in X_train[:,:,:,:]])
    X_valid = np.array([cv2.resize(img, (img_rows,img_cols)) for img in X_valid[:,:,:,:]])

    # Transform targets to keras compatible format
    Y_train = np_utils.to_categorical(Y_train, num_classes)
    Y_valid = np_utils.to_categorical(Y_valid, num_classes)

    X_train = X_train.astype('float32')
    X_valid = X_valid.astype('float32')

    # preprocess data
    X_train = X_train / 255.0
    X_valid = X_valid / 255.0

    return X_train, Y_train, X_valid, Y_valid

X_train, y_train, X_test, y_test = load_cifar10_data(224, 224)

    딥러닝 구조(architecture)를 정의한다. 필요한 정보가 주어지면 전체 인셉션 레이어를 되돌려주기 위한 함수를 정의한다.


def inception_module(x,
                     filters_1x1,
                     filters_3x3_reduce,
                     filters_3x3,
                     filters_5x5_reduce,
                     filters_5x5,
                     filters_pool_proj,
                     name=None):

    conv_1x1 = Conv2D(filters_1x1, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(x)

    conv_3x3 = Conv2D(filters_3x3_reduce, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(x)
    conv_3x3 = Conv2D(filters_3x3, (3, 3), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(conv_3x3)

    conv_5x5 = Conv2D(filters_5x5_reduce, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(x)
    conv_5x5 = Conv2D(filters_5x5, (5, 5), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(conv_5x5)

    pool_proj = MaxPool2D((3, 3), strides=(1, 1), padding='same')(x)
    pool_proj = Conv2D(filters_pool_proj, (1, 1), padding='same', activation='relu', kernel_initializer=kernel_init, bias_initializer=bias_init)(pool_proj)

    output = concatenate([conv_1x1, conv_3x3, conv_5x5, pool_proj], axis=3, name=name)

    return output

    이제 논문의 GoogLeNet을 생성한다.




kernel_init = keras.initializers.glorot_uniform()
bias_init = keras.initializers.Constant(value=0.2)

input_layer = Input(shape=(224, 224, 3))

x = Conv2D(64, (7, 7), padding='same', strides=(2, 2), activation='relu', name='conv_1_7x7/2', kernel_initializer=kernel_init, bias_initializer=bias_init)(input_layer)
x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_1_3x3/2')(x)
x = Conv2D(64, (1, 1), padding='same', strides=(1, 1), activation='relu', name='conv_2a_3x3/1')(x)
x = Conv2D(192, (3, 3), padding='same', strides=(1, 1), activation='relu', name='conv_2b_3x3/1')(x)
x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_2_3x3/2')(x)

x = inception_module(x,
                     filters_1x1=64,
                     filters_3x3_reduce=96,
                     filters_3x3=128,
                     filters_5x5_reduce=16,
                     filters_5x5=32,
                     filters_pool_proj=32,
                     name='inception_3a')

x = inception_module(x,
                     filters_1x1=128,
                     filters_3x3_reduce=128,
                     filters_3x3=192,
                     filters_5x5_reduce=32,
                     filters_5x5=96,
                     filters_pool_proj=64,
                     name='inception_3b')

x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_3_3x3/2')(x)

x = inception_module(x,
                     filters_1x1=192,
                     filters_3x3_reduce=96,
                     filters_3x3=208,
                     filters_5x5_reduce=16,
                     filters_5x5=48,
                     filters_pool_proj=64,
                     name='inception_4a')


x1 = AveragePooling2D((5, 5), strides=3)(x)
x1 = Conv2D(128, (1, 1), padding='same', activation='relu')(x1)
x1 = Flatten()(x1)
x1 = Dense(1024, activation='relu')(x1)
x1 = Dropout(0.7)(x1)
x1 = Dense(10, activation='softmax', name='auxilliary_output_1')(x1)

x = inception_module(x,
                     filters_1x1=160,
                     filters_3x3_reduce=112,
                     filters_3x3=224,
                     filters_5x5_reduce=24,
                     filters_5x5=64,
                     filters_pool_proj=64,
                     name='inception_4b')

x = inception_module(x,
                     filters_1x1=128,
                     filters_3x3_reduce=128,
                     filters_3x3=256,
                     filters_5x5_reduce=24,
                     filters_5x5=64,
                     filters_pool_proj=64,
                     name='inception_4c')

x = inception_module(x,
                     filters_1x1=112,
                     filters_3x3_reduce=144,
                     filters_3x3=288,
                     filters_5x5_reduce=32,
                     filters_5x5=64,
                     filters_pool_proj=64,
                     name='inception_4d')


x2 = AveragePooling2D((5, 5), strides=3)(x)
x2 = Conv2D(128, (1, 1), padding='same', activation='relu')(x2)
x2 = Flatten()(x2)
x2 = Dense(1024, activation='relu')(x2)
x2 = Dropout(0.7)(x2)
x2 = Dense(10, activation='softmax', name='auxilliary_output_2')(x2)

x = inception_module(x,
                     filters_1x1=256,
                     filters_3x3_reduce=160,
                     filters_3x3=320,
                     filters_5x5_reduce=32,
                     filters_5x5=128,
                     filters_pool_proj=128,
                     name='inception_4e')

x = MaxPool2D((3, 3), padding='same', strides=(2, 2), name='max_pool_4_3x3/2')(x)

x = inception_module(x,
                     filters_1x1=256,
                     filters_3x3_reduce=160,
                     filters_3x3=320,
                     filters_5x5_reduce=32,
                     filters_5x5=128,
                     filters_pool_proj=128,
                     name='inception_5a')

x = inception_module(x,
                     filters_1x1=384,
                     filters_3x3_reduce=192,
                     filters_3x3=384,
                     filters_5x5_reduce=48,
                     filters_5x5=128,
                     filters_pool_proj=128,
                     name='inception_5b')

x = GlobalAveragePooling2D(name='avg_pool_5_3x3/1')(x)

x = Dropout(0.4)(x)

x = Dense(10, activation='softmax', name='output')(x)

model = Model(input_layer, [x, x1, x2], name='inception_v1')

model.summary()

    Summary 결과는 아래와 같다.


__________________________________________________________________________________________________
Layer (type)                    Output Shape         Param #     Connected to                     
==================================================================================================
input_1 (InputLayer)            (None, 224, 224, 3)  0                                            
__________________________________________________________________________________________________
conv_1_7x7/2 (Conv2D)           (None, 112, 112, 64) 9472        input_1[0][0]                    
__________________________________________________________________________________________________
max_pool_1_3x3/2 (MaxPooling2D) (None, 56, 56, 64)   0           conv_1_7x7/2[0][0]               
__________________________________________________________________________________________________
norm1 (LRN2D)                   (None, 56, 56, 64)   0           max_pool_1_3x3/2[0][0]           
__________________________________________________________________________________________________
...
...
...
dropout_3 (Dropout)             (None, 1024)         0           avg_pool_5_3x3/1[0][0]           
__________________________________________________________________________________________________
dropout_1 (Dropout)             (None, 1024)         0           dense_1[0][0]                    
__________________________________________________________________________________________________
dropout_2 (Dropout)             (None, 1024)         0           dense_2[0][0]                    
__________________________________________________________________________________________________
output (Dense)                  (None, 10)           10250       dropout_3[0][0]                  
__________________________________________________________________________________________________
auxilliary_output_1 (Dense)     (None, 10)           10250       dropout_1[0][0]                  
__________________________________________________________________________________________________
auxilliary_output_2 (Dense)     (None, 10)           10250       dropout_2[0][0]                  
==================================================================================================
Total params: 10,334,030
Trainable params: 10,334,030
Non-trainable params: 0
__________________________________________________________________________________________________

    모델 훈련전에 아래와 같이 몇가지 마지막 작업을 추가할 수 있다.

  • 각 출력에 대한 손실함수
  • 해당 출력레이어에 할당된 가중치
  • 매 8번마다 가중치 감소를 포함하기 위해 수정된 최적화 함수
  • 평가지표

epochs = 25
initial_lrate = 0.01

def decay(epoch, steps=100):
    initial_lrate = 0.01
    drop = 0.96
    epochs_drop = 8
    lrate = initial_lrate * math.pow(drop, math.floor((1+epoch)/epochs_drop))
    return lrate

sgd = SGD(lr=initial_lrate, momentum=0.9, nesterov=False)

lr_sc = LearningRateScheduler(decay, verbose=1)

model.compile(loss=['categorical_crossentropy', 'categorical_crossentropy', 'categorical_crossentropy'], loss_weights=[1, 0.3, 0.3], optimizer=sgd, metrics=['accuracy'])

    모델이 준비되었으니 어떻게 동작하는지 점검하기 위해 테스트해보자.


history = model.fit(X_train, [y_train, y_train, y_train], validation_data=(X_test, [y_test, y_test, y_test]), epochs=epochs, batch_size=256, callbacks=[lr_sc])

    아래는 모델을 훈련한 결과이다.

Train on 50000 samples, validate on 10000 samples
Epoch 1/25

Epoch 00001: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 188s 4ms/step - loss: 3.7140 - output_loss: 2.3280 - auxilliary_output_1_loss: 2.3101 - auxilliary_output_2_loss: 2.3099 - output_acc: 0.1030 - auxilliary_output_1_acc: 0.1029 - auxilliary_output_2_acc: 0.0992 - val_loss: 3.6898 - val_output_loss: 2.3085 - val_auxilliary_output_1_loss: 2.3018 - val_auxilliary_output_2_loss: 2.3025 - val_output_acc: 0.1000 - val_auxilliary_output_1_acc: 0.1017 - val_auxilliary_output_2_acc: 0.0984
Epoch 2/25

Epoch 00002: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 181s 4ms/step - loss: 3.6635 - output_loss: 2.2894 - auxilliary_output_1_loss: 2.2817 - auxilliary_output_2_loss: 2.2987 - output_acc: 0.1161 - auxilliary_output_1_acc: 0.1321 - auxilliary_output_2_acc: 0.1151 - val_loss: 3.6559 - val_output_loss: 2.3095 - val_auxilliary_output_1_loss: 2.2315 - val_auxilliary_output_2_loss: 2.2565 - val_output_acc: 0.1466 - val_auxilliary_output_1_acc: 0.1478 - val_auxilliary_output_2_acc: 0.1417
Epoch 3/25

Epoch 00003: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 180s 4ms/step - loss: 3.2981 - output_loss: 2.0660 - auxilliary_output_1_loss: 2.0414 - auxilliary_output_2_loss: 2.0653 - output_acc: 0.2212 - auxilliary_output_1_acc: 0.2363 - auxilliary_output_2_acc: 0.2256 - val_loss: 3.1812 - val_output_loss: 2.0064 - val_auxilliary_output_1_loss: 1.9372 - val_auxilliary_output_2_loss: 1.9787 - val_output_acc: 0.2578 - val_auxilliary_output_1_acc: 0.2909 - val_auxilliary_output_2_acc: 0.2767
Epoch 4/25

Epoch 00004: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 181s 4ms/step - loss: 3.0797 - output_loss: 1.9258 - auxilliary_output_1_loss: 1.9214 - auxilliary_output_2_loss: 1.9248 - output_acc: 0.2803 - auxilliary_output_1_acc: 0.2914 - auxilliary_output_2_acc: 0.2872 - val_loss: 3.0099 - val_output_loss: 1.8852 - val_auxilliary_output_1_loss: 1.8900 - val_auxilliary_output_2_loss: 1.8589 - val_output_acc: 0.3080 - val_auxilliary_output_1_acc: 0.3122 - val_auxilliary_output_2_acc: 0.3296
Epoch 5/25

Epoch 00005: LearningRateScheduler reducing learning rate to 0.01.
50000/50000 [==============================] - 181s 4ms/step - loss: 2.8427 - output_loss: 1.7733 - auxilliary_output_1_loss: 1.7933 - auxilliary_output_2_loss: 1.7711 - output_acc: 0.3454 - auxilliary_output_1_acc: 0.3485 - auxilliary_output_2_acc: 0.3509 - val_loss: 2.6623 - val_output_loss: 1.6788 - val_auxilliary_output_1_loss: 1.6531 - val_auxilliary_output_2_loss: 1.6250 - val_output_acc: 0.3922 - val_auxilliary_output_1_acc: 0.4094 - val_auxilliary_output_2_acc: 0.4103
Epoch 6/25
...
...
...
Epoch 00024: LearningRateScheduler reducing learning rate to 0.008847359999999999.
50000/50000 [==============================] - 181s 4ms/step - loss: 0.7803 - output_loss: 0.3791 - auxilliary_output_1_loss: 0.7608 - auxilliary_output_2_loss: 0.5767 - output_acc: 0.8665 - auxilliary_output_1_acc: 0.7332 - auxilliary_output_2_acc: 0.7962 - val_loss: 1.0228 - val_output_loss: 0.6043 - val_auxilliary_output_1_loss: 0.7442 - val_auxilliary_output_2_loss: 0.6508 - val_output_acc: 0.7970 - val_auxilliary_output_1_acc: 0.7408 - val_auxilliary_output_2_acc: 0.7724
Epoch 25/25

Epoch 00025: LearningRateScheduler reducing learning rate to 0.008847359999999999.
50000/50000 [==============================] - 181s 4ms/step - loss: 0.7411 - output_loss: 0.3543 - auxilliary_output_1_loss: 0.7349 - auxilliary_output_2_loss: 0.5545 - output_acc: 0.8755 - auxilliary_output_1_acc: 0.7408 - auxilliary_output_2_acc: 0.8060 - val_loss: 0.9524 - val_output_loss: 0.5383 - val_auxilliary_output_1_loss: 0.7346 - val_auxilliary_output_2_loss: 0.6458 - val_output_acc: 0.8191 - val_auxilliary_output_1_acc: 0.7435 - val_auxilliary_output_2_acc: 0.7791

    위 결과에서는 검증셋에서 80%이상의 정확도를 보였다. 이는 이 모델 구조가 정말로 살펴볼 가치가 있다는 것을 보여준다.

반응형

+ Recent posts