이미지 생성기를 활용해서 하위 디렉토리를 기준으로 파일을 자동으로 불러오고 레이블을 지정했다. 이를 활용해서 컨볼루션 신경망으로 말과 사람 분류기를 구축하는 방법을 알아보자.
[1] data download and data load
아래 코드를 실행하여 압축된 데이터세트 Horse-or-human.zip을 다운로드한다.
colab 에서 수행시 wget
명령어를 사용하고, 주피터노트북을 사용한다면 request
모듈을 사용해서 파일을 다운로드 한다.
[colab]
!wget https://storage.googleapis.com/tensorflow-1-public/course2/week3/horse-or-human.zip
[jupyter notebook]
import request
url = 'https://storage.googleapis.com/tensorflow-1-public/course2/week3/horse-or-human.zip'
file_name = "horse-or-human.zip"
response = requests.get(url)
with open(file_name, 'wb') as f:
f.write(response.content)
print('다운로드 완료')
추후 받은 알집 파일을 압축 해제 한다. horse-or-human 디렉토리가 생기는데 horse-or-human 디렉토리 안에 horse, human 디렉토리가 생기고 해당 디렉토리안에 파일들이 저장되어 있다.
import zipfile
# Unzip the dataset
local_zip = './horse-or-human.zip'
zip_ref = zipfile.ZipFile(local_zip, 'r')
zip_ref.extractall('./horse-or-human')
zip_ref.close()
.zip의 내용은 기본 디렉터리 ./horse-or-human으로 추출되며, 각 디렉터리에는 말과 인간 하위 디렉터리가 포함된다.
간단히 말해서, 훈련 세트는 신경망 모델에 '말은 이렇게 생겼어요', '사람은 이렇게 생겼어요'라고 알려주는 데 사용되는 데이터이다.
이 샘플에서 주목해야 할 한 가지는 이미지에 말이나 인간이라는 라벨을 명시적으로 지정하지 않는다는 것이다.
대신 ImageDataGenerator API를 사용하게 되며 이는 디렉터리 이름과 구조에 따라 이미지에 자동으로 레이블을 지정하도록 코딩된다.
예를 들어, '말' 디렉토리와 '인간' 디렉토리를 포함하는 '훈련' 디렉토리가 있습니다. ImageDataGenerator는 이미지에 적절하게 레이블을 지정하여 코딩 단계를 줄여준다.
이제 다음 각 디렉터리를 정의할 수 있다.
import os
# Directory with our training horse pictures
train_horse_dir = os.path.join('./horse-or-human/horses')
# Directory with our training human pictures
train_human_dir = os.path.join('./horse-or-human/humans')
print(train_horse_dir)
print(train_human_dir)
#output
./horse-or-human/horses
./horse-or-human/humans
이제 말과 인간 훈련 디렉토리에서 파일 이름이 어떻게 보이는지 확인한다.
train_horse_names = os.listdir(train_horse_dir)
print(train_horse_names[:10])
train_human_names = os.listdir(train_human_dir)
print(train_human_names[:10])
#output
['horse43-5.png', 'horse06-5.png', 'horse20-6.png', 'horse04-7.png', 'horse41-7.png', 'horse22-4.png', 'horse19-2.png', 'horse24-2.png', 'horse37-8.png', 'horse02-1.png']
['human17-22.png', 'human10-17.png', 'human10-03.png', 'human07-27.png', 'human09-22.png', 'human05-22.png', 'human02-03.png', 'human02-17.png', 'human15-27.png', 'human12-12.png']
말과 인간의 훈련(학습) 데이터에 해당하는 파일명 10개를 출력했다.
print('total training horse images:', len(os.listdir(train_horse_dir)))
print('total training human images:', len(os.listdir(train_human_dir)))
#output
total training horse images: 500
total training human images: 527
훈련 데이터에서의 말 데이터는 500개, 인간 데이터는 527개이다.
[2] data visualization
%matplotlib inline
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
nrows = 4
ncols = 4
pic_index = 0
fig = plt.gcf()
fig.set_size_inches(ncols * 4, nrows * 4)
pic_index += 8
next_horse_pix = [os.path.join(train_horse_dir, fname)
for fname in train_horse_names[pic_index-8:pic_index]]
next_human_pix = [os.path.join(train_human_dir, fname)
for fname in train_human_names[pic_index-8:pic_index]]
for i, img_path in enumerate(next_horse_pix+next_human_pix):
sp = plt.subplot(nrows, ncols, i + 1)
sp.axis('Off')
img = mpimg.imread(img_path)
plt.imshow(img)
plt.show()
[3] Building a Small Model from Scratch
이제 훈련할 모델 아키텍처를 정의할 수 있다.
1단계는 텐서플로우를 가져오는 것이다.
import tensorflow as tf
그런 다음 이전 예제에서와 같이 컨벌루션 레이어를 추가하고 최종 결과를 평면화하여 조밀하게 연결된 레이어에 공급한다.
이는 2클래스 분류 문제, 즉 이진 분류 문제이기 때문에 시그모이드 활성화로 네트워크를 종료하게 된다. 이렇게 하면 네트워크의 출력 값이 0과 1 사이의 단일 스칼라가 되어 현재 이미지가 클래스 0이 아닌 클래스 1일 확률을 인코딩한다.
model = tf.keras.models.Sequential([
tf.keras.layers.Conv2D(16, (3,3), activation='relu', input_shape=(300,300,3)),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(32, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Conv2D(64, (3,3), activation='relu'),
tf.keras.layers.MaxPooling2D(2,2),
tf.keras.layers.Flatten(),
tf.keras.layers.Dense(512, activation='relu'),
tf.keras.layers.Dense(1, activation='sigmoid')
])
model.summary()
[4] Model compile
model.compile(
loss='binary_crossentropy',
optimizer=tf.keras.optimizers.RMSprop(learning_rate=0.001),
metrics=['accuracy']
)
[5] data preprocessing
from tensorflow.keras.preprocessing.image import ImageDataGenerator
train_datagen = ImageDataGenerator(rescale=1/255)
train_generator = train_datagen.flow_from_directory(
'./horse-or-human/',
target_size=(300,300),
batch_size=128,
class_mode='binary'
)
# output
Found 1027 images belonging to 2 classes.
[6] model training
history = model.fit(
train_generator,
steps_per_epoch=8,
epochs = 15,
verbose=1
)
[7] model prediction
이제 모델을 사용하여 실제로 예측을 실행하는 방법이다.
파일 시스템에서 하나 이상의 파일을 선택하고 업로드한 후 모델을 통해 실행하여 개체가 말인지 인간인지 여부를 표시할 수 있다.
코랩에서 실행하는 것과, 주피터에서 실행하는 코드가 약간 다르다.
[colab]
import numpy as np
from google.colab import files
from tensorflow.keras.utils import load_img, img_to_array
uploaded = files.upload()
for fn in uploaded.keys():
# predicting images
path = '/content/' + fn
img = load_img(path, target_size=(300, 300))
x = img_to_array(img)
x /= 255
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
classes = model.predict(images, batch_size=10)
print(classes[0])
if classes[0]>0.5:
print(fn + " is a human")
else:
print(fn + " is a horse")
[jupyter notebook]
해당 디렉토리에 사람과 말 사진을 두고 모델 예측
path1 = './test_horse.jpg'
path2 = './test_human.jpg'
for path in [path1,path2]:
img = load_img(path, target_size=(300, 300))
x = img_to_array(img)
x /= 255
x = np.expand_dims(x, axis=0)
images = np.vstack([x])
classes = model.predict(images, batch_size=10)
print(classes[0])
if classes[0] > 0.5:
print("The image is predicted to contain a human.")
else:
print("The image is predicted to contain a horse.")
각각의 사진은 말 :
사람 :
을 사용했다.
[8] Visualizing Intermediate Representations
CNN이 어떤 기능을 학습했는지 파악하려면 입력이 모델을 통과하면서 어떻게 변환되는지 시각화하면 된다.
훈련 세트에서 임의의 이미지를 선택한 다음 각 행이 레이어의 출력이고 행의 각 이미지가 해당 출력 특징 맵의 특정 필터인 그림을 생성할 수 있다.
다양한 학습 이미지에 대한 중간 표현을 생성하려면 아래의 코드를 실행한다.
import numpy as np
import random
from tensorflow.keras.utils import img_to_array, load_img
# Define a new Model that will take an image as input, and will output
# intermediate representations for all layers in the previous model after
# the first.
successive_outputs = [layer.output for layer in model.layers[1:]]
visualization_model = tf.keras.models.Model(inputs = model.inputs, outputs = successive_outputs)
# Prepare a random input image from the training set.
horse_img_files = [os.path.join(train_horse_dir, f) for f in train_horse_names]
human_img_files = [os.path.join(train_human_dir, f) for f in train_human_names]
img_path = random.choice(horse_img_files + human_img_files)
img = load_img(img_path, target_size=(300, 300)) # this is a PIL image
x = img_to_array(img) # Numpy array with shape (300, 300, 3)
x = x.reshape((1,) + x.shape) # Numpy array with shape (1, 300, 300, 3)
# Scale by 1/255
x /= 255
# Run the image through the network, thus obtaining all
# intermediate representations for this image.
successive_feature_maps = visualization_model.predict(x)
# These are the names of the layers, so you can have them as part of the plot
layer_names = [layer.name for layer in model.layers[1:]]
# Display the representations
for layer_name, feature_map in zip(layer_names, successive_feature_maps):
if len(feature_map.shape) == 4:
# Just do this for the conv / maxpool layers, not the fully-connected layers
n_features = feature_map.shape[-1] # number of features in feature map
# The feature map has shape (1, size, size, n_features)
size = feature_map.shape[1]
# Tile the images in this matrix
display_grid = np.zeros((size, size * n_features))
for i in range(n_features):
x = feature_map[0, :, :, i]
x -= x.mean()
x /= x.std()
x *= 64
x += 128
x = np.clip(x, 0, 255).astype('uint8')
# Tile each filter into this big horizontal grid
display_grid[:, i * size : (i + 1) * size] = x
# Display the grid
scale = 20. / n_features
plt.figure(figsize=(scale * n_features, scale))
plt.title(layer_name)
plt.grid(False)
plt.imshow(display_grid, aspect='auto', cmap='viridis')
위에서 강조 표시된 픽셀이 특히 하단 그리드에서 점점 더 추상적이고 컴팩트한 표현으로 바뀌는 것을 볼 수있다.
다운스트림 표현은 네트워크가 주의를 기울이는 부분을 강조하기 시작하고 "활성화"되는 기능의 수가 점점 더 적어진다.
대부분은 0으로 설정되어 있는데, 이를 표현 희소성(representation sparsity)이라고 하며 딥러닝의 핵심 기능이다. 이러한 표현은 이미지의 원본 픽셀에 대한 정보를 점점 더 적게 전달하지만 이미지 클래스에 대한 정보는 점점 더 정교해진다.
Conv2D(또는 일반적으로 DNN)을 각 계층이 가장 유용한 기능을 필터링하는 정보 증류 파이프라인으로 생각할 수 있다.