🧠

Tensorflow + Kerasによる、ものすごくシンプルな画像分類(りんごとオレンジ)

2021/07/03に公開

とりあえず、いろいろ読んでも良く解らないので keras + tensorflow でシンプルにやってみた。これがうまくいったらより複雑な問題にチャレンジしよう。

今回やるのは極めてシンプルな、リンゴ画像とオレンジ画像の分類。

画像はpythonのImage.openで読み込んだら配列になっているので、これを1次元配列に変換したら、あとはKerasによる、ものすごくシンプルな深層学習の例と同じように学習、結果を検証する。

下記の fruit.py と同じディレクトリに以下のように学習用データとテスト用データを配置。

./data/train/apple/(学習用りんご画像)
            /orange/(学習用オレンジ画像)

       test/apple/(テスト用りんご画像)
           /orange/(テスト用オレンジ画像)

画像は頑張ってグーグル画像検索から落とした。学習用画像はそれぞれ20枚くらい、テスト用画像は5枚づつ適当に選んで上記ディレクトリに保存しておく。気をつけるのは、学習用の画像とテスト用の画像は別でなければテストになりませんので学習用画像とは別のものをダウンロードします。

コードは下記のように。考え方は単純で、学習用りんご画像とオレンジ画像が入ったディレクトリ内の画像を一つ一つ読み込む。よみこんだ画像は[R,G,B]を一要素とする25x25の配列となっているので、これを乱暴だけど1次元画像に変換、あとは入力値を 255. で割って0~1の値にして学習用モデルに喰わせる。

ちなみに正解ラベルはラベルの値そのものではなく、0と1からなる配列に変換してから喰わせる。
たとえば 0[1,0]1[0,1] ようするに1が立っている位置のインデックスがラベルの値になるような配列。

fruit.py
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Activation, Dense, Dropout
from tensorflow.keras.utils import to_categorical
from tensorflow.keras.optimizers import Adagrad
from tensorflow.keras.optimizers import Adam
import numpy as np
from PIL import Image
import os

# 学習用のデータを作る.
image_list = []
label_list = []

# ./data/train 以下のorange,appleディレクトリ以下の画像を読み込む。
for dir in os.listdir("data/train"):
    if dir == ".DS_Store":
        continue

    dir1 = "data/train/" + dir 
    label = 0

    if dir == "apple":    # appleはラベル0
        label = 0
    elif dir == "orange": # orangeはラベル1
        label = 1

    for file in os.listdir(dir1):
        if file != ".DS_Store":
            # 配列label_listに正解ラベルを追加(りんご:0 オレンジ:1)
            label_list.append(label)
            filepath = dir1 + "/" + file
            # 画像を25x25pixelに変換し、1要素が[R,G,B]3要素を含む配列の25x25の2次元配列として読み込む。
            # [R,G,B]はそれぞれが0-255の配列。
            image = np.array(Image.open(filepath).resize((25, 25)))
            print(filepath)
            # 配列を変換し、[[Redの配列],[Greenの配列],[Blueの配列]] のような形にする。
            image = image.transpose(2, 0, 1)
            # さらにフラットな1次元配列に変換。最初の1/3はRed、次がGreenの、最後がBlueの要素がフラットに並ぶ。
            image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
            # 出来上がった配列をimage_listに追加。
            image_list.append(image / 255.)

# kerasに渡すためにnumpy配列に変換。
image_list = np.array(image_list)

# ラベルの配列を1と0からなるラベル配列に変更
# 0 -> [1,0], 1 -> [0,1] という感じ。
Y = to_categorical(label_list)

# モデルを生成してニューラルネットを構築
model = Sequential()
model.add(Dense(200, input_dim=1875))
model.add(Activation("relu"))
model.add(Dropout(0.2))

model.add(Dense(200))
model.add(Activation("relu"))
model.add(Dropout(0.2))

model.add(Dense(2))
model.add(Activation("softmax"))

# オプティマイザにAdamを使用
opt = Adam(lr=0.001)
# モデルをコンパイル
model.compile(loss="categorical_crossentropy", optimizer=opt, metrics=["accuracy"])
# 学習を実行。10%はテストに使用。
model.fit(image_list, Y, epochs=1500, batch_size=100, validation_split=0.1)

# テスト用ディレクトリ(./data/train/)の画像でチェック。正解率を表示する。
total = 0.
ok_count = 0.

for dir in os.listdir("data/train"):
    if dir == ".DS_Store":
        continue

    dir1 = "data/test/" + dir 
    label = 0

    if dir == "apple":
        label = 0
    elif dir == "orange":
        label = 1

    for file in os.listdir(dir1):
        if file != ".DS_Store":
            label_list.append(label)
            filepath = dir1 + "/" + file
            image = np.array(Image.open(filepath).resize((25, 25)))
            print(filepath)
            image = image.transpose(2, 0, 1)
            image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
            result = model.predict_classes(np.array([image / 255.]))
            print("label:", label, "result:", result[0])

            total += 1.

            if label == result[0]:
                ok_count += 1.

print("seikai: ", ok_count / total * 100, "%")

上を実行した結果は以下

(学習過程の出力)....
Epoch 1498/1500
1/1 [==============================] - 0s 12ms/step - loss: 7.6416e-07 - accuracy: 1.0000 - val_loss: 0.6871 - val_accuracy: 0.8000
Epoch 1499/1500
1/1 [==============================] - 0s 12ms/step - loss: 6.8343e-06 - accuracy: 1.0000 - val_loss: 0.6892 - val_accuracy: 0.8000
Epoch 1500/1500
1/1 [==============================] - 0s 12ms/step - loss: 7.7979e-05 - accuracy: 1.0000 - val_loss: 0.7102 - val_accuracy: 0.8000

上記スクリプトで学習が完了すると自動的に判定を実行する。
実行結果だけだとわかりにくいので、実際に判定された写真を添えて見てみよう。

判定結果

data/test/apple/huji_mutai1.jpg
label: 0 result: 0(判定は result:0 = りんご。正解)

data/test/apple/keyvisual02.jpg
label: 0 result: 0(判定は result:0 = りんご。正解)

data/test/apple/reitou--770x513.jpg
label: 0 result: 0(判定は result:0 = りんご。正解)

data/test/apple/1609302619_2.jpg
label: 0 result: 0(判定は result:0 = りんご。正解)

data/test/apple/51qu+EJFNuL._SL500_.jpg
label: 0 result: 0(判定は result:0 = りんご。正解)

data/test/orange/resized_6800084d-2f12-419b-a2b1-81d7aad38001.jpg
label: 1 result: 1(判定は result:1 = オレンジ。正解)

data/test/orange/sheraz-shaikh-Zx3kcU2Kw9E-unsplash.jpg
label: 1 result: 1(判定は result:1 = オレンジ。正解)

data/test/orange/7202_orange3.jpg
label: 1 result: 1(判定は result:1 = オレンジ。正解)

data/test/orange/wide_thumbnail_normal.jpg
label: 1 result: 1(判定は result:1 = オレンジ。正解)

正解率

seikai:  100.0 %(正解率100%)

今回は正解率100%だったが、毎度100%ではなくて90%になることもある。

感想

とりあえずわからんので、乱暴に1次元配列に変換してやってみたら、まあそれなりに判別できた。ただ、これだと顔の判別とかは難しそうだ。より微妙な違いを判別する方法にチャレンジしていきたい。

Discussion