PCA(Principal Component Analysis),即主成分分析方法,是一种运用广泛的数据降维算法。PCA的主要思想是将N维特征映射到K维空间上,这K维空间中的特征是全新的正交特征,即为主成分,是在原有N维特征空间的基础上重新构造出来的K维特征。PCA的工作就是从原始维度空间中按序找出一组相互正交的坐标轴,坐标轴的选择与数据是密切相关的。首先找出原始数据中方差最大的方向定为第一个坐标轴,其次在第一个坐标轴正交的平面上找到使得数据方差最大的方向定为第二个坐标轴,然后在与之前两个坐标轴都正交的平面上找到方差最大的方向定位第三个坐标轴,以此类推,得到N个坐标轴。其中大部分方差都在前K个坐标轴中,其余几乎为零。所以保留前K个坐标轴,即保留包含大部分方差的维度,忽略其余为度,实现对数据降维的目的。

原理

找到方差最大的K个维度的方法是计算原始数据的协方差矩阵,之后得到矩阵的特征值和特征向量,选择特征值最大的,即方差最大的K个特征所对应的特征向量,将这些特征向量组合成一个矩阵,即完成了将原始数据转换到新的维度空间的目的。

样本$X$与样本$Y$的协方差计算方法如下:

对于三维样本数据$X,Y,Z$,计算协方差就转化为计算其协方差矩阵:

PCA算法的实现

基于特征值分解协方差矩阵实现PCA算法

输入为数据集$X={x_1,x_2,x_3,…,x_n}​$。

  1. 将每一个特征减去平均值,即去中心化
  2. 计算协方差矩阵$\frac{1}{n}XX^T​$
  3. 特征值分解法求协方差矩阵的特征值和特征向量
  4. 对特征值进行排序,选取最大的k个特征值
  5. 将其所对应的k个特征向量作为行向量组合起来形成特征向量矩阵P
  6. 将原始数据转换到新的特征空间中,即$Y=PX​$

基于SVD分解协方差矩阵实现PCA算法

输入为数据集$X={x_1,x_2,x_3,…,x_n}$。

  1. 将每一个特征减去平均值,即去中心化
  2. 计算协方差矩阵$\frac{1}{n}XX^T$
  3. 通过SVD计算协方差矩阵的特征值和特征向量
  4. 对特征值进行排序,选取最大的k个特征值
  5. 将其所对应的k个特征向量作为行向量组合起来形成特征向量矩阵P
  6. 将原始数据转换到新的特征空间中,即$Y=PX$

PCA算法的理论

最大方差理论

在信号处理中,认为信号具有较大的方差,而噪声具有较小的方差,信噪比就是信号与噪声的方差比,即越大越好。如果信号在$u_1$上的投影方差较大,在$u_2$上的投影方差较小,那么可以认为$u_2$上的投影是由噪声引起的。则最好的降维结果就是降维之后每一维的样本方差都很大。

主成分个数

其中$x_{approx}$表示投影后的位置,选取满足上式条件的最小k值。其中$t$由自己决定,当$t=0.01$则代表PCA算法保留99%的主要信息。

代码实现(Java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
import Jama.Matrix;
import com.sun.source.tree.Tree;

import java.util.*;

public class PCA {

private static final double threshold = 0.95;

public double[][] meanToZero(double[][] data) {
int rows = data.length;
int cols = data[0].length;
double[] eachRowSum = new double[cols];
double[] eachRowAve = new double[cols];
for(int i = 0; i < cols; i++) {
for (int j = 0; j < rows; j++) {
eachRowSum[i] += data[j][i];
}
eachRowAve[i] = eachRowSum[i] / rows;
}

double[][] newData = new double[rows][cols];
for(int i = 0; i < cols; i++) {
for(int j = 0; j < rows; j++) {
newData[j][i] = data[j][i] - eachRowAve[i];
}
}
return newData;
}

public double[][] getCoveMatrix(double[][] matrix) {
int rows = matrix.length;
int cols = matrix[0].length;
double[][] covMatrix = new double[cols][cols];
for(int i = 0; i < cols; i++) {
for(int j = 0; j < cols; j++) {
double temp = 0;
for(int k = 0; k < rows; k++) {
temp += matrix[k][i] * matrix[k][j];
}
covMatrix[i][j] = temp / rows;
}
}
return covMatrix;
}

public double[][] featureVal(double[][] matrix) {
Matrix a = new Matrix(matrix);
double[][] res = a.eig().getV().getArray();
return res;
}

public Matrix getPrincipalComponent(double[][] preArray, double[][] featureVal, double[][] featureVec) {
Matrix a = new Matrix(featureVec);
double[][] featureVecT = a.transpose().getArray();
Map<Integer, double[]> principalMap = new HashMap<Integer, double[]>();
TreeMap<Double, double[]> featureMap = new TreeMap<Double, double[]>(Collections.reverseOrder());
double feaValSum = 0;
int index = 0, n = featureVal.length;
double[] featureValArray = new double[n];
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(i == j) {
featureValArray[index] = featureVal[i][j];
}
}
index++;
}
for(int i = 0; i < featureVecT.length; i++) {
double[] val = new double[featureVecT[0].length];
val = featureVecT[i];
featureMap.put(featureValArray[i], val);
}

for(int i = 0; i < n; i++) {
feaValSum += featureValArray[i];
}

double tmp = 0;
int principalComponentNum = 0;
List<Double> pcFeaVal = new ArrayList<Double>();
for(double key : featureMap.keySet()) {
if(tmp / feaValSum <= threshold) {
tmp += key;
pcFeaVal.add(key);
principalComponentNum++;
}
}
System.out.println("\n" + "特征阈值:" + threshold);
System.out.println("得到主成分个数:" + principalComponentNum + "\n");

for(int i = 0; i < pcFeaVal.size(); i++) {
if(featureMap.containsKey(pcFeaVal.get(i))) {
principalMap.put(i, featureMap.get(pcFeaVal.get(i)));
}
}

double[][] principalArray = new double[principalMap.size()][];
Iterator<Map.Entry<Integer, double[]>> it = principalMap.entrySet().iterator();
for(int i = 0; it.hasNext(); i++) {
principalArray[i] = it.next().getValue();
}

Matrix principalMatrix = new Matrix(principalArray);
return principalMatrix;
}

public Matrix getRes(double[][] pre, Matrix matrix) {
Matrix preMatrix = new Matrix(pre);
Matrix res = preMatrix.times(matrix.transpose());
return res;
}

public static void main(String[] args) {

PCA pca = new PCA();
//获得样本集
double[][] data = new double[2][4];
data[0][0] = 5.1;
data[0][1] = 3.5;
data[0][2] = 1.4;
data[0][3] = 0.2;
data[1][0] = 4.9;
data[1][1] = 3.0;
data[1][2] = 1.4;
data[1][3] = 0.2;
System.out.println("--------------------------------------------");
System.out.println("原数据: ");
for(int i = 0; i < data.length; i++) {
for(int j = 0; j < data[i].length; j++) {
System.out.print(data[i][j]);
System.out.print(" ");
}
System.out.print("\n");
}
System.out.println("--------------------------------------------");
//均值为零矩阵
double[][] averageArray = pca.meanToZero(data);

//协方差矩阵
double[][] varMatrix = pca.getCoveMatrix(averageArray);

//特征值矩阵
double[][] eigenvalueMatrix = pca.featureVal(varMatrix);

//特征向量矩阵
double[][] eigenVectorMatrix = pca.featureVal(varMatrix);

//主成分矩阵
Matrix principalMatrix = pca.getPrincipalComponent(data, eigenvalueMatrix, eigenVectorMatrix);

//降维后矩阵
Matrix resultMatrix = pca.getRes(data, principalMatrix);

int c = resultMatrix.getColumnDimension(); //列数
int r = resultMatrix.getRowDimension();//行数
System.out.println(resultMatrix.getRowDimension() + "," + resultMatrix.getColumnDimension());

}
}