PCA 到底在做什麼?從幾何直覺、特徵值到 SVD 的完整推導

PCA 到底在做什麼?從幾何直覺、特徵值到 SVD 的完整推導


在一個有 10,000 個欄位的資料集裡,真正困難的通常不是把模型跑起來,而是回答一個更早的問題:這 10,000 個方向,是否真的都是資料用來變化的方向?

主成分分析(Principal Component Analysis, PCA)處理的正是這個問題。它不會替你找出「最重要的原始欄位」,也不會知道哪個方向對分類標籤最有用。PCA 做的事情更幾何,也更克制:把資料平移到中心,旋轉一組新的互相垂直座標軸,讓第一根軸捕捉最多變異,第二根軸捕捉在第一根軸之外最多的變異,依此類推。

所以,PCA 的核心不是「刪欄位」,而是「換座標系,再視需要丟掉後面的座標」。理解這一點,後面的特徵值、奇異值、 explained variance 和 n_components 才不會變成 API 背誦題。

先看一個兩維例子

假設每一筆資料有兩個特徵,資料點大致沿著右上方的斜線排列。原本的 feature_1 和 feature_2 都有意義,但它們不是資料最自然的描述方式:兩個欄位其實一起增減,可能反映同一種統計變化,但 PCA 本身不證明共同的潛在因素或成因存在。

PCA 會把座標軸旋轉成:

  • PC1(第一主成分):沿著點雲最長的方向,保留最多變異。
  • PC2(第二主成分):垂直於 PC1,負責描述點雲厚度,以及沒有被 PC1 解釋的變化。

如果點雲非常扁,PC1 可能已經保留 96% 的變異。把每個點投影到 PC1 上,就可以用一個數字近似原本的兩個數字。注意這不是把 feature_2 刪掉;PC1 通常同時混合了兩個原始特徵。

圖上的「主成分」也不是一條穿過資料的迴歸線。普通最小平方法(Ordinary Least Squares, OLS)迴歸線會利用目標變數 (y),最小化指定 (y) 方向上的殘差;PCA 沒有 (y),第一主軸則最小化資料點到軸的正交平方距離,只看 (X) 的幾何分布。

PCA 的數學問題:找一個最有變異的方向

令資料矩陣為 (X \in \mathbb{R}^{n \times d}),每一列是一筆樣本,每一欄是一個特徵。第一步是對每一欄減去訓練資料的平均值:

Xc=X−1μTX_c = X - \mathbf{1}\mu^T

這一步叫 centering(中心化)。若不中心化,第一主成分可能會被資料相對於原點的 second moment 主導,而不是描述資料繞著平均值如何散開。

接著,找一個長度為 1 的方向向量 (w),讓資料投影到這個方向後的變異最大:

max⁡∥w∥2=1Var⁡(Xcw)\max_{\|w\|_2=1} \operatorname{Var}(X_c w)

對中心化資料,樣本共變異矩陣為:

S=1n−1XcTXcS = \frac{1}{n-1}X_c^T X_c

因此:

Var⁡(Xcw)=wTSw\operatorname{Var}(X_cw) = w^T S w

這是一個帶有單位長度限制的 Rayleigh quotient。用拉格朗日乘數法:

L(w,λ)=wTSw−λ(wTw−1)\mathcal{L}(w,\lambda)=w^TSw-\lambda(w^Tw-1)

對 (w) 微分並令其為零:

Sw=λwSw=\lambda w

這就是特徵值問題。第一主成分方向是最大特徵值對應的特徵向量;第二主成分是下一個特徵向量,而且因為對稱矩陣的特徵向量可以選成正交,新的座標軸彼此垂直。每個主成分的變異量,就是對應的特徵值。

這也解釋了名字:PCA 不是先假設某個「真實因素」存在,而是從資料的共變異結構中找出 principal axes。Karl Pearson 在 1901 年把這個問題描述成尋找最貼近一組空間點的線與平面;Harold Hotelling 在 1933 年將它系統化為統計變數的 principal components。Pearson, 1901;Hotelling, 1933

同一件事的另一種看法:最小重建誤差

「最大化投影後的變異」是最常見的直覺,但 PCA 還有一個等價的說法:找一個 (k) 維線性子空間,使資料投影回原空間時的平方誤差最小。

保留前 (k) 個主成分組成矩陣 (W_k=[w_1,\ldots,w_k]),樣本 (x) 的低維表示是:

z=(x−μ)TWkz=(x-\mu)^TW_k

用這個表示重建:

x^=μ+zWkT\hat{x}=\mu+zW_k^T

PCA 選的 (W_k),會讓所有樣本的總重建誤差

∑i∥xi−x^i∥22\sum_i\|x_i-\hat{x}_i\|_2^2

在所有 (k) 維線性子空間中最小。對中心化資料而言,這是線性子空間;映回原始座標後,則是通過平均值 (\mu) 的 (k) 維 affine 子空間。這個觀點很適合理解壓縮和去噪:丟掉後面的方向,就是把相對小的變化視為細節或噪聲;但 PCA 不會自動分辨 signal 與 noise,「相對小」也不等於「對任務不重要」。

為什麼實作通常用 SVD,而不是直接算特徵值

理論上可以先算 (S=X_c^TX_c/(n-1)),再對 (S) 做特徵值分解。實務上更常直接對中心化資料做奇異值分解(Singular Value Decomposition, SVD):

Xc=UΣVTX_c=U\Sigma V^T

其中 (V) 的每一欄(等價於 (V^T) 的每一列)給出一個主成分方向,奇異值的平方和共變異矩陣的特徵值只差一個 (n-1) 的縮放:

λj=σj2n−1\lambda_j=\frac{\sigma_j^2}{n-1}

NumPy 的 linalg.svd 文件也明確說明,(V^H) 的列是 (A^HA) 的特徵向量,而對應特徵值是奇異值平方。NumPy linalg.svd

直接對 (X_c) 做 SVD 有兩個好處。第一,避免先形成可能很大的 (d\times d) 共變異矩陣;第二,數值線性代數套件對 SVD 有成熟、穩定的實作。scikit-learn 的 PCA 文件也把 PCA 描述為先中心化,再透過 SVD 取得 components;它預設不會替各欄位做 scaling。scikit-learn PCA guide

一個可以手算對照的數值例子

下面使用十筆二維資料,兩個欄位故意設計成高度相關。這段程式刻意用 eigh 直接分解共變異矩陣,因為它最貼近上一節的推導;實務上若要直接分解中心化後的資料矩陣,通常使用 SVD:

import numpy as np

X = np.array([
    [2.5, 2.4], [0.5, 0.7], [2.2, 2.9], [1.9, 2.2],
    [3.1, 3.0], [2.3, 2.7], [2.0, 1.6], [1.0, 1.1],
    [1.5, 1.6], [1.1, 0.9],
])

X_centered = X - X.mean(axis=0)
cov = np.cov(X, rowvar=False)
eigenvalues, eigenvectors = np.linalg.eigh(cov)
order = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[order]
components = eigenvectors[:, order]

scores = X_centered @ components[:, :1]
reconstructed = scores @ components[:, :1].T + X.mean(axis=0)
explained_ratio = eigenvalues / eigenvalues.sum()

print(eigenvalues)
print(components[:, 0])
print(explained_ratio)

這組資料的平均值是 ([1.81, 1.91]),共變異矩陣約為:

S=[0.61660.61540.61540.7166]S=\begin{bmatrix} 0.6166 & 0.6154\\ 0.6154 & 0.7166 \end{bmatrix}

兩個特徵值約為 (1.2840) 和 (0.0491),所以 PC1 的 explained variance ratio 約為 96.32%。第一個方向可以寫成約 (0.678,x_1+0.735,x_2);符號整體反過來也完全等價,因為一條軸朝右上或朝左下描述的是同一條軸。

這裡有一個常見誤會:PC1 的係數 (0.678) 和 (0.735) 不是預測係數、feature importance、causal effect,也不能單獨用來判斷哪些原始特徵造成結果。它們是 unit-norm principal direction 的係數;本文暫稱為 component weights,部分文獻也稱為 loadings。若採「原始變數與 component score 的相關」那種 loading 定義,還需要乘上 (\sqrt{\lambda_j})。這些係數只表示新座標如何由中心化後的原始欄位組成。

explained variance:保留幾個主成分?

第 (j) 個主成分的解釋變異比例是:

EVR⁡j=λj∑ℓ=1dλℓ\operatorname{EVR}_j=\frac{\lambda_j}{\sum_{\ell=1}^{d}\lambda_\ell}

前 (k) 個主成分的累積比例是:

CumulativeEVR⁡(k)=∑j=1kλj∑ℓ=1dλℓ\operatorname{CumulativeEVR}(k)=\frac{\sum_{j=1}^{k}\lambda_j}{\sum_{\ell=1}^{d}\lambda_\ell}

常見的做法是畫 scree plot,找「肘部」;或設定一個像 0.90、0.95 的累積比例。這些方法是描述性準則,不是自然法則。若你的目標是預測,最終應在交叉驗證中比較不同 (k) 對下游任務的表現;若 (k) 是超參數,也應把 PCA 放在 GridSearchCV 的 Pipeline 內。95% 的變異不保證保留 95% 的預測能力。

原因很簡單:PCA 是無監督方法。它只知道 (X) 哪裡變化最大,不知道 (y) 藏在哪裡。對某個預測問題而言,變異很小的方向可能剛好攜帶關鍵訊號。scikit-learn 也特別提醒,PCA 的降維是 unsupervised,可能丟掉對 target 重要、但自身變異較小的特徵。scikit-learn cross-decomposition guide

中心化和標準化:PCA 最容易被忽略的決策

scikit-learn 的 PCA 會中心化,但不會自動把每個特徵縮放到單位變異。這個預設很合理,因為「以原始量綱計算變異」本身就是一個選擇;它不一定適合你的資料。

例如一個欄位是年收入(元),另一個是年齡(歲),收入的數值尺度可能讓它主導共變異矩陣。若你認為兩個欄位應該在相對尺度上公平競爭,通常先做 z-score 標準化:

xij′=xij−μjσjx'_{ij}=\frac{x_{ij}-\mu_j}{\sigma_j}

在一般 z-score、相同缺失值處理與標準差定義下,這等價於對相關矩陣而非原始共變異矩陣做 PCA。兩種結果都可能正確,關鍵是你要能說清楚:你要保留的是原始物理尺度下的變化,還是各特徵相對於自身波動的變化?標準化不是客觀上更公平,而是選擇讓各欄位按自身標準差衡量。

在預測、泛化評估或 production pipeline 中,標準化的平均值和標準差只能用訓練集估計。若先把 train 和 test 合在一起再 fit_transform,測試集的分布就滲進了模型;PCA 本身也一樣,components 必須只在訓練資料上 fit。純 EDA 沒有 holdout 評估時,則應把這個界線說清楚。

from sklearn.decomposition import PCA
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression

model = make_pipeline(
    StandardScaler(),
    PCA(n_components=0.95),
    LogisticRegression(max_iter=1000),
)

model.fit(X_train, y_train)
pred = model.predict(X_test)

把 preprocessing、PCA 和模型放進 Pipeline,讓每個 cross-validation fold 都只用該 fold 的訓練部分估計參數。這不是寫法偏好,而是避免資料洩漏的基本邊界。

whitening 是什麼?為什麼不能順手打開?

白化(whitening)會把投影後的每個主成分再除以其標準差,使各 component 具有單位變異。以 SVD 寫,概念上就是把 (U\Sigma) 的各軸尺度消掉;scikit-learn 實作上會依樣本數與奇異值縮放 components,使輸出使用 population variance 定義時為 unit variance。scikit-learn PCA API

白化有時對假設各方向尺度相近的下游模型有幫助,例如 RBF kernel 的 SVM 或 K-Means;但它也會丟掉「哪個方向原本變異比較大」這個資訊。換句話說,普通 PCA 是旋轉加截斷;whitening 是旋轉、截斷,再重新調整每根軸的尺。

除非下游模型確實需要近似等方差的輸入,否則不要把 whitening 當作 PCA 的標準收尾。先用驗證集確認它真的改善了任務。

PCA 不是 feature selection,也不是 factor analysis

這三個概念常被混在一起,但回答的問題不同:

  • Feature selection 從原始欄位中挑子集,保留欄位名稱與原始語意;PCA 產生的是原始欄位的線性組合。
  • PCA 是幾何與重建導向的無監督線性降維,要求 components 彼此正交。
  • Factor analysis 是機率模型,對觀測資料背後的潛在因素和噪聲做建模;它不要求因素 loading 像 PCA components 一樣必然正交。

scikit-learn 將 probabilistic PCA 寫成等向性的噪聲協方差 (\Psi=\sigma^2I) 的 latent-variable model,而一般 Factor Analysis 使用對角但不必等值的 (\Psi)。這兩者可能得到相似的圖,但統計假設不同。scikit-learn decomposition guide

什麼時候 PCA 很適合?

PCA 常見的合理用途包括:

  • 把高維連續特徵壓到 2D 或 3D,先檢查資料的粗略結構。
  • 在保留主要線性變化的前提下壓縮特徵,降低下游模型的計算量。
  • 對高度相關的測量值建立較低維的表示。
  • 以低秩重建做簡單的去噪或壓縮。
  • 對影像或其他寬矩陣做近似分解;若只需要少量最大的奇異向量,可考慮 randomized SVD。scikit-learn 說明,這在只保留少數 components 的寬資料上能避免計算完整分解。scikit-learn randomized PCA

如果資料量大到無法一次放進記憶體,可以用 Incremental PCA 分批更新;它透過 partial_fit 逐批處理資料,記憶體需求主要由 batch size、feature 數與 component 數共同決定。結果通常接近 batch PCA,但會受 batch size、批次順序與數值誤差影響,不保證和一次性完整 SVD 完全相同。scikit-learn IncrementalPCA

什麼時候不要直接用 PCA?

PCA 的限制不是附註,而是它的定義帶來的結果:

  1. 線性限制:若資料結構落在彎曲的 manifold 上,線性子空間可能需要很多 components 才能描述。Kernel PCA 能透過 kernel 做非線性降維,但 inverse transform 通常是近似的,且 kernel matrix 可能帶來 (O(n^2)) 的記憶體成本。scikit-learn Kernel PCA
  2. 對離群值敏感:共變異矩陣使用平方偏差,少數極端點可能旋轉主軸。可先檢查離群值,或考慮 robust covariance、robust scaling 等方法。
  3. 不保證可解釋:dense loading 常同時混合很多原始欄位;如果你需要「每個 component 只由少數欄位組成」,Sparse PCA 是另一類以重建誤差加上 (L_1) sparsity penalty 求解的問題。它的 components 不必彼此正交,因此普通 PCA 的特徵值與 explained variance ratio 不能直接照搬。scikit-learn Sparse PCA
  4. 不是時間序列模型:PCA 可以壓縮一個時間窗的特徵,但不會自動理解順序、延遲或因果關係。
  5. 不是預測目標最佳化器:它找的是 (X) 的大變異方向,不是讓 loss 對 (y) 最小的方向。

還有一個看似奇怪、但很重要的細節:主成分方向的符號沒有意義。若某次執行得到 (w),另一次得到 (-w),投影座標也只會整體變號,幾何子空間完全相同。不要把「loading 是正是負」單獨當作可重現的語意結論;要看相對關係,而且要固定資料、前處理與 solver。

把 PCA 記成一個可操作的流程

實務上可以用下面的順序思考:

  1. 先定義每一列是什麼、每一欄的量綱是什麼,以及離群值是否合理。
  2. 決定用 covariance PCA 還是 standardize 後的 PCA;不要讓套件預設替你做決策。
  3. 只在 training data 上 fit preprocessing 和 PCA。
  4. 用 scree plot、累積 explained variance,以及下游 cross-validation 一起選 (k)。
  5. 讀 component loading 時,把它當作座標組合,不要誤稱成因果效果或模型 feature importance。
  6. 若資料非線性、樣本太大、需要稀疏解釋或對離群值敏感,改看 Kernel PCA、Incremental PCA、Sparse PCA 或 robust 方法。

最後把 PCA 濃縮成一句話:它在所有 (k) 維的表示中,選擇能以最小平方重建誤差保留最多資料變異的那一組正交座標;映回原始空間時,它是通過平均值的 affine 子空間。它很擅長整理資料的幾何結構,卻不會替你回答「什麼對結果最重要」。那個問題,仍然要交給明確的目標、驗證設計與領域判斷。

References