1. 程式人生 > >機器學習演算法——PCA演算法介紹以及Java實現

機器學習演算法——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());
    }
}