Python Keras(TensorFlow2.0)による畳み込みニューラルネットワーク,CNN

 畳み込みニューラルネットワーク, CNN(Convolutional Neural Network)は、画像分類において、画像の位置ずれに強くしたNNです。そのため、画像認証のマルチクラス分類に活用されます。特徴は、2次元フィルタによる畳み込み層と(最大)プーリング層の2種類の中間層で構築することです。

 ここで、TensorFlowのチュートリアルのひとつで、Kerasを用いたNNの分類問題の例が下記リンクですhttps://www.tensorflow.org/tutorials/keras/classification

 本記事では、上記リンクの普通のNNに加えてCNNで実行する雛形コードとその比較結果を載せます。使用するデータセットは、Fashion MNIST(ファッションエムニスト)と呼び、下表のように衣料品10カテゴリの画像が7000枚あります。その内の6000枚を訓練データ、1000枚をテストデータとして使用します。

ラベル クラス
0 T-shirt/top
1 Trouser
2 Pullover
3 Dress
4 Coat
5 Sandal
6 Shirt
7 Sneaker
8 Bag
9

Ankle boot

処理の流れは大まかには、下図左のようにカラー画像(0~255の数値データ:256階調8bitRGB)を下図右のように白黒画像(0~1の数値データ:10階調白黒)へ正規化して情報量を減らし、輪郭と濃淡から画像を識別して分類します。

f:id:HK29:20200505223143p:plain

即ち、下図のように各ピクセル(画素)の数値データを操作します。

f:id:HK29:20200505224027p:plain

下図は、本コードを実行した結果である。下図左はNN、右はCNNの各々15枚の結果を可視化した画像である。棒の高さは予測時の自身満々度を表す。そして、青色は的中、赤色はハズレを表す。よって、下図左の赤色は自身満々にハズした結果を意味する。

f:id:HK29:20200505224646p:plain

そのNNがハズした内容は、下図のように87%の確率でサンダルとして誤認識した。

f:id:HK29:20200505225038p:plain

一方、CNNの結果は、下図のように60%の確率でスニーカーとして的中した。

f:id:HK29:20200505225237p:plain

 目的関数(損失関数:sparse_categorical_crossentropy)の世代進行に伴う変化の様子を下図に示す。下図左はNNで下図右はCNNである。CNNの方がlossが小さく、良いことがわかる。但し、解析時間はNNは44秒、CNNは375秒(6分25秒)で、CNNは8.5倍の時間を要した。つまり、精度と解析時間はトレードオフの関係にある。

f:id:HK29:20200505225639p:plain

■本プログラム
 CNNは、フラグ「CNN_flag = True」で実行、NNは「CNN_flag = False」で実行する仕様です。適合度には、ハイパーパラメータの影響も少なからずあります。それらは、世代数、バッチ数、隠れ層の数、ニューロン数、学習率、ドロップ率、最適化アルゴリズムのオプションです。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# https://www.tensorflow.org/tutorials/keras/classification
import tensorflow as tf
from tensorflow import keras
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import time

# データの観察と正規化をする関数
def check_data_and_normalization(train_images, train_labels, test_images, test_labels):
    # 画像データの構成
    print("train_images", train_images[0]) # 28×28の2次元配列「28×28のピクセル(画素)8ビットのnumpyアレイ形式」
    print("train_images.shape", train_images.shape) # (60000, 28, 28)←訓練用の28×28のデータ数が6万枚
    print("train_labels", len(set(train_labels))) # 10←ラベル数をチェック。重複があるため一旦setに変換後に要素数をカウント
    print("test_images.shape", test_images.shape) # (10000, 28, 28)←テスト用のデータ数は1万枚

    # 代表画像を表示
    plt.figure()
    plt.imshow(train_images[0]) # 1画素0~255階調の数値データであることがわかる
    plt.colorbar()
    plt.grid(False)
    #plt.show()
    plt.title(class_names[train_labels[0]])
    plt.savefig('No0.png')
    plt.close()

    # 代表画像を数値データで表示
    img_cols = test_images[0].shape[1] # 列数
    pd.options.display.max_columns = img_cols # pandasのカラム表示の設定を変更
    df_img_matrix = pd.DataFrame(train_images[0]) # numpyのArray形式からpandasのDataFrame形式へ変換
    df_img_matrix_sorted = df_img_matrix.sort_index(ascending=False) # インデックスで降順にソートする
    #print(df_img_matrix_sorted)
    df_img_matrix_sorted.to_csv('No0.csv')

    # データの前処理
    # 0~255階調の数値データを0~1の範囲に変換(正規化)
    normalized_train_images = train_images / 255.0
    normalized_test_images = test_images / 255.0
    # 前処理後の画像を表示
    plt.figure()
    plt.imshow(normalized_train_images[0], cmap=plt.cm.binary)
    plt.colorbar()
    plt.grid(True)
    #plt.show()
    plt.title(class_names[train_labels[0]])
    plt.savefig('normalized_No0.png')
    plt.close()

    # 代表画像を数値データで表示
    df_img_matrix = pd.DataFrame(normalized_train_images[0])
    df_img_matrix_sorted = df_img_matrix.sort_index(ascending=False) # インデックスで降順にソートする
    #print(df_img_matrix_sorted)
    df_img_matrix_sorted.to_csv('normalized_No0.csv')
    
    return normalized_train_images, normalized_test_images

# 予測結果のパーセンテージを画像に記載する関数
def plot_image(i, predictions_array, true_label, img):
    if len(predictions_array) != 1:
        j = i
    else:
        j = 0 # 予測値のリスト内が1枚のみの場合(1枚ずつ処理)、ゼロを代入する

    predictions_array, true_label, img = predictions_array[j], true_label[i], img[i]
    plt.grid(False)
    plt.xticks([])
    plt.yticks([])
    plt.imshow(img, cmap=plt.cm.binary)

    predicted_label = np.argmax(predictions_array)
    if predicted_label == true_label:
        color = 'blue'
    else:
        color = 'red'

    plt.xlabel("{} {:2.0f}% ({})".format(class_names[predicted_label],
                                         100*np.max(predictions_array),
                                         class_names[true_label]),
                                         color=color)

# 予測結果を棒グラフ化する関数
def plot_value_array(i, predictions_array, true_label):
    if len(predictions_array) != 1:
        j = i
    else:
        j = 0 # 予測値のリスト内が1枚のみの場合(1枚ずつ処理)、ゼロを代入する

    predictions_array, true_label = predictions_array[j], true_label[i]
    plt.grid(False)
    plt.xticks([])
    plt.yticks([])
    thisplot = plt.bar(range(10), predictions_array, color="#777777")
    plt.ylim([0, 1]) 
    predicted_label = np.argmax(predictions_array)

    thisplot[predicted_label].set_color('red')
    thisplot[true_label].set_color('blue')

# 予測結果を可視化するための親関数
def visualization(predictions, test_labels, test_images, test_images_ori):
    # X個のテスト画像、予測されたラベル、正解ラベルを表示します。
    # 正しい予測は青で、間違った予測は赤で表示しています。
    num_rows = 5
    num_cols = 3

    num_images = num_rows * num_cols
    plt.figure(figsize=(2 * 2 * num_cols, 2 * num_rows))
    for i in range(num_images):
        plt.subplot(num_rows, 2 * num_cols, 2 * i + 1)
        plot_image(i, predictions, test_labels, test_images_ori)
        plt.subplot(num_rows, 2 * num_cols, 2 * i + 2)
        plot_value_array(i, predictions, test_labels)
    #plt.show()    
    plt.savefig(file_name + '_summary.png')
    plt.close()

    for i in range(num_rows*num_cols):
        # テスト用データセットから画像を1枚取り出す
        img = test_images_ori[i]
        #print('img.shape', img.shape)例:(28, 28)

        # 画像1枚の数値データをリスト化(tf.kerasモデルはバッチ処理のため)
        img = (np.expand_dims(img,0)) # サイズ1の次元を追加する。例:(1, 28, 28)
        #print('img.shape', img.shape)
        
        # 予測
        if CNN_flag:
            img = img.reshape(img.shape[0], img_rows, img_cols, 1)
        predictions_single = model.predict(img)

        # 画像出力
        #print('predictions_single', predictions_single)
        plt.figure(figsize=(7,3))
        plt.subplot(1,2,1)
        plot_image(i, predictions_single, test_labels, test_images_ori)
        plt.subplot(1,2,2)
        plot_value_array(i, predictions_single, test_labels) 
        _ = plt.xticks(range(10), class_names, rotation=45) # 横軸
        plt.tight_layout()
        #plt.show()
        plt.savefig(file_name + '_' + str(i) + '.png')
        plt.close()
        #print("np.argmax(predictions_single[0])", np.argmax(predictions_single[0]))

# メイン関数
def main():
    global class_names, model, img_rows, img_cols

    start_time = time.time()
    ##### データのロード
    fashion_mnist = keras.datasets.fashion_mnist
    (train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
    # データセットにはクラス名がないため、後ほど画像出力時に使用するために定義
    class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat', 
                   'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']

    ##### データの正規化
    train_images, test_images = check_data_and_normalization(train_images, train_labels, test_images, test_labels)
    # データチェック
    img_rows = train_images[0].shape[0] # 行数
    img_cols = test_images[0].shape[1] # 列数
    print('img_rows', img_rows)
    print('img_cols', img_cols)

    #####
    # モデルの構築
    if CNN_flag: # 畳み込みニューラルネットワーク:Convolutional Neural Network(CNN)
        print('CNN_flag', CNN_flag)
        print(keras.backend.image_data_format())
        # 2D畳み込みニューラルネットワークの場合、4次元配列にする
        # change from (6000,28,28) to (6000,28,28,1)
        train_images = train_images.reshape(train_images.shape[0], img_rows, img_cols, 1)
        test_images_bp = test_images # 後に可視化するためにバックアップ
        print("test_images test_images_bp")
        print(test_images, test_images_bp) 
        test_images = test_images.reshape(test_images.shape[0], img_rows, img_cols, 1)
        
        model = keras.Sequential([
            keras.layers.Conv2D(filters = 32, # 2次元フィルタの枚数
                                kernel_size = (3,3), # フィルタのサイズ(縦, 横)
                                padding = "same", # ゼロパンディング。フィルタリングによる画素数低下の防止
                                activation = "relu", # 活性化関数。Noneの場合は線形活性
                                input_shape = (img_rows,img_cols,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(10, activation='softmax') # ソフトマックス関数で、10個のクラスに分類する。
        ])
    else: # 普通のニューラルネットワーク:Neural Network(NN)
        model = keras.Sequential([
            keras.layers.Flatten(input_shape=(img_rows, img_cols)),
            keras.layers.Dense(64, activation='relu'),
            keras.layers.Dropout(drop_rate),
            keras.layers.Dense(10, activation='softmax')
        ])

    #最適化アルゴリズムの設定
    optimization_algorithm = keras.optimizers.Adam(lr=learning_rate, 
                                                   beta_1=0.9,
                                                   beta_2=0.999,
                                                   epsilon=1e-08)
    
    # モデルのコンパイル
    model.compile(loss = 'sparse_categorical_crossentropy', # 損失関数
                  optimizer = optimization_algorithm,
                  metrics = ['accuracy']) # 評価関数←ウォッチしてるだけ。訓練には使われない。

    # モデルのサマリー
    print('model.summary\n', model.summary)

    # 訓練データを用いてモデルの訓練
    early_stopping = keras.callbacks.EarlyStopping(monitor='loss', patience=10, verbose=1) # 枝刈り
    hist = model.fit(train_images,
                     train_labels,
                     batch_size = batch_size,
                     epochs = epochs,
                     callbacks = [early_stopping])

    # 学習曲線をグラフ化
    loss = hist.history['loss']
    plt.rc('font', family='serif')
    fig = plt.figure()
    if CNN_flag:
        mycolor = 'red'
    else:
        mycolor = 'blue'
    plt.plot(np.arange(1, len(loss)+1), loss, label='loss', color=mycolor)
    plt.xlabel('epochs')
    plt.ylabel('loss')
    print(len(loss))
    print(type(len(loss)))
    plt.xticks(np.arange(1, int(len(loss)+1), 1))
    plt.ylim([0,1])
    plt.grid()
    plt.title(file_name)
    plt.savefig(file_name + '_epochs.png')
    plt.close()

    # テストデータを用いて正解率の評価
    test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
    print('\nTest loss:', test_loss)
    print('Test accuracy:', test_acc)

    # 予測
    predictions = model.predict(test_images)

    ##### 予測結果を抜き取り検査(可視化)
    if CNN_flag:
        visualization(predictions, test_labels, test_images, test_images_bp)
    else:
        visualization(predictions, test_labels, test_images, test_images)
    model.save(file_name + '_model.h5')
    print('analysis time:{0:.3f} sec'.format(time.time() - start_time))
    
    with open('result.txt', 'a') as f:
        f.write(file_name + '_test_loss=' + str(test_loss) + '\n')
        f.write(file_name + '_test_acc=' + str(test_acc) + '\n')
        f.write(file_name + '_time=' + str(time.time() - start_time) + '\n')
    
if __name__ == '__main__':
    print(tf.__version__)
    
    # parameter
    CNN_flag = True
    if CNN_flag:
        file_name = 'CNN'
    else:
        file_name = 'NN'

    epochs = 10 # 世代数
    batch_size = 32 # バッチサイズ default 32
    learning_rate = 0.001 # 学習率 default 0.001
    drop_rate = 0.5 # ドロップアウト率
    
    # call function
    main()

以上

<広告>