(tensorflow v2.3.0)
2020/11/17 - [ML & DL/tensorflow] - Hand SIGNS 분류 예제 (1)
이번에는 이전 게시글에서 사용했던 dataset으로 Convolutional Neural Network(ConvNet) 구조로 모델을 구현하고 학습을 해보려고 합니다.
그리고, 이전 게시글에서 언급했듯이 label data를 one_hot 처리를 해서 loss object로 'categorical entropy'를 사용할 것입니다.
1. Dataset 준비
준비과정은 이전과 유사하며, Y_train과 Y_test에 대해서 tf.one_hot 함수를 통해 각 라벨에 해당하는 확률로 변경시켜 줍니다. 즉, 각 라벨에 해당하는 열은 1이고 나머지 열은 0이 됩니다.
import tensorflow as tf
import matplotlib.pyplot as plt
import h5py
import numpy as np
import math
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()
# Example of a picture
index = 6
plt.imshow(X_train_orig[index])
print ("y = " + str(np.squeeze(Y_train_orig[index])))
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))
Y_train과 Y_test가 각 라벨의 확률을 나타내기 때문에, (m, 6)의 형태로 나타납니다. 6은 라벨 class의 개수입니다.
그리고 mini-batch를 사용해서 최적화를 할 예정인데, 직접 mini_batch를 무작위로 섞는 함수를 사용합니다.
def random_mini_batches(X, Y, mini_batch_size = 64, seed = 0):
m = X.shape[0]
mini_batches = []
np.random.seed(seed)
# Step 1: Shuffle (X, Y)
permutation = list(np.random.permutation(m))
shuffled_X = X[permutation,:,:,:]
shuffled_Y = Y[permutation,:]
# Step 2: Partition (shuffled_X, shuffled_Y)
num_complete_minibatches = math.floor(m/mini_batch_size)
for k in range(0, num_complete_minibatches):
mini_batch_X = shuffled_X[k * mini_batch_size:k * mini_batch_size + mini_batch_size,:,:,:]
mini_batch_Y = shuffled_Y[k * mini_batch_size:k * mini_batch_size + mini_batch_size,:]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
# Handling the end case
if m % mini_batch_size != 0:
mini_batch_X = shuffled_X[num_complete_minibatches * mini_batch_size:m,:,:,:]
mini_batch_Y = shuffled_Y[num_complete_minibatches * mini_batch_size:m,:]
mini_batch = (mini_batch_X, mini_batch_Y)
mini_batches.append(mini_batch)
return mini_batches
오늘 두 가지의 방법으로 동일한 모델을 구현할 예정인데, 첫 번째 방법에서는 이 함수를 사용하고, 두 번째 방법에서는 이 함수를 사용하지 않고, mini-batch를 사용해서 학습해보도록 하겠습니다.
시작하기에 앞서서 구현해야될 ConvNet의 구조는 다음과 같습니다.
Conv2D(F=8, stride=1, padding='same') -> ReLU -> Max pool(filter=(8,8), strides=(8,8)
-> Conv2D(F=16, stride=1, padding='same') -> ReLU -> Max pool(filter=(4,4), strides=(4,4)
-> Flatten -> Softmax(6 units)
2-1. tf.keras.Model을 상속받는 Model Class 구현 및 학습
www.tensorflow.org/tutorials/quickstart/advanced?hl=ko
위 튜토리얼에서 알려주는 방법을 토대로 구현을 해보았습니다.
class ConvNet(tf.keras.Model):
def __init__(self):
super(ConvNet, self).__init__()
self.conv1 = tf.keras.layers.Conv2D(8, (4, 4), strides=(1,1), padding='same',
activation='relu', kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0))
self.maxpool1 = tf.keras.layers.MaxPool2D(pool_size=(8,8), strides=(8,8), padding='same')
self.conv2 = tf.keras.layers.Conv2D(16, (2, 2), strides=(1,1), padding='same',
activation='relu', kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0))
self.maxpool2 = tf.keras.layers.MaxPool2D(pool_size=(4,4), strides=(4,4), padding='same')
self.flatten = tf.keras.layers.Flatten()
self.d1 = tf.keras.layers.Dense(6, activation='softmax',
kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0))
def call(self, x):
x = self.conv1(x)
x = self.maxpool1(x)
x = self.conv2(x)
x = self.maxpool2(x)
x = self.flatten(x)
x = self.d1(x)
return x
class 생성자를 통해서 layer들을 만들어주고, call을 통해 \(\hat{y}\)를 계산합니다. 이 예측값은 각 라벨의 확률을 의미합니다.
다음으로는 학습을 진행할 함수를 구현합니다.
# Model
def Model_1(X_train, Y_train, X_test, Y_test, learning_rate=0.009,
num_epochs=100, minibatch_size=64, print_cost=True):
tf.random.set_seed(4)
(m, n_H0, n_W0, n_C0) = X_train.shape
seed = 3
costs = []
model = ConvNet()
loss_object = tf.keras.losses.CategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam(learning_rate=learning_rate)
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.CategoricalCrossentropy(name='train_accuracy')
for epoch in range(num_epochs):
minibatch_cost=0
num_minibatches = int(m/minibatch_size)
seed = seed + 1
minibatches = random_mini_batches(X_train, Y_train, minibatch_size, seed)
for minibatch in minibatches:
(minibatch_X, minibatch_Y) = minibatch
with tf.GradientTape() as tape:
pred = model(minibatch_X)
cost = loss_object(minibatch_Y, pred)
grads = tape.gradient(cost, model.trainable_variables)
optimizer.apply_gradients(zip(grads, model.trainable_variables))
train_loss(cost)
train_accuracy(minibatch_Y, pred)
minibatch_cost += cost / num_minibatches
if print_cost and epoch % 5 == 0:
print(f'Cost after epoch {epoch}: {minibatch_cost}')
costs.append(minibatch_cost)
# plot the cost
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
# training set
y_pred = model(X_train)
y_pred = tf.math.argmax(y_pred, 1)
y_true = tf.math.argmax(Y_train, 1)
train_acc = tf.math.reduce_mean(tf.cast(tf.equal(y_pred, y_true), tf.float32))
y_pred = model(X_test)
y_pred = tf.math.argmax(y_pred, 1)
y_true = tf.math.argmax(Y_test, 1)
test_acc = tf.math.reduce_mean(tf.cast(tf.equal(y_pred, y_true), tf.float32))
print(f'Train acc : {train_acc}')
print(f'Test acc : {test_acc}')
return model
이전글에서 구현하던 방식과는 다르게 model class를 직접 구현하고, 직접 예측 확률값과 loss를 구한 다음에 tf.GradientTape()를 사용해 연산을 기록하고 자동으로 미분항를 구하고 있습니다.
그리고 학습을 진행합니다. learning_rate(0.009), epochs(100), minibatch_size(64)는 기본값으로 사용했습니다.
m = Model_1(X_train, Y_train, X_test, Y_test)
이전 게시글에서 Regularization을 적용했을 때와 유사한 결과가 나오고 있습니다. 아직 overfitting이 되었을 가능성이 있어 보이기도 하네요.
모델의 구성을 살펴보면 아래처럼 구성된 것을 볼 수 있습니다.
2-2. keras sequential model을 통한 구현 및 학습
두 번째 방법으로 구현을 해보도록 하겠습니다. 이 방법은 이전 게시글들에서 사용하던 방법과 동일합니다.
def Model_2(X_train, Y_train, X_test, Y_test, learning_rate=0.009,
num_epochs=100, minibatch_size=64):
tf.random.set_seed(0)
seed = 3
(m, n_H0, n_W0, n_C0) = X_train.shape
n_y = Y_train.shape[1]
costs = []
# initialize parameters
#parameters = initialize_parameters()
# setting optimizer
optimizer = tf.optimizers.Adam(learning_rate=learning_rate)
# CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(8, (4, 4), strides=(1,1), padding="SAME",
activation='relu', input_shape=(64, 64, 3),
kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0)),
tf.keras.layers.MaxPool2D(pool_size=(8,8), strides=(8,8), padding="SAME"),
tf.keras.layers.Conv2D(16, (2, 2), strides=(1,1), padding="SAME", activation='relu',
kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0)),
tf.keras.layers.MaxPool2D(pool_size=(4,4), strides=(4,4), padding="SAME"),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(6, activation='softmax',
kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0))
])
model.summary()
model.compile(optimizer=optimizer,
loss='categorical_crossentropy',
metrics=['accuracy'])
hist = model.fit(X_train, Y_train, epochs=num_epochs, batch_size=minibatch_size, verbose=1)
costs = hist.history['loss']
for epoch in range(0, num_epochs, 5):
print(f'Cost after epoch {epoch}: {costs[epoch]}')
# plot the cost
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
train_acc = hist.history['accuracy'][-1]
y_pred = model.predict(X_test)
y_pred = tf.math.argmax(y_pred, 1)
y_true = tf.math.argmax(Y_test, 1)
test_acc = tf.math.reduce_mean(tf.cast(tf.equal(y_pred, y_true), tf.float32))
print(f'Train acc : {train_acc}')
print(f'Test acc : {test_acc}')
return model
그리고 학습을 진행합니다.
m2 = Model_2(X_train, Y_train, X_test, Y_test)
이전 게시글과 유사한 현상이 ConvNet에서도 발생했습니다. 아마, 초기화가 적절하지 않았기 때문에 발생했다고 추측하고 global seed를 0에서 2로 변경해서 다시 학습을 진행해보았습니다.
global seed = 2(tf.random.set_seed(2))
m3 = Model_2(X_train, Y_train, X_test, Y_test)
결과가 정상적인 성능으로 나왔습니다. class로 구현한 것보다 조금 덜 overfitting해보이며, Test 성능은 거의 동일하게 나오고 있습니다.
- Regularization 적용
두 번째 모델을 가지고 L2 Regularization을 적용해보도록 하겠습니다.
def Model_2_Reg(X_train, Y_train, X_test, Y_test, learning_rate=0.009,
num_epochs=100, minibatch_size=64, lambd=0.01):
tf.random.set_seed(2)
seed = 3
(m, n_H0, n_W0, n_C0) = X_train.shape
n_y = Y_train.shape[1]
costs = []
# initialize parameters
#parameters = initialize_parameters()
# setting optimizer
optimizer = tf.optimizers.Adam(learning_rate=learning_rate)
# setting regularizer
regularizer = tf.keras.regularizers.L2(lambd)
# CONV2D -> RELU -> MAXPOOL -> CONV2D -> RELU -> MAXPOOL -> FLATTEN -> FULLYCONNECTED
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(8, (4, 4), strides=(1,1), padding="SAME",
activation='relu', input_shape=(64, 64, 3),
kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0),
kernel_regularizer=regularizer),
tf.keras.layers.MaxPool2D(pool_size=(8,8), strides=(8,8), padding="SAME"),
tf.keras.layers.Conv2D(16, (2, 2), strides=(1,1), padding="SAME", activation='relu',
kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0),
kernel_regularizer=regularizer),
tf.keras.layers.MaxPool2D(pool_size=(4,4), strides=(4,4), padding="SAME"),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(6, activation='softmax',
kernel_initializer=tf.keras.initializers.GlorotUniform(seed=0),
kernel_regularizer=regularizer)
])
model.summary()
model.compile(optimizer=optimizer,
loss='categorical_crossentropy',
metrics=['accuracy'])
hist = model.fit(X_train, Y_train, epochs=num_epochs, batch_size=minibatch_size, verbose=1)
costs = hist.history['loss']
for epoch in range(0, num_epochs, 5):
print(f'Cost after epoch {epoch}: {costs[epoch]}')
# plot the cost
plt.plot(np.squeeze(costs))
plt.ylabel('cost')
plt.xlabel('iterations (per tens)')
plt.title("Learning rate =" + str(learning_rate))
plt.show()
train_acc = hist.history['accuracy'][-1]
y_pred = model.predict(X_test)
y_pred = tf.math.argmax(y_pred, 1)
y_true = tf.math.argmax(Y_test, 1)
test_acc = tf.math.reduce_mean(tf.cast(tf.equal(y_pred, y_true), tf.float32))
print(f'Train acc : {train_acc}')
print(f'Test acc : {test_acc}')
return model
m4 = Model_2(X_train, Y_train, X_test, Y_test)
lambda의 값이 너무 커서 파라미터의 영향을 너무 감소시켜버린 것 같습니다.
- lambda = 0.001
m5 = Model_2_Reg(X_train, Y_train, X_test, Y_test, lambd=0.001)
lambda 값을 감소시켰더니, 테스트 성능이 조금 증가했습니다만, 학습 성능도 증가해버렸네요. Regularization의 효과가 쪼금은 나타난 것 같습니다. 그렇지만 dataset의 크기가 작은 편이기 때문에 overfitting을 줄이기에는 조금 힘들어 보이긴 하네요.. 이 부분은 나중에 성능을 확인해볼 수 있는 큰 dataset이 있을 때, 다시 비교해보도록 하겠습니다.
마지막으로 epochs를 200으로 증가시켜서 학습을 해보았습니다.
m6 = Model_2_Reg(X_train, Y_train, X_test, Y_test, num_epochs=200, lambd=0.001)
거의 90%의 테스트 성능을 달성했습니다 !
'ML & DL > tensorflow' 카테고리의 다른 글
Hand SIGNS 분류 예제 (3) - ResNet (0) | 2020.11.24 |
---|---|
MNIST dataset 예제(ConvNet, VGG16) (0) | 2020.11.22 |
Hand SIGNS 분류 예제 (1) (0) | 2020.11.17 |
batch GD (with momentum, adam) 비교 (0) | 2020.11.16 |
Regularization 적용에 따른 학습 비교 (0) | 2020.11.16 |
댓글