PCA詳解-並用scikit-learn實現PCA壓縮紅酒數據集

引言

在這篇文章中,我會介紹一些PCA背後的數學概念,然後我們用Wine數據集作爲實例,一步一步地實現PCA。最後,我們用更加強大的scikit-learn方便快速地實現PCA,並用邏輯迴歸來擬合用PCA轉換後的數據集。爲了讓大家更好地理解PCA,整篇文章都貫穿着實例,現在,讓我們享受這篇文章吧。

標準差(Standard Deviation)

在引入標準差之前,我先介紹一下平均值,假設我們有個樣本集X,其中的樣本爲X=[1,2,3,4,5,6] ,求平均值的公式如下:

X¯=ni=1Xin
  • X¯ :平均值
  • n :樣本的個數
  • Xi :第i 個樣本

X的平均值爲:

X¯=6i=1Xi6=1+2+3+4+5+66=3.5

求平均值的python代碼如下:

import numpy as np
X=np.array([1,2,3,4,5,6])
np.mean(X)

不幸的是平均值並沒有告訴我們關於樣本集的很多信息。比如[0,8,12,20]和[8,9,11,12]的平均值都是10,但是它們的數據分散程度有着明顯的不同。因此,我們並不滿足於僅僅求出一個小小的平均值,它只是一個我們到達偉大目標的一個墊腳石。下面讓我們引入標準差,它度量着數據的分散程度。它的公式如下:

s=ni=1(XiX¯)2n1

上面的公式測量着樣本到樣本均值的平均距離。你可能會想,分母爲什麼不是n 而是n1 ?

比如說:你想調查整個人類的生活水平,但是你的能力和精力都是有限的,你不可能調查到世界上的每一個人,因此,你只能找出一部分人來評估整個人類的生活水平。也就是說,你的樣本集是現實世界的子集。在這樣的情況下,你應該用n1 ,它被證明是更加接近標準差的。然而,如果你的樣本集是調查了全部的人口,那麼你應該用n 而不是n1 。這個解釋給你一種直觀的感覺,如果你想要看更多的細節,請參考http://mathcentral.uregina.ca/RR/database/RR.09.95/weston2.html

求標準差的python代碼如下:

import numpy as np
X=np.array([1,2,3,4,5,6])
np.std(X)

方差(Variance)

方差和標準差一樣,都是度量着數據的分散程度。它的公式就是標準差的平方,如下:

s2=ni=1(XiX¯)2n1

求方差的python代碼如下:

import numpy as np
X=np.array([1,2,3,4,5,6])
np.var(X)

協方差(Covariance)

方差和標準差只能操作一維的數據集,但是在現實生活中,樣本可能有很多的特徵,也就是說你的數據集有可能是很多維的。但是,測量樣本特徵之間的關係很有必要,比如說:我們的樣本有2個特徵,一個是你的學習時間,一個是你的成績,那麼正常情況下,一定是你的學習時間越長,你的成績越好。但是,方差和標準差不能度量這種特徵之間的關係,而協方差可以。

假設我們有兩個特徵X,Y。計算它們之間的協方差公式爲:

cov(X,Y)=ni=1(XiX¯)(YiY¯)n1

從上面的公式我們能看出cov(X,Y)cov(Y,X)

協方差具體的大小並不重要,但是它的正負是重要的。如果它的值是正的,這表明兩個特徵之間同時增加或減小;如果它的值是負的,這表明兩個特徵之間有一個增加而另一個減小。如果它的值爲0,則表明兩個特徵之間是獨立的。

在實際應用中,我們樣本的特徵不可能只有2個,而是有更多的特徵。在這種情況下我們用協方差矩陣表示。假設我們的樣本有n個特徵,這裏我用1,2,3,4,,n 來表示每個特徵。協方差矩陣如下所示:

C=cov(1,1)cov(2,1)cov(3,1)cov(n,1)cov(1,2)cov(2,2)cov(3,2)cov(n,2)cov(1,3)cov(2,3)cov(3,3)cov(n,3)cov(1,n)cov(2,n)cov(3,n)cov(n,n)

上面C 的維數是nn ,我們可以看到對角線上紅色的那個是特徵與自己的協方差,因此它是方差。我們看到對角線兩側的協方差是對稱的,因此協方差矩陣是一個關於對角線對稱的方陣。

下面,我用上面那個學習時間(Time)和分數(Score)的例子來演示一下協方差,假設我們收集的數據如下:

時間(T) 分數(S)
9 39
15 56
25 93
14 61
10 50
18 75
0 32
16 85
5 42
19 70
16 66
20 80

求協方差的python代碼如下:

import numpy as np
T=np.array([9,15,25,14,10,18,0,16,5,19,16,20])
S=np.array([39,56,93,61,50,75,32,85,42,70,66,80])
T=T[:,np.newaxis]
S=S[:,np.newaxis]
X=np.hstack((T,S))
np.cov(X.T)

# 協方差輸出如下
array([[  47.71969697,  122.9469697 ],
       [ 122.9469697 ,  370.08333333]])

從上面的輸出我們看到,cov(T,S)=cov(S,T)=122.9469697 ,這是正數,因此說明你的學習時間越長,你的分數越高。

特徵向量(Eigenvectors)與特徵值(Eigenvalues)

特徵向量是非零向量,當線性變換應用到特徵向量時並不會改變它的方向。換句話說:如果v 是一個非零向量,要想v 是線性變換T 的特徵向量,則T(v) 是一個標量值乖上v ,它可以被寫成如下形式:

T(v)=λvλv

如果線性變換T 被表示成作爲一個方陣A 的變換,那麼上面的等式可以寫成如下形式:

Av=λvv

舉個例子:假設上面的方陣A(2231) ,那麼,我可以寫出如下等式:

(2231)(32)=4(32)

上面的(32) 爲特徵向量,4爲特徵值。

特徵向量有下面幾個屬性:

1、只有方陣才能求出特徵向量,但是,並不是每個方陣都有特徵向量。假設我有一個n * n的矩陣,那麼就有n個特徵向量。

2、如果我在乖上特徵向量之前縮放它,我依然會得到同一個特徵值。比如:

2(32)=(64)(2231)(64)=4(64)

3、一個矩陣中的所有特徵向量都是相互垂直的,無論你的數據集中有多少特徵。

4、向量的大小不能影響它是不是一個特徵向量,但是,它的方向可以。因此,在我們找到特徵向量以後要把它們單位化,使所有的特徵向量都有相同的長度。

你可能會想,我應該怎麼找到這些神祕的特徵向量呢?不幸的是,我們只能找到相對較小的方陣的特徵向量,如果對於大的方陣,我們只能用複雜的迭代方法來求出特徵向量。我建議你去Google一些優秀的線性代數庫來做這個事情。

主成分分析(Principal Components Analysis)

PCA是一種無監督線性轉換技術,它主要應用在降維(dimensionality reduction)上。PCA基於數據特徵之間的相關性幫我們找到數據中的模式。簡而言之,PCA的目的是在高維數據中找到最大方差的方向,接着映射它到比最初維數小或相等的新的子空間。

PCA在高維數據中找到最大方差方向

下面我介紹關於主成分分析技術的實現過程。並用葡萄酒(Wine)數據集實例,它的數據集描述可以在下面的鏈接中發現。

https://archive.ics.uci.edu/ml/datasets/Wine

數據標準化

PCA對數據縮放是相當敏感的,因此,在我們實現PCA的時候,如果數據特徵在不同的範圍上,我們首先要對數據集標準化。現在,我要用scikit-learn對數據進行標準化,代碼如下:

import pandas as pd

df_wine = pd.read_csv('http://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data', header=None) # 加載葡萄酒數據集

from sklearn.cross_validation import train_test_split
from sklearn.preprocessing import StandardScaler

X, y = df_wine.iloc[:, 1:].values, df_wine.iloc[:, 0].values # 把數據與標籤拆分開來
X_train, X_test, y_train, y_test = train_test_split(X, y,test_size=0.3, random_state=0) # 把整個數據集的70%分爲訓練集,30%爲測試集

# 下面3行代碼把數據集標準化爲單位方差和0均值
sc = StandardScaler()
X_train_std = sc.fit_transform(X_train)
X_test_std = sc.fit_transform(X_test)

計算協方差矩陣

如果你看了上面數據集的描述,每個樣本有13個屬性(特徵),因此協方差矩陣的維數應該是13 * 13

import numpy as np
cov_mat = np.cov(X_train_std.T)
cov_mat.shape # 輸出爲(13, 13)

計算協方差矩陣的特徵向量和特徵值

因爲協方差矩陣是方陣,所以我們可以計算它的特徵向量和特徵值。

import numpy as np
eigen_vals, eigen_vecs = np.linalg.eig(cov_mat)

eigen_vecs.shape # 輸出爲(13, 13)

# 下面的輸出爲eigen_vals
array([ 4.8923083 ,  2.46635032,  1.42809973,  1.01233462,  0.84906459,
        0.60181514,  0.52251546,  0.08414846,  0.33051429,  0.29595018,
        0.16831254,  0.21432212,  0.2399553 ])

我們上面求得的特徵向量都是單位特徵向量,它們的長度爲1,這對PCA很重要,幸運的是,一些流行的數學包都會給你這樣的單位特徵向量。下面這幅圖的特徵是2維的,因此它只有2個特徵向量,但是我們的數據集的特徵是13維的,我很難可視化出來。但是,你可以把我們求出的13維特徵向量想像成彼此正交的座標軸,每個方向都指向方差最大的方向。

PCA在高維數據中找到最大方差方向

由於我們的特徵向量是單位向量,我們只知道它指向的方向,但是,我想知道哪個方向上保留的方差最多,這其實並不難,我們求出的特徵值的意義就是特徵向量的大小,因此我們只要找出最大特徵值所對應的特徵向量就可以知道哪個方向保留的方差最多。

下面,我要畫出方差解釋率(variance explained ratios),也就是每個特徵值佔所有特徵值和的比例。

tot = sum(eigen_vals) # 求出特徵值的和
var_exp = [(i / tot) for i in sorted(eigen_vals, reverse=True)] # 求出每個特徵值佔的比例(降序)
cum_var_exp = np.cumsum(var_exp) # 返回var_exp的累積和

import matplotlib.pyplot as plt

# 下面的代碼都是繪圖的,涉及的參數建議去查看官方文檔
plt.bar(range(len(eigen_vals)), var_exp, width=1.0, bottom=0.0, alpha=0.5, label='individual explained variance')
plt.step(range(len(eigen_vals)), cum_var_exp, where='post', label='cumulative explained variance')
plt.ylabel('Explained variance ratio')
plt.xlabel('Principal components')
plt.legend(loc='best')
plt.show()

PCA保留方差

從上圖你也看到了,其實2個特徵就已經保留了數據集的大部分信息,可見在實際應用中,大部分的特徵都是冗餘的。注意,有一點你始終要記在心裏,PCA是無監督學習算法,這就意味着它會忽視類標籤信息。

特徵變換(Feature transformation)

我們已經成功地把協方差方陣轉換成了特徵向量和特徵值,現在讓我們把葡萄酒數據集映射到新的主成分座標軸吧。爲了數據的可視化,我只想把我的數據集映射到2個保留最多方差的主成分。因此,我們要從大到小排序特徵值,選出前2個特徵值對應的特徵向量,並用這2個特徵向量構建映射矩陣,用這個映射矩陣把數據集轉換到2維空間。

eigen_pairs =[(np.abs(eigen_vals[i]),eigen_vecs[:,i]) for i in range(len(eigen_vals))] # 把特徵值和對應的特徵向量組成對
eigen_pairs.sort(reverse=True) # 用特徵值排序

下面,我只選出前2對來構建我們的映射矩陣,但是在實際應用中,你應該權衡你的計算效率和分類器之間的性能來選擇恰當的主成分數量。

first = eigen_pairs[0][1]
second = eigen_pairs[1][1]
first = first[:,np.newaxis]
second = second[:,np.newaxis]
W = np.hstack((first,second))

現在,我們已經構建出了13×2維的映射矩陣 W ,我們可以用這個映射矩陣來轉換我們的訓練集X_train_std(124×13維)到只包含2個特徵的子空間,用X_train_pca=X_train_stdW ,現在這個2維的新空間我可以可視化了,代碼如下:

X_train_pca = X_train_std.dot(w) # 轉換訓練集
colors = ['r', 'b', 'g']
markers = ['s', 'x', 'o']
for l, c, m in zip(np.unique(y_train), colors, markers):
    plt.scatter(X_train_pca[y_train==l, 0], X_train_pca[y_train==l, 1], c=c, label=l, marker=m) # 散點圖
plt.xlabel('PC 1')
plt.ylabel('PC 2')
plt.legend(loc='lower left')
plt.show()

PCA可視化

從上圖我們可以看到在PC 1軸上數據是更加分散的,因此它的方差更大。現在,我們也可以看到數據是線性可分的。

scikit-learn實現PCA

爲了更好地理解PCA,我們自己實現了這個算法。但是scikit-learn已經幫我們更好地實現了。代碼如下:

from sklearn.linear_model import LogisticRegression
from sklearn.decomposition import PCA
pca = PCA(n_components=2) # 保留2個主成分
lr = LogisticRegression() # 創建邏輯迴歸對象
X_train_pca = pca.fit_transform(X_train_std) # 把原始訓練集映射到主成分組成的子空間中
X_test_pca = pca.transform(X_test_std) # 把原始測試集映射到主成分組成的子空間中
lr.fit(X_train_pca, y_train) # 用邏輯迴歸擬合數據
plot_decision_regions(X_train_pca, y_train, classifier=lr)
lr.score(X_test_pca, y_test) # 0.98 在測試集上的平均正確率爲0.98
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.legend(loc='lower left')
plt.show()

關於邏輯迴歸的具體細節參考:http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html

PCA 邏輯迴歸

從上圖我們可以看出,用PCA壓縮數據以後,我們依然很好對樣本進行分類。

我們也可以查看PCA的方差解釋率(variance explained ratios),代碼如下:

pca = PCA(n_components=None) # 保留所有的主成分
X_train_pca = pca.fit_transform(X_train_std)
pca.explained_variance_ratio_

# 輸出如下
array([ 0.37329648,  0.18818926,  0.10896791,  0.07724389,  0.06478595,
        0.04592014,  0.03986936,  0.02521914,  0.02258181,  0.01830924,
        0.01635336,  0.01284271,  0.00642076])
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章