« 年末恒例・・・のはずが年始に自炊作業やりました | トップページ | 写真から人物の実を切り取ってくれるサイト”remove.bg” »

2019年1月 8日 (火)

TensorFlowの画像認識プログラムをKerasに書き換えてみた

ずっと以前からやろうやろうと思っていてやれなかったことを、最近ようやくやりました。

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」フォルダ内に入れてください。

その場合、ラベルごとに分けたフォルダを作り、その中にラベルに応じた画像を入れます。

Tfkemofr02

こんな感じです。なお、フォルダ名は英数字、スペースなしお願いします。

それが終われば、

> python cnn_keras_train_56.py

と実行します。

Cnn_keras01

こんな画面が出て、 学習が実行されます。

なお、以前のTensorFlowのコードの場合、Loss値がNanとなることが多かったんですが、Kerasにすると全然問題なく収束しますね。

上のコード(cnn_keras_train_56.py)の92~107行目のmodel.~の部分を深層化しても、全然問題なく収束してくれます。

以前は深層化するために、一段浅い学習モデルを初期値にしてまわしてたんですが、そんな手間がなくなりました。しかも、速い・・・TensorFlowでのあの苦労が、嘘のようです。

学習が終わると、「checkpoints」というフォルダができているはずです。

そのフォルダにある、一番最後に吐き出したモデルファイル(model_.[サイクル数]-[Loss値].h5)というファイルができているので、いちばんLoss値の小さいやつを一つ上の階層に移動して、「model.h5」という名前に変えておきます。

■ 推論

ここで、推論させます。

「analysis」というフォルダを作って、その中に推論させたい画像を入れます。

Tfkemofr04

こんな感じです。正方形に切っておくと、なおベターです。

そこで、以下のコマンドを実行。

> python cnn_keras_app_56.py

すると、こんな画面が出るはずです。

Cnn_keras02

ファイルごとに、推論結果が出てきます。

ちなみに、もう一つの推論コード

> python cnn_keras_app_56_vis.py

を実行すると、「cnn_keras_app_56.py」と同じ挙動をしつつも、「visible」というフォルダが作られて、「analysis」フォルダにある元画像1枚1枚に対し、以下のような特徴分布画像を出力します。

【元画像】

Kaban3

【特徴分布】(弱い:青~強い:赤)

Hpool3_cont_kaban3

【元画像+特徴分布】

Hpool3_dst_kaban3

赤いところほど強く反応しているところで、青いところはほぼ特徴がないととらえている場所です。

どうやらこの学習器では、「かばんちゃん」の前髪の辺りに特徴を見出して判断してるようですね。

教師データの与え方やハイパーパラメーターチューニングなどに、重要なヒントを与えてくれそうです。

これを使えば、その画像のどの「特徴」をとらえて推論しているかが、一目瞭然ですぐに分かるというものです。

これを応用すれば

Cnn_keras03

こんなことも可能です(cnn_keas_app_56.pyにOpenCVの物体検出を併用、コード省略) 。

深層化や、トライアンドエラーが、すごく捗ります。

しかも、なぜかTensorFlowのコードよりも速いです。

その分、ちょっと扱いに注意が必要ですが・・・例えば、Conv2D(~)の中の”padding='same'”を忘れると、えらいことになります(畳み込み層を通るごとに、画像サイズが小さくなります)。

このKeras版コード、実際に業務用に使っていますが、全然問題なく使えますね。

コードが見やすい分、教育用にも適してます。

安定性が増す、見やすい、チューニングが捗る。いいことづくめです。こんなことなら、もっと早く取り組んでおけばよかった・・・

もちろん、Kerasにも欠点はあります(用意されていない手法を実装するのが、とても苦手)が、よほどそういう事態には巡り合わないものと思われます。

画像認識を本気で学習してみたい方は、それこそKerasを使わずにガチでコードを書いた方が学べることも多いですが、ともかく画像認識を業務等で使いたい!という向きの方には、Kerasはおすすめです。

PythonとKerasによるディープラーニング

« 年末恒例・・・のはずが年始に自炊作業やりました | トップページ | 写真から人物の実を切り取ってくれるサイト”remove.bg” »

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

コメント

丁寧な記事で,大変参考になりました.
質問なのですが,推論(cnn_keras_app_56.py)においてクラスと同時に分類確率を表示させるには,どのようにすれば良いでしょうか.教えていただけると幸いです.

こんにちは、ゆうさん。

ググってみましたが、推論(cnn_keras_app_56.py)コードの「predictons=~」の下に

pred_prob = model.predict_proba(test_image)

を書き(インデントに注意)、for pred inpredictions:の部分を


for pred in predictions:
print(u'ファイル : ', test_fileNM[i], u'は ', label_name[pred], '(' , pred_prob[i,pred] * 100.0 , '% )' u'です。')
i +=1

と書き直すと、確率が出せます。お試しください。

返信ありがとうございます.

試したところ無事,確率を表示できるようになりました!
とても助かりました.

コメントを書く

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

トラックバック


この記事へのトラックバック一覧です: TensorFlowの画像認識プログラムをKerasに書き換えてみた:

« 年末恒例・・・のはずが年始に自炊作業やりました | トップページ | 写真から人物の実を切り取ってくれるサイト”remove.bg” »

無料ブログはココログ

スポンサード リンク

ブログ村