使用Eigen库进行主成分分析

8
我正在尝试使用Eigen在C++中从数据集计算2个主要的主成分。我目前的做法是将数据标准化为[0,1]之间,并使其居中。之后,我计算协方差矩阵并对其进行特征值分解。我知道SVD更快,但是我对计算出的主成分感到困惑。
以下是我如何实现它的主要代码(其中traindata是我的MxN大小的输入矩阵):
Eigen::VectorXf normalize(Eigen::VectorXf vec) {
  for (int i = 0; i < vec.size(); i++) { // normalize each feature.
      vec[i] = (vec[i] - minCoeffs[i]) / scalingFactors[i];
  }
  return vec;
}

// Calculate normalization coefficients (globals of type Eigen::VectorXf). 
maxCoeffs = traindata.colwise().maxCoeff();
minCoeffs = traindata.colwise().minCoeff();
scalingFactors = maxCoeffs - minCoeffs;

// For each datapoint.
for (int i = 0; i < traindata.rows(); i++) { // Normalize each datapoint.
  traindata.row(i) = normalize(traindata.row(i));
}

// Mean centering data.
Eigen::VectorXf featureMeans = traindata.colwise().mean();
Eigen::MatrixXf centered = traindata.rowwise() - featureMeans;

// Compute the covariance matrix.
Eigen::MatrixXf cov = centered.adjoint() * centered;
cov = cov / (traindata.rows() - 1);

Eigen::SelfAdjointEigenSolver<Eigen::MatrixXf> eig(cov);
// Normalize eigenvalues to make them represent percentages.
Eigen::VectorXf normalizedEigenValues =  eig.eigenvalues() / eig.eigenvalues().sum();


// Get the two major eigenvectors and omit the others.
Eigen::MatrixXf evecs = eig.eigenvectors();
Eigen::MatrixXf pcaTransform = evecs.rightCols(2);


// Map the dataset in the new two dimensional space.
traindata = traindata * pcaTransform;

这段代码的结果大致如下:

enter image description here

为了确认我的结果,我尝试使用WEKA进行相同的操作。所以我按照这个顺序使用了归一化和居中过滤器,然后是主成分过滤器,并保存并绘制输出。结果如下:

enter image description here

从技术上讲,我应该做同样的事情,但结果却非常不同。有人能看出我是否犯了错误吗?

有一件事要补充:我非常确定WEKA正在使用SVD。但这不应该解释结果的差异,对吧? - Chris
我知道这个问题比较老了,但您是否知道您用于此实现的论文(或其他来源)是哪一篇? - Koenigsberg
我想我只是在维基百科和其他通过搜索找到的多个页面上查阅了它。但并没有参考科学论文。但如果你需要更可靠的来源,我相信你可以在很多数学/统计/数据分析书籍中找到这个过程。 - Chris
我确实在寻找这个特定的资源。谢谢您的快速回复。 - Koenigsberg
2个回答

7
当缩放到0,1时,您修改了局部变量vec,但忘记更新traindata
此外,这可以更轻松地完成:
RowVectorXf minCoeffs = traindata.colwise().maxCoeff();
RowVectorXf minCoeffs = traindata.colwise().minCoeff();
RowVectorXf scalingFactors = maxCoeffs - minCoeffs;
traindata = (traindata.rowwise()-minCoeffs).array().rowwise() / scalingFactors.array();

也就是说,使用行向量和数组特性。
还要补充一点,对称特征值分解实际上比SVD更快。在这种情况下,SVD的真正优势在于它避免了平方项,但由于您的输入数据已经被归一化和居中,并且您只关心最大的特征值,所以这里没有精度问题。

那是我的一个错误,在我的大代码中,我做了一个返回值的函数调用,就像这样:traindata.row(i) = normalize(traindata.row(i));。我在这里进行了更改,以确保没有错误。感谢您简化代码,我猜想它可能有其他问题。您能看到另一个问题吗? - Chris
另一个问题,当我运行你的代码时,我的编译器告诉我“没有匹配的'operator/'”。我有Eigen3,似乎没有按行除法或者? - Chris
如果您的1个特征的最大值等于最小值,那么这将引发除以零的错误,我说得对吗? - Jan Hackenberg
你需要在scalingFactors中将零替换为一。 - ggael

2

原因是Weka对数据集进行了标准化。这意味着它将每个特征的方差缩放到单位方差。当我这样做时,图形看起来相同。从技术上讲,我的方法也是正确的。


请问您能否提供运行代码?谢谢。 - Jan Hackenberg
我会看一下,不确定我是否仍然可以访问那段代码以及最终使用的版本。但我肯定谈到了经典标准化,这是明确定义的:https://en.wikipedia.org/wiki/Feature_scaling#Standardization - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接