1. 程式人生 > >主成分分析 | Principal Components Analysis | PCA

主成分分析 | Principal Components Analysis | PCA

理論

僅僅使用基本的線性代數知識,就可以推匯出一種簡單的機器學習演算法,主成分分析(Principal Components Analysis, PCA)。

假設有 $m$ 個點的集合:$\left\{\boldsymbol{x}^{(1)}, \ldots, \boldsymbol{x}^{(m)}\right\}$ in $\mathbb{R}^{n}$,我們希望對這些點進行有失真壓縮(lossy compression)。有失真壓縮是指,失去一些精度作為代價,用更少的儲存空間來儲存這些點。我們當然希望損失的精度儘可能少。

一種方法是,我們對這些點進行編碼:以低維的方式,來表示這些點。

那就是說,對於任意點:$\boldsymbol{x}^{(i)} \in \mathbb{R}^{n}$,我們會計算相應的碼向量(code vector):$\boldsymbol{c}^{(i)} \in \mathbb{R}^{l}$。

如果 $l$ 小於 $m$,那麼我們就是用一個低維向量來表示一個高維向量。需要的儲存空間變小,當然同時可能會有精度損失。

我們希望知道某個編碼函式(encoding function),根據輸入,計算編碼(code),即滿足:

\begin{equation}
f(\boldsymbol{x})=\boldsymbol{c}
\end{equation}

並且還希望知道某個解碼函式(decoding function),根據編碼,計算重構的輸入(reconstructed input),即滿足:

\begin{equation}
\boldsymbol{x} \approx g(f(\boldsymbol{x}))
\end{equation}

PCA 就是選擇特定的解碼函式。具體說來,我們選擇矩陣乘法,作為從編碼空間到 $\mathbb{R}^{n}$ 的對映。令 $g(\boldsymbol{c})=\boldsymbol{D} \boldsymbol{c}$,其中 $\boldsymbol{D} \in \mathbb{R}^{n \times l}$ 就是解碼函式的矩陣定義。

PCA 還限制了 $\boldsymbol{D}$ 的每一列互為正交(注意:除非 $l=n$,否則  $\boldsymbol{D}$ 不是嚴格意義上的正交矩陣)。

此外,為了不受尺度的影響,我們還限定了 $\boldsymbol{D}$ 的每一列都是單位向量。

為了把基本思想轉換為可實現的演算法,首先我們需要知道:如何生成每個輸入點 $\boldsymbol{x}$ 的最優編碼點 $C^{*}$。其中一個方法就是,最小化輸入點 $\boldsymbol{x}$  和重構點 $g\left(\boldsymbol{c}^{*}\right)$ 的距離。在主成分演算法中,我們使用 $L^{2}$ 範數:

\begin{equation}
\boldsymbol{c}^{*}=\underset{\boldsymbol{c}}{\arg \min }\|\boldsymbol{x}-g(\boldsymbol{c})\|_{2}
\end{equation}

我們可以切換為計算平方的 $L^{2}$ 範數,它們優化的 $\boldsymbol{c}$ 值相同:

\begin{equation}
\boldsymbol{c}^{*}=\underset{\boldsymbol{c}}{\arg \min }\|\boldsymbol{x}-g(\boldsymbol{c})\|_{2}^{2}
\end{equation}

該函式可以化簡為:

\begin{equation}
\begin{array}{l}
(\boldsymbol{x}-g(\boldsymbol{c}))^{\top}(\boldsymbol{x}-g(\boldsymbol{c})) \\
=\boldsymbol{x}^{\top} \boldsymbol{x}-\boldsymbol{x}^{\top} g(\boldsymbol{c})-g(\boldsymbol{c})^{\top} \boldsymbol{x}+g(\boldsymbol{c})^{\top} g(\boldsymbol{c}) \\
=\boldsymbol{x}^{\top} \boldsymbol{x}-2 \boldsymbol{x}^{\top} g(\boldsymbol{c})+g(\boldsymbol{c})^{\top} g(\boldsymbol{c})
\end{array}
\end{equation}

我們可以刪除第一項,因為它和  $\boldsymbol{x}$ 無關,於是方程最終可以化簡為:

\begin{equation}
\boldsymbol{c}^{*}=\underset{\boldsymbol{c}}{\arg \min }-2 \boldsymbol{x}^{\top} g(\boldsymbol{c})+g(\boldsymbol{c})^{\top} g(\boldsymbol{c})
\end{equation}

根據 $g(\boldsymbol{c})$ 的定義,代入可得:

\begin{equation}
\begin{aligned}
\boldsymbol{c}^{*} &=\underset{\boldsymbol{c}}{\arg \min }-2 \boldsymbol{x}^{\top} \boldsymbol{D} \boldsymbol{c}+\boldsymbol{c}^{\top} \boldsymbol{D}^{\top} \boldsymbol{D} \boldsymbol{c} \\
&=\arg \min -2 \boldsymbol{x}^{\top} \boldsymbol{D} \boldsymbol{c}+\boldsymbol{c}^{\top} \boldsymbol{I}_{l} \boldsymbol{c} \\
&=\arg \min -2 \boldsymbol{x}^{\top} \boldsymbol{D} \boldsymbol{c}+\boldsymbol{c}^{\top} \boldsymbol{c}
\end{aligned}
\end{equation}

使用向量積分,我們可以求解這個優化問題:

\begin{equation}
\begin{array}{c}
\nabla_{\boldsymbol{c}}\left(-2 \boldsymbol{x}^{\top} \boldsymbol{D} \boldsymbol{c}+\boldsymbol{c}^{\top} \boldsymbol{c}\right)=\mathbf{0} \\
-2 \boldsymbol{D}^{\top} \boldsymbol{x}+2 \boldsymbol{c}=\mathbf{0} \\
\boldsymbol{c}=\boldsymbol{D}^{\top} \boldsymbol{x}
\end{array}
\end{equation}

這使得該演算法很高效,我們可以用矩陣-向量運算,來最優地對 $\boldsymbol{x}$ 進行編碼。要對一個向量進行編碼,我們使用編碼函式:

\begin{equation}
f(\boldsymbol{x})=\boldsymbol{D}^{\top} \boldsymbol{x}
\end{equation}

我們同樣可以定義 PCA 的重構運算:

\begin{equation}
r(\boldsymbol{x})=g(f(\boldsymbol{x}))=\boldsymbol{D} \boldsymbol{D}^{\top} \boldsymbol{x}
\end{equation}

下一步,我們需要選擇解碼矩陣 $\boldsymbol{D}$,與前面最小化一個點上的重構誤差不同,由於我們需要對所有點都使用相同的矩陣 $\boldsymbol{D}$,因此我們必須針對所有點,最小化誤差矩陣的 Frobenius 範數(又稱 F-範數):

\begin{equation}
\boldsymbol{D}^{*}=\underset{\boldsymbol{D}}{\arg \min } \sqrt{\sum_{i, j}\left(x_{j}^{(i)}-r\left(\boldsymbol{x}^{(i)}\right)_{j}\right)^{2}} \text { subject to } \boldsymbol{D}^{\top} \boldsymbol{D}=\boldsymbol{I}_{l}
\end{equation}

為了推導計算 $\boldsymbol{D}^{*}$ 的演算法,我們首先考慮 $l=1$ 的情況。此時,$\boldsymbol{D}^{*}$ 就是一個向量:$d$。將式(10)代入到式(11),可得:

\begin{equation}
\boldsymbol{d}^{*}=\underset{\boldsymbol{d}}{\arg \min } \sum_{i}\left\|\boldsymbol{x}^{(i)}-\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{x}^{(i)}\right\|_{2}^{2} \text { subject to }\|\boldsymbol{d}\|_{2}=1
\end{equation}

$\boldsymbol{d}^{\top} \boldsymbol{x}^{(i)}$ 是一個標量,移動到式子左邊,可得:

\begin{equation}
\boldsymbol{d}^{*}=\underset{\boldsymbol{d}}{\arg \min } \sum_{i}\left\|\boldsymbol{x}^{(i)}-\boldsymbol{d}^{\top} \boldsymbol{x}^{(i)} \boldsymbol{d}\right\|_{2}^{2} \text { subject to }\|\boldsymbol{d}\|_{2}=1
\end{equation}

標量的轉置等於標量本身,可得:

\begin{equation}
\boldsymbol{d}^{*}=\underset{\boldsymbol{d}}{\arg \min } \sum_{i}\left\|\boldsymbol{x}^{(i)}-\boldsymbol{x}^{(i) \top} \boldsymbol{d} \boldsymbol{d}\right\|_{2}^{2} \text { subject to }\|\boldsymbol{d}\|_{2}=1
\end{equation}

令 $\boldsymbol{X} \in \mathbb{R}^{m \times n}$,其中 $\boldsymbol{X}_{i,:}=\boldsymbol{x}^{(i)^{\top}}$,方程整理為:

\begin{equation}
\boldsymbol{d}^{*}=\underset{\boldsymbol{d}}{\arg \min }\left\|\boldsymbol{X}-\boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right\|_{F}^{2} \text { subject to } \boldsymbol{d}^{\top} \boldsymbol{d}=1
\end{equation}

先不考慮約束條件,我們可以簡化 Frobenius 範數:

\begin{equation}
\begin{array}{l}
\underset{\boldsymbol{d}}{\arg \min }\left\|\boldsymbol{X}-\boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right\|_{F}^{2} \\
=\underset{\boldsymbol{d}}{\arg \min } \operatorname{Tr}\left(\left(\boldsymbol{X}-\boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)^{\top}\left(\boldsymbol{X}-\boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)\right) \\
=\underset{\boldsymbol{d}}{\arg \min } \operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X}-\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}-\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X}+\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \\
=\underset{\boldsymbol{d}}{\arg \min } \operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X}\right)-\operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)-\operatorname{Tr}\left(\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X}\right)+\operatorname{Tr}\left(\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \\
=\underset{\boldsymbol{d}}{\arg \min }-\operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)-\operatorname{Tr}\left(\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X}\right)+\operatorname{Tr}\left(\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \\
=\underset{\boldsymbol{d}}{\arg \min }-2 \operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)+\operatorname{Tr}\left(\boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \\
=\underset{\boldsymbol{d}}{\arg \min }-2 \operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)+\operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{d} \boldsymbol{d}^{\top}\right)
\end{array}
\end{equation}

現在,我們重新引入約束:

\begin{equation}
\begin{array}{l}
\underset{\boldsymbol{d}}{\arg \min }-2 \operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)+\operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \text { subject to } \boldsymbol{d}^{\top} \boldsymbol{d}=1 \\
=\underset{\boldsymbol{d}}{\arg \min }-2 \operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right)+\operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \text { subject to } \boldsymbol{d}^{\top} \boldsymbol{d}=1 \\
=\underset{\boldsymbol{d}}{\arg \min }-\operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \text { subject to } \boldsymbol{d}^{\top} \boldsymbol{d}=1 \\
=\underset{\boldsymbol{d}}{\arg \max } \operatorname{Tr}\left(\boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d} \boldsymbol{d}^{\top}\right) \text { subject to } \boldsymbol{d}^{\top} \boldsymbol{d}=1 \\
=\underset{\boldsymbol{d}}{\arg \max } \operatorname{Tr}\left(\boldsymbol{d}^{\top} \boldsymbol{X}^{\top} \boldsymbol{X} \boldsymbol{d}\right) \text { subject to } \boldsymbol{d}^{\top} \boldsymbol{d}=1
\end{array}
\end{equation}

我們可以使用特徵值分解,來求解這個優化問題。具體地說,通過計算 $\boldsymbol{X}^{\top} \boldsymbol{X}$ 最大特徵值的特徵向量,可以求出最優值 $d$ 。

這個推導式針對 $l=1$ 的情況而言的,它只能夠求出第一個主成分。在更一般的情況,如果我們想要求出主成分的基,矩陣 $\boldsymbol{D}$ 由最大特徵值的 $l$ 個特徵向量確定。這個可以通過歸納法證明。

實踐

PCA 通過資料降維,常見的應用有加速機器學習演算法和資料視覺化。

資料視覺化

載入 Iris 資料集

1 import pandas as pd
2 
3 url = "https://archive.ics.uci.edu/ml/machine-learning-databases/iris/iris.data"
4 # load dataset into Pandas DataFrame
5 df = pd.read_csv(url, names=['sepal length', 'sepal width', 'petal length', 'petal width', 'target'])
6 print(df.head())

輸出:

   sepal length  sepal width  petal length  petal width       target
0           5.1          3.5           1.4          0.2  Iris-setosa
1           4.9          3.0           1.4          0.2  Iris-setosa
2           4.7          3.2           1.3          0.2  Iris-setosa
3           4.6          3.1           1.5          0.2  Iris-setosa
4           5.0          3.6           1.4          0.2  Iris-setosa

資料標準化

PCA 會受到資料尺度的影響,因此在使用 PCA 之前最好進行標準化。使用 StandardScaler 可以標準化資料集特徵到一個單位尺度(均值為 0,方差為 1):這是很多機器學習演算法效能優化的條件。

1 from sklearn.preprocessing import StandardScaler
2 
3 features = ['sepal length', 'sepal width', 'petal length', 'petal width']
4 # Separating out the features
5 x = df.loc[:, features].values
6 # Separating out the target
7 y = df.loc[:, ['target']].values
8 # Standardizing the features
9 x = StandardScaler().fit_transform(x)

使用 PCA 投影到 2D

下面程式碼將原始資料從 4 維投影到 2 維。降維後的每個主成分通常不會有特定的語義。

1 from sklearn.decomposition import PCA
2 
3 pca = PCA(n_components=2)
4 principalComponents = pca.fit_transform(x)
5 principalDf = pd.DataFrame(data=principalComponents,
6                            columns=['principal component 1', 'principal component 2'])

 

最後把特徵和標籤放在一起:

finalDf = pd.concat([principalDf, df[['target']]], axis=1)

 

2D 投影的視覺化

下面程式碼繪製二維資料:

 1 import matplotlib.pyplot as plt
 2 
 3 fig = plt.figure(figsize=(8, 8))
 4 ax = fig.add_subplot(1, 1, 1)
 5 ax.set_xlabel('Principal Component 1', fontsize=15)
 6 ax.set_ylabel('Principal Component 2', fontsize=15)
 7 ax.set_title('2 component PCA', fontsize=20)
 8 targets = ['Iris-setosa', 'Iris-versicolor', 'Iris-virginica']
 9 colors = ['r', 'g', 'b']
10 for target, color in zip(targets, colors):
11     indicesToKeep = finalDf['target'] == target
12     ax.scatter(finalDf.loc[indicesToKeep, 'principal component 1']
13                , finalDf.loc[indicesToKeep, 'principal component 2']
14                , c=color
15                , s=50)
16 ax.legend(targets)
17 ax.grid()
18 plt.show()

輸出:

可解釋方差

可解釋方差(explained variance)可以體現出:有多少資訊(方差)來自每個主成分。當你把 4 維降為 2 維時,你損失了一些資訊。通過 explained_variance_ratio_,你可以看到主成分包含了多少資訊。

print(pca.explained_variance_ratio_)

輸出:

[0.72770452 0.23030523]

加速機器學習演算法

如果你的學習演算法,由於輸入維度太高的原因,變得很慢,那麼使用 PCA 來加速可能是一個不錯的選擇。

這裡我們使用 MNIST 資料集。

下載並載入資料

1 from sklearn.datasets import fetch_openml
2 
3 mnist = fetch_openml('mnist_784')

這裡有 70000 張影象,每張影象維度是 784。

將資料劃分為訓練集和測試集

1 from sklearn.model_selection import train_test_split
2 
3 # test_size: what proportion of original data is used for test set
4 train_img, test_img, train_lbl, test_lbl = train_test_split(mnist.data, mnist.target, test_size=1 / 7.0, random_state=0)

資料標準化

1 from sklearn.preprocessing import StandardScaler
2 
3 scaler = StandardScaler()
4 # Fit on training set only.
5 scaler.fit(train_img)
6 # Apply transform to both the training set and the test set.
7 train_img = scaler.transform(train_img)
8 test_img = scaler.transform(test_img)

匯入並使用 PCA

與硬編碼選擇多少主成分不同,下面程式碼選擇剛好可以保留 95% 以上資訊的主成分個數,因此主成分個數是可變的。

1 from sklearn.decomposition import PCA
2 
3 # Make an instance of the Model
4 pca = PCA(0.95)

再訓練集進行 PCA 擬合:

pca.fit(train_img)

檢視有多少個主成分:

print(pca.n_components_)

輸出:

327

對訓練集和測試集使用 transform

train_img = pca.transform(train_img)
test_img = pca.transform(test_img)

對變換後的資料使用邏輯迴歸

 1 import time
 2 from sklearn.linear_model import LogisticRegression
 3 
 4 # lbfgs 很快但是可能警報不能收斂
 5 logisticRegr = LogisticRegression(solver='lbfgs')
 6 t0 = time.time()
 7 logisticRegr.fit(train_img, train_lbl)
 8 print(f'time elapsed: {time.time() - t0}')
 9 
10 print(logisticRegr.score(test_img, test_lbl))

注:如果使用 lbfgs 報錯:

AttributeError: 'str' object has no attribute 'decode'

scikit-learn 需要升級到 0.24 以上。

使用 PCA 後的擬合時間

以下是某一組測試效能圖表,可以作為參考:

壓縮表徵後的影象重構

PCA 當然也可以對影象進行壓縮:

 

如果對這段程式碼感興趣,可以參考:github。

 

 

參考

  • Goodfellow I, Bengio Y, Courville A, et al. Deep learning[M]. Cambridge: MIT press, 2016.
  • PCA using Python (scikit-learn)