PCA基於opencv的實現和樣本數小於維度的問題

之前是論文中看到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:

B=AA;Bx=bx;C=AA;
Cy=cy>AAy=cy>AA(Ay)=c(Ay)>c=b,x=Ay

因爲C只有N個特徵值,所以B也只有N個特徵值,也就是隻有N個components。爲什麼這樣呢?不懂。

1.3 使用eigen

只好捨棄opencv現成的函數,用eigen函數算特徵向量和特徵值,其他運算直接DIY,好在opencv的eigen函數返回的特徵向量是按特徵值降序排好了的,於是又有了如下的代碼:

void MyPCACalcEvects( const Mat &_data, Mat &eigenvalues, Mat &eigenvectors  )
{

    Mat data =  cv::Mat_<float>(_data);

    int N = data.rows;
    int D = data.cols;

    // mean
    Mat m = Mat::zeros( 1, D, data.type() );

    for ( int j=0; j<D; j++ )
    {
        for ( int i=0; i<N; i++ )
        {
            m.at<float>(0,j) += data.at<float>(i,j);
        }
        m.at<float>(0,j) /= N;
    }

    WriteData("mu.txt",m);

    Mat S =  Mat::zeros( N, D, data.type() );
    for ( int i=0; i<N; i++ )
    {
        for ( int j=0; j<D; j++ )
        {
            S.at<float>(i,j) = data.at<float>(i,j) - m.at<float>(0,j);
        }
    }

    Mat C = S.t() * S /(N-1);
    eigen( C, eigenvalues, eigenvectors );
}

但是問題又來了,對於我的task,這裏計算出的特徵向量在內存中放不下,如果要在計算特徵向量的過程中寫外存,就要自己DIY一個寫外存的SVD分解。。並且用小的數據測試有一個key observation:輸出的nComponents個特徵值和特徵向量,發現第N個之後的特徵值都爲0了。這麼說來之前那種”scrambled”的方法好像是有道理的,可這又是爲什麼呢?

1.4 Online PCA

就在一籌莫展的時候,發現了一種online PCA,就是將樣本逐個輸入,降維結果逐個輸出,pca映射矩陣是每次都在更新的,也就是說,每個樣本降維時,用的映射矩陣都不一樣。

2 PCA的病態問題

在讀online PCA算法的論證時,忽然想到以前看到的,PCA降維的過程,實際上就是每一次向着能最大程度保持樣本variance(差異性)的一個主軸方向投影,我們假設有N個D維樣本,根據之前的實驗結果,在第N次投影后,特徵值爲0了,也即投影后樣本方差爲0了,那麼投影后樣本沒了差異性,就不能再投影了。

當然這只是自己的一個初步的想法,去stack exchange翻了一些大牛們比較嚴謹的解釋,才弄明白這個困擾了一個星期的問題到底是怎麼回事。

假設在3維空間中有N=2個點,N個點來自維度爲N-1的manifold,因爲2個點必然在一條直線上,即這2個點得到1維子空間,它們的方差是“spread”在這個1維子空間上的,和樣本的維度並沒有什麼關係。也就是說,N個樣本的方差是spread在N-1維子空間上,即只有N-1個components。

舉個例子來說,有兩個點(1,1,1),(2,2,2),根據PCA的投影步驟:
(1)找到新的座標系統的原點(1.5,1.5,1.5)
(2)兩個點到新座標軸的投影距離儘可能小,此處爲0
(3)第一個component將是從(0,0,0)到(3,3,3)的直線,這個直線是使樣本點投影到它後方差最大的一條線
(4)第二個component方向必須和第一個component正交,這條線可能是從(0,0,3)到(3,3,0),或者(0,3,0)到(3,0,3),但無論向誰投影,這兩個點投影后它們的方差爲0.
當樣本數小於樣本維度時,出現的這種現象就是“the curse of dimensionality”

另外,有大牛推薦了PLSR(Partial Least Squares Regression),也就是偏最小二乘迴歸做成分分析,號稱結合了PCA和CCA的優點,且components不用正交,細節還不清楚,先開個坑以後用到再細看吧。


Reference
[1] A tutorial on Principal Components Analysis
[2] Making sense of principal component analysis, eigenvectors & eigenvalues
[3] PCA when the dimensionality is greater than the number of samples
[4] Why are there only n−1 principal components for n data points if the number of dimensions is larger than n?
[5] Partial Least Squares Regression

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章