机器学习经典算法 - PCA

对于机器学习来说,数据的质量很大程度上决定了模型的质量,因此对于几乎所有的情况下都需要对于数据进行预处理。其中较为常用的数据处理方式是数据的标准化,进一步还可以通过主成分分析 Principal components analysis, PCA,随机投影 Random Projection 和独立成分分析 Independent Conponent Analysis 来对数据进行降维 dimensionality reduction。

数据的标准化

数据标准化最基本目的是对于将要进行运算的数据的取值进行缩放,使得数据在取值范围上处于同一个数量级,避免在运算过程中取值较小的特征被取值较大的特征“吃掉”。但将不同特征缩放到同一数量级的前提是各自特征对于最终分类和判断的重要性是等同的,否则就不应该做这一处理。

常用的标准化方式:

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

  • 采用正态分布的标准值 standard value 的方式:z = (x - x̄) / σ

-采用整体缩放的方式:在图像处理中常常将图片数组整体除以 255 以将取值缩放在 [0, 1] 之间

这里 xx'z 代表向量,采用最大值最小缩放标准化后的取值范围是 [0, 1],而标准值方法标准化的结果是将原本服从正态分布的元素进一步标准化成服从均值为 0, 方差为 1 的标准正态分布,且这两种方法在使用过程中必须要注意 排除异常值 Outlier 的影响。

数据的标准化可以通过借助 skitlearn 的预处理模块很容易的完成:

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]])

矩阵的数据处理

在实际的工作中,尤其是计算机视觉方面的应用, 更多的数据是以矩阵的形式存储的。对于一个形如 [N, D] 的矩阵 X,其中 N 为样本的数量,D 为特征的数量。

特征的去均值化 Mean subtraction

对于矩阵中的每一列特征都减去相应特征的均值,这种处理使得数据在各个特征维度上都更加趋近于中心位置,使得数据更加密集。

相应的在 Numpy 中的处理方式为:X = X - np.mean(X, axis=0)

而对于图像矩阵来说最常做的一个数据处理是在图像矩阵的各个通道上减去相应通道上全部训练样本的像素值的均值,例如在 VGG16 中的 Imagenet 数据前处理部分就是将输入图像的三个 RGB 通道上分别减去 103.939,116.779,123.68,后面这三个数是所有 Imagenet 中的图片在三个通道上分别计算得到的均值。

相应的在 Numpy 中的处理方式为:X = X - np.mean(X)

在去均值化的基础上,如果有必要还可以进一步除以相应特征的标准差使得数据进一步标准化:X /= np.std(X, axis=0)

Data normalization, from Stanford CS231n notes

在这里需要注意的是,这里的均值和方差都是针对训练数据集而言的,也即应该在划分训练数据集、验证数据集和测试数据集后在训练数据集上进行计算,再用训练数据集中的均值和方差来处理验证数据集和测试数据集。

主成分分析 Principal Components Analysis

对于本部分需要的数学知识,如 基的变换、本征值分解和奇异值分解协方差 请见链接中的笔记内容,这里直接进入主题。

之所以要做主成分分析,是因为大多数实践中的数据都是默认基于自然基 naive basis 进行记录和表示的,并且这些数据当中通常有大量的干扰噪声 noise 和冗余特征 redundancy,而寻找主成分的过程就是希望通过对于基的线性变换来找到对于被观察的数据更简洁的表示形式,从中提取最为重要的特征,即主成分,而忽略次要特征,这对简化模型复杂度和提高模型的稳健性具有重要的意义。

如果我们通过对自然基下的特征数据做线性变换后发现其在某一个方向上变动的离散程度很大,也即方差最大,则这个方向就可以认为是特征取值变动的方向,通常也就是我们感兴趣的方向,或者称信号的方向,而与之垂直的方向则可以理解为噪声的方向。评估数据质量的一个重要指标是信噪比 Signal-to-noise-ratio, SNR,其数学定义为:

  • SNR = σ2signal / σ2noise ,这个公式也隐含(一般情况下)高信号方差对应高信噪比
Signals with variances not coincide with data collection coordinate system / basis

除噪声外,多个特征之间很可能存在直接的相关性,也即我们只需要包含其中的部分特征就可以推导出其他的全部特征,因此可以在数据处理的过程中去除冗余特征,借此降低特征矩阵的维数,以减小模型需要处理的数据量。

在机器学习中对于被研究对象的多个特征的多次观测的结果通常会以一个矩阵的形式表示,称为特征矩阵,因此为了便于区分,后续涉及到利用本征值对矩阵进行的分解我都称之为本征值分解,而不是国内很多教材上的特征分解。借由统计相关知识,两个特征的相关性可以通过协方差 Covariance 来衡量,对于多个特征来说,则可以基于原有的特征矩阵构建协方差矩阵 Covariance matrix。在协方差矩阵中,对角线元素为同一个特征的方差,较大的方差值则意味着其可能是我们需要主要关注的重要变动元素。而非对角线元素则对应不同的两个特征之间的协方差,较大的协方差数值意味着两个特征之间具有较大的线性相关性,也即存在较大可能的冗余。

如果期望可以最大程度的降低冗余,则希望这个协方差矩阵可以通过线性变换变成一个对角矩阵。从协方差矩阵的构建过程可以看到它是一个实对称矩阵,而对于任意实对称矩阵来说都可以进行本征值分解,其结果为 C = QΛQT = QΛQ-1,其中 Q 为本征向量构成的正交矩阵 Quadrature matrix,Λ 为本征值构成的对角矩阵。对于本征向量来说,如果一个向量是矩阵的本征向量,则其任意非零 k 倍也是本征向量,这也可以理解为在本征向量的方向上可以有最多的信号聚集,在机器学习和深度学习的语境中,协方差矩阵的本征向量构成的正交矩阵就是特征数据集的主成分 Principal components

同时,为突出具有较大方差的特征的重要性,可以将 Λ 对角线上的元素按照从大到小的顺序进行布置。对本征值从大到小的排列后,为了满足本征分解的运算条件,本征值对应的本征向量也要在正交矩阵中保持相同的顺序,这也使得我们可以容易的识别出哪些本征向量的方向最为重要,进而舍弃掉不重要的特征实现维度缩减。

Correlation means redundancy

对于已有的输入特征矩阵 X,在通过本征值分解进行主元素分析时,需要采用以下几个步骤:

  1. 对特征矩阵的每一列 x 进行去均值化得到标准化后的矩阵 XX -= np.mean(X, axis = 0)

  2. 通过计算每一列特征 x 与其他特征的协方差来构造协方差矩阵

    • 两个特征向量的协方差计算公式为:Cov(x, y) = sx,y = (x - x̄) ⋅ (y - ȳ) / n - 1,其中 n 为每一个特征的样本数量,n - 1 是为了实现误差校正,即减少因样本方差少于总体方差带来的估计误差,并且采用代码实现时分母采用向量内积 np.dot(x - x̄, y - ȳ)

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

  3. 在 Numpy 中实施本征分解的方法为:

    • eigen_values, eigen_vectors = np.linalg.eig(convariance_matrix)
  4. 在本征分解后,将本征值和本征向量配对,并将本征值按照从大到小的方式排列(Numpy 默认不是按照本征值大小进行排列的):

    • 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

    • 此时在 Numpy 中本征向量会以行向量的方式进行存储,构造本征向量构成的投影矩阵 P

  5. 用之前的特征矩阵乘以这个投影矩阵得到 Y = XPT 即为基变换后的矩阵,如果只选取前 n' 行,n' ≤ n 即可以实现降维 Y = np.dot(X, P.T)

在 Numpy 中的 PCA 具体实现举例如下:

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 分解后会默认的将奇异值按照从大到小的方式进行排列,因此上述 PCA 过程还可以利用 Numpy 的 SVD 分解来进行:


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 ]])

上述两种方法计算得到的特征向量有一个互为相反数,是因为特征向量不唯一导致的。

进一步地,如果输入特征本身不可以通过自然基线性表示、不服从正态分布,或者特征之间不能正交分离,那么则无法有效的通过上述方法进行降维。

Situations where PCA fails

随机投影 Random Projection

当发现数据集中的特征维数过高时,此时如果通过 PCA 来实现降维可能所需的计算量非常大,此时可以考虑通过矩阵乘积的形式对原始数据进行随机投影,这一投影降维的过程可以理解为一种 Embedding 实现。

在 Scikit-Learn 中实现随机投影的代码如下:

from sklearn import random_projection

rp = random_projection.SparseRandomProjection()

projected = rp.fit_transform(X)

独立成分分析 ICA

PCA 通过分离出输入数据中方差变化较大的项而实现降维,ICA 则从另一个角度,其认为输入的高维数据是由多个不同的独立成分混合而成的,因此这一算法试图分离出这些独立的成分以实现降维。

在 Scikit-Learn 中实现 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. 斯坦福大学计算机视觉CS231n 课程笔记

  3. 主成分分析 PCA 学习总结

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,830评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,992评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,875评论 0 331
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,837评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,734评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,091评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,550评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,217评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,368评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,298评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,350评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,027评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,623评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,706评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,940评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,349评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,936评论 2 341

推荐阅读更多精彩内容