KerasでVGG16モデルを実装して花の種類を画像分類

プログラミング

はじめに

今回はDeep Learningの画像分類で代表的なモデルであるVGG16を実装して、花の種類を分類してみました。
kerasには学習済みのVGG16モデルがすでに実装されているので、こちらを使うのが王道な気がしますが、ネットワーク構築のお勉強も兼ねて元論文などを参考に自前実装しました。

VGG16とは

VGG16は2014年のILSVRC(ImageNet Large Scale Visual Recognition Challenge)という画像認識のコンペティションで提案されたニューラルネットワークです。
VGG16モデルのアーキテクチャは下の表のようになっています。
VGG16は下の表の右から二番目になります。このアーキテクチャを見ると、畳み込み層が13層あり、全結合層が3層の計16層から成る畳み込みニューラルネットワーク(CNN)であることがわかります。
そして、VGG16モデルは出力層が1000ユニットあるので、1000クラスを分類するネットワークです。引用: Karen Simonyan, Andrew Zisserman ;  Very Deep Convolutional Networks for Large-Scale Image Recognition

データセット

今回はkaggleにあるFlower Recognitionデータセットを使って花の種類分類をします。
このデータセットは、フリッカーを使ってGoogle画像等で4242枚の花の画像を集めたものです。
花の種類は下記の5種類について集められています。
・カモミール
・チューリップ
・バラ
・ひまわり
・タンポポ各画像のサイズは異なっており、およそ320 x 240のようです。

必要なライブラリのインポート

各種必要なライブラリをインポートします。

# data visualisation and manipulation
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import style
import seaborn as sns

# configure
%matplotlib inline
style.use('fivethirtyeight')
sns.set(style='whitegrid', color_codes=True)

# model seletion
from sklearn.model_selection import train_test_split
from sklearn.model_selection import KFold
from sklearn.metrics import accuracy_score, precision_score, recall_score, confusion_matrix, roc_curve, roc_auc_score
from sklearn.model_selection import GridSearchCV
from sklearn.preprocessing import LabelEncoder

# preprocess
from keras.preprocessing.image import ImageDataGenerator

# dl libraries
from keras import backend as K
from keras.models import Sequential
from keras.models import Model
from keras.layers import Dense
from keras.optimizers import Adam, SGD, Adagrad, Adadelta, RMSprop
from keras.utils import to_categorical

# specifically for cnn
from keras.layers import Dropout, Flatten, Activation, Lambda
from keras.layers import Input, Conv2D, MaxPooling2D, BatchNormalization

import tensorflow as tf
import random as rn

# other library
import cv2
import numpy as np
from tqdm import tqdm
from random import shuffle
from zipfile import ZipFile
from PIL import Image

 

データの前処理・画像の確認

データセット画像は画像サイズが一致していないため、リサイズおよびラベル付けを行って配列に格納します。
そして確認のため、ランダムに画像を表示します。

X=[]
Z=[]
IMG_SIZE=150
FLOWER_DAISY_DIR='../input/flowers/flowers/daisy'
FLOWER_SUNFLOWER_DIR='../input/flowers/flowers/sunflower'
FLOWER_TULIP_DIR='../input/flowers/flowers/tulip'
FLOWER_DANDI_DIR='../input/flowers/flowers/dandelion'
FLOWER_ROSE_DIR='../input/flowers/flowers/rose'

def assign_label(img, flower_type):
    return flower_type

def make_train_data(flower_type, DIR):
    for img in tqdm(os.listdir(DIR)):
        label = assign_label(img, flower_type)
        path = os.path.join(DIR, img)
        img = cv2.imread(path, cv2.IMREAD_COLOR)
        if not img is None:
            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
            img = cv2.resize(img, (IMG_SIZE, IMG_SIZE))
        else:
            print('None image file : ', path)
            continue

        X.append(np.array(img))
        Z.append(str(label))

make_train_data('Daisy', FLOWER_DAISY_DIR)
print("total images: ", len(X))
print("image size: ", X[0].shape)

make_train_data('Sunflower', FLOWER_SUNFLOWER_DIR)
print("total images: ", len(X))

make_train_data('Tulip', FLOWER_TULIP_DIR)
print("total images: ", len(X))

make_train_data('Dandelion',FLOWER_DANDI_DIR)
print("total images: ", len(X))

make_train_data('Rose',FLOWER_ROSE_DIR)
print("total images: ", len(X))

fig, ax = plt.subplots(5,2)
fig.set_size_inches(15, 15)
for i in range(5):
    for j in range(2):
        l = rn.randint(0, len(Z))
        ax[i, j].imshow(X[l])
        ax[i, j].set_title('Flower: '+Z[l])

plt.tight_layout()

画像を見てみると、本物の花ではない画像が混じっていたりしていることが確認できます。
あまり質の良いデータセットではなさそうです。
本来はこういったノイズデータを削除すべきですが、今回はVGG16モデルの構築が主目的なので、このまま使います。

ラベルエンコーディングと正規化

One-hotベクトルでカテゴリ変数をラベルエンコーディングして、画像を正規化します。

num_classes = 5
# Label encoding
le = LabelEncoder()
Y = le.fit_transform(Z)
# one hot vector
Y = to_categorical(Y, num_classes)
# normalize
X = np.array(X)
X = X/255

 

トレーニングとテストデータに分割

トレーニング用とテスト用にデータを8:2の割合で分割します。

x_train, x_test, y_train, y_test = train_test_split(X, Y, test_size=0.2, random_state=1)

 

Data Augmentaion

学習画像に回転や反転などをして、学習画像の拡張を行います。

datagen = ImageDataGenerator(
        featurewise_center=False,  # set input mean to 0 over the dataset
        samplewise_center=False,  # set each sample mean to 0
        featurewise_std_normalization=False,  # divide inputs by std of the dataset
        samplewise_std_normalization=False,  # divide each input by its std
        zca_whitening=False,  # apply ZCA whitening
        rotation_range=10,  # randomly rotate images in the range (degrees, 0 to 180)
        zoom_range = 0.1, # Randomly zoom image 
        width_shift_range=0.2,  # randomly shift images horizontally (fraction of total width)
        height_shift_range=0.2,  # randomly shift images vertically (fraction of total height)
        horizontal_flip=True,  # randomly flip images
        vertical_flip=False)  # randomly flip images


datagen.fit(x_train)

 

VGG16モデルの構築

元論文を参考に、VGG16モデルを構築します。
VGG16モデルは入力画像のイメージサイズは224 x 224なので、最初にリサイズします。
そして、今回は5クラスに分類するように設定します。

_input = Input(shape=(150, 150, 3))
x = Lambda(lambda image: tf.image.resize_images(image, (224, 224)))(_input)
conv1 = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv1')(x)
conv2 = Conv2D(64, (3, 3), activation='relu', padding='same', name='block1_conv2')(conv1)
pool1 = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block1_pool')(conv2)

conv3 = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv1')(pool1)
conv4 = Conv2D(128, (3, 3), activation='relu', padding='same', name='block2_conv2')(conv3)
pool2 = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block2_pool')(conv4)

conv5 = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv1')(pool2)
conv6 = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv2')(conv5)
conv7 = Conv2D(256, (3, 3), activation='relu', padding='same', name='block3_conv3')(conv6)
pool3 = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block3_pool')(conv7)

conv8 = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv1')(pool3)
conv9 = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv2')(conv8)
conv10 = Conv2D(512, (3, 3), activation='relu', padding='same', name='block4_conv3')(conv9)
pool4 = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block4_pool')(conv10)

conv11 = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv1')(pool4)
conv12 = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv2')(conv11)
conv13 = Conv2D(512, (3, 3), activation='relu', padding='same', name='block5_conv3')(conv12)
pool5 = MaxPooling2D((2, 2), strides=(2, 2), padding='same', name='block5_pool')(conv13)

flat = Flatten(name='flatten')(pool5)
dense1 = Dense(4096, activation='relu', name='fc1')(flat)
dropout1 = Dropout(0.5, name='dropout1')(dense1)
dense2 = Dense(4096, activation='relu', name='fc2')(dropout1)
dropout2 = Dropout(0.5, name='dropout2')(dense2)
output = Dense(num_classes, activation='softmax', name='output')(dropout2)

 

コールバックの設定

kerasにはモデルの学習を効率よくかつ精度よく行うための様々なコールバック関数がデフォルトで用意されています。

今回は、ReduceLROnPlateauを使ってみたいと思います。
このコールバックは引数に設定したpatienceに設定した値の間に更新がなかった場合、学習率をfactor倍します。
これによって学習率を減らします。

詳しいことは公式ドキュメントを見ていただければと思います。

from keras.callbacks import ReduceLROnPlateau
red_lr = ReduceLROnPlateau(monitor='val_acc',
                           factor=0.1,
                           patience=5,
                           verbose=1,
                           mode='auto',
                           epsilon=0.0001,
                           cooldown=0,
                           min_lr=0.00001)

 

オプティマイザーの設定

今回はOptimizerとしてRMSprop、損失関数にクロスエントロピーを用います。
また、バッチサイズは128にして学習します。
最後にこれまで設定したモデルの確認をしてみます。

import keras
opt = keras.optimizers.rmsprop(lr=0.0001, decay=1e-6)

model  = Model(inputs=_input, outputs=output)

model.compile(loss='categorical_crossentropy', optimizer=opt, metrics=['accuracy'])

model.summary()

 

 

CNNの学習・評価

それでは、いよいよVGG16モデルで学習と評価をしてみます。
出力した学習曲線は下図のようになりました。

Accuracyはテストデータについては0.6~0.7の間で頭打ちになっています。
Lossもテストデータについては発散しており、あまり精度の良いモデルではないことがわかりました。

まとめ

VGG16モデルを実装して、花の種類を学習してみました。
結果としては今回の構築したモデルではよい学習結果を得ることは出来ませんでした。
データセットがそもそも質があまり良いとは言えないこともあるのですが、それ以外にも原因がありそうです。

次はモデルを再構築したりパラメータを変えてもう一度チャレンジしてみたいと思います。
(もしくは学習済みのVGG16 モデルに転移学習を加えてみるかなぁ)
今回はここまで。

参考

・VGG16元論文
Very Deep Convolutional Networks for Large-Scale Image Recognition
Karen Simonyan, Andrew Zisserman
(Submitted on 4 Sep 2014 (v1), last revised 10 Apr 2015 (this version, v6))
KerasでVGG16を使う

 

人気記事

 

タイトルとURLをコピーしました