numpy實現鳶尾花數據集PCA降維
PCA降維過程
在前面的一篇博客中我已經從數學角度解釋了PCA降維的原理,我們從中也可以得到PCA降維的過程
1)將原始數據做轉置運算,每一行代表一個維度
2)每一行(代表一個屬性字段)進行零均值化,即減去這一行的均值
3)得到原始數據的協方差矩陣
4)求出協方差矩陣的特徵值及對應的特徵向量的單位向量
5)將特徵向量按對應特徵值大小從上到下按行排列成矩陣,取前k行組成矩陣P
6)用上面得到矩陣P和標準化後數據相乘,即可得到降維到k維後的數據
數據集
數據集我們使用sklearn庫中的iris數據集,數據集中的每個樣本有4個特徵參數
原始數據格式:
5.1, 3.5, 1.4, 0.2
4.9, 3.0, 1.4, 0.2
4.7, 3.2, 1.3, 0.2
4.6, 3.1, 1.5, 0.2
5.0, 3.6, 1.4, 0.2
5.4, 3.9, 1.7, 0.4
4.6, 3.4, 1.4, 0.3
5.0, 3.4, 1.5, 0.2
4.4, 2.9, 1.4, 0.2
4.9, 3.1, 1.5, 0.1
numpy實現PCA降維
加載數據集
數據我們使用sklearn庫中的iris中的數據集,所以需要導入sklearn庫
data = datasets.load_iris()["data"]
print(data)
我們看一下加載進來的數據
[[ 5.1 3.5 1.4 0.2]
[ 4.9 3. 1.4 0.2]
[ 4.7 3.2 1.3 0.2]
[ 4.6 3.1 1.5 0.2]
[ 5. 3.6 1.4 0.2]
[ 5.4 3.9 1.7 0.4]
...
[ 6.7 3.3 5.7 2.5]
[ 6.7 3. 5.2 2.3]
[ 6.3 2.5 5. 1.9]
[ 6.5 3. 5.2 2. ]
[ 6.2 3.4 5.4 2.3]
[ 5.9 3. 5.1 1.8]]
數據標準化
原始數據每一列是同一個維度特徵,在標準化時候我們需要的也是對維度進行數據標準化處理,所以需要按列取數據,另外因爲後面我們需要計算協方差,所以對數據進行標準化方式是去均值
#axis = 0,按列取值求均值
mean_vector=np.mean(data,axis=0)
print("均值向量爲:%s\n標準化數據:%s"% (mean_vector,data - mean_vector))
標準化後的數據爲:
均值向量爲:[ 5.84333333 3.054 3.75866667 1.19866667]
標準化數據:
[[ -7.43333333e-01 4.46000000e-01 -2.35866667e+00 -9.98666667e-01]
[ -9.43333333e-01 -5.40000000e-02 -2.35866667e+00 -9.98666667e-01]
[ -1.14333333e+00 1.46000000e-01 -2.45866667e+00 -9.98666667e-01]
[ -1.24333333e+00 4.60000000e-02 -2.25866667e+00 -9.98666667e-01]
[ -8.43333333e-01 5.46000000e-01 -2.35866667e+00 -9.98666667e-01]
......
[ 4.56666667e-01 -5.54000000e-01 1.24133333e+00 7.01333333e-01]
[ 6.56666667e-01 -5.40000000e-02 1.44133333e+00 8.01333333e-01]
[ 3.56666667e-01 3.46000000e-01 1.64133333e+00 1.10133333e+00]
[ 5.66666667e-02 -5.40000000e-02 1.34133333e+00 6.01333333e-01]]
協方差矩陣
協方差計算的是不同維度之間的協方差,不是不同樣本的,首先我們要確定我們數據維度是按行還是列,我們的數據是一列是一個維度,並且要對標準化後的數據進行求協方差,這裏我們使用numpy中的 cov() 函數
# rowvar=0表示數據的每一列代表一個維度
cov_mat =np.cov(standData,rowvar=0)
print("協方差矩陣:\n%s"%cov_mat)
原始數據中一共四個維度,所以我們得到的協方差矩陣是一個4*4的矩陣
協方差矩陣:
[[ 0.68569351 -0.03926846 1.27368233 0.5169038 ]
[-0.03926846 0.18800403 -0.32171275 -0.11798121]
[ 1.27368233 -0.32171275 3.11317942 1.29638747]
[ 0.5169038 -0.11798121 1.29638747 0.58241432]]
協方差矩陣的特徵值和對應的特徵向量的計算
在另一篇博客中也說了協方差矩陣是實對稱方陣,所以是一定可以做矩陣分解的
在numpy的庫函數中,linalg.eig()可以用來計算計算特徵值和對應的特徵向量,輸入參數是一個方陣,得到兩個值:w,v
w:特徵值。每個特徵值根據它的多重性重複。這個數組將是複雜類型,除非虛數部分爲0。當傳進的參數a是實數時,得到的特徵值是實數
v:特徵向量。每個特徵值對應的特徵向量,相同特徵值可能對應的特徵向量不同
fvalue,fvector = np.linalg.eig(covMat)
print("特徵值爲:%s\n特徵向量爲:\n%s" %(fvalue,fvector))
可以得到四個特徵值,和四個特徵向量的單位向量
特徵值爲:[ 4.22484077 0.24224357 0.07852391 0.02368303]
特徵向量爲:
[[ 0.36158968 -0.65653988 -0.58099728 0.31725455]
[-0.08226889 -0.72971237 0.59641809 -0.32409435]
[ 0.85657211 0.1757674 0.07252408 -0.47971899]
[ 0.35884393 0.07470647 0.54906091 0.75112056]]
向量矩陣
前面我們已經得到協方差矩陣的特徵值和特徵向量,接下來需要按照特徵值的大小
#argsort函數返回的是數組值排序的索引值
#將特徵值按從大到小順序排列的索引值
fvaluesort=np.argsort(-fvalue)
print(fvaluesort)
#特徵值[ 4.22484077 0.24224357 0.07852391 0.02368303]
#output :[0 1 2 3]
argsort函數返回的是排序的索引值,默認是從小到大
eg:
x = np.array([3, 1, 2])
np.argsort(x)
out: array([1, 2, 0])
其他用法可以參考np.argsort的用法
我們假設現在要將數據降到二維,那麼就取最大的兩個特徵值對應的特徵向量
#先獲取特徵值較大的兩個索引以便於下一步得到對應的特徵向量
fValueTopN = fValueSort[:2 ]
#得到較大的特徵值對應的特徵向量
newdata = fVector[:,fValueTopN]
print(newdata)
#output:
[[ 0.36158968 -0.65653988]
[-0.08226889 -0.72971237]
[ 0.85657211 0.1757674 ]
[ 0.35884393 0.07470647]]
我們和上面得到的特徵向量和特徵值做個比較,確定拿到的是較大的兩個特徵值對應的特徵向量
降維後數據
拿上面得到的前2個較大特徵值對應的特徵向量和標準化後數據做內積運算,便可得到降維後的數據
newdata = np.dot(data,vectorMatrix)
print(newdata)
#output
降維後的數據:
[[-2.68412563 -0.31939725]
[-2.71414169 0.17700123]
[-2.88899057 0.14494943]
[-2.74534286 0.31829898]
[-2.72871654 -0.32675451]
.......
[ 1.52716661 0.37531698]
[ 1.76434572 -0.07885885]
[ 1.90094161 -0.11662796]
[ 1.39018886 0.28266094]]
至此,我們的降維過程已經完成,我們再試着恢復數據,看看和原始數據有沒有很大的差別
print("最終重構結果爲:\n{}".format(np.mat(newdata) * fvectormat.T + mean_vector))
#output
復原的數據:
[[5.08303897 3.51741393 1.40321372 0.21353169]
[4.7462619 3.15749994 1.46356177 0.24024592]
[4.70411871 3.1956816 1.30821697 0.17518015]
[4.6422117 3.05696697 1.46132981 0.23973218]
[5.07175511 3.52655486 1.36373845 0.19699991]
[5.50581049 3.79140823 1.67552816 0.32616959]
......
[6.95201347 3.04358556 5.90548444 2.09665999]
[6.91756285 3.07544671 5.77722508 2.04293748]
[6.66904015 3.02994114 5.39094874 1.88173174]
[6.14880195 2.65421139 5.13134845 1.77482994]
[6.53272206 2.96578609 5.25579114 1.825527 ]
[6.60688475 2.98181821 5.3662607 1.87161698]
[6.16013695 2.73344296 4.99793961 1.71875852]]
原始數據;
[[ 5.1 3.5 1.4 0.2]
[ 4.9 3. 1.4 0.2]
[ 4.7 3.2 1.3 0.2]
[ 4.6 3.1 1.5 0.2]
[ 5. 3.6 1.4 0.2]
[ 5.4 3.9 1.7 0.4]
...
[ 6.7 3.3 5.7 2.5]
[ 6.7 3. 5.2 2.3]
[ 6.3 2.5 5. 1.9]
[ 6.5 3. 5.2 2. ]
[ 6.2 3.4 5.4 2.3]
[ 5.9 3. 5.1 1.8]]
和原始數據做個比較就會發現和原始數據差別很小,因爲做降維相對來說會損失信息量,所以很難做到完整還原原始數據
完整代碼
from sklearn import datasets
import numpy as np
class PCAtest():
def __init__(self,k):
#降到k維
self.k = k
# 加載鳶尾花數據集中的特徵作爲PCA的原始數據集
def loadIris(self):
data = datasets.load_iris()["data"]
return data
#數據標準化(去均值)
def stand_data(self,data):
#axis = 0,按列取值求均值
mean_vector = np.mean(data,axis=0)
return mean_vector,data -mean_vector
# 計算協方差矩陣
def getCovMat(self,standData):
# rowvar=0表示數據的每一列代表一個維度
return np.cov(standData,rowvar=0)
# 計算協方差矩陣的特徵值和特徵向量
def getFValueAndFVector(self,covMat):
fValue,fVector = np.linalg.eig(covMat)
return fValue,fVector
# 得到特徵向量矩陣
def getVectorMatrix(self,fValue,fVector):
#從大到小排序,並返回排序後的原索引值
fValueSort = np.argsort(-fValue)
#print(fValueSort)
fValueTopN = fValueSort[:self.k]
#print(fValueTopN)
return fVector[:,fValueTopN]
# 得到降維後的數據
def getResult(self,data,vectorMat):
return np.dot(data,vectorMat)
if __name__=="__main__":
pca = PCAtest(2)
data = pca.loadIris()
print("原始數據:\n%s"%data)
(mean_vector ,standdata)= pca.stand_data(data)
print("均值向量爲:%s \n標準化數據:\n%s" % (mean_vector, standdata))
cov_mat = pca.getCovMat(standdata)
print("協方差矩陣:\n%s"%cov_mat)
fvalue,fvector = pca.getFValueAndFVector(cov_mat)
print("特徵值爲:%s\n特徵向量爲:\n%s" %(fvalue,fvector))
fvectormat = pca.getVectorMatrix(fvalue,fvector)
print("最終需要的特徵向量:\n%s"%fvector)
newdata = pca.getResult(standdata,fvectormat)
print("降維後的數據:\n%s"%newdata)
print("最終重構結果爲:\n{}".format(np.mat(newdata) * fvectormat .T + mean_vector))