1. 程式人生 > >特徵臉是怎麼提取的之主成分分析法PCA

特徵臉是怎麼提取的之主成分分析法PCA

機器學習筆記 多項式迴歸這一篇中,我們講到了如何構造新的特徵,相當於對樣本資料進行升維.

那麼相應的,我們肯定有資料的降維.那麼現在思考兩個問題

  • 為什麼需要降維
  • 為什麼可以降維

第一個問題很好理解,假設我們用KNN訓練一些樣本資料,相比於有1W個特徵的樣本,肯定是訓練有1K個特徵的樣本速度更快,因為計算量更小嘛.

第二個問題,為什麼可以降維.一個樣本原先有1W個特徵,現在減少到1K個,不管如何變換,資料包含的資訊肯定是減少了,這是毫無疑問的.但是資訊的減少是否意味著我們對於樣本的認知能力的下降?這個則未必.

以minist資料集為例,每一個數字圖片都由N個畫素構成,即樣本有N個特徵,但是我們不需要精確地知道每一個畫素的值,依然能夠分辨出這個數字是幾,你可以通俗地理解為,比如你把每張圖的邊邊角角都減掉,並不影響你識別這是幾。  由此,我們知道,樣本特徵的重要性並不是一致的,有的重要,也即所謂的主成分.

現在問題則轉換為:我們怎麼樣尋找到一種方法,使得降維後"重要的特徵"儘可能地保留,也即降維後資訊的丟失最少?

依然從一個極端的例子入手,假設我們有M個樣本,每個樣本2個特徵,特徵1的波動幅度很大,特徵2的波動幅度很小.

此時,如果要把這M個樣本降成1維,採用最簡單粗暴的方式,丟棄一個特徵,是丟棄feature1呢還是feature2呢?

如果,丟棄feature1的話,我們的樣本就只剩下了feature2,而feature2的值在3附近微小的波動,幾乎沒有變化.   沒有變化,機器學習演算法就沒法從中學習到足夠的資訊.比如M個樣本對應著M個label,這M個label各不相同.來了個新樣本,特徵值還是3,這時候我們是沒法推算出新樣本的label的.

對機器學習演算法而言,我們希望在合理的範圍內,資料的分佈越分散越好,即資料的方差越大越好,這樣才能學習到足夠多的資訊.

而這也正是PCA降維的理論基礎:我們希望找到一個新的N維座標系,使得資料在這個新的N維座標系的前K個座標軸上分佈,方差最大.這樣的N維座標系中的K個座標軸就是我們所要尋找的K個主成分.

比如對這個圖中的藍色的點而言,我們希望找到一個向量w(圖中的藍色線),使得各點在w上的投影點,方差最大.樣本點$X^{(i)}$投影到單位向量w後得到$X_p^{(i)}$,這些新的樣本點的方差為$\frac{\sum_{i=1}^m (X_p^{(i)} - \overline X_p)^2)} m$,為了方便計算,在向w投影之前,我們對樣本X做demean去均值處理,即每個特徵值減去該維度特徵的均值,這個操作並不會影響資料的分佈.demean後的樣本點投影到w後得到的新樣本點的方差變為$\frac{\sum_{i=1}^m (X_p^{(i)})^2)} m$.注意這裡$X_p^{(i)}$是一個向量.所以我們的方差可以轉變為$$Var(X_p) = \frac{\sum_{i=1}^m || X_p^{(i)} ||^2)} m$$

 

注意,樣本點$X^{(i)}$投影到單位向量w後得到$X_p^{(i)}$,由向量點乘的定義我們能夠得到$X^{(i)} \cdot w = || X^{(i)}|| \cdot || w|| \cdot \cos \theta =  || X^{(i)}|| \cdot \cos \theta = || X_p^{(i)} ||$.由此我們的目標轉變為使得$$Var(X_p) = \frac{\sum_{i=1}^m  X^{(i)} \cdot w^2)} m$$最大化.

接下來我們用梯度上升法求出這個w。現在先不用管梯度上升法是怎麼回事,本篇重點在於理解PCA。梯度下降法梯度上升法以後會再寫博文解釋.這個w也就是我們的第一主成分.即樣本投影到這個方向後,方差最大,損失資訊最少.那麼現在我們如何求出第二主成分呢?

求出這個w之後,注意w是一個N維向量. 我們就可以得到X在w之外剩餘的分量為$X^{’(i)}=X^{(i)}-X_p^{(i)}=X^{(i)}-X^{(i) \cdot w}$,對$X^{’(i)}$,我們再次求出一個w2,使得其在w2上的投影的方差最大,求解方法同上.

我們不斷迴圈這個過程,就可以求解出k個w,記做

$$W_k=\begin{bmatrix} W_1^{(1)}& W_2^{(1)}& … &W_n^{(1)} \\ W_1^{(2)}& W_2^{(2)}& … &W_n^{(2)} \\ … \\ W_1^{(k)}& W_2^{(k)}& … &W_n^{(k)} \\ \end{bmatrix}$$.即$$W_k=\begin{bmatrix} w_1\\ w_2 \\ … \\w_k\\ \end{bmatrix}$$

這個$W_k$即我們求出的新的N維座標系的前K個座標軸.

原始資料點在新的N維座標系的第k個座標軸的投影距離為$X^{(i)} \cdot w_k$,則原始樣本在新的N維座標系的前K個座標軸上可以表示為$$X^{‘}=XW_k^T$$。這樣我們就把矩陣M*N的矩陣X轉換成了M*K的矩陣$X^{'}$,從而達到了降維的目的.


 

原理部分說完了,我們來看一下sklearn中的具體api使用.

class sklearn.decomposition.PCA(n_components=Nonecopy=Truewhiten=Falsesvd_solver=’auto’tol=0.0iterated_power=’auto’random_state=None)[source]

常用引數有n_components,代表降維後需要保留的維度數.如果0<n_components<1的話,表示降維後需要保留的方差比例.(你可以通俗地理解為保留的資訊佔原始資訊的百分比)

常用屬性:

explained_variance_ array, shape (n_components,)  解釋了新的座標系中的k個座標軸的每一個軸所解釋的方差.

explained_variance_ratio_ array, shape (n_components,) 解釋了新的座標系中的k個座標軸的每一個軸所解釋的方差所佔百分比.

n_components_:主成分個數.即降到的維度數.

components_ array, shape (n_components, n_features) 主成分. 

這樣說顯得有點抽象,看個例子你就全明白.

from sklearn.decomposition import PCA

pca = PCA(n_components=2)
pca.fit(X_train)
X_train_reduction = pca.transform(X_train)
X_test_reduction = pca.transform(X_test)

knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train_reduction, y_train)

我們把64維的X_train降成二維的X_train_reduction.

pca.explained_variance_

array([ 175.77007821,  165.73864334])

pca.explained_variance_ratio_

array([ 0.14566817,  0.13735469])

什麼意思呢?降維後的X_train_reduction在第一個維度上(第一主成分)的方差為175.77,佔比為14.56%.

 

來看下當我們將n_components傳參為原始特徵數量時

from sklearn.decomposition import PCA

pca = PCA(n_components=X_train.shape[1])
pca.fit(X_train)
pca.explained_variance_ratio_

plt.plot([i for i in range(X_train.shape[1])], 
         [np.sum(pca.explained_variance_ratio_[:i+1]) for i in range(X_train.shape[1])])
plt.show()

可以看到前40個維度,(注意不是X_train的前40個維度的特徵,是降維後,新的座標系的前40個座標軸,即第一主成分到第四十主成分),已經可以表達接近100%的原始資訊了.

PCA降維後,我們的資料不像原始資料那樣具有可解釋性了. 這是PCA的一個缺點. 降維後,在數學的角度上我們可以通過$X=X^{(')}W_k$還原回去得到一個M*N的矩陣,但是這個X與原始的X已經不一樣了,因為降維後一定是損失了資訊的,所以說只是數學角度上可以還原回去.

PCA降維後,有些時候不但不會降低模型的訓練效果,反而提升了訓練效果,因為我們損失了一些資訊,但是這些不重要的資訊可能恰好是影響模型訓練的噪音.


 

PCA的一個典型應用,人臉識別中提取特徵臉.

from __future__ import print_function

from time import time import logging import matplotlib.pyplot as plt from sklearn.model_selection import train_test_split from sklearn.model_selection import GridSearchCV from sklearn.datasets import fetch_lfw_people from sklearn.metrics import classification_report from sklearn.metrics import confusion_matrix from sklearn.decomposition import PCA from sklearn.svm import SVC print(__doc__) # Display progress logs on stdout logging.basicConfig(level=logging.INFO, format='%(asctime)s %(message)s') # ############################################################################# # Download the data, if not already on disk and load it as numpy arrays lfw_people = fetch_lfw_people(min_faces_per_person=70, resize=0.4) # introspect the images arrays to find the shapes (for plotting) n_samples, h, w = lfw_people.images.shape # for machine learning we use the 2 data directly (as relative pixel # positions info is ignored by this model) X = lfw_people.data n_features = X.shape[1] # the label to predict is the id of the person y = lfw_people.target target_names = lfw_people.target_names n_classes = target_names.shape[0] print("Total dataset size:") print("n_samples: %d" % n_samples) print("n_features: %d" % n_features) print("n_classes: %d" % n_classes) # ############################################################################# # Split into a training set and a test set using a stratified k fold # split into a training and testing set X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.25, random_state=42) # ############################################################################# # Compute a PCA (eigenfaces) on the face dataset (treated as unlabeled # dataset): unsupervised feature extraction / dimensionality reduction n_components = 150 print("Extracting the top %d eigenfaces from %d faces" % (n_components, X_train.shape[0])) t0 = time() pca = PCA(n_components=n_components, svd_solver='randomized', whiten=True).fit(X_train) print("done in %0.3fs" % (time() - t0)) eigenfaces = pca.components_.reshape((n_components, h, w)) print("Projecting the input data on the eigenfaces orthonormal basis") t0 = time() X_train_pca = pca.transform(X_train) X_test_pca = pca.transform(X_test) print("done in %0.3fs" % (time() - t0)) # ############################################################################# # Train a SVM classification model print("Fitting the classifier to the training set") t0 = time() param_grid = {'C': [1e3, 5e3, 1e4, 5e4, 1e5], 'gamma': [0.0001, 0.0005, 0.001, 0.005, 0.01, 0.1], } clf = GridSearchCV(SVC(kernel='rbf', class_weight='balanced'), param_grid, cv=5) clf = clf.fit(X_train_pca, y_train) print("done in %0.3fs" % (time() - t0)) print("Best estimator found by grid search:") print(clf.best_estimator_) # ############################################################################# # Quantitative evaluation of the model quality on the test set print("Predicting people's names on the test set") t0 = time() y_pred = clf.predict(X_test_pca) print("done in %0.3fs" % (time() - t0)) print(classification_report(y_test, y_pred, target_names=target_names)) print(confusion_matrix(y_test, y_pred, labels=range(n_classes))) # ############################################################################# # Qualitative evaluation of the predictions using matplotlib def plot_gallery(images, titles, h, w, n_row=3, n_col=4): """Helper function to plot a gallery of portraits""" plt.figure(figsize=(1.8 * n_col, 2.4 * n_row)) plt.subplots_adjust(bottom=0, left=.01, right=.99, top=.90, hspace=.35) for i in range(n_row * n_col): plt.subplot(n_row, n_col, i + 1) plt.imshow(images[i].reshape((h, w)), cmap=plt.cm.gray) plt.title(titles[i], size=12) plt.xticks(()) plt.yticks(()) # plot the result of the prediction on a portion of the test set def title(y_pred, y_test, target_names, i): pred_name = target_names[y_pred[i]].rsplit(' ', 1)[-1] true_name = target_names[y_test[i]].rsplit(' ', 1)[-1] return 'predicted: %s\ntrue: %s' % (pred_name, true_name) prediction_titles = [title(y_pred, y_test, target_names, i) for i in range(y_pred.shape[0])] plot_gallery(X_test, prediction_titles, h, w) # plot the gallery of the most significative eigenfaces eigenface_titles = ["eigenface %d" % i for i in range(eigenfaces.shape[0])] plot_gallery(eigenfaces, eigenface_titles, h, w) plt.show()

具體詳情請參考sklearn官方文件.