C++ 实现PCA算法
1.公式
用二维数据举例
白数据:举个例子,我们有一组二维平面上的点 ,用 表示, 的分布符合正态分布,则这一组数据 则被称为白数据
对于其他各方向分量均值为0的来说数据来说,通过拉伸和旋转这种数据可以被分解为
对于多个维度一般的可以得到公式
协方差 :
问题有来了为什么是?这和无偏估计有关,目前我个人暂且给不出证明
协方差矩阵: ,如果数据的平均值为0的话可以推理得到
协方差矩阵的特征值就是对应轴向的方差,特征向量就是坐标轴方向,证明如下:
白数据协方差矩阵为单位矩阵
,令
又因为旋转矩阵是正交矩阵,对于正交矩阵来说 则可推
令
实际上由特征向量公式 , 可得到,的特征向量的集合就是旋转矩阵,的特征值的集合就是方差
协方差矩阵是对称矩阵,自然而然的我们想到了Jacobi求特征值和特征向量
我们应该实现的求协方差矩阵特征向量的方法 jacobi.h
参考之前写的Jacobi方法:如何求对称矩阵的特征值和特征向量
2.代码实现
//mat.h 偷懒了懒得自己写了用嵌套vector
#pragma once
#include<vector>
#include<iostream>
typedef std::vector<std::vector<double>> mat_d;
void print(mat_d mat) {
for (int i = 0; i < mat.size(); i++) {
for (int j = 0; j < mat[0].size(); j++) {
printf("%+5.3f\t", mat[i][j]);
}
printf("\n");
}
printf("\n");
}
mat_d get_mat_d(int i, int j) {
return std::vector<std::vector<double>>
(i, std::vector<double>(j, 0));
}
mat_d matmul(mat_d a, mat_d b) {
if (a[0].size() != b.size()) {
printf("Dim-matmul\n");
exit(-1);
};
int m = a.size();
int n = a[0].size();
int p = b[0].size();
mat_d res = get_mat_d(m, p);
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
for (int k = 0; k < p; k++) {
res[i][k] += a[i][j] * b[j][k];
}
}
}
return res;
}
mat_d transpose(mat_d a) {
mat_d res = get_mat_d(a[0].size(), a.size());
for (int i = 0; i < a.size(); i++) {
for (int j = 0; j < a[0].size(); j++) {
res[j][i] = a[i][j];
}
}
return res;
}
mat_d mul(mat_d a, double t) {
mat_d r = a;
for (int i = 0; i < a.size(); i++) {
for (int j = 0; j < a[0].size(); j++) {
r[i][j] = r[i][j] * t;
}
}
return r;
}
mat_d cov(mat_d a) {
mat_d b = transpose(a);
mat_d r = matmul(b,a);
if (a.size() < 2) {
printf("Dim\n");
exit(-1);
}
r = mul(r, 1.0 / (a.size() - 1));
return r;
}
PCA 算法主体
#pragma once
#include<algorithm>
#include"jacobi.h"
#include"mat.h"
/*
PCA降维实现
batch: 输入行特征行数目
feature: 输入特征列数目
Data: 输入特征的列方向平均后的数据
TargetData: 返回的目标数据
Cov: 协方差矩阵
idx: 存方差最大的方向
*/
class PCA
{
private:
using mat_d =
std::vector<std::vector<double>>;
struct Vec2d {
int x;
int y;
};
int batch;
int feature;
mat_d Data;
mat_d TargetData;
mat_d Cov;
mat_d EigenVal;
mat_d EigenVec;
std::vector<Vec2d> idx;
private:
mat_d mean(mat_d);
void FindMaxFeatue();
public:
PCA(mat_d&, int);
mat_d getPCA();
~PCA() {}
};
PCA::PCA(mat_d& input, int target_dim) {
//输入特征都是列向量 (批次,特征) 需要降低到的特征维度
Data = input;
Data = mean(Data);
Cov = cov(Data);
Jacobi jac(Cov, 1e-10, 100);
EigenVal = jac.EigenVal();
EigenVec = jac.EigenVec();
Data = matmul(Data,EigenVec);
FindMaxFeatue();
TargetData = get_mat_d(Data.size(),0);
for (int j = 0; j < target_dim; j++) {
int cur_col = idx[j].x;
for (int i = 0; i < Data.size(); i++) {
TargetData[i].push_back(Data[i][cur_col]);
}
}
}
mat_d PCA::mean(mat_d data) {
//求列方向上的平均值
mat_d res = data;
batch = data.size();
feature = data[0].size();
std::vector<double> mean(feature);
for (int i = 0; i < batch; i++) {
for (int j = 0; j < feature; j++) {
mean[j] += data[i][j];
}
}
for (int i = 0; i < feature; i++) {
mean[i] = mean[i] / batch;
}
for (int i = 0; i < batch; i++) {
for (int j = 0; j < feature; j++) {
res[i][j] = data[i][j] - mean[j];
}
}
return res;
}
void PCA::FindMaxFeatue() {
for (int i = 0; i < EigenVal.size(); i++) {
Vec2d temp;
temp.x = i;
temp.y = EigenVal[i][i];
idx.push_back(temp);
}
std::sort(idx.begin(), idx.end(),
[](Vec2d a, Vec2d b) {
return a.y > b.y;
});
}
mat_d PCA::getPCA() {
return TargetData;
}