본문 바로가기
ML & DL/tensorflow

Hand SIGNS 분류 예제 (3) - ResNet

by 별준 2020. 11. 24.

(tensorflow v2.3.0)

 

2020/11/17 - [ML & DL/tensorflow] - Hand SIGNS 분류 예제 (1)

 

Hand SIGNS 분류 예제 (1)

(tensorflow v2.3.0) Coursera Deep Learning 특화과정에 실습으로 진행했던 SIGNS dataset을 가지고, 위 이미지와 같이 분류를 해보도록 할 것입니다. Coursera 실습은 tensorflow 1로 진행되는데, 2버전에 맞추..

junstar92.tistory.com

2020/11/17 - [ML & DL/tensorflow] - Hand SIGNS 분류 예제 (2) - CNN 구조 사용

 

Hand SIGNS 분류 예제 (2) - CNN 구조 사용

(tensorflow v2.3.0) 2020/11/17 - [ML & DL/tensorflow] - Hand SIGNS 분류 예제 (1) Hand SIGNS 분류 예제 (1) (tensorflow v2.3.0) Coursera Deep Learning 특화과정에 실습으로 진행했던 SIGNS dataset을 가지..

junstar92.tistory.com

이전 Hand SIGN 분류 예제와 동일하지만 ResNet 구조를 구현해서 학습을 해보았습니다.

ResNet은 꽤나 Deep한 모델이고, 파라미터의 수가 많기 때문에 CPU 환경에서는 학습 시간이 꽤 길어지게 됩니다.

따라서 이번에는 GPU 환경에서 학습을 진행해보았습니다.

 

ResNet에 대한 내용은 아래 게시글 중간 부분에서 설명하고 있습니다.(Coursera Deep Learning 강의 내용)

2020/11/18 - [Coursera 강의/Deep Learning] - CNN (LeNet-5, AlexNet, VGG-16, ResNets, Inception Network)

 

 

1. Dataset 준비

이전 글에서와 마찬가지로 필요한 dataset을 읽어옵니다. 그리고 여러번 돌리다보면.. GPU 메모리가 터질 가능성도 있기 때문에 메모리 제한도 해제해주었습니다.

import tensorflow as tf
import matplotlib.pyplot as plt
import h5py
import numpy as np

gpus = tf.config.experimental.list_physical_devices('GPU')
if gpus:
  try:
    # Currently, memory growth needs to be the same across GPUs
    #for gpu in gpus:
    tf.config.experimental.set_memory_growth(gpus[0], True)
    logical_gpus = tf.config.experimental.list_logical_devices('GPU')
    print(len(gpus), "Physical GPUs,", len(logical_gpus), "Logical GPUs")
  except RuntimeError as e:
    # Memory growth must be set before GPUs have been initialized
    print(e)

def load_dataset():
    train_dataset = h5py.File('train_signs.h5', "r")
    train_set_x_orig = np.array(train_dataset["train_set_x"][:]) # your train set features
    train_set_y_orig = np.array(train_dataset["train_set_y"][:]) # your train set labels

    test_dataset = h5py.File('test_signs.h5', "r")
    test_set_x_orig = np.array(test_dataset["test_set_x"][:]) # your test set features
    test_set_y_orig = np.array(test_dataset["test_set_y"][:]) # your test set labels

    classes = np.array(test_dataset["list_classes"][:]) # the list of classes
    
    return train_set_x_orig, train_set_y_orig, test_set_x_orig, test_set_y_orig, classes
    
# Loading the data (signs)
X_train_orig, Y_train_orig, X_test_orig, Y_test_orig, classes = load_dataset()

# Dataset 전처리
X_train = X_train_orig.astype(np.float32)/255.
X_test = X_test_orig.astype(np.float32)/255.
Y_train = np.array(tf.one_hot(Y_train_orig, 6))
Y_test = np.array(tf.one_hot(Y_test_orig, 6))
print ("number of training examples = " + str(X_train.shape[0]))
print ("number of test examples = " + str(X_test.shape[0]))
print ("X_train shape: " + str(X_train.shape))
print ("Y_train shape: " + str(Y_train.shape))
print ("X_test shape: " + str(X_test.shape))
print ("Y_test shape: " + str(Y_test.shape))

 

2. Identity Block, Convolutional Block 구현

우리가 구현할 모델은 ResNet-50이며, 아래와 같은 구조를 갖고 있습니다.

출처 : Coursera

모델을 구현하기 전에, 우선 모델에서 사용되는 Identity Block과 Convolutional Block을 구현해보도록 하겠습니다.

 

- Identity Block

Identity Block은 아래와 같은 구조를 갖고 있습니다.

input으로 들어오는 x가 2개의 layer를 뛰어넘고 activation function을 적용하기 전에 더해집니다.

# The identity block
def identity_block(X, f, filters, stage, block):
    conv_name_base = 'res' + str(stage) + block + '_branch'
    bn_name_base = 'bn' + str(stage) + block + '_branch'
    
    F1, F2, F3 = filters
    
    X_shortcut = X
    
    # first step of main path
    X = tf.keras.layers.Conv2D(filters=F1, kernel_size=1, strides=1, padding='valid', name=conv_name_base + '2a',
                              kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    X = tf.keras.layers.BatchNormalization(axis=3, name=bn_name_base+'2a')(X)
    X = tf.keras.layers.Activation('relu')(X)
    
    # second step of main path
    X = tf.keras.layers.Conv2D(filters=F2, kernel_size=f, strides=1, padding='same', name=conv_name_base + '2b',
                              kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    X = tf.keras.layers.BatchNormalization(axis=3, name=bn_name_base+'2b')(X)
    X = tf.keras.layers.Activation('relu')(X) 
    
    # third step of main path
    X = tf.keras.layers.Conv2D(filters=F3, kernel_size=1, strides=1, padding='valid', name=conv_name_base + '2c',
                              kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    X = tf.keras.layers.BatchNormalization(axis=3, name=bn_name_base+'2c')(X)
    
    # add shortcut value and pass it through a ReLU activation
    X = tf.keras.layers.Add()([X, X_shortcut])
    X = tf.keras.layers.Activation('relu')(X)
    
    return X

 

- Convolutional Block

Convolutional Block은 아래의 구조를 갖습니다.

Identity Block과 동일하지만 shortcut을 통과하는 input x에도 convolution과 batch normalization을 적용해서 마지막 activation 적용 전에 더해주게 됩니다.

# Convolutional Block
def convolutional_block(X, f, filters, stage, block, s=2):
    conv_name_base = 'res'+str(stage)+block+'_branch'
    bn_name_base = 'bn'+str(stage)+block+'_branch'
    
    F1, F2, F3 = filters
    
    X_shortcut = X
    
    # first step of main path
    X = tf.keras.layers.Conv2D(filters=F1, kernel_size=1, strides=s, padding='valid', name=conv_name_base+'2a',
                              kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    X = tf.keras.layers.BatchNormalization(axis=3, name=bn_name_base+'2a')(X)
    X = tf.keras.layers.Activation('relu')(X)
    
    # second step of main path
    X = tf.keras.layers.Conv2D(filters=F2, kernel_size=f, strides=1, padding='same', name=conv_name_base+'2b',
                              kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    X = tf.keras.layers.BatchNormalization(axis=3, name=bn_name_base+'2b')(X)
    X = tf.keras.layers.Activation('relu')(X)
    
    # third step of main path
    X = tf.keras.layers.Conv2D(filters=F3, kernel_size=1, strides=1, padding='valid', name=conv_name_base+'2c',
                              kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    X = tf.keras.layers.BatchNormalization(axis=3, name=bn_name_base+'2c')(X)
    
    # shortcut path
    X_shortcut = tf.keras.layers.Conv2D(filters=F3, kernel_size=1, strides=s, padding='valid', name=conv_name_base+'1',
                                       kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X_shortcut)
    X_shortcut = tf.keras.layers.BatchNormalization(axis=3, name=bn_name_base+'1')(X_shortcut)
    
    # Add and pass it through a ReLU activation
    X = tf.keras.layers.Add()([X, X_shortcut])
    X = tf.keras.layers.Activation('relu')(X)
    
    return X

 

3. ResNet-50 Model 구현

앞서 정의한 Identity Block과 Convolutional Block을 사용해서 Resnet-50 Model을 구현해보겠습니다.

Deep Residual learning for Image Recognition

# ResNet50
def ResNet50(input_shape=(64,64,3), classes=6):
    X_input = tf.keras.layers.Input(input_shape)
    
    # zero padding
    X = tf.keras.layers.ZeroPadding2D((3,3))(X_input)
    
    # stage 1
    X = tf.keras.layers.Conv2D(filters=64, kernel_size=7, strides=2, name='conv1',
                              kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    X = tf.keras.layers.BatchNormalization(axis=3, name='bn_conv1')(X)
    X = tf.keras.layers.Activation('relu')(X)
    X = tf.keras.layers.MaxPooling2D((3,3), strides=(2,2))(X)
    
    # stage 2
    X = convolutional_block(X, f=3, filters=[64,64,256], stage=2, block='a', s=1)
    X = identity_block(X, 3, [64,64,256], stage=2, block='b')
    X = identity_block(X, 3, [64,64,256], stage=2, block='c')
    
    # stage 3
    X = convolutional_block(X, f = 3, filters = [128, 128, 512], stage = 3, block='a', s = 2)
    X = identity_block(X, 3, [128, 128, 512], stage = 3, block='b')
    X = identity_block(X, 3, [128, 128, 512], stage = 3, block='c')
    X = identity_block(X, 3, [128, 128, 512], stage = 3, block='d')
    
    # Stage 4
    X = convolutional_block(X, f = 3, filters = [256, 256, 1024], stage = 4, block='a', s = 2)
    X = identity_block(X, 3, [256, 256, 1024], stage = 4, block='b')
    X = identity_block(X, 3, [256, 256, 1024], stage = 4, block='c')
    X = identity_block(X, 3, [256, 256, 1024], stage = 4, block='d')
    X = identity_block(X, 3, [256, 256, 1024], stage = 4, block='e')
    X = identity_block(X, 3, [256, 256, 1024], stage = 4, block='f')

    # Stage 5
    X = convolutional_block(X, f = 3, filters = [512, 512, 2048], stage = 5, block='a', s = 2)
    X = identity_block(X, 3, [512, 512, 2048], stage = 5, block='b')
    X = identity_block(X, 3, [512, 512, 2048], stage = 5, block='c')
    
    # AVGPOOL
    X = tf.keras.layers.AveragePooling2D()(X)
    
    # output layer
    X = tf.keras.layers.Flatten()(X)
    X = tf.keras.layers.Dense(classes, activation='softmax', name='fc'+str(classes),
                             kernel_initializer=tf.keras.initializers.glorot_uniform(seed=0))(X)
    
    # Create Model
    model = tf.keras.models.Model(inputs=X_input, outputs=X, name='ResNet50')
    
    return model

 

4. 학습

구현은 완료했고, 이제 모델을 생성하고 학습을 진행해보도록 하겠습니다.

model = ResNet50(input_shape = (64, 64, 3), classes = 6)
model.summary()
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])

Model summary 일부

최적화 알고리즘으로는 Adam 알고리즘을 사용했고, 하이퍼파라미터는 기본값으로 설정했습니다.

 

batch size = 32, epoch = 30으로 학습을 진행해봅시다.

hist = model.fit(X_train, Y_train, epochs = 30, batch_size = 32)

마지막 10 epoch의 학습 내용

training acc는 100%가 나왔습니다. overfitting된 것이 아닐까 의심이 조금 되긴하지만, 우선 test data로 평가해보도록 하겠습니다.

model.evaluate(X_test, Y_test)

오.. test acc가 98%가 나왔습니다.

이전 게시글에서 약 90%의 정확도에 비해서 훨씬 더 성능이 향상된 것을 확인할 수 있습니다.

costs = hist.history['loss']
# plot the cost
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('epochs')
plt.title("Learning rate = 0.01")
plt.show()

 

이상으로 Hands SIGN dataset을 ResNet으로 학습해보았습니다.

댓글