主成分分析(PCA)算法模型实现及分析(源码在文章后附录)
Author: Nirvana Of Phoenixl
Proverbs for you:There is no doubt that good things will always come, and when it comes late, it can be a surprise.
主成分分析(PCA)是一种能够极大提升无监督特征学习速度的数据降维算法。在许多领域研究与应用当中,通常需要对含有多个变量的数据进行观测并分析规律,而许多变量之间可能存在相关性,从而增加了问题分析的复杂性。如果分别对每个指标进行分析,分析往往是孤立的,不能完全利用数据中的信息,因此盲目减少指标会损失很多有用的信息,从而产生错误的结论。
因而需要减少分析指标的同时,确保指标包含信息的相对完整,以达到对所收集数据进行全面分析的目的。由于各变量之间存在一定的相关关系,因此可以考虑将关系紧密的变量变成尽可能少的新变量,使这些新变量是两两不相关的,那么就可以用较少的综合指标分别代表存在于各个变量中的各类信息。
主成分分析(Principal Component Analysis,PCA)的方法,可以将具有多个观测变量的高维数据集降维,使人们可以从事物之间错综复杂的关系中找出一些主要的方面,从而能更加有效地利用大量统计数据进行定量分析,并可以更好地进行可视化、回归等后续处理。
PCA主要思想:是将n维特征映射到k维上,即在原有n维特征的基础上重新构造出来的k维特征,此过程产生的新的正交特征成为主成分。
PCA的工作:就是从原始的空间中顺序地找一组相互正交的坐标轴,新的坐标轴的选择与数据本身是密切相关的。其中,(1)第一个新坐标轴选择是原始数据中方差最大的方向,(2)第二个新坐标轴选取是与第一个坐标轴正交的平面中使得方差最大的,(3)第三个轴是与第1,2个轴正交的平面中方差最大的。依次类推,可以得到n个这样的坐标轴ID。通过这种方式获得的新的坐标轴,我们发现,大部分方差都包含在前面k个坐标轴中,后面的坐标轴所含的方差几乎为0。于是,我们可以忽略余下的坐标轴,只保留前面k个含有绝大部分方差的坐标轴。事实上,这相当于只保留包含绝大部分方差的维度特征,而忽略包含方差几乎为0的特征维度,实现对数据特征的降维处理。
通过计算数据矩阵的协方差矩阵,然后得到协方差矩阵的特征值特征向量,选择特征值最大(即方差最大)的k个特征所对应的特征向量组成的矩阵,即将数据矩阵转换到新的空间当中,实现数据特征的降维。如式(2-1)到(2-3)所示实现样本协方差的计算。
(1)计算样本均值:
其中x表示样本,n表示样本总数。
(2)样本方差:
(3)样本X和样本Y的协方差:
其中cov(X,Y)表示两样本的协方差,用记为E。
方差的计算公式是针对一维特征,即就是针对同一特征不同样本的取值计算得到的;而协方差至少要满足二维特征,实际上方差是协方差的特殊情况。
由于得到协方差矩阵的特征值特征向量有两种方法:特征值分解协方差矩阵、奇异值分解协方差矩阵,所以PCA算法有两种实现方法:基于特征值分解协方差矩阵实现PCA算法、基于SVD分解协方差矩阵实现PCA算法。本文选择MATLAB实现PCA算法。
选定MATLAB作为实验平台实现主成分分析达到降维目的,采用SVD算法是该平台默认的PCA实现算法。在完成明确2.2节的基本知识后,下面就可以确定具体实现降维的操作。
第一步:原始样本数据获取。
第二步:分别取求每个特征的平均值,针对所有样本,减去相应的均值。
第三步:求解协方差矩阵,如2-1所示步骤。
第四步: 奇异值分解,求取协方差矩阵的特征值和特征向量。
第五步: 倒序排列特征值(从大到小),选择最大特征值作为主成分,成为新的样本。
第六步:将特征值最大的d个向量作为投影向量,构成D*d维的投影矩阵W,对于任意维样本,将其投影选取的特征向量(主成分方向)上。
在MATLAB中需要载入其自带的数据集fisheriris,该数据集总共统计了三种鸢尾花的花萼长、花萼宽、花瓣长和花瓣宽,然后进行中心化处理,并计算协方差矩阵,如图2.1所示。

图2.1导入数据集并中心化处理
利用特征值分解法:使用eig函数,如图2.2所示,实现主成分分析,主要包括特征值矩阵的提取、按升序排列特征值等。

图2.2特征分解及协方差计算
通过计算方差的累积贡献率,如式2-4所示,结合数据模型可以实现数据的降维,基于方差贡献率可以确定最终降维的维数,一般来说是通过对数据的观察来确定主元个数,而利用此方法可以简单的确定PCA主成分分析中的主元个数。
其中, 是递增的,因此f (k)为单调递增的函数,且递增速度随着k增加逐渐降低。
一般来说,取f(r)≥ 某一阈值(如95%)的最小的r,这样最多损失5%的方差,不同算法取的 不同或者替换为 即可,通过画作图实现,如图2.3所示。

图2.3方差贡献率确定维数
实现效果如2.4所示,可以判断降维数,用以分析。

图2.4方差贡献率
结合MATLAB确定的降维维数,依据数据集完成降维,如图2.5所示。

通过上述中心化、计算协方差和特征分解并进行作图实现降维处理,如图2.6实现。

图2.6 PCA降维
实际上MATLAB中本身就集成了SVD算法,在一般情况下,MATLAB中的PCA算法也会使用SVD,所以在也可以通过奇异值分解(SVD)实现。同样地,也可以利用pca函数实现,两者的调用格式如图2.7所示,其作图部分与(2)中所示作图一致。

图2.7 不同PCA实现
PCA主成分分析算法,通过对高维数据的降维处理,将原本数据集使用合理的方法采用主成分代替,也就是利用主成分来代表高维数据尽可能的不失真的表示原本数据集。
通过上面鸢尾花的例子,降维后的数据仍然可以清晰地分为三类。当需要确定一种鸢尾花是,计算相应的T1和T2主成分得分(Principal component scores),即为新空间中的数据点,将其结果画在散点图中,就可以判断出其属于哪一种鸢尾花,同样的道理应用到更多的场合也可以实现。
PCA可以应用到很多场合比如聚类分析,然后应用到的电商场合的推送;图像的压缩和人脸检测与匹配等。
load fisheriris; %导入数据集
X = meas; % n = 150, m = 4
meanX = ones(size(X,1), 1) * mean(X); % 中心化处理
centredX = X - meanX;
C = cov(centredX); % 直接调用cov直接计算协方差矩阵即可
[W, Lambda] = eig(C); % W是特征向量组成的矩阵(4×4),Lambda是特征值组成的对角矩阵
ev = (diag(Lambda))'; % 提取特征值
ev = ev(:, end:-1:1); % eig计算出的特征值是升序的,这里手动倒序(W同理)
W = W(:, end:-1:1);
sum(W.*W, 1) % 可以验证每个特征向量各元素的平方和均为
Wr = W(:, 1:2); % 提取前两个主成分的特征向量
Tr = centredX * Wr; % 新坐标空间的数据点
% 作图
figure;
stairs(cumsum(ev)/sum(ev), 'LineWidth',1.5);
axis([1 4 0 1]);
xlabel('$ k $', 'Interpreter', 'latex');
ylabel('$ f(k)=\frac{\sum _{i=1}^i \lambda_k}{\sum_{i=1}^m \lambda_i} $',...
'Interpreter', 'latex');
hold on;
plot([1 4], [0.95 0.95], '--'); % 从图中可以看出,r为方差贡献率,取r = 2
figure;
scatter(Tr(:,1), Tr(:,2), 130, categorical(species), '.');
colormap(winter);
xlabel('Principal Component 1');
ylabel('Principal Component 2');
[U, Sigma, V] = svd(X); % 可以检验,W和V完全相同(向量的正负号不影响)
Vr = V(:, 1:2); % 提取前两个主成分的特征向量
Tr = X * Vr; % 新坐标空间的数据点
% 画图部分同上
[loadings, scores] = pca(X, 'NumComponents', r);
[Wr, Tr, ev] = pca(X, 'NumComponents',2); % 画图部分
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我需要从一个View访问多个模型。以前,我的links_controller仅用于提供以不同方式排序的链接资源。现在我想包括一个部分(我假设)显示按分数排序的顶级用户(@users=User.all.sort_by(&:score))我知道我可以将此代码插入每个链接操作并从View访问它,但这似乎不是“ruby方式”,我将需要在不久的将来访问更多模型。这可能会变得很脏,是否有针对这种情况的任何技术?注意事项:我认为我的应用程序正朝着单一格式和动态页面内容的方向发展,本质上是一个典型的网络应用程序。我知道before_filter但考虑到我希望应用程序进入的方向,这似乎很麻烦。最终从任何
我有一个包含模块的模型。我想在模块中覆盖模型的访问器方法。例如:classBlah这显然行不通。有什么想法可以实现吗? 最佳答案 您的代码看起来是正确的。我们正在毫无困难地使用这个确切的模式。如果我没记错的话,Rails使用#method_missing作为属性setter,因此您的模块将优先,阻止ActiveRecord的setter。如果您正在使用ActiveSupport::Concern(参见thisblogpost),那么您的实例方法需要进入一个特殊的模块:classBlah
我有一个表单,其中有很多字段取自数组(而不是模型或对象)。我如何验证这些字段的存在?solve_problem_pathdo|f|%>... 最佳答案 创建一个简单的类来包装请求参数并使用ActiveModel::Validations。#definedsomewhere,atthesimplest:require'ostruct'classSolvetrue#youcouldevencheckthesolutionwithavalidatorvalidatedoerrors.add(:base,"WRONG!!!")unlesss
我想向我的Controller传递一个参数,它是一个简单的复选框,但我不知道如何在模型的form_for中引入它,这是我的观点:{:id=>'go_finance'}do|f|%>Transferirde:para:Entrada:"input",:placeholder=>"Quantofoiganho?"%>Saída:"output",:placeholder=>"Quantofoigasto?"%>Nota:我想做一个额外的复选框,但我该怎么做,模型中没有一个对象,而是一个要检查的对象,以便在Controller中创建一个ifelse,如果没有检查,请帮助我,非常感谢,谢谢
我有一些非常大的模型,我必须将它们迁移到最新版本的Rails。这些模型有相当多的验证(User有大约50个验证)。是否可以将所有这些验证移动到另一个文件中?说app/models/validations/user_validations.rb。如果可以,有人可以提供示例吗? 最佳答案 您可以为此使用关注点:#app/models/validations/user_validations.rbrequire'active_support/concern'moduleUserValidationsextendActiveSupport:
我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden
对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs
我有一个正在构建的应用程序,我需要一个模型来创建另一个模型的实例。我希望每辆车都有4个轮胎。汽车模型classCar轮胎模型classTire但是,在make_tires内部有一个错误,如果我为Tire尝试它,则没有用于创建或新建的activerecord方法。当我检查轮胎时,它没有这些方法。我该如何补救?错误是这样的:未定义的方法'create'forActiveRecord::AttributeMethods::Serialization::Tire::Module我测试了两个环境:测试和开发,它们都因相同的错误而失败。 最佳答案
ruby如何管理内存。例如:如果我们在执行过程中采用C程序,则以下是内存模型。类似于这个ruby如何处理内存。C:__________________|||stack|||------------------||||------------------|||||Heap|||||__________________|||data|__________________|text|__________________Ruby:? 最佳答案 Ruby中没有“内存”这样的东西。Class#allocate分配一个对象并返回该对象。这就是程序