mecobalamin’s diary

人間万事塞翁が馬

WSLとJupyter-notebookでCNN、その2、CNNを試す

前回の続き
mecobalamin.hatenablog.com

環境ができたので手持ちのデータセットを試してみる

こちらのサイトを参考にコードを作成した
kerasでCNN 自分で拾った画像でやってみる - Qiita

主な変更点は以下の3点

  • 画像の読み込み
  • mnist_cnn.pyのモデルを使用
  • label、predict、画像ファイル名を保存する

こちらの環境ではlist_picturesが動かなかったので
ファイルを読み込む関数Image_Listを定義した
globは引数で指定したパスのファイル名を再帰的に取得する
ファイル名だけ取得したいのでrelpathで処理している

モデルはmnist_cnn.pyのと同じモデルを使ってみた

画像の分類は手動で行って
negativeとpositiveのディレクトリに保存してある
それぞれの画像がどのように分類されているかを知りたかったので
画像のファイル名とそれぞれがどのようにpredictされているかを
csvファイルに保存した

fit()で出力されるlistのindexも
バージョン違いのせいか"accu"ではエラーが出るので
"accuracy"に修正してある

実際のコードは以下の通り

# ライブラリのインポート
import keras
from keras.utils import np_utils
from keras.layers.convolutional import Conv2D, MaxPooling2D
from keras.models import Sequential
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras.preprocessing.image import array_to_img, img_to_array, load_img
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt

import pandas as pd
import numpy as np
from glob import glob
from os.path import relpath

# ファイル名の取得
def Image_List(Path_Images, Ext):
    List_Images = glob(Path_Images + "/*." + Ext)
    List_Names = []
    for i in List_Images:
        List_Names.append(relpath(i, Path_Images))
    return List_Names

# 画像の拡張子
Ext_Type = "png"

# 画像のパス
Path_Negative = '/path/to/negative/'
Path_Positive = '/path/to/positive/'

X = []
Y = []

# ネガティブ画像の読み込み
Image_Negative = Image_List(Path_Negative, Ext_Type)
for picture in Image_Negative:
    img = img_to_array(load_img(Path_Negative + picture, color_mode = "grayscale", target_size=(256, 256)))
    X.append(img)

    Y.append(0)


# ポジティブ画像の読み込み
Image_Positive = Image_List(Path_Positive, Ext_Type)
for picture in Image_Positive:
    img = img_to_array(load_img(Path_Positive + picture, color_mode = "grayscale", target_size=(256, 256)))
    X.append(img)

    Y.append(1)


# arrayに変換
# Zはファイル名のリスト
X = np.asarray(X)
Y = np.asarray(Y)
Z = pd.DataFrame(Image_Negative + Image_Positive)

# 画素値を規格化
X = X.astype('float32')
X = X / 255.0

# one-hot表現に変換
Y = np_utils.to_categorical(Y, 2)

# 学習用データとテストデータに分割
# ファイル名のリストも分割
X_train, X_test, y_train, y_test, indices_train, indices_test = train_test_split(X, Y, Z, test_size=0.33, random_state=111)

# モデルの構築
model = Sequential()

model.add(Conv2D(20, kernel_size=(3, 3),
                 activation='relu',
                 input_shape=X_train.shape[1:]))
model.add(Conv2D(40, (3, 3), 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(2))       # クラスは2個
model.add(Activation('softmax'))

# コンパイル
model.compile(loss='categorical_crossentropy',
              optimizer='SGD',
              metrics=['accuracy'])

# 実行
# 出力有り(verbose=1)。
history = model.fit(X_train, y_train, batch_size=300, epochs=4[f:id:mecobalamin:20200608101008p:plain]0,
                   validation_data = (X_test, y_test), verbose = 1)

plt.plot(history.history['accuracy'])
plt.plot(history.history['val_accuracy'])
plt.title('model accuracy')
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.legend(['acc', 'val_acc'], loc='lower right')
plt.show()

# テストデータに適用
predict_classes = model.predict_classes(X_test)

# マージ。yのデータは元に戻す
mg_df = pd.DataFrame({'predict': predict_classes, 'class': np.argmax(y_test, axis=1)})

# confusion matrix
pd.crosstab(mg_df['class'], mg_df['predict'])

# 結果の保存
# テストに使った画像ファイルのリストも保存
df_h = pd.concat([pd.DataFrame(y_test), indices_test], axis = 1)
pd.DataFrame(y_test).to_csv("/path/to/y_test.csv")
indices_test.to_csv("/path/to/indices_test.csv")
pd.DataFrame(predict_classes).to_csv("/path/to/predict_test.csv")

jupyter-notebookの複数のcellをまとめたが
gistを使うとそのまま貼り付けられるらしい



計算結果は以下の通り
Core i5-7200UのノートPCで
大体6時間ぐらいかかった

Train on 3287 samples, validate on 1619 samples
Epoch 1/40
3287/3287 [==============================] - 522s 159ms/step - loss: 0.6425 - accuracy: 0.5117 - val_loss: 0.5973 - val_accuracy: 0.5374
Epoch 2/40
3287/3287 [==============================] - 494s 150ms/step - loss: 0.5793 - accuracy: 0.6693 - val_loss: 0.5340 - val_accuracy: 0.7739
Epoch 3/40
3287/3287 [==============================] - 493s 150ms/step - loss: 0.5429 - accuracy: 0.7478 - val_loss: 0.5146 - val_accuracy: 0.7573

~~~ 省略 ~~~

Epoch 38/40
3287/3287 [==============================] - 501s 152ms/step - loss: 0.4346 - accuracy: 0.8059 - val_loss: 0.4734 - val_accuracy: 0.7900
Epoch 39/40
3287/3287 [==============================] - 493s 150ms/step - loss: 0.4001 - accuracy: 0.8190 - val_loss: 0.5238 - val_accuracy: 0.7622
Epoch 40/40
3287/3287 [==============================] - 497s 151ms/step - loss: 0.4388 - accuracy: 0.8026 - val_loss: 0.4895 - val_accuracy: 0.7857

Epochが増えると学習データに対するAccuracyは増えているが
テストデータに対するAccuracyはあまり増えていない
Jupyter Notebookをはてなブログに貼り付ける方法 - akatak’s blog

画像の分類は以下の通り
classは学習時に使った画像のラベルで
0はnegative、1はpositiveを表す
predictはcnnで分類された画像で
0/1はやはりnegative/positiveを表す

predict
0 1
class 0 652 104
1 243 620

10%-30%程度間違っている

グラフにしてないけれど損失関数も
mnistのデータを使ったときほど減っていない

使ったモデルが合っていないのか
教師データの分類が不十分なのか
そもそもデータの数が足りないのか

分類できた画像を確認したら
見た目にも分類が不十分に感じる
この画像がネガティブに分類?みたいな

計算はできたけど
ここからの修正はどうしたらいいだろうか