Tensorflow 2.0 Tutorial ch8.1 - 텐서플로 허브

Page content

공지

  • 본 Tutorial은 교재 시작하세요 텐서플로 2.0 프로그래밍의 강사에게 국비교육 강의를 듣는 사람들에게 자료 제공을 목적으로 제작하였습니다.

  • 강사의 주관적인 판단으로 압축해서 자료를 정리하였기 때문에, 자세하게 공부를 하고 싶으신 분은 반드시 교재를 구매하실 것을 권해드립니다.

  • 본 교재 외에 강사가 추가한 내용에 대한 Reference를 확인하셔서, 추가적으로 학습하시는 것을 권유드립니다.

Tutorial

이전 강의가 궁금하신 분들은 아래에서 선택하여 추가 학습 하시기를 바랍니다.

I. 개요

딥러닝은 일반적인 개발과 다르게 사실 시간과의 싸움입니다. 코딩의 양에 비해, 결과물이 바로 나오지 않기 때문에 저녁에 모형을 돌리고 아침에 와서 확인하는 경우가 예사입니다.

특히, 딥러닝의 경우, 기본적으로 좋은 성능을 보이려면 네트워크는 수십 및 또는 수백개의 레이어를 쌓은 경우가 대부분이고, 당연히 늘어난 레이어의 수만큼 네트워크를 훈련시키는 데 필요한 훈련 시간도 증가하게 됩니다.

다행히, 딥러닝은 아직까지는 범용적으로 사용하는 상용단계라기보다는 연구, 실험, 그리고 이제서야 적용 단계에 있는 기술이기 때문에, 다른 IT기술보다는 개방적인 연구 환경을 갖추고 있습니다.

이렇게 연구자들이 자신이 만든 사전 훈련된 모델(pre-trained model)을 인터넷에 올려놓아 다른 사람들이 쉽게 내려받을 수 있도록 합니다.

이렇게 내려받은 모델을 그대로 사용할 수도 있고, 전이 학습(Transfer Learning)이나 신경 스타일 전이(Neural Style Transfer)처럼 다른 과제를 위해 재가공해서 사용합니다.

II. 텐서플로 허브 소개

텐서플로에서 제공하는 텐서플로 허브는 재사용 가능한 모델을 쉽게 이용할 수 있도록 하는 라이브러리입니다.

텐서플로허브 홈페이지에서 확인해봅니다.

텐서플로 허브에서 사전 훈련된 MobileNet 모델을 불러와서 학습하는 것을 시도합니다.

  • 참고로, 텐서플로 2.0을 사용하시는 분들은 굳이 별도로 텐서플로 허브를 설치할 필요가 없지만, 1.x 버전을 사용하신다면 별도로 아래와 같이 설치하시기를 바랍니다.
!pip install tensorflow-hub

(1) MobileNet 불러오기

MobileNet은 계산 부담이 큰 컨볼루션 신경망을 연산 성능이 제한된 모바일 환경에서도 작동 가능하도록 네트워크 구조를 경량화한 것입니다.

# 텐서플로 2 버전 선택
try:
    # %tensorflow_version only exists in Colab.
    %tensorflow_version 2.x
except Exception:
    pass
import tensorflow as tf
import numpy as np
import pandas as pd
import tensorflow_hub as hub
mobile_net_url = 'https://tfhub.dev/google/tf2-preview/mobilenet_v2/classification/4'
model = tf.keras.Sequential([
  hub.KerasLayer(handle=mobile_net_url, input_shape=(224, 224, 3), trainable=False)
])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
keras_layer (KerasLayer)     (None, 1001)              3540265   
=================================================================
Total params: 3,540,265
Trainable params: 0
Non-trainable params: 3,540,265
_________________________________________________________________
  • MobileNetImageNet에 존재하는 1,000 종류의 이미지를 분류할 수 있으며, 이 가운데 어떤 것에도 속하지 않는다고 판단될 때는 background에 해당하는 인덱스 0을 반환합니다.
from tensorflow.keras.applications import MobileNetV2

mobilev2 = MobileNetV2()
tf.keras.utils.plot_model(mobilev2)

png

  • MobileNetV2의 구조는 이와 같이 도식화 되어 있습니다. 도식화에 대한 구체적인 설명은 교재 243페이를 확인하시기를 바랍니다.

  • MobileNet의 성능을 평가하기 위해 이미지를 학습시켰을 때 얼마나 적합한 라벨로 분류하는지 알아봅니다.

(2) ImageNet 불러오기

ImageNet의 데이터 중 일부만 모아놓은 ImageNetV21를 사용합니다. ImageNetV2아마존 메커니컬 터크2를 이용해 다수의 참가자에게서 클래스 예측값을 받아서 선별한 데이터입니다.

여기서는 각 클래스에서 가장 많은 선택을 받은 이미지 10장씩을 모아높은 10,000장의 이미지가 포함된 TopImages데이터를 사용합니다.

Note: 현재 아래코드는 실행되지 않습니다. (2020년 12월 2일 확인)

import os
import pathlib

content_data_url = '/content/sample_data'
data_root_orig = tf.keras.utils.get_file('imagenetV2', 'https://s3-us-west-2.amazonaws.com/imagenetv2public/imagenetv2-topimages.tar.gz', cache_dir=content_data_url, extract=True)
data_root = pathlib.Path(content_data_url + '/datasets/imagenetv2-topimages')
print(data_root)
/content/sample_data/datasets/imagenetv2-topimages

tf.keras.utils.get_file() 함수로 ImageNetV2 데이터를 불러올 수 있습니다. 함수의 인수 중 extract=True로 지정했기 때문에, tar.gz 형식의 압축 파일이 자동으로 해제됭 구글 코랩 가상 머신에 저장됩니다.

디렉터리의 경로를 출력해서, 예제 데이터를 확인해 봅니다.

for idx, item in enumerate(data_root.iterdir()):
  print(item)
  if idx == 9:
    break
/content/sample_data/datasets/imagenetv2-topimages/462
/content/sample_data/datasets/imagenetv2-topimages/630
/content/sample_data/datasets/imagenetv2-topimages/687
/content/sample_data/datasets/imagenetv2-topimages/858
/content/sample_data/datasets/imagenetv2-topimages/172
/content/sample_data/datasets/imagenetv2-topimages/856
/content/sample_data/datasets/imagenetv2-topimages/407
/content/sample_data/datasets/imagenetv2-topimages/300
/content/sample_data/datasets/imagenetv2-topimages/374
/content/sample_data/datasets/imagenetv2-topimages/0

하위 디렉토리는 0~999까지 총 1,000개입니다.

(3) 라벨 텍스트 불러오기

라벨에 대한 숫자가 어떤 데이터를 뜻하는지에 대한 정보인 라벨 텍스트는 따로 불러와야 합니다. MobileNet에서 사용된 라벨은 tf.keras.utils.get_file()함수로 불러옵니다.

label_file = tf.keras.utils.get_file('label', 'https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt')
label_text = None
with open(label_file, 'r') as f:
    label_text = f.read().split('\n')[:-1]
print(len(label_text))
print(label_text[:10])
print(label_text[-10:])
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/ImageNetLabels.txt
16384/10484 [==============================================] - 0s 0us/step
1001
['background', 'tench', 'goldfish', 'great white shark', 'tiger shark', 'hammerhead', 'electric ray', 'stingray', 'cock', 'hen']
['buckeye', 'coral fungus', 'agaric', 'gyromitra', 'stinkhorn', 'earthstar', 'hen-of-the-woods', 'bolete', 'ear', 'toilet tissue']

(4) 이미지 확인 시각화

그럼, 이제 이미지를 확인해봅니다. matplotlib.pyplot을 이용해서 이미지를 출력합니다.

all_image_paths = list(data_root.glob('*/*'))
all_image_paths = [str(path) for path in all_image_paths]

# 이미지를 랜덤하게 섞습니다. 
random.shuffle(all_image_paths)
image_count = len(all_image_paths)

print('image_count:', image_count)
image_count: 10000

이미지의 개수가 10,000인 것을 확인합ㄴ디ㅏ.

import PIL.Image as Image
import matplotlib.pyplot as plt
import random

plt.figure(figsize=(12, 12))
for c in range(9):
  image_path = random.choice(all_image_paths)
  plt.subplot(3, 3, c+1)
  plt.imshow(plt.imread(image_path))
  idx = int(image_path.split('/')[-2]) + 1
  plt.title(str(idx) + ', ' + label_text[idx])
  plt.axis('off')

plt.show()

png

10,000장의 이미지 중 랜덤하게 뽑은 9장의 이미지이기 때문에 독자와는 조금 다를 수 있습니다. 전반적으로 사진들이 잘 분류가 되고 있음을 확인할 수 있습니다.

위 코드에서 한가지 유의할 점은 라벨 텍스트는 background가 포함되어 있어서, 총 1,001개의 텍스트가 있지만, 실제 데이터의 라벨은 0에서 999까지의, 1000개라는 점입니다. 이러한 차이를 무마하기 위해서, 코드 idx = int(image_path.split('/')[-2]) + 1에서 파일 경로의 라벨 디렉토리에 해당하는 부분을 정수로 변환한 다음 1을 더해 첫번째부터 1,000번째까지의 라벨 텍스트와 동일한 값을 가리킵니다.

(5) 모형 성능 테스트

이제 이 이미지들을 MobileNet이 얼마나 잘 분류하는지 확인해봅니다. 전통적으로 ImageNet 대회에서는 예측하는 값 중 상위 5개 이내에 데이터의 실제 분류가 포함되어 있으면 정답으로 인정하는 Top-5 정확도를 분류 정확도로 측정합니다.

import cv2

top_1 = 0
top_5 = 0
for image_path in all_image_paths:
  img = cv2.imread(image_path)
  img = cv2.resize(img, dsize=(224, 224))
  img = img / 255.0
  img = np.expand_dims(img, axis=0)
  top_5_predict = model.predict(img)[0].argsort()[::-1][:5]
  idx = int(image_path.split('/')[-2])+1
  if idx in top_5_predict:
    top_5 += 1
    if top_5_predict[0] == idx:
      top_1 += 1

print('Top-5 correctness:', top_5 / len(all_image_paths) * 100, '%')
print('Top-5 correctness:', top_1 / len(all_image_paths) * 100, '%')
Top-5 correctness: 83.84 %
Top-5 correctness: 59.45 %

Top-5 정확도는 83.84%, Top-1 정확도는 59.45%가 나옵니다. 논문에서는 정확도를 높이기 위해 이미지에 여러 가지 전처리 기법을 사용하지만 여기서는 특별한 방법을 사용하지 않았기 때문에 정확도가 약간 낮게 나옵니다.

cv2 모듈은 OpenCV(Open Source Computer Vision) 라이브러리입니다. 이미지를 메모리에 불러오고 크기를 조정하는 등의 작업을 편하게 해줍니다.

top_5_predict = model.predict(img)[0].argsort()[::-1][:5]

여기에서 argsort()는 인덱스를 정렬합니다. 그런데, 우리가 원하는 것은 예측확률이 높은 순서이기 때문에 array[::-1]로 반전시키비다. 그 다음에 앞에서부터 5번째까지의 값을 array[:5]로 잘라서 top_5_predict에 저장합니다.

소스코드를 보면 더 쉽게 이해가 갈 것입니다.

a = np.array([99, 32, 5, 64, 20])
arg = np.argsort(a)
print(arg)
print(np.sort(a))
print(a[arg])
print(a[arg][::-1])
print(a[arg][::-1][:2])
[2 4 1 3 0]
[ 5 20 32 64 99]
[ 5 20 32 64 99]
[99 64 32 20  5]
[99 64]

참고로 교재에서는 print(a[arg][::-1])print(a[arg][::-1][:2])의 소스코드는 독자들의 이해를 더 구하기 위해 추가하였습니다.

(6) 분류 라벨 확인

MobileNet이 분류하는 라벨을 실제로 확인하고 Top-5예측을 표시합니다.

plt.figure(figsize=(16, 16))

def softmax(x):
    e_x = np.exp(x - np.max(x))
    return e_x / e_x.sum(axis=0)
  
for c in range(3):
    image_path = random.choice(all_image_paths)
    
    # 이미지 표시
    plt.subplot(3,2,c*2+1)
    plt.imshow(plt.imread(image_path))
    idx = int(image_path.split('/')[-2]) + 1
    plt.title(str(idx) + ', ' + label_text[idx])
    plt.axis('off')
    
    # 예측값 표시
    plt.subplot(3,2,c*2+2)
    img = cv2.imread(image_path)
    img = cv2.resize(img, dsize=(224, 224))
    img = img / 255.0
    img = np.expand_dims(img, axis=0)
    
    # MobileNet을 이용한 예측
    logits = model.predict(img)[0]
    prediction = softmax(logits)
    
    # 가장 높은 확률의 예측값 5개를 뽑음
    top_5_predict = prediction.argsort()[::-1][:5]
    labels = [label_text[index] for index in top_5_predict]
    color = ['gray'] * 5
    if idx in top_5_predict:
        color[top_5_predict.tolist().index(idx)] = 'green'
    color = color[::-1]
    plt.barh(range(5), prediction[top_5_predict][::-1] * 100, color=color)
    plt.yticks(range(5), labels[::-1])

png

랜덤하게 뽑은 세 개의 이미지 중 3개는 모두 Top-1 예측을 맞췄습니다. 이렇게 별도의 훈련 과정 없이 미리 훈련된 모델을 텐서플로 허브에서 불러오는 것으로 네트워크를 그대로 사용할 수 있습니다.

III. 연습 파일

VI. Reference

김환희. (2020). 시작하세요! 텐서플로 2.0 프로그래밍: 기초 이론부터 실전 예제까지 한번에 끝내는 머신러닝, 딥러닝 핵심 가이드. 서울: 위키북스.

Karpathy, A. (2015). The Unreasonable Effectiveness of Recurrent Neural Networks. Retrieved April 26, 2020, from http://karpathy.github.io/2015/05/21/rnn-effectiveness/


  1. https://github.com/modestyachts/imageNetV2 ↩︎

  2. 사람의 수작업이 필요한 이미지 라벨링 등을 위해 비교적 저렴한 가격으로 서비스 구매자와 제공자를 연결해주는 작업 플랫폼 ↩︎