22.01.28(중요) Tensorflow로 배우는 CNN 핵심정리

2022. 1. 29. 11:58작업/ComputerVision

영상의 특징량

inter-class variation 다른 클래스를 구분

사람이 빠르게 움직이거나, 프레임이 바뀌거나, 노이즈가 끼는 경우

 

좋은 특징량의 조건 

1. Repeatability

(geometric, photometric)한 변화가 생겨도

2. Saliency(내가 관심있는 부분만 보겠다)

3. Locality(영상 밖에 관심있는 게있거나, 너무 크면 안된다. 이는 작은 영역에서 있어야 한다)

 

특징량의 종류

Local(각 개체) vs Global(음식)

컨볼루션, 패딩, 피처맵

가로성분의 특징이 도드라졌다 111

그런데 컨볼루션을 계속하다보면 이미지가 작아진다는 문제점이 있다. -> 패딩

패딩 -> 이미지 점점 안작아지고, 테두리 정보 안 잃어버리고 컨볼루션 할 수 있다

입력영상을 넣고 커널을 여러개 넣을 수 있다.

컬러 영상 피처맵 만들기

스트라이드와 풀링

스트라이드

]

스트라이드 =2로 가로세로 모두에 준다.

스트라이드 - 이미지를 줄일 때 downsampling할 때 주로 쓰인다. 건너뛰는 값들을 생기게 하고 싶을 때

픽셀 유실이 있을 수 있다. 

풀링 - 이미지를 줄이지만 특징점(Max, Avg 등)을 강조하면서 가능하다. 스트라이드보다 최대한 정보유실은 막을 수 있다.

스트라이드와 풀링 - 다운샘플링(표본화)를 해서 추론속도를 높인다. 풀링은 다운샘플링 + 특징점 잃지 않을 수 있다.

속도자체는 스트라이드가 더 빠르다. 

패딩을 False로 하면 10x10영상으로

 

CNN 개요

전통적인 영상분류시스템

전통적인 영상분류는 인풋을 바꿀때마다 검출기랑 시스템을 싹다 바꿔야 한다. -> 머신러닝 도입하자

CNN 

CNN 은 2차원인데 왜 FC를 쓰냐

그 이미지에다가 filter를 적용할때는 w*h사이즈의 *C개의 채널에다가 2차원으로 적용시킨다. 그게 저 Feature Learning 부분. 그리고 그 뒤에 Classfication은 1차원 FC로 한다. (근데 사실 FC를 많이 쓰지 않는다. 다 연결해줘야되기 때문에 연산량이 많아서 속도가 느리다.)

 

일단 Feature를 뽑아내면 Flatten으로 그 feature들을 쭉 뽑는다.

그리고 이제 FC로 간다.

FC : 필터를 거친 그 결과인 Feature Node들의 모든 경우의 수를 분류하고싶은 Class 갯수와 모두 연결시킴(Flatten - FC)

그리고 나서 그 Class 결과에다가 Softmax를 적용시켜서 Class들중에서 가장 확률이 높은 애로 결론내림.

CNN에서 머신러닝이란... Kernel의 계수(weight)를 학습하는 것이다. 3x3 Kernel의 가장 최적의 weight가 무엇일까(000 135 000) 등..이 값

 

FCN, 활성화함수, 손실함수

1 0 0 1 .. 여기서 fileter는 4개 사용? 여러개의 filter를 사용한 것이다.

1 0 0 1 얘네가 021, 20-1 이 feature node에서 (car)에 얼마나 관여하는지=0, (truck)에 얼마나 관여하는지=2, (van)에 얼마나 관여하는지=1가 바로 weight이다.

이런식으로 weight를 계산한다. car는 최종 -1, truck는 최종3, van은 최종 2

그런데 이렇게 -1, 3, 2는 계산은 했는데 이게 천차만별이기 때문에 값들이 서로 비교가 안될 수도 있다. 0.1 , 3000, -3 이런식으로

그래서 활성화 함수로 비교를 한다.

 

활성화함수 : -1,3,2 만 보고 특정 세기 이상이면 활성화를 시켜서(weight를 보고 기준을 내려서) class로 식별하게 한다.

즉 -1,3,2 저 정도에 따라 0 또는 1로 분류해준다.

Softmax : 각 class로 분류될 확률을 0과 1사이의 값으로 표현한다. 

손실함수(정답이랑 얼마나 차이) = 비용함수(얼마나 효용성이 있느냐, 최적화의 관점) = 목적함수(결과가 목적에 얼마나 부합하는지) : 결론은 예측값 y와 정답label y^의 차이

손실함수로는 MSE : 평균 오차(절댓값제곱(미분가능떄문에)의 평균)를 많이 사용

실습 1 MSE 파이썬으로 구현

import numpy as np

def MSE(y, y_hat):
    """
    MSE 함수 구현을 채우세요.
    """
    error = y-y_hat
    squared = error ** 2
    mean = np.mean(squared)

    # 소수점 둘째 자리에서 반올림하세요.
    mean = round(mean, 2)
    return mean
    
# 테스트1
y     = np.array([0,   0,   1,   0,   0])
y_hat = np.array([0.1, 0.1, 0.6, 0.1, 0.1])
mse = MSE(y, y_hat)
print(mse)

# 테스트2
y     = np.array([0,   0,   1,   0,   0])
y_hat = np.array([0.2, 0.2, 0.2, 0.2, 0.2])
mse = MSE(y, y_hat)
print(mse)

# 테스트3
y     = np.array([43,  7, 53, 86, -44])
y_hat = np.array([54, 67, 23, 96, -50])
mse = MSE(y, y_hat)
print(mse)

CNN의 첫번쨰 적용모델 Lenet

c가 1이므로 일단 흑백영상을 넣는걸로 가정을 했다. filter 는 6가지 -> 6가지 feature이 나온다

Subsampling = Downsampling

14x14에서 5x5 conv를 또 적용하고, stride? padding?을2로 줘서 10x10이 된다.

저 10x10 의 커널갯수는 16개로 한다. 즉 16개의 feature map이 나온다. 그 다음 downsampling으로 5x5가 됨

 

마지막으로 5x5 feature map에 5x5 conv를 하면 최종 feature는 1x1이 된다.(conv가 한번밖에 안됨) 가 120개

Flatten = 1차원 배열로 .. -> 1x120

1x120 -> 1x84 -> 1x10 최종적으로 10개의 class를 구별 가능

(이 그림나열 기준으로) 32x32 -> 2x3 -> 4x4 -> 10x12 -> 7x12 -> 1x10

 

텐서플로우와 케라스 소개

텐서플로우 안에 keras가 포함되어 있다. NN을 만들기 위한 오픈소스 라이브러리임.

파이썬, C++, Java, Go를 지원

텐서 : 다차원 배열, 입력/출력 노드들을 일컫는다.

keras : 좀더 사람말같이 되어있어서 구현이 빠르고, 다양한 프레임워크들의 백엔드를 호환한다.

케라스 : 개인이 만든 거라서 레거시 코드의 위험이 있었으나, 텐서플로우가 채택했기 떄문에 이제는 글로벌 기업들의 지원을 받고 있다.

그러나 속도가 느리다.

 

이제는 상관 없다. 텐서플로우 2.0부터는 텐서플로우가 케라스를 먹어서 통합되엇기 때문에 같이 사용 가능

실습 2 CNN 직접 구현하기 tensorflow.keras 활용

from tensorflow.keras import datasets, layers, models, activations


# 모델 변수를 선언합니다.
model = models.Sequential()

# 모델에 첫 번째 입력 레이어를 추가합니다.
model.add(layers.Convolution2D(32, (3, 3), activation=activations.relu, input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D(pool_size=(2,2)))

# 아래에 지시상항에 있는 모델 구조가 되도록 나머지 모델 구조를 선언해주세요.
model.add(layers.Convolution2D(64, (3, 3), activation=activations.relu))
model.add(layers.MaxPooling2D(pool_size=(2,2)))
model.add(layers.Convolution2D(64, (3, 3), activation=activations.relu))
model.add(layers.Flatten())
model.add(layers.Dense(64,activation='relu'))
model.add(layers.Dense(10,activation='softmax'))
# Model 구조를 출력합니다.
model.summary()

실습3 CNN 학습시키기

import os
import cv2
import numpy
from tensorflow.keras import datasets, layers, models, activations, losses, optimizers, metrics

# mnist 데이터 셋을 로드합니다.
# 각각 학습셋(이미지, 라벨), 테스트 셋(이미지, 라벨)으로 구성이 되어 있습니다.
data_path = os.path.abspath("./mnist.npz")
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data(path=data_path)

# 학습과 테스트에 사용할 데이터의 수를 조정합니다.
# 참고, 원래 MNIST의 학습 이미지 셋은 60000개, 테스트 셋은 10000개 입니다.
train_cnt, test_cnt = 5, 1 # 60000, 10000
train_images, train_labels = train_images[:train_cnt], train_labels[:train_cnt]
test_images, test_labels = test_images[:test_cnt], test_labels[:test_cnt]

# 학습 셋은 사용할 갯수만큼 28x28 이진 이미지이므로 reshaping을 해줍니다.
train_images = train_images.reshape((train_cnt, 28, 28, 1))

# 테스트 셋 역시 사용할 갯수만큼 28x28 이진 이미지이므로 reshaping을 해줍니다.
test_images = test_images.reshape((test_cnt, 28, 28, 1))

# 픽셀 값을 0~1 사이로 정규화합니다.
train_images, test_images = train_images / 255.0, test_images / 255.0

# 모델을 구조를 선언하세요.
# 모델 변수를 선언합니다.
model = models.Sequential()

# 모델에 첫 번째 입력 레이어를 추가합니다.
model.add(layers.Convolution2D(32, (3, 3), activation=activations.relu, input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D(pool_size=(2,2)))

# 아래에 지시상항에 있는 모델 구조가 되도록 나머지 모델 구조를 선언해주세요.
model.add(layers.Convolution2D(64, (3, 3), activation=activations.relu))
model.add(layers.MaxPooling2D(pool_size=(2,2)))
model.add(layers.Convolution2D(64, (3, 3), activation=activations.relu))
model.add(layers.Flatten())
model.add(layers.Dense(64,activation='relu'))
model.add(layers.Dense(10,activation='softmax'))

# 모델을 컴파일 합니다.
adam_optimizer = optimizers.Adam()
loss_function = losses.sparse_categorical_crossentropy
metric = metrics.categorical_accuracy
model.compile(optimizer=adam_optimizer, loss=loss_function, metrics=[metric])

# 모델을 학습데이터로 학습하세요.
model.fit(train_images, train_labels, epochs=1)

# 모델을 평가하세요.
test_loss, test_acc = model.evaluate(test_images,test_labels)


# 학습 결과를 출력합니다.
print("test_loss:", test_loss, "test_acc:", test_acc)

# 모델에 테스트 이미지를 넣고 예측값을 확인해봅니다.
test_img = cv2.imread("7.png", cv2.IMREAD_GRAYSCALE)

# 입력 이미지의 픽셀을 0~1 사이로 정규화 합니다.
test_img = test_img / 255.0
row, col, channel = test_img.shape[0], test_img.shape[1], 1
confidence = model.predict(test_img.reshape((1, row, col, channel)))

for i in range(confidence.shape[1]):
    print(f"{i} 일 확률 = {confidence[0][i]}")

print(f"정답은 : {numpy.argmax(confidence, axis=1)}")

import os
import cv2
import numpy
from tensorflow.keras import datasets, layers, models, activations, losses, optimizers, metrics

# mnist 데이터 셋을 로드합니다.
# 각각 학습셋(이미지, 라벨), 테스트 셋(이미지, 라벨)으로 구성이 되어 있습니다.
data_path = os.path.abspath("./mnist.npz")
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data(path=data_path)

# 학습과 테스트에 사용할 데이터의 수를 조정합니다.
# 참고, 원래 MNIST의 학습 이미지 셋은 60000개, 테스트 셋은 10000개 입니다.
train_cnt, test_cnt = 6000, 1000
train_images, train_labels = train_images[:train_cnt], train_labels[:train_cnt]
test_images, test_labels = test_images[:test_cnt], test_labels[:test_cnt]

# 학습 셋은 사용할 갯수만큼 28x28 이진 이미지이므로 reshaping을 해줍니다.
train_images = train_images.reshape((train_cnt, 28, 28, 1))

# 테스트 셋 역시 사용할 갯수만큼 28x28 이진 이미지이므로 reshaping을 해줍니다.
test_images = test_images.reshape((test_cnt, 28, 28, 1))

# 픽셀 값을 0~1 사이로 정규화합니다.
train_images, test_images = train_images / 255.0, test_images / 255.0

# 모델을 구조를 선언하세요.
# 모델 변수를 선언합니다.
model = models.Sequential()

# 모델에 첫 번째 입력 레이어를 추가합니다.
model.add(layers.Convolution2D(32, (3, 3), activation=activations.relu, input_shape=(28, 28, 1)))
model.add(layers.MaxPooling2D(pool_size=(2,2)))

# 아래에 지시상항에 있는 모델 구조가 되도록 나머지 모델 구조를 선언해주세요.
model.add(layers.Convolution2D(64, (3, 3), activation=activations.relu))
model.add(layers.MaxPooling2D(pool_size=(2,2)))
model.add(layers.Convolution2D(64, (3, 3), activation=activations.relu))
model.add(layers.Flatten())
model.add(layers.Dense(64,activation='relu'))
model.add(layers.Dense(10,activation='softmax'))

# 모델을 컴파일 합니다.
adam_optimizer = optimizers.Adam()
loss_function = losses.sparse_categorical_crossentropy
metric = metrics.categorical_accuracy
model.compile(optimizer=adam_optimizer, loss=loss_function, metrics=[metric])

# 모델을 학습데이터로 학습하세요.
model.fit(train_images, train_labels, epochs=5)

# 모델을 평가하세요.
test_loss, test_acc = model.evaluate(test_images,test_labels)


# 학습 결과를 출력합니다.
print("test_loss:", test_loss, "test_acc:", test_acc)

# 모델에 테스트 이미지를 넣고 예측값을 확인해봅니다.
test_img = cv2.imread("7.png", cv2.IMREAD_GRAYSCALE)

# 입력 이미지의 픽셀을 0~1 사이로 정규화 합니다.
test_img = test_img / 255.0
row, col, channel = test_img.shape[0], test_img.shape[1], 1
confidence = model.predict(test_img.reshape((1, row, col, channel)))

for i in range(confidence.shape[1]):
    print(f"{i} 일 확률 = {confidence[0][i]}")

print(f"정답은 : {numpy.argmax(confidence, axis=1)}")

실습4 LeNet 직접 구현하기

import logging, os
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
from keras.backend.tensorflow_backend import tf
logger = tf.get_logger()
logger.setLevel(logging.FATAL)

import keras
from tensorflow.keras import datasets, layers, models, activations, utils

# 모델 변수를 선언합니다.
model = models.Sequential()
# 모델에 첫번째 입력 레이어를 추가합니다.
model.add(layers.Conv2D(6, kernel_size=(5, 5), strides=(1,1), activation='tanh', input_shape=(32, 32, 1)))

# 아래에 지시상항에 있는 모델 구조가 되도록 나머지 모델 구조를 선언해주세요.
model.add(layers.AveragePooling2D((2,2), strides=(2,2)))
model.add(layers.Conv2D(16, (5,5), strides=(1,1), activation='tanh'))
model.add(layers.AveragePooling2D((2,2), strides=(2,2)))
model.add(layers.Conv2D(120, (5,5), strides=(1,1), activation='tanh'))
model.add(layers.Flatten())
model.add(layers.Dense(84, 'tanh'))
model.add(layers.Dense(10, 'softmax'))

model.compile(loss=keras.losses.categorical_crossentropy, optimizer='SGD')

# Model 구조 확인
model.summary()

실습5 LeNet 학습시키기

import os 
import cv2
import numpy
from tensorflow.keras import datasets, layers, models, activations, losses, optimizers, metrics


import tensorflow as tf
import numpy as np


# mnist 데이터 셋을 로드합니다.
# 각각 학습셋(이미지, 라벨), 테스트 셋(이미지, 라벨)으로 구성이 되어 있습니다.
data_path = os.path.abspath("./mnist.npz")
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data(path=data_path)

train_cnt, test_cnt = 60000, 10000
train_images, train_labels = train_images[:train_cnt], train_labels[:train_cnt]
test_images, test_labels = test_images[:test_cnt], test_labels[:test_cnt]

# 학습 셋은 60000개의 28x28 이진 이미지이므로 reshaping을 해줍니다.
train_images = train_images.reshape((train_cnt, 28, 28, 1))

# 테스트 셋은 10000개의 28x28 이진 이미지이므로 reshaping을 해줍니다.
test_images = test_images.reshape((test_cnt, 28, 28, 1))

# LeNet의 입력은 32x32 이미지 입니다. 패딩을 주어서 28 x 28에서 32 x 32 이미지로 만듭니다.
train_images = numpy.pad(train_images, [[0, 0], [2,2], [2,2], [0,0]], 'constant')
test_images = numpy.pad(test_images, [[0, 0], [2,2], [2,2], [0,0]], 'constant')
print('train_images :', train_images.shape, type(train_images))
print('test_images :', test_images.shape, type(test_images))

# 픽셀 값을 0~1 사이로 정규화합니다.
train_images, test_images = train_images / 255.0, test_images / 255.0

# 모델을 구조를 선언합니다.
# 모델 변수를 선언합니다.
model = models.Sequential()
# 모델에 첫번째 입력 레이어를 추가합니다.
model.add(layers.Conv2D(6, kernel_size=(5, 5), strides=(1,1), activation='tanh', input_shape=(32, 32, 1)))

# 아래에 지시상항에 있는 모델 구조가 되도록 나머지 모델 구조를 선언해주세요.
model.add(layers.AveragePooling2D((2,2), strides=(2,2)))
model.add(layers.Conv2D(16, (5,5), strides=(1,1), activation='tanh'))
model.add(layers.AveragePooling2D((2,2), strides=(2,2)))
model.add(layers.Conv2D(120, (5,5), strides=(1,1), activation='tanh'))
model.add(layers.Flatten())
model.add(layers.Dense(84, 'tanh'))
model.add(layers.Dense(10, 'softmax'))

model.compile(loss=losses.categorical_crossentropy, optimizer='SGD')


# 모델을 컴파일 합니다.
adam_optimizer = optimizers.Adam()
loss_function = losses.sparse_categorical_crossentropy
metric = metrics.categorical_accuracy
model.compile(optimizer = adam_optimizer, loss=loss_function, metrics=[metric])

# 모델을 학습데이터로 학습합니다.
model.fit(train_images, train_labels, epochs=1)

# 모델을 평가합니다.

test_loss, test_acc = model.evaluate(test_images, test_labels)


# 학습 결과를 출력합니다.
print("test_loss:", test_loss, "test_acc:", test_acc)

# 모델에 테스트 이미지를 넣고 예측값을 확인해봅니다.
test_img = cv2.imread("2.png", cv2.IMREAD_GRAYSCALE)
print(test_img)

# 입력 이미지의 픽셀을 0~1 사이로 정규화 합니다.
test_img = test_img / 255.0
row, col, channel = test_img.shape[0], test_img.shape[1], 1
confidence = model.predict(test_img.reshape((1, row, col, channel)))

for i in range(confidence.shape[1]):
    print(f"{i} 일 확률 = {confidence[0][i]}")

print(f"정답은 : {numpy.argmax(confidence, axis=1)}")

배치 정규화

1 epoch = 모든 데이터를 한번에 다 때려넣고 학습을 진행

batch (mini batch) 데이터를 조금씩 나눠서 학습하는 방법 : + 속도가 빠름 - 잘못 학습될 수 있다(일부만 보기 때문)

batch size

n = 메모리를 쓰는 한에서 최대한 큰 값으로~

그러나 batch size가 너무 커지면(너무 잘게 나누면) 학습이 잘 안된다

mini batch의 경우 1번데이터묶음 2번데이터묶음 이렇게 나눠 들어가므로 레이어를 겪을수록 분포가 달라진다.

각 레이어마다 분포가 달라지면 값이 양 극단으로 치우친다( activation함수 거치면 죽어버린다 0보다 작으니까) 
-> 그래서 layer를 거칠때마다 batch normalization 을 해줘야 한다.

보통 Conv 끝나고 BN을 해준다.

 

 

배치정규화(BN)

실습6 배치정규화 BN으로 Lenet 성능 개선하기

Before BN
After BN

 

import os 
import cv2
import numpy

# Fix seed
import tensorflow as tf
tf.random.set_seed(1)
import numpy as np
np.random.seed(1)

from tensorflow.keras import datasets, layers, models, activations, losses, optimizers, metrics
from tensorflow.keras import utils

# mnist 데이터 셋을 로드합니다.
# 각각 학습셋(이미지, 라벨), 테스트 셋(이미지, 라벨)으로 구성이 되어 있습니다.
data_path = os.path.abspath("./mnist.npz")
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data(path=data_path)

train_cnt, test_cnt = 50000, 10000
train_images, train_labels = train_images[:train_cnt], train_labels[:train_cnt]
test_images, test_labels = test_images[:test_cnt], test_labels[:test_cnt]

# 학습 셋은 60000개의 28x28 이진 이미지이므로 reshaping을 해줍니다.
train_images = train_images.reshape((train_cnt, 28, 28, 1))

# 테스트 셋은 10000개의 28x28 이진 이미지이므로 reshaping을 해줍니다.
test_images = test_images.reshape((test_cnt, 28, 28, 1))

# LeNet의 입력은 32x32 이미지 입니다. 패딩을 주어서 28 x 28에서 32 x 32 이미지로 만듭니다.
train_images = numpy.pad(train_images, [[0, 0], [2,2], [2,2], [0,0]], 'constant')
test_images = numpy.pad(test_images, [[0, 0], [2,2], [2,2], [0,0]], 'constant')
print('train_images :', train_images.shape, type(train_images))
print('test_images :', test_images.shape, type(test_images))

# 픽셀 값을 0~1 사이로 정규화합니다.
train_images, test_images = train_images / 255.0, test_images / 255.0

# 모델을 구조를 선언합니다.
model = models.Sequential()
model.add(layers.Conv2D(6,(5,5), strides=(1,1), activation='tanh'))
model.add(layers.BatchNormalization())
model.add(layers.AveragePooling2D((2,2), strides=(2,2)))
model.add(layers.Conv2D(16,(5,5), strides=(1,1), activation='tanh'))
model.add(layers.AveragePooling2D((2,2), strides=(2,2)))
model.add(layers.Conv2D(120,(5,5), strides=(1,1), activation='tanh'))
model.add(layers.BatchNormalization())
model.add(layers.Flatten())
model.add(layers.Dense(84, activation='tanh'))
model.add(layers.Dense(10, activation='softmax'))


# 모델을 컴파일 합니다.
model.compile(loss=losses.sparse_categorical_crossentropy, 
              optimizer=optimizers.Adam(),
              metrics=[metrics.categorical_accuracy])

# 모델을 학습합니다.
model.fit(train_images, train_labels, epochs=1)
test_loss, test_acc = model.evaluate(test_images, test_labels)

# 모델에 테스트 이미지를 넣고 예측값을 확인해봅니다.
test_img = cv2.imread("2.png", cv2.IMREAD_GRAYSCALE)

# 입력 이미지의 픽셀을 0~1 사이로 정규화 합니다.
test_img = test_img / 255.0
row, col, channel = test_img.shape[0], test_img.shape[1], 1
confidence = model.predict(test_img.reshape((1, row, col, channel)))

for i in range(confidence.shape[1]):
    print(f"{i} 일 확률 = {confidence[0][i]}")

# 학습 결과를 출력합니다.아래 내용을 수정하면 채점이 되지 않습니다.
print(numpy.argmax(confidence, axis=1), round(test_loss, 2), round(test_acc, 2))

 

셔플링 - mini batch에서 데이터를 섞는 것

iteration 학습하는 횟수

한 미니배치를 학습할 떄마다 데이터를 섞어서 학습을 하게 한다

예를들어 batch size=4이고 1epoch당 4번 한다고 치면

만약 셔플링 안해주면 로드되는 데이터가 같은 자리에만 로드하게되면 과적합의 문제가 있을 수 있다.

근데 셔플링이 막 엄청 확실한 효과가 있는 건 아니다. 막 꼭 해야되는 그런 건 아님.

다만 과적합이 되지 않을 가능성을 열어두는 일이다

셔플을 하면 gradient가 최적해를 좀더 다양한 방향으로 탐색하게 되며, 셔플링이란 이터레이션에서 데이터순서를 바꿔주는 것을 의미한다.

실습 7 셔플링 진행해서 모델성능 개선하기

import os 
import cv2
import numpy
import matplotlib.pyplot as plt

# Fix seed
import tensorflow as tf
tf.random.set_seed(1)
import numpy as np
np.random.seed(1)

from tensorflow.keras import datasets, layers, models, activations, losses, optimizers, metrics
from tensorflow.keras import utils

# mnist 데이터 셋을 로드합니다.
# 각각 학습셋(이미지, 라벨), 테스트 셋(이미지, 라벨)으로 구성이 되어 있습니다.
data_path = os.path.abspath("./mnist.npz")
(train_images, train_labels), (test_images, test_labels) = datasets.mnist.load_data(path=data_path)

train_cnt, test_cnt = 5000, 1000
train_images, train_labels = train_images[:train_cnt], train_labels[:train_cnt]
test_images, test_labels = test_images[:test_cnt], test_labels[:test_cnt]

# 학습 셋은 60000개의 28x28 이진 이미지이므로 reshaping을 해줍니다.
train_images = train_images.reshape((train_cnt, 28, 28, 1))

# 테스트 셋은 10000개의 28x28 이진 이미지이므로 reshaping을 해줍니다.
test_images = test_images.reshape((test_cnt, 28, 28, 1))

# LeNet의 입력은 32x32 이미지 입니다. 패딩을 주어서 28 x 28에서 32 x 32 이미지로 만듭니다.
train_images = numpy.pad(train_images, [[0, 0], [2,2], [2,2], [0,0]], 'constant')
test_images = numpy.pad(test_images, [[0, 0], [2,2], [2,2], [0,0]], 'constant')
print('train_images :', train_images.shape, type(train_images))
print('test_images :', test_images.shape, type(test_images))

# 픽셀 값을 0~1 사이로 정규화합니다.
train_images, test_images = train_images / 255.0, test_images / 255.0

# 모델을 구조를 선언합니다.
model = models.Sequential()
model.add(layers.Conv2D(6, kernel_size=(5, 5), strides=(1, 1), activation='tanh', input_shape=(32, 32, 1)))
model.add(layers.BatchNormalization())
model.add(layers.AveragePooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(layers.Conv2D(16, kernel_size=(5, 5), strides=(1, 1), activation='tanh'))
model.add(layers.BatchNormalization())
model.add(layers.AveragePooling2D(pool_size=(2, 2), strides=(2, 2)))
model.add(layers.Conv2D(120, kernel_size=(1, 1), strides=(5, 5), activation='tanh'))
model.add(layers.BatchNormalization())
model.add(layers.Flatten())
model.add(layers.Dense(84, activation='tanh'))
model.add(layers.Dense(10, activation='softmax'))


# 모델을 컴파일합니다.
model.compile(loss=losses.sparse_categorical_crossentropy, 
              optimizer=optimizers.Adam(),
              metrics=[metrics.categorical_accuracy])


# 모델을 학습하는 코드를 작성하세요. (shuffle을 하지 않습니다.)
results_no_shuffle = model.fit(train_images, train_labels, epochs=5, shuffle=False)
no_shuffle_test_loss, no_shuffle_test_acc = model.evaluate(test_images, test_labels)


model.compile(loss=losses.sparse_categorical_crossentropy, 
              optimizer=optimizers.Adam(),
              metrics=[metrics.categorical_accuracy])

# 모델을 학습하는 코드를 작성하세요. (shuffle을 사용해 봅니다.)
results_shuffle = model.fit(train_images, train_labels, epochs=5, shuffle=True)
shuffle_test_loss, shuffle_test_acc = model.evaluate(test_images, test_labels)

# 코드 작성 후 epoch별 loss를 비교한 결과를 확인해보세요.
print('No Shuffle loss :', [round(x, 3) for x in results_no_shuffle.history['loss']]) 
print('   Shuffle loss :', [round(x, 3) for x in results_shuffle.history['loss']])

print(results_no_shuffle.history['loss'][-1], results_shuffle.history['loss'][-1])