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

相关文章

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

Qt中QGroupBox控件的实现

《Qt中QGroupBox控件的实现》QGroupBox是Qt框架中一个非常有用的控件,它主要用于组织和管理一组相关的控件,本文主要介绍了Qt中QGroupBox控件的实现,具有一定的参考价值,感兴趣... 目录引言一、基本属性二、常用方法2.1 构造函数 2.2 设置标题2.3 设置复选框模式2.4 是否

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法

《springboot整合阿里云百炼DeepSeek实现sse流式打印的操作方法》:本文主要介绍springboot整合阿里云百炼DeepSeek实现sse流式打印,本文给大家介绍的非常详细,对大... 目录1.开通阿里云百炼,获取到key2.新建SpringBoot项目3.工具类4.启动类5.测试类6.测

pytorch自动求梯度autograd的实现

《pytorch自动求梯度autograd的实现》autograd是一个自动微分引擎,它可以自动计算张量的梯度,本文主要介绍了pytorch自动求梯度autograd的实现,具有一定的参考价值,感兴趣... autograd是pytorch构建神经网络的核心。在 PyTorch 中,结合以下代码例子,当你

如何在Mac上安装并配置JDK环境变量详细步骤

《如何在Mac上安装并配置JDK环境变量详细步骤》:本文主要介绍如何在Mac上安装并配置JDK环境变量详细步骤,包括下载JDK、安装JDK、配置环境变量、验证JDK配置以及可选地设置PowerSh... 目录步骤 1:下载JDK步骤 2:安装JDK步骤 3:配置环境变量1. 编辑~/.zshrc(对于zsh

SpringBoot集成Milvus实现数据增删改查功能

《SpringBoot集成Milvus实现数据增删改查功能》milvus支持的语言比较多,支持python,Java,Go,node等开发语言,本文主要介绍如何使用Java语言,采用springboo... 目录1、Milvus基本概念2、添加maven依赖3、配置yml文件4、创建MilvusClient

JS+HTML实现在线图片水印添加工具

《JS+HTML实现在线图片水印添加工具》在社交媒体和内容创作日益频繁的今天,如何保护原创内容、展示品牌身份成了一个不得不面对的问题,本文将实现一个完全基于HTML+CSS构建的现代化图片水印在线工具... 目录概述功能亮点使用方法技术解析延伸思考运行效果项目源码下载总结概述在社交媒体和内容创作日益频繁的

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各