article thumbnail image
Published 2022. 3. 3. 16:46

목차

  1. CIFAR10 Dataset 생성
  2. CIFAR10 데이터 시각화
  3. Data preprocessing
  4. Custom Model 생성
  5. Model 학습 수행 및 테스트 데이터로 평가
  6. model.predict()를 통해 이미지 분류 예측

1. CIFAR10 Dataset 생성

tf.keras.datasets의 cifar10.load_data()는 웹에서 Local computer로 Download후 train과 test용 image와 label array로 로딩.

import numpy as np # linear algebra
import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)
import os
from tensorflow.keras.datasets import cifar10

# 전체 6만개 데이터 중, 5만개는 학습 데이터용, 1만개는 테스트 데이터용으로 분리
(train_images, train_labels), (test_images, test_labels) = cifar10.load_data()
print("train dataset shape:", train_images.shape, train_labels.shape)
print("test dataset shape:", test_images.shape, test_labels.shape)

4차원 데이터인 것을 확인할 수 있다.

 

NAMES = np.array(['airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'])
print(train_labels[:10])

 CIFAR의 클래스 이름은 0~9까지 있다.

 


2. CIFAR10 데이터 시각화

  • 이미지 크기는 32x32이며 RGB채널.
  • 전반적으로 Label에 해당하는 대상이 이미지의 중앙에 있고, Label 대상 오브젝트 위주로 이미지가 구성.
import matplotlib.pyplot as plt
import cv2
%matplotlib inline 

def show_images(images, labels, ncols=8):
    figure, axs = plt.subplots(figsize=(22, 6), nrows=1, ncols=ncols)
    for i in range(ncols):
        axs[i].imshow(images[i])
        label = labels[i].squeeze()
        axs[i].set_title(NAMES[int(label)])
        
show_images(train_images[:8], train_labels[:8], ncols=8)
show_images(train_images[8:16], train_labels[8:16], ncols=8)

subplots(nrows=1, ncols=8) 이면, 한줄로 8개의 이미지를 반환한다.

nrows=2 로 하면, 두줄로 총 16개의 이미지가 반환된다.

figure는 한줄에 8개 이미지가 있는 전체를 지칭한다.

axs는 개별적인 1개의 이미지를 지칭한다.

 

그리고 나서 ncols만큼 루프를 돌려서

axs의 i번째 입력으로 들어온 이미지를 표현한다.

squeeze()는 2차원을 1차원으로 뭉그러뜨리는 기능을 한다.

 


3. Data preprocessing

  • image array의 0 ~ 255 사이의 값으로 되어 있는 pixel intensity 값을 0 ~ 1 사이 값으로 변환. 정수값 pixel 값을 255.0 으로 나눔.
  • label array는 숫자형 값으로 바꾸되, 원-핫 인코딩을 적용할지 선택. 일반적으로 원-핫 인코딩을 적용하는게 Keras Framework활용이 용이
  • image array, label array 모두 float32 형으로 변환. numpy 의 float32는 tensor 변환시 tf.float32 로 변환되며 기본적으로 Tensorflow backend Keras는 tf.float32를 기반으로 함.
(train_images, train_labels), (test_images, test_labels) = cifar10.load_data()

# label은 원-핫 인코딩이 Keras에서는 활용이 용이하나, 여기서는 sparse categorical crossentropy 테스트를 위해 적용하지 않음. 
def get_preprocessed_data(images, labels):
    
    # 학습과 테스트 이미지 array를 0~1 사이값으로 scale 및 float32 형 변형. 
    images = np.array(images/255.0, dtype=np.float32)
    labels = np.array(labels, dtype=np.float32)
    
    return images, labels

train_images, train_labels = get_preprocessed_data(train_images, train_labels)
test_images, test_labels = get_preprocessed_data(test_images, test_labels)

픽셀값을 0~1사이 값으로 바꾸기 위해서 255.0으로 나누어 준다. 그러면 0~1 사이 값으로 스케일링 된다.

그 다음 float32로 바꿔준다. 그러면 나중에 텐서로 올라갈 때 tf.float32로 바뀐다.

 

print(train_images.shape, train_labels.shape)

 

Keras는 CNN(정확히는 CNN 2D) 모델에 학습 데이터를 입력할 시 Image array는 반드시 4차원 배열이 되어야 한다. 
RGB 채널 이미지 array는 기본적으로 3차원이다. 여기에 이미지의 갯수를 포함하므로 4차원이 된다.  
만일 Grayscale인 2차원 이미지 array라도 의도적으로 채널을 명시해서 3차원으로 만들어 주고, 여기에 이미지 개수를 포함해서 4차원이 된다. 

Conv2D는 묵시적으로 4차원 데이터를 받는다.

 

# 2차원인 labels 데이터를 1차원으로 변경. 
train_labels = train_labels.squeeze()
test_labels = test_labels.squeeze()

label 데이터는 2차원이다.

이를 Keras 모델에 입력해도 별 문제없이 동작하지만, label의 경우는 OHE적용이 안되었는지를 알 수 있게 명확하게 1차원으로 표현해 주는것이 좋다. 


전통적인 CNN Architecture

4. Custom Model 생성

  • CNN Model의 맨처음 Layer는 Input layer. Input layer의 shape를 이미지 사이즈와 RGB 3채널에 맞게 (32, 32, 3) 으로 설정.
  • Conv 연산을 연달아 적용하고 MaxPooling을 적용하는 루틴으로 모델 생성. MaxPooling 적용 후에는 필터 갯수를 더욱 증가 시킴.
  • MaxPooling 적용 후에 출력 피처맵의 사이즈는 작아지되, 채널(깊이)는 늘어나는 형태로 모델 생성.
  • CIFAR10의 Label수가 10개이므로 Classification을 위한 맨 마지막 Dense layer의 units 갯수는 10개임
  • label 값이 원-핫 인코딩 되지 않았기 때문에 model.compile()에서 loss는 반드시 sparse_categorical_crossentropy 여야 함.
  • 만일 label값이 원-핫 인코딩 되었다면 loss는 categorical_crossentropy 임.

 

IMAGE_SIZE = 32

image size는 32x32 이다.

 

from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Input, Dense , Conv2D , Dropout , Flatten , Activation, MaxPooling2D , GlobalAveragePooling2D
from tensorflow.keras.optimizers import Adam , RMSprop 
from tensorflow.keras.layers import BatchNormalization
from tensorflow.keras.callbacks import ReduceLROnPlateau , EarlyStopping , ModelCheckpoint , LearningRateScheduler

input_tensor = Input(shape=(IMAGE_SIZE, IMAGE_SIZE, 3)) # RGB 3채널

# 이미지가 작기 때문에 커널을 3x3 정도로만 해준다.
x = Conv2D(filters=32, kernel_size=(5, 5), padding='valid', activation='relu')(input_tensor)
# x = Conv2D(filters=32, kernel_size=(3, 3), padding='same', activation='relu')(input_tensor)
x = Conv2D(filters=32, kernel_size=(3, 3), padding='same', activation='relu')(x)
x = MaxPooling2D(pool_size=(2, 2))(x) # 2나 (2,2)나 똑같음

x = Conv2D(filters=64, kernel_size=(3, 3), padding='same', activation='relu')(x)
x = Conv2D(filters=64, kernel_size=(3, 3), padding='same')(x) # 이렇게만 하면 activation 적용이 안된다.
x = Activation('relu')(x) # 이렇게 하면 activation 적용한다.
x = MaxPooling2D(pool_size=2)(x)

x = Conv2D(filters=128, kernel_size=(3,3), padding='same', activation='relu')(x)
x = Conv2D(filters=128, kernel_size=(3,3), padding='same', activation='relu')(x)
x = MaxPooling2D(pool_size=2)(x)

# cifar10의 클래스가 10개 이므로 마지막 classification의 Dense layer units갯수는 10
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()

 

 

(5,5,3) Filter를 32개 연산 = 5x5x3x32 + 32

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

# optimizer는 Adam으로 설정하고, label값이 원-핫 인코딩이 아니므로 loss는 sparse_categorical_crossentropy 임. 
model.compile(optimizer=Adam(), loss='sparse_categorical_crossentropy', metrics=['accuracy'])

label이 원핫인코딩을 거치지 않았으므로 loss function은 sparse_categorical_crossentropy로 해준다.


5. Model 학습 수행 및 테스트 데이터로 평가

  • Model의 fit() 메소드를 호출하여 학습
  • fit()은 학습 데이터가 Numpy array 자체로 들어올때, Generator 형태로 들어올때 약간의 수행로직 차이가 있음.
  • 인자로 x에는 학습 image data, y는 학습 label 데이터.
  • batch_size는 한번에 가져올 image/label array 갯수. 1개씩 가져오면 수행속도가 너무 느리고, 전체를 가져오면 GPU Memory 부족이 발생할 수 있으므로 적절한 batch_size 설정이 필요. 만약 학습 데이터가 generator일 경우, fit()에서 batch_size를 설정하지 않음.
  • epochs 는 전체 학습 데이터 학습을 반복 수행할 횟수
  • steps_per_epoch는 전체 학습 데이터를 몇번 배치 작업으로 수행하는가를 의미. 보통 입력데이터가 generator일 경우 설정.
  • validation_data는 검증용 데이터 세트
  • validation_steps는 검증용 데이터의 steps_per_epoch임.
  • validation_split는 validation_data로 별도의 검증용 데이터 세트를 설정하지 않고 자동으로 학습용 데이터에서 검증용 데이터 세트 분할.

 

 

history = model.fit(x=train_images, y=train_labels, batch_size=64, epochs=30, validation_split=0.15 )

오버피팅이 조금 되고 있는게 보인다.

 

 

import matplotlib.pyplot as plt
%matplotlib inline

def show_history(history):
    plt.figure(figsize=(6, 6))
    plt.yticks(np.arange(0, 1, 0.05))
    plt.plot(history.history['accuracy'], label='train')
    plt.plot(history.history['val_accuracy'], label='valid')
    plt.legend()
    
show_history(history)

# 테스트 데이터로 성능 평가
model.evaluate(test_images, test_labels)


6. model.predict()를 통해 이미지 분류 예측

  • 4차원 이미지 배열을 입력해서 모델을 학습함. predict()시에도 4차원 이미지 배열을 입력해야함.
  • 학습 데이터의 원-핫 인코딩 적용 여부와 관계없이 softmax 적용 결과는 무조건 2차원 임에 유의

 

preds = model.predict(test_images[0])

위 코드는 오류가 발생한다.

Conv2D를 사용한 모델에 4차원 이미지 배열을 입력해서 모델을 학습했으므로,

predict()시에도 테스트용 4차원 이미지 배열을 입력해야 한다.

 

# 테스트용 4차원 이미지 배열을 입력해서 predict()수행. 
preds = model.predict(np.expand_dims(test_images[0], axis=0))
# preds = model.predict(test_images[:32], batch_size=32) # 32개 예측
print('예측 결과 shape:', preds.shape)
print('예측 결과:', preds)

 

predict()의 결과는 softmax 적용 결과이다.

레이블을 원-핫 인코딩을 처리하지 않고 1차원으로 넣었기 때문에 그렇다.

그러나 학습 데이터의 원-핫 인코딩 적용 여부와 관계없이 softmax 적용 결과는 무조건 2차원이다.

예측한 클래스 값 하나만 딱 내주는 것이 아니다!

10개의 클래스 별 확률이 출력된다.

따라서 argmax를 해주어야 한다.

 

predicted_class = np.argmax(preds, axis=1)
print('예측 클래스 값:', predicted_class)

 

32개를 예측한 것을 봐보자.

show_images(test_images[:8], predicted_class[:8], ncols=8)
show_images(test_images[:8], test_labels[:8], ncols=8)

 

하나가 틀린 것을 볼 수 있다.

 

복사했습니다!