使用样本进行模型训练时,特征个数太多会增加模型训练的复杂性。所以,我们希望数据集既能特征个数较少,又蕴含信息较多的信息。
虽然我们已经拿到数据集,并且数据集的维度可能较大,我们也可以通过一些方法来降低数据的维度。它的思想是:
在数据集中,特征之间是有一定的相关关系的;
当两个特征之间有一定相关关系时,可以解释为这两个特征表达的信息有一定的重叠。
PCA 就是将重复的特征(关系紧密)删去多余,建立尽可能少的新特征,使得这些新特征是两两不相关的,而且这些新特征则会尽可能多的保留原有的信息。
注意:PCA 是删除原有关系紧密的特征,产生新的不相关的特征。
你可能会想到,我们删除多余特征,保留一部分特征不就可以吗?举个例子:我们有 A、B、C 三个特征,特征之间具有一定的相关性,即:我们认为 3 个特征中包含了重叠、冗余的信息。 PCA 将新产生两个新的特征 D、E,然后将 A、B、C 中包含的部分信息融合到 D 特征中,部分信息融合到 E 特征中,并且 D 和 E 是不具有相关性。这样既能去除数据中的重叠、冗余信息,又能够实现数据维度的降低。
在这里也要清楚,当数据的维度降低时,势必会丢失部分原始数据信息。只要这部分信息不影响到我们的模型训练,我们是乐意这么做的。原始的特征在新特征组成的数据集中不会再存在。
1. 协方差矩阵
我们在前面提到 PCA 会根据特征之间的相关性来去除信息冗余,实现数据降维。在这里,通过协方差矩阵就能表现特征之间的相关性,方差和协方差的计算公式如下:
方差表示衡量特征的离散程度。方差是协方差的特殊情况,当 x 和 y 是同一个变量时,即:方差。当 x 和 y 非同一个变量时,即:协方差。
当 x 和 y 都大于自身的期望值时,协方差的值为正值,反之则为负值。协方差为 0 的两个变量,可以理解为不相关的两个变量。
协方差矩阵则为多个变量中,两两变量的协方差组成的矩阵。下面使用 Python 计算多个变量的方差和协方差矩阵:
import numpy as np import pandas as pd def cov(): np.random.seed(0) data = np.random.randint(1, 10, [3, 4]) dataset = pd.DataFrame(data, columns=['特征%d' % (index + 1) for index in range(4)], index=['样本%d' % (index + 1) for index in range(3)]) print(dataset, end='\n\n') # pandas 计算协方差矩阵 print('协方差矩阵:\n', dataset.cov().round(2)) print() # numpy 计算协方差矩阵 print('协方差矩阵:\n', np.cov(dataset.T)) print() # 计算协方差矩阵 data = data - data.mean(axis=0) data_cov = (data.T @ data) / (data.shape[0] - 1) print('协方差矩阵:\n', data_cov) if __name__ == '__main__': cov()
程序执行结果:
特征1 特征2 特征3 特征4 样本1 6 1 4 4 样本2 8 4 6 3 样本3 5 8 7 9 协方差矩阵: 特征1 特征2 特征3 特征4 特征1 2.33 -2.17 -0.33 -4.17 特征2 -2.17 12.33 5.17 9.33 特征3 -0.33 5.17 2.33 3.17 特征4 -4.17 9.33 3.17 10.33 协方差矩阵: [[ 2.33333333 -2.16666667 -0.33333333 -4.16666667] [-2.16666667 12.33333333 5.16666667 9.33333333] [-0.33333333 5.16666667 2.33333333 3.16666667] [-4.16666667 9.33333333 3.16666667 10.33333333]] 协方差矩阵: [[ 2.33333333 -2.16666667 -0.33333333 -4.16666667] [-2.16666667 12.33333333 5.16666667 9.33333333] [-0.33333333 5.16666667 2.33333333 3.16666667] [-4.16666667 9.33333333 3.16666667 10.33333333]]
2. 主成分
为了能够去除特征之间的相关关系,我们需要将样本投射到新的坐标系空间中,如下图所示:
上图中,左侧的图找那个的点表示 4 个样本点,横纵轴表示每个样本的 2 个特征。从图上可以看到,2 个特征之间呈现的是很强的线性相关关系。为了去除这个线性关系,我们找到了一个新的坐标系(如右图所示),并将原来的样本投射到新坐标中,此时,我们可以看到样本基本都投射到 X 轴上,投射到 Y 轴时方差为 0(几乎没啥信息),也就是说在新的坐标系中,原来的 2 个特征只需要用 1 个特征就能表示出所有的信息。我们把 x 轴叫做主成分,也可以把 y 轴叫做第二主成分。
如果原来我们的数据有 100 维,我们想将其降到 2 维,就只需要找到一个坐标系将原数据分别投射到第一主成分、第二主成分得到的 2 两个维度的数据就可以作为去除线性相关性之后的保留下来的信息,如果再增加几个主成分保留的信息能够更多一些。比如:想增加到 3 维,则找到第三个与 x 和 y 正交的轴,将其投射到该轴就会得到一个新的特征。
为了能够极大的降低数据的维度,并且能够尽可能多的保留原始数据的信息。我们希望找到的前 N 个主成分都能够极大的保留的数据的信息(也就是说,找到的轴使得原始数据投射到该轴时能够方差最大,方差越大我们认为该特征保留的信息越多,反之,方差越小则说明该特征保留的信息就越少)。
所以,只要能够找到原始数据分布中,方差最大的一个方向作为主成分,次大的作为第二成分,次次大的作为第三个主成分…. 以此类推,我们就可以实现将原始数据降维到指定维度。
前面提到的协方差矩阵就是为了找到这些个主成分。我们通过对协方差矩阵进行特征值分解会得到特征值和特征向量,特征值最大对应的特征向量,就是第一主成分,以此类推。 然后将原始数据依次投射到各个主成分上,就会得到一个由 M(M < N) 个新特征组成的降维后的数据,并且该数据去除数据中的相关性,并尽可能多的保留了原始数据的信息。
3. PCA API 使用
scikit-learn 中的 PCA API 的 n_components 参数可以指定降维的维度,也可以指定一个小数,保留多少信息量,这个信息量就是根据 explained_variance_ratio_ 来衡量。
from sklearn.decomposition import PCA if __name__ == '__main__': # 数据包含 4 个特征 data = [[2, 8, 4, 5], [6, 3, 0, 8], [5, 4, 9, 1]] # 实例化 PCA 对象,并指定维度 model = PCA(n_components=2) # 数据降维 decomposition_data = model.fit_transform(data) print('降维后数据:\n', decomposition_data) # 打印主成分 print('主成分:\n', model.components_) # 新特征的方差 print('特征方差:\n', model.explained_variance_) # 打印特征的方差比率 print('方差比率:\n', model.explained_variance_ratio_)
程序执行结果:
降维后数据: [[ 1.28620952e-15 3.82970843e+00] [ 5.74456265e+00 -1.91485422e+00] [-5.74456265e+00 -1.91485422e+00]] 主成分: [[ 0.08703883 -0.08703883 -0.78334945 0.6092718 ] [-0.6092718 0.78334945 -0.08703883 0.08703883]] 特征方差: [33. 11.] 方差比率: [0.75 0.25]