聊聊基於Alink庫的主成分分析(PCA)

又見阿郎發表於2023-10-03

概述

主成分分析(Principal Component Analysis,PCA)是一種常用的資料降維和特徵提取技術,用於將高維資料轉換為低維的特徵空間。其目標是透過線性變換將原始特徵轉化為一組新的互相無關的變數,這些新變數稱為主成分,它們按照方差遞減的順序排列,以保留儘可能多的原始資料資訊。
主成分分析的基本思想可以總結如下:

  1. 尋找新的特徵空間:PCA透過線性變換,尋找一組新的特徵空間,使得新的特徵具有以下性質:
    • 主成分具有最大的方差,儘可能保留原始資料的資訊。
    • 不同主成分之間彼此無關,即它們是正交的(互相垂直)。
  2. 降低資料維度:保留方差較大的主成分,捨棄方差較小的主成分,從而實現資料降維。

主成分分析的步驟如下:

  • 中心化資料:將原始資料進行中心化,使得資料的均值為零。
  • 計算協方差矩陣:計算特徵之間的協方差矩陣,描述了特徵之間的線性關係。
  • 計算特徵值和特徵向量:對協方差矩陣進行特徵值分解,得到特徵值和對應的特徵向量。
  • 選擇主成分:按照特徵值的大小選擇保留的主成分數量,通常選擇方差較大的前幾個主成分。
  • 得到新的特徵空間:將原始特徵投影到選定的主成分上,得到新的特徵空間。

主成分分析的應用包括降維、去除資料噪聲、資料視覺化、特徵選擇等。透過保留最重要的特徵,可以在減少資料維度的同時保持對資料的關鍵資訊進行捕獲。
在實際使用中,有時會將各個變數進行標準化,此時的協方差矩陣就相當於原始資料的相關係數矩陣。所以Alink的主成分分析元件提供了兩種計算選擇,引數CalculationType可以設定為相關係數矩陣(CORR)或者協方差矩陣(COV),預設為相關係數矩陣,即對標準化後的資料計算其主成分。

Alink庫中的實現與應用

示例

以美國50個州的7種犯罪率為例,做主成分分析。這7種犯罪分別是:"murder", "rape", "robbery", "assault", "burglary", "larceny", "auto"。從這7個變數出發來評價各州的治安和犯罪情況是很難的,而使用主成分分析可以把這些變數概括為2-3個綜合變數(即主成分),便於更簡便的分析這些資料。

/**
 * 主成分分析
 * 1.基於預設的計算方式(CORR),計算主成分
 * 2.設定K為4,將原先的7個維度降低到4個維度
 * 3.輸出向量列,使用VectorToColumnsBatchOp組元件將向量列轉為4個資料列,名稱分別為"prin1, prin2, prin3, prin4"
 * */
static void c_1() throws Exception {

    MemSourceBatchOp source = new MemSourceBatchOp(CRIME_ROWS_DATA, CRIME_COL_NAMES);

    source.lazyPrint(10, "Origin data");

    BatchOperator <?> pca_result = new PCA()
        .setK(4)
        .setSelectedCols("murder", "rape", "robbery", "assault", "burglary", "larceny", "auto")
        .setPredictionCol(VECTOR_COL_NAME)
        .enableLazyPrintModelInfo()
        .fit(source)
        .transform(source)
        .link(
            new VectorToColumnsBatchOp()
                .setVectorCol(VECTOR_COL_NAME)
                .setSchemaStr("prin1 double, prin2 double, prin3 double, prin4 double")
                .setReservedCols("state")
        )
        .lazyPrint(10, "state with principle components");

    pca_result
        .select("state, prin1")
        .orderBy("prin1", 100, false)
        .lazyPrint(-1, "Order by prin1");

    pca_result
        .select("state, prin2")
        .orderBy("prin2", 100, false)
        .lazyPrint(-1, "Order by prin2");

    BatchOperator.execute();

}

當然還可以先將資料標準化後再做主成分分析。如下

/**
 * 主成分分析
 * 1. 先將資料標準化
 * 2. 設定計算方式為協方差計算,設定K為4,將原先的7個維度降低到4個維度
 * 3.輸出向量列,使用VectorToColumnsBatchOp組元件將向量列轉為4個資料列,名稱分別為"prin1, prin2, prin3, prin4"
 * */
static void c_2() throws Exception {

    MemSourceBatchOp source = new MemSourceBatchOp(CRIME_ROWS_DATA, CRIME_COL_NAMES);

    Pipeline std_pca = new Pipeline()
        .add(
            new StandardScaler()
                .setSelectedCols("murder", "rape", "robbery", "assault", "burglary", "larceny", "auto")
        )
        .add(
            new PCA()
                .setCalculationType(CalculationType.COV)
                .setK(4)
                .setSelectedCols("murder", "rape", "robbery", "assault", "burglary", "larceny", "auto")
                .setPredictionCol(VECTOR_COL_NAME)
                .enableLazyPrintModelInfo()
        );

    std_pca
        .fit(source)
        .transform(source)
        .link(
            new VectorToColumnsBatchOp()
                .setVectorCol(VECTOR_COL_NAME)
                .setSchemaStr("prin1 double, prin2 double, prin3 double, prin4 double")
                .setReservedCols("state")
        )
        .lazyPrint(10, "state with principle components");
    BatchOperator.execute();

}

應用

在聚類方面的應用

主要透過降維來減少特徵的維度,從而在聚類過程中降低資料的複雜度和計算成本,同時提高聚類的效果。主要實現過程如下:

  1. 使用 PCA 對資料進行降維,得到新的特徵空間。設定降維後的維度,通常選擇較小的維度以減少特徵數。
  2. 在降維後的特徵空間上應用聚類演算法,比如 K-means、DBSCAN 等。
  3. 使用適當的聚類評估指標,如輪廓係數等,來評估聚類的效果。

示例程式碼如下:

/**
 * 聚類+主成分分析
 * 1. 將資料降維,只使用5%的維度資料
 * 2. K-Means聚類:分別將原始資料與主成分分析後的資料做聚類操作
 * */
static void c_3() throws Exception {

    AkSourceBatchOp source = new AkSourceBatchOp().setFilePath(DATA_DIR + SPARSE_TRAIN_FILE);

    source
        .link(
            new PcaTrainBatchOp()
                .setK(39)
                .setCalculationType(CalculationType.COV)
                .setVectorCol(VECTOR_COL_NAME)
                .lazyPrintModelInfo()
        )
        .link(
            new AkSinkBatchOp()
                .setFilePath(DATA_DIR + PCA_MODEL_FILE)
                .setOverwriteSink(true)
        );
    BatchOperator.execute();

    BatchOperator <?> pca_result = new PcaPredictBatchOp()
        .setVectorCol(VECTOR_COL_NAME)
        .setPredictionCol(VECTOR_COL_NAME)
        .linkFrom(
            new AkSourceBatchOp().setFilePath(DATA_DIR + PCA_MODEL_FILE),
            source
        );

    Stopwatch sw = new Stopwatch();

    KMeans kmeans = new KMeans()
        .setK(10)
        .setVectorCol(VECTOR_COL_NAME)
        .setPredictionCol(PREDICTION_COL_NAME);

    sw.reset();
    sw.start();
    kmeans
        .fit(source)
        .transform(source)
        .link(
            new EvalClusterBatchOp()
                .setVectorCol(VECTOR_COL_NAME)
                .setPredictionCol(PREDICTION_COL_NAME)
                .setLabelCol(LABEL_COL_NAME)
                .lazyPrintMetrics("KMeans")
        );
    BatchOperator.execute();
    sw.stop();
    System.out.println(sw.getElapsedTimeSpan());

    sw.reset();
    sw.start();
    kmeans
        .fit(pca_result)
        .transform(pca_result)
        .link(
            new EvalClusterBatchOp()
                .setVectorCol(VECTOR_COL_NAME)
                .setPredictionCol(PREDICTION_COL_NAME)
                .setLabelCol(LABEL_COL_NAME)
                .lazyPrintMetrics("KMeans + PCA")
        );
    BatchOperator.execute();
    sw.stop();
    System.out.println(sw.getElapsedTimeSpan());

}

相關文章