5-tips-improve-knowledge-transfer-elearning-e1475138920743

Transfer Learning

대부분의 경우, 전체 네트워크를 새로 학습하는 것은 시간, 자원, 노력등의 낭비를 초래합니다. 예를 들어 ImageNet과 같은 대규모 데이터셋에 대한 최신 ConvNets에 대한 훈련은 여러 GPU에서 몇 주가 걸립니다. 대신, 대부분의 사람들은 미리 훈련된 네트워크를 이용하여 feature extractor로 사용하거나 fine-tuning을 하기 위한 초기 네트워크로 사용합니다. 이 포스트에서는 미리 훈련된 VGGNet을 이용해서 꽃을 분류하는 Classifier를 만들어보겠습니다.

VGGNet

vgg16

VGGNetImageNet dates에 대해 학습되어있는 네트워크입니다. VGGNet은 간단하고 우수한 성능을 갖추고 있어 ImageNet Competetion에서 2위를 차지했습니다.

0_mH17FEz4LHpaJrPb

여기서의 아이디어는 우리가 모든 Convolution Layer를 유지하되, 최종적인 Fully-Connected Layer를 우리가 원하는 Classifier로 만들겁니다.이렇게 하면 VGGNet을 이미지용 feature extractor로 사용해 간단한 분류자를 쉽게 학습할 수 있습니다. 지금부터 ReLU를 이용한 임계값 설정을 포함하여 4096개의 node가 완전히 연결된 첫 번째 계층을 살펴보겠습니다. 이러한 값을 각 이미지에 대한 코드로 사용하여 해당 코드 위에 classifier를 작성할 수 있습니다.

만약 transfer learning에 대해 더 알고 싶다면 the CS231n course notes를 읽어보세요.

Pretrained VGGNet

우리는 미리 학습되어 있는 VGG 네트워크를 사용하기로 했습니다. VGG Network가 tensorflow에 적용된 버전의 코드는 이 곳에 업로드 되어 있습니다. 이 notebook을 clone한 후 우리의 Classifier를 만들어보도록 하겠습니다.

from urllib.request import urlretrieve
from os.path import isfile, isdir
from tqdm import tqdm

vgg_dir = 'tensorflow_vgg/'
# Make sure vgg exists
if not isdir(vgg_dir):
    raise Exception("VGG directory doesn't exist!")

class DLProgress(tqdm):
    last_block = 0

    def hook(self, block_num=1, block_size=1, total_size=None):
        self.total = total_size
        self.update((block_num - self.last_block) * block_size)
        self.last_block = block_num

if not isfile(vgg_dir + "vgg16.npy"):
    with DLProgress(unit='B', unit_scale=True, miniters=1, desc='VGG16 Parameters') as pbar:
        urlretrieve(
            'https://s3.amazonaws.com/content.udacity-data.com/nd101/vgg16.npy',
            vgg_dir + 'vgg16.npy',
            pbar.hook)
else:
    print("Parameter file already exists!")
    Parameter file already exists!

Flower power

여기서는 VGGNet을 사용하여 꽃의 이미지를 분류할 것입니다. 꽃 데이터 세트를 얻으려면 아래 코드를 이용합니다. 이 데이터셋은 TensorFlow inception tutorial에서 제공됩니다.

import tarfile

dataset_folder_path = 'flower_photos'

class DLProgress(tqdm):
    last_block = 0

    def hook(self, block_num=1, block_size=1, total_size=None):
        self.total = total_size
        self.update((block_num - self.last_block) * block_size)
        self.last_block = block_num

if not isfile('flower_photos.tar.gz'):
    with DLProgress(unit='B', unit_scale=True, miniters=1, desc='Flowers Dataset') as pbar:
        urlretrieve(
            'http://download.tensorflow.org/example_images/flower_photos.tgz',
            'flower_photos.tar.gz',
            pbar.hook)

if not isdir(dataset_folder_path):
    with tarfile.open('flower_photos.tar.gz') as tar:
        tar.extractall()
        tar.close()

ConvNet Codes

아래에서는 데이터셋의 모든 이미지를 실행하고 각 이미지에 대한 코드를 가져옵니다. 즉, VGGNet corvolution 레이어를 통해 영상을 실행하고 첫 번째 완전히 연결된 레이어의 값을 기록합니다. 그런 다음 Classifier를 만들 때 나중에 파일에 쓸 수 있습니다.

여기서는 텐서플로우_vgg의 vg16 모듈을 사용하고 있습니다. 네트워크는 224×224×3224×224×3 크기의 이미지를 입력으로 사용합니다. 그리고 5 세트의 Convolution Layer들이 있습니다. 여기에 구현된 네트워크에는 다음과 같은 구조가 있습니다. (the source code)

self.conv1_1 = self.conv_layer(bgr, "conv1_1")
self.conv1_2 = self.conv_layer(self.conv1_1, "conv1_2")
self.pool1 = self.max_pool(self.conv1_2, 'pool1')

self.conv2_1 = self.conv_layer(self.pool1, "conv2_1")
self.conv2_2 = self.conv_layer(self.conv2_1, "conv2_2")
self.pool2 = self.max_pool(self.conv2_2, 'pool2')

self.conv3_1 = self.conv_layer(self.pool2, "conv3_1")
self.conv3_2 = self.conv_layer(self.conv3_1, "conv3_2")
self.conv3_3 = self.conv_layer(self.conv3_2, "conv3_3")
self.pool3 = self.max_pool(self.conv3_3, 'pool3')

self.conv4_1 = self.conv_layer(self.pool3, "conv4_1")
self.conv4_2 = self.conv_layer(self.conv4_1, "conv4_2")
self.conv4_3 = self.conv_layer(self.conv4_2, "conv4_3")
self.pool4 = self.max_pool(self.conv4_3, 'pool4')

self.conv5_1 = self.conv_layer(self.pool4, "conv5_1")
self.conv5_2 = self.conv_layer(self.conv5_1, "conv5_2")
self.conv5_3 = self.conv_layer(self.conv5_2, "conv5_3")
self.pool5 = self.max_pool(self.conv5_3, 'pool5')

self.fc6 = self.fc_layer(self.pool5, "fc6")
self.relu6 = tf.nn.relu(self.fc6)

그래서 우리가 원하는 것은 ReLUd (self.relu6) 이후의 첫 번째 Fully connected layer 입니다. 네트워크를 구축하기 위해 우리는 아래 코드를 사용합니다.

with tf.Session() as sess:
    vgg = vgg16.Vgg16()
    input_ = tf.placeholder(tf.float32, [None, 224, 224, 3])
    with tf.name_scope("content_vgg"):
        vgg.build(input_)

이렇게 하면 vgg object를 만든 다음 ‘vg.build(input_)’로 그래프를 만듭니다. 그런 다음 layer로부터 값을 가져옵니다.

feed_dict = {input_: images}
codes = sess.run(vgg.relu6, feed_dict=feed_dict)

import os

import numpy as np
import tensorflow as tf

from tensorflow_vgg import vgg16
from tensorflow_vgg import utils
data_dir = 'flower_photos/'
contents = os.listdir(data_dir)
classes = [each for each in contents if os.path.isdir(data_dir + each)]

이제 이미지를 VGG Network에 통과시켜 feature를 추출하고 저장하겠습니다. 이 추출한 feature들을 code라고 부르겠습니다.

# Batch Size를 정합니다. 만약 GPU 메모리가 있다면 높입니다.
batch_size = 128
codes_list = []
labels = []
batch = []

codes = None

with tf.Session() as sess:

    # TODO: Build the vgg network here
    vgg = vgg16.Vgg16()
    input_ = tf.placeholder(tf.float32, [None,224,224,3])

    with tf.name_scope("content_vgg"):
        vgg.build(input_)

    for each in classes:
        print("Starting {} images".format(each))
        class_path = data_dir + each
        files = os.listdir(class_path)
        for ii, file in enumerate(files, 1):
            # ii=1, file= file_name01
            # ii=2, file= file_name02 ...
            #
            # 이미지를 현재 Batch에 추가합니다.
            # utils.load_image는 input image를 중간부분부터 잘라줍니다.
            img = utils.load_image(os.path.join(class_path, file))
            batch.append(img.reshape((1, 224, 224, 3)))
            labels.append(each)

            # Batch를 네트워크에 실행시킵니다.
            if ii % batch_size == 0 or ii == len(files):

                # Batch를 VGG Network에 통과시킵니다.
                images = np.concatenate(batch)

                # TODO: relu6 레이어로부터 값을 얻습니다.

                feed_dict = {input_: images}
                codes_batch = sess.run(vgg.relu6, feed_dict=feed_dict)

                # Here I'm building an array of the codes
                if codes is None:
                    codes = codes_batch
                else:
                    codes = np.concatenate((codes, codes_batch))

                # 다음 batch를 만들기 위해 reset합니다.
                batch = []
                print('{} images processed'.format(ii))
    npy file loaded
    build model started
    build model finished: 0s
    Starting dandelion images
    128 images processed
    256 images processed
    384 images processed
    ...
# write codes to file
with open('codes', 'w') as f:
    codes.tofile(f)

# write labels to file
import csv
with open('labels', 'w') as f:
    writer = csv.writer(f, delimiter='\n')
    writer.writerow(labels)

Building the Classifier

이제 모든 이미지에 대한 코드가 있으므로 그 위에 간단한 classifier를 만들 수 있습니다. 그 코드는 단순한 MLP와 같습니다. 아래에서는 대부분의 작업을 수행하도록 하겠습니다.

# read codes and labels from file
import csv

with open('labels') as f:
    reader = csv.reader(f, delimiter='\n')
    labels = np.array([each for each in reader if len(each) > 0]).squeeze()
with open('codes') as f:
    codes = np.fromfile(f, dtype=np.float32)
    codes = codes.reshape((len(labels), -1))

Data prep

평소처럼 이제 라벨을 한 번 인코딩하고 validation/test 세트를 작성해야 합니다. 우선, 라벨을 만들겠습니다. one-hot encoded vector를 만들기 위해 scikit-learn의 LabelBinarizer를 사용합니다.

from sklearn import preprocessing

 # Your one-hot encoded labels array here
lb = preprocessing.LabelBinarizer()
labels_vecs = lb.fit_transform(labels)
print(labels_vecs)
    [[0 1 0 0 0]
     [0 1 0 0 0]
     [0 1 0 0 0]
     ...,
     [0 0 0 1 0]
     [0 0 0 1 0]
     [0 0 0 1 0]]

이제 training, validation, and test 세트를 생성하고자 합니다. 여기서 주목할 중요한 점은 우리의 라벨과 데이터가 아직 랜덤화되지 않았다는 것입니다. Validation 및 Test 세트에 모든 클래스의 데이터가 포함되도록 데이터를 섞어야 합니다. 그렇지 않으면 모두 한 클래스인 테스트 세트로 끝날 수 있습니다. 또한 일반적으로 각 작은 집합에는 전체 데이터 집합과 동일한 클래스의 분포가 있는지 확인해야 합니다. 가장 쉬운 방법은 scikit-learn의 StratifiedShuffleSplit 을 사용하는 것입니다.

StratifiedShuffleSplit를 사용하면 splitter를 다음과 같이 만들 수 있습니다.

ss = StratifiedShuffleSplit(n_splits=1, test_size=0.2)

그리고 data를 다음과 같이 split합니다.

splitter = ss.split(x, y)

s.split은 indice의 생성기를 반환합니다. 인덱스를 배열로 전달하여 split 세트를 가져올 수 있습니다. 생성기라는 것은 생성기를 반복하거나 다음(Splitter)을 사용하여 인덱스를 구해야 한다는 것을 의미합니다. 사용자 가이드documentation을 읽어 보세요.


from sklearn.model_selection import StratifiedShuffleSplit

#객체 생성: StratifiedShuffleSplit
ss = StratifiedShuffleSplit(n_splits=1, test_size=0.2)
train_index, val_index = next(ss.split(codes, labels_vecs))
val_index, test_index = val_index[:int(len(val_index)/2)], val_index[:int(len(val_index)/2)]

train_x, train_y = codes[train_index], labels_vecs[train_index]  
val_x, val_y =codes[val_index], labels_vecs[val_index]
test_x, test_y = codes[test_index], labels_vecs[test_index]  
print("Train shapes (x, y):", train_x.shape, train_y.shape)
print("Validation shapes (x, y):", val_x.shape, val_y.shape)
print("Test shapes (x, y):", test_x.shape, test_y.shape)
    Train shapes (x, y): (2936, 4096) (2936, 5)
    Validation shapes (x, y): (367, 4096) (367, 5)
    Test shapes (x, y): (367, 4096) (367, 5)

Train, Test, Validation Set으로 잘 나누어졌네요.

Classifier layers

자, 다시한번 복기하겠습니다. 먼저 image들을 VGGNet을 통과시켜 4096D Vector로 된 feature를 추출했습니다. 이걸 code라고 불렀습니다. 이 feature를 input으로 하는 fully connected layer에 통과시키면 최종적으로 class에 대한 output이 나오게 됩니다.

우리는 fully-connected layer를 학습해 flower-classifier를 만들기 전에, 먼저 image들을 VGGNet에 통과시켜 feature들을 전부 뽑아놓고 저장했습니다. 이제 이렇게 저장한 Convolution code들을 가지고 있으므로 이제 fully connected layer로 부터 Classifier를 만들면 됩니다. input으로 code를 사용하고 target으로 image label을 사용합니다. 따라서 이것은 전형적인 Multi-layer Perceptron Neural Network가 됩니다.


inputs_ = tf.placeholder(tf.float32, shape=[None, codes.shape[1]])
labels_ = tf.placeholder(tf.int64, shape=[None, labels_vecs.shape[1]])

# TODO: Classifier layers and operations
fc = tf.contrib.layers.fully_connected(inputs_, 256)

# output layer logits
logits = tf.contrib.layers.fully_connected(fc, labels_vecs.shape[1], activation_fn=None)
# cross entropy loss
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(labels=labels_, logits=logits)
cost = tf.reduce_mean(cross_entropy)
# training optimizer
optimizer = tf.train.AdamOptimizer().minimize(cost)

# Operations for validation/test accuracy
predicted = tf.nn.softmax(logits)
correct_pred = tf.equal(tf.argmax(predicted, 1), tf.argmax(labels_, 1))
accuracy = tf.reduce_mean(tf.cast(correct_pred, tf.float32))

Batches!

Batch를 하기 위한 간단한 방법을 짰습니다. 이 코드는 모든 데이터를 포함하도록 작성했습니다. 때로는 데이터가 완전히 Batch되었는지 확인하기 위해 끝에 데이터를 몇 개 던질 수도 있습니다. 나머지 데이터를 포함하도록 마지막 배치를 확장하겠습니다.

def get_batches(x, y, n_batches=10):
    """ Return a generator that yields batches from arrays x and y. """
    batch_size = len(x)//n_batches

    for ii in range(0, n_batches*batch_size, batch_size):
        # If we're not on the last batch, grab data with size batch_size
        if ii != (n_batches-1)*batch_size:
            X, Y = x[ii: ii+batch_size], y[ii: ii+batch_size]
        # On the last batch, grab the rest of the data
        else:
            X, Y = x[ii:], y[ii:]
        # I love generators
        yield X, Y

Training

이제, 학습을 시작합니다.

saver = tf.train.Saver()
epochs = 10
iteration = 0

with tf.Session() as sess:

    # TODO: Your training code here
    sess.run(tf.global_variables_initializer())
    for e in range(epochs):
        for x,y in get_batches(train_x, train_y):
            feed = {inputs_:x, labels_:y}
            loss, _ = sess.run([cost, optimizer], feed_dict=feed )
            print("Epoch: {}/{}".format(e+1, epochs),
                  "Iteration: {}".format(iteration),
                  "Training loss: {:.5f}".format(loss))
            iteration += 1

            if iteration % 5 == 0:
                feed = {inputs_ : val_x, labels_: val_y}
                val_acc = sess.run(accuracy, feed_dict=feed)
                print("Epoch: {}/{}".format(e+1, epochs),
                      "Iteration: {}".format(iteration),
                      "Validation Acc: {:.4f}".format(val_acc))

    saver.save(sess, "checkpoints/flowers.ckpt")
    Epoch: 1/10 Iteration: 0 Training loss: 8.68141
    Epoch: 1/10 Iteration: 1 Training loss: 14.07866
    Epoch: 1/10 Iteration: 2 Training loss: 16.56735
    ...
    Epoch: 10/10 Iteration: 98 Training loss: 0.02889
    Epoch: 10/10 Iteration: 99 Training loss: 0.02861
    Epoch: 10/10 Iteration: 100 Validation Acc: 0.9183

Testing

잘 학습되었는지 test해보도록 하겠습니다.

with tf.Session() as sess:
    saver.restore(sess, tf.train.latest_checkpoint('checkpoints'))

    feed = {inputs_: test_x,
            labels_: test_y}
    test_acc = sess.run(accuracy, feed_dict=feed)
    print("Test accuracy: {:.4f}".format(test_acc))
    INFO:tensorflow:Restoring parameters from checkpoints/flowers.ckpt
    Test accuracy: 0.9183

이제 실제 이미지를 가지고 분류를 해보겠습니다.

%matplotlib inline

import matplotlib.pyplot as plt
from scipy.ndimage import imread

test_img_path = 'flower_photos/roses/10894627425_ec76bbc757_n.jpg'
test_img = imread(test_img_path)
plt.imshow(test_img)
    <matplotlib.image.AxesImage at 0x7faffb0991d0>

output_29_1

with tf.Session() as sess:
    img = utils.load_image(test_img_path)
    img = img.reshape((1, 224, 224, 3))

    feed_dict = {input_: img}
    code = sess.run(vgg.relu6, feed_dict=feed_dict)

saver = tf.train.Saver()
with tf.Session() as sess:
    saver.restore(sess, tf.train.latest_checkpoint('checkpoints'))

    feed = {inputs_: code}
    prediction = sess.run(predicted, feed_dict=feed).squeeze()

plt.barh(np.arange(5), prediction)
_ = plt.yticks(np.arange(5), lb.classes_)

output_33_0

사진에는 튤립과 장미가 있다고 하네요. 잘 분류됬습니다!

Comments

Eungbean Lee's Picture

About Eungbean Lee

Lee is a Student, Programmer, Engineer, Designer and a DJ

Seoul, South Korea https://eungbean.github.io