#!/usr/bin/env python # coding: utf-8 # # 머신 러닝 교과서 3판 # # 15장 - 심층 합성곱 신경망으로 이미지 분류 (2/2) # **아래 링크를 통해 이 노트북을 주피터 노트북 뷰어(nbviewer.jupyter.org)로 보거나 구글 코랩(colab.research.google.com)에서 실행할 수 있습니다.** # # # # #
# 주피터 노트북 뷰어로 보기 # # 구글 코랩(Colab)에서 실행하기 #
# ### 목차 # - CNN을 사용해 얼굴 이미지의 성별 분류하기 # - CelebA 데이터셋 로드하기 # - 이미지 변환과 데이터 증식 # - CNN 성별 분류기 훈련 # - 요약 # In[1]: import tensorflow as tf import tensorflow_datasets as tfds import numpy as np from IPython.display import Image import matplotlib.pyplot as plt # ## CNN을 사용해 얼굴 이미지의 성별 분류하기 # ### CelebA 데이터셋 로드하기 # In[2]: # 아래 셀에서 CelebA 데이터를 다운로드할 때 에러가 발생하면 https://git.io/JL5GM 에서 # 또는 gdown 패키지를 사용해 역자의 드라이브에서 다운로드할 수 있습니다. import gdown gdown.download(id='1vDDFjykRuzEagaHwwIlv6hpaHDgEUcGo', output='img_align_celeba.zip') gdown.download(id='1XDTGJ2-QNMvkIdFwlvYAO28uc9v2d9PO', output='list_attr_celeba.txt') gdown.download(id='1V6zDszhMCokTZTh4EZ48RB9wslY_YSCl', output='list_eval_partition.txt') gdown.download(id='1iwam-RFy3tuh0yj29kK9tgEJOqGrH4_r', output='list_landmarks_align_celeba.txt') get_ipython().system('mkdir -p ~/tensorflow_datasets/downloads/manual') get_ipython().system('cp img_align_celeba.zip ~/tensorflow_datasets/downloads/manual') get_ipython().system('cp list_attr_celeba.txt ~/tensorflow_datasets/downloads/manual') get_ipython().system('cp list_eval_partition.txt ~/tensorflow_datasets/downloads/manual') get_ipython().system('cp list_landmarks_align_celeba.txt ~/tensorflow_datasets/downloads/manual') # In[3]: celeba_bldr = tfds.builder('celeb_a') celeba_bldr.download_and_prepare() celeba = celeba_bldr.as_dataset(shuffle_files=False) print(celeba.keys()) celeba_train = celeba['train'] celeba_valid = celeba['validation'] celeba_test = celeba['test'] def count_items(ds): n = 0 for _ in ds: n += 1 return n print('훈련 데이터셋: {}'.format(count_items(celeba_train))) print('검증 데이터셋: {}'.format(count_items(celeba_valid))) print('테스트 데이터셋: {}'.format(count_items(celeba_test))) # In[4]: celeba_train = celeba_train.take(16000) celeba_valid = celeba_valid.take(1000) print('훈련 데이터셋: {}'.format(count_items(celeba_train))) print('검증 데이터셋: {}'.format(count_items(celeba_valid))) # ### 이미지 변환과 데이터 증식 # In[5]: ## 5개 샘플을 가져옵니다 examples = [] for example in celeba_train.take(5): examples.append(example['image']) fig = plt.figure(figsize=(16, 8.5)) ## 1열: 바운딩 박스로 자르기 ax = fig.add_subplot(2, 5, 1) ax.imshow(examples[0]) ax = fig.add_subplot(2, 5, 6) ax.set_title('Crop to a \nbounding-box', size=15) img_cropped = tf.image.crop_to_bounding_box( examples[0], 50, 20, 128, 128) ax.imshow(img_cropped) ## 2열: (수평으로) 뒤집기 ax = fig.add_subplot(2, 5, 2) ax.imshow(examples[1]) ax = fig.add_subplot(2, 5, 7) ax.set_title('Flip (horizontal)', size=15) img_flipped = tf.image.flip_left_right(examples[1]) ax.imshow(img_flipped) ## 3열: 대비 조정 ax = fig.add_subplot(2, 5, 3) ax.imshow(examples[2]) ax = fig.add_subplot(2, 5, 8) ax.set_title('Adjust constrast', size=15) img_adj_contrast = tf.image.adjust_contrast( examples[2], contrast_factor=2) ax.imshow(img_adj_contrast) ## 4열: 명도 조정 ax = fig.add_subplot(2, 5, 4) ax.imshow(examples[3]) ax = fig.add_subplot(2, 5, 9) ax.set_title('Adjust brightness', size=15) img_adj_brightness = tf.image.adjust_brightness( examples[3], delta=0.3) ax.imshow(img_adj_brightness) ## 5열: 이미지 중앙 자르기 ax = fig.add_subplot(2, 5, 5) ax.imshow(examples[4]) ax = fig.add_subplot(2, 5, 10) ax.set_title('Centeral crop\nand resize', size=15) img_center_crop = tf.image.central_crop( examples[4], 0.7) img_resized = tf.image.resize( img_center_crop, size=(218, 178)) ax.imshow(img_resized.numpy().astype('uint8')) # plt.savefig('images/15_14.png', dpi=300) plt.show() # In[6]: tf.random.set_seed(1) fig = plt.figure(figsize=(14, 12)) for i,example in enumerate(celeba_train.take(3)): image = example['image'] ax = fig.add_subplot(3, 4, i*4+1) ax.imshow(image) if i == 0: ax.set_title('Orig.', size=15) ax = fig.add_subplot(3, 4, i*4+2) img_crop = tf.image.random_crop(image, size=(178, 178, 3)) ax.imshow(img_crop) if i == 0: ax.set_title('Step 1: Random crop', size=15) ax = fig.add_subplot(3, 4, i*4+3) img_flip = tf.image.random_flip_left_right(img_crop) ax.imshow(tf.cast(img_flip, tf.uint8)) if i == 0: ax.set_title('Step 2: Random flip', size=15) ax = fig.add_subplot(3, 4, i*4+4) img_resize = tf.image.resize(img_flip, size=(128, 128)) ax.imshow(tf.cast(img_resize, tf.uint8)) if i == 0: ax.set_title('Step 3: Resize', size=15) # plt.savefig('images/15_15.png', dpi=300) plt.show() # In[7]: def preprocess(example, size=(64, 64), mode='train'): image = example['image'] label = example['attributes']['Male'] if mode == 'train': image_cropped = tf.image.random_crop( image, size=(178, 178, 3)) image_resized = tf.image.resize( image_cropped, size=size) image_flip = tf.image.random_flip_left_right( image_resized) return (image_flip/255.0, tf.cast(label, tf.int32)) else: image_cropped = tf.image.crop_to_bounding_box( image, offset_height=20, offset_width=0, target_height=178, target_width=178) image_resized = tf.image.resize( image_cropped, size=size) return (image_resized/255.0, tf.cast(label, tf.int32)) ## testing: #item = next(iter(celeba_train)) #preprocess(item, mode='train') # In[8]: tf.random.set_seed(1) ds = celeba_train.shuffle(1000, reshuffle_each_iteration=False) ds = ds.take(2).repeat(5) ds = ds.map(lambda x:preprocess(x, size=(178, 178), mode='train')) fig = plt.figure(figsize=(15, 6)) for j,example in enumerate(ds): ax = fig.add_subplot(2, 5, j//2+(j%2)*5+1) ax.set_xticks([]) ax.set_yticks([]) ax.imshow(example[0]) # plt.savefig('images/15_16.png', dpi=300) plt.show() # In[9]: BATCH_SIZE = 32 BUFFER_SIZE = 1000 IMAGE_SIZE = (64, 64) steps_per_epoch = np.ceil(16000/BATCH_SIZE) print(steps_per_epoch) ds_train = celeba_train.map( lambda x: preprocess(x, size=IMAGE_SIZE, mode='train')) ds_train = ds_train.shuffle(buffer_size=BUFFER_SIZE).repeat() ds_train = ds_train.batch(BATCH_SIZE) ds_valid = celeba_valid.map( lambda x: preprocess(x, size=IMAGE_SIZE, mode='eval')) ds_valid = ds_valid.batch(BATCH_SIZE) # ### CNN 성별 분류기 훈련 # # * **전역 평균 풀링** # In[10]: Image(url='https://git.io/JL53N', width=800) # In[11]: model = tf.keras.Sequential([ tf.keras.layers.Conv2D( 32, (3, 3), padding='same', activation='relu'), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Dropout(rate=0.5), tf.keras.layers.Conv2D( 64, (3, 3), padding='same', activation='relu'), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Dropout(rate=0.5), tf.keras.layers.Conv2D( 128, (3, 3), padding='same', activation='relu'), tf.keras.layers.MaxPooling2D((2, 2)), tf.keras.layers.Conv2D( 256, (3, 3), padding='same', activation='relu'), ]) # In[12]: model.compute_output_shape(input_shape=(None, 64, 64, 3)) # In[13]: model.add(tf.keras.layers.GlobalAveragePooling2D()) model.compute_output_shape(input_shape=(None, 64, 64, 3)) # In[14]: model.add(tf.keras.layers.Dense(1, activation=None)) # In[15]: tf.random.set_seed(1) model.build(input_shape=(None, 64, 64, 3)) model.summary() # In[16]: model.compile(optimizer=tf.keras.optimizers.Adam(), loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), metrics=['accuracy']) history = model.fit(ds_train, validation_data=ds_valid, epochs=20, steps_per_epoch=steps_per_epoch) # In[17]: hist = history.history x_arr = np.arange(len(hist['loss'])) + 1 fig = plt.figure(figsize=(12, 4)) ax = fig.add_subplot(1, 2, 1) ax.plot(x_arr, hist['loss'], '-o', label='Train loss') ax.plot(x_arr, hist['val_loss'], '--<', label='Validation loss') ax.legend(fontsize=15) ax.set_xlabel('Epoch', size=15) ax.set_ylabel('Loss', size=15) ax = fig.add_subplot(1, 2, 2) ax.plot(x_arr, hist['accuracy'], '-o', label='Train acc.') ax.plot(x_arr, hist['val_accuracy'], '--<', label='Validation acc.') ax.legend(fontsize=15) ax.set_xlabel('Epoch', size=15) ax.set_ylabel('Accuracy', size=15) # plt.savefig('images/15_18.png', dpi=300) plt.show() # In[18]: ds_test = celeba_test.map( lambda x:preprocess(x, size=IMAGE_SIZE, mode='eval')).batch(32) results = model.evaluate(ds_test, verbose=0) print('테스트 정확도: {:.2f}%'.format(results[1]*100)) # In[19]: history = model.fit(ds_train, validation_data=ds_valid, epochs=30, initial_epoch=20, steps_per_epoch=steps_per_epoch) # In[20]: hist2 = history.history x_arr = np.arange(len(hist['loss'] + hist2['loss'])) fig = plt.figure(figsize=(12, 4)) ax = fig.add_subplot(1, 2, 1) ax.plot(x_arr, hist['loss']+hist2['loss'], '-o', label='Train Loss') ax.plot(x_arr, hist['val_loss']+hist2['val_loss'], '--<', label='Validation Loss') ax.legend(fontsize=15) ax = fig.add_subplot(1, 2, 2) ax.plot(x_arr, hist['accuracy']+hist2['accuracy'], '-o', label='Train Acc.') ax.plot(x_arr, hist['val_accuracy']+hist2['val_accuracy'], '--<', label='Validation Acc.') ax.legend(fontsize=15) plt.show() # In[21]: ds_test = celeba_test.map( lambda x:preprocess(x, size=IMAGE_SIZE, mode='eval')).batch(32) results = model.evaluate(ds_test, verbose=0) print('테스트 정확도: {:.2f}%'.format(results[1]*100)) # In[22]: ds = ds_test.unbatch().take(10) pred_logits = model.predict(ds.batch(10)) probas = tf.sigmoid(pred_logits) probas = probas.numpy().flatten()*100 fig = plt.figure(figsize=(15, 7)) for j,example in enumerate(ds): ax = fig.add_subplot(2, 5, j+1) ax.set_xticks([]); ax.set_yticks([]) ax.imshow(example[0]) if example[1].numpy() == 1: label='Male' else: label = 'Female' ax.text( 0.5, -0.15, 'GT: {:s}\nPr(Male)={:.0f}%'.format(label, probas[j]), size=16, horizontalalignment='center', verticalalignment='center', transform=ax.transAxes) # plt.savefig('images/15_19.png', dpi=300) plt.show() # In[23]: model.save('models/celeba-cnn.h5') # ... # # # ## 요약 # # ... # ## 부록: # # ### 초기 셔플링의 영향 # In[24]: import tensorflow as tf import tensorflow_datasets as tfds import numpy as np import pandas as pd ## MNIST dataset mnist_bldr = tfds.builder('mnist') mnist_bldr.download_and_prepare() datasets = mnist_bldr.as_dataset(shuffle_files=False) mnist_train_orig, mnist_test_orig = datasets['train'], datasets['test'] mnist_train = mnist_train_orig.map( lambda item: (tf.cast(item['image'], tf.float32)/255.0, tf.cast(item['label'], tf.int32))) mnist_test = mnist_test_orig.map( lambda item: (tf.cast(item['image'], tf.float32)/255.0, tf.cast(item['label'], tf.int32))) tf.random.set_seed(1) mnist_train = mnist_train.shuffle(buffer_size=10000, reshuffle_each_iteration=False) mnist_valid = mnist_train.take(100)#.batch(BATCH_SIZE) mnist_train = mnist_train.skip(100)#.batch(BATCH_SIZE) # `mnist_bldr.as_dataset(shuffle_files=False)`로 지정하면 데이터셋이 로드될 때 `mnist_valid`에 있느 레이블의 개수가 일정하게 유지됩니다. `shuffle_files` 매개변수 기본값이 `False`입니다. # In[25]: from collections import Counter def count_labels(ds): counter = Counter() for example in ds: counter.update([example[1].numpy()]) return counter print('레이블 개수:', count_labels(mnist_valid)) print('레이블 개수:', count_labels(mnist_valid)) # In[26]: ## MNIST dataset mnist_bldr = tfds.builder('mnist') mnist_bldr.download_and_prepare() datasets = mnist_bldr.as_dataset(shuffle_files=True) mnist_train_orig, mnist_test_orig = datasets['train'], datasets['test'] mnist_train = mnist_train_orig.map( lambda item: (tf.cast(item['image'], tf.float32)/255.0, tf.cast(item['label'], tf.int32))) mnist_test = mnist_test_orig.map( lambda item: (tf.cast(item['image'], tf.float32)/255.0, tf.cast(item['label'], tf.int32))) tf.random.set_seed(1) mnist_train = mnist_train.shuffle(buffer_size=10000, reshuffle_each_iteration=False) mnist_valid = mnist_train.take(100)#.batch(BATCH_SIZE) mnist_train = mnist_train.skip(100)#.batch(BATCH_SIZE) # `mnist_bldr.as_dataset(shuffle_files=True)`로 지정하면 데이터셋이 로드될 때 `mnist_valid`에 있느 레이블의 개수가 일정하게 유지되지 않습니다. # In[27]: from collections import Counter def count_labels(ds): counter = Counter() for example in ds: counter.update([example[1].numpy()]) return counter print('레이블 개수:', count_labels(mnist_valid)) print('레이블 개수:', count_labels(mnist_valid))