« スマート家電リモコンでテレビの選局を可能に | トップページ | iPhone SEの後継は4.8インチ有機ELの「iPhone XE」!? »

2019年4月12日 (金)

ディープラーニングの画像認識の精度を向上させる手法?な「BC(Between-Class) learning」を試してみた

ディープラーニング(CNN)を用いた画像認識では、それこそ1ラベル当たりたくさんの画像を用意するのがベストです。

が、曖昧な画像をうまく認識させるための手法として、「BC(Between-Class) learning」というのがあります。

例えば、「犬」と「猫」を識別する学習器を作ろうとすると、「犬」を0、「猫」を1というラベルを与えて、それぞれたくさんの画像を準備して学習させます。

が、BC learningでは、「犬」×0.7 + 「猫」×0.3というわざと曖昧な画像を作り、教師データとして用います。

すると、曖昧さに対して強固な学習器が生成できて、より精度が上がる・・・という手法のようなのです。

分かったような、分からないような手法ですが、早速試してみました。

ただし、この手法を使うには前提があります。

例えば、TensorFlowで”日本のお城”を識別させてみた: EeePCの軌跡の記事では、

「犬山城」=0、「熊本城」=1、・・・「大阪城」=6

のようなラベルの付け方をしてましたが、BC learningを使うためには、one-hot型の教師信号に変える必要があります。

具体的には

「犬山城」=[1,0,0,0,0,0,0]、「熊本城」=[0,1,0,0,0,0,0]、・・・「大阪城」=[0,0,0,0,0,0,1]

のようなラベルにする必要があります。

すると、例えば「犬山城」×0.7 + 「熊本城」×0.3という画像を作った場合の教師信号は

「犬山城」×0.7 + 「熊本城」×0.3 =[0.7,0.3,0,0,0,0,0]

てな感じに与えられます。

で、私自身も気づいてませんでしたが、以前TensorFlowの画像認識プログラムをKerasに書き換えてみた: EeePCの軌跡の記事で、画像認識コードをKeras化したときに、すでにone-hot型にしていました。

先のリンク先のコード中の「Y = np_utils.to_categorical(Y, NUM_CLASSES)」のところで、すでにone-hot型に変えております。

で、上の記事の学習用コード「cnn_keras_train_56.py」を改良した「cnn_keras_train_56_bc.py」というのを作りました。

【cnn_keras_train_56_bc.py】

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import sys
import cv2
import numpy as np
import tensorflow as tf
import tensorflow.python.platform
import random

import keras
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense,Dropout,Flatten
from keras.layers import Conv2D,MaxPooling2D
from keras.preprocessing.image import array_to_img,img_to_array,load_img
from keras import backend as K
from sklearn.model_selection import train_test_split
from keras.models import load_model
from keras.callbacks import ModelCheckpoint

path=os.getcwd()+'/data/'
checkDir=os.getcwd()+'/checkpoints/'
if not os.path.exists(checkDir):
          os.mkdir(checkDir)
class_count = 0
folder_list=os.listdir(path)

for folder in folder_list:

  class_count = class_count+1

NUM_CLASSES = class_count
IMAGE_SIZE = 56

batch_size = 20
epochs = 100

flags = tf.app.flags
FLAGS = flags.FLAGS

flags.DEFINE_string('label', 'label.txt', 'File name of label')

if __name__ == '__main__':

    count=0
    folder_list = sorted(os.listdir(path))

    train_image = []
    train_label = []
    test_image = []
    test_label = []
    X = []
    Y = []

    f = open(FLAGS.label, 'w')
    for folder in folder_list:
        subfolder = os.path.join(path,folder)
        file_list = sorted(os.listdir(subfolder))

        filemax = 0

        i = 0

        for file in file_list:

            i = i + 1

            img = img_to_array(load_img('./data/' + folder + '/' + file,target_size=(56,56)))
            X.append(img)
            Y.append(count)

        label_name = folder + ' ' + str(count) + '\n'
        f.write(label_name)

        count +=1

    X = np.asarray(X)
    Y = np.asarray(Y)
    X = X.astype('float32')
    X = X / 255.0

    Y = np_utils.to_categorical(Y, NUM_CLASSES)

    #Make BC learning dataset

    add_image_bc = 100
    insert_count = 1

    for i in range(add_image_bc):
        get_index1 = int(random.random() * X.shape[0]) # First image
        get_index2 = int(random.random() * X.shape[0]) # Second image
        mix_rasio = random.uniform(0.2,0.8) # First X Second Mix

        temp_x1 = X[get_index1,:]
        temp_y1 = Y[get_index1,:]

        temp_x2 = X[get_index2,:]
        temp_y2 = Y[get_index2,:]

        if all(temp_y1 == temp_y2):
            print('Same Label,Skip!')
            continue
        else:
            temp_x = temp_x1 * mix_rasio + temp_x2 * (1.0 - mix_rasio)
            temp_y = temp_y1 * mix_rasio + temp_y2 * (1.0 - mix_rasio)

        X = np.insert(X,get_index1,temp_x,axis = 0)
        Y = np.insert(Y,get_index1,temp_y,axis = 0)
        print('Insert Mix image!' + str(insert_count))
        insert_count +=1

        # BC learning end
    train_image, test_image, train_label, test_label = train_test_split(X,Y,test_size=0.20)

    f.close()
    print(u'画像読み込み終了')

    input_shape = (IMAGE_SIZE, IMAGE_SIZE, 3)

    model = Sequential()
    model.add(Conv2D(32,kernel_size=(3,3),
                     activation='relu',
                     padding='same',
                     input_shape=input_shape))
    model.add(Conv2D(32, (3,3), activation='relu', padding='same'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Dropout(0.25))

    model.add(Conv2D(64, (3,3), activation='relu', padding='same'))
    model.add(Conv2D(64, (3,3), activation='relu', padding='same'))
    model.add(Conv2D(64, (3,3), activation='relu', padding='same'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Dropout(0.25))

    model.add(Conv2D(128, (3,3), activation='relu', padding='same'))
    model.add(Conv2D(128, (3,3), activation='relu', padding='same'))
    model.add(Conv2D(128, (3,3), activation='relu', padding='same'))
    model.add(MaxPooling2D(pool_size=(2,2)))
    model.add(Dropout(0.25))

    model.add(Flatten())
    model.add(Dense(1024, activation='relu'))
    model.add(Dropout(0.5))
    model.add(Dense(NUM_CLASSES, activation='softmax'))

    model.compile(loss=keras.losses.categorical_crossentropy,
                  optimizer=keras.optimizers.Adadelta(),
                  metrics=['accuracy']
                  )

    chkpt = os.path.join(checkDir, 'model_.{epoch:02d}-{val_loss:.2f}.h5')
    cp_cb = ModelCheckpoint(filepath = chkpt, monitor='val_loss', verbose=1,
                            save_best_only=True, mode='auto')

    model.fit(train_image, train_label,
              batch_size=batch_size,
              epochs=epochs,
              verbose=1,
              validation_data=(test_image, test_label),
              callbacks=[cp_cb],
              )

    model.summary()

    score = model.evaluate(test_image, test_label, verbose=0)

    model.save('model.h5')

コード中の「#Make BC learning dataset」から「# BC learning end」の間が加わったくらいです(あと、乱数を使うために「import random」が追加されたくらい)。

実行はいつものように

> python cnn_keras_train_56_bc.py

で行けます。

Bcl01_1

とりあえず、このコード中では「add_image_bc」という変数がBC learningによって増やす画像の数を示してます。

が、同じラベルの画像を作っても仕方がないので、同じ画像を足そうとした場合はスキップするようになっています。

今回の場合、100枚作らせて、87枚加わっただけでした。

Bcl02

後はいつものように、学習を始めます。

終わったところで、「checkpoints」フォルダにある一番Loss値の小さいモデルを上の階層にあげて「model.h5」として

> python cnn_keras_app_56.py

と実行すれば、「analysis」フォルダ中の画像を推論してくれます。

あくまでも、model.h5の精度を上げるというのが目的の手法なので、推論側は特に変更するところはないです。

 

Bcl03

 

もっとも、あまり結果が変わってる感じはありませんが・・・会社で別のデータで試したところ、若干精度が上がりました。

それこそ、中間のデータを作り出すため、教師データを増やす手法の一つには違いありませんね。

ですが、例えばこのお城のデータの場合(383枚)、1000枚作らせてみた(同ラベルを消去して、900枚程度増)んですが、かえって精度が落っこちました。収束性も悪くなるため、やりすぎもよくないようです。

どうしても画像がたくさん揃えられない場合、この手法で画像を増やす、あるいは曖昧さをうまく区別させるために使う、という目的なら、有効な方法のようです。

深層学習教科書 ディープラーニング G検定(ジェネラリスト) 公式テキスト

« スマート家電リモコンでテレビの選局を可能に | トップページ | iPhone SEの後継は4.8インチ有機ELの「iPhone XE」!? »

数値解析系」カテゴリの記事

コメント

コメントを書く

(ウェブ上には掲載しません)

« スマート家電リモコンでテレビの選局を可能に | トップページ | iPhone SEの後継は4.8インチ有機ELの「iPhone XE」!? »

無料ブログはココログ

スポンサード リンク

ブログ村