ずっと以前からやろうやろうと思っていてやれなかったことを、最近ようやくやりました。
「TensorFlowで歴代「クラウン」の画像を判別させてみた: EeePCの軌跡」に始まり、「TensorFlowで”日本のお城”を識別させてみた: EeePCの軌跡」や「TensorFlowで「けものフレンズ」の”フレンズ判別器”作ってみた: EeePCの軌跡」で使った、TenforFlow用の画像認識コードを、Keras用に書き換えてみました。
なぜ今さら、Kerasに書き換えたのか?
メリットは2つあります。
(1) コードが見やすい
(2) 収束性が安定する
何と言っても、コードが見やすいです。以下に、学習用(cnn_keras_train_56.py)と推論用(cnn_keras_app_56.py)のコードを載せます。(おまけ:可視化用コード cnn_keras_app_56_vis.py も載せました)
【cnn_keras_train_56.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 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)
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')
【cnn_keras_app_56.py】
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import numpy as np
import tensorflow as tf
#import cv2
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
path=os.getcwd()+'/analysis/'
file_list=os.listdir(path)
i = 0
label_name = []
flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_string('label','label.txt','File name of label')
f = open(FLAGS.label,'r')
for line in f:
line = line.rstrip()
l = line.rstrip()
label_name.append(l)
i = i + 1
NUM_CLASSES = i
IMAGE_SIZE = 56
if __name__ == '__main__':
test_image = []
test_fileNM = []
path=os.getcwd()+'/analysis/'
file_list=os.listdir(path)
for file in file_list:
img = img_to_array(load_img(path + file, target_size=(56,56)))
test_image.append(img)
test_fileNM.append(file)
test_image = np.asarray(test_image)
test_image = test_image.astype('float32')
test_image = test_image / 255.0
model = load_model('model.h5')
predictions = model.predict_classes(test_image)
print('predictions :',predictions)
print(label_name)
i = 0
for pred in predictions:
print(u'ファイル : ', test_fileNM[i], u'は ', label_name[pred], u'です。')
i +=1
旧記事に載っているコードと比べると、格段に短くなっています。
短いだけでなく、動作も安定してますね。
おまけですが、もう一つ推論用コード(可視化用)を。
【cnn_keras_app_56_vis.py】
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import numpy as np
import tensorflow as tf
import cv2
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.models import Model
from PIL import Image
path=os.getcwd()+'/analysis/'
outpath=os.getcwd()+'/visible/'
if not os.path.exists(outpath):
os.mkdir(outpath)
file_list=os.listdir(path)
i = 0
label_name = []
flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_string('label','label.txt','File name of label')
f = open(FLAGS.label,'r')
for line in f:
line = line.rstrip()
l = line.rstrip()
label_name.append(l)
i = i + 1
NUM_CLASSES = i
IMAGE_SIZE = 56
if __name__ == '__main__':
model = load_model('model.h5')
for file in file_list:
img2 = img_to_array(load_img(path + file, target_size=(56,56)))
test_image = np.asarray(img2)
test_image = test_image.astype('float32')
test_image = test_image / 255.0
test_image = test_image.reshape((1,) + test_image.shape)
intermediante_layer_model = Model(input=model.input,
outputs=model.get_layer("max_pooling2d_3").output)
y = intermediante_layer_model.predict(test_image)
test = np.split(y,128,axis=3)
test2 = np.zeros((1,7,7,1))
for ii in range(128):
test2 = test2 + test[ii]
image2 = np.array(test2)
image3 = np.reshape(image2,(7,7))
img = Image.new('RGB',(7,7),'black')
pix = img.load()
max = np.max(image3)
if max>255:
max = 255
min = np.min(image3)
if min < 0:
min = 0
dif = max-min
for x in range(7):
for y in range(7):
r = 0
g = 0
b = 0
p = image3[x,y]
if p < 1.0:
r = 0
g = 0
b = 0
elif p < min:
r = 0
b = 0
b = int(p*17.0)
elif p < min + 1.0 * dif/4.0:
r = 0
g = int((p-15.0)*17)
b = 255
elif p < min + 2.0 * dif/4.0:
r = 0
g = 255
b = int(255-(p-min + 1.0 * dif/4.0)*51.0)
elif p < min + 3.0 * dif/4.0:
r = int((min + 2.0 * dif/4.0)*51.0)
g = 255
b = 0
elif p < max:
r = 255
g = int(255-(p-min + 3.0 * dif/4.0)*51.0)
b = 0
else:
r = 255
g = 0
b = 0
pix[y,x] = (b,g,r)
oimg = np.array(img)
oimg = cv2.resize(oimg,(56,56))
savecv2 = outpath + 'hpool3_cont_' + file
cv2.imwrite(savecv2,oimg)
srcf1 = path + file
src1 = cv2.imread(srcf1)
src1 = cv2.resize(src1,(56,56))
dst = cv2.addWeighted(src1, 0.7, oimg,0.6, 0)
cnt_filenm = outpath + 'hpool3_dst_' + file
cv2.imwrite(cnt_filenm,dst)
predictions = model.predict_classes(test_image)
#print('predictions :',predictions)
#print(label_name)
print(u'ファイル : ', file, u'は ', label_name[predictions[0]], u'です。')
こちらは、推論の結果を「可視化」してくれるコードです。
さて、このコードの使い方です。
■ 準備
まず、上の2つのコード(「cnn_keras_train_56.py」「cnn_keras_app_56.py」)をコピペして、保存してください。
Windows版Anaconda 3やLinux環境下で、いくつかpipコマンドでインストールしてください。
(tensorflow[tensorflow-gpu]、keras、opencv-python、h5py、numpyなど)
■ 学習
教師データを「data」フォルダ内に入れてください。
その場合、ラベルごとに分けたフォルダを作り、その中にラベルに応じた画像を入れます。
こんな感じです。なお、フォルダ名は英数字、スペースなしお願いします。
それが終われば、
> python cnn_keras_train_56.py
と実行します。
こんな画面が出て、 学習が実行されます。
なお、以前のTensorFlowのコードの場合、Loss値がNanとなることが多かったんですが、Kerasにすると全然問題なく収束しますね。
上のコード(cnn_keras_train_56.py)の92~107行目のmodel.~の部分を深層化しても、全然問題なく収束してくれます。
以前は深層化するために、一段浅い学習モデルを初期値にしてまわしてたんですが、そんな手間がなくなりました。しかも、速い・・・TensorFlowでのあの苦労が、嘘のようです。
学習が終わると、「checkpoints」というフォルダができているはずです。
そのフォルダにある、一番最後に吐き出したモデルファイル(model_.[サイクル数]-[Loss値].h5)というファイルができているので、いちばんLoss値の小さいやつを一つ上の階層に移動して、「model.h5」という名前に変えておきます。
■ 推論
ここで、推論させます。
「analysis」というフォルダを作って、その中に推論させたい画像を入れます。
こんな感じです。正方形に切っておくと、なおベターです。
そこで、以下のコマンドを実行。
> python cnn_keras_app_56.py
すると、こんな画面が出るはずです。
ファイルごとに、推論結果が出てきます。
ちなみに、もう一つの推論コード
> python cnn_keras_app_56_vis.py
を実行すると、「cnn_keras_app_56.py」と同じ挙動をしつつも、「visible」というフォルダが作られて、「analysis」フォルダにある元画像1枚1枚に対し、以下のような特徴分布画像を出力します。
【元画像】
【特徴分布】(弱い:青~強い:赤)
【元画像+特徴分布】
赤いところほど強く反応しているところで、青いところはほぼ特徴がないととらえている場所です。
どうやらこの学習器では、「かばんちゃん」の前髪の辺りに特徴を見出して判断してるようですね。
教師データの与え方やハイパーパラメーターチューニングなどに、重要なヒントを与えてくれそうです。
これを使えば、その画像のどの「特徴」をとらえて推論しているかが、一目瞭然ですぐに分かるというものです。
これを応用すれば
こんなことも可能です(cnn_keas_app_56.pyにOpenCVの物体検出を併用、コード省略) 。
深層化や、トライアンドエラーが、すごく捗ります。
しかも、なぜかTensorFlowのコードよりも速いです。
その分、ちょっと扱いに注意が必要ですが・・・例えば、Conv2D(~)の中の”padding='same'”を忘れると、えらいことになります(畳み込み層を通るごとに、画像サイズが小さくなります)。
このKeras版コード、実際に業務用に使っていますが、全然問題なく使えますね。
コードが見やすい分、教育用にも適してます。
安定性が増す、見やすい、チューニングが捗る。いいことづくめです。こんなことなら、もっと早く取り組んでおけばよかった・・・
もちろん、Kerasにも欠点はあります(用意されていない手法を実装するのが、とても苦手)が、よほどそういう事態には巡り合わないものと思われます。
画像認識を本気で学習してみたい方は、それこそKerasを使わずにガチでコードを書いた方が学べることも多いですが、ともかく画像認識を業務等で使いたい!という向きの方には、Kerasはおすすめです。
最近のコメント