之前是论文中看到PCA,有了一个简单了解,还写了一篇入门笔记:PCA主成分分析
直到前一阵帮做实验用到PCA,才发现之前的理解实在是too young too naive…
1 基于opencv的几种PCA实现
故事是这样的,有一天,我拿到一个5000个样本的数据集,每个样本用一个30w维的特征向量描述,被要求对特征降维到3w维。简单复习了下之前的入门笔记,便哼哧哼哧开始coding了。
1.1 使用cvCalcPCA
我查到opencv中已经有函数cvCalcPCA和cvProjectPCA,大喜,于是很快写出了如下的代码:
cv::Mat data;
LoadData(inputFile, data , nSamples, 0, 0);
int nDim = data.cols;
CvMat tmpD = data;
CvMat* pData;
pData = &tmpD;
CvMat* pMean = cvCreateMat(1,nDim,CV_32FC1);
CvMat* pEigVectors = cvCreateMat(nComponents,nDim,CV_32FC1);
CvMat* pEigValues = cvCreateMat(1,nComponents,CV_32FC1);
cvCalcPCA(pData,pMean,pEigValues,pEigVectors,CV_PCA_DATA_AS_ROW);
cvProjectPCA(pData,pMean,pEigVectors,pResult);
cv::Mat tmpResult(pResult->rows, pResult->cols, CV_32FC1, pResult->data.fl);
cvReleaseMat(&pResult);
WriteData(pcaResultFile, tmpResult);
运行程序,发现,在opencv的matmul.cpp中函数cvCalcPCA的实现中,这一句:
CV_Assert( (evals0.cols == 1 || evals0.rows == 1) &&
ecount0 <= ecount &&
evects0.cols == evects.cols &&
evects0.rows == ecount0 );
第二句报错,意思是已创建的eignvalue数组和程序计算得到的eignvalue数组大小不一致。而当设nComponents小于5000时,程序不报错。
1.2 使用cv::PCA
看到在cvCalcPCA中封装了cv::PCA,于是把cv::PCA单独拎出来,想摆脱那句Assert,就有了如下的代码:
cv::PCA pca(data,noArray(),CV_PCA_DATA_AS_ROW,nComponents);
FileStorage outputPca(pcaMatrixFile,FileStorage::WRITE);
pca.write(outputPca);
cv::Mat evects = pca.eigenvectors;
cv::Mat result = pca.project(data);
FileStorage outputResult(pcaResultFile,FileStorage::WRITE);
outputResult << "result" << result;
发现这种写法舒服太多,首先,直接用Mat,省去了向CvMat*转换的麻烦,其次,这个pca.write()函数可直接将特征向量特征值等信息写入同个xml文件。
然而不幸的是,只要nComponents超过样本个数(5000),程序输出的结果都是5000维的,我们本来期望是nComponents维,也就是3w维。
在pca.cpp中查看其实现,发现,当样本维度D大于样本个数N时,使用“scrambled”方法计算pca,设A为输入样本按行存储,A大小为N×D: