[Classificaiton] VGGNET 모델 코딩

2021. 6. 24. 18:39코딩연습장/Keras

이번에는 실제로 VGGNet을 구현해보고, 훈련까지 해볼 것이다. 

코딩 글에서는 코드에 대한 간략한 설명과 결과 정도만 쓸 예정이다.

 

https://aistudy9314.tistory.com/25

 

[분류모델] VGGNET

 제목은 분류모델로 써놓았지만 이 카테고리에 쓸 모델들은 Object Detection, Segmentation 등 등 다른 Task의 기반이 되는 backbone 역할을 해준다고 보면 된다. 이러한 backbone 모델의 성능에 따라 딥러닝

aistudy9314.tistory.com

1. Layer

나는 자주쓰는 layer나 반복되어 쓰이는 과정을 block으로 묶어서 함수로 정의해두는 편이다.

 

먼저 실제 VGGNet에서 Batch Normalization이 실제로 쓰이지는 않았지만 개인적으로 묶어두는 것을 선호하여 다음과 같이 convolution layer를 정의하였다.

def conv_bn(inputs,filter_size, kernel_size, strides=1, padding="same", activation="relu", regularizer=l2(0.001)):
    layer1 = keras.layers.Conv2D(filters=filter_size, kernel_size=kernel_size, strides=strides, padding=padding
                                 ,kernel_regularizer=regularizer)(inputs)
    layer2 = keras.layers.BatchNormalization()(layer1)
    out = keras.layers.Activation(activation=activation)(layer2)
    return out

 

두번째로는 밑 그림에서 동그라미쳐진 부분들이 같은 형식으로 짜여져 있어 block으로 정의했다.

 

def vgg_block(inputs, filter_size, kernel_size,  strides=1, padding="same", activation="relu", regularizer=l2(0.001)):
    block = conv_bn(inputs, filter_size=filter_size, kernel_size=kernel_size, strides=strides,
                     padding=padding, activation=activation, regularizer=regularizer)
    block = conv_bn(block, filter_size=filter_size, kernel_size=kernel_size, strides=strides,
                     padding=padding, activation=activation, regularizer=regularizer)
    block = conv_bn(block, filter_size=filter_size, kernel_size=kernel_size, strides=strides,
                     padding=padding, activation=activation, regularizer=regularizer)
    block_out = keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(block)
    return block_out

 

2. Model

이것도 개인 취향인데 나는 모델을 Class로 정의해놓는 편이다. 그리고 그 모델을 Stem, Body, Head로 나누어 표현하는 것을 좋아한다.

2.1 Init

Class에서 self 변수들을 정의하고 초기화해주는 부분이다.

클래스 선언 부분에서 나처럼 self로 초기화하여도 되고, 함수 단에서 파라메터로 넣어주는 것도 한 방법일 것이다.

class VGGNET16:
    def __init__(self,input_shape, class_num, weight_decay=0.001):
        self.input_shape = input_shape
        self.class_num = class_num
        self.l2_reg = keras.regularizers.l2(weight_decay)

 

2.2 Stem

stem이란 보통 모델의 초반 부분을 나타내는 말이다.

**일단 모델을 위 구조와 같이 만들었지만 CIFAR-100 데이터셋을 사용한다면 Maxpool layer를 줄여야 한다.

**이미지 크기가 32 x 32라 후반에 w, h가 너무 작아지게 되기 때문.

    def vggnet_stem(self, inputs):
        '''32 x 32'''
        block1 = conv_bn(inputs, filter_size=64, kernel_size=(3, 3), strides=1,
                         padding="same", activation="relu", regularizer=self.l2_reg)
        block1 = conv_bn(block1, filter_size=64, kernel_size=(3, 3), strides=1,
                         padding="same", activation="relu", regularizer=self.l2_reg)
        block1_out = keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(block1)

        '''16 x 16'''
        block2 = conv_bn(block1_out, filter_size=128, kernel_size=(3, 3), strides=1,
                         padding="same", activation="relu", regularizer=self.l2_reg)
        block2 = conv_bn(block2, filter_size=128, kernel_size=(3, 3), strides=1,
                         padding="same", activation="relu", regularizer=self.l2_reg)
        block2_out = keras.layers.MaxPooling2D(pool_size=(2, 2), strides=(2, 2))(block2)
        return block2_out

 

2.3 Body

Body는 이전에 정의해두었던 vgg_block을 이용해서 매우 깔끔하게 정리할 수 있었다.

    def vggnet_body(self, inputs):
        '''8 x 8'''
        block_3 = vgg_block(inputs, filter_size=256, kernel_size=(3,3), strides=1,
                            padding="same", activation="relu", regularizer=self.l2_reg)

        '''4 x 4'''
        block_4 = vgg_block(block_3, filter_size=512,  kernel_size=(3,3), strides=1,
                            padding="same", activation="relu", regularizer=self.l2_reg)

        '''2 x 2'''
        block_5 = vgg_block(block_4, filter_size=512, kernel_size=(3,3), strides=1,
                            padding="same", activation="relu", regularizer=self.l2_reg)

        return block_5

 

2.4 VGGNet

VGGNet모델의 base 코드이다. head는 너무 짧아서 굳이 함수로 만들지는 않았다.

    def vggnet(self):
        inputs = keras.layers.Input(shape=self.input_shape)
        '''stem'''
        stem = self.vggnet_stem(inputs)

        '''body'''
        body_out = self.vggnet_body(stem)

        '''head'''
        flatten = keras.layers.Flatten()(body_out)
        fc1 = keras.layers.Dense(units=4096)(flatten)
        fc2 = keras.layers.Dense(units=4096)(fc1)
        fc3 = keras.layers.Dense(units=self.class_num, activation="softmax")(fc2)
        return keras.models.Model(inputs, fc3)

 

3. Train

이제 모델을 다 만들었으니 훈련을 시켜보겠다.

 

먼저 model train관련한 하이퍼 파라메터들과 Call back함수 관련한 변수들을 정의하였다. 

**사용자가 조절하기 편하도록 가장 윗단에 정의했다.

 

def train():
    '''model configuration'''
    input_shape = (32, 32, 3)
    class_num = 100
    epochs = 1000
    batch_size = 128
    lr = 1e-3
    optimizer = keras.optimizers.Adam(learning_rate=lr)
    loss = keras.losses.CategoricalCrossentropy()
    metrics = keras.metrics.CategoricalAccuracy()
    model_name = "vgg"

    '''call back'''
    log_dir = "./logs/%s/%s"%(model_name, datetime.datetime.now().strftime("%Y%m%d-%H%M%S"))
    weight_save_dir = "./save_weights/"
    if not os.path.isdir(weight_save_dir):
        os.mkdir(weight_save_dir)
    weight_save_file = "%s/%s_{epoch:05d}.h5"%(weight_save_dir, model_name)

 

다음으로는 데이터셋을 가져오는 부분이다.

이전 글에서 cifar100 데이터셋을 다운로드 했다면 알아서 load를 해줄 것이다. 

 

cifar100 데이터셋은 Normalize와 one-hot 인코딩이 되어있지 않기 때문에 이미지를 255.0으로 나누어주는 것과 to_categorical함수를 사용하여 one_hot형태로 바꾸어주는 것이 필요하다.

    '''get datasets'''
    x_train, y_train, x_test, y_test = get_cifar100()
    x_train = x_train / 255.0
    x_test = x_test / 255.0
    y_train = keras.utils.to_categorical(y_train, class_num)
    y_test = keras.utils.to_categorical(y_test, class_num)

 

이제 마지막으로 model을 정의하고, 훈련을 시키는 부분이다.

여기서 validation-set으로는 test데이터셋을 이용하였고, Call back함수Tensorboard와 ReduceLR, Checkpoint를 사용하였다. 

    '''train'''
    vgg = VGGNET16(input_shape=input_shape, class_num=class_num)
    model = vgg.vggnet()
    model.summary()
    model.compile(optimizer=optimizer, loss=loss, metrics=[metrics])
    model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs,
              validation_data=(x_test, y_test),
              callbacks=[TensorBoard(log_dir),
                         ReduceLROnPlateau(monitor="val_loss", factor=0.5, patience=10),
                         ModelCheckpoint(weight_save_file, monitor="val_loss", save_best_only=True)])

 

이제 프로그램을 돌려보면 train이 시작되는 것을 볼 수 있다.

Train on 50000 samples, validate on 10000 samples
Epoch 1/1000
2021-06-24 18:22:26.443968: I tensorflow/stream_executor/dso_loader.cc:152] successfully opened CUDA library cublas64_100.dll locally

  128/50000 [..............................] - ETA: 24:06 - loss: 8.8209 - categorical_accuracy: 0.0000e+00
  256/50000 [..............................] - ETA: 12:26 - loss: 14.1655 - categorical_accuracy: 0.0000e+00
  384/50000 [..............................] - ETA: 8:32 - loss: 15.9980 - categorical_accuracy: 0.0052     
  512/50000 [..............................] - ETA: 6:36 - loss: 16.9717 - categorical_accuracy: 0.0059
  640/50000 [..............................] - ETA: 5:25 - loss: 17.5329 - categorical_accuracy: 0.0078
  768/50000 [..............................] - ETA: 4:39 - loss: 17.9504 - categorical_accuracy: 0.0065
  896/50000 [..............................] - ETA: 4:05 - loss: 18.1774 - categorical_accuracy: 0.0100
 1024/50000 [..............................] - ETA: 3:40 - loss: 18.4110 - categorical_accuracy: 0.0088
 1152/50000 [..............................] - ETA: 3:21 - loss: 18.5787 - categorical_accuracy: 0.0087
 1280/50000 [..............................] - ETA: 3:05 - loss: 18.7251 - categorical_accuracy: 0.0078
 1408/50000 [..............................] - ETA: 2:52 - loss: 18.8101 - categorical_accuracy: 0.0092
 1536/50000 [..............................] - ETA: 2:41 - loss: 18.9013 - categorical_accuracy: 0.0091
 1664/50000 [..............................] - ETA: 2:32 - loss: 18.9778 - categorical_accuracy: 0.0090
 1792/50000 [>.............................] - ETA: 2:24 - loss: 19.0335 - categorical_accuracy: 0.0095
 1920/50000 [>.............................] - ETA: 2:18 - loss: 19.0808 - categorical_accuracy: 0.0099
 2048/50000 [>.............................] - ETA: 2:12 - loss: 19.1134 - categorical_accuracy: 0.0107
 2176/50000 [>.............................] - ETA: 2:06 - loss: 19.1634 - categorical_accuracy: 0.0101
 2304/50000 [>.............................] - ETA: 2:02 - loss: 19.1859 - categorical_accuracy: 0.0109
 2432/50000 [>.............................] - ETA: 1:58 - loss: 19.2049 - categorical_accuracy: 0.0115
 2560/50000 [>.............................] - ETA: 1:54 - loss: 19.2336 - categorical_accuracy: 0.0113
 2688/50000 [>.............................] - ETA: 1:50 - loss: 19.2405 - categorical_accuracy: 0.0123
 2816/50000 [>.............................] - ETA: 1:47 - loss: 19.2687 - categorical_accuracy: 0.0117
 2944/50000 [>.............................] - ETA: 1:44 - loss: 19.2769 - categorical_accuracy: 0.0122
 3072/50000 [>.............................] - ETA: 1:42 - loss: 19.2939 - categorical_accuracy: 0.0120
 3200/50000 [>.............................] - ETA: 1:39 - loss: 19.3035 - categorical_accuracy: 0.0122
 3328/50000 [>.............................] - ETA: 1:37 - loss: 19.3162 - categorical_accuracy: 0.0120
 3456/50000 [=>............................] - ETA: 1:35 - loss: 19.3317 - categorical_accuracy: 0.0116
 3584/50000 [=>............................] - ETA: 1:33 - loss: 19.3361 - categorical_accuracy: 0.0117
 3712/50000 [=>............................] - ETA: 1:31 - loss: 19.3435 - categorical_accuracy: 0.0116
Total params: 65,481,024
Trainable params: 65,472,576
Non-trainable params: 8,448

**참고로 위 파라메터는 stem layer에서 max pool 2개를 제외한 network의 파라메터이다.

 

좀 더 자세하게 훈련 진행을 보고 싶다면 cmd창에서 Tensorboard를 실행시키면 된다.

tensorboard --logdir logs

단, 여기서 아래와 같은 주소를 입력하라고 하는데

TensorBoard 1.14.0 at http://DESKTOP-6BEJ2KG:6006/ (Press CTRL+C to quit)

복붙해도 열리지 않을 것이다. 다음과 같이 인터넷 주소창에 입력해주면 된다!

localhost:6006

 

※ 주의사항(필독)

아까 말했듯이 input size가 너무 작아서 학습이 전혀 안되는 것을 볼 수 있다. 

 

(잘못된 VGGNet 학습)

 

이것을 해결하려면 input size를 늘리던가 max_pool layer를 줄여서 중간 shape가 적당한 크기가 되도록 해주어야한다.

 

또한, 옵션으로 Regularizer를 넣었는데 decay값이 너무 크면 학습이 잘 안되니, 작은 값을 주길 바란다(아니면 아예 빼거나).

 

마지막으로 learning rate도 초반에 큰 값으로 설정되면 학습이 잘 안되기 때문에 Cosine Annealing이나 Cyclic LR, 아니면 le-4이하의 작은 값으로 초기화하기 바란다.

 

(올바른 VGGNet 학습)

 

하지만 이 결과도 Validation 성능을 보면 처참하다....

 

 

이렇게 train-set에서는 accuracy 및 loss 등의 지표가 잘 나오는데, valid-set에서는 매우 저하된 성능을 보이는 현상을 "오버피팅" 이라고 한다.

 

이러한 현상을 보완하기 위해 Data Augmentation, Drop-out, Regularizer, Batch Norm 등이 사용되거나 아예 모델 구조면에서 새로운 방식을 사용하기도 한다. 

 

다음 모델은 모델 구조에 이전과는 다른 접근 방식을 적용하여 성능을 높인 ResNet에 대해서 알아보고 구현할 것이다.

'코딩연습장 > Keras' 카테고리의 다른 글

[Keras] Generator 만들기  (2) 2021.07.08
[데이터 증강] IMGAUG 모듈  (0) 2021.07.08
[데이터 준비] Tiny-Imagenet  (2) 2021.06.29
[Classification] ResNet 코딩  (0) 2021.06.29
[데이터] 데이터 준비 단계(CIFAR-100)  (0) 2021.06.24