主成分分析(PCA)による画像分類が上手くいきません
解決したいこと
Pythonで主成分分析を用いた画像分類(コスモスと猫の写真を見分ける)を試みているのですが、すべてコスモスと判定されてしまい、正しく判定することができません。
ここでは、学習用にコスモスの写真100枚、判定用にコスモスの写真5枚&猫の写真5枚計10枚を用いました。
該当するソースコード
from sklearn.decomposition import PCA
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
#from google.colab import drive
#drive.mount('/content/drive/')
#os.chdir("/content/drive/MyDrive/")
img_width, img_height = 64, 64
# 学習用のデータを読み取る.
image_list_train = []
label_list_train = []
for dir in os.listdir("data/train"):
if dir == ".DS_Store":
continue
dir1 = "data/train/" + dir
label = 0
if dir == "cosmos":
label = 0
for file in os.listdir(dir1):
if file != ".DS_Store":
# 配列label_listに正解ラベルを追加(intact:0 void: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の配列]] のような形にする。
#image = image.transpose(2, 0, 1)
# さらにフラットな1次元配列に変換。最初の1/3はRed、次がGreenの、最後がBlueの要素がフラットに並ぶ。
image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
#image = (image.reshape(img_width*img_height*3)).astype("float32")
#image = np.reshape(image, [img_width, img_height, 3])
#print("image_input",image.shape)
# 出来上がった配列をimage_listに追加。
image_list_train.append(image / 255.)
# kerasに渡すためにnumpy配列に変換。
x_input = np.array(image_list_train)
# ラベルの配列を1と0からなるラベル配列に変更
y_input=np.array(label_list_train)
# 判定用のデータを読み取る.
image_list_test= []
label_list_test = []
# ./data/test 以下のcosmos,catディレクトリ以下の画像を読み込む。
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 == "cat": # catはラベル1
label = 1
for file in os.listdir(dir1):
if file != ".DS_Store":
# 配列label_listに正解ラベルを追加(空隙なし:0 空隙あり: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の配列]] のような形にする。
#image = image.transpose(2, 0, 1)
# さらにフラットな1次元配列に変換。最初の1/3はRed、次がGreenの、最後がBlueの要素がフラットに並ぶ。
image = image.reshape(1, image.shape[0] * image.shape[1] * image.shape[2]).astype("float32")[0]
# image = (image.reshape(img_width*img_height*3)).astype("float32")
# image = np.reshape(image, [img_width, img_height, 3])
# print("image_test",image.shape)
# 出来上がった配列を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)
#PCA
# 学習用データの20%を閾値計算用に用いる↓
x_train,x_th,y_train,y_th=train_test_split(x_input,y_input,test_size=0.8)#,random_state=42)
#
pca=PCA(n_components=0.95)
pca.fit(x_train) #主成分分析
os.chdir("./data/")
#累積寄与率を描画
cum_exp_var_ratio = np.hstack([0, pca.explained_variance_ratio_]).cumsum()
plt.plot(cum_exp_var_ratio, 'D-')
plt.xticks(np.arange(0,26,5))
plt.yticks(np.arange(0,1.1,0.10))
plt.grid()
plt.xlabel('Num of Components')
plt.ylabel('Cumulative Contribution Ratio')
os.chdir("./PCA/")
plt.savefig("cum_exp_var_ratio.png")
os.chdir("../")
plt.show()
from scipy.stats import boxcox
from scipy.special import inv_boxcox
from scipy import stats
#閾値設定
nnn=1.96 #信頼区間を指定する nnn=0.5:34%信頼区間 nnn=1:68%信頼区間 nnn=2:95%信頼区間 nnn=3:99%信頼区間
#標準正規分布に従う場合 nnn= 0.013:1% 0.063:5% 0.13:10% 0.26:20% 0.68:50% 1.29:80% 1.65:90% 1.96:95% 2.58:99%
x_th_pca=pca.transform(x_th) #x_thを主成分空間に写像
x_th_pca_inverse=pca.inverse_transform(x_th_pca) #x_test_pcaを逆変換する
diff_th = (np.array(x_th_pca_inverse)-np.array(x_th)) #平均絶対誤差で判定する場合
mae_th_pre_bc = np.mean(np.abs(diff_th), axis=1) #平均絶対誤差で判定する場合
mae_th=mae_th_pre_bc
#Box-Cox変換(平均絶対誤差の場合)
mae_th_post_bc, fitted_lambda, alpha = boxcox(mae_th_pre_bc, lmbda=None, alpha=0.05)
#極端値(99%信頼区間より外側)を除外
outlier_max=mae_th_post_bc.mean()+258*mae_th_post_bc.std()
outlier_min=mae_th_post_bc.mean()-258*mae_th_post_bc.std()
mae_th=[]
outlier_list=[]
for i in range(len(mae_th_post_bc)):
if outlier_min< mae_th_post_bc[i]< outlier_max:
mae_th.append(mae_th_post_bc[i])
else:
outlier_list.append(mae_th_post_bc[i])
mae_th=np.array(mae_th)
#閾値を計算
threshold_boxcox = (mae_th.mean()+nnn*(mae_th.std()))
threshold = inv_boxcox(threshold_boxcox, fitted_lambda)
print("threshold", threshold)
#空隙の有無を判定
x_test_pcatr=pca.transform(x_test)
x_test_pca_inversetr=pca.inverse_transform(x_test_pcatr)
abs_diff_test = np.abs(x_test - x_test_pca_inversetr)
mae_test = (np.mean(abs_diff_test, axis=1))
#平均絶対誤差で判定する場合
y_pred = []
for j in range(len(x_test)):
#for j in range(232):
if mae_test[j] <= threshold:
y_pred.append(0)
elif mae_test[j] > threshold:
y_pred.append(1)
#異常度を計算 #2022.5.1追記
abnormality = []
for k in range(len(x_test)):
diff_threshold = mae_test[k]/threshold
abnormality.append(diff_threshold)
# Generate reconstructions
pred_test = x_test_pca_inversetr
print("pred_test", pred_test.shape)
pred_test_a = []
for i in range(len(pred_test)):
pred_test_i = np.reshape(pred_test[i], [img_height, img_width, 3])
pred_test_a.append(pred_test_i)
pred_test_a = np.array(pred_test_a)
print("pred_test_a", pred_test_a.shape)
x_test_a = []
for i in range(len(x_test)):
#for i in range(5):
x_test_i = np.reshape(x_test[i], [img_height, img_width, 3])
x_test_a.append(x_test_i)
x_test_a = np.array(x_test_a)
print("x_test_a", x_test_a.shape)
#ROC curve
fpr, tpr, thresholds = metrics.roc_curve(y_test, y_pred) #tpr=TP/(TP+FN) fpr=FP/(FP+TN)
auc = metrics.auc(fpr, tpr)
plt.plot(fpr, tpr, label='ROC curve (area = %.2f)'%auc)
plt.legend()
plt.xlabel('FPR: False positive rate')
plt.ylabel('TPR: True positive rate')
plt.grid()
os.chdir("./PCA/")
plt.savefig("ROC-curve.png")
plt.show()
os.chdir("../")
#テキストデータに出力
os.chdir("./PCA/")
np.savetxt("out.txt", y_pred)
np.savetxt("abnormality.csv", abnormality)
os.chdir("../")
# Confution_matrix
report = classification_report(y_test, y_pred)
cm = confusion_matrix(y_test, y_pred)
TN, FP, FN, TP = confusion_matrix(y_test, y_pred).ravel()
cm_list = ['TN', 'FP', 'FN', 'TP']
cm_value = [TN, FP, FN, TP]
cm = [cm_list, cm_value]
os.chdir("./PCA/")
listitems = [cm_list, cm_value]
with open('cm.txt', 'w') as temp_file:
for item in listitems:
temp_file.write("%s\n" % item)
os.chdir("../")
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]
print('Accuracy=', Accuracy)
print('Precision=', Precision)
print('Recall=', Recall)
print('F1=', F1)
#寄与率
pca_col = ["PC{}".format(x + 1) for x in range(17)]
df_con_ratio = pd.DataFrame([pca.explained_variance_ratio_], columns = pca_col)
print(df_con_ratio.head())
np.savetxt("con_ratio.csv", df_con_ratio, delimiter=",")
#
#PCA の固有値
eigenvalue=pd.DataFrame(pca.explained_variance_, index=["PC{}".format(x + 1) for x in range(17)])
print("eigenvalue", eigenvalue)
np.savetxt("eigenvalue.csv", eigenvalue, delimiter=",")
#PCA の固有ベクトル
x_train = pd.DataFrame(x_train)
eigenvector = pd.DataFrame(pca.components_, index=["PC{}".format(x + 1) for x in range(17)])
eigenvector = eigenvector.T
print("eigenvector",eigenvector)
np.savetxt("eigenvector.csv", eigenvector, delimiter=",")
# Plot reconstructions
for i in range(0, 10):
fig, axes = plt.subplots(1, 2)
axes[0].imshow(x_test_a[i],cmap='jet')
axes[0].set_title('Original image')
axes[0].set_xticks([])
axes[0].set_yticks([])
axes[0].spines['top'].set_visible(False)
# axes[0].spines['bottom'].set_visible(False)
axes[0].spines['right'].set_visible(False)
axes[0].spines['left'].set_visible(False)
#post-rereconstructed
axes[1].imshow(pred_test_a[i],cmap='jet')
axes[1].set_title('Reconstruction with PCA')
axes[1].set_xticks([])
axes[1].set_yticks([])
axes[1].spines['top'].set_visible(False)
axes[1].spines['bottom'].set_visible(False)
axes[1].spines['right'].set_visible(False)
axes[1].spines['left'].set_visible(False)
os.chdir("./PCA/fig/")
j = i+1
plt.savefig(f'PCA_{j:03d}.png')
plt.show()
os.chdir("../../")
自分で試したこと
判定用閾値の計算時における信頼区間を調整したり、画像の読み取り時における画像サイズを調整したりするなどしましたが、改善には至りませんでした。
また、PCAによる圧縮・再構成後の画像は、コスモスの写真に類似していました。
0