본문 바로가기
ML & DL/tensorflow

Hand SIGNS 분류 예제 (1)

by 별준 2020. 11. 17.

(tensorflow v2.3.0)

 

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

 

신경망은 이전에 고양이 분류와 같이 입력을 flatten해주고, 2개의 hidden layer를 갖도록 할 것이고, 우리가 구현해야 할 신경망의 구조는 다음과 같습니다.

Input -> linear(25 units) -> relu -> linear(12 units) -> relu -> softmax(6 units)

 

1. Dataset 준비

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

%matplotlib inline

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 dataset
X_train_orig, Y_train, X_test_orig, Y_test, classes = load_dataset()

# Example of a picture
index = 0
plt.imshow(X_train_orig[index])
print ("y = " + str(np.squeeze(Y_train_orig[index])))

dataset을 읽어들이고, 샘플 이미지를 살펴보면, 64x64x3의 값을 가지는 이미지라는 것을 확인할 수 있습니다.

그리고, flatten과 normalization 과정을 수행합니다.

# Flatten
X_train_flatten = X_train_orig.reshape(X_train_orig.shape[0], -1)
X_test_flatten = X_test_orig.reshape(X_test_orig.shape[0], -1)

# normalization
X_train = X_train_flatten / 255.
X_test = X_test_flatten / 255.

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. Model 구현

앞서 언급했듯이 2개의 hidden layer를 갖도록 하며, hidden layer들은 각각 25, 12개의 입력을 갖도록 할 것입니다.

# input -> linear(25) -> relu -> linear(12) -> relu -> softmax(6)
layers_dims = [12288, 25, 12, 6]

그리고, tensorflow로 모델을 구현하면 다음과 같습니다.

def Model(train_x, train_y, test_x, test_y, layers_dims, 
          learning_rate=0.0001, num_epochs=1500, batch_size=32):
    tf.random.set_seed(2)
    L = len(layers_dims)
    costs = []

    optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate)
    initializer=tf.keras.initializers.glorot_uniform(seed=2)

    model = tf.keras.models.Sequential([
        tf.keras.layers.Input(shape=(train_x.shape[1],))
    ])

    for i in range(1, L):
        if i != L-1:
            model.add(tf.keras.layers.Dense(layers_dims[i],
                                            activation='relu',
                                            kernel_initializer=initializer))
        else:
            model.add(tf.keras.layers.Dense(layers_dims[i],
                                            activation='softmax',
                                            kernel_initializer=initializer))
    
    model.summary()

    model.compile(optimizer=optimizer,
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    
    hist = model.fit(train_x, train_y, batch_size=batch_size, epochs=num_epochs, verbose=0)

    for i in range(0, num_epochs, 5):
        costs.append(hist.history['loss'][i])

        if i % 100 == 0:
            print(f"Cost after epoch {i}: {hist.history['loss'][i]}")
    
    #plot the cost
    plt.plot(costs)
    plt.ylabel('cost')
    plt.xlabel('epochs (per 5)')
    plt.title('Learning rate ='+ str(learning_rate))
    plt.show()

    train_acc = hist.history['accuracy'][-1]

    model.evaluate(test_x, test_y)
    y_pred = model.predict(test_x)
    test_acc = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(y_pred, 1), tf.cast(test_y, tf.int64)), tf.float32))

    print (f"Training Accuracy: {train_acc*100}%")
    print (f"Test Accuracy: {test_acc*100}%")

    return model, hist

모델의 loss object는 'sparse_categorical_crossentropy'를 사용했습니다. output이 각 label이 될 확률을 구하는 softmax logistic regression이기 때문에, 예측값은 각 label에 해당하는 확률이 될 것입니다.

+) 'categorical_crossentropy'와는 차이가 있는데, 학습에 사용하는 y_true이 label값이라면 'sparse_categorical_crossentropy'를 사용하면되고, 만약 학습에 사용하는 y_true의 형태가 각 label의 확률(one_hot 처리를 했는 경우)이라면, 'categorical_crossentropy'를 사용해야 합니다. 'sparse_categorical_crossentropy'의 경우에는 loss를 구할 때, 예측한 y_pred가 각 label에 대한 확률이기 때문에 y_true를 one_hot시켜서 구하게 됩니다.

다음에 똑같은 dataset을 다른 방법으로 구현해 볼 예정인데, 그때 'categorical_crossentropy'를 사용해보겠습니다.

참조 : tf.keras.losses.sparse_categorical_crossentropy, tf.keras.losses.categorical_crossentropy

 

3. 학습

m1, h1 = Model(X_train, Y_train, X_test, Y_test, layers_dims)

기본 설정값(learning_rate=0.0001, num_epochs=1500, batch_size=32)으로 학습을 해보았습니다.

Coursera 실습과 동일한 hyperparameter설정으로 했는데, Train 정확도에서 큰 차이를 보이고 있습니다.(실습에서는 99%로 overfitting됨)

아마 random seed와 초기화 방법에 따라서 학습 결과가 조금씩 달라지는 것으로 추측됩니다. 여기서 operation seed는 초기화에 사용되는 random seed입니다.

 

그래서, global seed와 operation seed를 다르게 시험을 몇 번 해보았는데, 꽤 상이한 결과들이 많이 나왔습니다.

global seed와 operation seed를 바꾸어 주었더니, train 정확도가 100%가 나와서 완벽하게 overfitting된 모습을 볼 수 도 있었습니다. 그래도 1080개의 샘플을 통해서 이정도 학습이면, 꽤나 괜찮은 결과라고도 볼 수 있을 것 같은데... 전혀 새로운 사진들로 테스트해보면 어떨지 궁금하긴 하네요.

 

 

- learning rate 변경 실험(0.0005으로 변경)

첫번째 학습(learning_rate=0.0001, global seed=2, operation seed=2)에서 learning rate를 바꾸면 어떻게 되는지 궁금해져서 실험을 해보았습니다.

m2, h2 = Model(X_train, Y_train, X_test, Y_test, layers_dims, learning_rate=0.0005)

cost가 1.79에서 계속 머물면서, 학습이 잘 되지 않는 모습을 보여주고 있습니다.

 

4. L2 Regularization 적용

두 번째 학습(global seed=3, operation seed=2)에서 과적합된 모습을 보여주고 있는데, L2 Regularization을 적용하면 결과가 어떻게 될 지 시도해보겠습니다.

L2 Regularization을 적용한 모델은 아래처럼 구현하였습니다. 기본 lambda 값은 0.01입니다.

def Model_L2(train_x, train_y, test_x, test_y, layers_dims, learning_rate=0.0001, 
             num_epochs=1500, batch_size=32, lambd=0.01):
    tf.random.set_seed(3)
    L = len(layers_dims)
    costs = []

    optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate)
    initializer=tf.keras.initializers.glorot_uniform(seed=0)
    regularizer=tf.keras.regularizers.l2(lambd)

    model = tf.keras.models.Sequential([
        tf.keras.layers.Input(shape=(train_x.shape[1],))
    ])

    for i in range(1, L):
        if i != L-1:
            model.add(tf.keras.layers.Dense(layers_dims[i],
                                            activation='relu',
                                            kernel_initializer=initializer,
                                            kernel_regularizer=regularizer))
        else:
            model.add(tf.keras.layers.Dense(layers_dims[i],
                                            activation='softmax',
                                            kernel_initializer=initializer))
    
    model.summary()

    model.compile(optimizer=optimizer,
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    
    hist = model.fit(train_x, train_y, batch_size=batch_size, epochs=num_epochs, verbose=0)

    for i in range(0, num_epochs, 5):
        costs.append(hist.history['loss'][i])

        if i % 100 == 0:
            print(f"Cost after epoch {i}: {hist.history['loss'][i]}")
    
    #plot the cost
    plt.plot(costs)
    plt.ylabel('cost')
    plt.xlabel('epochs (per 5)')
    plt.title('Learning rate ='+ str(learning_rate))
    plt.show()

    train_acc = hist.history['accuracy'][-1]

    model.evaluate(test_x, test_y)
    y_pred = model.predict(test_x)
    test_acc = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(y_pred, 1), tf.cast(test_y, tf.int64)), tf.float32))

    print (f"Training Accuracy: {train_acc*100}%")
    print (f"Test Accuracy: {test_acc*100}%")

    return model, hist

 

lambda값을 증가시키면서 학습해보도록 하겠습니다.

- lambda = 0.01

m3, h3 = Model_L2(X_train, Y_train, X_test, Y_test, layers_dims)

L2 Regularization을 적용하지 않았을 때(83.3%)보다 88.3%의 Test 정확도로 Test Dataset에서는 조금 더 성능이 좋아진 것 같습니다만, 여전히 Train 정확도가 99%로 매우 높고, Overfitting의 확률이 있어보입니다.

 

- lambda = 0.03

m4, h4 = Model_L2(X_train, Y_train, X_test, Y_test, layers_dims, lambd=0.03)

드라마틱한 결과는 없었고, 0.01일 때와 크게 다르지 않았습니다.

 

- lambda = 0.1

m5, h5 = Model_L2(X_train, Y_train, X_test, Y_test, layers_dims, lambd=0.1)

lambda값을 증가시킨다고 더 좋아지지는 않는 것 같습니다.

 

5. Dropout 적용

dropout을 적용하면 어떻게 될 지 궁금해서 L2 Regularization 대신, dropout을 적용해서 학습해보았습니다.

def Model_dropout(train_x, train_y, test_x, test_y, layers_dims, learning_rate=0.0001,
                  num_epochs=1500, batch_size=32, keep_prob=0.8):
    tf.random.set_seed(3)
    L = len(layers_dims)
    costs = []

    optimizer=tf.keras.optimizers.Adam(learning_rate=learning_rate)
    initializer=tf.keras.initializers.glorot_uniform(seed=0)

    model = tf.keras.models.Sequential([
        tf.keras.layers.Input(shape=(train_x.shape[1],))
    ])

    for i in range(1, L):
        if i != L-1:
            model.add(tf.keras.layers.Dense(layers_dims[i],
                                            activation='relu',
                                            kernel_initializer=initializer))
            if keep_prob != 1.0:
                model.add(tf.keras.layers.Dropout(1-keep_prob))
        else:
            model.add(tf.keras.layers.Dense(layers_dims[i],
                                            activation='softmax',
                                            kernel_initializer=initializer))
    
    model.summary()

    model.compile(optimizer=optimizer,
                  loss='sparse_categorical_crossentropy',
                  metrics=['accuracy'])
    
    hist = model.fit(train_x, train_y, batch_size=batch_size, epochs=num_epochs, verbose=0)

    for i in range(0, num_epochs, 5):
        costs.append(hist.history['loss'][i])

        if i % 100 == 0:
            print(f"Cost after epoch {i}: {hist.history['loss'][i]}")
    
    #plot the cost
    plt.plot(costs)
    plt.ylabel('cost')
    plt.xlabel('epochs (per 5)')
    plt.title('Learning rate ='+ str(learning_rate))
    plt.show()

    train_acc = hist.history['accuracy'][-1]

    model.evaluate(test_x, test_y)
    y_pred = model.predict(test_x)
    test_acc = tf.reduce_mean(tf.cast(tf.equal(tf.argmax(y_pred, 1), tf.cast(test_y, tf.int64)), tf.float32))

    print (f"Training Accuracy: {train_acc*100}%")
    print (f"Test Accuracy: {test_acc*100}%")

    return model, hist

- keep_prob = 0.8

m6, h6 = Model_dropout(X_train, Y_train, X_test, Y_test, layers_dims)

Train 정확도 74.4%, Test 정확도 77.5%로 overfitting은 해결한 것 같습니다. 

 

추가로, 위 결과에서 반복횟수를 2000으로 증가시켜서 한 번 테스트해보았습니다.

성능이 더 떨어졌습니다. cost가 요동치는 것으로 보아, 반복횟수보다는 learning rate에 의해서 overshooting이 발생한 것으로 추측됩니다.

 

마지막으로 learning_rate를 10분의1로 줄여서 테스트해보았습니다.

m8, h8 = Model_dropout(X_train, Y_train, X_test, Y_test, layers_dims, learning_rate=0.00001)

테스트 성능이 조금 더 향상되었습니다...!

 

강의에서 hyperparameter는 여러가지 시도해보는 것이라고 했는데, 이렇게 직접 실험을 통해서 여러가지를 시도해보니 확실히 와닿는 것 같네요. 하지만 아직 어떤 값이 직감적으로 더 괜찮을 지 감은 오지 않는 것 같습니다... 

 

다음에는 같은 dataset을 가지고, Convolutional Neural Network를 사용해서 구현해보도록 하겠습니다.

댓글