【机器学习】主成分分析法(PCA)
- 一、摘要
- 二、主成分分析的基本概念
- 三、主成分分析的数学模型
- 五、主成分分析法目标函数公式推导(`梯度上升法`求解目标函数)
- 六、梯度上升法求解目标函数第一个主成分
- 七、求解前n个主成分及PCA在数据预处理中的处理步骤(后续实现)
一、摘要
本文主要讲述了主成分分析法(PCA)的原理和应用。PCA通过选择最重要的特征,将高维数据映射到低维空间,同时保持数据间的关系,实现降维和去噪。通过具体的例子和图示解释了PCA的原理,并比较了两种降维方案,解释了为什么选择将数据映射到x轴上可以得到更好的分类效果。最后强调了在PCA之前需要对所有样本的
均值进行归零处理
。通过这篇文章,读者可以深入了解PCA的原理和应用,并掌握如何使用PCA进行数据降维和去噪,提高分类效果。
二、主成分分析的基本概念
- 定义 :
PCA (Principal Component Analysis
)即主成分分析
,是机器学习中一种常用的数据分析技术,PCA 是一种无监督的线性降维算法 ,它通过线性变换将原始数据转换为一组新的特征 ,这些新特征 被称为主成分
,它们是原始特征的线性组合,并且在尽可能保留原始数据信息的前提下 ,实现数据维度的降低。 - 作用 :
- 数据降维 :在很多实际应用中,数据的维度可能非常高,这会导致计算量增大、模型训练时间变长以及可能出现的过拟合等问题。
PCA 可以将高维数据映射到低维空间,在保留大部分关键信息的同时,大大减少数据的维度,提高计算效率和模型性能。
- 去除噪声和冗余 :原始数据中往往存在一些噪声和冗余信息,这些信息可能会干扰模型的训练和预测。
PCA 通过提取主成分,可以去除一些与其他特征高度相关的冗余信息,以及一些对数据整体结构影响较小的噪声,使数据更加干净和易于处理。
- 数据可视化 :对于高维数据,很难直接进行可视化展示。
通过 PCA 将数据降维到二维或三维空间后,可以方便地进行数据可视化,帮助人们直观地理解数据的分布和结构,发现数据中的潜在规律和异常点。
- 数据降维 :在很多实际应用中,数据的维度可能非常高,这会导致计算量增大、模型训练时间变长以及可能出现的过拟合等问题。
- 底层原理 :
- 协方差矩阵与特征值分解 :
首先
计算原始数据的协方差矩阵
,协方差矩阵可以衡量各个特征之间的相关性。然后
对协方差矩阵进行特征值分解
,得到特征值
和特征向量
。特征值
表示对应主成分
的方差大小
,特征向量
则表示主成分
的方向
。 - 主成分的选取 :按照
特征值
从大到小 的顺序 对特征向量
进行排序
,选择前k个特征向量 作为主成分
,其中k
是降维后想要得到的维度
。这些主成分所对应的特征值较大,意味着它们能够解释原始数据中较大比例的方差,即包含了原始数据的大部分重要信息。 - 数据投影 :将原始数据
投影
到选取的主成分
所张成 的子空间
上,就得到了降维后的数据。具体来说,对于原始数据中的每个样本,通过与主成分对应的特征向量进行线性变换,将其映射到新的低维空间中。
- 协方差矩阵与特征值分解 :
- 补充方差的概念
- 方差的意义:方差是数据点与其平均值之间差异的平方的平均数。它反映了数据分布的分散程度。
- 方差大 vs 方差小 :
- 方差大:数据点远离均值,分布广泛,样本显得稀疏。
- 方差小:数据点接近均值,集中分布,样本显得紧密。
- 图形化理解 :高方差的数据通常在图表中表现为更长的尾巴或更大的范围,而低方差则集中在中间区域。
结论:方差越大,样本间越稀疏;方差越小,样本间越紧密。
三、主成分分析的数学模型
-
PCA的目标是找到一个方向向量w,使得所有样本映射到该方向后方差最大。而PCA寻找最大方差方向,进行数据降维。
-
映射后的样本方差计算公式为每个样本与方向向量w的点积的平方和除以样本数量m。 映射过程可以通过几何方式解释,即将每个样本点垂直投影到目标方向向量w上。投影长度等于样本点与方向向量w的点积结果,反映了样本在该方向上的分布情况。
-
当均值为零时,方差计算公式进一步简化为向量模的平方和除以m。
五、主成分分析法目标函数公式推导(梯度上升法
求解目标函数)
-
目标函数对每一个w求偏导数 (
梯度
):
-
通过向量点乘 后简化公式如下:
-
通过进一步向量化点乘 处理后,可以简化如下所示:
-
最后来看下简化的梯度 :
六、梯度上升法求解目标函数第一个主成分
-
生成测试数据集
pythonimport 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) # 绘制数据散点图 plt.scatter(X[:, 0], X[:, 1]) plt.show()
效果:
-
均值归零处理
python# 在进行PCA之前需要实现均值归一划处理,也就是将均值归零 # 1. 定义均值归零的函数,定义了一个名为demean的函数,该函数接受一个数据集X作为参数。 def demean(X): # return X - np.mean(X, axis=0) 是函数的主体部分。 # np.mean(X, axis=0)计算数据集X每列的均值,axis=0表示沿着列方向计算。 # 然后用原始数据集X减去每列的均值,实现对数据的去均值操作,即让每列数据的均值变为 0 ,最后返回去均值后的数据。 return X - np.mean(X, axis=0) # 2. 调用函数,并赋值给X_demean X_demean = demean(X) # 3. plt.scatter(X_demean[:, 0], X_demean[:, 1]) 使用matplotlib库的scatter函数绘制散点图,横坐标为去均值后数据X_demean的第一列,纵坐标为第二列。 plt.scatter(X_demean[:, 0], X_demean[:, 1]) plt.show()
效果:
验证下均值归零是否正常:
python# 验证下均值归零是否正常 print(np.mean(X_demean[:, 0])) # 计算第一列均值 print(np.mean(X_demean[:, 1])) # 计算第二列均值
结果:
1.1723955140041652e-14 约等于0
2.5579538487363606e-15 约等于0
-
目标函数、梯度上升法等对应公式的代码实现
python# 1. 实现目标函数:找到w,使得方差最大的函数,接受权重向量w和数据集X作为参数 def f(w, X): return np.sum((X.dot(w)**2)) / len(X) # 2. 求梯度,即目标函数f的解析梯度函数,用于计算梯度。这里通过数学推导得出的梯度公式,X.T.dot(X.dot(w) * 2. / len(X))实现对目标函数求导后的计算,返回梯度向量。 def df_math(w, X): return X.T.dot(X.dot(w) * 2. / len(X)) # 3. 该函数是通过有限差分法近似计算梯度,用于调试和验证df_math函数的正确性。 # epsilon是一个很小的数,用于计算数值梯度。通过分别对权重向量w的每个维度加上和减去epsilon,计算对应的函数值差,再除以2 * epsilon来近似该维度上的梯度,最终返回一个近似梯度向量。 def df_debug(w, X, epsilon=0.0001): res = np.empty(len(w)) for i in range(len(w)): w_1 = w.copy() w_1[i] += epsilon w_2 = w.copy() w_2[i] -= epsilon res[i] = (f(w_1, X) - f(w_2, X)) / (2 * epsilon) return res # 4. 该函数用于将输入的向量w进行归一化处理,使其成为单位向量。 # np.linalg.norm(w)计算向量w的范数(长度),通过将向量w的每个元素除以其范数,得到方向不变但长度为 1 的单位向量。 def direction(w): return w / np.linalg.norm(w) # 5. 定义gradient_ascent函数,实现梯度上升算法: # def gradient_ascent(df, X, initial_w, eta, n_iters = 1e4, epsilon=1e-8),实现梯度上升算法。参数含义如下: # df:计算目标函数梯度的函数。 # X:数据集。 # initial_w:初始权重向量。 # eta:学习率,控制每次权重更新的步长。 # n_iters:最大迭代次数,默认值为10000(1e4)。 # epsilon:收敛阈值,默认值为1e-8,用于判断算法是否收敛。 # w = direction(initial_w),对初始权重向量initial_w进行归一化处理,得到初始的单位权重向量w。 # cur_iter = 0,初始化当前迭代次数为0。 # while cur_iter < n_iters:,开始迭代循环,只要当前迭代次数小于最大迭代次数就继续执行: # gradient = df(w, X),调用梯度计算函数df,计算当前权重w下的梯度。 # last_w = w,保存上一次的权重向量。 # w = w + eta * gradient,根据梯度上升公式更新权重向量,沿着梯度方向移动eta倍的梯度长度。 # w = direction(w),对更新后的权重向量w再次进行归一化处理,保持其为单位向量。 # if(abs(f(w, X) - f(last_w, X)) < epsilon): break,计算当前权重和上一次权重对应的目标函数值之差的绝对值,如果小于收敛阈值epsilon,则认为算法收敛,跳出循环。 # cur_iter += 1,每次迭代结束后,迭代次数加1。 # return 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) if(abs(f(w, X) - f(last_w, X)) < epsilon): break cur_iter += 1 return w
验证代码正确性:
python# 测试梯度上升法求解结果的正确性 # 初始化权重向量 # 生成一个长度等于数据特征维度(这里是 2)的随机权重向量。不能用 0 向量开始,可能是因为用 0 向量开始在梯度上升法迭代中会导致一些问题,比如收敛缓慢或无法收敛。 initial_w = np.random.random(X.shape[1]) # 注意2:不能用0向量开始 print(initial_w) # 设置梯度上升法中的学习率,控制每次权重更新的步长。 eta = 0.001 # 注意3:不能使用StandardScaler标准化数据 print(gradient_ascent(df_debug, X_demean, initial_w, eta)) print(gradient_ascent(df_math, X_demean, initial_w, eta))
结果如下:
[0.55799828 0.70512732]
[0.76320173 0.64616029] df_debug函数求得的数据
[0.76320173 0.64616029] df_math函数求得的数据,和df_debug函数求得的数据是一致的。
-
求解第一主成分
python# 调用gradient_ascent函数,传入梯度计算函数df_math、去均值后的数据集X_demean、初始权重向量initial_w和学习率eta,通过梯度上升法计算得到最终的权重向量w。 w = gradient_ascent(df_math, X_demean, initial_w, eta) # 绘制w向量在二维坐标平面上的图像,这就是我们通过梯度上升法找到的第一个主成分 plt.scatter(X_demean[:, 0], X_demean[:, 1]) # 横坐标为X_demean的第一列数据,纵坐标为第二列数据 plt.plot([0, w[0]*30], [0, w[1]*30], color='r') # 由于w是单位向量,对于的数字太小了(1),为了图像好效果明显些,这里分别在横纵坐标轴各自乘以30作为系数 plt.show()
效果如下:
七、求解前n个主成分及PCA在数据预处理中的处理步骤(后续实现)
- 数据标准化 :首先对原始数据进行标准化处理,将每个特征的
均值
变为0,方差
变为1。这是因为PCA对数据的尺度比较敏感,如果不同特征的尺度差异较大,可能会导致某些特征在协方差计算中占据主导地位,影响主成分的提取效果。均值归零的处理办法:- 在进行主成分分析之前,需要对所有样本进行均值归零处理。
- 均值归零处理通过减去样本整体均值,使样本在每个维度上的均值变为零。
- 处理后的方差计算公式得到简化,方便后续计算。
- 计算协方差矩阵:对标准化后的数据计算协方差矩阵,协方差矩阵能够反映各个特征之间的线性关系。
- 特征值分解:对协方差矩阵进行特征值分解,得到特征值和特征向量。
- 确定主成分个数:根据具体的应用需求和数据特点,确定需要保留的主成分个数k。常见的方法是根据累计方差贡献率来确定,即选择使得累计方差贡献率达到一定阈值(如80%、90%等)的最小k值。
- 构建主成分矩阵:选取前k个最大特征值对应的特征向量,构建一个大小为n×k的主成分矩阵,其中n是原始数据的特征维度。
- 数据降维:将原始数据与主成分矩阵相乘,得到降维后的数据。降维后的数据维度为k,且尽可能保留了原始数据的重要信息。