PCA主成分分析的MATLAB和Eigen实现以及应用

目前在项目中需要对模型的位置进行矫正,想到了使用主成分分析,提取主方向,将模型方向进行变换得到正确的方向。以下对PCA过程首先在matlab中进行实现,然后在C++矩阵库Eigen中实现,总体较为简单。

PCA实现和应用

PCA理论

PCA(Principal component analysis)主成分分析, 是一种统计方法。通过正交变换将一组可能存在相关性的变量转换为一组线性不相关的变量,转换后的这组变量叫主成分[参考百度百科]。通俗来讲,PCA就是将数据从一个空间映射到一个各个维度不相关的一个空间,这样就提取了主方向。
比如下面的数据:
这里写图片描述
如果以xy两维【垂直和水平的轴】,来描述数据,我们发现数据相差较近,方差较小,不易区分。因此我们求取主成分,变换后的空间的主轴是如图所示的椭圆的两个轴。如果压缩到一维的空间,我们可以只选取长轴,这样也尽可能多的表达原数据的方差;如果选取两个轴,我们将各个方向的方差达到了最大,将原数据尽可能区分。

计算协方差矩阵

进行PCA计算的第一步是计算协方差矩阵,协方差是数据每一个维度之间的相互关系,计算公式如下:
这里写图片描述
我们可以应用以上任意一个公式进行计算,但注意样本方差和总体方差的转化【会在后面代码中提示,但博主也不完全清楚其中的理论==】

PCA过程

  1. 计算协方差矩阵的特征向量和特征值
  2. 对特征向量进行排序,降序排序
  3. 按照特征值的顺序对特征向量进行排序,组成变换矩阵【这里我们可以选取需要保留的维度个数】
  4. 对原数据进行变换

MATLAB实现

求协方差矩阵

这里提供了三种方式:
1. 使用matlab自带的求协方差矩阵
2. 分别使用以上的两个公式

// 初步展示
data = rand(10,3);      % 生成103列的随机数据
c = cov(data);          % 协方差矩阵

// 使用公式1计算的
cov_matrix = zeros(3,3);
for i=1:3
    for j = 1:3
        cov_matrix(i,j) = sum((data(:,i)-mean(data(:,i))).*(data(:,j)-mean(data(:,j))))/(size(data,1)-1);
        cov_matrix(j,i) = cov_matrix(i,j);
    end
end

// 使用公式2计算的,需要将方差进行转化 n/(n-1)
for i=1:3
    for j=1:3
        cov_matrix(i,j) = mean(data(:,i).*data(:,j))-mean(data(:,i))*mean(data(:,j));
        cov_matrix(i,j) = cov_matrix(i,j)*(size(data,1)/(size(data,1)-1));      % 需要对方差进行变换,n/(n-1)
        cov_matrix(j,i) = cov_matrix(i,j);
    end
end

PCA过程

// 这里的data是协方差矩阵
[E,D] = eig(data);
// 求特征值和特征向量,D是特征值,E是特征向量
[dummy,order] = sort(diag(-D));
// 按特征值进行排序,dummy获得的是排列后的数据,order获得的是排序,sort默认是升序,因此用负值
E = E(:,order);
// 按照这个顺序进行排列,获得排序后的特征向量,matlab的矩阵可以按照这个顺序进行取值。如果要用前两个的,可以用order(1:2)
Y = d*E;
//变换原数据,即可得到降维后的效果

Eigen实现

Eigen是一个C++的矩阵库,含有常用的线性代数和统计计算,使用只需要将头文件包含以下就可以了,没有lib,比较方便,博主刚开始使用,不是很熟悉,欢迎指教。

// 原始数据
MatrixXf m(point_cnt, 3);
// 计算协方差矩阵
    Matrix3f cov_matrix;
    for(int i=0;i<3;++i)
        for (int j = 0; j < 3; ++j)
        {           
            VectorXf v1 = m.col(i);
            VectorXf v2 = m.col(j);
            float mean_1 = v1.mean();
            float mean_2 = v2.mean();           
            for (int k = 0; k < v1.size(); ++k)
            {
                v1[k] -= mean_1;
                v2[k] -= mean_2;
            }           
            float sum = v1.dot(v2);
            sum /= 9;
            cov_matrix(i, j) = cov_matrix(j, i) = sum;
        }       

    // 获得特征值和特征向量
    EigenSolver<Matrix3f> es(cov_matrix);
    Vector3f D = es.pseudoEigenvalueMatrix().diagonal();// 特征值
    Matrix3f V = es.pseudoEigenvectors();    // 特征向量
    // 以下过程按照特征值对特征向量进行排序
    vector<pair<int, float>> eigen_value_index;
    for (int i = 0; i < 3; ++i)
        eigen_value_index.push_back(make_pair(i,D[i]));
    auto f = [](pair<int, float> &a, pair<int, float>&b)->bool {return a.second > b.second;  };
    sort(eigen_value_index.begin(), eigen_value_index.end(), f);               // 按照特征值对特征向量进行排序
    Matrix3f tranform_matrix;
    for (int i = 0; i < 3; ++i)
    {
        int idx = eigen_value_index[i].first;
        tranform_matrix.col(i) = V.col(idx);
    }   

    // 变换后的结果
    auto res = m*V;

实现效果图

目标是对模型的朝向进行矫正,这里是矫正结果,上边是原图,下边是矫正结果。
这里写图片描述

这里写图片描述

github地址

如有错误,欢迎指出~

猜你喜欢

转载自blog.csdn.net/hu694028833/article/details/80320482