알고리즘

CNN을 사용한 MNIST숫자분류기 구현

WDmil 2024. 6. 18. 01:23
728x90
import tensorflow as tf

# MNIST 데이터를 다운로드 합니다.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# 이미지들을 float32 데이터 타입으로 변경합니다.
x_train, x_test = x_train.astype('float32'), x_test.astype('float32')
# 28*28 형태의 이미지를 784차원으로 flattening 합니다.
x_train, x_test = x_train.reshape([-1, 784]), x_test.reshape([-1, 784])
# [0, 255] 사이의 값을 [0, 1]사이의 값으로 Normalize합니다.
x_train, x_test = x_train / 255., x_test / 255.
# 레이블 데이터에 one-hot encoding을 적용합니다.
y_train, y_test = tf.one_hot(y_train, depth=10), tf.one_hot(y_test, depth=10)

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

# tf.keras.Model을 이용해서 CNN 모델을 정의합니다.
class CNN(tf.keras.Model):
  def __init__(self):
    super(CNN, self).__init__()
    # 첫번째 Convolution Layer
    # 5x5 Kernel Size를 가진 32개의 Filter를 적용합니다.
    self.conv_layer_1 = tf.keras.layers.Conv2D(filters=32, kernel_size=5, strides=1, padding='same', activation='relu')
    self.pool_layer_1 = tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=2)

    # 두번째 Convolutional Layer
    # 5x5 Kernel Size를 가진 64개의 Filter를 적용합니다.
    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=(2, 2), strides=2)

    # Fully Connected Layer
    # 7x7 크기를 가진 64개의 activation map을 1024개의 특징들로 변환합니다.
    self.flatten_layer = tf.keras.layers.Flatten()
    self.fc_layer_1 = tf.keras.layers.Dense(1024, activation='relu')

    # Output Layer
    # 1024개의 특징들(feature)을 10개의 클래스-one-hot encoding으로 표현된 숫자 0~9-로 변환합니다.
    self.output_layer = tf.keras.layers.Dense(10, activation=None)

  def call(self, x):
    # MNIST 데이터를 3차원 형태로 reshape합니다. MNIST 데이터는 grayscale 이미지기 때문에 3번째차원(컬러채널)의 값은 1입니다.
    x_image = tf.reshape(x, [-1, 28, 28, 1])
    # 28x28x1 -> 28x28x32
    h_conv1 = self.conv_layer_1(x_image)
    # 28x28x32 -> 14x14x32
    h_pool1 = self.pool_layer_1(h_conv1)
    # 14x14x32 -> 14x14x64
    h_conv2 = self.conv_layer_2(h_pool1)
    # 14x14x64 -> 7x7x64
    h_pool2 = self.pool_layer_2(h_conv2)
    # 7x7x64(3136) -> 1024
    h_pool2_flat = self.flatten_layer(h_pool2)
    h_fc1 = self.fc_layer_1(h_pool2_flat)
    # 1024 -> 10
    logits = self.output_layer(h_fc1)
    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.nn.softmax_cross_entropy_with_logits(logits=logits, labels=y))

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

# 최적화를 위한 function을 정의합니다.
@tf.function
def train_step(model, x, y):
  with tf.GradientTape() as tape:
    y_pred, logits = model(x)
    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):
  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

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

# 10000 Step만큼 최적화를 수행합니다.
for i in range(10000):
  # 50개씩 MNIST 데이터를 불러옵니다.
  batch_x, batch_y = next(train_data_iter)
  # 100 Step마다 training 데이터셋에 대한 정확도를 출력합니다.
  if i % 100 == 0:
    train_accuracy = compute_accuracy(CNN_model(batch_x)[0], batch_y)
    print("반복(Epoch): %d, 트레이닝 데이터 정확도: %f" % (i, train_accuracy))
  # 옵티마이저를 실행해 파라미터를 한스텝 업데이트합니다.
  train_step(CNN_model, batch_x, batch_y)

# 학습이 끝나면 학습된 모델의 정확도를 출력합니다.
print("정확도(Accuracy): %f" % compute_accuracy(CNN_model(x_test)[0], y_test))

코드를 분할해서 이해해보자.


1. 데이터 준비 및 전처리

# MNIST 데이터를 다운로드 합니다.
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()
# 이미지들을 float32 데이터 타입으로 변경합니다.
x_train, x_test = x_train.astype('float32'), x_test.astype('float32')
# 28*28 형태의 이미지를 784차원으로 flattening 합니다.
x_train, x_test = x_train.reshape([-1, 784]), x_test.reshape([-1, 784])
# [0, 255] 사이의 값을 [0, 1]사이의 값으로 Normalize합니다.
x_train, x_test = x_train / 255., x_test / 255.
# 레이블 데이터에 one-hot encoding을 적용합니다.
y_train, y_test = tf.one_hot(y_train, depth=10), tf.one_hot(y_test, depth=10)

 

  • tf.keras.datasets.mnist.load_data()
    MNIST 데이터셋을 다운로드. 이는 28x28 크기의 손글씨 숫자 이미지와 해당 레이블로 구성된다.

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

  • reshape([-1, 784])
    이미지를 28x28에서 784 차원의 벡터로 평탄화한다.

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

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

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

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

 

  • tf.data.Dataset.from_tensor_slices((x_train, y_train))
    훈련 데이터를 텐서로 변환.

  • repeat()
    데이터셋을 무한 반복 한다.

  • shuffle(60000)
    60000개의 데이터를 무작위로 섞는다.

  • batch(50)
    데이터를 50개의 배치로 나눈다.

  • 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=32, kernel_size=5, strides=1, padding='same', activation='relu')
    self.pool_layer_1 = tf.keras.layers.MaxPool2D(pool_size=(2, 2), 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=(2, 2), strides=2)
    self.flatten_layer = tf.keras.layers.Flatten()
    self.fc_layer_1 = tf.keras.layers.Dense(1024, activation='relu')
    self.output_layer = tf.keras.layers.Dense(10, activation=None)
  
  def call(self, x):
    x_image = tf.reshape(x, [-1, 28, 28, 1])
    h_conv1 = self.conv_layer_1(x_image)
    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_pool2_flat = self.flatten_layer(h_pool2)
    h_fc1 = self.fc_layer_1(h_pool2_flat)
    logits = self.output_layer(h_fc1)
    y_pred = tf.nn.softmax(logits)
    return y_pred, logits

 

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

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.Adam(1e-4)

 

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

5. 학습 단계 정의

@tf.function
def train_step(model, x, y):
  with tf.GradientTape() as tape:
    y_pred, logits = model(x)
    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) : 한 단계 학습을 수행하는 함수.
    • model(x) :
      모델에 입력값 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. 모델 학습 및 평가

CNN_model = CNN()

for i in range(10000):
  batch_x, batch_y = next(train_data_iter)
  if i % 100 == 0:
    train_accuracy = compute_accuracy(CNN_model(batch_x)[0], batch_y)
    print("반복(Epoch): %d, 트레이닝 데이터 정확도: %f" % (i, train_accuracy))
  train_step(CNN_model, batch_x, batch_y)

print("정확도(Accuracy): %f" % compute_accuracy(CNN_model(x_test)[0], y_test))

 

 

  • CNN_model = CNN(): CNN 모델을 생성.
  • for i in range(10000): 10000번의 학습 스텝을 수행한다.
    • batch_x, batch_y = next(train_data_iter): 배치 데이터를 가져온다.
    • if i % 100 == 0: 매 100 스텝마다 정확도를 출력.
      • train_accuracy = compute_accuracy(CNN_model(batch_x)[0], batch_y):
        학습 배치에 대한 정확도를 계산한다.
      • print("반복(Epoch): %d, 트레이닝 데이터 정확도: %f" % (i, train_accuracy)): 정확도를 출력.
    • train_step(CNN_model, batch_x, batch_y): 모델을 한 스텝 학습.
  • print("정확도(Accuracy): %f" % compute_accuracy(CNN_model(x_test)[0], y_test)):
    테스트 데이터에 대한 최종 정확도를 출력한다.

 

728x90