主成份分析(PCA)基本原理/步骤及其C++ 实现与优化(结合Eigen矩阵库)

本文主要是介绍主成份分析(PCA)基本原理/步骤及其C++ 实现与优化(结合Eigen矩阵库),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

主成份分析是常用的降维方法,其他降维方法还有线性判别分析LDA,二者的区别见:https://www.cnblogs.com/pinard/p/6244265.html   简要说就是:

1.PCA将原始数据投影到方差最大的方向,LDA将数据投影到不同样本的中心点距离最大的方向。

2. PCA是无监督降维,LDA是有监督降维。

3. 若分类主要依赖均值而非方差,则LDA效果好,反之PCA效果好

 

PCA 的主要步骤:

1. 使用PCA之前必须进行特征缩放!feature scaling

2. 计算特征矩阵X的协方差矩阵Sigma

sigma = 1/m × X^T × X;

相关文献大部分公式都是要求计算协方差矩阵之前先将特征矩阵每一个维度减去平均值,这样是让数据分布以原点为中心,但并非必要,并不影响对数据分布方差的分析。因为协方差矩阵和PCA主要考虑方差而非均值,与LDA正好相反。

协方差矩阵描述了样本的分布形状。

m 是样本数,即特征矩阵X的行数。X 的维度是 m×n,n 是特征向量的维度,即降维之前原始特征数。

得到的协方差矩阵sigma 是 n×n 的矩阵

3. 对协方差矩阵进行奇异值分解

奇异值分解的几何意义这篇文章讲的特别好:  https://blog.csdn.net/jinshengtao/article/details/18448355

[U, S, V]  = svd(sigma);

U,S,V 都是n×n的矩阵

奇异值(特征值)描述了数据分布的形状。最大特征值(奇异值)对应的特征向量指向数据主要分布方向,即方差最大的方向!

->协方差矩阵特征值从小到大排列对应的特征向量指向数据分布的方差从大到小的方向。协方差矩阵特征值不受刚性变换的影响,而特征向量受刚性变换的影响!

其中 U 是 包含左奇异向量的矩阵,V 是包含右奇异向量的矩阵。S 是一个对角阵,对角线上的元素都是奇异值:s11, s22, s33, ..., snn,奇异值在S中从大到小排列.  特征向量即PCA需要将数据投影的方向!为什么PCA要将数据投影到特征向量的方向即方差最大的方向呢?因为数据的分布无非是用均值和方差来表征,PCA主要考虑方差,投影后保留大部分的方差就意味着保留数据分布的大部分特征!使得样本数据往低维投影后,能尽可能表征原始的数据。

下面这张图很关键:

这样按照上图,就可以取U的前k列,作为Uredue,降维后的特征矩阵 Xreduce = X × Ureduce

将特征向量矩阵取前k列,与原矩阵相乘,这样的几何意义是将原矩阵投影到k个特征向量上,因为矩阵乘法的意义就是一个变换矩阵作用于另一个矩阵X。

协方差矩阵的几何意义详见我这篇博客:https://blog.csdn.net/shaozhenghan/article/details/81291988

C++代码(结合Eigen矩阵库)

do_pca.cpp

对之前的不等式等价变换,如上图。变换后的不等式右边项是固定值,代码实现时放在for循环外面。左边项是累加,每次循环都比上次循环多加一个数。因此把这个累加和定义在循环外面,每次在原来的基础上加一个数。这样就不用每次从头加起。

float sum_sing_part = 0.0;

unsigned int k = 0;

while (k < S.rows())

{      

       sum_sing_part  +=  S.row(k).sum();

       ..........

 

#include "do_pca.h"using namespace std;
using namespace Eigen;bool pca (const MatrixXf & X, MatrixXf & X_reduced, const float variance_remain)
{// m: number of rows of original data setunsigned int m = X.rows();// Covariance Matrix Sigma MatrixXf Sigma = 1.0 / m * (X.transpose() * X); // SVD decomposition: [U, S, V] = svd(Sigma);JacobiSVD<MatrixXf> svd(Sigma, ComputeFullU);// left_singular_matrixMatrixXf U = svd.matrixU();// singular values vectorMatrixXf S = svd.singularValues();cout << "\n S = \n" << S << endl; // debug// (variance_remain*100)% of variance should be retainedif(variance_remain < 0 || variance_remain > 1.0){cout << "\n variance_remain should in [0.0, 1.0]! \n" << endl;return(false);}float sum_sing_remained = variance_remain * S.sum();cout << "\n S.sum() = " << S.sum() << endl;  // debugfloat sum_sing_part = 0.0;unsigned int k = 0;while (k < S.rows()){sum_sing_part += S.row(k).sum();cout << "\n sum_sing_part = " << sum_sing_part << "for k = " << k << endl; // debug if (sum_sing_part >= sum_sing_remained){cout << "\n" << " more than " << 100*variance_remain << "% of variance is retained for k = " << k << endl; break;}++k;}// Uk: n*(k+1)MatrixXf Uk = U.leftCols(k + 1); // X_reduced: m * (k+1)X_reduced = X * Uk; return (true);
}

 

do_pca.h

#ifndef DO_PCA_H
#define DO_PCA_H#include <pcl/common/eigen.h>bool pca (const Eigen::MatrixXf & X, Eigen::MatrixXf & X_reduced, const float variance_remain);#endif

 

写一个测试代码:用随机数矩阵测试一下。

test_pca.cpp

#include "do_pca.h"
#include <ctime>
#include <iostream>using namespace std;
using namespace Eigen;int main(int argc, char const *argv[])
{srand((unsigned)time(NULL));MatrixXf X = (MatrixXf::Random(10,10));cout << "\n X before pca\n" << X << endl;MatrixXf X_reduced;if(pca(X, X_reduced, 0.99)){cout << "\n X after pca \n" << X_reduced << endl;}return 0;
}

 

cmake make 之后,运行结果为:

因为k从0计数,所以k=6 对应7列。X after pca 之后是10 行 7 列。

 

 

 

这篇关于主成份分析(PCA)基本原理/步骤及其C++ 实现与优化(结合Eigen矩阵库)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/413894

相关文章

Flutter实现文字镂空效果的详细步骤

《Flutter实现文字镂空效果的详细步骤》:本文主要介绍如何使用Flutter实现文字镂空效果,包括创建基础应用结构、实现自定义绘制器、构建UI界面以及实现颜色选择按钮等步骤,并详细解析了混合模... 目录引言实现原理开始实现步骤1:创建基础应用结构步骤2:创建主屏幕步骤3:实现自定义绘制器步骤4:构建U

SpringBoot中四种AOP实战应用场景及代码实现

《SpringBoot中四种AOP实战应用场景及代码实现》面向切面编程(AOP)是Spring框架的核心功能之一,它通过预编译和运行期动态代理实现程序功能的统一维护,在SpringBoot应用中,AO... 目录引言场景一:日志记录与性能监控业务需求实现方案使用示例扩展:MDC实现请求跟踪场景二:权限控制与

Android实现定时任务的几种方式汇总(附源码)

《Android实现定时任务的几种方式汇总(附源码)》在Android应用中,定时任务(ScheduledTask)的需求几乎无处不在:从定时刷新数据、定时备份、定时推送通知,到夜间静默下载、循环执行... 目录一、项目介绍1. 背景与意义二、相关基础知识与系统约束三、方案一:Handler.postDel

慢sql提前分析预警和动态sql替换-Mybatis-SQL

《慢sql提前分析预警和动态sql替换-Mybatis-SQL》为防止慢SQL问题而开发的MyBatis组件,该组件能够在开发、测试阶段自动分析SQL语句,并在出现慢SQL问题时通过Ducc配置实现动... 目录背景解决思路开源方案调研设计方案详细设计使用方法1、引入依赖jar包2、配置组件XML3、核心配

Java NoClassDefFoundError运行时错误分析解决

《JavaNoClassDefFoundError运行时错误分析解决》在Java开发中,NoClassDefFoundError是一种常见的运行时错误,它通常表明Java虚拟机在尝试加载一个类时未能... 目录前言一、问题分析二、报错原因三、解决思路检查类路径配置检查依赖库检查类文件调试类加载器问题四、常见

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B