본문 바로가기

VISION

고급 합성곱 신경망 구조 (2) - AlexNet, VGGNet, InceptionNet

https://rhks13.tistory.com/43

 

고급 합성곱 신경망 구조 (1) - CNN의 패턴과 LeNet

전통적인 CNN을 기반으로 다양한 최신 합성곱 신경망 구조들이 등장했다. 이번 포스팅부터는 그 새로운 구조들의 특징과 각 구조들이 어떻게 발전되어 탄생했는지 정리해보려 한다. 우선 그에

rhks13.tistory.com

지난 포스팅에 이어 

더 나아진 CNN 모델들을 살펴보겠다

 


 

1. AlexNet

 

앞서보았던 LeNet은 얇은 층으로 구성되고, 

MNIST 데이터를 단순히 10개의 클래스로 구분하는 과제를 수행했다.

 

훨씬 복잡한 이미지를 더 많은 클래스로 구분하려면

LeNet만으로는 힘들 수 있다.

 

이렇게 LeNet에 비해 더 복잡한 문제를 해결할 수 있도록

2012년에 등장한 구조가 바로 AlexNet이다.

LeNet과 구조가 크게 달라지지는 않았지만

층수와 층당 필터의 개수가 조금씩 증가했음을 확인할 수 있다.

 

1-1. 구조

커널 사이즈는 맨 처음에 11 by 11을 사용한다. 

너무 작은 사이즈는 특징 추출에 어려움이 있다고 판단했고, 

또 너무 큰 커널은 파라미터의 수가 급격하게 증가해서

그 둘의 절충안을 찾은게 11인가 보다.

이후로는 3x3 과 5x5를 섞어서 사용한다. 

 

중간중간 사이즈는 고정되어있지만 채널의 수가 달라지는 과정들이 존재한다.

이때는 패딩을 끼고 컨볼루션을 적용하여

인접한 위치의 픽셀끼리 위치관계를 파악하는 과정이다.

 

이를 통해 edge 정보(선), object와 배경의 차이 등

더 많은 특징들을 잘 추출할 수 있게끔 해준다.

 

1-2. 발전된 부분

 

- relu 사용 : 드디어 시그모이드와 tanh를 벗어났다. 덕분에 기울기 소실 문제를 줄이고, 학습시간을 대거 단축했다.

- 드롭아웃 층 : 신경망 모델의 과적합을 방지하기 위해 전결합층에서 0.5의 비율을 적용하였다.

- 데이터 강화 : 레이블 값을 변화시키지 않고 이미지를 대조,회전등의 단순 작업을 통해 증가시켜 과적합을 방지했다.

- 국소 응답 정규화(Local Response Normalization, LRN)

   : 인접한 커널들의 출력 값을 정규화 => 인접한 커널들의 출력값이 비슷해지는 것을 방지해준다.

      => 특징을 잘 도출할 수 있도록 해준다

      but 최근에는 LRN대신 배치 정규화를 더욱 많이 사용하고 있다..

- 가중치 규제화 : L2 규제화와 같은 개념으로, 0.00005의 가중치 감쇠가 적용되었다.

 

1-3. 구현

model = Sequential()
# 첫 번째 층 (Conv + POOL + 배치정규화)
model.add(Conv2D(filters = 96, kernel_size = (11,11), strides = (4,4), padding = 'valid', input_shape = (227, 227,3)))    #kernel_size, stride 숫자 하나만 써도 되고 둘 다 써도 됨
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size = (3,3), strides = (2,2)))
model.add(BatchNormalization())
# 두 번째 층 (Conv + POOL + 배치정규화)
model.add(Conv2D(filters = 256, kernel_size = (5,5), strides = (1,1), padding = 'same', kernel_regularizer = l2(0.0005)))
model.add(Activation('relu'))
model.add(MaxPool2D(pool_size = (3,3), strides = (2,2), padding = 'valid'))
model.add(BatchNormalization())
# 세 번째 층 (Conv + 배치정규화)
model.add(Conv2D(filters = 384, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_regularizer = l2(0.0005)))
model.add(Activation('relu'))
model.add(BatchNormalization())
# 네 번째 층 (Conv + 배치정규화)
model.add(Conv2D(filters = 384, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_regularizer = l2(0.0005)))
model.add(Activation('relu'))
model.add(BatchNormalization())
# 다섯 번째 층 (Conv + 배치정규화)
model.add(Conv2D(filters = 256, kernel_size = (3,3), strides = (1,1), padding = 'same', kernel_regularizer = l2(0.0005)))
model.add(Activation('relu'))
model.add(BatchNormalization())
# Max Pool 후 1차원 변환
model.add(MaxPool2D(pool_size = (3,3), strides = (2,2), padding = 'valid'))
model.add(Flatten())
# 여섯 번째 층 (FC + 드롭아웃)
model.add(Dense(units = 4096, activation = 'relu'))
model.add(Dropout(0.5))
# 일곱 번째 층 (FC + 드롭아웃)
model.add(Dense(units = 4096, activation = 'relu'))
model.add(Dropout(0.5))
# 여덟 번째 층 (FC (소프트맥스))
model.add(Dense(units = 1000, activation = 'softmax'))

model.summary()

6천만개의 파라미터..

 

그리고 아래와 같이 하이퍼 파라미터를 설정한다.

#하이퍼 파라미터 설정하기

# 검증 오차가 정체 될 때 마다 학습률을 1/10으로 감소시킨다.
reduce_lr = ReduceLROnPlateau(monitor = 'val_loss', factor = np.sqrt(0.1))
# SGD 옵티마이저를 학습률 0.01, 모멘텀 0.9로 설정한다.
optimzier = keras.optimizers.sgd(lr = 0.01, momentum = 0.9)
model.compile(loss='categorical_crossentropy', optimzier = optimizer)
model.fix(X_train, y_train, batch_size = 128, epochs=90, validation_data = (X_test, y_test), verbose = 2, callbacks=[reduce_lr])

 


2. VGGNet

AlexNet보다 월등히 많아진 깊이와

3x3으로 고정된 채널의 크기가 딱 눈에 꽂힌다.

이 두가지 사항이 VGGNet의 키 포인트 이다.

 

2-1. 발전 부분

- 신경망 단순화 :

   커널 크기, 패딩/스트라이드 크기 등의 하이퍼 파라미터들을 하나하나 조정하는 것 -> 너무 힘든일

   VGGNet에서는 동일하게 설정된 층을 사용해서 신경망 구조를 단순화시킴.

    - 모든 합성곱층  => 3x3 크기의 필터와 스트라이드 1, 패딩 1 => 세밀한 특징 추출 가능

    - 모든 풀링층 => 2x2의 크기, 스트라이드 2

- 깊어진 깊이 : 크기가 큰 하나의 커널 사용시 성능< 크기가 작은 커널을 여러개 사용시 성능

    ex) 7x7 -> 49 파라미터

          3x3x3 -> 27 파라미터 

         => 더 적은 파라미터의 수로 좋은 성능을 보일 수 있음

 

2-2. 구현

model = Sequential()

# 블록 #1
model.add(Conv2D(filters = 64, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same', input_shape = (224, 224,3))) 
model.add(Conv2D(filters = 64, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(MaxPool2D(pool_size = (2,2), strides = (2,2)))

# 블록 #2
model.add(Conv2D(filters = 128, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(Conv2D(filters = 128, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(MaxPool2D(pool_size = (2,2), strides = (2,2)))

# 블록 #3
model.add(Conv2D(filters = 258, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(Conv2D(filters = 258, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(Conv2D(filters = 258, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(MaxPool2D(pool_size = (2,2), strides = (2,2)))

# 블록 #4
model.add(Conv2D(filters = 512, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(Conv2D(filters = 512, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(Conv2D(filters = 512, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(MaxPool2D(pool_size = (2,2), strides = (2,2)))

# 블록 #5
model.add(Conv2D(filters = 512, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(Conv2D(filters = 512, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(Conv2D(filters = 512, kernel_size = (3,3), strides = (1,1), activation = 'relu',padding = 'same')) 
model.add(MaxPool2D(pool_size = (2,2), strides = (2,2)))

# 블록 #6(분류기)
model.add(Flatten())
model.add(Dense(4096, activation = 'relu'))
model.add(Dropout(0.5))
model.add(Dense(4096, activation = 'relu'))
model.add(Dropout(0.5))
model.add(Dense(1000, activation = 'softmax'))

model.summary()

1억!

이제 LeNet의 5만개는 귀여워 보일 정도이다

 


3. Inception(GoogleLeNet)

인셉션의 키 포인트는 하나의 블락을 병렬적으로 만들어서 쌓는 것이다.

이 부분을 유념하면서 흐름을 파악해보자.

 

3-1. 구조

지금까지와는 극명하게 다른 구조를 사용한다.

 

기존의 방식대로 층을 쌓는 것이 아닌,

커널 크기가 서로 다른 합성곱층으로 병력적으로 구성된 인셉션 블락을 만들어

이 블락들을 쌓아 신경망을 구성한다. 

전체적인 구성은 다음과 같다. 

동일한 블록들이 켜켜이 쌓여있음을 확인할 수 있다.

 

3-2. 발전 부분

 - 병렬 연결 : 이론이 없으니 어떤 커널의 크기를 사용하는 것이 좋은지 답이 나오지 않음

                      => 다 사용해서 붙여버리자! 

                      -> 이렇게 사용하려니 층의 깊이가 너무 깊어져.. -> 층을 압축시키자

 - 차원 축소 층 : 

 

1x1 합성곱층을 달지 않았을 때의 모습이다.

각기 다른 합성곱층/풀링층의 결과를 합치니 

똑같은 크기에 더 깊어진 이미지가 뽑혀 나온다.

 

5x5 합성곱층만 고려해 보았을 때, 

합성곱 연산에 필요한 곱셈의 수는

32x32x200 * 32x32x32 -> 1억 6300만회

1x1 차원 축소층을 연결했을 때의 5x5 커널 연산수는 

(32x32x200 * 1x1x16) + (32x32x32 * 5x5x16) => 1630만회로

앞선 연산을 1/10 줄인 셈이다.

 

이렇게 채널 정보를 압축하여 여러개의 채널 속에서의 특징들이 결합된다. 

전에 있던 특징을 온전히 살리지는 못하지만, 

적당한 특징들을 잘 추출 해 낼 수 있다고 한다.