알고리즘

TensorFlow 2.0을 이용한 CIFAR-10 이미지 분류 CNN구현

WDmil 2024. 6. 18. 03:01
728x90

CIFAR-10 데이터셋은 10개의 클래스, 50,000개의 traing images, 10,000 개의 testing images로 구성되어있다.

 

이미지크기 : 32x32x3 pixels(Color Image)

 

레이블 : airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck.

 

import tensorflow as tf

# CIFAR-10 데이터를 다운로드하고 데이터를 불러옵니다.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
# 이미지들을 float32 데이터 타입으로 변경합니다.
x_train, x_test = x_train.astype('float32'), x_test.astype('float32')
# [0, 255] 사이의 값을 [0, 1]사이의 값으로 Normalize합니다.
x_train, x_test = x_train / 255., x_test / 255.
# scalar 형태의 레이블(0~9)을 One-hot Encoding 형태로 변환합니다.
y_train_one_hot = tf.squeeze(tf.one_hot(y_train, 10), axis=1)
y_test_one_hot = tf.squeeze(tf.one_hot(y_test, 10), axis=1)

# tf.data API를 이용해서 데이터를 섞고 batch 형태로 가져옵니다.
train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train_one_hot))
train_data = train_data.repeat().shuffle(50000).batch(128)
train_data_iter = iter(train_data)

test_data = tf.data.Dataset.from_tensor_slices((x_test, y_test_one_hot))
test_data = test_data.batch(1000)
test_data_iter = iter(test_data)

# tf.keras.Model을 이용해서 CNN 모델을 정의합니다.
class CNN(tf.keras.Model):
  def __init__(self):
    super(CNN, self).__init__()
    # 첫번째 convolutional layer - 하나의 RGB 이미지를 64개의 특징들(feature)으로 맵핑(mapping)합니다.
    self.conv_layer_1 = tf.keras.layers.Conv2D(filters=64, kernel_size=5, strides=1, padding='same', activation='relu')
    self.pool_layer_1 = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)
    # 두번째 convolutional layer - 64개의 특징들(feature)을 64개의 특징들(feature)로 맵핑(mapping)합니다.
    self.conv_layer_2 = tf.keras.layers.Conv2D(filters=64, kernel_size=5, strides=1, padding='same', activation='relu')
    self.pool_layer_2 = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)
    # 세번째 convolutional layer
    self.conv_layer_3 = tf.keras.layers.Conv2D(filters=128, kernel_size=3, strides=1, padding='same', activation='relu')
    # 네번째 convolutional layer
    self.conv_layer_4 = tf.keras.layers.Conv2D(filters=128, kernel_size=3, strides=1, padding='same', activation='relu')
    # 다섯번째 convolutional layer
    self.conv_layer_5 = tf.keras.layers.Conv2D(filters=128, kernel_size=3, strides=1, padding='same', activation='relu')
    # Fully Connected Layer 1 - 2번의 downsampling 이후에, 우리의 32x32 이미지는 8x8x128 특징맵(feature map)이 됩니다.
    # 이를 384개의 특징들로 맵핑(mapping)합니다.
    self.flatten_layer = tf.keras.layers.Flatten()
    self.fc_layer_1 = tf.keras.layers.Dense(384, activation='relu')
    self.dropout = tf.keras.layers.Dropout(0.2)
    # Fully Connected Layer 2 - 384개의 특징들(feature)을 10개의 클래스-airplane, automobile, bird...-로 맵핑(mapsping)합니다.
    self.output_layer = tf.keras.layers.Dense(10, activation=None)

  def call(self, x, training=False):
    # 입력 이미지
    h_conv1 = self.conv_layer_1(x)
    h_pool1 = self.pool_layer_1(h_conv1)
    h_conv2 = self.conv_layer_2(h_pool1)
    h_pool2 = self.pool_layer_2(h_conv2)
    h_conv3 = self.conv_layer_3(h_pool2)
    h_conv4 = self.conv_layer_4(h_conv3)
    h_conv5 = self.conv_layer_5(h_conv4)
    h_conv5_flat = self.flatten_layer(h_conv5)
    h_fc1 = self.fc_layer_1(h_conv5_flat)
    # Dropout - 모델의 복잡도를 컨트롤합니다. 특징들의 co-adaptation을 방지합니다.
    h_fc1_drop = self.dropout(h_fc1, training=training)
    logits = self.output_layer(h_fc1_drop)
    y_pred = tf.nn.softmax(logits)

    return y_pred, logits

# cross-entropy 손실 함수를 정의합니다.
@tf.function
def cross_entropy_loss(logits, y):
  return tf.reduce_mean(tf.keras.losses.categorical_crossentropy(y, logits, from_logits=True))

# 최적화를 위한 RMSprop 옵티마이저를 정의합니다.
optimizer = tf.optimizers.RMSprop(1e-3)

# 최적화를 위한 function을 정의합니다.
@tf.function
def train_step(model, x, y, training=True):
  with tf.GradientTape() as tape:
    y_pred, logits = model(x, training=training)
    loss = cross_entropy_loss(logits, y)
  gradients = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))

# 모델의 정확도를 출력하는 함수를 정의합니다.
@tf.function
def compute_accuracy(y_pred, y):
  accuracy = tf.reduce_mean(tf.keras.metrics.categorical_accuracy(y, y_pred))
  return accuracy

# Convolutional Neural Networks(CNN) 모델을 선언합니다.
CNN_model = CNN()

# 10000 Step만큼 최적화를 수행합니다.
for i in range(10000):
  batch_x, batch_y = next(train_data_iter)

  # 100 Step마다 training 데이터셋에 대한 정확도와 loss를 출력합니다.
  if i % 100 == 0:
    y_pred, logits = CNN_model(batch_x, training=False)
    train_accuracy = compute_accuracy(y_pred, batch_y)
    loss_print = cross_entropy_loss(logits, batch_y)

    print("반복(Epoch): %d, 트레이닝 데이터 정확도: %f, 손실 함수(loss): %f" % (i, train_accuracy, loss_print))
  # 20% 확률의 Dropout을 이용해서 학습을 진행합니다.
  train_step(CNN_model, batch_x, batch_y)

# 학습이 끝나면 테스트 데이터(10000개)에 대한 정확도를 출력합니다.
test_accuracy = 0.0
for i in range(10):
  test_batch_x, test_batch_y = next(test_data_iter)
  y_pred, _ = CNN_model(test_batch_x, training=False)
  test_accuracy += compute_accuracy(y_pred, test_batch_y)

test_accuracy /= 10
print("테스트 데이터 정확도: %f" % test_accuracy)

 

 

유의! 학습과정이 엄청 오래걸린다!


 

코드를 분리해서 해석해보자.


1. 데이터 준비 및 전처리

# CIFAR-10 데이터를 다운로드하고 데이터를 불러옵니다.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.cifar10.load_data()
# 이미지들을 float32 데이터 타입으로 변경합니다.
x_train, x_test = x_train.astype('float32'), x_test.astype('float32')
# [0, 255] 사이의 값을 [0, 1]사이의 값으로 Normalize합니다.
x_train, x_test = x_train / 255., x_test / 255.
# scalar 형태의 레이블(0~9)을 One-hot Encoding 형태로 변환합니다.
y_train_one_hot = tf.squeeze(tf.one_hot(y_train, 10), axis=1)
y_test_one_hot = tf.squeeze(tf.one_hot(y_test, 10), axis=1)

 

  • tf.keras.datasets.cifar10.load_data(): CIFAR-10 데이터셋을 다운로드.
    이는 32x32 크기의 3채널(RGB) 이미지와 해당 레이블로 구성된다.

  • astype('float32'): 이미지를 32비트 부동 소수점 형식으로 변환한다.

  • / 255.: 이미지 픽셀 값을 [0, 255] 범위에서 [0, 1] 범위로 정규화.

  • tf.squeeze(tf.one_hot(y_train, 10), axis=1): 레이블을 원-핫 인코딩.
    이는 각 레이블을 10차원 벡터로 변환하여 클래스별로 이진 값을 가지게 된다.

2. 데이터셋을 섞고 배치로 묶기

# tf.data API를 이용해서 데이터를 섞고 batch 형태로 가져옵니다.
train_data = tf.data.Dataset.from_tensor_slices((x_train, y_train_one_hot))
train_data = train_data.repeat().shuffle(50000).batch(128)
train_data_iter = iter(train_data)

test_data = tf.data.Dataset.from_tensor_slices((x_test, y_test_one_hot))
test_data = test_data.batch(1000)
test_data_iter = iter(test_data)

 

 

  • tf.data.Dataset.from_tensor_slices((x_train, y_train_one_hot)): 훈련 데이터를 텐서로 변환.
  • repeat(): 데이터셋을 무한 반복한다.
  • shuffle(50000): 50000개의 데이터를 무작위로 섞는다.
  • batch(128): 데이터를 128개의 배치로 나눈다.
  • iter(train_data): 데이터셋을 반복할 수 있는 이터레이터로 변환한다.

3. CNN모델 정의

class CNN(tf.keras.Model):
  def __init__(self):
    super(CNN, self).__init__()
    self.conv_layer_1 = tf.keras.layers.Conv2D(filters=64, kernel_size=5, strides=1, padding='same', activation='relu')
    self.pool_layer_1 = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)
    self.conv_layer_2 = tf.keras.layers.Conv2D(filters=64, kernel_size=5, strides=1, padding='same', activation='relu')
    self.pool_layer_2 = tf.keras.layers.MaxPool2D(pool_size=(3, 3), strides=2)
    self.conv_layer_3 = tf.keras.layers.Conv2D(filters=128, kernel_size=3, strides=1, padding='same', activation='relu')
    self.conv_layer_4 = tf.keras.layers.Conv2D(filters=128, kernel_size=3, strides=1, padding='same', activation='relu')
    self.conv_layer_5 = tf.keras.layers.Conv2D(filters=128, kernel_size=3, strides=1, padding='same', activation='relu')
    self.flatten_layer = tf.keras.layers.Flatten()
    self.fc_layer_1 = tf.keras.layers.Dense(384, activation='relu')
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.output_layer = tf.keras.layers.Dense(10, activation=None)

  def call(self, x, is_training):
    h_conv1 = self.conv_layer_1(x)
    h_pool1 = self.pool_layer_1(h_conv1)
    h_conv2 = self.conv_layer_2(h_pool1)
    h_pool2 = self.pool_layer_2(h_conv2)
    h_conv3 = self.conv_layer_3(h_pool2)
    h_conv4 = self.conv_layer_4(h_conv3)
    h_conv5 = self.conv_layer_5(h_conv4)
    h_conv5_flat = self.flatten_layer(h_conv5)
    h_fc1 = self.fc_layer_1(h_conv5_flat)
    h_fc1_drop = self.dropout(h_fc1, training=is_training)
    logits = self.output_layer(h_fc1_drop)
    y_pred = tf.nn.softmax(logits)

    return y_pred, logits

 

 

  • Conv2D: 2D 합성곱 층. 필터 수, 커널 크기, 스트라이드, 패딩 방식, 활성화 함수를 지정한다.
    • filters=64: 첫 번째 합성곱 층은 64개의 필터를 사용.
    • kernel_size=5: 각 필터의 크기는 5x5
    • strides=1: 필터가 한 번에 이동하는 픽셀 수는 1
    • padding='same': 출력 크기를 입력 크기와 동일하게 유지
    • activation='relu': 활성화 함수로 ReLU를 사용
  • MaxPool2D: 최대 풀링 층. 풀링 크기와 스트라이드를 지정
    • pool_size=(3, 3): 풀링 윈도우 크기는 3x3.
    • strides=2: 풀링 윈도우가 한 번에 이동하는 픽셀 수는 2.
  • Flatten(): 입력을 1차원으로 변환
  • Dense: 완전 연결 층.
    • units=384: 첫 번째 완전 연결 층은 384개의 뉴런을 가진다.
    • activation='relu': 활성화 함수로 ReLU를 사용.
    • units=10: 출력 층은 10개의 뉴런을 가지며, 클래스 수와 동일하다.
  • Dropout(0.2): 20%의 드롭아웃 확률을 사용하여 오버피팅을 방지한다.

4. 손실 함수와 옵티마이저 정의

@tf.function
def cross_entropy_loss(logits, y):
  return tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))

optimizer = tf.optimizers.RMSprop(1e-3)

 

  • cross_entropy_loss(logits, y): 크로스 엔트로피 손실 함수를 정의.
    이는 모델의 출력값과 실제 레이블 사이의 차이를 계산하는것 과 같다.
    • logits: 모델의 출력값.
    • y: 실제 레이블.
  • tf.optimizers.RMSprop(1e-3): RMSprop 옵티마이저를 정의한다. 학습률은 0.001로 설정.

5. 학습 단계 정의

@tf.function
def train_step(model, x, y, is_training):
  with tf.GradientTape() as tape:
    y_pred, logits = model(x, is_training)
    loss = cross_entropy_loss(logits, y)
  gradients = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))

 

 

  • train_step(model, x, y, is_training): 한 단계 학습을 수행하는 함수.
    • model(x, is_training): 모델에 입력값 x를 넣어 예측값 y_pred와 로짓 logits를 얻는다.
    • cross_entropy_loss(logits, y): 손실을 계산.
    • tape.gradient(loss, model.trainable_variables): 손실에 대한 그라디언트를 계산.
    • optimizer.apply_gradients(zip(gradients, model.trainable_variables)): 그라디언트를 적용하여 모델의 파라미터를 업데이트.

6. 정확도 계산 함수

@tf.function
def compute_accuracy(y_pred, y):
  correct_prediction = tf.equal(tf.argmax(y_pred,1), tf.argmax(y,1))
  accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

  return accuracy
  • compute_accuracy(y_pred, y): 모델의 예측 정확도를 계산하는 함수.
    • tf.argmax(y_pred, 1): 예측값에서 가장 높은 확률을 가진 클래스의 인덱스를 찾는다.
    • tf.equal(...): 예측값과 실제값이 같은지 비교한다.
    • tf.reduce_mean(...): 정확도의 평균을 계산.

7. 모델 학습 및 평가

# Convolutional Neural Networks(CNN) 모델을 선언합니다.
CNN_model = CNN()

# 10000 Step만큼 최적화를 수행합니다.
for i in range(10000):
  batch_x, batch_y = next(train_data_iter)

  # 100 Step마다 training 데이터셋에 대한 정확도와 loss를 출력합니다.
  if i % 100 == 0:
    train_accuracy = compute_accuracy(CNN_model(batch_x, False)[0], batch_y)
    loss_print = cross_entropy_loss(CNN_model(batch_x, False)[1], batch_y)

    print("반복(Epoch): %d, 트레이닝 데이터 정확도: %f, 손실 함수(loss): %f" % (i, train_accuracy, loss_print))
  # 20% 확률의 Dropout을 이용해서 학습을 진행합니다.
  train_step(CNN_model, batch_x, batch_y, True)

# 학습이 끝나면 테스트 데이터(10000개)에 대한 정확도를 출력합니다.
test_accuracy = 0.0
for i in range(10):
  test_batch_x, test_batch_y = next(test_data_iter)
  test_accuracy = test_accuracy + compute_accuracy(CNN_model(test_batch_x, False)[0], test_batch_y).numpy()
test_accuracy = test_accuracy / 10
print("테스트 데이터 정확도: %f" % test_accuracy)

 

  • CNN_model = CNN(): CNN 모델을 선언.
  • for i in range(10000): 10000번 반복하여 학습을 수행.
    • batch_x, batch_y = next(train_data_iter): 학습 데이터를 배치 단위로 가져온다.
    • if i % 100 == 0: 100번 반복마다 훈련 데이터의 정확도와 손실을 출력.
      • compute_accuracy(...): 정확도를 계산.
      • cross_entropy_loss(...): 손실을 계산.
    • train_step(...): 학습을 진행.
  • test_accuracy = 0.0: 테스트 데이터 정확도를 초기화.
  • for i in range(10): 테스트 데이터 10개 배치에 대해 정확도를 계산.
    • test_accuracy = test_accuracy + compute_accuracy(...): 각 배치의 정확도를 누적.
  • test_accuracy = test_accuracy / 10: 평균 정확도를 계산.
  • print("테스트 데이터 정확도: %f" % test_accuracy): 최종 테스트 데이터 정확도를 출력한다.

 

728x90