主成份分析(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

相关文章

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Mac系统下卸载JAVA和JDK的步骤

《Mac系统下卸载JAVA和JDK的步骤》JDK是Java语言的软件开发工具包,它提供了开发和运行Java应用程序所需的工具、库和资源,:本文主要介绍Mac系统下卸载JAVA和JDK的相关资料,需... 目录1. 卸载系统自带的 Java 版本检查当前 Java 版本通过命令卸载系统 Java2. 卸载自定

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Nginx 配置跨域的实现及常见问题解决

《Nginx配置跨域的实现及常见问题解决》本文主要介绍了Nginx配置跨域的实现及常见问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来... 目录1. 跨域1.1 同源策略1.2 跨域资源共享(CORS)2. Nginx 配置跨域的场景2.1

Python中提取文件名扩展名的多种方法实现

《Python中提取文件名扩展名的多种方法实现》在Python编程中,经常会遇到需要从文件名中提取扩展名的场景,Python提供了多种方法来实现这一功能,不同方法适用于不同的场景和需求,包括os.pa... 目录技术背景实现步骤方法一:使用os.path.splitext方法二:使用pathlib模块方法三

CSS实现元素撑满剩余空间的五种方法

《CSS实现元素撑满剩余空间的五种方法》在日常开发中,我们经常需要让某个元素占据容器的剩余空间,本文将介绍5种不同的方法来实现这个需求,并分析各种方法的优缺点,感兴趣的朋友一起看看吧... css实现元素撑满剩余空间的5种方法 在日常开发中,我们经常需要让某个元素占据容器的剩余空间。这是一个常见的布局需求

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

使用Python和OpenCV库实现实时颜色识别系统

《使用Python和OpenCV库实现实时颜色识别系统》:本文主要介绍使用Python和OpenCV库实现的实时颜色识别系统,这个系统能够通过摄像头捕捉视频流,并在视频中指定区域内识别主要颜色(红... 目录一、引言二、系统概述三、代码解析1. 导入库2. 颜色识别函数3. 主程序循环四、HSV色彩空间详解

Windows下C++使用SQLitede的操作过程

《Windows下C++使用SQLitede的操作过程》本文介绍了Windows下C++使用SQLite的安装配置、CppSQLite库封装优势、核心功能(如数据库连接、事务管理)、跨平台支持及性能优... 目录Windows下C++使用SQLite1、安装2、代码示例CppSQLite:C++轻松操作SQ