input_shape=(12288,1)
の12288
の意図を教えてください.
Conv1Vで画像分類を試していますが、lossが0のまま変化しません。
解決したいこと
2種類の画像データを読み取って、Cov1Dで分類するコードを書きましたが、lossが0のまま変わらないため、正しく判定することができません。
発生している問題・エラー
accuracyは学習とともに値が変わっているのに対し、lossは0のまま変化がありません。
例)
Epoch 50/50
13/13 [==============================] - 5s 369ms/step - loss: 0.0000e+00 - accuracy: 0.7087 - val_loss: 0.0000e+00 - val_accuracy: 0.0000e+00
該当するソースコード
# -*- coding: utf-8 -*-
"""
Created on Thu Mar 10 19:46:13 2022
@author:
"""
from tensorflow.keras.layers import Input,Dense
from tensorflow.keras.models import Model
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.metrics import confusion_matrix
from PIL import Image
import os
import pprint
from sklearn.metrics import confusion_matrix
from sklearn import metrics
from matplotlib import cm
import tensorflow as tf
from tensorflow import keras
from scipy.special import boxcox, inv_boxcox
from scipy import stats
img_width, img_height =64, 64
# 学習用のデータを読み取る.
image_list_train = []
label_list_train = []
# ./data/train 以下のcosmosディレクトリ以下の画像を読み込む。
for dir in os.listdir("data/train"):
if dir == ".DS_Store":
continue
dir1 = "data/train/" + dir
label = 0
if dir == "cosmos": # cosmosはラベル0
label = 0
elif dir == "sakura": # sakuraはラベル1
label = 1
for file in os.listdir(dir1):
if file != ".DS_Store":
# 配列label_listに正解ラベルを追加(cosmos:0 sakura:1)
label_list_train.append(label)
filepath = dir1 + "/" + file
# 画像をn×n(n=50)pixelに変換し、1要素が[R,G,B]3要素を含む配列のnxnの2次元配列として読み込む。
# [R,G,B]はそれぞれが0-255の配列。
image = Image.open(filepath).resize((img_width, img_height))
image = image.convert("RGB")
image = np.array(image)
# 配列を変換し、[[Redの配列],[Greenの配列],[Blueの配列]] のような形にする。
# さらにフラットな1次元配列に変換。最初の1/3はRed、次がGreenの、最後がBlueの要素がフラットに並ぶ。
image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
# 出来上がった配列をimage_listに追加。
image_list_train.append(image / 255.)
# kerasに渡すためにnumpy配列に変換。
x_input = np.array(image_list_train)
print("x_input",x_input.shape)
# ラベルの配列を1と0からなるラベル配列に変更
#dataset_ytr=np.array(label_list_train)
y_input=np.array(label_list_train)
print("y_input",y_input.shape)
# 判定用のデータを読み取る.
image_list_test= []
label_list_test = []
# ./data/test 以下のcosmos,sakuraディレクトリ以下の画像を読み込む。
for dir in os.listdir("data/test"):
if dir == ".DS_Store":
continue
dir1 = "data/test/" + dir
label = 0
if dir == "cosmos": # cosmosはラベル0
label = 0
elif dir == "sakura": # sakuraはラベル1
label = 1
for file in os.listdir(dir1):
if file != ".DS_Store":
# 配列label_listに正解ラベルを追加(cosmos:0, sakura:1)
label_list_test.append(label)
filepath = dir1 + "/" + file
# 画像をn×n(n=50)pixelに変換し、1要素が[R,G,B]3要素を含む配列のnxnの2次元配列として読み込む。
# [R,G,B]はそれぞれが0-255の配列。
image = Image.open(filepath).resize((img_width, img_height))
image = image.convert("RGB")
image = np.array(image)
# 配列を変換し、[[Redの配列],[Greenの配列],[Blueの配列]] のような形にする。
# さらにフラットな1次元配列に変換。最初の1/3はRed、次がGreenの、最後がBlueの要素がフラットに並ぶ。
image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
# 出来上がった配列をimage_listに追加。
image_list_test.append(image / 255.)
# kerasに渡すためにnumpy配列に変換。
x_test = np.array(image_list_test)
#x_test = np.transpose(0, 1, 2)
print("x_test", x_test.shape)
# ラベルの配列を1と0からなるラベル配列に変更
y_test= np.array(label_list_test)
print("y_test", y_test.shape)
from keras.layers.convolutional import Conv1D, MaxPooling1D
from keras.models import Sequential
from tensorflow.keras import layers
from keras.layers.core import Dense, Dropout, Activation, Flatten
from keras import optimizers
model = Sequential()
model.add(Conv1D(8, kernel_size=3, input_shape=(12288,1)))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dense(800))
model.add(Activation('relu'))
model.add(Dense(400))
model.add(Activation('relu'))
model.add(Dense(200))
model.add(Activation('relu'))
model.add(Dense(100))
model.add(Activation('relu'))
model.add(Dense(1))
model.add(Activation('sigmoid'))
model.summary()
model.compile(loss='sparse_categorical_crossentropy',optimizer='adam',metrics=['accuracy'])
history = model.fit(x_input, y_input, batch_size=8, epochs=50,validation_split=0.1, verbose = 1)
y_pred = model.predict(x_test)
print("y_pred",y_pred.shape)
np.savetxt('y_pred.csv',y_pred)
from sklearn.metrics import accuracy_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
Accuracy = accuracy_score(y_test, y_pred)*100
Precision = precision_score(y_test, y_pred)*100
Recall = recall_score(y_test, y_pred)*100
F1 = f1_score(y_test, y_pred)*100
report_list = ['Accuracy', 'Precision', 'Recall', 'F1']
report_value = [Accuracy, Precision, Recall, F1]
# Confution_matrix
cm = confusion_matrix(y_test, y_pred)
print(cm)
print("Accuracy", Accuracy)
print("Precision", Precision)
print("Recall", Recall)
print("F1", F1)
# ROC曲線
cnt=0
point=[0,0]
roc=[]
roc.append([0,0])
rank=list(reversed(np.argsort(y_test)))
error=np.sum((y_test.astype("int")==1))
for ridx in rank:
if y_test[ridx]==1:
point[1]=point[1]+1
cnt=cnt+1
else:
point[0]=point[0]+1
roc.append([point[0],point[1]])
if cnt==error:
roc.append([y_test.shape[0],point[1]])
break
roc=np.array(roc)
np.savetxt("roc.txt",roc)
plt.plot(roc[:,0]/y_test.shape,roc[:,1]/error)
plt.plot([0,1],color='black',linestyle='dashed')
plt.savefig("ROC_curve.png")
plt.show()
# Plot training & validation loss values
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.title('Model loss')
plt.ylabel('Loss')
plt.xlabel('Epoch')
plt.legend(['Train', 'Test'], loc='upper left')
plt.savefig("loss-epoch.png")
plt.show()
例)
def greet
puts Hello World
end
自分で試したこと
optimizerをadamからsgdにしたところ、lossがNanになりました。
2Answer
Comments
@noname20220504
Questioner64(画像縦方向のピクセル数)×64(画像横方向のピクセル数)×(原色数)(=12288)からなる3次元の画像データセット(テンソル)を1次元のデータセット(12288,1)(ベクトル)に変換しています。
なるほど,
image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
という操作で12288のサイズを実現しているわけですね.
では,# 配列を変換し、[[Redの配列],[Greenの配列],[Blueの配列]] のような形にする。 # さらにフラットな1次元配列に変換。最初の1/3はRed、次がGreenの、最後がBlueの要素がフラットに並ぶ。 image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
の箇所での
reshape
は,コメントの通りに動作していないことはご存知でしょうか.
具体的には,[Rの配列,Gの配列,Bの配列]
の順ではなく,[Rの0番目,Gの0番目,Bの0番目,Rの1番目,Gの1番目,Bの1番目,...]
となっているということです.これをコメントの通りに実装するには,現状のChannel Lastの形式からChannel Firstの形式にtranspose
してからreshape
するのがベストです.弊環境で再現するにあたり色々聞きたいところですが,直感的にはモデルで
Conv1D
が1レイヤしか採用されていない点やbatch_size
が小さい点,optimizer = 'adam'
としていて,学習率をデフォルトのまま利用している点,loss = 'sparse_categorical_crossentropy'
が悪さしている可能性を否定できません.特に治すならloss = 'binary_crossentropy'
にしてみたり,学習率を小さくすることでNaNが出ることを防ぐ可能性があります.そもそも画像処理CNNの常套手段である
Conv2D
を使わずConv1D
なのか教えていただけるとモデル改善を提案することもできます.現状,1つのkernelがkernel_size=3
,strides=1
で[R0, G0, B0, R1, G1, B1, ...]
の配列を畳み込むことを考えてみるとモデルが破綻していることがよくわかると思います.先ほど検証したところ,
loss='sparse_categorical_crossentropy'
であることが悪さしているようです.lossがNaNになりました.したがって今回のタスクに適さない損失関数です.loss = 'binary_crossentropy'
にしましょう.ちなみに精度はランダム推測と同等の50%
を記録しました.これはモデルの改善で修正可能です.@noname20220504
Questionerご教示いただきありがとうございます。Conv1Dにしたのはあくまでも練習用で、Conv2Dが実装できたのでConv1Dも試してみようという感じで行ったものです。
バッチサイズが小さいのは、もとのデータ数が100個程度しかないため、このように設定しました。
また、ご教示いただいた通り、損失関数をbinary_crossentropyにしたところ、lossがNaNになるという事象はなくなりました。
学習率などその他のパラメータについても見直したいと思います。なるほど,非常に良い試みだと思います.まずは前処理から見直しましょう.
従来
Conv2D
で処理されるような元々の画像バッチ(N, W, H, C)
であったのをConv1D
で処理するために(N, W * H, C)
形式にしたりするところを,貴コードでは(N, W * H * C, 1)
にしてしまっており精度の低下に繋がっています.余力があればViT等に使われるようにパッチをあてたtoken形式にして入力データとしましょう.
@noname20220504
Questionerレスポンスが遅くなってしまいすみません。前処理についてあれこれ試してみたのですが、自分のコーディング能力のなさのせいがエラーが出てしまい上手くいきませんでした。
代わりに、学習・判定のところで、
nb_class = 2
nb_features = 12288
model = Sequential()
model.add(Conv1D(filters=16, kernel_size=2, input_shape=(nb_features,1)))
model.add(Activation('relu'))
model.add(Flatten())
#model.add(Dropout(0.4))
model.add(Dense(512, activation='relu'))
#model.add(Dense(128, activation='relu'))
model.add(Dense(nb_class))
model.add(Activation('sigmoid'))model.summary()
#model=Model(inputs=input_layer,outputs=output_layer)
model.compile(loss='sparse_categorical_crossentropy',optimizer='adam', metrics=['accuracy'])
#earstop=EarlyStopping(monitor='val_loss',min_delta=0.0,patience=5,)history= model.fit(x_input,y_input,batch_size=8,epochs=100,validation_split=0.05)#,callbacks=[earstop])
y_pred = model.predict(x_test)
print("y_pred",y_pred.shape)
np.savetxt('y_pred.csv',y_pred)a=[]
for i in range(123):
if y_pred[i][0] > y_pred[i][1]:
a.append(0)
else:
a.append(1)
#print("a",a)
np.savetxt('a.csv', a)
y_pred=a
としたところ、正解率・F値ともに90%を超え、高い学習性能を示しました。正解率・F値はどのように算出したのでしょうか.
貴モデルは性能が良すぎて過学習するはずです@noname20220504
Questioner正解率=(TP+TN)/(TP+FP+TN+FN)
F値:適合率と再現率の調和平均
で算出しました。ここでは陽性:コスモス、陰性:桜としました。@noname20220504
Questionerやはり
val_loss
がずっと0,val_accuracy
が1であり続けているのが気になります.
trainingデータ(x_input
,y_input
)は何個ありますでしょうか.validation_split=0.05
ということは,len(x_input)*0.05
件がvalidationに使われます.100件しかないのでしたらたった5件ということになってしまいます(これは一般的な配分ではありません)教師データと評価データが非常によく似ていた
というのは,具体的にどのような状態なのでしょうか.
「画角を少しだけ変えて同じ花を再撮影した.」とかなのでしょうか@noname20220504
Questionertrainデータは全部で120個になります。validation_splitについては、当初0.20や0.10としていたのですが、ためしに0.05でやったところ正解率・F値が改善されたので、その値としていました。ただ、仰るように、全体のデータ数に比して小さすぎたかもしれません。
教師データと評価データが非常によく似ていたというのは、仰る通りで相違ございません。力不足で申し訳ないのですが,手元のCIFAR10のデータでcatとdogのみ抜き出して分類を試みても再現できません.
一応,cross validationをやってみて評価するということはできると思います.@noname20220504
Questionerレスポンスが遅くなってしまい申し訳ありません。
再現できないとのこと承知しました。こちらでも交差検証とあわせ、データを変えて試してみたいと思います。