简短版本: 我使用PCA来减少训练数据的维数时遇到了困难。我的训练数据是为一个二维CNN准备的,用于将图形图像分类为三个类别。
模型的目的
我对主成分分析(PCA)还不熟悉。我有一个二维卷积神经网络(CNN),用于将36×36像素的图形图像分类为三个类别,如下所示:
改进模型
我发现大部分像素都是白色的,因此CNN效率很低,训练时间很长。我了解到降维技术,并尝试使用PCA。我将其中一张训练图像转换为灰度,并可视化了“特征图”(如左图所示)。然后我从特征图重建了原图(如右图所示)。
X=grayscale pca_oliv = PCA(n_components = 36)X_proj = pca_oliv.fit_transform(X)print(np.cumsum(pca_oliv.explained_variance_ratio_))plt.imshow(np.reshape(pca_oliv.components_, (36,36)), cmap=plt.cm.bone, interpolation='nearest')
问题
但我知道它可以做得更好。 这是使用n=36维的情况。通过绘制解释方差,我发现拐点在3维。这意味着只用36维中的3维,就可以保留91.7%的方差。
但如果我将pca_oliv = PCA(n_components = 36)
改为pca_oliv = PCA(n_components = 3)
,一切都会变得混乱:ValueError: cannot reshape array of size 108 into shape (36,36)
。为什么会这样?我做错了什么?
最小工作示例
pip install tensorflowpip install numpypip install matplotlib"""# 导入库"""# 导入库import tensorflow as tffrom tensorflow import kerasfrom keras.models import Sequentialfrom keras.layers import Dense, Flatten, Conv2D, MaxPooling2D, Dropoutfrom tensorflow.keras import layersfrom tensorflow.keras.utils import to_categoricalimport numpy as npimport matplotlib.pyplot as pltplt.style.use('fivethirtyeight')"""# 加载数据集"""import pathlibdataset_url = "*/TrainingSet.tar.gz"data_dir = tf.keras.utils.get_file(origin = dataset_url, fname = "TrainingSet", untar = True)data_dir = pathlib.Path(data_dir)"""# 显示#图像以检查"""print(list(data_dir.glob('*/*.png')))image_count = len(list(data_dir.glob('*/*.png')))print(image_count)"""# 显示示例图像"""pip install sklearnimport numpy as npimport osimport PILimport PIL.Imageimport tensorflow as tfimport tensorflow_datasets as tfdsfrom sklearn.decomposition import PCAgraphs = list(data_dir.glob('*/*.png'))PIL.Image.open(str(graphs[6]))"""# 定义图像尺寸和批量大小"""batch_size = 32img_height = 36img_width = 36"""# 创建训练和验证集(80%,20%)"""train_ds = tf.keras.preprocessing.image_dataset_from_directory( data_dir, validation_split=0.2, subset="training", seed=123, image_size=(img_height, img_width), batch_size=batch_size)val_ds = tf.keras.preprocessing.image_dataset_from_directory( data_dir, validation_split=0.2, subset="validation", seed=123, image_size=(img_height, img_width), batch_size=batch_size)"""# 定义3个类别"""class_names = ['Cubic Sinusoidal', 'Linear Sinusoidal', 'Quadratic Sinusoidal']print(class_names)"""# 监督学习(来自训练集的9个样本)"""!pip install skimagefrom skimage import datafrom skimage.color import rgb2grayimport matplotlib.pyplot as pltsubGraphs = []plt.figure(figsize=(10, 10))for images, labels in train_ds.take(1): for i in range(9): ax = plt.subplot(3, 3, i + 1) plt.imshow(images[i].numpy().astype("uint8")) subGraphs.append(images[i].numpy().astype("uint8")) plt.title(class_names[labels[i]]) plt.axis("off")subGraphs = np.array(subGraphs)print(subGraphs.shape)grayscale = rgb2gray(subGraphs[1])print(grayscale.shape)X=grayscale pca_oliv = PCA(n_components = 36)X_proj = pca_oliv.fit_transform(X)print(np.cumsum(pca_oliv.explained_variance_ratio_))plt.plot(np.cumsum(pca_oliv.explained_variance_ratio_))plt.imshow(np.reshape(pca_oliv.components_, (36,36)), cmap=plt.cm.bone, interpolation='nearest')X_inv_proj = pca_oliv.inverse_transform(X_proj)X_proj_img = np.reshape(X_inv_proj,(1,36,36))plt.imshow(X_proj_img[0], cmap=plt.cm.bone, interpolation='nearest')
供参考,这是我的Jupyter笔记本:PCA+CNN。如果有人能帮忙,那就太好了。
回答:
PCA
用于减少维数,同时确保低维表示能够覆盖尽可能多的变化。现在,想想你最初有多少维度。我看到你使用了(36, 36)
的灰度图像。这里,每个像素都是你原始的特征。你又选取了9张图像来对它们应用PCA
。
在这种情况下,你的样本数量
少于原始特征数量
,即9 < 36*36
,因此,你只需要不超过9
个主成分就能覆盖全部方差。但如果你的样本数量大于特征数量(36*36 = 1296)
,你就可以设置更大的n_components
值。参见这里,sklearn.decomposition.PCA 和 为什么对于n个数据,如果维数≥n,只有n−1个主成分?
但无论如何,我不会深入探讨PCA
的细节,而是描述你需要在代码中做哪些更改。
grayscale = rgb2gray(subGraphs)print(grayscale.shape)grayscale = grayscale.reshape((grayscale.shape[0], grayscale.shape[1] * grayscale.shape[2]))print(grayscale.shape)
由于PCA
期望输入形状为(样本数量, 特征数量)
,因此,你需要将样本数量
保持在第一维度,第二维度将是所有像素值(原始特征)。如果你使用了彩色图像,那么你需要将所有通道的特征包含在同一第二维度中,类似于这样:
color_img = color_img.reshape((color_img.shape[0], color_img.shape[1] * color_img.shape[2] * color_img.shape[3]))print(color_img.shape)
现在你可以应用PCA
:
X=grayscale pca_oliv = PCA(n_components = 9)X_proj = pca_oliv.fit_transform(X)print(np.cumsum(pca_oliv.explained_variance_ratio_))plt.plot(np.cumsum(pca_oliv.explained_variance_ratio_))
请注意,你无法将n_components
设置为大于9
,因为你只使用了9张图像。如果你查看X_proj
的形状,你会发现它的形状是(9, 9)
。第一个9
是样本数量,第二个9
是每个样本在低维空间中的表示,该空间有9
维(n_components
)。
最后,进行逆变换以恢复原始维度(这只是为了说明目的,你将使用X_proj
作为低维表示来训练模型):
X_inv_proj = pca_oliv.inverse_transform(X_proj)print(X_inv_proj.shape)for index in range(len(X_inv_proj)): # 9 X_proj_img = np.reshape(X_inv_proj[index],(36,36)) plt.imshow(X_proj_img, cmap=plt.cm.bone, interpolation='nearest') plt.show()
再重复一遍,X_proj
包含你的9
个样本的低维表示(9
维)。因为它不是图像,所以你不需要重新塑形。你可以直接使用它来训练模型,就好像这9
个特征代表了你原始的36*36
特征一样。
这里请注意,逆变换并不是总是无损变换。在你的案例中,我们采用了9
个主成分(这是可能的最大值)。所以,我们在获取PCA
时实际上已经采用了100%
的变化,因此,当我们应用逆变换时,它将恢复100%
的变化,即恢复原始数据。但如果我们将n_components
设为更低的值,那么逆变换将无法完全恢复原始信息,尽管X_inv_proj
的形状不会改变,但它所包含的信息将不是原始数据的完整信息。