前言
一. PCA主成分分析
文章链接:
http://blog.codinglabs.org/articles/pca-tutorial.html
1. PCA函数
class sklearn.decomposition.PCA (n_components=None, copy=True, whiten=False, svd_solver=’auto’, tol=0.0,
iterated_power=’auto’, random_state=None)
该函数为使用主成分分析(PCA)进行数据降维的类。主成分分析是一种常用的无监督学习算法,它可以将高维数据降到低维,并且保留数据的主要特征。PCA通过找到数据中的主成分来实现降维,主成分是原始数据在不同方向上的投影,每个主成分都是原始数据的线性组合。
参数说明:
- n_components:指定PCA降维后的维数,默认为None,表示保留所有的特征。
- copy:是否将原始数据复制一份,默认为True,表示复制。
- whiten:是否对降维后的数据进行白化处理,默认为False,表示不进行白化处理。
- svd_solver:指定SVD分解的方法,可选值为{‘auto’, ‘full’, ‘arpack’, ‘randomized’},默认为’auto’。
- tol:指定SVD分解的精度,当svd_solver为’arpack’时生效。
- iterated_power:指定幂迭代的次数,当svd_solver为’randomized’时生效。
- random_state:随机种子。
PCA类的属性:
- components_:返回一个数组,表示主成分的权重向量。
- explained_variance_ratio_:返回一个数组,表示每个主成分所解释的方差占总方差的比例;又称为
可解释性方差贡献率
。 - mean_:返回一个数组,表示每个特征的均值。
- n_components_:返回一个整数,表示实际降维后的维数。
- noise_variance_:返回一个浮点数,表示噪声方差的估计值。
(1)参数n_components选取方式
- 累积可解释方差贡献率曲线:首先不填写任何值,来画出累计可解释方差贡献率曲线,以此选择最好的
n_components
的整数取值。 - 最大似然估计自选超参数:
mle
- 按信息量占比选超参数:输入
[0,1]
之间的浮点数,并且让参数svd_solver =='full'
,表示希望降维后的总解释性方差占比大于n_components
指定的百分比
2. 参数svd_solver 与 random_state
3. 重要属性components_,查看特征空间
(1)案例(降维图像中的特征空间)
from sklearn.datasets import fetch_lfw_people
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
faces = fetch_lfw_people(min_faces_per_person=60)
faces.images.shape fig, axes = plt.subplots(4,5
,figsize=(8,4)
,subplot_kw = {"xticks":[],"yticks":[]} #不要显示坐标轴
)
fig
axes
axes.flat
这段代码使用sklearn中的人脸数据集进行主成分分析降维,并将降维后的结果展示在4行5列的子图中。
- 第1行导入所需模块和函数。
- 第2行获取人脸数据集,min_faces_per_person=60表示只保留至少有60张照片的人的数据。
- 第3行创建一个4行5列的子图,subplot_kw参数用于设置每个子图的属性,{“xticks”:[],“yticks”:[]}表示不显示坐标轴。
- 第4行创建一个空的画布fig。
- 第5行获取所有子图并展开为一维数组。
- 该代码缺少关键的部分,即主成分分析降维操作。PCA的fit_transform()方法可以用于对数据进行降维,并返回降维后的结果。可以在fit_transform()方法中指定降维后的维数。例如,如果要将数据降到50维,则可以使用以下代码:
pca = PCA(n_components=50)
faces_pca = pca.fit_transform(faces.data)
然后可以将降维后的数据faces_pca传递给子图来展示。具体而言,可以使用imshow()方法将每个子图中的人脸图像展示出来,例如:
for i, ax in enumerate(axes.flat):
ax.imshow(faces_pca[i].reshape(62,47), cmap="gray")
其中,reshape()方法用于将一维的降维后的数据重新变为二维的人脸图像。cmap参数用于指定灰度图像的颜色映射。最后,可以使用plt.show()方法显示结果。
(2)axes.flat的作用是什么?
axes.flat
是一个NumPy数组的属性,用于将多维数组转换为一维数组。在matplotlib中,axes
通常是一个二维数组或多维数组,表示由多个子图组成的图形对象。对于二维数组,axes.flat
可以将其转换为一维数组,方便对每个子图进行遍历和操作。
例如,在一个2行3列的子图对象中,可以使用以下代码来遍历每个子图对象并进行相应的操作:
import matplotlib.pyplot as plt
fig, axes = plt.subplots(2, 3)
for ax in axes.flat:
# 对每个子图进行操作
pass
在上面的代码中,axes.flat
将2x3的子图对象转换为一个长度为6的一维数组,然后使用for
循环遍历每个子图对象,并对其进行操作。这种方式简化了代码的编写,提高了可读性和可维护性。
需要注意的是,axes.flat
返回的是一个迭代器对象,因此可以使用next()
函数来获取下一个子图对象。同时,如果需要对多维数组进行操作,也可以使用numpy.ravel()
函数将其转换为一维数组,但是这种方式返回的是一个数组对象,而不是迭代器对象。
(3)numpy.flatiter是什么
numpy.flatiter
是一个迭代器对象,用于按照数组的扁平化顺序对多维数组进行迭代。它是通过调用numpy.ndarray.flat
属性获得的,可以被用于类似于Python内置函数iter()
的上下文中。使用flatiter
可以方便地遍历多维数组中的所有元素。
flatiter
对象有很多方法,例如next()
方法可以返回下一个元素,index
属性可以返回当前元素在扁平化数组中的索引,coords
属性可以返回当前元素在原始多维数组中的坐标。
以下是一个简单的示例,展示如何使用flatiter
迭代多维数组:
import numpy as np
# 创建一个3x3的二维数组
arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
# 获取该数组的迭代器
it = arr.flat
# 遍历数组中的所有元素
for element in it:
print(element)
输出结果为:
1
2
3
4
5
6
7
8
9
(4)解释enumerate(axes.flat)
enumerate()
是Python内置函数,用于将一个可遍历的对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标,一般用在for循环中。axes.flat
是一个扁平化的迭代器对象,可以用于按照一维顺序遍历多个子图。
enumerate(axes.flat)
则将axes.flat
中的每个元素和其对应的索引打包成一个元组,并返回一个可迭代的对象。在这个例子中,enumerate(axes.flat)
会返回一个可迭代对象,其中每个元素都是一个形如(index, element)
的元组,其中index
表示当前元素在axes.flat
中的索引,element
表示当前元素即为遍历到的子图对象。
因此,我们可以使用enumerate(axes.flat)
来遍历所有的子图对象,并且可以得到每个子图对象在axes.flat
中的索引,方便进行进一步的操作。例如,在以下示例代码中,我们使用enumerate(axes.flat)
遍历所有子图,并在每个子图中显示一张随机图片:
import matplotlib.pyplot as plt
import numpy as np
# 创建一个4行5列的子图
fig, axes = plt.subplots(nrows=4, ncols=5)
# 遍历所有子图,并在每个子图中显示一张随机图片
for i, ax in enumerate(axes.flat):
img = np.random.rand(32, 32) # 创建一个随机图片
ax.imshow(img) # 在当前子图中显示图片
ax.set_title("Image {}".format(i)) # 设置子图标题
# 显示结果
plt.show()
在这个例子中,我们使用enumerate(axes.flat)
遍历所有的子图对象,并在每个子图中显示一张随机图片。同时,我们还使用ax.set_title()
方法为每个子图设置了一个标题,标题中包含了该子图在axes.flat
中的索引。
(5)[*enumerate(axes.flat)]和enumerate(axes.flat)有什么区别
enumerate()
是Python内置函数,用于将一个可遍历的数据对象(如列表、元组、字符串等)组合成一个索引序列,同时列出数据和数据下标,常用于循环遍历。而在这里,enumerate(axes.flat)
返回的是一个迭代器对象,其中每个元素是一个二元组(index, ax)
,其中index
表示当前子图对象的序号,ax
表示当前子图对象本身。
在[*enumerate(axes.flat)]
中,*
是一种解包运算符,可以将一个可迭代对象拆分成多个单独的元素。因此,[*enumerate(axes.flat)]
实际上是将enumerate(axes.flat)
返回的迭代器对象转换为一个列表,其中每个元素都是一个二元组(index, ax)
。
例如,如果axes
是一个2行3列的子图对象,则[*enumerate(axes.flat)]
返回以下列表:
[(0, ax1), (1, ax2), (2, ax3), (3, ax4), (4, ax5), (5, ax6)]
而在enumerate(axes.flat)
中,没有使用解包运算符*
,因此返回的是一个迭代器对象,其中每个元素仍然是一个二元组(index, ax)
。这种方式可以直接在循环中使用,例如:
for i, ax in enumerate(axes.flat):
# 对每个子图进行操作
pass
在上面的代码中,enumerate(axes.flat)
返回一个迭代器对象,然后使用for
循环遍历每个子图对象,并将其序号和对象本身分别赋值给变量i
和ax
,然后对其进行操作。
总之,[*enumerate(axes.flat)]
将enumerate(axes.flat)
返回的迭代器对象转换为列表,而enumerate(axes.flat)
返回的是一个迭代器对象,可以直接在循环中使用。
(6)解包运算符*如何理解?
在Python中,*
是一种解包运算符,可以将一个可迭代对象(如列表、元组、集合等)拆分成多个单独的元素。
具体来说,*
的作用取决于它的位置和使用方式。
- 函数调用中的解包运算符:
在函数调用中,*
可以将一个可迭代对象拆分成多个参数,用于将可迭代对象传递给函数的可变长度参数。例如:
def foo(a, b, c):
print(a, b, c)
lst = [1, 2, 3]
foo(*lst) # 等价于 foo(1, 2, 3)
在上面的代码中,foo(*lst)
将列表lst
拆分成三个单独的元素,然后传递给函数foo
的三个形参a
、b
、c
。
- 序列解包:
在赋值语句中,*
可以将一个序列拆分为多个单独的变量,用于将序列的元素赋值给多个变量。例如:
lst = [1, 2, 3, 4]
a, b, *c = lst
print(a, b, c) # 输出:1 2 [3, 4]
在上面的代码中,a, b, *c = lst
将列表lst
的前两个元素赋值给变量a
和b
,剩余的元素赋值给列表c
。由于*
可以匹配任意个元素,因此c
被赋值为包含剩余元素的列表。
- 迭代器解包:
在迭代器中,*
可以将一个迭代器拆分成多个单独的元素,用于将迭代器的元素传递给函数或其他语句。例如:
lst = [1, 2, 3, 4]
it = iter(lst)
a, b, *c = it
print(a, b, c) # 输出:1 2 [3, 4]
在上面的代码中,a, b, *c = it
将迭代器it
的前两个元素赋值给变量a
和b
,剩余的元素赋值给列表c
。由于*
可以匹配任意个元素,因此c
被赋值为包含剩余元素的列表。
总之,*
是一种很有用的解包运算符,在函数调用、序列解包和迭代器解包等场景中都有广泛的应用。理解和掌握*
的使用方法可以提高代码的可读性和可维护性。
(7)代码解释,如何填充图像?
for i, ax in enumerate(axes.flat):
ax.imshow(faces.images[i,:,:]
,cmap="gray" #选择色彩的模式
这段代码是一个使用matplotlib库绘制图像的示例。axes.flat
是一个扁平化的迭代器对象,可以用于按照一维顺序遍历多个子图。在这个例子中,enumerate(axes.flat)
被用来遍历所有的子图对象,并将每个子图中的图像用ax.imshow()
方法填充。
具体而言,ax.imshow()
方法用于在当前子图中显示一张图像。该方法的第一个参数是要显示的图像,它应该是一个三维数组,表示一个宽度为m、高度为n、通道数为3(RGB)或1(灰度)的图像。在这个例子中,faces.images
是一个三维的人脸图像数组,其形状为(n_samples, h, w)
,其中n_samples
表示样本数,h
和w
分别表示图像的高度和宽度。因此,faces.images[i,:,:]
表示第i
个图像,它是一个二维数组,表示一个高度为h
、宽度为w
的灰度图像。
cmap
参数用于指定色彩的映射方式,即将灰度值映射到颜色的方式。在这个例子中,cmap="gray"
表示使用灰度映射方式,即将0-255之间的灰度值映射到黑白两种颜色之间。
综上,这段代码的作用是将人脸数据集中的每个图像显示在一个子图中,并使用灰度映射方式将其转换为灰度图像。
(8) ax.imshow的作用是什么?
ax.imshow()
是matplotlib库中的一个函数,用于在子图对象ax
中显示图像。具体来说,ax.imshow()
将一个二维或三维数组解释为图像,并将其显示在子图对象ax
中。
例如,可以使用以下代码在子图对象ax
中显示一个灰度图像:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
img = np.random.rand(10, 10)
ax.imshow(img, cmap='gray')
在上面的代码中,np.random.rand(10, 10)
生成了一个10x10的随机灰度图像,然后使用ax.imshow(img, cmap='gray')
将其显示在子图对象ax
中。其中cmap='gray'
表示使用灰度色彩映射将颜色值映射到灰度值。
除了灰度图像外,ax.imshow()
还支持RGB图像、RGBA图像、二值图像等多种格式的图像显示。同时,ax.imshow()
还可以设置图像的坐标轴、插值方式、透明度等参数,以满足不同的图像显示需求。
总之,ax.imshow()
是matplotlib库中用于显示图像的重要函数,它可以将二维或三维数组解释为图像,并在子图对象ax
中显示出来。
(9)接口inverse_transform
inverse_transform()
是scikit-learn中许多机器学习算法中常用的方法之一,它通常用于将降维后的数据重新映射回原始空间。在降维过程中,我们通常会损失一些信息,因此将降维后的数据重新映射回原始空间可以帮助我们恢复一些被损失的信息。
具体而言,inverse_transform()
方法的作用是将降维后的数据重新映射回原始空间。在使用PCA等降维算法时,我们通常会使用transform()
方法将原始数据集映射到低维空间中,得到一个降维后的数据集。然后,我们可以使用inverse_transform()
方法将降维后的数据重新映射回原始空间中,从而得到一个与原始数据集形状相同的数据集。在这个过程中,我们可能会损失一些信息,但是可以通过调整降维后的维数来平衡信息损失和降维效果。
案例:用人脸识别看PCA降维后的信息保存量
以下是一个简单的示例,展示了如何使用inverse_transform()
方法将降维后的数据重新映射回原始空间:
from sklearn.decomposition import PCA
from sklearn.datasets import load_digits
# 加载手写数字数据集
digits = load_digits()
# 创建一个PCA对象,将数据降到2维
pca = PCA(n_components=2)
digits_pca = pca.fit_transform(digits.data)
# 将降维后的数据重新映射回原始空间
digits_inv = pca.inverse_transform(digits_pca)
# 显示原始数据集和恢复后的数据集
fig, ax = plt.subplots(1, 2)
ax[0].imshow(digits.data[0].reshape(8, 8), cmap='gray')
ax[0].set_title("Original Image")
ax[1].imshow(digits_inv[0].reshape(8, 8), cmap='gray')
ax[1].set_title("Recovered Image")
plt.show()
在这个例子中,我们使用PCA将手写数字数据集降到了2维,并使用inverse_transform()
方法将降维后的数据重新映射回原始空间。然后,我们将原始数据集和恢复后的数据集可视化出来,以便比较它们之间的差异。
用PCA做噪音过滤
相比噪音,有效的特征所带的信息应该不会在PCA过程中被大量抛弃。inverse_transform能够在不恢复原始数据的情况下,将降维后的数据返回到原本的高维空间,即是说能够实现”保证维度,但去掉方差很小特征所带的信息“。
为数据增加噪音
np.random.RandomState(42) #在指定的数据集中,随机抽取服从正态分布的数据
#两个参数,分别是指定的数据集,和抽取出来的正太分布的方差
noisy = np.random.normal(digits.data,2)
plot_digits(noisy)
这段代码使用np.random.normal()
方法向数据集中添加噪声。np.random.RandomState(42)
是一个随机数生成器,用于生成服从正态分布的随机数。该方法的第一个参数digits.data
是原始的手写数字数据集,第二个参数2表示噪声的方差。
具体而言,np.random.normal()
方法会从指定的数据集digits.data
中抽取一些样本,并以指定的方差2为标准差生成服从正态分布的随机数,最终得到一个噪声数据集noisy
,其形状与原始数据集相同。
然后,plot_digits(noisy)
方法被用于将噪声数据集可视化出来。plot_digits()
方法是一个自定义的方法,用于将手写数字图像可视化。它的作用是将noisy
中的每个样本显示在一个子图中。因为noisy
的形状与原始数据集相同,所以可以直接调用plot_digits()
方法进行可视化。
综上,这段代码的作用是向手写数字数据集中添加一些噪声,并将噪声数据集可视化出来。
RandomState(42)有什么作用
np.random.RandomState(42)
是一个随机数生成器,它的作用是指定随机数生成的种子,从而保证每次运行程序时生成的随机数都是一样的。在这段代码中,使用RandomState(42)
指定了种子为42,因此每次运行程序时生成的随机数都是一样的,从而使得程序的运行结果可以重现。
如果去掉RandomState(42)
,则生成的随机数将会根据系统时间等因素来确定,每次运行程序生成的随机数都可能不同。这样做可能会影响程序的可重现性,因为每次运行程序时生成的噪声数据都不同,从而可能导致可视化结果不同。
因此,如果需要保证程序的可重现性,最好不要去掉RandomState(42)
;如果不需要保证可重现性,可以去掉RandomState(42)
,让程序生成不同的随机数。例如,可以使用以下代码生成随机数:
noisy = np.random.normal(digits.data, 2)
此时,np.random.normal()
方法会根据系统时间等因素自动确定随机数生成的种子,从而生成不同的随机数。
逆转降维结果,实现降噪
pca = PCA(0.5).fit(noisy)
X_dr = pca.transform(noisy)
X_dr.shape
without_noise = pca.inverse_transform(X_dr)
plot_digits(without_noise)