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}$。
将每一个特征减去平均值,即去中心化
计算协方差矩阵$\frac{1}{n}XX^T$
用特征值分解法 求协方差矩阵的特征值和特征向量
对特征值进行排序,选取最大的k个特征值
将其所对应的k个特征向量作为行向量组合起来形成特征向量矩阵P
将原始数据转换到新的特征空间中,即$Y=PX$
基于SVD分解协方差矩阵实现PCA算法 输入 为数据集$X={x_1,x_2,x_3,…,x_n}$。
将每一个特征减去平均值,即去中心化
计算协方差矩阵$\frac{1}{n}XX^T$
通过SVD 计算协方差矩阵的特征值和特征向量
对特征值进行排序,选取最大的k个特征值
将其所对应的k个特征向量作为行向量组合起来形成特征向量矩阵P
将原始数据转换到新的特征空间中,即$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()); } }