Python 自前の画像を分類するため、畳み込みニューラルネットワーク(CNN)による学習器の作成「TensorFlow2」

'21/03/23更新:
 本記事では、自前の画像を分類する学習器を作成する雛形コードを載せました。kerasを利用したシーケンシャルなコードです。
#####
tensorflow2.4.1で動作確認しています。tensorflow2系のインストール方法は、Anacondaのverによっては、pip install tensorflow で行う必要もありました。anaconda環境で行う場合は仮想環境で行うことをおススメします。
#####

 はじめに、お試し用にjpg画像ファイルを作成するのは大変なためにネットから入手する方法です。例えば、手書き数字のjpg画像ファイル群は、Kaggleのhttps://www.kaggle.com/scolianni/mnistasjpgにあるDownloadボタンから、圧縮ファイル「archive.zip」を入手できます。このarchive.zipを展開すると、更に圧縮ファイル「trainingSet.tar.gz」と「testSet.tar.gz」の2つがあります。

 trainingSet.tar.gzを展開すると、フォルダ「trainingSet」の中に下図のように「0」~「9」の数字のフォルダがあり、各フォルダ内に各数字のjpg画像ファイルが格納されています。これらを教師データとして学習させることになります。

f:id:HK29:20210323222443p:plain

本プログラム内で上記「trainingSet」のフォルダパスを指定して実行すると、下図のように分類学習器とその重みのデータをバイナリファイルとして保存します。

f:id:HK29:20210323224022p:plain

 そして、下図のように学習履歴をグラフで画像ファイルに保存します。

f:id:HK29:20210328192100p:plain
■本プログラム

# -*- coding: utf-8 -*-
import os, glob
import numpy as np
import tensorflow as tf
#print("Num GPUs:", len(tf.config.list_physical_devices('GPU')))
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.preprocessing.image import array_to_img, img_to_array, load_img
from tensorflow.keras.utils import to_categorical
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
plt.rcParams['font.size'] = 18


# 学習過程をグラフ化
def plot_history(history, save_graph_img_path):

    acc = history.history['accuracy']
    val_acc = history.history['val_accuracy']
    loss = history.history['loss']
    val_loss = history.history['val_loss']

    plt.figure(figsize=(10, 8))
    plt.subplot(1, 2, 1)
    plt.plot(history.epoch, acc, label='Training Accuracy')
    plt.plot(history.epoch, val_acc, label='Validation Accuracy')
    plt.legend(loc='lower right')
    plt.title('Accuracy')
    plt.xlabel('Epochs')
    plt.grid()

    plt.subplot(1, 2, 2)
    plt.plot(history.epoch, loss, label='Training Loss')
    plt.plot(history.epoch, val_loss, label='Validation Loss')
    plt.legend(loc='lower left')
    plt.title('Loss')
    plt.xlabel('Epochs')
    plt.grid()
        
    plt.legend()
    plt.tight_layout()
    plt.savefig(save_graph_img_path)
    plt.close()


# メイン関数
def main():
    data_x = []
    data_y = []

    # 教師データと分類画像を読み込む
    for label_data in label_data_list:
        # 各ラベルの画像のある親フォルダパスとラベル番号をアンパック代入
        label_path, label_no = label_data
        # 各ラベルの各画像ファイルパスをリストで取得
        file_path_list = glob.glob(label_path + '/*.jpg')

        # 各画像にラベルを設定する
        for file_path in file_path_list:
            # 画像をサイズ変換
            img = img_to_array(load_img(file_path, target_size=(img_width, img_height, img_ch)))
            data_x.append(img) # 画像
            data_y.append(label_no) # ラベルを設定

    # numpyアレイ型へ変換
    x_np = np.asarray(data_x)
    y_np = np.asarray(data_y)

    # 学習用とテスト用にデータを分割
    x_train, x_test, y_train, y_test = train_test_split(x_np, y_np, test_size=test_size)

    # 学習データをfloat32型に変換する。
    x_train = x_train.astype('float32')
    x_test = x_test.astype('float32')
    # 正規化する(0~1)
    x_train = x_train / 255.0
    x_test = x_test / 255.0

    # 正解ラベルをone hotエンコーディング
    y_train = to_categorical(y_train, myclasses)
    y_test = to_categorical(y_test, myclasses)

    # データセットの個数を表示
    print(r'x train, y_train')
    print(x_train.shape, x_train.shape)
    print(r'x test, y_test')
    print(x_test.shape, y_test.shape)

    # モデルの構築
    model = keras.Sequential([
        keras.layers.Conv2D(filters = 32, # 2次元フィルタの枚数
                            kernel_size = (3,3), # フィルタのサイズ(縦, 横)
                            padding = "same", # ゼロパンディング。フィルタリングによる画素数低下の防止
                            activation = "relu", # 活性化関数。Noneの場合は線形活性
                            input_shape = x_train.shape[1:]), # 読み込むデータ。28×28の1チャネル。前処理しないRGBの場合は3チャネル
        keras.layers.MaxPooling2D(pool_size=(2,2)), # プーリング層(2×2の最大プーリング手法)。座標ずれの感度を低くする
        keras.layers.Flatten(), # 1次元に変換
        keras.layers.Dense(64, activation='relu'),
        keras.layers.Dropout(drop_rate), # ドロップアウト率
        keras.layers.Dense(myclasses, activation='softmax') # ソフトマックス関数で、指定数のクラスに分類する。
    ])

    # モデル構造の表示
    model.summary()

    # アルゴリズムの設定
    model.compile(optimizer='adam', 
                  loss='categorical_crossentropy',
                  metrics=['accuracy'])

    # 学習する。履歴をオブジェクトへ保存
    history = model.fit(x_train, 
                        y_train, 
                        batch_size=batch_size, 
                        epochs=epochs, 
                        verbose=1, 
                        validation_split=0.3)

    # テストデータで学習器を評価
    score = model.evaluate(x_test, y_test, verbose=0)
    print('Test loss:', score[0]) # 損失値
    print('Test accuracy:', score[1]) # 正答率

    # 学習履歴をグラフ化
    plot_history(history, save_graph_img_path = 'history.png') 

    # 分類学習器をjsonファイルで保存
    open('model.json', 'w').write(model.to_json())  

    # 学習器の重みをバイナリファイルで保存
    model.save_weights('model_weight.hdf5')


if __name__ == '__main__':
    print(tf.__version__)
    
    # 教師画像を格納したフォルダパスを取得する
    dir_path_for_set_label = './trainingSet' # 親フォルダを指定
    
    label_data_list = []
    cnt = 0    
    for dirpath, dirnames, fnames in os.walk(dir_path_for_set_label):
        parent_dirname = dirpath.split(os.path.sep) # リスト化
        if len(parent_dirname) >= 2: # 子ディレクトリの場合に処理
            label_data_list.append((dirpath, cnt))
            cnt += 1
    print(label_data_list)
    myclasses = len(label_data_list) # 分類クラス数
    print(myclasses)

    # ハイパーパラメータ
    test_size = 0.3
    epochs = 50 # 世代数 default 200
    batch_size = 16 # バッチサイズ default 32
    drop_rate = 0.3 # ドロップアウト率
    
    # 画像の読み込み設定
    img_width = 28 # 横
    img_height = 28 # 縦
    img_ch = 3 # 3ch(RGB)
    
    main()

 分類学習器を作成後、これを使用して手書き画像を分類することになります。ここで、もう一方のtestSet.tar.gzを展開すると、testSetというフォルダがひとつあって下図のように0~9のjpg画像ファイルが格納されています。

f:id:HK29:20210323222936p:plain

これを分類する雛形コードは次のリンク先を参照下さい。

hk29.hatenablog.jp

以上

<広告>