1. mnist 데이터세트로 이미지분류
데이터를 메모리에 로드(Keras 사용)
다수의 딥러닝 프레임워크가 존재하며, 각자 나름의 장점을 지니고 있습니다. 이 워크숍에서는 Tensorflow 2, 구체적으로는 Keras API를 사용할 것입니다. Keras에는 컴퓨터 비전 작업을 위해 고안된 다수의 유용한 기능이 내장되어 있습니다. 가독성 및 효율성 덕분에 전문가적 환경에서 딥러닝을 위한 타당한 선택이기도 합니다. 하지만 Keras만 그런 것이 아니므로 딥러닝 프로젝트를 시작할 때는 다양한 프레임워크를 살펴볼 가치가 있습니다.
Keras가 제공하는 여러 유용한 기능 중 하나는 MNIST를 비롯한 여러 공통 데이터 세트를 위한 다수의 헬퍼 메서드를 포함하는 모듈입니다.
먼저 MNIST를 위한 Keras 데이터세트를 로드해 보겠습니다.
from tensorflow.keras.datasets import mnist
# the data, split between train and validation sets
(x_train, y_train), (x_valid, y_valid) = mnist.load_data()
MNIST 데이터 살펴보기
앞서 언급했던 것처럼 MNIST 데이터세트에는 수기 문자로 이루어진 70,000개의 회색조 이미지가 포함되어 있습니다. 다음 셀을 실행하면 Keras가 트레이닝을 위해 60,000개의 이미지, 검증(트레이닝 후)을 위해 10,000개의 이미지를 분할했으며 각 이미지 자체가 28x28 차원의 2D 어레이임을 확인할 수 있습니다.
x_train.shape
x_valid.shape
x_train.min()
x_train.max()
x_train[0]
(60000, 28, 28)
(10000, 28, 28)
0
255
array([[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3,
18, 18, 18, 126, 136, 175, 26, 166, 255, 247, 127, 0, 0,
0, 0],
[ 0, 0, 0, 0, 0, 0, 0, 0, 30, 36, 94, 154, 170,
253, 253, 253, 253, 253, 225, 172, 253, 242, 195, 64, 0, 0,.....
import matplotlib.pyplot as plt
image = x_train[0]
plt.imshow(image, cmap='gray')
트레이닝을 위한 데이터 준비
딥러닝에서는 대부분의 경우 트레이닝을 위한 적합한 상태로 데이터를 변환해야 합니다. 이러한 특정 이미지 분류 문제의 경우, 트레이닝을 준비하는 과정에서 데이터에 3가지 작업을 수행해야 합니다.
- 이미지 데이터를 평탄화하여 모델에 입력되는 이미지를 간소화해야 합니다.
- 이미지 데이터를 정규화하여 이미지 입력 값이 모델에서 더 쉽게 작동되도록 해야 합니다.
- 레이블을 분류하여 레이블 값이 모델에서 더 쉽게 작동되도록 해야 합니다.
이미지 데이터 평탄화
딥러닝 모델에서 2차원 이미지(우리 경우에는 28x28픽셀)를 사용할 수도 있긴 하지만 여기서는 간단하게 각 이미지를 784개의 연속 픽셀(참고: 28x28 = 784)로 이루어진 단일 어레이로 재구성(reshape)하겠습니다. 이는 이미지 평탄화라고도 불립니다.
여기서는 헬퍼 메서드 reshape를 사용하여 이를 수행합니다.
x_train = x_train.reshape(60000, 784)
x_valid = x_valid.reshape(10000, 784)
x_train.shape
x_train[0]
(60000, 784)
array([ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 18, 18, 18,
126, 136, 175, 26, 166, 255, 247, 127, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 30, 36, 94, 154, 170, 253,
253, 253, 253, 253, 225, 172, 253, 242, 195, 64, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 49, 238, 253, 253, 253,
253, 253, 253, 253, 253, 251, 93, 82, 82, 56, 39, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 219, 253,....
이미지 데이터 정규화
딥러닝 모델은 0에서 1 사이의 부동 소수점 수를 처리하는 데 더 뛰어납니다(이 주제에 대해서는 나중에 자세히 다루겠습니다). 정수 값을 0에서 1 사이의 부동 소수점 값으로 변환하는 것을 정규화라고 하며, 여기서는 데이터를 정규화하기 위해 모든 픽셀 값(앞에서 언급했던 것처럼 0~255)을 255로 나누는 단순한 접근 방법을 사용하겠습니다.
#값을 모두 0.0~1.0의 부동 소수점 값으로 변환
x_train = x_train / 255
x_valid = x_valid / 255
x_train.dtype
x_train.min()
x_train.max()
0.0
1.0
범주 인코딩
7 - 2의 답이 뭐냐고 묻는 질문을 받는다고 가정해 보겠습니다. 4라고 답하는 것은 9라고 답하는 것보다 훨씬 정답에 근접합니다. 하지만 이러한 이미지 분류 문제의 경우 뉴럴 네트워크가 이런 종류의 추론을 배우지 않도록 하는 것이 좋습니다. 그냥 올바른 범주를 선택하고 숫자 5의 이미지가 있는 경우 4를 추측하는 것이 9를 추측하는 것 만큼이나 나쁘다는 점을 이해하도록 해야 합니다.
이미지의 레이블은 현재 상태 그대로 0~9의 정수입니다. 이러한 값이 숫자 범위를 나타내므로, 모델은 올바른 숫자 범주에 얼마나 근접하게 추측하는지에 따라 성능에 대한 어떤 결론을 내리려 할 수도 있습니다.
따라서 여기서는 데이터에 범주 인코딩이라는 작업을 수행하겠습니다. 이러한 변환은 이 특정 값이 true로 설정된 실제 범주를 포함해 각 값이 가능한 모든 범주의 모음이 되도록 데이터를 수정합니다.
간단히 예를 들면, 빨간색, 파란색, 초록색, 이렇게 3개의 범주가 있다고 가정해 보겠습니다. 주어진 색상에 대해 이러한 범주 중 둘은 false, 나머지 하나는 true가 될 것입니다.
Red | True | False | False |
Green | False | False | True |
Blue | False | True | False |
Green | False | False | True |
"True" 또는 "False"를 사용하는 대신 0 또는 1의 바이너리를 사용하여 같은 내용을 표현할 수 있습니다.
Red | 1 | 0 | 0 |
Green | 0 | 0 | 1 |
Blue | 0 | 1 | 0 |
Green | 0 | 0 | 1 |
이것이 범주 인코딩 즉, 범주 레이블로 이해되어야 하는 값을 모델이 범주 특성을 알 수 있는 표현으로 변환하는 것입니다. 따라서 다음의 값을 트레이닝에 사용할 경우
values = ['red, green, blue, green']
... 뉴럴 네트워크가 이를 이해하기는 매우 어렵기에 다음과 같이 변환합니다.
values = [
[1, 0, 0],
[0, 0, 1],
[0, 1, 0],
[0, 0, 1]
]
레이블 범주 인코딩
Keras는 값을 범주 인코딩하는 유틸리티를 제공하며, 여기서는 이를 사용하여 트레이닝 및 검증 레이블 모두에 대한 범주 인코딩을 수행합니다.
import tensorflow.keras as keras
num_categories = 10
y_train = keras.utils.to_categorical(y_train, num_categories)
y_valid = keras.utils.to_categorical(y_valid, num_categories)
다음은 트레이닝 레이블과 관련된 첫 번째 10개 값으로, 지금은 범주 인코딩이 되어 있는 것을 확인할 수 있습니다.
y_train[0:9]
array([[0., 0., 0., 0., 0., 1., 0., 0., 0., 0.],
[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 1., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 0., 0., 0., 0., 0., 0., 1.],
[0., 0., 1., 0., 0., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.],
[0., 0., 0., 1., 0., 0., 0., 0., 0., 0.],
[0., 1., 0., 0., 0., 0., 0., 0., 0., 0.]], dtype=float32)
모델 생성
모델 인스턴스화
우선 Keras의 순차 모델 클래스를 사용하여 데이터가 연속으로 통과할 일련의 레이어를 보유한 모델의 인스턴스를 인스턴스화하겠습니다.
from tensorflow.keras.models import Sequential
model = Sequential()
입력 레이어 생성
다음으로, 입력 레이어를 추가합니다. 이 레이어는 밀집 연결되어 있습니다. 따라서 포함된 각 뉴런과 가중치가 다음 레이어의 모든 뉴런에 영향을 줍니다. Keras로 이를 수행하려면 Keras의 Dense 레이어 클래스를 사용해야 합니다.
from tensorflow.keras.layers import Dense
units 인수는 레이어 내 뉴런 수를 지정합니다. 여기서는 실험에서 선택한 512를 사용하겠습니다. 올바른 뉴런 수를 선택하는 것은 데이터세트의 통계적 복잡성을 없애주는 일이므로, "데이터 사이언스" 작업의 핵심이라 할 수 있습니다. 나중에 이 값을 시험해보며 트레이닝에 어떤 영향을 미치는지 살펴보고 이 숫자의 의미에 대한 감을 키우시기 바랍니다.
활성화 함수에 대해서는 나중에 자세히 알아보겠지만 일단은 relu 활성화 함수를 사용하겠습니다. 간략하게 설명하자면, 이 함수는 네트워크가 일부 엄격한 선형 함수를 토대로 추측해야 하는 경우에 비해 데이터에 대한 좀 더 정교한 추측을 하는 방법을 배울 수 있게 도와줍니다.
input_shape 값은 수신되는 데이터의 모양을 지정하며, 여기서는 784개 값으로 이루어진 1D 어레이입니다.
model.add(Dense(units=512, activation='relu', input_shape=(784,)))
숨겨진 레이어 생성
이제 밀집 연결된 추가 레이어를 더해 보겠습니다. 마찬가지로, 이와 관련된 내용도 나중에 자세히 알아보겠지만, 지금은 이러한 레이어가 추측에 기여하는 더 많은 매개변수 즉, 정확한 학습을 위한 좀 더 예리한 기회를 네트워크에 제공한다는 사실을 알면 됩니다.
model.add(Dense(units = 512, activation='relu'))
출력 레이어 생성
마지막으로, 출력 레이어를 추가하겠습니다. 이 레이어는 각 레이어의 값이 0에서 1사이의 확률이 되도록 하고 레이어의 모든 출력이 1에 추가되도록 하는 활성 함수인 softmax를 사용합니다. 이 경우에는 네트워크가 1에서 10까지의 가능한 범주에 속하는 단일 이미지에 대해 추측을 수행하므로 출력은 10개가 됩니다. 각 출력은 이미지가 해당 특정 클래스에 속한다는 모델의 추측(확률)을 제공합니다.
model.add(Dense(units = 10, activation='softmax'))
모델 요약
Keras는 모델에 대한 읽을 수 있는 요약을 출력하는 모델 인스턴스 메서드 요약을 제공합니다.
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense (Dense) (None, 512) 401920
_________________________________________________________________
dense_1 (Dense) (None, 512) 262656
_________________________________________________________________
dense_2 (Dense) (None, 10) 5130
=================================================================
Total params: 669,706
Trainable params: 669,706
Non-trainable params: 0
_________________________________________________________________
트레이닝 가능한 매개변수의 수를 확인하십시오. 이러한 각 매개변수는 트레이닝 도중에 조정 가능하며 트레이닝된 모델의 추측에 기여합니다.
모델 컴파일
model.compile(loss='categorical_crossentropy', metrics=['accuracy'])
모델 트레이닝
트레이닝 및 검증 데이터와 모델이 준비되었으니 이제 트레이닝 데이터로 모델을 트레이닝하고 검증 데이터로 이를 검증해야 합니다.
"데이터로 모델을 트레이닝"하는 것을 흔히 "모델을 데이터에 맞춘다"라고도 합니다. 모델을 데이터에 맞춘다는 말은 주어지고 있는 데이터를 좀 더 정확하게 이해하기 위해 모델이 점차적으로 모양을 바꾼다는 점을 부각시킵니다.
Keras로 모델을 맞추는(트레이닝하는) 경우에는 모델의 fit 메서드를 사용합니다. 그러면 다음 인수를 예상합니다.
- 트레이닝 데이터
- 트레이닝 데이터의 레이블
- 전체 트레이닝 데이터세트에 대해 트레이닝해야 하는 횟수(에포크)
- 검증 또는 테스트 데이터 및 해당 레이블
- 트레이닝 데이터
- 트레이닝 데이터의 레이블
- 전체 트레이닝 데이터세트에 대해 트레이닝해야 하는 횟수(에포크)
- 검증 또는 테스트 데이터 및 해당 레이블
아래 셀을 실행하여 모델을 트레이닝하십시오. 출력에 대해서는 트레이닝이 완료된 후에 논의하겠습니다.
history = model.fit(
x_train, y_train, epochs=5, verbose=1, validation_data=(x_valid, y_valid))
Epoch 1/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.1941 - accuracy: 0.9427 - val_loss: 0.0930 - val_accuracy: 0.9742
Epoch 2/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0985 - accuracy: 0.9739 - val_loss: 0.1068 - val_accuracy: 0.9738
Epoch 3/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0797 - accuracy: 0.9814 - val_loss: 0.1049 - val_accuracy: 0.9758
Epoch 4/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0706 - accuracy: 0.9842 - val_loss: 0.1217 - val_accuracy: 0.9765
Epoch 5/5
1875/1875 [==============================] - 4s 2ms/step - loss: 0.0613 - accuracy: 0.9862 - val_loss: 0.1420 - val_accuracy: 0.9778
정확도 관찰
5회의 에포크 각각에 대해 accuracy 및 val_accuracy 점수를 살펴보십시오. accuracy는 모든 트레이닝 데이터에 대한 에포크 동안의 모델 성능이 어땠는지를 명시합니다. val_accuracy는 모델을 트레이닝하는 데 전혀 사용되지 않는 검증 데이터에 대한 모델 성능이 어땠는지를 나타냅니다.
모델이 꽤 뛰어난 성능을 보여줬습니다! 정확도는 거의 100%에 빠르게 도달했고, 이는 검증 정확도의 경우에도 비슷했습니다. 이제 수기 이미지를 정확하게 검출하고 분류하는 데 사용할 수 있는 모델이 준비되었습니다.
다음 단계는 이 모델을 사용하여 처음 접하는 새로운 수기 이미지를 분류하는 것입니다. 이를 추론이라고 부릅니다. 추론 과정은 이후 연습에서 살펴보겠습니다.
요약
우리가 여기서 수행한 내용을 잠시 평가해 볼 가치가 있습니다. 예전에는 이러한 작업을 수행하도록 구축된 전문가 시스템이 매우 복잡했으며, 사람들은 이를 구축하느라 경력을 소비했습니다(공식 MNIST 페이지의 참조 자료와 주요 시점에 도달한 연도 참조).
MNIST는 컴퓨터 비전에 대한 기존의 영향 요인에 유용할 뿐만 아니라 뛰어난 벤치마크이자 디버깅 툴이기도 합니다. 새로운 근사한 머신 러닝 아키텍처를 작동하는 데 어려움을 겪고 계십니까? MNIST와 비교해 보십시오. 이 데이터세트에 대해 학습할 수 없다면 더 복잡한 이미지와 데이터세트에 대해 학습할 수 없을 가능성이 높습니다.
2. 합성곱신경망 (Convolutional Neural Networks)
이전 섹션에서 우리는 ASL 이미지를 분류하는 간단한 모델을 설계하고 학습했습니다. 모델은 매우 높은 정확도로 학습(Training) 데이터를 올바르게 분류하였지만, 검증(Validation) 데이터셋에서는 잘 작동하지 않았습니다. 학습하지 않은 데이터를 잘 일반화하지 못하는 현상을 과적합이라고 부르며, 이번 섹션에서 우리는 이미지를 읽고 분류하는데 자주 사용되는 합성곱신경망(CNN) 이라고 불리는 모델을 배울 예정입니다.
목표
- CNN 모델을 위한 데이터 준비
- 세련된 형태의 CNN 모델 생성, 다양한 layer들의 이해
- CNN 모델 학습 및 성능 관찰
데이터 준비 및 로드
아래 셀은 이전 lab에서 학습한 데이터 전처리 기술이 포함되어 있습니다. 다음 단계로 가기 이전 실행하신 후 검토하십시오.
import tensorflow.keras as keras
import pandas as pd
# Load in our data from CSV files
train_df = pd.read_csv("data/asl_data/sign_mnist_train.csv")
valid_df = pd.read_csv("data/asl_data/sign_mnist_valid.csv")
# Separate out our target values
y_train = train_df['label']
y_valid = valid_df['label']
del train_df['label']
del valid_df['label']
# Separate out our image vectors
x_train = train_df.values
x_valid = valid_df.values
# Turn our scalar targets into binary categories
num_classes = 24
y_train = keras.utils.to_categorical(y_train, num_classes)
y_valid = keras.utils.to_categorical(y_valid, num_classes)
# Normalize our image data
x_train = x_train / 255
x_valid = x_valid / 255
CNN을 위한 이미지 변환(Reshaping)
마지막 연습문제에서, 데이터셋의 개별 형식은 일렬로 된 784픽셀 형식이었습니다.
x_train.shape, x_valid.shape
이러한 형식은 서로 가까운 픽셀에 대한 모든 정보를 가지고 있지 않습니다. 그렇기 때문에, feature를 탐지하는 컨볼루션(convolution)을 적용할 수 없습니다. 데이터 셋을 28x28픽셀 형식으로 변경해 보겠습니다. 이를 통해 컨볼루션에서 인접한 픽셀끼리 그룹을 짓고 중요한 feature들을 탐지할 수 있습니다.
모델의 첫 번째 컨볼루션 레이어의 경우 이미지의 높이와 너비뿐만 아니라 채널의 숫자도 요구된다는 점에 유의해야합니다. 우리의 이미지는 Grayscale이기 때문에 채널은 1의 값을 가져야합니다.
즉, 현재의 shape인 (27455, 784)을 (27455, 28, 28, 1)로 변환할 필요가 있습니다. 편의상, 우리는 동일하게 유지하기를 원하는 모든 차원에 대해 reshape을 진행하는 경우 '-1'을 통해 표현할 수 있으므로 다음과 같이 구성할 수 있습니다.
x_train = x_train.reshape(-1,28,28,1)
x_valid = x_valid.reshape(-1,28,28,1)
x_train.shape
x_valid.shape
x_train.shape, x_valid.shape
합성곱 모델 생성
오늘날, 많은 데이터 과학자는 유사한 프로젝트를 기반으로, 자신만의 프로젝트를 시작합니다. 문제가 완전히 새로운 형태가 아닌 경우, 사람들은 TensorFlow Hub나 NGC Catalog 와 같은 온라인 저장소에 게시된 모델과 유사한 모델을 활용할 가능성이 높습니다. 오늘, 우리는 이 문제에 다음과 같은 형태의 모델을 활용할 것입니다.
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import (
Dense,
Conv2D,
MaxPool2D,
Flatten,
Dropout,
BatchNormalization,
)
model = Sequential()
model.add(Conv2D(75, (3, 3), strides=1, padding="same", activation="relu",
input_shape=(28, 28, 1)))
model.add(BatchNormalization())
model.add(MaxPool2D((2, 2), strides=2, padding="same"))
model.add(Conv2D(50, (3, 3), strides=1, padding="same", activation="relu"))
model.add(Dropout(0.2))
model.add(BatchNormalization())
model.add(MaxPool2D((2, 2), strides=2, padding="same"))
model.add(Conv2D(25, (3, 3), strides=1, padding="same", activation="relu"))
model.add(BatchNormalization())
model.add(MaxPool2D((2, 2), strides=2, padding="same"))
model.add(Flatten())
model.add(Dense(units=512, activation="relu"))
model.add(Dropout(0.3))
model.add(Dense(units=num_classes, activation="softmax"))
모델 요약
다소 복잡하게 느껴질 수 있지만 걱정할 필요는 없습니다. CNN 모델을 효과적으로 훈련하기 위해 지금 당장 모든 것을 이해하는 것은 중요하지 않습니다. 가장 중요한 것은 이미지로부터 유용한 정보를 추출하는 데 도움이 되고 분류 작업에 사용될 수 있다는 것을 알고 계시면 충분합니다.
이제, 방금 만든 모델을 요약할 수 있습니다. 이전 노트북의 모델에 비해 학습가능(Trainable)한 매개 변수가 얼마나 적은지 알아 보십시오.
model.summary()
Compiling the Model
이전의 예에서와 동일한 방식으로 모델을 컴파일할 수 있습니다.
model.compile(loss="categorical_crossentropy", metrics=["accuracy"])
Training the Model
매우 다른 모델 구조에도 불구하고, 학습은 거의 비슷한 과정으로 진행됩니다. 아래 셀을 실행하여 20 에포크동안 학습하고, 정확도가 향상되는지 확인해 보겠습니다.
model.fit(x_train, y_train, epochs=20, verbose=1, validation_data=(x_valid, y_valid))
결과에 대한 논의
이 모델은 상당히 개선된 것 같습니다! 학습 정확도도 매우 높고, 검증 정확도도 향상되었습니다. 이것은 좋은 결과이며, 우리는 단지 모델을 새로운 모델로 바꾸기만 하면 됩니다.
추가적으로, 검증 정확도가 수렴하지 않는 것을 확인하셨을 수 있습니다. 이것은 여전히 우리 모델이 완벽히 일반화가 되지 않았다는 것을 의미합니다. 다행히도, 우리가 추가적으로 할 수 있는 것이 남아있습니다. 그것은 다음장에서 다루도록 하겠습니다.
요약
이 섹션에서는 CNN을 구현하기 위해 몇 가지 새로운 종류의 레이어를 활용하여, 이전 섹션에서 사용된 단순한 모델보다 더욱 높은 성능을 기록할 수 있었습니다. 준비된 데이터로 모델을 만들고 훈련하는 전반적인 과정에 더욱 익숙해지시기를 바랍니다.
3. 전이 학습(Transfer Learning)
지금까지 대규모 데이터세트에 대해 정확한 모델을 트레이닝했으며 트레이닝할 필요없이 사용한 사전 트레이닝된 모델도 다운로드했습니다. 하지만 정확히 필요한 작업을 수행하는 사전 트레이닝된 모델을 찾을 수 없거나 모델을 처음부터 트레이닝하기 위한 충분히 큰 데이터세트가 없는 경우에는 어떻게 해야 할까요? 이 경우에는 전이 학습이라는 매우 유용한 기술을 사용할 수 있습니다.
전이 학습을 사용하면 사전 트레이닝된 모델을 원래의 트레이닝 작업과 어느 정도 중첩되는 작업에 대해 다시 트레이닝할 수 있습니다. 수채화에 능하지만 목탄화도 연습하고 싶어하는 화가를 좋은 예로 들 수 있습니다. 수채화를 그리면서 배운 기술은 목탄화 그리는 법을 배우는 데 매우 유용할 것입니다.
딥러닝의 예로, 다양한 유형의 자동차를 인식하는 성능이 뛰어난 사전 트레이닝된 모델이 있고 이 모델이 모터사이클 유형을 인식하도록 트레이닝하려 한다고 가정해 보겠습니다. 전조등 및 휠 인식 능력을 비롯한 자동차 모델의 수많은 학습이 매우 유용할 가능성이 높습니다.
전이 학습은 크고 다양한 데이터세트가 없는 경우에 특히 강력한효과가 있습니다. 이런 경우에는 처음부터 트레이닝된 모델이 트레이닝 데이터를 빠르게 기억할 가능성이 높지만 새 데이터를 제대로 일반화할 수 없습니다. 전이 학습을 이용하면 소규모 데이터세트에 대해 정확하고 강력한 모델을 트레이닝할 수 있는 가능성을 높일 수 있습니다.
목표
- 전이 학습을 위한 사전 트레이닝된 모델 준비
- 사전 트레이닝된 모델로 자체적인 소규모 데이터세트에 대해 전이 학습 수행
- 훨씬 더 나은 성능을 위해 모델을 추가로 파인튜닝
맞춤형 분류기
지난 연습에서는 사전 트레이닝된 ImageNet 모델을 사용하여 개만 출입을 허용하고 다른 동물들의 출입은 막아 보았습니다. 이 연습에서는 특정한 개만 출입 가능한 개구멍을 만들어 보려고 합니다. 이 경우에는 2009년부터 2017년까지 미국의 퍼스트 독이었던 Bo라는 개를 위한 자동 개구멍을 만들어 보겠습니다. presidential_doggy_door 폴더에 Bo의 사진이 더 있습니다.
문제는 사전 트레이닝된 모델이 이 특정 개를 인식하도록 트레이닝되지 않았으며 우리에게 Bo의 사진이 30개밖에 없다는 것입니다. 30개의 사진을 사용하여 모델을 처음부터 트레이닝하려고 할 경우 과적합과 저조한 일반화를 경험하게 됩니다. 하지만 개를 검출하는 데 능숙한 사전 트레이닝된 모델로 시작하면 이러한 학습을 활용하여 더 작은 데이터세트로도 Bo에 대한 일반화된 이해도를 얻을 수 있습니다. 전이 학습을 사용하여 이 문제를 해결할 수 있습니다.
사전 트레이닝된 모델 다운로드
ImageNet 사전 트레이닝된 모델이 컴퓨터 비전 전이 학습에 적합한 경우가 많은 이유는 다양한 유형의 이미지를 분류하도록 학습했기 때문입니다. 이 과정에서 모델은 이미지 인식에서 중요할 수 있는 수많은 다양한 유형의 특징을 검출하는 법을 학습했습니다. ImageNet 모델은 개를 비롯한 동물을 검출하는 법을 학습했으므로 Bo를 검출하기 위한 이 전이 학습 작업에 특히 적합합니다.
먼저 사전 트레이닝된 모델을 다운로드하겠습니다. 마찬가지로 이 모델은 Keras 라이브러리에서 바로 가져올 수 있습니다. 다운로드 과정에서는 중요한 차이점에 발생하게 됩니다. ImageNet 모델의 마지막 레이어는 데이터세트에서 1,000개의 가능한 클래스를 나타내는 1,000개의 유닛으로 이루어진 밀집 레이어입니다. 여기서는 다른 분류를 수행하도록 해야 합니다. 즉, Bo인지 아닌지를 분류하도록 해야 합니다. 다르게 분류를 해야 하므로 모델의 마지막 레이어를 제거하겠습니다. 이렇게 하려면 모델을 다운로드할 때 플래그 include_top=False를 설정하면 됩니다. 이 맨 위 레이어를 제거한 후에는 우리가 원하는 분류 유형을 제공할 새 레이어를 추가할 수 있습니다.
from tensorflow import keras
base_model = keras.applications.VGG16(
weights='imagenet', # Load weights pre-trained on ImageNet.
input_shape=(224, 224, 3),
include_top=False)
base_model.summary()
기본 모델 동결
새 레이어를 사전 트레이닝된 모델에 추가하기 전에 수행해야 할 중요한 단계가 있습니다. 모델의 사전 트레이닝된 레이어를 동결하는 것입니다. 즉, 트레이닝할 때 사전 트레이닝된 모델에서 기본 레이어는 업데이트하지 않는 것입니다. 대신 새로운 분류를 위해 우리가 끝에 추가하는 새 레이어만 업데이트하게 됩니다. 초기 레이어를 동결하는 이유는 ImageNet 데이터세트에 대한 트레이닝에서 달성한 학습을 유지해야 하기 때문입니다. 이 단계에서 동결 해제된 경우에는 이 중요한 정보가 손실될 가능성이 높습니다 나중에 파인튜닝이라는 과정에서 이러한 레이어를 동결 해제하고 트레이닝할 수 있는 옵션이 있습니다.
기본 레이어를 동결하는 것은 모델에 대해 트레이닝 가능을 False로 설정하는 것 만큼이나 간단합니다.
base_model.trainable = False
새 레이어 추가
이제 트레이닝 가능한 새 레이어를 사전 트레이닝된 모델에 추가할 수 있습니다. 새 레이어는 사전 트레이닝된 레이어의 피처를 취해 새 데이터세트에 대한 예측으로 변환합니다. 모델에 두 개의 레이어를 추가하겠습니다. 첫 번째는 이전의 CNN(Convolutional Neural Network)에서 봤던 풀링 레이어입니다. . 그런 다음에는 Bo인지 아닌지를 분류할 마지막 레이어를 추가해야 합니다. 이는 한 개의 출력을 포함하는 밀접하게 연결된 레이어가 됩니다.
inputs = keras.Input(shape=(224, 224, 3))
# Separately from setting trainable on the model, we set training to False
x = base_model(inputs, training=False)
x = keras.layers.GlobalAveragePooling2D()(x)
# A Dense classifier with a single unit (binary classification)
outputs = keras.layers.Dense(1)(x)
model = keras.Model(inputs, outputs)
사전 트레이닝된 모델을 새 레이어와 결합했으니 이제 모델을 살펴보겠습니다.
model.summary()
Keras는 여기서 적절한 요약을 제공합니다. 내부 레이어 전체를 표시하는 대신 vgg16 사전 트레이닝된 모델을 하나의 유닛으로 보여주기 때문입니다. 또한 사전 트레이닝된 모델을 동결했으므로 트레이닝 불가한 다수의 매개변수도 있다는 점도 주목할 가치가 있습니다.
모델 컴파일
이전 연습에서 그랬던 것처럼 손실 및 지표 옵션으로 모델을 컴파일해야 합니다. 여기서는 몇 가지 다른 선택을 해야 합니다. 이전 사례에서는 분류 문제에 다수의 범주가 있었습니다. 그래서 손실 계산을 위한 범주형 크로스 엔트로피를 선택했습니다. 이번 사례에서는 바이너리 분류 문제(Bo인지 아닌지 여부)만 있으므로 바이너리 크로스 엔트로피를 선택하겠습니다. 둘 사이의 차이점에 대한 자세한 내용은 여기에서 확인할 수 있습니다. 또한 기존 정확도 대신 바이너리 정확도를 사용합니다.
from_logits=True를 설정함으로써 출력 값이 softmax 등으로 정규화되지 않았음을 손실 함수에 알립니다.
# Important to use binary crossentropy and binary accuracy as we now have a binary classification problem
model.compile(loss=keras.losses.BinaryCrossentropy(from_logits=True), metrics=[keras.metrics.BinaryAccuracy()])
데이터 증강
매우 작은 데이터세트를 다루고 있는 만큼 데이터를 증강하는 것이 특히 중요합니다. 전과 마찬가지로, 기존 이미지를 약간 수정하면 모델이 보다 광범위한 이미지를 확인하고 학습할 수 있습니다. 이는 단순히 트레이닝하는 사진만 기억하는 대신 Bo의 새로운 사진을 인식하는 법을 학습하는 데 도움이 됩니다.
from tensorflow.keras.preprocessing.image import ImageDataGenerator
# create a data generator
datagen = ImageDataGenerator(
samplewise_center=True, # set each sample mean to 0
rotation_range=10, # randomly rotate images in the range (degrees, 0 to 180)
zoom_range = 0.1, # Randomly zoom image
width_shift_range=0.1, # randomly shift images horizontally (fraction of total width)
height_shift_range=0.1, # randomly shift images vertically (fraction of total height)
horizontal_flip=True, # randomly flip images
vertical_flip=False) # we don't expect Bo to be upside-down so we will not flip vertically
데이터 로드
우리는 지금까지 몇 가지 형식의 데이터세트를 보았습니다. MNIST 연습에서는 Keras 라이브러리 내에서 바로 데이터세트를 다운로드할 수 있었습니다. 수화 데이터세트의 경우에는 데이터가 CSV 파일 형식이었습니다. 이 연습에서는 Keras의 flow_from_directory 함수를 사용하여 폴더에서 바로 이미지를 로드하겠습니다. 이러한 과정이 원활하게 이루어지도록 디렉토리를 설정해 놓았습니다. presidential_doggy_door 디렉토리에는 트레이닝 및 검증 디렉토리가 있으며, 각 디렉토리에는 Bo인 이미지와 Bo가 아닌 이미지를 위한 폴더가 있습니다. not_bo 디렉토리에는 다른 반려동물을 막도록 모델에 가르치기 위한 다른 개와 고양이의 사진이 있습니다. 자유롭게 미지를 살펴보며 데이터세트에 대한 감을 익히시기 바랍니다.
flow_from_directory 또한 모델에 맞게 이미지 크기를 변경할 수 있도록 해줍니다(244x244픽셀 및 3채널).
# load and iterate training dataset
train_it = datagen.flow_from_directory('data/presidential_doggy_door/train/',
target_size=(224, 224),
color_mode='rgb',
class_mode='binary',
batch_size=8)
# load and iterate validation dataset
valid_it = datagen.flow_from_directory('data/presidential_doggy_door/valid/',
target_size=(224, 224),
color_mode='rgb',
class_mode='binary',
batch_size=8)
모델 트레이닝
이제 모델을 트레이닝하고 성능을 확인해볼 시간입니다. 데이터 생성기를 사용했을 당시에는 steps_per_epoch의 수를 명확하게 설정해야 했습니다.
model.fit(train_it, steps_per_epoch=12, validation_data=valid_it, validation_steps=4, epochs=20)
결과에 대한 논의
트레이닝 및 검증 정확도 둘 다 상당히 높아야 합니다. 이는 꽤 멋진 결과입니다! 소규모 데이터세트에 대한 트레이닝을 했지만 ImageNet 모델에서 전이된 지식 덕분에 높은 정확도와 적절한 일반화를 달성할 수 있었습니다. 즉, Bo인지 아닌지를 매우 잘 구별할 수 있다는 것입니다.
검증 정확도에서 약간의 변동을 목격했더라도 걱정할 필요 없습니다. 다음 섹션에는 모델 개선을 위한 기술에 대해 이야기하겠습니다.
모델의 새 레이어가 트레이닝되었으므로 이제 파인튜닝이라는 마지막 요령을 적용하여 모델을 개선할 수 있습니다. 이렇게 하려면 전체 모델을 동결 해제한 후 아주 작은 학습률로 다시 트레이닝해야 합니다. 그러면 사전 트레이닝된 기본 레이어가 아주 작은 단계를 취해 약간씩 조정되면서 모델이 조금씩 개선됩니다.
이 단계는 동결된 레이어를 포함하는 모델이 완전히 트레이닝된 후에만 수행해야 합니다. 앞서 모델에 추가한 트레이닝되지 않은 풀링 및 분류 레이어는 임의로 초기화되었습니다. 즉, 이미지를 올바르게 분류하기 위해서는 이 두 레이어에 꽤 많은 업데이트가 필요합니다. 역전파 프로세스 과정에서 마지막 레이어의 대규모 초기 업데이트가 사전 트레이닝된 레이어에서도 대규모 업데이트를 초래했을 가능성이 있습니다. 이러한 업데이트는 중요한 사전 트레이닝된 피처를 손상시켰을 것입니다. 하지만 최종 레이어가 트레이닝되고 수렴된 만큼 모델에 대한 업데이트 전체가 크게 축소되어(특히 매우 작은 학습률로) 이전 레이어의 피처를 손상시키지 않을 것입니다.
사전 트레이닝된 레이어를 동결 해제한 다음 모델을 파인튜닝해 보겠습니다.
# Unfreeze the base model
base_model.trainable = True
# It's important to recompile your model after you make any changes
# to the `trainable` attribute of any inner layer, so that your changes
# are taken into account
model.compile(optimizer=keras.optimizers.RMSprop(learning_rate = .00001), # Very low learning rate
loss=keras.losses.BinaryCrossentropy(from_logits=True),
metrics=[keras.metrics.BinaryAccuracy()])
model.fit(train_it, steps_per_epoch=12, validation_data=valid_it, validation_steps=4, epochs=10)
예측 검사
잘 트레이닝된 모델이 준비되었으니 이제 Bo를 위한 개구멍을 만들어야 합니다! 먼저 모델이 제공하는 예측부터 살펴보겠습니다. 이미지는 지난 개구멍의 경우와 동일한 방식으로 사전 처리하겠습니다.
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from tensorflow.keras.preprocessing import image as image_utils
from tensorflow.keras.applications.imagenet_utils import preprocess_input
def show_image(image_path):
image = mpimg.imread(image_path)
plt.imshow(image)
def make_predictions(image_path):
show_image(image_path)
image = image_utils.load_img(image_path, target_size=(224, 224))
image = image_utils.img_to_array(image)
image = image.reshape(1,224,224,3)
image = preprocess_input(image)
preds = model.predict(image)
return preds
make_predictions('data/presidential_doggy_door/valid/bo/bo_20.jpg')
make_predictions('data/presidential_doggy_door/valid/not_bo/121.jpg')
7. 평가
from tensorflow import keras # Keras 라이브러리 임포트
# VGG16 모델을 불러오고 ImageNet 가중치를 사용하며, 최상위 레이어를 포함하지 않음
base_model = keras.applications.VGG16(
weights='imagenet',
input_shape=(224, 224, 3), # 입력 이미지 크기 설정
include_top=False) # 최상위 레이어 제외
# 기본 모델의 가중치를 고정하여 학습하지 않도록 설정
base_model.trainable = False
# 모델의 입력 형태를 정의
inputs = keras.Input(shape=(224, 224, 3))
# 기본 모델을 입력에 적용하여 특징을 추출
x = base_model(inputs, training=False)
# Global Average Pooling 레이어 추가
x = keras.layers.GlobalAveragePooling2D()(x)
# 최종 Dense 레이어 추가 (6개의 클래스, softmax 활성화 함수 사용)
outputs = keras.layers.Dense(6, activation='softmax')(x)
# 입력과 출력을 결합하여 전체 모델 생성
model = keras.Model(inputs, outputs)
# 모델 요약 출력 (모델 구조를 확인)
model.summary()
# 모델 컴파일 (손실 함수 및 평가 지표 설정)
model.compile(loss=keras.losses.CategoricalCrossentropy(from_logits=False),
metrics=[keras.metrics.CategoricalAccuracy()])
from tensorflow.keras.preprocessing.image import ImageDataGenerator # 이미지 데이터 증강을 위한 라이브러리 임포트
# 데이터 생성기 생성 (이미지 증강을 위한 설정)
datagen = ImageDataGenerator(
samplewise_center=True, # 각 샘플의 평균을 0으로 설정
rotation_range=10, # 이미지를 무작위로 10도 회전
zoom_range=0.1, # 이미지를 무작위로 10% 확대
width_shift_range=0.1, # 이미지를 수평으로 10% 이동
height_shift_range=0.1, # 이미지를 수직으로 10% 이동
horizontal_flip=True, # 이미지를 무작위로 수평으로 뒤집음
vertical_flip=False) # 이미지를 수직으로 뒤집지 않음
# 훈련 데이터셋을 로드하여 반복 가능한 데이터 생성기 생성
train_it = datagen.flow_from_directory('data/fruits/train/',
target_size=(224, 224), # 이미지 크기 조정
color_mode='rgb', # 색상 모드 설정
class_mode='categorical', # 클래스 모드 설정 (범주형)
batch_size=8) # 배치 크기 설정
# 검증 데이터셋을 로드하여 반복 가능한 데이터 생성기 생성
valid_it = datagen.flow_from_directory('data/fruits/valid/',
target_size=(224, 224), # 이미지 크기 조정
color_mode='rgb', # 색상 모드 설정
class_mode='categorical', # 클래스 모드 설정 (범주형)
batch_size=8) # 배치 크기 설정
# 모델 학습 시작
model.fit(train_it,
validation_data=valid_it, # 검증 데이터셋 사용
steps_per_epoch=train_it.samples // train_it.batch_size, # 한 에포크당 스텝 수 설정
validation_steps=valid_it.samples // valid_it.batch_size, # 검증 스텝 수 설정
epochs=20) # 총 에포크 수 설정
VGG16 모델을 사용하여 과일 분류를 위한 이미지 분류 모델을 구축하고 훈련하는 과정
'ABC 부트캠프 데이터 탐험가 4기' 카테고리의 다른 글
[29 일차] ABC 부트캠프 : 기술나눔활동(데이터 라벨링) (0) | 2024.08.17 |
---|---|
[28 일차] ABC 부트캠프 : 딥러닝 기초(2) (0) | 2024.08.13 |
[26 일] ABC 부트캠프 : 머신러닝 팀 프로젝트 (2) (0) | 2024.08.08 |
[25 일차] ABC 부트캠프 : 머신러닝 팀 프로젝트(1) (0) | 2024.08.07 |
[24 일차] ABC 부트캠프 : 머신러닝 (6) Code21, FaceDetecer (0) | 2024.08.06 |