機器學習演算法——PCA演算法介紹以及Java實現
PCA演算法
一、演算法概述
主成分分析(PCA)是多元統計分析中用來分析資料的一種方法,PCA通過線性變換將原始資料變換為一組各維度線性無關的表示,可用於提取資料的主要特徵分量,常用於高維資料的降維。
PCA方法最著名的應用應該是在人臉識別中特徵提取及資料維,我們知道輸入200*200大小的人臉影象,單單提取它的灰度值作為原始特徵,則這個原始特徵將達到40000維,這給後面分類器的處理將帶來極大的難度。在這種情況下,我們必須對資料進行降維。
降維當然意味著資訊的丟失,不過鑑於實際資料本身常常存在的相關性,我們可以想辦法在降維的同時將資訊的損失儘量降低。
例如某淘寶店鋪的資料記錄為(日期, 瀏覽量, 訪客數, 下單數, 成交數, 成交金額),從經驗我們可以知道,“瀏覽量”和“訪客數”往往具有較強的相關關係,而“下單數”和“成交數”也具有較強的相關關係。這裡我們非正式的使用“相關關係”這個詞,可以直觀理解為“當某一天這個店鋪的瀏覽量較高(或較低)時,我們應該很大程度上認為這天的訪客數也較高(或較低)”。後面的章節中我們會給出相關性的嚴格數學定義。
這種情況表明,如果我們刪除瀏覽量或訪客數其中一個指標,我們應該期待並不會丟失太多資訊。因此我們可以刪除一個,以降低機器學習演算法的複雜度。所以,我們可以採用PCA進行降緯。
二、演算法原理
1、資料準備
假設有M個樣本,每個樣本有N個特徵,例如第i個(i=1,2,…,M)樣本為:
則M個樣本構成了M行N列的數值矩陣A。
2、資料歸一化處理
通常做法是將每一維的資料都減去該維的均值,使每一維的均值都為0。
3、計算協方差矩陣
協方差是一種用來度量兩個隨機變數關係的統計量,其定義為:
M*N樣本的協方差矩陣為:
4、求出協方差矩陣的特徵值及對應的特徵向量
若AX=λX,則稱λ是A的特徵值,X是對應的特徵向量。實際上可以這樣理解:矩陣A作用在它的特徵向量X上,僅僅使得X的長度發生了變化,縮放比例就是相應的特徵值λ。
特別地,當A是對稱矩陣時,A的奇異值等於A的特徵值,存在正交矩陣Q(Q-1=QT),使得:
對A進行奇異值分解就能求出所有特徵值和Q矩陣。
A∗Q=Q∗DA∗Q=Q∗D,D是由特徵值組成的對角矩陣
由特徵值和特徵向量的定義知,Q的列向量就是A的特徵向量。
5、將特徵向量按對應的特徵值大小從上往下按行排列成矩陣,取前k行組成矩陣P,P為k行n列矩陣
6、Y=AP’ 即為降維到k維後的資料,Y為M行k列矩陣
三、程式碼實現
PCA.class
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import Jama.Matrix;
/*
* 演算法步驟:
* 1)將原始資料按列組成n行m列矩陣X
* 2)特徵中心化。即每一維的資料都減去該維的均值,使每一維的均值都為0
* 3)求出協方差矩陣
* 4)求出協方差矩陣的特徵值及對應的特徵向量
* 5)將特徵向量按對應的特徵值大小從上往下按行排列成矩陣,取前k行組成矩陣p
* 6)Y=PX 即為降維到k維後的資料
*/
public class PCA {
private static final double threshold = 0.95;// 特徵值閾值
/**
*
* 使每個樣本的均值為0
*
* @param primary
* 原始二維陣列矩陣
* @return averageArray 中心化後的矩陣
*/
public double[][] changeAverageToZero(double[][] primary) {
int n = primary.length;
int m = primary[0].length;
double[] sum = new double[m];
double[] average = new double[m];
double[][] averageArray = new double[n][m];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
sum[i] += primary[j][i];
}
average[i] = sum[i] / n;
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
averageArray[j][i] = primary[j][i] - average[i];
}
}
return averageArray;
}
/**
*
* 計算協方差矩陣
*
* @param matrix
* 中心化後的矩陣
* @return result 協方差矩陣
*/
public double[][] getVarianceMatrix(double[][] matrix) {
int n = matrix.length;// 行數
int m = matrix[0].length;// 列數
double[][] result = new double[m][m];// 協方差矩陣
for (int i = 0; i < m; i++) {
for (int j = 0; j < m; j++) {
double temp = 0;
for (int k = 0; k < n; k++) {
temp += matrix[k][i] * matrix[k][j];
}
result[i][j] = temp / (n - 1);
}
}
return result;
}
/**
* 求特徵值矩陣
*
* @param matrix
* 協方差矩陣
* @return result 向量的特徵值二維陣列矩陣
*/
public double[][] getEigenvalueMatrix(double[][] matrix) {
Matrix A = new Matrix(matrix);
// 由特徵值組成的對角矩陣,eig()獲取特徵值
// A.eig().getD().print(10, 6);
double[][] result = A.eig().getD().getArray();
return result;
}
/**
* 標準化矩陣(特徵向量矩陣)
*
* @param matrix
* 特徵值矩陣
* @return result 標準化後的二維陣列矩陣
*/
public double[][] getEigenVectorMatrix(double[][] matrix) {
Matrix A = new Matrix(matrix);
// A.eig().getV().print(6, 2);
double[][] result = A.eig().getV().getArray();
return result;
}
/**
* 尋找主成分
*
* @param prinmaryArray
* 原始二維陣列陣列
* @param eigenvalue
* 特徵值二維陣列
* @param eigenVectors
* 特徵向量二維陣列
* @return principalMatrix 主成分矩陣
*/
public Matrix getPrincipalComponent(double[][] primaryArray,
double[][] eigenvalue, double[][] eigenVectors) {
Matrix A = new Matrix(eigenVectors);// 定義一個特徵向量矩陣
double[][] tEigenVectors = A.transpose().getArray();// 特徵向量轉置
Map<Integer, double[]> principalMap = new HashMap<Integer, double[]>();// key=主成分特徵值,value=該特徵值對應的特徵向量
TreeMap<Double, double[]> eigenMap = new TreeMap<Double, double[]>(
Collections.reverseOrder());// key=特徵值,value=對應的特徵向量;初始化為翻轉排序,使map按key值降序排列
double total = 0;// 儲存特徵值總和
int index = 0, n = eigenvalue.length;
double[] eigenvalueArray = new double[n];// 把特徵值矩陣對角線上的元素放到陣列eigenvalueArray裡
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (i == j)
eigenvalueArray[index] = eigenvalue[i][j];
}
index++;
}
for (int i = 0; i < tEigenVectors.length; i++) {
double[] value = new double[tEigenVectors[0].length];
value = tEigenVectors[i];
eigenMap.put(eigenvalueArray[i], value);
}
// 求特徵總和
for (int i = 0; i < n; i++) {
total += eigenvalueArray[i];
}
// 選出前幾個主成分
double temp = 0;
int principalComponentNum = 0;// 主成分數
List<Double> plist = new ArrayList<Double>();// 主成分特徵值
for (double key : eigenMap.keySet()) {
if (temp / total <= threshold) {
temp += key;
plist.add(key);
principalComponentNum++;
}
}
System.out.println("\n" + "當前閾值: " + threshold);
System.out.println("取得的主成分數: " + principalComponentNum + "\n");
// 往主成分map裡輸入資料
for (int i = 0; i < plist.size(); i++) {
if (eigenMap.containsKey(plist.get(i))) {
principalMap.put(i, eigenMap.get(plist.get(i)));
}
}
// 把map裡的值存到二維數組裡
double[][] principalArray = new double[principalMap.size()][];
Iterator<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;
}
/**
* 矩陣相乘
*
* @param primary
* 原始二維陣列
*
* @param matrix
* 主成分矩陣
*
* @return result 結果矩陣
*/
public Matrix getResult(double[][] primary, Matrix matrix) {
Matrix primaryMatrix = new Matrix(primary);
Matrix result = primaryMatrix.times(matrix.transpose());
return result;
}
}
主函式呼叫PCA:
import Jama.Matrix;
import java.io.FileWriter;
import java.io.IOException;
public class PCAMain {
public static void main(String[] args) throws IOException {
// TODO Auto-generated catch block
SelectData selectData = new SelectData();
PCA pca = new PCA();
//獲得樣本集
double[][] primaryArray = selectData.getdatas();
System.out.println("--------------------------------------------");
double[][] averageArray = pca.changeAverageToZero(primaryArray);
System.out.println("--------------------------------------------");
System.out.println("均值0化後的資料: ");
System.out.println(averageArray.length + "行,"
+ averageArray[0].length + "列");
System.out.println("---------------------------------------------");
System.out.println("協方差矩陣: ");
double[][] varMatrix = pca.getVarianceMatrix(averageArray);
System.out.println("--------------------------------------------");
System.out.println("特徵值矩陣: ");
double[][] eigenvalueMatrix = pca.getEigenvalueMatrix(varMatrix);
System.out.println("--------------------------------------------");
System.out.println("特徵向量矩陣: ");
double[][] eigenVectorMatrix = pca.getEigenVectorMatrix(varMatrix);
System.out.println("--------------------------------------------");
Matrix principalMatrix = pca.getPrincipalComponent(primaryArray, eigenvalueMatrix, eigenVectorMatrix);
System.out.println("主成分矩陣: ");
// principalMatrix.print(6, 3);
System.out.println("--------------------------------------------");
System.out.println("降維後的矩陣: ");
Matrix resultMatrix = pca.getResult(primaryArray, principalMatrix);
// resultMatrix.print(6, 3);
int c = resultMatrix.getColumnDimension(); //列數
int r = resultMatrix.getRowDimension();//行數
System.out.println(resultMatrix.getRowDimension() + "," + resultMatrix.getColumnDimension());
}
}