机器学习算法笔记(十二):主成分分析(PCA)初探

主成分分析(Principal Component Analysis,简称PCA)是一个非监督的机器学习算法,主要用于数据的降维处理。通过降维,我们可以发现数据中更便于人类理解的特征,方便数据可视化,使人类更容易理解可视化后的数据。它也能提高算法的运行效率,经过主成分分析以后再用于机器学习算法,数据的被识别率也会更好。

一、主成分分析的主要原理

降维是在训练算法模型前对数据集进行处理,会丢失信息。降维的方式本质是有无穷多种的,我们期望在其中找到“最好”,或者说“丢失信息”最少的那一种。主成分分析就是对数据进行降维和数据优化的一种手段。

以上面这张图为例,这是个二维的特征样本。若我们把它降到一维,显然有两种最简单的做法:

我们分别将这些数据完全映射在其中一个轴上。显然,右边的方案是相对较好的,因为第二种方案点和点之间的距离相对较大,点与点之间相对的有可区分度,相对保持了原来点和点之间的距离。那我们有更好的降维方案吗?

这里的“更好”是指使样本的区分度更加明显。

如上图所示,若我们将所有的数据映射到红线上,映射后的样本整体与原来样本的分布并没有更大的差距。尽管这根红线是斜的,但我们也可以把它看作是一个维度的坐标轴,将所有的点从二维降到了一维。点与点之间的距离也更加趋近于原来点与点之间的距离,区分度也更加明显。那我们如何找到这个让样本间间距最大的轴?我们用“方差”来定义样本间的间距,即。这时我们的问题就变成了:

为了解决这个问题,在进行主成分分析之前,我们先要将样本的均值归零(demean),将所有的样本减去它们整体的均值,实际上就是把坐标轴进行平移,使得样本在每一个维度的均值都为零:

上面提到方差公式为,既然样本在每一个维度的均值都为零,即为0,那方差公式就化为

我们将这条红线的方向向量设为 w = (w1,w2),所谓主成分分析法就是先将数据进行demean处理,并且想要求一个轴的方向(w1,w2)。使所有的样本方向映射到 w 后,有:

其中Xproject为映射后的样本。

又因为我们先前进行过demean处理,均值为0,所以就成了:

其中的又是谁呢?我们画一张图来说明:

实际上就是向量间的运算

若我们要将 X(i) 映射到 w 上,也就是向 w 所在的轴作一条垂线,垂足记作(Xpr1(i), Xpr2(i))。我们要求的模的平方实际就是图示蓝色部分长度对应的平方,根据向量点乘的法则,把 w 看作单位向量(因为 w 就代表方向,这里为了之后运算方便),蓝色部分的长度就等于X(i) · w。

我们的最终目标,现在转化成了求一个 w,使得最大,我们成功将它转换成了一个目标函数最优化问题,用梯度上升法解决。虽然我们也能用数学推导主成分分析,本文我们还是重点用搜索的方法解决主成分分析,对梯度法有更加深入的认识。

二、使用梯度上升法求解PCA问题

我们的目标在上一节被转化为求一个 w,使得最大。把 X(i) · w 展开,设整个方差为 f(X),得。现在我们来求 f(X) 的梯度,根据求导法则,有:

把右边的部分写作两个矩阵相乘的形式:

进一步进行向量化、化简,得:

最终 f 的梯度可以化简成下面的式子:

三、运用梯度上升法编程求解PCA

新建一个工程,创建一个main.py文件,实现如下:

import numpy as np
import matplotlib.pyplot as plt

#创建测试数据
X = np.empty((100, 2))
X[:,0] = np.random.uniform(0., 100., size=100)
X[:,1] = 0.75 * X[:,0] + 3. + np.random.normal(0, 10., size=100)

#样本均值归零
def demean(X):
    return X - np.mean(X, axis=0) #axis=0,沿着行的方向计算均值,实则求每一列均值

X_demean = demean(X)

#定义f
def f(w, X):
    return np.sum((X.dot(w) ** 2)) / len(X)

#定义f的导数
def df(w, X):
    return X.T.dot(X.dot(w)) * 2. / len(X)

#对一个向量求单位向量(单位化)
def direction(w):
    return w / np.linalg.norm(w)

#实现梯度上升法
def gradient_ascent(df, X, initial_w, eta, n_iters=1e4, epsilon=1e-8):
    w = direction(initial_w)
    cur_iter = 0

    while cur_iter < n_iters:
        gradient = df(w, X)
        last_w = w
        w = w + eta * gradient #与梯度下降的区别就是将减号改为加号
        w = direction(w)  # 注意1:每次求一个单位方向
        if (abs(f(w, X) - f(last_w, X)) < epsilon):
            break

        cur_iter += 1

    return w

initial_w = np.random.random(X.shape[1]) # 注意2:不能用0向量开始,因为0是目标函数的极小值点,会让梯度上升法错误的终止

# 注意3: 不能使用StandardScaler标准化数据

w = gradient_ascent(df, X_demean, initial_w, 0.001)
print(w) #输出结果

plt.scatter(X_demean[:,0], X_demean[:,1])
plt.plot([0, w[0]*30], [0, w[1]*30], color='r') #绘制 w 轴
plt.show()

输出结果和图像如下:

图示红线的方向就是所求 w 的方向

与线性回归不同,梯度下降法求解线性回归问题时需要对数据做归一化处理,而在PCA算法中不需要。

红线所在的轴就是我们求出的第一个主成分,我们又称它为“第一主成分”。当然,对于更高维的数据,我们还可以求出第二、第三主成分……下一篇文章会对此进行探索。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注