PCA的主要思想是将n维特征映射到k维上,这k维是全新的正交特征也被称为主成分,是在原有n维特征的基础上重新构造出来的k维特征。PCA的工作就是从原始的空间中顺序地找一组相互正交的坐标轴,新的坐标轴的选择与数据本身是密切相关的。其中,第一个新坐标轴选择是原始数据中方差最大的方向,第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴。通过这种方式获得的新的坐标轴,我们发现,大部分方差都包含在前面k个坐标轴中,后面的坐标轴所含的方差几乎为0。于是,我们可以忽略余下的坐标轴,只保留前面k个含有绝大部分方差的坐标轴。事实上,这相当于只保留包含绝大部分方差的维度特征,而忽略包含方差几乎为0的特征维度,实现对数据特征的降维处理。
问题是我们如何得到这些包含最大差异性的主成分方向呢?事实上,通过计算数据矩阵的协方差矩阵,然后得到协方差矩阵的特征值特征向量,选择特征值最大(即方差最大)的k个特征所对应的特征向量组成的矩阵。这样就可以将数据矩阵转换到新的空间当中,实现数据特征的降维。
由于得到协方差矩阵的特征值特征向量有两种方法:特征值分解协方差矩阵、奇异值分解协方差矩阵,所以PCA算法有两种实现方法:基于特征值分解协方差矩阵实现PCA算法、基于SVD分解协方差矩阵实现PCA算法,本文介绍后者。
奇异值分解是一个能适用于任意矩阵的一种分解的方法,对于任意矩阵A总是存在一个奇异值分解:
假设A是一个mn的矩阵,那么得到的U是一个mm的方阵,U里面的正交向量被称为左奇异向量。Σ是一个mn的矩阵,Σ除了对角线其它元素都为0,对角线上的元素称为奇异值。 VT是v的转置矩阵,是一个nn的矩阵,它里面的正交向量被称为右奇异值向量。而且一般来讲,我们会将Σ上的值按从大到小的顺序排列。
SVD分解矩阵A的步骤:
(1) 求A*AT的特征值和特征向量,用单位化的特征向量构成 U。
(2) 求 AT*A的特征值和特征向量,用单位化的特征向量构成 V。
(3) 将 AAT或者ATA的特征值求平方根,然后构成 Σ。
将矩阵A分解为两个正交矩阵和一个对角矩阵的乘积,这个对角矩阵的元素就是原矩阵的奇异值。然后可以据此求主成分:
输入:数据集 X={x1,x2…xn},需要降到k维。
输入就是网络的输出特征X和我们想要得到的维度k,输出就是降维后的特征:
def PCA_svd(X, k, center=True):
n = X.size()[0]
ones = torch.ones(n).view([n,1])
h = ((1/n) * torch.mm(ones, ones.t())) if center else torch.zeros(n*n).view([n,n])
H = torch.eye(n) - h
H = H.cuda()
X_center = torch.mm(H.double(), X.double())
u, s, v = torch.svd(X_center)
components = v[:k].t()
#explained_variance = torch.mul(s[:k], s[:k])/(n-1)
return components
在调用的时候:
self.feature_dim = 256
feature = PCA_svd(feature, self.feature_dim)
feature = feature.float()
或者可以用numpy实现:
##Python实现PCA
import numpy as np
def pca(X,k):#k is the components you want
#mean of each feature
n_samples, n_features = X.shape
mean=np.array([np.mean(X[:,i]) for i in range(n_features)])
#normalization
norm_X=X-mean
#scatter matrix
scatter_matrix=np.dot(np.transpose(norm_X),norm_X)
#Calculate the eigenvectors and eigenvalues
eig_val, eig_vec = np.linalg.eig(scatter_matrix)
eig_pairs = [(np.abs(eig_val[i]), eig_vec[:,i]) for i in range(n_features)]
# sort eig_vec based on eig_val from highest to lowest
eig_pairs.sort(reverse=True)
# select the top k eig_vec
feature=np.array([ele[1] for ele in eig_pairs[:k]])
#get new data
data=np.dot(norm_X,np.transpose(feature))
return data
X = np.array([[-1, 1], [-2, -1], [-3, -2], [1, 1], [2, 1], [3, 2]])
print(pca(X,1))