VGGNet

VGGNet(Visual Geometry Group Network)은 2014년 1000개의 이미지 클래스를 분류하는 이미지넷 이미지 인식 대회에서 준우승을 한 모델입니다. 옥스포드 대학의 연구팀 VGG에 의해 개발되었다고 하는데요. 이번 포스팅에서는 VGGNet 논문에서 중요한 내용을 살펴보고 직접 코드화하는 과정을 정리해 작성해 보겠습니다.


<VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION>
논문 링크 : https://arxiv.org/pdf/1409.1556

VGG의 핵심 장점은 다음과 같습니다.

1. convolution의 depth를 깊게 구성
2. 연산량을 획기적으로 줄여 좋은 성능 확보
3. 깊은 레이어 구조를 통해 Activation function을 여러 번 집어넣어서 비선형성을 더 많이 확보

왜 이런 장점이 있는지 논문 내용과 함께 살펴봅시다.


2.1. Architecture

VGG 이전의 기존 CNN 모델들은 주로 Convolution 레이어와 Pooling 레이어를 번갈아 연속적으로 사용하는 것이 일반적이었습니다. VGGNet은 이러한 기존의 틀에서 벗어나 Convolution을 2번 또는 3번 연속해서 쌓은 뒤 Pooling 레이어를 배치하는 새로운 구조를 제시합니다. 

VGG16 구조

위 사진은 대표적인 VGG16모델의 레이어 설계도입니다. 책이나 인터넷에서 쉽게 찾아볼 수 있는 그림인데요. 보이는 것처럼 파란색의 Convolution 레이어가 연속해서 2-3번 쌓이고, 이어서 빨간색 max pooling layer가 배치된 것을 볼 수 있습니다. 논문 2.1 Architecture에 관한 부분을 함께 살펴보도록 하겠습니다.

the input to our ConvNets is a fixed-size 224 × 224 RGB image
  • 입력 이미지의 shape은 (224, 224, 3)인 것을 알 수 있습니다.
The image is passed through a stack of convolutional (conv.) layers, where we use filters with a very small receptive field: 3 × 3 (which is the smallest size to capture the notion of left/right, up/down, center). (중략) The convolution stride is fixed to 1 pixel
  • 콘볼루션 레이어를 여러겹 쌓고 필터는 (3 x 3) 작은 사이즈로 고정하여 사용한다고 명시하고 있습니다. VGG 이전의 CNN 모델들은 전통적으로 필터 사이즈가 7, 9, 11 정도로 큰 것이 일반적이었다고 합니다. 그래서 사이즈가 3인 필터는 'very small'이라고 표현되는 것으로 보입니다.
  • 콘볼루션 레이어의 stride는 1픽셀로 고정됩니다.
Max-pooling is performed over a 2 × 2 pixel window, with stride 2.
  • 사용된 Maxpooling의 필터 사이즈와 stride 모두 (2, 2)인 것을 알 수 있습니다.
A stack of convolutional layers is followed by three Fully-Connected (FC) layers: the first two have 4096 channels each, the third performs 1000- way ILSVRC classification and thus contains 1000 channels (one for each class). The final layer is the soft-max layer.
  • CNN 레이어 이후에는 순서대로 4096 - 4096 - 1000개의 필터가 사용된 FC(Fully-Connected) 레이어가 3번 배치되었습니다.
  • 마지막 필터가 1000개인 이유는 VGG모델이 총 1000개의 이미지 클래스를 분류하기 때문입니다.
  • 마지막 레이어의 activation function로는 소프트맥스가 사용된 것을 알 수 있습니다.

2.2. Configuration

다음으로 여러 가지 버전의 VGG모델을 표현된 논문의 Table 1, 2를 함께 살펴보겠습니다.

The width of conv. layers (the number of channels) is rather small, starting from 64 in the first layer and then increasing by a factor of 2 after each max-pooling layer, until it reaches 512. 
  • 콘볼루션 레이어의 필터의 수는 64개로 작게 시작해서 마지막으로 512가 될때까지 2의 배수로 늘려 가겠다고 말하고 있습니다.
The convolutional layer parameters are denoted as “conv[receptive field size]-[number of channels]”. The ReLU activation function is not shown for brevity.
※ [2-1] All hidden layers are equipped with the rectification (ReLU (Krizhevsky et al., 2012)) non-linearity.
  • Table 1에서 표현된 레이어 수식(conv0-000형식)을 읽을 수 있어야 합니다. 예를 들어 conv3-256의 경우 필터 사이즈 (3x3)인 콘볼루션 레이어가 256장 쌓인 것으로 해석할 수 있습니다.
  • 간결한 표현을 위해 아래 배치도(Table 1)에서 활성함수 ReLU는 생략해서 표현되었으나, 논문 2.1 Architecture에 언급되었듯이 모든 히든레이어에는 ReLU가 적용되었습니다.

Table 1

  • 우리가 맨 위에서 살펴본 빨강파랑 레이어 구조도는 VGG16모델입니다. Convolution 레이어와 Dense 레이어를 합해 16개의 레이어가 사용되었습니다. 여기에서 레이어가 3개 늘어나면 VGG19(E모델)이 됩니다.
  • VGG16에 해당하는 모델 C와 D의 차이점은 3겹의 콘볼루션 레이어에서 마지막에 필터 사이즈 1짜리 레이어가 쓰였느냐 3짜리가 쓰였느냐 정도로 구분이 되네요.

VGG16 구조
Table 2

In Table 2 we report the number of parameters for each configuration. In spite of a large depth, the number of weights in our nets is not greater than the number of weights in a more shallow net with larger conv. layer widths and receptive fields (144M weights in (Sermanet et al., 2014)).
  • 테이블 2에는 백만 단위의 파라미터 수가 표현이 되어 있는데요. VGG16에 해당하는 모델 C-D의 경우 1억 3천만 개 정도의 파라미터를 찾아야 하네요.
  • 그렇다면 사이즈 3의 작은 필터를 사용한 3개의 콘볼루션 레이어를 연속적으로 사용해서 얻은 이점이 무엇일까요?

Table 2 아래에 중요한 내용이 담겨있습니다.

So what have we gained by using, for instance, a stack of three 3×3 conv. layers instead of a single 7×7 layer? First, we incorporate three non-linear rectification layers instead of a single one, which makes the decision function more discriminative. Second, we decrease the number of parameters
예: (10, 10) 이미지에 (7, 7) 필터 1번 적용
* 콘볼루션 후 최종 사이즈 : (4, 4)
* 찾아야 할 파라미터  : (7, 7) -> 49개
예 : (10, 10) 이미지에 (3, 3) 필터 3번 적용
* 콘볼루션 후 최종 사이즈 : (4, 4)
* 찾아야 할 파라미터 : (3, 3) 3개 -> 9 * 3 -> 27개
  • 최종적으로 얻는 이미지의 사이즈는 (4, 4)로 동일한데 찾아야 할 파라미터의 수는 49개에서 27개로 줄어드는 것을 확인할 수 있습니다.
  • 콘볼루션 레이어를 연속해서 쌓는 VGG 구조를 통해 찾아야 할 파라미터 수를 획기적으로 줄여 연산량을 줄이고 성능을 개선할 수 있다는 것입니다.
  • 또한 한 번의 콘볼루션 레이어만 추가했을 때보다 세 번의 연속적인 콘볼루션 레이어를 추가하게 되면서 비선형성을 더욱 강조해 모델의 유연성을 확보할 수 있게 되었고 이 역시 성능 개선으로 이어지게 되었습니다.

코드화

VGG16 모델 D - 코드로 구현해보기

from tensorflow.keras.layers import Conv2D, MaxPool2D, Flatten, Dense, Dropout

필요한 라이브러리를 호출합니다.

# 논문 VGG16 D모델 그대로 설계해보기
    
model_D_VGG_origin = tf.keras.Sequential([
    Conv2D(64, 3, input_shape = (224, 224, 3), padding = "same", activation = "relu"),
    Conv2D(64, 3, padding = "same", activation = "relu"),
    MaxPool2D(pool_size = 2, strides = 2),
    Conv2D(128, 3, padding = "same", activation = "relu"),
    Conv2D(128, 3, padding = "same", activation = "relu"),
    MaxPool2D(pool_size = 2, strides = 2),
    Conv2D(256, 3, padding = "same", activation = "relu"),
    Conv2D(256, 3, padding = "same", activation = "relu"),
    Conv2D(256, 3, padding = "same", activation = "relu"),
    MaxPool2D(pool_size = 2, strides = 2),
    Conv2D(512, 3, padding = "same", activation = "relu"),
    Conv2D(512, 3, padding = "same", activation = "relu"),
    Conv2D(512, 3, padding = "same", activation = "relu"),
    MaxPool2D(pool_size = 2, strides = 2),
    Flatten(),
    Dense(4096, activation = "relu"),
    Dense(4096, activation = "relu"),
    Dense(1000, activation = "softmax") # 1000가지 이미지를 분류하는 대회였음
])


VGG16 모델 D - Fashion Mnist에 맞게 튜닝하기

VGG16 Model D를 튜닝해서 fashion_mnist 분류모델을 구현하는 코드로 작성해 보겠습니다.

  • 논문 상 입력 데이터는 (224, 224, 3)이지만 Fashion Mnist의 데이터는 (28, 28, 1)입니다. 28을 224로 늘리는 것은 해상도가 너무 깨지고 특징이 소실되므로 의미가 없다는 판단 하에, 입력 부분을 28로 튜닝해서 사용하기로 결정했습니다.
  • MaxPool 횟수를 4회에서 3회로 줄이기로 했습니다. 이미지 사이즈가 가로 세로 (28, 28)이기 때문에 논문 그대로 4번 사용하면 사이즈가 소실될 수도 있습니다.
  • 논문 속 레이블은 1000개이지만 Fashion Mnist의 레이블은 10개입니다. 따라서 마지막 Dense 레이어의 필터 수를 1000에서 10으로 줄이겠습니다.
import tensorflow as tf

# 데이터 불러오기
fashion_mnist = tf.keras.datasets.fashion_mnist
(X_train, y_train), (X_test, y_test) = fashion_mnist.load_data()

# 정규화
X_train, X_test = X_train/255. , X_test/255.

# 3D를 4D로 변환
X_train = X_train.reshape(-1, 28, 28, 1)
X_test = X_test.reshape(-1, 28, 28, 1)
# 논문 VGG16 D모델 -> 패션 엠니스트에 맞는 모델로 바꾸어보기
    
model_D_VGG_fashion = tf.keras.Sequential([
    Conv2D(64, 3, input_shape = (28, 28, 1), padding = "same", activation = "relu"),
    Conv2D(64, 3, padding = "same", activation = "relu"),
    MaxPool2D(pool_size = 2, strides = 2),
    Conv2D(128, 3, padding = "same", activation = "relu"),
    Conv2D(128, 3, padding = "same", activation = "relu"),
    MaxPool2D(pool_size = 2, strides = 2),
    Conv2D(256, 3, padding = "same", activation = "relu"),
    Conv2D(256, 3, padding = "same", activation = "relu"),
    Conv2D(256, 3, padding = "same", activation = "relu"),
    MaxPool2D(pool_size = 2, strides = 2),
    Flatten(),
    Dense(4096, activation = "relu"),
    Dense(4096, activation = "relu"),
    Dense(10, activation = "softmax") # 10가지 이미지로 분류하기
])
model_D_VGG_fashion.summary()

model_D_VGG_fashion.compile(loss = tf.keras.losses.SparseCategoricalCrossentropy(),
                 optimizer = Adam(),
                 metrics = ['accuracy'])
import os

# callbacks
early = tf.keras.callbacks.EarlyStopping(patience = 5)
cp_path = "training/cp-{epoch:04d}.ckpt"
cp_dir = os.path.dirname(cp_path)
cp_callback = tf.keras.callbacks.ModelCheckpoint(cp_path,
                                                 monitor = 'val_loss',
                                                 verbose = 1,
                                                 save_weights_only=True)

# train
history_D_fashion = model_D_VGG_fashion.fit(X_train,
                                            y_train,
                                            validation_split = 0.25,
                                            batch_size = 128,
                                            epochs = 200,
                                            callbacks = [early,cp_callback])

model_D_VGG_fashion.evaluate(X_test, y_test)

# 313/313 [==============================] - 2s 5ms/step - loss: 0.2621 - accuracy: 0.9195
# [0.2621194124221802, 0.9194999933242798]

VGG16모델을 튜닝해서 사용한 결과 단 한 번의 시도만에 0.92에 가까운 valid accuracy를 확보하게 되었습니다 :)


 
 
이것으로 간단하게 살펴본 VGG 논문 리뷰를 마치겠습니다. 감사합니다 :)

+ Recent posts