MNISTやCIFAR-10でモデル評価時に間違った画像を確認する方法

間違った画像を確認してみよう

こんにちは。

AI coordinatorの清水秀樹です。

以前からモデル評価時に間違った画像を確認してみたいと思っていたため、今回改めて実装してみました。

間違った画像を確認する方法を紹介しているサイトが見つからなかったので、実装に結構苦労しました。

(単純に筆者のスキルが無いだけですが・・・)

 

興味がある方は、ぜひ試してみてください。

MNISTで間違った画像を確認する方法

間違った画像を保存するフォルダを作成し、以下のソースを貼り付けるだけで、間違った評価をした画像を確認できるようになります。

from PIL import Image

categories = [ "0","1", "2" , "3", "4" , "5", "6" , "7", "8" , "9"]

pre = model.predict(X_test)
for i,v in enumerate(pre):
    pre_ans = v.argmax()
    ans = y_test[i].argmax()
    dat = X_test[i]
    if ans == pre_ans: continue
    fname = "NG_photo/" + str(i) + "-" + categories[pre_ans] + \
        "-ne-" + categories[ans] + ".png"
    dat *= 255
    img = Image.fromarray(dat.reshape((28,28))).convert("RGB")
    img.save(fname)

 

MNISTソースコードの紹介

以下、ソースコードです。

そのままでも動くと思います。

from keras.datasets import mnist
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation
from keras.optimizers import RMSprop
from keras.utils import np_utils
import matplotlib.pyplot as plt
from PIL import Image
import numpy as np

categories = [ "0","1", "2" , "3", "4" , "5", "6" , "7", "8" , "9"]

def build_model():
    # モデルの作成
    model = Sequential()
    model.add(Dense(512, input_shape=(784,)))
    model.add(Activation('relu'))
    model.add(Dropout(0.2))

    model.add(Dense(512))
    model.add(Activation('relu'))
    model.add(Dropout(0.2))

    model.add(Dense(10))
    model.add(Activation('softmax'))

    # 損失関数の定義
    model.compile(
        loss='categorical_crossentropy',
        optimizer=RMSprop(),
        metrics=['accuracy'])
    
    return model

def plot_history(history):
    # 精度の履歴をプロット
    plt.plot(history.history['acc'],"o-",label="accuracy")
    plt.plot(history.history['val_acc'],"o-",label="val_acc")
    plt.title('model accuracy')
    plt.xlabel('epoch')
    plt.ylabel('accuracy')
    plt.legend(loc="lower right")
    plt.show()
 
    # 損失の履歴をプロット
    plt.plot(history.history['loss'],"o-",label="loss",)
    plt.plot(history.history['val_loss'],"o-",label="val_loss")
    plt.title('model loss')
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.legend(loc='lower right')
    plt.show()

if __name__ == "__main__":
    # MNISTのデータの読み込み
    # 訓練データ6万件、テストデータ1万件
    # 28ピクセル × 28ピクセル = 784ピクセルのデータ
    # 色は0〜255
    (X_train, y_train), (X_test, y_test) = mnist.load_data()
    X_train = X_train.reshape(60000, 784).astype('float32') / 255
    X_test  = X_test.reshape(10000, 784).astype('float32') / 255
    print('X_train shape:', X_train.shape)

    # 10次元配列に変換 //数字の5ならこんな感じ[0,0,0,0,1,0,0,0,0,0]
    y_train = np_utils.to_categorical(y_train, 10)
    y_test  = np_utils.to_categorical(y_test, 10)

    # データで訓練 今回は時間省略のため5回で学習する
    model = build_model()
    history = model.fit(X_train, y_train, 
        nb_epoch=5, 
        batch_size=500, 
        validation_data=(X_test, y_test)
    )


    #学習モデルの保存
    json_string = model.to_json()
    #モデルのファイル名 拡張子.json
    open('mnist.json', 'w').write(json_string)
    #重みファイルの保存 拡張子がhdf5
    model.save_weights('mnist.hdf5')
    
    # 間違った画像を出力する。
    pre = model.predict(X_test)
    for i,v in enumerate(pre):
        pre_ans = v.argmax()
        ans = y_test[i].argmax()
        dat = X_test[i]
        if ans == pre_ans: continue
        fname = "NG_photo/" + str(i) + "-" + categories[pre_ans] + \
            "-ne-" + categories[ans] + ".png"
        dat *= 255
        img = Image.fromarray(dat.reshape((28,28))).convert("RGB")
        img.save(fname)

    # モデルの評価を行う
    score = model.evaluate(X_test, y_test, verbose=1)

    print('loss=', score[0])
    print('accuracy=', score[1])
    
    # modelに学習させた時の変化の様子をplot
    plot_history(history)

 

MNISTの結果確認

どんな画像を間違えたか確認してみました。

なんの数字だか分かりますでしょうか?

正解は4です。

学習モデルは6と認識したようです。

 

その他に間違えた画像は以下のような画像がありました。

人間がみても4なのか9なのか、0なのか6なのか判断が難しいものがありますね。

 

こうやって間違った画像を確認してみると、学習モデルの精度が100%の認識率でないと使えないと思われがちになりますが、そもそも人間の目で確認しても正解が分からないような画像もあるので、100%の認識率でないからダメと見切りをつけるのは早計な気がしますね。

 

CIFAR-10でも試してみよう

続いてCIFAR-10 datesetでも試してみましょう。

こちらはConvolutional Neural Networkで構築した学習モデルで間違った画像を表示してみます。

なのでMNISTで紹介したソースコードとは若干異なります。

間違った画像を抽出するソースコードも多少異なります。

また、折角なので学習モデル図も作成するようにしています。

 

CIFAR-10用のソースコードは以下を参考にしてください。

# coding:utf-8
import numpy as np
import scipy.misc
import tensorflow as tf
from keras.utils import np_utils
from keras.models import Sequential, Model, model_from_json
from keras.layers.core import Dense, Activation, Flatten, Dropout
from keras.layers.convolutional import Convolution2D
from keras.layers.convolutional import MaxPooling2D
from keras.optimizers import SGD
from keras.layers.normalization import BatchNormalization
from keras.callbacks import EarlyStopping, TensorBoard, ModelCheckpoint
from keras.datasets import cifar10
from keras.utils.visualize_util import plot

import keras.backend.tensorflow_backend as KTF
import matplotlib.pyplot as plt

from PIL import Image

#1024個の一層目を定義
in_shape = (32, 32, 3) #縦、横、RGB

#クラスを指定
categories = ["airplane","automobile","bird","cat","deer","dog","frog","horse","ship","truck"]
nub_classes = len(categories)

#モデルの作成
def build_model():
    model = Sequential()
    
    #畳み込み層の作成
    #1層目の追加  1024個の層を最初に作り、フィルター3*3のフィルターを16個作成
    model.add(Convolution2D(16, 3, 3, border_mode="same", input_shape=in_shape)) 
    model.add(Activation("relu"))
    
    #2層目の畳み込み層
    model.add(Convolution2D(16, 3, 3, border_mode="same"))
    model.add(Activation("relu"))
    
     #プーリング層
    model.add(MaxPooling2D())
    
    #Dropoutとは過学習を防ぐためのもの 0.5は次のニューロンへのパスをランダムに半分にするという意味
    model.add(Dropout(0.5))
    
    #3層目の作成
    model.add(Convolution2D(32, 3, 3, border_mode="same"))
    model.add(Activation("relu"))
    
    #4層目の作成
    model.add(Convolution2D(32, 3, 3, border_mode="same"))
    model.add(Activation("relu"))
    
     #プーリング層
    model.add(MaxPooling2D())
    model.add(Dropout(0.5))
    
    #5層目
    model.add(Convolution2D(64, 3, 3, border_mode="same"))
    model.add(Activation("relu"))
    
    #6層目
    model.add(Convolution2D(64, 3, 3, border_mode="same"))
    model.add(Activation("relu"))
    
    #プーリング層
    model.add(MaxPooling2D())
    
    #Dropout
    model.add(Dropout(0.5))
    
    #7層目
    model.add(Convolution2D(128, 3, 3, border_mode="same"))
    model.add(Activation("relu"))
    
    #Dropout
    model.add(Dropout(0.5))
    
    #平坦化
    model.add(Flatten())
    
    #8層目 全結合層 FC
    model.add(Dense(100))
    model.add(Activation("relu"))
    
    #Dropout
    model.add(Dropout(0.5))
    
    #8層目 引数nub_classesとは分類の数を定義する。
    model.add(Dense(nub_classes))
    model.add(Activation('softmax'))
    
    #ここまででモデルの層完成
    
    #lossは損失関数を定義するところ
    model.compile(loss="categorical_crossentropy", 
        metrics   = ["accuracy"], 
        optimizer = "adam"
    )
    
    #学習モデル図の作成
    plot(model, to_file='model.png')
    
    return model

def plot_history(history):
    # 精度の履歴をプロット
    plt.plot(history.history['acc'],"o-",label="accuracy")
    plt.plot(history.history['val_acc'],"o-",label="val_acc")
    plt.title('model accuracy')
    plt.xlabel('epoch')
    plt.ylabel('accuracy')
    plt.legend(loc="lower right")
    plt.show()

    # 損失の履歴をプロット
    plt.plot(history.history['loss'],"o-",label="loss",)
    plt.plot(history.history['val_loss'],"o-",label="val_loss")
    plt.title('model loss')
    plt.xlabel('epoch')
    plt.ylabel('loss')
    plt.legend(loc='lower right')
    plt.show()

if __name__ == "__main__":
    #ここに読み込み処理と関数呼び出しを行う
    
    #cifar10からデータを取得 10種類の画像データセットがある 計5万枚
    (X_train, y_train), (X_test, y_test) = cifar10.load_data()
    print(X_train.shape, y_train.shape)
    print(X_test.shape, y_test.shape)

    y_train = np_utils.to_categorical(y_train)
    y_test  = np_utils.to_categorical(y_test)

    X_train = X_train.astype('float32') / 255
    X_test  = X_test.astype('float32') / 255
    
    print("X_train", X_train.shape)
    print("y_train", y_train.shape)
    print("X_test", X_test.shape)
    print("y_test", y_test.shape)
    
    #ここからが上記で作成したモデルを呼び出す処理
    model = build_model()
    history = model.fit(X_train, y_train, 
        nb_epoch=10, #学習させる回数 今回は10 回数はお好みで pythonのnb_epochとはrangeの繰り返しのこと
        batch_size=500, #無作為に何枚学習に使用するか決定する。数字はなんでも良い
        validation_data=(X_test, y_test)
             )
    
    # モデルの評価する
    pre = model.predict(X_test)
    for i,v in enumerate(pre):
        pre_ans = v.argmax()
        ans = y_test[i].argmax()
        dat = X_test[i]
        if ans == pre_ans: continue
        print("[NG]", categories[pre_ans], "!=", categories[ans])
        print(v)
        fname = "NG_photo/" + str(i) + "-" + categories[pre_ans] + \
            "-ne-" + categories[ans] + ".png"
        dat *= 256
        img = Image.fromarray(np.uint8(dat))
        img.save(fname)
        
    #学習モデルの保存
    json_string = model.to_json()
    
    #モデルのファイル名 拡張子.json
    open('cifar10.json', 'w').write(json_string)
    
    #重みファイルの保存 拡張子がhdf5
    model.save_weights('cifar10.hdf5')

    # モデルの評価evaluate
    score = model.evaluate(X_train, y_train)
    
    print("test loss", score[0])
    print("test acc",  score[1])
    
    # modelに学習させた時の変化の様子をplot
    plot_history(history)

こちらも、そのままで動くはずです。

 

CIFAR-10の結果確認

それでは間違った画像を確認してみましょう。

今回はepoch=10なので、大した学習精度が出せていない状態での結果確認となります。

間違った画像の一部を紹介すると、

catをfrogと間違えたようです。

airplaneをshipと間違えたようです。

なるほどなるほど。

その他に間違えた画像の一例を載せておきます。

 

それではまた。

その他のDeepLearning記事はこちらから

あなたにオススメの記事

コメント

    • 井上 英耶
    • 2019年 3月 11日

    いつも勉強させていただいています。

    初心者の質問で大変恐縮ですが、MNISTのコードを実行したところ、以下のエラーがでます。

    間違った写真を保存するフォルダをどのような名前で、どこに作成したらいいのでしょうか?

    実行コードがあるファイルに『fname』というフォルダを作成したのですが、ダメでした。

    ご教授いただければ幸いです。
     
    Train on 60000 samples, validate on 10000 samples
    Epoch 1/5
    60000/60000 [==============================] – 5s 79us/step – loss: 0.3839 – acc: 0.8797 – val_loss: 0.1583 – val_acc: 0.9514
    Epoch 2/5
    60000/60000 [==============================] – 4s 70us/step – loss: 0.1414 – acc: 0.9571 – val_loss: 0.0885 – val_acc: 0.9729
    Epoch 3/5
    60000/60000 [==============================] – 4s 72us/step – loss: 0.0915 – acc: 0.9712 – val_loss: 0.0813 – val_acc: 0.9750
    Epoch 4/5
    60000/60000 [==============================] – 4s 69us/step – loss: 0.0684 – acc: 0.9781 – val_loss: 0.0727 – val_acc: 0.9773
    Epoch 5/5
    60000/60000 [==============================] – 4s 70us/step – loss: 0.0532 – acc: 0.9832 – val_loss: 0.0663 – val_acc: 0.9815

    —————————————————————————
    FileNotFoundError Traceback (most recent call last)
    in
    92 dat *= 255
    93 img = Image.fromarray(dat.reshape((28,28))).convert(“RGB”)
    —> 94 img.save(fname)
    95
    96 # モデルの評価を行う

    C:\Anaconda3\envs\tf140\lib\site-packages\PIL\Image.py in save(self, fp, format, **params)
    1989 # Open also for reading (“+”), because TIFF save_all
    1990 # writer needs to go back and edit the written data.
    -> 1991 fp = builtins.open(filename, “w+b”)
    1992
    1993 try:

    FileNotFoundError: [Errno 2] No such file or directory: ‘NG_photo/115-9-ne-4.png’

    • 管理人の清水です。
    • 2019年 3月 11日

    井上さん。
    フォルダ名を「NG_photo」として頂ければ動くかと思います。

      • 井上 英耶
      • 2019年 3月 12日

      清水様

      ありがとうございます。

      無事できました。

      お忙しいところ恐縮です。

      今後とも宜しくお願い致します。

    • 井上 英耶
    • 2019年 3月 13日

    清水 様

     なんども聞いてしまい大変申し訳ありません。

     清水様のコードのおかげで、間違っている写真を取り出すことができ、大変勉強になっています。

     現在、3種類のプランクトンに対して、各60枚の学習データと各30まいのテストデータに対して評価を行っています。

     コードの最後に、

    test loss 0.017122555635650513
    test acc 0.99640522875817

     と表示され、テストデータの正解率は99.6%と思ったのですが、NG_Photoには5枚の写真が入っていました。

     テストデータは計90枚あったので、そのうち5枚の写真が間違っているなら、Test_accuracyは94.5%になると思うのですが、上述のように99.6%と表示されています。

     この違いはなぜでしょうか?

     ご教授いただければ幸いです。

      • 清水
      • 2019年 3月 15日

      井上さん

      詳細は私もしっかり調べていないので分かりません。
      epoch数やバッチサイズで式が変わってくるのかと思います。(想像ですが・・・)

        • 井上 英耶
        • 2019年 3月 17日

        清水様

        ご教授ありがとうございます。

        式等勉強します。

        色々ありがとうございます。

        • 井上 英耶
        • 2019年 3月 20日

         清水様

         いつもブログで勉強させていただいています。

         先週質問させていただいた不正解写真の数と、表示されるTest_accuracyの違いですが、CIFAR-10のコードの178行目にX_train, y_trainとなっているのですが、表示には、180, 181行目のように’test_…’となっているのが原因ということが分かりました。

         色々といつもありがとうございます。

         今後も勉強させていただきます。

  1. この記事へのトラックバックはありません。

PAGE TOP