본문 바로가기
딥러닝

패션 MNIST 데이터셋을 활용한 딥러닝 모델 성능 비교와 분석

by 짱태훈 2024. 11. 5.
728x90

이제는 딥러닝에 대한 게시글도 작성해 보려고 한다. 지금까지는 전통적인 알고리즘 기반의 학습으로, 분류, 회귀, 클러스터링을 하는 머신러닝을 주로 했었다. 이제는 머신러닝의 일종이지만, 신경망 기반의 학습으로 대규모 데이터와 복잡한 패턴 인식에 강점이 있는 기술인 딥러닝도 같이 게시물을 올려보려고 한다. 처음 머신러닝 게시물이 타이타닉 생존자 예측이었다. 딥러닝 역시 패션 MNIST 데이터셋을 이용해 딥러닝을 시작하려고 한다.


 

1. 문제에 대한 정보 수집

  1. 문제 정의
  2. 분석 대상에 대한 이해 - 데이터셋의 전반적인 구성과 속성을 파악

2. 기대 효과

  1. 딥러닝 모델의 성능 검증과 비교
  2. 딥러닝 학습 과정 이해

3. 요약

  1. 프로젝트 흐름도
  2. 결과

4. 분석 대상 확인

  1. 이미지 샘플 확인

5. 모델 훈련

  1. 직접 설계한 모델
  2. 사전 훈련 모델

6. 결론

 

1. 프로젝트 목표

1. 프로젝트 목표 및 정의

 본 게시물에서는 Kaggle에서 제공하는 패션 MNIST 데이터셋을 이용해 10개의 클레스를 구분하는 딥러닝 모델을 만들어 보려고 한다. 뿐만 아니라 패션 MNIST를 이용한 기존 딥러닝 모델 게시물들과 다르게 직접 만든 모델과 사전 훈련된 모델을 활용해 10개의 클레스 분류를 통해 성능을 확인해보려고 한다.

 패션 MNIST 데이터는 머신러닝을 처음 배울 때 학습용으로 사용하는 타이타닉 데이터셋과 비슷하게 딥러닝을 처음 시작할 때 사용하는 데이터셋으로 유명하다. 패션 MNIST는 의류 상품 이미지로 구성된 데이터셋으로, 훈련 세트에 60,000개의 예제와 테스트 세트에 10,000개의 예제가 포함되어 있다. 각 예제는 10개의 클래스 중 하나의 라벨이 있는 28x28 크기의 흑백 이미지로 구성되어 있다. MNIST는 연구자들이 가장 먼저 시도하는 데이터셋 중 하나로 "MNIST에서 동작하지 않는다면, 어디서도 동작하지 않을 것이다" 또는 "MNIST에서 동작하더라도, 다른 곳에서는 실패할 수 있다"라는 말이 있을 정도로 유명한 데이터셋이다. 

 

2. 분석 대상에 대한 이해 - 데이터셋의 전반적인 구성과 속성을 파악

  • 이미지 크기: 각 이미지는 28x28 픽셀로, 총 784개의 픽셀로 구성
  • 픽셀 값: 각 픽셀에는 밝기 또는 어두움을 나타내는 픽셀 값, 높은 숫자는 더 어두운 픽셀을 의미하며, 값은 0에서 255 사이의 정수
  • 데이터 형식: 훈련 및 테스트 데이터셋은 785개의 열(column)로 이루어져 있다.
    • 첫 번째 열은 클래스 라벨로, 옷의 종류를 나타낸다.
    • 나머지 784개 열은 각 픽셀의 값
  • 라벨
    • 0: T-shirt/top
    • 1: Trouser
    • 2: Pullover
    • 3: Dress
    • 4: Coat
    • 5: Sandal
    • 6: Shirt
    • 7: Sneaker
    • 8: Bag
    • 9: Ankle boot

 

2. 기대 효과

1. 딥러닝 모델의 성능 검증과 비교

기존의 딥러닝 모델과 사전 훈련된 모델(pre-trained model)을 비교함으로써, 사전 학습된 모델이 특정 데이터셋에서 어떻게 성능을 향상시킬 수 있는지를 확인할 수 있다. 이 과정에서 사전 훈련된 모델을 사용함으로써 특징 학습의 효율성과 성능 향상 효과를 체험할 수 있다.

 

2. 딥러닝 학습 과정 이해

패션 MNIST는 딥러닝 학습을 처음 접하는 사람들에게 적합한 데이터셋으로, 기본적인 이미지 분류 문제를 통해 딥러닝 모델의 구조와 학습 과정을 이해하는 데 도움이 된다. CNN(Convolutional Neural Network)과 같은 이미지 처리 모델을 다루며, 모델 설계와 튜닝 과정을 학습할 수 있다.

 

3. 요약

1. 프로젝트 흐름도

 

2. 결과

그림1
그림2

직접 만든 모델 두 개와 사전 훈련된 모델 4개를 이용해서 훈련을 진행했다. 훈련 성능에서 사전 훈련 모델인 xception 모델은 거의 99.93%에 도달하며 훈련 데이터에 매우 잘 맞추고 있다. 하지만 훈련 손실이 매우 낮아 훈련 데이터에 과적합될 가능성이 있다. 검증 성능에서 xception 모델이 0.9509로 가장 높은 검증 정확도를 보여주지만 검증 손실에서는 make_model2와 vgg16이 비교적 낮은 검증 손실을 보여준다.

make_model2는 그림3과 같이 테스트 데이터에서 100개의 이미지를 예측했을 때 8개의 이미지 예측에서 틀렸다. 반면 훈련 데이터에서 좋은 성능을 보여 줬지만 검증 데이터에서는 make_model2 보다 낮은 성능을 보여준 사전 훈련 모델인 xception은 그림4와 같이 100개의 테스트 이미지에서 72개를 다르게 예측했다. 

결론적으로 make_model2가 훈련과 검증 성능에서 균형이 잡혀 있어, 일반화 성능이 뛰어난 모델로 패션 MNIST 데이터셋에 더 좋은 모델이라고 볼 수 있다.

그림3
그림4

4. 분석 대상 확인

1. 이미지 샘플 확인

패션 MNIST 데이터셋은 Kaggle에서 제공받은 데이터이다. 

[ZALANDO RESEARCH, "Fashion MNIST", Kaggle, 2017년 수정, 2024년 11월 접속, https://www.kaggle.com/datasets/zalando-research/fashionmnist]

 

Fashion MNIST

An MNIST-like dataset of 70,000 28x28 labeled fashion images

www.kaggle.com

이미지는 총 60,000개의 이미지가 있으며, 10개의 분류로 구분된다.

10개의 옷은 'T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot'로 

훈련 데이터에서 앞의 사진 16개를 확인해 보면 그림5와 같이 확인할 수 있다. 

그림5

 

5. 모델 훈련

1. 직접 설계한 모델

1) make_model1

IMAGE_SIZE: 28, BATCH_SIZE: 32

모델 구성: 처음 층에서만 padding을 'same'으로 설정했으며, 이후에는 'valid'로 진행

훈련 시간: 총 35초

def create_model_make1():
    input_tensor = Input(shape=(28, 28, 1))
    x = Conv2D(filters=32, kernel_size=3, strides=1, padding='same', activation='relu')(input_tensor)
    x = Conv2D(filters=64, kernel_size=3, activation='relu')(x)
    x = MaxPooling2D(2)(x)
    x = Flatten()(x)
    x = Dropout(rate=0.5)(x)
    x = Dense(100, activation='relu')(x)
    output = Dense(10, activation='softmax')(x)
    
    model = Model(inputs=input_tensor, outputs=output)
    model.summary()
    return model

model1 = create_model_make1()


model1.compile(optimizer=Adam(0.001), loss='categorical_crossentropy', metrics=['accuracy'])
history1 = model1.fit(x=tr_images, y=tr_oh_labels, batch_size=32, epochs=30, shuffle=True, \
          validation_data=(val_images, val_oh_labels), callbacks=[rlr_cb, ely_cb])

그림6, model summary, train-valid accuracy

accuracy: 0.9907 - loss: 0.0276 - val_accuracy: 0.9363 - val_loss: 0.3183

데이터 증강 기법을 적용하지 않고 learning_rate는 0.001로 진행했다. ReduceLROnPlateau과 EarlyStopping을 각각 patience=3, patience=5 적용했기에 총 30 epoch 중 5번 째 epoch가 완료된 후 early 조기 종료(Early Stopping)가 적용되어 학습이 중단되었다. 훈련 결과와 그래프를 통해서 봤을 때 make_model1은 훈련 성능은 매우 높고, 학습이 진행됨에 따라 약간씩 증가하고 있다. 거의 0.99에 가까운 성능을 보여 과적합(overfitting)이 의심된다고 볼 수 있다. 또한, 검증 성능은 훈련 성능에 비해 낮고, 거의 개선되지 않는 모습을 보이고 있다. 이는 모델이 학습 데이터에는 잘 맞지만, 검증 데이터에는 일반화되지 않고 있음을 보여준다. 

테스트 세트를 이용해 평가(evaluate)를 진행했을 때 아래와 같은 점수가 나왔다. 또한, 그림7과 같이 100개의 테스트 세트에서 8개의 이미지를 예측하는데 실패했다.

accuracy: 0.9345 - loss: 0.3658

그림7

BATCH_SIZE 설정의 기준은 그림8과 같다. 

그림8

 

훈련 정확도(왼쪽 그래프)

  • 초기 에포크: 배치 크기가 작을수록(특히 배치 크기 32와 64) 정확도가 빠르게 상승하는 경향을 보인다. 이는 작은 배치 크기일 때 모델이 더 자주 가중치를 업데이트하므로, 초기 학습 속도가 빠른 것과 관련이 있다.
  • 후반 에포크: 배치 크기 32와 64는 높은 훈련 정확도를 유지하며, 거의 93% 이상의 성능에 도달했다. 반면, 배치 크기 256과 512는 최종 정확도가 낮거나 약간의 불안정성을 보이고 있다.

훈련 손실(오른쪽 그래프)

  • 초기 에포크: 모든 배치 크기에서 초기 몇 에포크 동안 손실이 빠르게 감소하고 있으며, 배치 크기 32와 64의 경우, 초기 손실이 빠르게 감소하며, 비교적 낮은 손실을 유지하는 경향을 보여준다.
  • 후반 에포크: 배치 크기 32와 64는 에포크가 진행됨에 따라 손실이 다시 증가하는 불안정한 패턴을 보며, 과적합의 가능성을 보여준다. 배치 크기 256과 512은 손실이 낮은 수준에서 유지되는 경향이 있지만, 배치 크기 256은 후반에 비교적 더 높아지고, 배치 크기 512는 전체적으로 가장 안정적인 손실 감소를 보여준다.

결론적으로 조기 중단을 이용했을 때 BATCH_SIZE가 작을 때 초기에 정확도가 높고 손실은 낮기 때문에 BATCH_SIZE를 32로 설정했다.

 

2) make_model2

IMAGE_SIZE: 28, BATCH_SIZE: 64

모델 구성: 모든 층에 'same' 패딩을 적용했고, 가중치 초기화로 'he_normal'을 적용했다.

훈련 시간: 총92초

def create_model_make2():

    input_tensor = Input(shape=(28, 28, 1))
    x = Conv2D(filters=32, kernel_size=(3, 3), padding='same', activation='relu', kernel_initializer='he_normal')(input_tensor)
    x = Conv2D(filters=32, kernel_size=(3, 3), padding='same', activation='relu', kernel_initializer='he_normal')(x)
    x = MaxPooling2D(pool_size=(2, 2))(x)
    
    x = Conv2D(filters=64, kernel_size=3, padding='same', activation='relu', kernel_initializer='he_normal')(x)
    x = Conv2D(filters=64, kernel_size=3, padding='same', kernel_initializer='he_normal')(x)
    x = Activation('relu')(x)
    x = MaxPooling2D(pool_size=2)(x)
    
    x = Conv2D(filters=128, kernel_size=3, padding='same', activation='relu', kernel_initializer='he_normal')(x)
    x = Conv2D(filters=128, kernel_size=3, padding='same', activation='relu', kernel_initializer='he_normal')(x)
    x = MaxPooling2D(pool_size=2)(x)
    
    x = Flatten(name='flatten')(x)
    x = Dropout(rate=0.5)(x)
    x = Dense(300, activation='relu', name='fc1')(x)
    x = Dropout(rate=0.3)(x)
    output = Dense(10, activation='softmax', name='output')(x)
    
    model = Model(inputs=input_tensor, outputs=output)
    
    model.summary()
    return model

model2 = create_model_2()

model2.compile(optimizer=Adam(0.001), loss='categorical_crossentropy', metrics=['accuracy'])
history2 = model2.fit(x=tr_images, y=tr_oh_labels, batch_size=64, epochs=50, shuffle=True, \
          validation_data=(val_images, val_oh_labels), callbacks=[rlr_cb, ely_cb])

그림9

accuracy: 0.9701 - loss: 0.0819 - val_accuracy: 0.9342 - val_loss: 0.2238

make_model1과 같이 데이터 증강 기법을 적용하지 않고 learning_rate는 0.001로 진행했다. ReduceLROnPlateau과 EarlyStopping을 각각 patience=3, patience=5 적용했기에 총 30 epoch 중 14번 째 epoch가 완료된 후 early 조기 종료(Early Stopping)가 적용되어 학습이 중단되었다. 훈련 결과와 그래프를 통해서 봤을 때 make_model2은 훈련 성능은 초반에 급격히 증가하다가 학습이 진행되면서 완만해지며 계속 상승하고 있다. 검증 성능 역시 초반에 급격히 증가하며, 어느 정도 높은 값을 유지하고 있다. 다만 학습이 진행될수록 train 성능에 비해 valid 성능의 증가가 둔화되는 모습을 보여준다. 하지만 간격이 점점 벌어지고 있기 때문에 모델이 학습 데이터와 검증 데이터 모두에서 잘 학습하고 있지만, 학습이 길어질수록 약간의 과적합이 발생할 수 있다.

테스트 세트를 이용해 평가(evaluate)를 진행했을 때 아래와 같은 점수가 나왔다. 또한, 그림10과 같이 100개의 테스트 세트에서 8개의 이미지를 예측하는데 실패했다.

accuracy: 0.9267 - loss: 0.2474

그림10

BATCH_SIZE 설정의 기준은 그림11과 같다. 

그림11

훈련 정확도(왼쪽 그래프)

  • 초기 에포크: 배치 크기가 작을수록(특히 배치 크기 32와 64) 정확도가 빠르게 상승하는 경향을 보인다. 이는 작은 배치 크기일 때 모델이 더 자주 가중치를 업데이트하므로, 초기 학습 속도가 빠른 것과 관련이 있다.
  • 후반 에포크: 배치 크기 64는 높은 훈련 정확도를 유지하며, 거의 93% 이상의 성능에 도달했다. 반면, 배치 크기 256과 512는 최종 정확도가 낮거나 약간의 불안정성을 보이고 있고 배치 크기 32는 최종 정확도가 낮음을 보여준다.

훈련 손실(오른쪽 그래프)

  • 초기 에포크: 모든 배치 크기에서 초기 몇 에포크 동안 손실이 빠르게 감소하고 있으며, 배치 크기 32와 64의 경우, 초기 손실이 빠르게 감소하며, 비교적 낮은 손실을 유지하는 경향을 보여준다.
  • 후반 에포크: 배치 크기 32, 64는 에포크가 진행됨에 따라 손실이 다시 증가하는 불안정한 패턴을 보며, 과적합의 가능성을 보여준다. 배치 크기 256과 512은 손실이 낮은 수준에서 유지되는 경향이 있지만, 배치 크기 256은 후반에 비교적 더 높아지고, 배치 크기 512는 전체적으로 가장 안정적인 손실 감소를 보여준다.

결론적으로 조기 중단을 이용했을 때 BATCH_SIZE가 작을 때 초기에 정확도가 높고 손실은 낮기 때문에 BATCH_SIZE를 64로 설정했다.

 

2. 사전 훈련 모델

공통 코드

사전 훈련 모델은 이미지 IMAGE_SIZE가 28이면 너무 작아서 끝까지 훈련을 하지 못하기 때문에 강제적으로 이미지의 크기를 늘려야 한다. 뿐만 아니라 패션 MNIST 데이터는 흑백 사진으로 채널이 1이지만, 사전 훈련 모델은 채널 역시 3으로 맞춰야 하기 때문에 사진의 크기와 채널을 3으로 맞추는 작업이 필요하다. 따라서 아래와 같이 코드를 추가해야 사전 훈련 모델 사용이 가능하다.

import tensorflow as tf
from keras.preprocessing.image import img_to_array, array_to_img

IMAGE_SIZE = 48
BATCH_SIZE = 8

def preprocess_mode():
    train_data = pd.read_csv('../input/fashionmnist/fashion-mnist_train.csv')
    test_data = pd.read_csv('../input/fashionmnist/fashion-mnist_test.csv')

    X_train = np.array(train_data.iloc[:,1:])
    y_train = np.array (train_data.iloc[:,0])

    X_test = np.array(test_data.iloc[:,1:])
    y_test = np.array(test_data.iloc[:,0])

    X_train = np.dstack([X_train] * 3)
    X_test = np.dstack([X_test]*3)

    X_train = X_train.reshape(-1, 28,28,3)
    X_test= X_test.reshape (-1,28,28,3)

    X_train = np.asarray([img_to_array(array_to_img(im, scale=False).resize((48,48))) for im in X_train])
    X_test = np.asarray([img_to_array(array_to_img(im, scale=False).resize((48,48))) for im in X_test])
    print(X_train.shape, X_test.shape)

    return X_train, y_train, X_test, y_test

뿐만 아니라 사전 훈련 모델은 각 모델마다 이미지의 크기를 정규화하는 방법이 따로 있기 때문에 함수를 이용해 복잡한 과정을 실수 없이 처리할 수 있게 만들었다.

from tensorflow.keras.applications.xception import preprocess_input as xcp_preprocess_input
from tensorflow.keras.applications.mobilenet import preprocess_input as mobile_preprocess_input
from tensorflow.keras.applications.vgg16 import preprocess_input as vgg16_preprocess_input
from tensorflow.keras.applications.resnet50 import preprocess_input as resnet50_preprocess_input

def get_preprocessed_data(images, labels, preprocessing_func=None):
    if preprocessing_func is None:
        images = np.array(images/255.0, dtype=np.float32)
    else:
        images = preprocessing_func(images)
        
    labels = np.array(labels, dtype=np.float32)
    return images, labels


def get_preprocessed_ohe(images, labels, preprocessing_func=None):
    images, labels = get_preprocessed_data(images, labels, preprocessing_func=None)
    oh_labels = to_categorical(labels)
    return images, oh_labels


def get_train_valid_test_set(train_images, train_labels, test_images, test_labels, valid_size=0.15, preprocessing_func=None, random_state=2021):
    train_images, train_oh_labels = get_preprocessed_ohe(train_images, train_labels, preprocessing_func)
    test_images, test_oh_labels = get_preprocessed_ohe(test_images, test_labels, preprocessing_func)
    
    tr_images, val_images, tr_oh_labels, val_oh_labels = train_test_split(train_images, train_oh_labels, test_size=valid_size, random_state=random_state)
    return (tr_images, tr_oh_labels), (val_images, val_oh_labels), (test_images, test_oh_labels )
model_list = ['vgg16', 'resnet50', 'xception', 'mobilenet']
preprocess_list = [vgg16_preprocess_input, resnet50_preprocess_input, xcp_preprocess_input, mobile_preprocess_input]
model_dict = {}
history_dict = {}


def total_model():
    X_train, y_train, X_test, y_test = preprocess_mode()
    
    for index, i in enumerate(model_list):
        print(f"\nTraining model: {i}")
        
        model = create_model(model_name=i, verbose=False)
        
        (tr_images, tr_oh_labels), (val_images, val_oh_labels), (test_images, test_oh_labels) = \
        get_train_valid_test_set(X_train, y_train, X_test, y_test, valid_size=0.15, preprocessing_func=preprocess_list[index], random_state=2021)
        
        model.compile(optimizer=Adam(0.001), loss='categorical_crossentropy', metrics=['accuracy'])

        print(tr_images.shape, tr_oh_labels.shape, val_images.shape, val_oh_labels.shape, test_images.shape, test_oh_labels.shape)
        
        history = model.fit(x=tr_images, y=tr_oh_labels, batch_size=32, epochs=50, shuffle=True, \
          validation_data=(val_images, val_oh_labels), callbacks=[rlr_cb, ely_cb])

        model_dict[i] = model
        history_dict[i] = history

        test_loss, test_accuracy = model.evaluate(test_images, test_oh_labels)
        print(f"Model: {i}, Test Accuracy: {test_accuracy:.4f}, Test Loss: {test_loss:.4f}")

total_model()

 

1) VGG16

IMAGE_SIZE: 48, BATCH_SIZE: 8

훈련 시간: 총 555초(9분 15초)

그림12, train-validation accuracy, test세트에서의 예측

accuracy: 0.9689 - loss: 0.0846 - val_accuracy: 0.9328 - val_loss: 0.2357

 

이전에 했던 모델들과 같이 데이터 증강 기법을 적용하지 않고 learning_rate는 0.001로 진행했다. ReduceLROnPlateau과 EarlyStopping을 각각 patience=3, patience=5 적용했기에 총 30 epoch 중 13번 째 epoch가 완료된 후 early 조기 종료(Early Stopping)가 적용되어 학습이 중단되었다. 훈련 결과와 그래프를 통해서 봤을 때 vgg16은 훈련 성능 에포크가 진행됨에 따라 꾸준히 증가하고 있으며, 거의 100%에 근접. 이는 모델이 훈련 데이터를 잘 학습하고 있다는 것을 의미한다. 검증 성능에서는 초기 에포크 동안 급격히 증가하다가, 중반 이후에는 상승세가 완만해지고 비교적 일정하게 유지되고 있다. 즉, 훈련 데이터에 대해 높은 정확도를 보이지만, 검증 데이터에서는 성능 향상이 제한되며, 과적합 가능성이 있다.

테스트 세트를 이용해 평가(evaluate)를 진행했을 때 아래와 같은 점수가 나왔다. 또한, 그림12과 같이 100개의 테스트 세트에서 19개의 이미지를 예측하는데 실패했다. 그림 13을 보면 테스트 세트에서 앞의 8개를 어떻게 예측했는지 확인할 수 있다.

BATCH_SIZE의 경우 메모리 부족으로 8로 진행했다.

Test Accuracy: 0.9373, Test Loss: 0.2176

그림13, 테스트 세트 100개 중 앞 8개

 

2) ResNet50

IMAGE_SIZE: 48, BATCH_SIZE: 8

훈련 시간: 총 332초(5분 32초)

그림14, train-validation accuracy, test세트에서의 예측

accuracy: 0.9208 - loss: 0.2193 - val_accuracy: 0.8972 - val_loss: 0.2985

이전에 했던 모델들과 같이 데이터 증강 기법을 적용하지 않고 learning_rate는 0.001로 진행했다. ReduceLROnPlateau과 EarlyStopping을 각각 patience=3, patience=5 적용했기에 총 30 epoch 중 5번 째 epoch가 완료된 후 early 조기 종료(Early Stopping)가 적용되어 학습이 중단되었다. 훈련 결과와 그래프를 통해서 봤을 때 resnet50은 모델이 학습 초기에는 훈련 데이터와 검증 데이터 모두에 대해 적절히 학습하고 있지만, 중반 이후부터는 검증 데이터에 대한 성능이 불안정해지고 있다. 하지만 검증 성능에서 중간에 일시적으로 감소했다가 다시 상승하는 변동이 있다. 이러한 변동은 일반적으로 데이터가 복잡하거나 학습이 불안정할 때 발생할 수 있다.

테스트 세트를 이용해 평가(evaluate)를 진행했을 때 아래와 같은 점수가 나왔다. 또한, 그림14과 같이 100개의 테스트 세트에서 90개의 이미지를 예측하는데 실패했다. 그림 15을 보면 테스트 세트에서 앞의 8개를 어떻게 예측했는지 확인할 수 있다.

역시 BATCH_SIZE의 경우 메모리 부족으로 8로 진행했다.

Test Accuracy: 0.8998, Test Loss: 0.2982

그림15

 

3) Xception

IMAGE_SIZE: 48, BATCH_SIZE: 8

훈련 시간: 총 752초(12분 32초)

그림16, train-validation accuracy, test세트에서의 예측

accuracy: 0.9993 - loss: 0.0022 - val_accuracy: 0.9509 - val_loss: 0.3138

이전에 했던 모델들과 같이 데이터 증강 기법을 적용하지 않고 learning_rate는 0.001로 진행했다. ReduceLROnPlateau과 EarlyStopping을 각각 patience=3, patience=5 적용했기에 총 30 epoch 중 13번 째 epoch가 완료된 후 early 조기 종료(Early Stopping)가 적용되어 학습이 중단되었다. 훈련 결과와 그래프를 통해서 봤을 때 xception은 훈련 정확도가 빠르게 상승하여 약 100%에 도했으며, 이는 모델이 훈련 데이터를 거의 완벽하게 맞추고 있다. 그러나 훈련 정확도가 100%에 가까워진다는 것은 모델이 훈련 데이터에 과도하게 적합되고 있을 가능성이 있다. 검증 정확도는 초기 몇 에포크 동안 빠르게 증가하다가, 약 4번째 에포크부터는 평평해지며 약간의 변동만 있을 뿐 큰 성능 향상이 없다. 모델이 검증 데이터에 대해 일반화하는 데 어려움을 겪고 있으며, 더 이상 학습이 이루어지지 않고 있다는 의미일 수 있다. 이러한 패턴은 과적합의 전형적인 모습이다.

테스트 세트를 이용해 평가(evaluate)를 진행했을 때 아래와 같은 점수가 나왔다. 또한, 그림16과 같이 100개의 테스트 세트에서 72개의 이미지를 예측하는데 실패했다. 그림 17을 보면 테스트 세트에서 앞의 8개를 어떻게 예측했는지 확인할 수 있다.

역시 BATCH_SIZE의 경우 메모리 부족으로 8로 진행했다.

Test Accuracy: 0.9457, Test Loss: 0.3386

그림17

 

4) MobileNetV2

IMAGE_SIZE: 48, BATCH_SIZE: 8

훈련 시간: 총 209초(3분 29초)

그림18, train-validation accuracy, test세트에서의 예측

accuracy: 0.9186 - loss: 0.2372 - val_accuracy: 0.8764 - val_loss: 0.5800

이전에 했던 모델들과 같이 데이터 증강 기법을 적용하지 않고 learning_rate는 0.001로 진행했다. ReduceLROnPlateau과 EarlyStopping을 각각 patience=3, patience=5 적용했기에 총 30 epoch 중 5번 째 epoch가 완료된 후 early 조기 종료(Early Stopping)가 적용되어 학습이 중단되었다. 훈련 결과와 그래프를 통해서 봤을 때 MobileNetV2은 훈련 정확도가 초기 에포크에서 빠르게 증가한 후 거의 90% 이상에 도달하여 평평해지는 모습을 보인다. 이는 모델이 훈련 데이터에 대해 빠르게 학습하고 있다는 것을 의미하지만, 추가적인 성능 향상이 거의 없다. 검증 정확도는 초반에는 빠르게 상승하지만, 중간부터 변동을 보이며 일관된 상승 없이 상승과 하락을 반복하고 있다. 이는 모델이 검증 데이터에 대해 일반화하는 데 어려움을 겪고 있다 즉, 모델이 훈련 데이터에 과적합되고 있으며, 검증 데이터에 대해서는 일관성 있는 성능을 보여주지 못하고 있다.

테스트 세트를 이용해 평가(evaluate)를 진행했을 때 아래와 같은 점수가 나왔다. 또한, 그림18과 같이 100개의 테스트 세트에서 69개의 이미지를 예측하는데 실패했다. 그림 19을 보면 테스트 세트에서 앞의 8개를 어떻게 예측했는지 확인할 수 있다.

역시 BATCH_SIZE의 경우 메모리 부족으로 8로 진행했다.

Test Accuracy: 0.8870, Test Loss: 0.5138

그림19

 

6. 결론

이번 프로젝트에서는 패션 MNIST 데이터셋을 활용해 직접 설계한 모델과 사전 훈련된 딥러닝 모델의 성능을 비교했다. 패션 MNIST는 10개의 의류 이미지를 구분하는 데이터셋으로, 이미지가 28x28 크기이자 흑백으로 구성되어 있다. 대다수의 이미지가 중앙에 위치하여 단순한 구조를 띄고 있어 딥러닝 모델이 상대적으로 높은 정확도를 보일 수 있었다. 그러나 사진 화질이 비교적 낮고, 단순히 흑백 데이터로 구성되어 있기 때문에 일부 세밀한 특징을 인식하는 데 제한이 있었다.

  • 직접 설계한 모델 (make_model1, make_model2)

직접 설계한 모델들 중 make_model2는 훈련과 검증 세트에서 안정적인 성능을 보이며, 테스트 데이터에서도 비교적 우수한 예측 성능을 보였다. 이는 모델이 학습과 검증 과정에서 과적합을 방지하며, 일반화 성능을 어느 정도 유지할 수 있었기 때문이다. 특히 데이터 증강 없이 학습을 진행했음에도 높은 성능을 보여, 패션 MNIST와 같은 단순 구조의 데이터에서는 적절히 설계된 소형 CNN 모델이 강력한 성능을 발휘할 수 있음을 확인할 수 있었다.

  • 사전 훈련된 모델 (VGG16, ResNet50, Xception, MobileNetV2)

사전 훈련된 모델들은 패션 MNIST 이미지의 특성을 고려하여 IMAGE_SIZE를 48x48로 확장하고, 채널을 1에서 3으로 조정해야만 사용할 수 있었다. 이러한 조정은 모델이 학습 과정에서 좋은 성능을 보이도록 해주었으나, 실제 테스트 세트에서는 예측 오류가 높아 훈련 및 검증과 테스트 간의 성능 차이가 발생하는 과적합 문제를 보였다. 이는 사전 훈련된 모델이 단순히 이미지 크기와 채널을 확장하는 것만으로는 패션 MNIST의 특성에 적절히 적응하지 못하고 있음을 시사한다.

  • 느낀 점 및 최종 결론

패션 MNIST는 중앙에 위치한 단순한 흑백 이미지로 구성되어 있어 CNN 모델이 상대적으로 높은 성능을 달성할 수 있는 데이터셋이다. 그러나 저화질의 흑백 이미지라는 점은 모델이 일부 세밀한 패턴을 인식하는 데 한계를 부여하며, 예측 성능에도 영향을 줄 수 있다. 특히, 사전 훈련된 모델은 이미지 크기와 채널 조정을 통해 훈련이 가능하였으나, 이는 테스트 세트에서 모델의 일반화 성능을 저하시키는 결과로 이어졌다.

결론적으로, 패션 MNIST와 같은 단순 이미지 데이터는 직접 설계한 소형 CNN 모델이 더 적합할 수 있으며, 사전 훈련된 대규모 모델을 적용할 때는 데이터의 특성에 맞게 추가적인 조정과 최적화가 필요하다.

패션 MNIST 데이터셋은 딥러닝 초보자들에게 CNN 학습의 기초를 다질 수 있는 좋은 실습 자료이며, 모델 선택 및 과적합 방지 전략의 중요성을 경험할 수 있었다.

728x90