4.pca与梯度上升法
(一)什么是pca
pca,也就是主成分分析法(principal component analysis),主要是用来对数据集进行降维处理。举个最简单的例子,我要根据姓名、年龄、头发的长度、身高、体重、皮肤的白皙程度(随便想的)等特征来预测一个人的性别,但这些特征中有一个是最没有用的,是什么的?显然是年龄,因为年龄的大小跟这个人的性别无关。还有姓名,这个特征显然起不到决定性作用,因为有的男孩的名字起的就像女孩(比如我本人),反之亦然,但是起码绝大多数情况还是能判断的。同理还有身高,一个180CM的很大概率是男孩,当然女孩也有180cm的,比如模特。像这样我从样本的特征中,挑选出最能代表样本、或者对样本预测起到决定性作用最大的n个特征,就叫做主成分分析。为什么会有pca呢?可以想象一个,显示生活中,样本的特征很多,成百上千个也是正常的,但是我们训练不可能用全部的特征进行训练,因为肯定有很多特征是没有用的,或者说起到的作用是很小的,我们的目的就是希望找到起到决定性最大的n个特征。
主成分分析的特征
- 一个非监督的机器学习算法
- 主要用于数据的降维
- 通过降维,可以发现更便于人类理解的特征
- 其他特征:可视化,去噪等等
我们举一个只有两个特征的例子
如果我们只考虑特征1,不考虑特征2的话,那么显然,蓝色的点要从二维映射到一维
那么同理,如果我们只考虑特征2,不考虑特征1的话,那么显然会是这样
现在我们将两种降维的结果对比一下,觉得哪个好呢?
可能会觉得映射到x轴上的效果好,因为点和点之间的距离比较大,映射到y轴上的点都挨着了,不好区分。但是这是最好的方法吗?有没有更好的方法呢?
那么有没有一根直线,这根直线是斜着的,我们将所有的点都映射到这根直线上。
这样的话,降维的效果是不是更好了呢?虽然这个轴是斜着的,但是总归还是一个轴,我们确实将样本从二维映射到了一维上。使用这种方式,这些点更加趋近于原来的那些点的分布情况。点与点之间的距离也比较大,更好区分。那么问题来了,如果找到使得样本间的距离最大的那个轴呢?
既然使得样本间的距离最大,那么距离的定义也是一个需要考虑的方面。如果定义呢?显然我们可以使用方差,因为方差就是描述样本之间分布的疏密或者说均匀情况,方差越大,说明样本之间约稀疏,方差越小,说明样本之间越密集。
那么我们问题就可以转换一下了,相当于是找到一个轴,使得样本空间所有点映射到这个轴之后,对应的方差最大。
那么在解决这个问题,使用主成分分析之前,我们需要做一下处理。
对样本的均值归零(demean),也就是所有样本都减去这批样本的均值,这样这批样本的均值就变为0了,为什么这么做后面会看到。其实样本的均值归0,就是把坐标轴平移了一下
可以看到,样本的分布式没有改变的,我们只是把样本坐标轴进行了平移。
由于均值为0,那么显然方差可以化简如下,为什么要将坐标轴平移,原因在此。因此我们的过程总结起来,如下。
此时的Xproject就是X在轴的方向w上的映射,那么此时的Xproject = ||x|| · ||w|| · cosθ = X · w
因此我们的目标函数可以变成如下
(二)使用梯度上升法求解pca问题
既然要找到一个函数的最大值,那么显然可以使用梯度上升法来解决。
如果把X1、X2、X3····Xn用向量X表示、w1、w2、w3····wn表示,那么式子可以转换如下。
其实到这里已经可以编程实现了,但是肯定还要使用for循环,我们看看可不可以再变换一下,使得其可以编程向量之间的运算
可以看到,左边的式子其实就是两个红色方框里面的式子做点乘运算,最终可以化成最右边的形式。
因此我们在求梯度,等于求最右边的式子
(三)求数据的主成分pca
生成测试数据集
import numpy as np
import matplotlib.pyplot as plt
X = np.zeros((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()
均值归零
# 均值归0
def demean(X):
# 多个特征,每一个特征分别均值归0
return X - np.mean(X, axis=0)
X_demean = demean(X)
plt.scatter(X_demean[..., 0], X_demean[..., 1])
plt.show()
可以看到样本的分布没有什么变化,但是注意坐标轴变化了
梯度上升求解
# 目标函数
def f(w, X):
return np.sum((X @ w) ** 2) / len(x)
# 求梯度
def df(w, X):
return (X.T @ (X @ w)) * 2. / len(x)
# 梯度上升
def gradient_ascent(df, X, initial_w, eta, n_iters=1e4, epsilon=1e-8):
# 当前的上升次数
cur_iter = 0
# 注意我们的initial_w实际上是一个单位向量,所以我们要转化一下
# 用w 除以w的模即可
w = initial_w / np.linalg.norm(initial_w)
while cur_iter < n_iters:
gradient = df(w, X)
last_w = w
w = w + eta * gradient
w = w / np.linalg.norm(w)
# 当函数增加的值小于epsilon,我们就说梯度上升,爬坡爬不动了
if abs(f(w, X) - f(last_w, X)) < epsilon:
break
cur_iter += 1
return w
initial_w = np.random.random(X.shape[1]) # 不能从零向量开始
eta = 0.001
w = gradient_ascent(df, X_demean, initial_w, eta)
print(w) # array([0.77718535, 0.62927175])
# 绘制图像
plt.scatter(X_demean[:, 0], X_demean[:, 1])
plt.plot([0, w[0] * 30], [0, w[1] * 30], color="red")
plt.show()
此时红色的线便是我们找到的第一个主成分
(四)求数据的前n个主成分
我们之前使用梯度上升法求出了对于一组数据来说,相应的第一个主成分,是一个坐标轴对应的方向。
这是我们找到的第一个主成分,得到的结果就是,这些样本点所保持的方差是最大的。不过需要注意的是,这些样本点本身是二维的数据,我们映射到这个轴上,它还不是一维的数据,只不过是使得样本在这个轴上的方差是最大的,所以还会有另外一个轴。我们这里是二维的,同理n维也是一样,也是对应n个轴,只不过我们通过主成分分析重新对这n个轴进行了排列,使得第一个轴保持这些样本的方差是最大的,第二个轴次之,第三个轴再次之···,以此类推。因此主成分分析法实际上是从一组坐标系转移到了另外一组坐标系。
所以问题来了,当我们求出第一主成分之后,如何求出下一个主成分呢?
很简单,数据进行改变,将数据在第一个主成分上的分量去掉就行了。那么求出第二主成分,就相当于在去掉第一主成分的数据集上求第一主成分,那么同理,在去掉第一主成分、第二主成分的数据集上再求第一主成分就是原数据的第三主成分,然后循环往复
(五)sklearn中的pca
from sklearn.decomposition import PCA
from sklearn.datasets import load_boston
boston = load_boston()
X = boston.data
# 里面的n_components表示我们要选择多少个主成分
# 我们这里选择1个
pca = PCA(n_components=1)
pca.fit(X)
X_reduction = pca.transform(X)
print(f"X shape: {X.shape}") # X shape: (506, 13)
print(f"X_reduction shape: {X_pca.shape}") # X_reduction shape: (506, 1)
# 我们还可以将降维后的数据转换回去
print(f"X_inverse: {pca.inverse_transform(X_reduction).shape}") # X_inverse: (506, 13)
我们来试试手写数据集
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
data = load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
print(X_train.shape) # (1347, 64)
knn_clf = KNeighborsClassifier()
knn_clf.fit(X_train, y_train)
print(knn_clf.score(X_test, y_test)) # 0.9866666666666667
可以看到在不进行降维,使用全部的数据集特征进行训练的时候,准确率是0.98667,下面我们进行降维处理。降到多少维呢?首先这个样本有64个特征,我们就从1到64全部看看
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
data = load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
knn_clf = KNeighborsClassifier()
# 存放维度和评分的列表
n_components = []
scores = []
for n in range(1, 64):
pca = PCA(n_components=n)
pca.fit(X_train)
# 我们需要对训练集和测试集同时进行降维
X_train_reduction = pca.transform(X_train)
X_test_reduction = pca.transform(X_test)
# 训练模型
knn_clf.fit(X_train_reduction, y_train)
# 将每一次的降维的个数和分数加到对应的列表里面
n_components.append(n)
scores.append(knn_clf.score(X_test_reduction, y_test))
plt.plot(n_components, scores)
plt.locator_params("y", nbins=20)
plt.locator_params("y", nbins=30)
plt.xlabel("n_components")
plt.ylabel("score")
plt.show()
从维度和分数之间的变化可以看出,当我们只取25个特征,准确率就已经达到百分之九十八了,说明剩下的特征对于样本的预测几乎起不到什么作用了,或许使用全部的样本训练会使准确率变得更高,但是提高的程度微乎其微,但是使用25个样本训练所需要的时间肯定远远小于使用全部样本训练所需要的时间,当然内存、cpu的消耗也会更少。因为使用25个样本所得到的准确率相比使用全部样本得到的准确率只下降了一点点,因此在实际的项目生产中我们是愿意牺牲这一点点的精确率来换取时间和空间上的效率的
我们这种搜索主成分个数的方式有点类似于网格搜索,实际上PCA为我们提供了一个特殊的指标,我们可以使用这个指标方便的找到合适的主成分个数
pca.explained_variance_ratio_
:可以解释的方差比率,如果我们只取两个主成分的话,会得到大概这样一个结果array([0.14566817, 0.13735469]),里面的每一个值代表了解释原来数据方差的比例。回忆一下pca主成分分析,就是找能使原来数据的方差维持到最大的轴。第一个轴能维持0.146,第二个轴能维持0.137,加一起大概百分之28左右,意思是只能维持原来方差的百分之28左右,剩下的百分之72就丢失掉了,所以只选两个主成分是不够的,需要多选几个,但是我们也能想到。总共64个特征,会对应64个轴,那么最后面的轴能解释的方差所占的比例基本上小到可以忽略不计了。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
data = load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
knn_clf = KNeighborsClassifier()
pca = PCA(n_components=64)
pca.fit(X_train)
# 保留5位小数
print(np.array([round(i, 5) for i in pca.explained_variance_ratio_]))
"""
[1.4567e-01 1.3735e-01 1.1778e-01 8.5000e-02 5.8600e-02 5.1150e-02
4.2660e-02 3.6010e-02 3.4110e-02 3.0540e-02 2.4230e-02 2.2870e-02
1.8030e-02 1.7930e-02 1.4580e-02 1.4200e-02 1.3000e-02 1.2660e-02
1.0170e-02 9.0900e-03 8.8500e-03 7.7400e-03 7.6100e-03 7.1200e-03
6.8600e-03 5.7600e-03 5.7200e-03 5.0800e-03 4.8900e-03 4.3500e-03
3.7300e-03 3.5800e-03 3.2700e-03 3.1500e-03 3.0900e-03 2.8800e-03
2.5000e-03 2.2500e-03 2.2000e-03 1.9800e-03 1.8800e-03 1.5300e-03
1.4300e-03 1.3800e-03 1.1800e-03 1.0700e-03 9.6000e-04 9.0000e-04
5.8000e-04 3.8000e-04 2.4000e-04 8.0000e-05 6.0000e-05 5.0000e-05
1.0000e-05 0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00
0.0000e+00 0.0000e+00 0.0000e+00 0.0000e+00]
"""
可以看到,最后面的几个基本上为0了,可以丢掉不计了,我们以此来绘制图像。横轴很好理解,主成分的个数n,纵轴就是前n个主成分加起来所能解释的方差占总方差的比例
import numpy as np
import matplotlib.pyplot as plt
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
data = load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
knn_clf = KNeighborsClassifier()
pca = PCA(n_components=64)
pca.fit(X_train)
plt.plot([n for n in range(1, X.shape[1] + 1)],
np.cumsum(pca.explained_variance_ratio_))
plt.locator_params("x", nbins=20)
plt.locator_params("y", nbins=30)
plt.show()
和我们之前通过预测样本得到的结果是类似的,但是有时候我们不希望每次都通过这种方式来判断到底应该选取多少个主成分,而是给一个比率,比如0.95,意思是我需要能解释原数据百分之95的方差,然后pca算法自动帮助我们判断该选取多少个主成分。我们可以通过循环来实现,但幸运的是,sklearn已经帮助我们实现好了
from sklearn.decomposition import PCA
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split
data = load_digits()
X = data.data
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=666)
knn_clf = KNeighborsClassifier()
pca = PCA(n_components=0.95)
pca.fit(X_train)
# 打印主成分的个数
print(pca.n_components_) # 28
X_train_reduction = pca.transform(X_train)
X_test_reduction = pca.transform(X_test)
knn_clf.fit(X_train_reduction, y_train)
print(knn_clf.score(X_test_reduction, y_test)) # 0.98
(六)使用PCA对数据进行降噪
大家看这张图,我们发现曲线并不平滑,有些时候随着主成分的增加,精确率反而还降低了,这是因为数据含有噪声。因此我们对数据进行降维的时候,会发现速度变快的同时,精确率还提高了一点,这是因为在降维的时候还进行了降噪。
4.pca与梯度上升法的更多相关文章
- 机器学习(七) PCA与梯度上升法 (上)
一.什么是PCA 主成分分析 Principal Component Analysis 一个非监督学的学习算法 主要用于数据的降维 通过降维,可以发现更便于人类理解的特征 其他应用:可视化:去噪 第一 ...
- 机器学习(4)——PCA与梯度上升法
主成分分析(Principal Component Analysis) 一个非监督的机器学习算法 主要用于数据的降维 通过降维,可以发现更便于人类理解的特征 其他应用:可视化.去噪 通过映射,我们可以 ...
- 第7章 PCA与梯度上升法
主成分分析法:主要作用是降维 疑似右侧比较好? 第三种降维方式: 问题:????? 方差:描述样本整体分布的疏密的指标,方差越大,样本之间越稀疏:越小,越密集 第一步: 总结: 问题:????怎样使其 ...
- 机器学习(七) PCA与梯度上升法 (下)
五.高维数据映射为低维数据 换一个坐标轴.在新的坐标轴里面表示原来高维的数据. 低维 反向 映射为高维数据 PCA.py import numpy as np class PCA: def __ini ...
- 机器学习:PCA(使用梯度上升法求解数据主成分 Ⅰ )
一.目标函数的梯度求解公式 PCA 降维的具体实现,转变为: 方案:梯度上升法优化效用函数,找到其最大值时对应的主成分 w : 效用函数中,向量 w 是变量: 在最终要求取降维后的数据集时,w 是参数 ...
- 【笔记】求数据的对应主成分PCA(第一主成分)
求数据的第一主成分 (在notebook中) 将包加载好,再创建出一个虚拟的测试用例,生成的X有两个特征,特征一为0到100之间随机分布,共一百个样本,对于特征二,其和特征一有一个基本的线性关系(为什 ...
- Python3入门机器学习经典算法与应用
<Python3入门机器学习经典算法与应用> 章节第1章 欢迎来到 Python3 玩转机器学习1-1 什么是机器学习1-2 课程涵盖的内容和理念1-3 课程所使用的主要技术栈第2章 机器 ...
- Python3入门机器学习经典算法与应用☝☝☝
Python3入门机器学习经典算法与应用 (一个人学习或许会很枯燥,但是寻找更多志同道合的朋友一起,学习将会变得更加有意义✌✌) 使用新版python3语言和流行的scikit-learn框架,算法与 ...
- 特征脸是怎么提取的之主成分分析法PCA
机器学习笔记 多项式回归这一篇中,我们讲到了如何构造新的特征,相当于对样本数据进行升维. 那么相应的,我们肯定有数据的降维.那么现在思考两个问题 为什么需要降维 为什么可以降维 第一个问题很好理解,假 ...
随机推荐
- python|爬虫东宫小说
2k小说网爬取最近大火的<东宫>小说,借鉴之前看过的一段代码,修改之后,进行简单爬取. from urllib import requestfrom bs4 import Beautifu ...
- 加密算法之 MD5算法
题记:本人自测了很多次,该算法和apache的commons utils包中的MD5算法计算一致 一.针对文件内容生成MD5值 应用场景:针对文件,在传输过程由于网络原因丢帧或者被人别恶意篡改内容,可 ...
- 手动部署 OpenStack Rocky 双节点
目录 文章目录 目录 前言 OpenStack 架构 Conceptual architecture Logical architecture 网络选型 Networking Option 1: Pr ...
- 应用安全 - 工具 - Jmeter - 漏洞 - 汇总
CVE-2018-1297 Date 类型 rmi 反序列化导致远程代码执行 影响范围
- 【Python开发】Python之re模块 —— 正则表达式操作
Python之re模块 -- 正则表达式操作 这个模块提供了与 Perl 相似l的正则表达式匹配操作.Unicode字符串也同样适用. 正则表达式使用反斜杠" \ "来代表特殊形式 ...
- Java架构师 成长之路 -- 跳出程序员陷阱(转载)
本人也是coding很多年,虽然很失败,但也总算有点失败的心得,不过我在中国,大多数程序员都是像我一样,在一直走着弯路. 如果想成为一个架构师,就必须走正确的路,否则离目标越来越远,正在辛苦工作的程序 ...
- WinForm笔记1:TextBox编辑时和DataGridView 单元格编辑时 的事件及其顺序
TextBox 编辑框 When you change the focus by using the mouse or by calling the Focus method, focus event ...
- mysql常用排查命令
1.查看服务器使用状态 mysqladmin -uroot -p -S /tmp/mysql.sock -r -i 1 ext |\ awk -F"|" \ "BEGIN ...
- DNS 域名系统与邮件服务器
目录 DNS 域名系统 定义 域名分类 解析流程 DNS分类 资源记录 格式 资源记录类型 用bind搭建一台DNS服务器 安装bind 创建自己的zone文件 在主配置文件中,增加自己的zone 检 ...
- 【Linux-驱动】简单字符设备驱动结构和初始化
(1)在编写简单字符设备驱动的时候,首先要申请一个设备结构struct cdev: struct cdev { struct kobject kobj; struct module *owner; / ...