這兩天學習了吳恩達老師機器學習中的主成分分析法(Principal Component Analysis, PCA),PCA是一種經常使用的降維方法。這裏對PCA算法作一個小筆記,並利用python完成對應的練習(ps:最近公式有點多,開始沒找到怎麼敲公式,前面幾篇都是截的圖^_^,後面問了度娘,原來是支持latex的)。代碼和數據見githubhtml
將數據從原來的座標系轉換到新的座標系,新座標系的選擇由數據自己決定。第一個新座標軸選擇的是原始數據中方差最大的方向,第二個新座標軸選擇的是和第一個座標軸正交且具備最大方差的方向,依次類推,直到找出\(k\)個新座標軸。也就是將原始數據投影到一個低維的座標系中。python
以最小化投影偏差爲目標函數,這裏注意與線性迴歸的區別,線性迴歸是最小化垂直距離。下圖左邊圖中藍線是線性迴歸的目標,右圖中藍線是PCA的目標。
git
對 $ X= x^{(1)}, x^{(2)}, ... , x^{(m)} $ 計算平均值和方差,\[ u_j = \frac{1}{m}\sum_{i=1}^{m}x_j^{(i)} \] \[ s_j = \frac{1}{m}\sum_{i=1}^{m}(x_j^{(i)}-u_j)^2 \]
對原始數據進行歸一化處理獲得 \[ x_j = \frac{x_j-u_j}{s_j} \]
其中 $ u_j $ 表示均值, $ s_j $ 表示方差。github
\[ \Sigma = \frac{1}{m} \sum_{i=1}^{m}(x^{(i)}.(x^{(i)})^T) \]
其中 $ x^{(i)} $ 是 $ n \times 1 $ 維的,則其轉秩是$ 1 \times n $ 維的,因此 $ \Sigma $ 是 $ n \times n $ 維的。算法
\[ U,S,V = svd(\Sigma) \]
奇異值分解能夠參考博客,博主講的比較清楚。上式中
\[ U = [u^{(1)}, u^{(2)},...,u^{(n)}]\in R^{n*n} \]
從中選取 $ k $ 個主要成分
\[ U_k = [u^{(1)}, u^{(2)},...,u^{(k)}] \]
則 $ x^{(i)} $ 在新座標上的投影能夠表示爲
\[ z^{(i)} = [u^{(1)}, u^{(2)},...,u^{(k)}]^T\cdot x^{(i)}= \begin{bmatrix} u^{(1)}\\ u^{(2)}\\ \vdots \\ u^{(k)}\\ \end{bmatrix}\cdot x^{(i)} \]
其中 $ U_k $ 是 $ k \times n $ 維的,其轉秩爲 $ n \times k $ 維的,$ x^{(i)} $ 是 $ n \times 1 $ 維,因此 $ z^{(i)} $ 是 $ k \times 1 $ 維的。投影 $ Z $ 能夠表示爲
\[ Z = U_k^T \cdot X \]app
\[ x^{(i)}_{approx} = U_k \cdot z^{(i)} \]
其中 $ U_k $ 是 $ n \times k $ 維,$ z^{(i)} $ 是 $ k \times 1 $ 維,則 $ x_{approx}^{(i)} $ 是 $ n \times 1 $ 維。機器學習
\[ \frac{\frac{1}{m}\sum_{i=1}^{m}||x^{(i)}-x^{(i)}_{approx}||^2}{\frac{1}{m}\sum_{i=1}^{m}||x^{(i)}||^2} \leq 0.01 \]
若是不知足上式,則從步驟3中從新選擇 $ k $ 個主成分,繼續第4和第5步,直到知足要求爲止。小於等於0.01代表保留了原始數據99%信息,這裏能夠根據需求進行更改。
這裏若是不用奇異值分解後的 $ U $ 矩陣,也能夠根據奇異值矩陣 $ S $ 計算 ,$ S $ 是主對角線上爲從大到小排列的奇異值,其餘元素全爲0的對角矩陣。
\[ 1-\frac{\sum_{i=1}^{k}S_{ii}}{\sum_{i=1}^{n}S_{ii}}\leq 0.01 \]函數
from scipy.io import loadmat import numpy as np import pandas as pd import matplotlib.pyplot as plt
data1 = loadmat('./ex7/ex7data1.mat') data1
X = data1['X'] X.shape
原始數據展現學習
plt.scatter(X[:,0], X[:,1]) plt.show()
# 定義歸一化函數featureNormalize def featureNormalizse(x): mean = x.mean(axis=0) std = x.std(axis=0) return (x-mean)/std, mean, std
# test x_norm, means, stds = featureNormalizse(X) x_norm[:5]
print(x_norm.shape) sigma = (x_norm.T.dot(x_norm))/x_norm.shape[0] sigma
U,S,V = np.linalg.svd(sigma) U,S,V
# 整合sigma和svd def pca(x): sigma = (x.T.dot(x))/x.shape[0] U,S,V = np.linalg.svd(sigma) return U,S,V
可視化主成分spa
x_norm, means, stds = featureNormalizse(X) U, S, V = pca(x_norm) plt.figure(figsize=(8, 6)) plt.scatter(X[:,0], X[:,1], label='sample data') # 樣本數據點 plt.plot([means[0], means[0] + 1.5*S[0]*U[0,0]], [means[1], means[1] + 1.5*S[0]*U[0,1]], c='r', linewidth=3, label='First Principal Component') # 第一個成分 plt.plot([means[0], means[0] + 1.5*S[1]*U[1,0]], [means[1], means[1] + 1.5*S[1]*U[1,1]], c='g', linewidth=3, label='Second Principal Component') # 第二個成分 plt.grid() plt.axis("equal") plt.legend() plt.show()
# 根據U_reduce計算x_norm的投影Z def compute_z(X, U, k): Z = X.dot(U[:,:k]) return Z # test Z = compute_z(x_norm, U, 1) Z[:5]
# 計算x_approx def compute_x_approx(U, k, Z): x_approx = Z.dot(U[:,:k].T) # 50*1 * 1*2 return x_approx # test x_approx = compute_x_approx(U, 1, Z) x_approx[:5]
plt.figure(figsize=(8,6)) plt.axis("equal") plot = plt.scatter(x_norm[:,0], x_norm[:,1], s=30, facecolors='none', edgecolors='b',label='Original Data Points') # 畫出歸一化後原始樣本點 plot = plt.scatter(x_approx[:,0], x_approx[:,1], s=30, facecolors='none', edgecolors='r',label='PCA Reduced Data Points') # 畫出通過PCA後構造的估計值 plt.title("Example Dataset: Reduced Dimension Points Shown",fontsize=14) plt.xlabel('x1 [Feature Normalized]',fontsize=14) plt.ylabel('x2 [Feature Normalized]',fontsize=14) plt.grid(True) for x in range(x_norm.shape[0]): # 畫出變換先後的連線 plt.plot([x_norm[x,0],x_approx[x,0]],[x_norm[x,1],x_approx[x,1]],'k--') # 輸入第一項全是X座標,第二項都是Y座標 plt.legend() plt.show()
# 導入數據 face_data = loadmat('./ex7/ex7faces.mat') face = face_data['X'] face.shape
def showFace(X, row, col): fig, axs = plt.subplots(row, col, figsize=(8,8)) for r in range(row): for c in range(col): axs[r][c].imshow(X[r*col + c].reshape(32,32).T, cmap = 'Greys_r') axs[r][c].set_xticks([]) axs[r][c].set_yticks([]) showFace(face, 10, 10) plt.show()
face_norm, means, stds = featureNormalizse(face) # 歸一化 U, S, V = pca(face_norm) # 奇異值分解 Z = compute_z(face_norm, U, 16) # 計算Z face_approx = compute_x_approx(U, 16, Z) # 計算降維重構後的數據 face_approx.shape
showFace(face, 10, 10) showFace(face_approx, 10, 10) plt.show()