Atsumaru Engineer's Blog

集客プラットフォーム事業を手がける株式会社あつまるのエンジニアブログです

【機械学習】TensorflowとKerasで、ブライダルの画像判定ソフトを作ってみた

f:id:snoopy_no_sora:20190116110324p:plain

弊社は様々な業界の集客をお手伝いしています。
その一つがブライダルです。

ブライダル業界の集客において、画像(ビジュアル)は最重要事項の一つです。
素敵な結婚式をイメージできるビジュアルに、花嫁たちは魅かれます。

そのビジュアルは、ターゲット(花嫁)の趣味嗜好に応じていくつかのパターンに分類されます。

今回は300枚のデータを用意し、訓練したモデルをもとに、新しいビジュアルを読み込んでクラス分類するソフトを作成します。

作るもの(ゴール)

サンプル画像を読み込み、どのクラスに所属するか判定するソフトを作る。

大まかなステップ

①画像データ収集・分類 ②データ生成 ③cnnモデル定義・訓練・評価 ④推定モデル作成

①画像データ収集・分類

今回はブライダルの画像300枚を、下記3つのパターンに分類しました。
▼「A」フォルダ(100枚)……チャペルをイメージとした、「ザ・結婚式」な写真
(例)

▼「F」フォルダ(100枚)……友人たちと歓談する、フレンドリーな雰囲気の写真
(例)

▼「O」フォルダ(100枚)……その他
(例)

※ディレクトリ構成

visual_ml
├─ A
│  └(画像100枚)
├─ F
│  └(画像100枚)
├─ O
│  └(画像100枚)
│   
├── gen_data.py // データを生成する
├── predict.py // 画像を読み込んで推定する
├── visual_cnn.h5
├── visual_cnn.py // cnnモデル定義
├── visual.npy
└── sample.jpg

②データ生成

gen_data.pyで、300枚の画像を良い感じにデータ変換し、保存します。

# gen_data.py
from PIL import Image
import os, glob
import numpy as np
from sklearn import model_selection

classes = ['A', 'F', 'O']
num_classes = len(classes)
image_size = 50

# 画像の読み込み

X = []
Y = []
for index, classlabel in enumerate(classes):
    photos_dir = './' + classlabel # classesからフォルダ名をfor文で取得
    files = glob.glob(photos_dir + '/*.jpg') # 画像のパスとファイル名を取得
    for i, file in enumerate(files):
        if i > 100: break # 画像が100枚を越えるとbreak
        image = Image.open(file) # Image.openで画像ファイルを開く
        image = image.convert('RGB') # 画像をRGB(Red,Green,Blue)に変換
        image = image.resize((image_size, image_size)) # 画像をimage_size(50px)に変換
        data = np.asarray(image) # 画像をnumpy配列に変換
        X.append(data) # リストXに追加する
        Y.append(index) # リストYに追加する

# Tensorflowが扱いやすいデータ型に揃える
X = np.array(X)
Y = np.array(Y)

# scikitlearnのmodel_selectionを使って、300枚の画像XとラベルYを「訓練用」と「テスト用」に分類
X_train, X_test, y_train, y_test = model_selection.train_test_split(X, Y)
xy = (X_train, X_test, y_train, y_test)

#分割したデータ一式をnpy形式で同階層に保存
np.save('./visual.npy', xy)

③cnnモデル定義・訓練・評価

②で保存したデータをもとに、cnnモデルを定義して保存します。 model_eval()の実行により、学習結果の精度を評価します。

# visual_cnn.py
from keras.models import Sequential
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import np_utils
import keras
import numpy as np

classes = ['A', 'F', 'O']
num_classes = len(classes)
image_size = 50

# メインの関数を定義する
def main():
    # visual_cnn.pyで作成したvisual.npyをloadする
    X_train, X_test, y_train, y_test = np.load("./visual.npy")
    # Xのデータをfloat型にして、各値を最大値が1になるように256で割る
    X_train = X_train.astype("float") / 256
    X_test = X_test.astype("float") / 256
    # yの分類を数値(0="A",1="F",2="O")に変換
    y_train = np_utils.to_categorical(y_train, num_classes)
    y_test = np_utils.to_categorical(y_test, num_classes)

    # 訓練関数
    model = model_train(X_train, y_train)
    # 評価関数
    model_eval(model, X_test, y_test)

def model_train(X, y): # CNN
    model = Sequential()
    model.add(Conv2D(32, (3, 3), padding = 'same', input_shape = X.shape[1:]))
    model.add(Activation('relu'))
    model.add(Conv2D(32, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2, 2)))
    model.add(Dropout(0.25))

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

    model.add(Flatten())
    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
    model.add(Dense(3))
    model.add(Activation('softmax'))

    opt = keras.optimizers.rmsprop(lr = 0.0001, decay = 1e-6)
    model.compile(loss = 'categorical_crossentropy', optimizer = opt, metrics = ['accuracy'])
    model.fit(X, y, batch_size = 32, epochs = 100)

    # モデルの保存
    model.save('./visual_cnn.h5')

    return model

def model_eval(model, X, y):
    scores = model.evaluate(X, y, verbose = 1)
    print('Test Loss: ', scores[0])
    print('Test Accuracy: ', scores[1])

if __name__ == "__main__":
    main()

④推定モデル作成

コマンドライン引数で読み込む画像を指定し、判定を行います。 ※モデル定義自体は②と同じです。

# predict.py
from keras.models import Sequential, load_model
from keras.layers import Conv2D, MaxPooling2D
from keras.layers import Activation, Dropout, Flatten, Dense
from keras.utils import np_utils
import keras, sys
import numpy as np
from PIL import Image

classes = ['A', 'F', 'O']
num_classes = len(classes)
image_size = 50

def build_model(): # CNN
    model = Sequential()
    model.add(Conv2D(32, (3, 3), padding = 'same', input_shape = (50, 50, 3)))
    model.add(Activation('relu'))
    model.add(Conv2D(32, (3, 3)))
    model.add(Activation('relu'))
    model.add(MaxPooling2D(pool_size = (2,2)))
    model.add(Dropout(0.25))

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

    model.add(Flatten())
    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.5))
    model.add(Dense(3))
    model.add(Activation('softmax'))

    opt = keras.optimizers.rmsprop(lr = 0.0001, decay = 1e-6)

    model.compile(loss = 'categorical_crossentropy', optimizer = opt, metrics = ['accuracy'])

    # モデルの保存
    model = load_model('./visual_cnn.h5')

    return model

def main():
    image = Image.open(sys.argv[1])
    image = image.convert('RGB')
    image = image.resize((image_size, image_size))
    data = np.asarray(image)
    X = []
    X.append(data)
    X = np.array(X)
    model = build_model()

    result = model.predict([X])[0]
    predicted = result.argmax()
    percentage = int(result[predicted] * 100)
    print('{0}({1}%)'.format(classes[predicted], percentage))

if __name__ == '__main__':
    main()

実際に推定してみます。
友人たちとワイワイして、カップルがこちらを見ている下記の画像を読み込ませてみます。
なんとなくFっぽいですね。

コマンドラインの第二引数(左から3番目)にsample.jpgをおきます。

$ python predict.py sample.jpg



「F」の画像である確率が100%であると推定されました。
想定通り分類されています。

所感

実際のブライダルの画像は、3パターン(「ザ・結婚式」「みんなでワイワイ」「その他」)で分類できるほど単純ではないのが実情です。
ここからは、
・分類パターンを増やす
・教師なし学習で、クラスタリングする
などの展開ができます。
今回はシンプルな推定モデルの作成までやってみました。