인공지능/CNN

이미지 증식과 분석, 사전학습된 것 적용시키기 (전이 학습)

쿠와와 2020. 12. 9. 19:36
# Day_25_01_DogsAndCats.py
import tensorflow as tf
import numpy as np
import os
import time
import pickle
import matplotlib.pyplot as plt
from tensorflow.keras import optimizers
from tensorflow.keras.preprocessing.image import ImageDataGenerator as IDG


# 베이스 라인 만들 것임
# 이미지 증식 후 분석
# slim 에서 본거 같이 사전학습된 것으로 우리꺼 예측해볼 것 (전이 학습)

def get_model_name(version):
    filename = 'dogcat_small_{}.h5'.format(version)     # h5 keras에서 사용하는 확장자
    return os.path.join('dogs_and_cats_model', filename)


def get_history_name(version):
    filename = 'dogcat_small_{}.history'.format(version)     # h5 keras에서 사용하는 확장자
    return os.path.join('dogs_and_cats_model', filename)


def show_history(history, version):
    plt.subplot(1, 2, 1)        # 2개로 나눠서 쓸거임
    plt.plot(history['loss'], 'r', label='train')
    plt.plot(history['val_loss'], 'g', label='valid')
    plt.title('loss {}'.format(version))
    plt.legend()

    plt.subplot(1, 2, 2)     # acc
    plt.plot(history['acc'], 'r', label='train')
    plt.plot(history['val_acc'], 'g', label='valid')
    plt.title('acc {}'.format(version))
    plt.legend()

    plt.show()


def save_history(history, version):
    with open(get_history_name(version), 'wb') as f:    # 쓰기 모드로 파일 염
        pickle.dump(history.history, f)         # history 에 접근


def load_history(version):
    with open(get_history_name(version), 'rb') as f:    # 쓰기 모드로 파일 염
        history = pickle.load(f)         # history 에 접근
        show_history(history, version)


def load_model(version):
    model = tf.keras.models.load_model(get_model_name(version))
    # model.summary()

    test_gen = IDG(rescale=1/255)

    test_flow = test_gen.flow_from_directory(
        'dogs_and_cats/small/test',
        batch_size=1000,            # 검사 데이터 전체
        target_size=(150, 150),  # resize 기능을 제공해줌
        class_mode='binary'  # sigmoid 어울리는 모드로 변환
    )

    # print('acc :', model.evaluate_generator(test_flow, steps=1, verbose=0))

    x_test, y_test = test_flow.next()
    print('acc :', model.evaluate(x_test, y_test, verbose=0))


def model_1_baseline():
    # 이미지 만들어내는 함수
    # 이미지를 만들어냄 방법만 알고 있고 소스를 다시 연결해줘야함
    data_gen = IDG(rescale=1/255)
    batch_size = 32
    # 3가지 방법 -> (x,y), pandas, 폴더에 있는것
    # 우리 폴더 안에 있는 25000개의 데이터를 무제한으로 가져옴 -> 조심해야함
    train_flow = data_gen.flow_from_directory(
        'dogs_and_cats/small/train',
        batch_size=batch_size,
        target_size=(150, 150),         # resize 기능을 제공해줌
        class_mode='binary'             # sigmoid 어울리는 모드로 변환
    )
    # class_mode    "categorical", "binary", "sparse"

    valid_flow = data_gen.flow_from_directory(
        'dogs_and_cats/small/train',
        batch_size=batch_size,
        target_size=(150, 150),         # resize 기능을 제공해줌
        class_mode='binary'             # sigmoid 어울리는 모드로 변환
    )
    # 여기까지 x, y 만든셈임

    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Input(shape=[150, 150, 3]))       # 224 써도 되지만 하드웨어 문제 -> 150만 쓰자

    model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))
    model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))
    model.add(tf.keras.layers.Conv2D(128, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))
    model.add(tf.keras.layers.Conv2D(128, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))

    # 3차원에서 2차원
    model.add(tf.keras.layers.Flatten())

    model.add(tf.keras.layers.Dense(512, activation='relu'))
    model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

    model.compile(optimizer=tf.keras.optimizers.RMSprop(0.0001),
                  loss=tf.keras.losses.binary_crossentropy,
                  metrics=['acc'])
    history = model.fit_generator(
        train_flow,
        epochs=10,
        steps_per_epoch=2000 // batch_size,     # 정확하게 나눠 떨어지지 않으면 자투리는 사용하지 않겠다
        validation_data=valid_flow,
        # validation_steps=batch_size,
        verbose=2,
    )

    model.save(get_model_name(version=1))
    save_history(history, version=1)


def model_2_augmentation():
    # 이미지 만들어내는 함수
    # 이미지를 만들어냄 방법만 알고 있고 소스를 다시 연결해줘야함
    # featurewise_center=False,
    #                samplewise_center=False,
    #                featurewise_std_normalization=False,
    #                samplewise_std_normalization=False,
    #                zca_whitening=False,                   # 공부해보기 이미지 preprocessing 할 때 씀
    #                zca_epsilon=1e-6,
    #                rotation_range=0,
    #                width_shift_range=0.,
    #                height_shift_range=0.,
    #                brightness_range=None,
    #                shear_range=0.,
    #                zoom_range=0.,
    #                channel_shift_range=0.,
    #                fill_mode='nearest',
    #                cval=0.,
    #                horizontal_flip=False,
    #                vertical_flip=False,
    #                rescale=None,
    #                preprocessing_function=None,
    #                data_format=None,
    #                validation_split=0.0,
    #                dtype=None):
    train_gen = IDG(        # 이미지 다양하게 증식
        rescale=1/255,
        horizontal_flip=True,       # 수직뒤집기, 수평뒤집기중 수집 뒤집기 사용할 것임
        width_shift_range=0.1,      # 10% 로 이동가능하다.
        height_shift_range=0.1,
        shear_range=0.1,            # 이미지 변형
        zoom_range=0.1,
        rotation_range=20,
    )
    valid_gen = IDG(
        rescale=1/255
    )
    # 이미지 증식의 이유 -> 다양한 데이터로 학습하기 위해 -> 검색은 데이터 증식할 필요 없다.
    # 그렇기 때문에 2개로 generation 나눔

    batch_size = 32
    # 3가지 방법 -> (x,y), pandas, 폴더에 있는것
    # 우리 폴더 안에 있는 25000개의 데이터를 무제한으로 가져옴 -> 조심해야함
    train_flow = train_gen.flow_from_directory(
        'dogs_and_cats/small/train',
        batch_size=batch_size,
        target_size=(150, 150),         # resize 기능을 제공해줌
        class_mode='binary'             # sigmoid 어울리는 모드로 변환
    )
    # class_mode    "categorical", "binary", "sparse"

    valid_flow = valid_gen.flow_from_directory(
        'dogs_and_cats/small/train',
        batch_size=batch_size,
        target_size=(150, 150),         # resize 기능을 제공해줌
        class_mode='binary'             # sigmoid 어울리는 모드로 변환
    )
    # 여기까지 x, y 만든셈임

    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Input(shape=[150, 150, 3]))       # 224 써도 되지만 하드웨어 문제 -> 150만 쓰자

    model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))
    model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))
    model.add(tf.keras.layers.Conv2D(128, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))
    model.add(tf.keras.layers.Conv2D(128, [3, 3], activation='relu'))
    model.add(tf.keras.layers.MaxPool2D([2, 2]))

    # 3차원에서 2차원
    model.add(tf.keras.layers.Flatten())

    model.add(tf.keras.layers.Dense(512, activation='relu'))
    model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

    model.compile(optimizer=tf.keras.optimizers.RMSprop(0.0001),
                  loss=tf.keras.losses.binary_crossentropy,
                  metrics=['acc'])
    history = model.fit_generator(
        train_flow,
        epochs=10,
        steps_per_epoch=2000 // batch_size,     # 정확하게 나눠 떨어지지 않으면 자투리는 사용하지 않겠다
        validation_data=valid_flow,
        # validation_steps=batch_size,
        verbose=2,
    )
    # model.fit()

    model.save(get_model_name(version=2))
    save_history(history, version=2)


# 이미 학습해서 만들어놓음
def model_3_pretrained():
    def extract_features(conv_base, data_gen, directory, sample_count, batch_size):
        x = np.zeros([sample_count, 4, 4, 512])
        y = np.zeros([sample_count])

        flow = data_gen.flow_from_directory(
            directory,
            target_size=(150, 150),
            batch_size=batch_size,
            class_mode='binary'
        )

        # feature 생성
        for ii, (xx, yy) in enumerate(flow):
            n1 = ii * batch_size
            n2 = n1 + batch_size

            # 32로 나눴을 때 자투리가 있음 마지막 자투리를 x, y에 추가하는 코드를 구현해보자
            if n2 > sample_count:
                remained = sample_count - n1
                x[n1:n2] = conv_base.predict(xx[:remained])
                y[n1:n2] = yy
                break

            # feature 생성 핵심 코드
            x[n1:n2] = conv_base.predict(xx)    # feature 예측과 추출은 동일한 것이기 때문에
            y[n1:n2] = yy

        # x를 2차원으로 변환을 함
        return x.reshape(-1, 4 * 4 * 512), y

    # 마지막 dense layer 1000으로 고정되어 있으서 내가 수정해줘야함
    conv_base = tf.keras.applications.VGG16(
        include_top=False,      # dense layer 가져오지 않는다.
        input_shape=[150, 150, 3]
    )
    # conv 5블록 가져옴 -> 어디에 사용?? Feature 추출할 때 사용
    conv_base.summary()     # Total params: 14,714,688

    batch_size = 32
    data_gen = IDG(rescale=1/255)

    # feature을 생성하는데 이미지 증식하지 않겠다. ->피처는 이미지 원본을 증식한 후에 피처를 만드는 것임
    x_train, y_train = extract_features(conv_base, data_gen, 'dogs_and_cats/small/train', 2000, batch_size)
    x_valid, y_valid = extract_features(conv_base, data_gen, 'dogs_and_cats/small/validation', 1000, batch_size)
    x_test, y_test = extract_features(conv_base, data_gen, 'dogs_and_cats/small/test', 1000, batch_size)

    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Input(shape=[4 * 4 * 512]))       # x_train 이 2차원이기 때문에 이렇게 사용

    # ------------------------------ #
    # 이미 피처를 만들었기 때문에 해줄 필요 없음
    # model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu'))
    # model.add(tf.keras.layers.MaxPool2D([2, 2]))
    # model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu'))
    # model.add(tf.keras.layers.MaxPool2D([2, 2]))
    # model.add(tf.keras.layers.Conv2D(128, [3, 3], activation='relu'))
    # model.add(tf.keras.layers.MaxPool2D([2, 2]))
    # model.add(tf.keras.layers.Conv2D(128, [3, 3], activation='relu'))
    # model.add(tf.keras.layers.MaxPool2D([2, 2]))
    # ------------------------------ #

    model.add(tf.keras.layers.Dense(512, activation='relu'))
    model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

    model.compile(optimizer=tf.keras.optimizers.RMSprop(0.0001),
                  loss=tf.keras.losses.binary_crossentropy,
                  metrics=['acc'])

    history = model.fit(
        x_train, y_train,
        epochs=1, batch_size=batch_size,
        verbose=2, validation_data=(x_valid, y_valid)
    )

    # 모델을 저장하긴 하지만 다른 모델가 호환이 안되기 때문에 의미는 없음( 입력 모양이 다름 )
    model.save(get_model_name(version=3))
    save_history(history, version=3)

    print('acc :', model.evaluate(x_test, y_test))  # load 모델을 이용해서 출력이 안됨


def model_4_pretrained_augmentation():
    train_gen = IDG(
        rescale=1 / 255,
        horizontal_flip=True,  # 수직뒤집기, 수평뒤집기중 수집 뒤집기 사용할 것임
        width_shift_range=0.1,  # 10% 로 이동가능하다.
        height_shift_range=0.1,
        shear_range=0.1,  # 이미지 변형
        zoom_range=0.1,
        rotation_range=20,
    )
    valid_gen = IDG(
        rescale=1 / 255
    )
    # 이미지 증식의 이유 -> 다양한 데이터로 학습하기 위해 -> 검색은 데이터 증식할 필요 없다.
    # 그렇기 때문에 2개로 generation 나눔

    batch_size = 32
    # 3가지 방법 -> (x,y), pandas, 폴더에 있는것
    # 우리 폴더 안에 있는 25000개의 데이터를 무제한으로 가져옴 -> 조심해야함
    train_flow = train_gen.flow_from_directory(
        'dogs_and_cats/small/train',
        batch_size=batch_size,
        target_size=(150, 150),  # resize 기능을 제공해줌
        class_mode='binary'  # sigmoid 어울리는 모드로 변환
    )
    # class_mode    "categorical", "binary", "sparse"

    valid_flow = valid_gen.flow_from_directory(
        'dogs_and_cats/small/train',
        batch_size=batch_size,
        target_size=(150, 150),  # resize 기능을 제공해줌
        class_mode='binary'  # sigmoid 어울리는 모드로 변환
    )
    # 여기까지 x, y 만든셈임

    # 마지막 dense layer 1000으로 고정되어 있으서 내가 수정해줘야함
    conv_base = tf.keras.applications.VGG16(
        include_top=False,  # dense layer 가져오지 않는다.
    )
    # --------------------------------------- #
    # 학습하지 않게 얼려버림 (웨이트가 바뀌지 않음 왜?? 이미 이 코드 만든사람이 잘 만들었으니깐)
    conv_base.trainable = False     # 업데이트 하지 않을 것임

    # 이런식으로 업데이트를 일정 layer만 할 수 있음
    for layer in conv_base.layers:
        print(layer.name)
        # layer.trainable = True
        # block5 들어가 있는건 업데이트 하지 않겠다.
        if 'block5' in layer.name:
            layer.trainable = False

    model = tf.keras.Sequential()
    model.add(tf.keras.layers.Input(shape=[150, 150, 3]))  # 224 써도 되지만 하드웨어 문제 -> 150만 쓰자
    # -------------------------------------------------------------------  시작 #
    # layer처럼 직접 넣을 수도 있음
    model.add(conv_base)

    # 3차원에서 2차원
    model.add(tf.keras.layers.Flatten())

    model.add(tf.keras.layers.Dense(512, activation='relu'))
    model.add(tf.keras.layers.Dense(1, activation='sigmoid'))

    model.compile(optimizer=tf.keras.optimizers.RMSprop(0.0001),
                  loss=tf.keras.losses.binary_crossentropy,
                  metrics=['acc'])
    history = model.fit_generator(
        train_flow,
        epochs=10,
        steps_per_epoch=2000 // batch_size,  # 정확하게 나눠 떨어지지 않으면 자투리는 사용하지 않겠다
        validation_data=valid_flow,
        # validation_steps=batch_size,
        # verbose=2,
    )
    # model.fit()
    model.save(get_model_name(version=4))
    save_history(history, version=4)

# model_1_baseline()
# model_2_augmentation()
model_3_pretrained()
# model_4_pretrained_augmentation()


# load_history(version=3)
# load_model(version=4)
# load_history(version=4)
# Instructions for updating:

# load_model(version=1)
# load_model(version=4)

# Please use Model.fit, which supports generators.
# fit generation 대신 fit 을 써달라.


'인공지능 > CNN' 카테고리의 다른 글

Keras CNN functional로 글써보기  (0) 2020.12.10
이미지 증식  (0) 2020.12.09
이미지 사이즈 조절 및 이미지 분석 (feat. VGG)  (0) 2020.12.04
VGG  (0) 2020.12.04
LeNet5  (0) 2020.12.04