機(jī)器學(xué)習(xí)經(jīng)典算法 - PCA

對(duì)于機(jī)器學(xué)習(xí)來說,數(shù)據(jù)的質(zhì)量很大程度上決定了模型的質(zhì)量,因此對(duì)于幾乎所有的情況下都需要對(duì)于數(shù)據(jù)進(jìn)行預(yù)處理。其中較為常用的數(shù)據(jù)處理方式是數(shù)據(jù)的標(biāo)準(zhǔn)化,進(jìn)一步還可以通過主成分分析 Principal components analysis, PCA,隨機(jī)投影 Random Projection 和獨(dú)立成分分析 Independent Conponent Analysis 來對(duì)數(shù)據(jù)進(jìn)行降維 dimensionality reduction。

數(shù)據(jù)的標(biāo)準(zhǔn)化

數(shù)據(jù)標(biāo)準(zhǔn)化最基本目的是對(duì)于將要進(jìn)行運(yùn)算的數(shù)據(jù)的取值進(jìn)行縮放,使得數(shù)據(jù)在取值范圍上處于同一個(gè)數(shù)量級(jí),避免在運(yùn)算過程中取值較小的特征被取值較大的特征“吃掉”。但將不同特征縮放到同一數(shù)量級(jí)的前提是各自特征對(duì)于最終分類和判斷的重要性是等同的,否則就不應(yīng)該做這一處理。

常用的標(biāo)準(zhǔn)化方式:

  • 采用最大值最小值縮放 Min-Max Scaler 的方式:x' = [ x - min(x) ] / [ max(x) - min(x) ]

  • 采用正態(tài)分布的標(biāo)準(zhǔn)值 standard value 的方式:z = (x - x?) / σ

-采用整體縮放的方式:在圖像處理中常常將圖片數(shù)組整體除以 255 以將取值縮放在 [0, 1] 之間

這里 xx', z 代表向量,采用最大值最小縮放標(biāo)準(zhǔn)化后的取值范圍是 [0, 1],而標(biāo)準(zhǔn)值方法標(biāo)準(zhǔn)化的結(jié)果是將原本服從正態(tài)分布的元素進(jìn)一步標(biāo)準(zhǔn)化成服從均值為 0, 方差為 1 的標(biāo)準(zhǔn)正態(tài)分布,且這兩種方法在使用過程中必須要注意 排除異常值 Outlier 的影響。

數(shù)據(jù)的標(biāo)準(zhǔn)化可以通過借助 skitlearn 的預(yù)處理模塊很容易的完成:

In[3]:
import numpy as np
from sklearn.preprocessing import MinMaxScaler

# sklearn prefers your inputs as float
# otherwise will pop out a warning BUT still do the calculation
weights = np.array([[115], [140], [175]]) 
scaler = MinMaxScaler()
rescaled_weight = scaler.fit_transform(weights)
# rescaled_weight = MinMaxScaler().fit_transform(weights)
rescaled_weight

Out[3]:
array([[ 0.        ],
       [ 0.41666667],
       [ 1.        ]])

In [4]:
from sklearn.preprocessing import StandardScaler

weights = np.array([[115], [140], [175]]) 
scaler = StandardScaler()
rescaled_weight = scaler.fit_transform(weights) 
# rescaled_weight = StandardScaler().fit_transform(weights)
rescaled_weight

Out[4]:
array([[-1.15138528],
       [-0.13545709],
       [ 1.28684238]])

矩陣的數(shù)據(jù)處理

在實(shí)際的工作中,尤其是計(jì)算機(jī)視覺方面的應(yīng)用, 更多的數(shù)據(jù)是以矩陣的形式存儲(chǔ)的。對(duì)于一個(gè)形如 [N, D] 的矩陣 X,其中 N 為樣本的數(shù)量,D 為特征的數(shù)量。

特征的去均值化 Mean subtraction

對(duì)于矩陣中的每一列特征都減去相應(yīng)特征的均值,這種處理使得數(shù)據(jù)在各個(gè)特征維度上都更加趨近于中心位置,使得數(shù)據(jù)更加密集。

相應(yīng)的在 Numpy 中的處理方式為:X = X - np.mean(X, axis=0)

而對(duì)于圖像矩陣來說最常做的一個(gè)數(shù)據(jù)處理是在圖像矩陣的各個(gè)通道上減去相應(yīng)通道上全部訓(xùn)練樣本的像素值的均值,例如在 VGG16 中的 Imagenet 數(shù)據(jù)前處理部分就是將輸入圖像的三個(gè) RGB 通道上分別減去 103.939,116.779,123.68,后面這三個(gè)數(shù)是所有 Imagenet 中的圖片在三個(gè)通道上分別計(jì)算得到的均值。

相應(yīng)的在 Numpy 中的處理方式為:X = X - np.mean(X)

在去均值化的基礎(chǔ)上,如果有必要還可以進(jìn)一步除以相應(yīng)特征的標(biāo)準(zhǔn)差使得數(shù)據(jù)進(jìn)一步標(biāo)準(zhǔn)化:X /= np.std(X, axis=0)

Data normalization, from Stanford CS231n notes

在這里需要注意的是,這里的均值和方差都是針對(duì)訓(xùn)練數(shù)據(jù)集而言的,也即應(yīng)該在劃分訓(xùn)練數(shù)據(jù)集、驗(yàn)證數(shù)據(jù)集和測(cè)試數(shù)據(jù)集后在訓(xùn)練數(shù)據(jù)集上進(jìn)行計(jì)算,再用訓(xùn)練數(shù)據(jù)集中的均值和方差來處理驗(yàn)證數(shù)據(jù)集和測(cè)試數(shù)據(jù)集。

主成分分析 Principal Components Analysis

對(duì)于本部分需要的數(shù)學(xué)知識(shí),如 基的變換、本征值分解和奇異值分解,協(xié)方差 請(qǐng)見鏈接中的筆記內(nèi)容,這里直接進(jìn)入主題。

之所以要做主成分分析,是因?yàn)榇蠖鄶?shù)實(shí)踐中的數(shù)據(jù)都是默認(rèn)基于自然基 naive basis 進(jìn)行記錄和表示的,并且這些數(shù)據(jù)當(dāng)中通常有大量的干擾噪聲 noise 和冗余特征 redundancy,而尋找主成分的過程就是希望通過對(duì)于基的線性變換來找到對(duì)于被觀察的數(shù)據(jù)更簡(jiǎn)潔的表示形式,從中提取最為重要的特征,即主成分,而忽略次要特征,這對(duì)簡(jiǎn)化模型復(fù)雜度和提高模型的穩(wěn)健性具有重要的意義。

如果我們通過對(duì)自然基下的特征數(shù)據(jù)做線性變換后發(fā)現(xiàn)其在某一個(gè)方向上變動(dòng)的離散程度很大,也即方差最大,則這個(gè)方向就可以認(rèn)為是特征取值變動(dòng)的方向,通常也就是我們感興趣的方向,或者稱信號(hào)的方向,而與之垂直的方向則可以理解為噪聲的方向。評(píng)估數(shù)據(jù)質(zhì)量的一個(gè)重要指標(biāo)是信噪比 Signal-to-noise-ratio, SNR,其數(shù)學(xué)定義為:

  • SNR = σ2signal / σ2noise ,這個(gè)公式也隱含(一般情況下)高信號(hào)方差對(duì)應(yīng)高信噪比
Signals with variances not coincide with data collection coordinate system / basis

除噪聲外,多個(gè)特征之間很可能存在直接的相關(guān)性,也即我們只需要包含其中的部分特征就可以推導(dǎo)出其他的全部特征,因此可以在數(shù)據(jù)處理的過程中去除冗余特征,借此降低特征矩陣的維數(shù),以減小模型需要處理的數(shù)據(jù)量。

在機(jī)器學(xué)習(xí)中對(duì)于被研究對(duì)象的多個(gè)特征的多次觀測(cè)的結(jié)果通常會(huì)以一個(gè)矩陣的形式表示,稱為特征矩陣,因此為了便于區(qū)分,后續(xù)涉及到利用本征值對(duì)矩陣進(jìn)行的分解我都稱之為本征值分解,而不是國(guó)內(nèi)很多教材上的特征分解。借由統(tǒng)計(jì)相關(guān)知識(shí),兩個(gè)特征的相關(guān)性可以通過協(xié)方差 Covariance 來衡量,對(duì)于多個(gè)特征來說,則可以基于原有的特征矩陣構(gòu)建協(xié)方差矩陣 Covariance matrix。在協(xié)方差矩陣中,對(duì)角線元素為同一個(gè)特征的方差,較大的方差值則意味著其可能是我們需要主要關(guān)注的重要變動(dòng)元素。而非對(duì)角線元素則對(duì)應(yīng)不同的兩個(gè)特征之間的協(xié)方差,較大的協(xié)方差數(shù)值意味著兩個(gè)特征之間具有較大的線性相關(guān)性,也即存在較大可能的冗余。

如果期望可以最大程度的降低冗余,則希望這個(gè)協(xié)方差矩陣可以通過線性變換變成一個(gè)對(duì)角矩陣。從協(xié)方差矩陣的構(gòu)建過程可以看到它是一個(gè)實(shí)對(duì)稱矩陣,而對(duì)于任意實(shí)對(duì)稱矩陣來說都可以進(jìn)行本征值分解,其結(jié)果為 C = QΛQT = QΛQ-1,其中 Q 為本征向量構(gòu)成的正交矩陣 Quadrature matrix,Λ 為本征值構(gòu)成的對(duì)角矩陣。對(duì)于本征向量來說,如果一個(gè)向量是矩陣的本征向量,則其任意非零 k 倍也是本征向量,這也可以理解為在本征向量的方向上可以有最多的信號(hào)聚集,在機(jī)器學(xué)習(xí)和深度學(xué)習(xí)的語境中,協(xié)方差矩陣的本征向量構(gòu)成的正交矩陣就是特征數(shù)據(jù)集的主成分 Principal components。

同時(shí),為突出具有較大方差的特征的重要性,可以將 Λ 對(duì)角線上的元素按照從大到小的順序進(jìn)行布置。對(duì)本征值從大到小的排列后,為了滿足本征分解的運(yùn)算條件,本征值對(duì)應(yīng)的本征向量也要在正交矩陣中保持相同的順序,這也使得我們可以容易的識(shí)別出哪些本征向量的方向最為重要,進(jìn)而舍棄掉不重要的特征實(shí)現(xiàn)維度縮減。

Correlation means redundancy

對(duì)于已有的輸入特征矩陣 X,在通過本征值分解進(jìn)行主元素分析時(shí),需要采用以下幾個(gè)步驟:

  1. 對(duì)特征矩陣的每一列 x 進(jìn)行去均值化得到標(biāo)準(zhǔn)化后的矩陣 XX -= np.mean(X, axis = 0)

  2. 通過計(jì)算每一列特征 x 與其他特征的協(xié)方差來構(gòu)造協(xié)方差矩陣

    • 兩個(gè)特征向量的協(xié)方差計(jì)算公式為:Cov(x, y) = sx,y = (x - x?) ? (y - y?) / n - 1,其中 n 為每一個(gè)特征的樣本數(shù)量,n - 1 是為了實(shí)現(xiàn)誤差校正,即減少因樣本方差少于總體方差帶來的估計(jì)誤差,并且采用代碼實(shí)現(xiàn)時(shí)分母采用向量?jī)?nèi)積 np.dot(x - x?, y - y?)

    • covariance_matrix = np.dot(X.T, X) / (X.shape[0] - 1)

  3. 在 Numpy 中實(shí)施本征分解的方法為:

    • eigen_values, eigen_vectors = np.linalg.eig(convariance_matrix)
  4. 在本征分解后,將本征值和本征向量配對(duì),并將本征值按照從大到小的方式排列(Numpy 默認(rèn)不是按照本征值大小進(jìn)行排列的):

    • eigen_pairs = [(np.abs(eigen_values[i]), eigen_vectors[:, i]) for i in range(len(eigen_values))]

    • eigen_paris.sort(reverse=True) # sort the pairs with eigen_values

    • 此時(shí)在 Numpy 中本征向量會(huì)以行向量的方式進(jìn)行存儲(chǔ),構(gòu)造本征向量構(gòu)成的投影矩陣 P

  5. 用之前的特征矩陣乘以這個(gè)投影矩陣得到 Y = XPT 即為基變換后的矩陣,如果只選取前 n' 行,n' ≤ n 即可以實(shí)現(xiàn)降維 Y = np.dot(X, P.T)

在 Numpy 中的 PCA 具體實(shí)現(xiàn)舉例如下:

import numpy as np
In [42]:
X = np.array([[1, 2, 3], [4, 6, 1], [6, 2, 0], [7, 3, 1]], dtype='float64')
X
Out[42]:
array([[ 1.,  2.,  3.],
       [ 4.,  6.,  1.],
       [ 6.,  2.,  0.],
       [ 7.,  3.,  1.]])
In [44]:
X -= np.mean(X, axis=0)
X
Out[44]:
array([[-3.5 , -1.25,  1.75],
       [-0.5 ,  2.75, -0.25],
       [ 1.5 , -1.25, -1.25],
       [ 2.5 , -0.25, -0.25]])
In [45]:
cov = np.dot(X.T, X)
cov
Out[45]:
array([[ 21.  ,   0.5 ,  -8.5 ],
       [  0.5 ,  10.75,  -1.25],
       [ -8.5 ,  -1.25,   4.75]])
In [46]:
eigen_values, eigen_vectors = np.linalg.eig(cov)
In [47]:
eigen_values
Out[47]:
array([ 24.6986712 ,   1.02265454,  10.77867426])
In [48]:
eigen_vectors
Out[48]:
array([[ 0.91627689,  0.38756163, -0.10115656],
       [ 0.06821481,  0.0978696 ,  0.99285864],
       [-0.39469406,  0.9166338 , -0.06323821]])
In [49]:
eigen_pairs = [(np.abs(eigen_values[i]), eigen_vectors[:, i]) for i in range(len(eigen_values))]
eigen_pairs.sort(reverse=True)
In [50]:
eigen_pairs
Out[50]:
[(24.698671197292434, array([ 0.91627689,  0.06821481, -0.39469406])),
 (10.778674258520375, array([-0.10115656,  0.99285864, -0.06323821])),
 (1.0226545441871755, array([ 0.38756163,  0.0978696 ,  0.9166338 ]))]
In [51]:
projection = np.array([element[1] for element in eigen_pairs[:2]])
projection
Out[51]:
array([[ 0.91627689,  0.06821481, -0.39469406],
       [-0.10115656,  0.99285864, -0.06323821]])
In [52]:
Y = np.dot(X, projection.T)
Y
Out[52]:
array([[-3.98295224, -0.99769222],
       [-0.17187419,  2.79674909],
       [ 1.78251439, -1.31376037],
       [ 2.37231203, -0.4852965 ]])

由于 Numpy 中 SVD 分解后會(huì)默認(rèn)的將奇異值按照從大到小的方式進(jìn)行排列,因此上述 PCA 過程還可以利用 Numpy 的 SVD 分解來進(jìn)行:


In [53]:
cov
Out[53]:
array([[ 21.  ,   0.5 ,  -8.5 ],
       [  0.5 ,  10.75,  -1.25],
       [ -8.5 ,  -1.25,   4.75]])
In [54]:
U, S, V = np.linalg.svd(cov)
U
Out[54]:
array([[-0.91627689,  0.10115656,  0.38756163],
       [-0.06821481, -0.99285864,  0.0978696 ],
       [ 0.39469406,  0.06323821,  0.9166338 ]])
In [55]:
S
Out[55]:
array([ 24.6986712 ,  10.77867426,   1.02265454])
In [56]:
V
Out[56]:
array([[-0.91627689, -0.06821481,  0.39469406],
       [ 0.10115656, -0.99285864,  0.06323821],
       [ 0.38756163,  0.0978696 ,  0.9166338 ]])
In [57]:
Z = np.dot(X, U[:, :2])
Z
Out[57]:
array([[ 3.98295224,  0.99769222],
       [ 0.17187419, -2.79674909],
       [-1.78251439,  1.31376037],
       [-2.37231203,  0.4852965 ]])

上述兩種方法計(jì)算得到的特征向量有一個(gè)互為相反數(shù),是因?yàn)樘卣飨蛄坎晃ㄒ粚?dǎo)致的。

進(jìn)一步地,如果輸入特征本身不可以通過自然基線性表示、不服從正態(tài)分布,或者特征之間不能正交分離,那么則無法有效的通過上述方法進(jìn)行降維。

Situations where PCA fails

隨機(jī)投影 Random Projection

當(dāng)發(fā)現(xiàn)數(shù)據(jù)集中的特征維數(shù)過高時(shí),此時(shí)如果通過 PCA 來實(shí)現(xiàn)降維可能所需的計(jì)算量非常大,此時(shí)可以考慮通過矩陣乘積的形式對(duì)原始數(shù)據(jù)進(jìn)行隨機(jī)投影,這一投影降維的過程可以理解為一種 Embedding 實(shí)現(xiàn)。

在 Scikit-Learn 中實(shí)現(xiàn)隨機(jī)投影的代碼如下:

from sklearn import random_projection

rp = random_projection.SparseRandomProjection()

projected = rp.fit_transform(X)

獨(dú)立成分分析 ICA

PCA 通過分離出輸入數(shù)據(jù)中方差變化較大的項(xiàng)而實(shí)現(xiàn)降維,ICA 則從另一個(gè)角度,其認(rèn)為輸入的高維數(shù)據(jù)是由多個(gè)不同的獨(dú)立成分混合而成的,因此這一算法試圖分離出這些獨(dú)立的成分以實(shí)現(xiàn)降維。

在 Scikit-Learn 中實(shí)現(xiàn) ICA 的代碼如下:

from sklearn.decomposition import FastICA

ica = FastICA(n_components=3)
components = ica.fit(X)

參考閱讀

  1. A tutorial on Principal Components Analysis by Google

  2. 斯坦福大學(xué)計(jì)算機(jī)視覺CS231n 課程筆記

  3. 主成分分析 PCA 學(xué)習(xí)總結(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容