目次
はじめに
今回はDeep Learningの画像分類で代表的なモデルであるVGG16を実装して、花の種類を分類してみました。
kerasには学習済みのVGG16モデルがすでに実装されているので、こちらを使うのが王道な気がしますが、ネットワーク構築のお勉強も兼ねて元論文などを参考に自前実装しました。
VGG16とは
VGG16モデルのアーキテクチャは下の表のようになっています。
VGG16は下の表の右から二番目になります。このアーキテクチャを見ると、畳み込み層が13層あり、全結合層が3層の計16層から成る畳み込みニューラルネットワーク(CNN)であることがわかります。
そして、VGG16モデルは出力層が1000ユニットあるので、1000クラスを分類するネットワークです。引用: Karen Simonyan, Andrew Zisserman ; Very Deep Convolutional Networks for Large-Scale Image 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を使う
人気記事