Basic ConvNN(VGG-16모방한 기본)구현

Basic ConvNN 구현

  • 참고로 저는 mac을 사용하기에 local에서말고 GPU를 사용하게끔 Google Colab을 사용하였다. 제가 구현한 방식은 tensorflow 2.0 version이므로(tf.function을 사용하느라) colab의 tensorflow의 version이 뭔지 먼저 확인했습니다. 1.15 version이어서 2.0으로 설치를 진행한 후 코드를 실행하였습니다. 참고로 2.0으로 설치하고 난 후에는 꼭 반드시 런타임을 재시작 해주셔야 업데이트 한 2.0 version으로 사용하실 수 있습니다.
1
2
3
import tensorflow as tf
import numpy as np
print(tf.__version__)

런타임 재시작 후

1
!pip install tensorflow==2.0.0-beta1

기본 합성곱 신경망 구현

1
2
import tensorflow as tf
import numpy as np

하이퍼 파라미터

1
EPOCHS = 10

참고로 conv layer을 통과한 출력의 dimension을 계산하는 것은 다음과 같다.

padding : 2N+1 = kernel_size(Filter_size)로 N을 구한다.

output dimension :

모델 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class ConvNet(tf.keras.Model):
def __init__(self):
super(ConvNet, self).__init__()
self.sequence = []
conv2d = tf.keras.layers.Conv2D
max_pool = tf.keras.layers.MaxPool2D
flatten = tf.keras.layers.Flatten
# filters = 16 (출력되는 channel의 수)
# kernel_size = 3 * 3
# padding의 default값인 'valid'는 zero-padding을 해주지 않음으로써 영상의 크기가 Conv layer를 통과함으로써 줄어들 수 있다.
# 'same'은 zero-padding을 의미 여기서는 동일한 크기를 유지하기 위해
# input data는 (28x28x1)을 갖는 MNIST이다.
# VGG-16의 가장 큰 특징은 Pooling을 하기 전에 동일한 Conv Layer를 반복해서 사용하는 것이다.
self.sequence.append(conv2d(16, (3,3), padding='same', activation='relu')) # output dimension (28x28x16)
self.sequence.append(conv2d(16, (3,3), padding='same', activation='relu')) # output dimension (28x28x16)
# 2x2 pooling을 한다. maxpooling을 이용하여 영상의 크기를 줄여준다.
self.sequence.append(max_pool((2,2))) # output dimension (14x14x16)

self.sequence.append(conv2d(32, (3,3), padding='same', activation='relu')) # output dimension (14x14x32)
self.sequence.append(conv2d(32, (3,3), padding='same', activation='relu')) # output dimension (14x14x32)
self.sequence.append(max_pool((2,2))) # output dimension (7x7x32)

self.sequence.append(conv2d(64, (3,3), padding='same', activation='relu')) # output dimension (7x7x64)
self.sequence.append(conv2d(64, (3,3), padding='same', activation='relu')) # output dimension (7x7x64)

self.sequence.append(flatten()) # 1568x1
self.sequence.append(tf.keras.layers.Dense(2028, activation='relu'))
self.sequence.append(tf.keras.layers.Dense(10, activation='softmax'))

def call(self, x, training=False, mask=None):
for layer in self.sequence:
x = layer(x)
return x

학습, 테스트 루프 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# Implement training loop
@tf.function
def train_step(model, images, labels, loss_object, optimizer, train_loss, train_accuracy):
with tf.GradientTape() as tape:
predictions = model(images)
loss = loss_object(labels, predictions)
gradients = tape.gradient(loss, model.trainable_variables)

optimizer.apply_gradients(zip(gradients, model.trainable_variables))
train_loss(loss)
train_accuracy(labels, predictions)

# Implement algorithm test
@tf.function
def test_step(model, images, labels, loss_object, test_loss, test_accuracy):
predictions = model(images)

t_loss = loss_object(labels, predictions)
test_loss(t_loss)
test_accuracy(labels, predictions)

데이터셋 준비

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mnist = tf.keras.datasets.mnist

# 입력 영상이 총 8bit 즉, 0~255 사이의 값들로 이루어져 있으므로
(x_train, y_train), (x_test, y_test) = mnist.load_data()

# 0~1표현으로 바꿔준다.
x_train, x_test = x_train / 255.0, x_test / 255.0

# 입력 영상 하나의 사이즈는 28x28이므로 channel을 하나 더 늘려 주어야한다.
print(x_train.shape)
print(x_train[0].shape)

# x_train : (NUM_SAMPLE, 28, 28) -> (NUM_SAMPLE, 28, 28 , 1)
# ...은 해당 데이터 객체의 모든 axis를 표현하는 것이다.
# 위에서 255.0으로 나누어주게 되면 float64로 되므로 자료형을 float32로 해야 error가 없다.
## x_train[:,:,:, tf.newaxis]
x_train = x_train[..., tf.newaxis].astype(np.float32)
x_test = x_test[..., tf.newaxis].astype(np.float32)

# Numpy object나 Tensor로 부터 데이터셋을 구축할 수 있다.
train_ds = tf.data.Dataset.from_tensor_slices((x_train, y_train)).shuffle(10000).batch(32)
# test data는 suffle할 필요없다.
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

학습 환경 정의

모델 생성, 손실함수, 최적화 알고리즘, 평가지표 정의

1
2
3
4
5
6
7
8
9
10
11
12
13
# Create model
model = ConvNet()

# Define loss and optimizer
loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
optimizer = tf.keras.optimizers.Adam()

# Define performance metrics
train_loss = tf.keras.metrics.Mean(name='train_loss')
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')

학습 루프 동작

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
for epoch in range(EPOCHS):
for images, labels in train_ds:
train_step(model, images, labels, loss_object, optimizer, train_loss, train_accuracy)

for test_images, test_labels in test_ds:
test_step(model, test_images, test_labels, loss_object, test_loss, test_accuracy)

template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
print(template.format(epoch + 1,
train_loss.result(),
train_accuracy.result() * 100,
test_loss.result(),
test_accuracy.result() * 100))
# reset_state는 새로운 값들을 받기 위해 하는 건가?
train_loss.reset_states()
train_accuracy.reset_states()
test_loss.reset_states()
test_accuracy.reset_states()

result