ディープラーニング(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
で行けます。
とりあえず、このコード中では「add_image_bc」という変数がBC learningによって増やす画像の数を示してます。
が、同じラベルの画像を作っても仕方がないので、同じ画像を足そうとした場合はスキップするようになっています。
今回の場合、100枚作らせて、87枚加わっただけでした。
後はいつものように、学習を始めます。
終わったところで、「checkpoints」フォルダにある一番Loss値の小さいモデルを上の階層にあげて「model.h5」として
> python cnn_keras_app_56.py
と実行すれば、「analysis」フォルダ中の画像を推論してくれます。
あくまでも、model.h5の精度を上げるというのが目的の手法なので、推論側は特に変更するところはないです。
もっとも、あまり結果が変わってる感じはありませんが・・・会社で別のデータで試したところ、若干精度が上がりました。
それこそ、中間のデータを作り出すため、教師データを増やす手法の一つには違いありませんね。
ですが、例えばこのお城のデータの場合(383枚)、1000枚作らせてみた(同ラベルを消去して、900枚程度増)んですが、かえって精度が落っこちました。収束性も悪くなるため、やりすぎもよくないようです。
どうしても画像がたくさん揃えられない場合、この手法で画像を増やす、あるいは曖昧さをうまく区別させるために使う、という目的なら、有効な方法のようです。
最近のコメント