python numpy 和 sklearn 之间的 PCA 差异

PCA difference between python numpy and sklearn

我正在尝试使用 numpy.linalg.eig 和两种不同的方法(使用特征脸中使用的协方差和 pca 方法)来实现 PCA,并将我的结果与 sklearn 的 PCA 进行比较。但是我发现我的结果不同,所以我想知道我犯了哪个错误。我有 3 个样本,每个样本有 4 个特征。我正在尝试将样本的维度减少到 3。 编辑:使用 SVD 方法添加。我使用来自 sklearn 的协方差 PCA、SVD 和 PCA 得到的结果非常接近。但是使用 "eigenface" 方法就完全不同了,为什么? 将 numpy 导入为 np 来自 sklearn.decomposition 导入 PCA

x = np.array([[0.5, 0.8, 1.5, -2.4], [-1.9, -8.7, 0.02, 4.9], [5.5,6.1, -8.1,3.0]])
print(x)
K = 3

# -- sklearn -- #
pca = PCA(n_components=K).fit(x)
res = pca.transform(x)
print('sklearn :', res)

# -- numpy covariance -- #
X = x - np.mean(x, axis = 0)  

cov = np.cov(X.T)
print("covariance :", cov.shape)

evals , evecs = np.linalg.eig(cov)

idx = np.argsort(evals)[::-1]
evecs = evecs[:,idx]
evals = evals[idx]

res2 = X.dot(evecs[:,:K]) 
print("numpy with cov:", res2)

# -- numpy scatter matrix -- #
X = x - np.mean(x, axis = 0)
C = np.dot(X, X.T)
evals , evecs = np.linalg.eig(C)
idx = np.argsort(evals)[::-1]
evecs = evecs[:,idx]
evals = evals[idx]

v = np.dot(evecs, X)
print("v :", v.shape)
res3= X[:, :K].dot(v)
print('numpy with scatter matrix : ', res3)

# -- numpy svd -- #
X = x - np.mean(x, axis = 0)  
U, S, V = np.linalg.svd(X, full_matrices=False)
U, V = svd_flip(U, V)
res2 = X.dot(V.T) 
print("numpy with svd:", res2)

我的猜测是,您计算特征向量的两种方法给出的结果与 scipy.linalg.svd 不同,这正是 scipy 的 PCA 实现所使用的 (https://github.com/scikit-learn/scikit-learn/blob/f3320a6f/sklearn/decomposition/pca.py#L399)。

这可能是一个很好的起点:

首先:这是什么意思?您在 4 维 space 中有三个点。它们跨越一个二维平面。 PCA 找到这个平面的基础,以及该基础中你的点的系数。使用 Matlab 的 [C, S] = pca(x) 进行比较:我们得到

C =
    0.4028    0.1082
    0.7895   -0.3198
   -0.4560   -0.5881
   -0.0806    0.7349

S =
   -0.5865   -5.8249
   -8.9674    3.1891
    9.5539    2.6357

是具有 属性 的矩阵,S*C' 恢复居中数据(在您的符号中是 X)。 C 的列是 2D subspace 的基向量,S 的行是该子集中三个点的坐标。

Sklearn returns

[[ -5.86525831e-01   5.82485371e+00  -2.65147201e-16]
 [ -8.96738194e+00  -3.18911605e+00   1.41061647e-16]
 [  9.55390777e+00  -2.63573766e+00  -5.28988843e-16]]

其中第三列是噪声(基本上为零),反映出这些点位于二维平面内;没有找到第 3 个主成分。前两列与 Matlab 中的 S 匹配,但符号选择除外。

您对 "NumPy with cov" 的计算与 sklearn 相同,只是第 3 列中的随机噪声不同。顺便说一句,对于此计算,您不需要将数据居中; cov 自己做。 cov = np.cov(x.T) 也可以。

[[ -5.86525831e-01  -5.82485371e+00   5.26721273e-16]
 [ -8.96738194e+00   3.18911605e+00   3.83725073e-15]
 [  9.55390777e+00   2.63573766e+00  -3.35763132e-15]]  

"Eigenface"接近

这里的主要思想是,我们将使用更小的 C = np.dot(X, X.T),而不是计算 np.dot(X.T, X)(本质上是协方差,直到常数因子)。我们需要的基向量将通过将 C 的特征向量与 X.T 相乘获得(如果您遵循 Wikipedia's article,请注意它们的 T 与您的 X 的方向不同)。但是,与 np.linalg.eig 返回的向量不同,这些向量未被归一化。我们必须在使用之前对它们进行标准化:

X = x - np.mean(x, axis = 0)
C = np.dot(X, X.T)
evals , evecs = np.linalg.eig(C)
idx = np.argsort(evals)[::-1]
evecs = evecs[:,idx]
evals = evals[idx]
v = np.dot(X.T, evecs)
v /= np.linalg.norm(v, axis=0)
res3 = X.dot(v)

这个returns

[[-0.58652583 -5.82485371  5.05711518]
 [-8.96738194  3.18911605  1.72266002]
 [ 9.55390777  2.63573766 -6.7797752 ]]

前两列是正确的。同样,第三列是噪声,但现在是经过归一化处理的噪声,所以一点也不小。人们必须明白第三列是没有意义的。