본문 바로가기
컴퓨터비전/CNN

[딥러닝] VGG 모델의 개요 및 특징

by PIAI 2022. 3. 17.

특징

https://www.geeksforgeeks.org/vgg-16-cnn-model/
https://www.geeksforgeeks.org/vgg-16-cnn-model/

 

  • VGG모델은 네트워크의 깊이와 모델 성능 영향에 집중한 것입니다.
  • 커널 사이즈를 Alexnet과는 다르게 3 x 3으로 고정하였습니다, 그 이유는 커널 사이즈가 크면 이미지 사이즈 축소가 급격하게 이루어져 깊은 층 만들기가 어렵고, 파라미터 개수와 연산량이 많이 필요하기 때문입니다.
  • 작은 필터를 사용함으로써 더 많은 ReLU함수를 사용할 수 있고 더 많은 비선형성 확보할 수 있었습니다.

AlexNet에서도 그랬듯이, 아래의 그림을 보면 깊이에 따라 모델 성능이 좋아짐을 알 수 있습니다.

 

 

1. 위의 3x3 커널을 2개 적용한 경우 3x3x2 = 18 개

2. 5x5 커널을 1개 적용한 경우 5x5 = 25 개

 

그림을 보면 3x3을 2번 적용함으로써 2번 추상화 시킬수 있고, 최종으로 5x5의 추상화도 시킬 수 있습니다. 그리고 파라미터 수도 더 적습니다. 

 

  • AlexNet에서 11x11, 5x5와 같은 넓은 크기의 커널로 Convolutation연산을 적용하는 것보다 여러 개의 3x3 Convolution 연산을 수행하는 것이 더 뛰어난 Feature를 추출합니다.
  • AlexNet 대비 더 많은 채널수와 깊은 Layer 구성
  • 3x3 크기의 커널을 연속해서 Convolution 적용한 뒤에 Max Pooling 적용하여 Convolution Feature map Block을 생성
  • Block 내에는 동일한 커널 크기와 Channel 개수를 적용하여 동일한 크기의 feature map들을 생성
  • 이전 Block 내에 있는 Feature Map의 크기는 2배로 줄어들지만 채널은 2배로 늘어남

이제 코드로 구현을 해보겠습니다.

 

코드(Cifar10)

import numpy as np
import pandas as pd
import os

from tensorflow.keras.models import Model
from tensorflow.keras.layers import Input, Dense , Conv2D , Dropout , Flatten , Activation, MaxPooling2D , GlobalAveragePooling2D
from tensorflow.keras.optimizers import Adam , RMSprop 
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.callbacks import ReduceLROnPlateau , EarlyStopping , ModelCheckpoint , LearningRateScheduler
from tensorflow.keras import regularizers
from tensorflow.keras.applications.vgg16 import preprocess_input

from tensorflow.keras.datasets import cifar10
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split

from tensorflow.keras.utils import Sequence
import cv2
import sklearn

class CIFAR_Dataset(Sequence):
    def __init__(self, images_array, labels, batch_size=64, aug=None, shuffle=False, pre_func=None):
        self.images_array = images_array
        self.labels = labels
        self.batch_size = batch_size
        self.aug = aug
        self.pre_func = pre_func
        self.shuffle = shuffle
        if self.shuffle:
            self.on_epoch_end()

    def __len__(self):
        return len(self.labels) // self.batch_size

    def __getitem__(self, index):
        # 배치 사이즈 만큼 np.array를 가져옴
        images_fetch = self.images_array[index*self.batch_size:(index+1)*self.batch_size]
        # 훈련데이터인 경우
        if self.labels is not None:
            label_batch = self.labels[index*self.batch_size:(index+1)*self.batch_size]
        # 배치 사이즈만큼 이미지를 담을 빈 공간을 만듬
        image_batch = np.zeros((images_fetch.shape[0], 128, 128, 3))

        for i in range(images_fetch.shape[0]):
            # 이미지가 32 x 32 이므로 128, 128로 사이즈를 늘려줌
            image = cv2.resize(images_fetch[i], (128, 128))
            # augmentation이 있으면 적용
            if self.aug is not None:
                image = self.aug(image=image)['image']
            # scaling이 있으면 적용
            if self.pre_func is not None:
                image = self.pre_func(image)
            # 모두 적용한 데이터를 넣음
            image_batch[i] = image

        return image_batch, label_batch

    def on_epoch_end(self):
        if self.shuffle:
            self.images_array, self.labels = sklearn.utils.shuffle(self.images_array, self.labels)

def create_vggnet(in_shape=(224, 224, 3), n_classes=10):
    input_tensor = Input(shape=in_shape)
    
    # Block 1
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(input_tensor)
    x = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block1_pool')(x)

    # Block 2
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(x)
    x = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block2_pool')(x)

    # Block 3
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(x)
    x = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block3_pool')(x)

    # Block 4
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block4_pool')(x)

    # Block 5
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(x)
    x = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(x)
    x = MaxPooling2D((2, 2), strides=(2, 2), name='block5_pool')(x)

    x = GlobalAveragePooling2D()(x)
    x = Dropout(0.5)(x)
    x = Dense(units = 120, activation = 'relu')(x)
    x = Dropout(0.5)(x)

    # 마지막 softmax 층 적용. 
    output = Dense(units = n_classes, activation = 'softmax')(x)
    
    model = Model(inputs=input_tensor, outputs=output)
    model.summary()
    
    return model

(train_images, train_labels), (test_images, test_labels) = cifar10.load_data()

train_oh_labels = to_categorical(train_labels)
test_oh_labels = to_categorical(test_labels)

tr_images, val_images, tr_oh_labels, val_oh_labels = train_test_split(train_images, train_oh_labels, test_size=0.15)

tr_dataset = CIFAR_Dataset(tr_images, tr_oh_labels, batch_size=64, aug=None, shuffle=True, pre_func= preprocess_input)
val_dataset = CIFAR_Dataset(val_images, val_oh_labels, batch_size=64, aug=None, shuffle=False, pre_func= preprocess_input)
test_dataset = CIFAR_Dataset(test_images, test_oh_labels, batch_size=64, aug=None, shuffle=False, pre_func=preprocess_input)

model = create_vggnet(in_shape=(128, 128, 3), n_classes=10)
model.compile(optimizer=Adam(0.0001), loss='categorical_crossentropy', metrics=['accuracy'])

learn_cb = ReduceLROnPlateau(monitor='val_loss', mode='min', factor=0.2, patience=5, verbose=1)
stop_cb = EarlyStopping(monitor='val_loss', mode='min', patience=5, verbose=1)

model.fit(tr_dataset, epochs=30, validation_data=val_dataset, callbacks=[learn_cb, stop_cb])

model.evaluate(test_dataset)

댓글