PCA方法從原理到實現


一、簡介 

       PCA(Principal Components Analysis)即主成分分析,是圖像處理中經常用到的降維方法,大家知道,我們在處理有關數字圖像處理方面的問題時,比如經常用的圖像的查詢問題,在一個幾萬或者幾百萬甚至更大的數據庫中查詢一幅相近的圖像。這時,我們通常的方法是對圖像庫中的圖片提取響應的特征,如顏色,紋理,sift,surf,vlad等等特征,然后將其保存,建立響應的數據索引,然后對要查詢的圖像提取相應的特征,與數據庫中的圖像特征對比,找出與之最近的圖片。這里,如果我們為了提高查詢的准確率,通常會提取一些較為復雜的特征,如sift,surf等,一幅圖像有很多個這種特征點,每個特征點又有一個相應的描述該特征點的128維的向量,設想如果一幅圖像有300個這種特征點,那么該幅圖像就有300*vector(128維)個,如果我們數據庫中有一百萬張圖片,這個存儲量是相當大的,建立索引也很耗時,如果我們對每個向量進行PCA處理,將其降維為64維,是不是很節約存儲空間啊?對於學習圖像處理的人來說,都知道PCA是降維的,但是,很多人不知道具體的原理,為此,我寫這篇文章,來詳細闡述一下PCA及其具體計算過程:

二、PCA原理

1、原始數據:

為了方便,我們假定數據是二維的,借助網絡上的一組數據,如下:

x=[2.5, 0.5, 2.2, 1.9, 3.1, 2.3, 2, 1,1.5, 1.1]T
y=[2.4, 0.7, 2.9, 2.2, 3.0, 2.7, 1.6, 1.1, 1.6, 0.9]T

2、計算協方差矩陣

什么是協方差矩陣?相信看這篇文章的人都學過數理統計,一些基本的常識都知道,但是,也許你很長時間不看了,都忘差不多了,為了方便大家更好的理解,這里先簡單的回顧一下數理統計的相關知識,當然如果你知道協方差矩陣的求法你可以跳過這里。

(1)協方差矩陣:

首先我們給你一個含有n個樣本的集合,依次給出數理統計中的一些相關概念:

均值:            


標准差:    


方差:     

既然我們都有這么多描述數據之間關系的統計量,為什么我們還要用協方差呢?我們應該注意到,標准差和方差一般是用來描述一維數據的,但現實生活我們常常遇到含有多維數據的數據集,最簡單的大家上學時免不了要統計多個學科的考試成績。面對這樣的數據集,我們當然可以按照每一維獨立的計算其方差,但是通常我們還想了解這幾科成績之間的關系,這時,我們就要用協方差,協方差就是一種用來度量兩個隨機變量關系的統計量,其定義為:

                                                                                      

從協方差的定義上我們也可以看出一些顯而易見的性質,如:

 

需要注意的是,協方差也只能處理二維問題,那維數多了自然就需要計算多個協方差,比如n維的數據集就需要計算CN2【此乃組合數基本公式】個協方差,那自然而然的我們會想到使用矩陣來組織這些數據。給出協方差矩陣的定義:

                                 

這個定義還是很容易理解的,我們可以舉一個簡單的三維的例子,假設數據集有三個維度{x,y,z},則協方差矩陣為

                          

可見,協方差矩陣是一個對稱的矩陣,而且對角線是各個維度上的方差。

(2)協方差矩陣的求法:

協方差矩陣計算的是不同維度之間的協方差,而不是不同樣本之間的。下面我們將在matlab中用一個例子進行詳細說明:

首先,隨機產生一個10*3維的整數矩陣作為樣本集,10為樣本的個數,3為樣本的維數。

 

[cpp]  view plain copy
 
  1. MySample = fix(rand(10,3)*50)  


 

 

根據公式,計算協方差需要計算均值,那是按行計算均值還是按列呢,我一開始就老是困擾這個問題。前面我們也特別強調了,協方差矩陣是計算不同維度間的協方差,要時刻牢記這一點。樣本矩陣的每行是一個樣本,每列為一個維度,所以我們要按列計算均值。為了描述方便,我們先將三個維度的數據分別賦值:

 

dim1 = MySample(:,1);
dim2 = MySample(:,2);
dim3 = MySample(:,3);
%計算dim1與dim2,dim1與dim3,dim2與dim3的協方差:
sum( (dim1-mean(dim1)) .*(dim2-mean(dim2)) ) / ( size(MySample,1)-1 ) %得到  74.5333
sum( (dim1-mean(dim1)) .* (dim3-mean(dim3)) ) / ( size(MySample,1)-1 ) % 得到  -10.0889
sum( (dim2-mean(dim2)) .* (dim3-mean(dim3)) ) / ( size(MySample,1)-1 ) % 得到  -10***000
%搞清楚了這個后面就容易多了,協方差矩陣的對角線就是各個維度上的方差,下面我們依次計算:
std(dim1)^2 % 得到   108.3222
std(dim2)^2 % 得到   260.6222
std(dim3)^2 % 得到  94.1778
%這樣,我們就得到了計算協方差矩陣所需要的所有數據,調用Matlab自帶的cov函數進行驗證:
cov(MySample)

 


 

 

可以看到跟我們計算的結果是一樣的,說明我們的計算是正確的。但是通常我們不用這種方法,而是用下面簡化的方法進行計算:

先讓樣本矩陣中心化,即每一維度減去該維度的均值,然后直接用新的到的樣本矩陣乘上它的轉置,然后除以(N-1)即可。其實這種方法也是由前面的公式通道而來,只不過理解起來不是很直觀而已。大家可以自己寫個小的矩陣看一下就明白了。其Matlab代碼實現如下:

 1 X = MySample –repmat(mean(MySample),10,1);    %中心化樣本矩陣
 2 C = (X’*X)./(size(X,1)-1)
 3 %為方便對matlab不太明白的人,小小說明一下各個函數,同樣,對matlab有一定基礎的人直接跳過:
 4 %B = repmat(A,m,n )   %%將矩陣 A復制 m×n塊,即把 A 作為 B的元素,B由 m×n個 A平鋪而成。B的維數是 [size(A,1)*m, (size(A,2)*n] 
 5 %B = mean(A)的說明:
 6 %如果你有這樣一個矩陣:A = [1 2 3; 3 36; 4 6 8; 4 7 7];
 7 %用mean(A)(默認dim=1)就會求每一列的均值
 8 % ans =
 9 %    3.0000    4.5000    6.0000
10 % 用mean(A,2)就會求每一行的均值 
11 % ans =
12 %     2.0000
13 %     4.0000
14 %     6.0000
15 %     6.0000
16 size(A,n)%% 如果在size函數的輸入參數中再添加一項n,並用1或2為n賦值,則 size將返回矩陣的行數或列數。其中r=size(A,1)該語句返回的是矩陣A的行數, %c=size(A,2)該語句返回的是矩陣A的列數

 

上面我們簡單說了一下協方差矩陣及其求法,言歸正傳,我們用上面簡化求法,求出樣本的協方差矩陣為:

                                      

                     

3、計算協方差矩陣的特征向量和特征值

因為協方差矩陣為方陣,我們可以計算它的特征向量和特征值,如下:

 [eigenvectors,eigenvalues] = eig(cov)  


           

 

我們可以看到這些矢量都是單位矢量,也就是它們的長度為1,這對PCA來說是很重要的。

4、選擇成分組成模式矢量

求出協方差矩陣的特征值及特征向量之后,按照特征值由大到小進行排列,這將給出成分的重要性級別。現在,如果你喜歡,可以忽略那些重要性很小的成分,當然這會丟失一些信息,但是如果對應的特征值很小,你不會丟失很多信息。如果你已經忽略了一些成分,那么最后的數據集將有更少的維數,精確地說,如果你的原始數據是n維的,你選擇了前p個主要成分,那么你現在的數據將僅有p維。現在我們要做的是組成一個模式矢量,這只是幾個矢量組成的矩陣的一個有意思的名字而已,它由你保持的所有特征矢量構成,每一個特征矢量是這個矩陣的一列。

對於我們的數據集,因為有兩個特征矢量,因此我們有兩個選擇。我們可以用兩個特征矢量組成模式矢量:

                                   

我們也可以忽略其中較小特征值的一個特征矢量,從而得到如下模式矢量:

                                                            

5、得到降維后的數據

                                           

其中rowFeatureVector是由模式矢量作為列組成的矩陣的轉置,因此它的行就是原來的模式矢量,而且對應最大特征值的特征矢量在該矩陣的最上一行。rowdataAdjust是每一維數據減去均值后,所組成矩陣的轉置,即數據項目在每一列中,每一行是一維,對我們的樣本來說即是,第一行為x維上數據,第二行為y維上的數據。FinalData是最后得到的數據,數據項目在它的列中,維數沿着行。

這將給我們什么結果呢?這將僅僅給出我們選擇的數據。我們的原始數據有兩個軸(x和y),所以我們的原始數據按這兩個軸分布。我們可以按任何兩個我們喜歡的軸表示我們的數據。如果這些軸是正交的,這種表達將是最有效的,這就是特征矢量總是正交的重要性。我們已經將我們的數據從原來的xy軸表達變換為現在的單個特征矢量表達。

說明:如果要恢復原始數據,只需逆過程計算即可,即:

                                                      

                                                        

到此為止,相信你已經掌握了PCA的原理了。

 

三 . PCA的應用

      PCA及其改進算法主要應用的人臉識別領域,是人臉識別的經典算法之一。OpenCv2.4以后的版本實現了三種經典的人臉識別算法,其中就包括PCA。對openCv比較老的版本也可以調用PCA的算法去做,只是稍顯復雜而已,網上有一篇博文如下:

http://www.cognotics.com/opencv/servo_2007_series/part_5/index.html

 該代碼運行在openCv2.1之前的版本當中,但是該代碼有個重要的bug就是特征數K被設置為固定的值,而選擇更小的值的時候,代碼會crash。

 

   PCA另外一個主要的用途是作為其他算法的預處理,術語叫做數據的白化。由於PCA具有壓縮數據的作用,所以可以認為經過PCA處理過之后的數據是不相關的,但一般未必是獨立的。實際可用的PCA算法一般不是以解析解的形式給出的,而是在線學習算法。有很多的原因決定了只能使用在線學習算法。在線學習算法主要有基於神經網絡學習的算法和遞歸最小二乘法,相關的文獻如下:

http://wenku.baidu.com/view/c91f31c058f5f61fb73666f8.html

 

要注意的是openCv的實現不是在線學習算法。

 

四.  PCA 的實現

           前面已經談到了PCA的實現分為解析解和在線學習算法。解析解適合於數據量小並且數據完全已知的情況下,這里給出一種高效的解析解的實現代碼。
 
  4.1 數據結構定義及API說明如下:
 1 #ifndef _FCE__PCA__H__
 2 #define _FCE__PCA__H__
 3 
 4 #define HIGH_PRECISON
 5 
 6 #ifdef HIGH_PRECISON
 7 #define real float
 8 #else
 9 #define real double
10 #endif
11 
12 
13 #ifdef _cplusplus
14 {
15 extern "C"
16 #endif
17 
18 
19 typedef struct _FCE_PCA{
20     int count; //the number of sample
21     int n;     // the number of features
22     real *covariance;
23     real *mean;
24     real *z;
25 }FCE_PCA;
26 
27 
28 FCE_PCA *fce_pca_init(int n);
29 
30 void fce_pca_push_add(FCE_PCA *pca, real *v);
31 
32 int  fce_pca_solve_eig(FCE_PCA *pca, real *eigenvector, real *eigenvalue);
33 
34 void fce_pca_free(FCE_PCA *pca);
35 
36 #ifdef _cplusplus
37 }
38 #endif
39 
40 #endif

 

 
           函數 fce_pca_push_add 用於把每一個樣本點添加到PCA模型之中,例如,一個人臉的樣本數據。
           函數 fce_pca_solve_eig 采用雅克比迭代法快速求解對稱矩陣的特征值和特征向量,其它兩個函數分別用以創建PCA模型和釋放PCA模型。
 
4.2  各函數的實現
  1 #include "fce_pca.h"
  2 
  3 
  4 #define FCE_MIN(i,j)   (((i) > (j)) ? (j) : (i))
  5 #define FCE_MAX(i,j)   (((i) > (j)) ? (i) : (j))
  6 
  7 FCE_PCA *fce_pca_init(int n){
  8     FCE_PCA *pca;
  9     real zero = 0.0;
 10     if(n <= 1)
 11         return NULL;
 12 
 13     pca = (FCE_PCA* )malloc(sizeof(FCE_PCA));
 14     if (pca == NULL){
 15         return NULL;
 16     }
 17     
 18     pca->n = n;
 19     pca->z = (real* )malloc(sizeof(*pca->z) * n);
 20     if (pca->z == NULL){
 21         free(pca);
 22         return NULL;
 23     }
 24     
 25     memset(pca->z, zero, sizeof(*pca->z) * n);
 26 
 27     pca->count=0;
 28     pca->covariance= (real* )malloc(sizeof(real) * n * n);
 29     if (pca->covariance == NULL){
 30         free(pca->z);
 31         free(pca);
 32         return NULL;
 33     }
 34     
 35     memset(pca->covariance, zero, sizeof(real) * n * n);
 36 
 37     pca->mean = (real* )malloc(sizeof(real) * n);
 38     if (pca->mean == NULL){
 39         free(pca->covariance);
 40         free(pca->z);
 41         free(pca);
 42         return NULL;
 43     }
 44     
 45     memset(pca->mean, zero, sizeof(real) * n);
 46 
 47     return pca;
 48 }
 49 
 50 void fce_pca_free(FCE_PCA *pca){
 51     free(pca->covariance);
 52     free(pca->mean);
 53     free(pca->z);
 54     free(pca);
 55 }
 56 
 57 void fce_pca_push_add(FCE_PCA *pca, real *v){
 58     int i, j;
 59     const int n = pca->n;
 60     for(i = 0; i < n; i++){
 61         pca->mean[i] += v[i];
 62         for(j = i; j < n; j++)
 63             pca->covariance[j + i * n] += v[i]*v[j];
 64     }
 65     pca->count++;
 66 }
 67 
 68 int fce_pca_solve_eig(FCE_PCA *pca, real *eigenvector, real *eigenvalue){
 69     int i, j, pass;
 70     int k = 0;
 71     const int n = pca->n;
 72     real *z = pca->z;
 73     real zero = 0.0;
 74 
 75     memset(eigenvector, zero, sizeof(real)*n*n);
 76 
 77     for(j = 0; j < n; j++){
 78         pca->mean[j] /= pca->count;
 79         eigenvector[j + j * n] = 1.0;
 80         for(i = 0; i <= j; i++){
 81             pca->covariance[j + i * n] /= pca->count;
 82             pca->covariance[j + i * n] -= pca->mean[i] * pca->mean[j];
 83             pca->covariance[i + j * n] = pca->covariance[j + i * n];
 84         }
 85         eigenvalue[j] = pca->covariance[j + j*n];
 86         z[j] = 0;
 87     }
 88 
 89     for(pass=0; pass < 50; pass++){
 90         real sum = 0;
 91         for(i = 0; i < n; i++)
 92             for(j = i+1; j < n; j++)
 93                 sum += fabs(pca->covariance[j + i * n]);
 94 
 95         if(sum == 0){
 96             for(i = 0; i < n; i++){
 97                 real maxvalue = -1;
 98                 for(j = i; j < n; j++){
 99                     if(eigenvalue[j] > maxvalue){
100                         maxvalue = eigenvalue[j];
101                         k= j;
102                     }
103                 }
104                 eigenvalue[k] = eigenvalue[i];
105                 eigenvalue[i] = maxvalue;
106                 for(j = 0; j < n; j++){
107                     real tmp = eigenvector[k + j * n];
108                     eigenvector[k + j * n] = eigenvector[i + j * n];
109                     eigenvector[i + j * n] = tmp;
110                 }
111             }
112             return pass;
113         }
114 
115         for(i = 0; i < n; i++){
116             for(j = i + 1; j < n; j++){
117                 real covar = pca->covariance[j + i * n];
118                 real t,c,s,tau,theta, h;
119 
120                 if(pass < 3 && fabs(covar) < sum / (5*n*n)) 
121                     continue;
122                 if(fabs(covar) <= 0.00000000001) 
123                     continue;
124                 if(pass >=3 && fabs((eigenvalue[j]+z[j])/covar) > (1LL<<32) && fabs((eigenvalue[i]+z[i])/covar) > (1LL<<32)){
125                     pca->covariance[j + i * n]=0.0;
126                     continue;
127                 }
128 
129                 h = (eigenvalue[j] + z[j]) - (eigenvalue[i] + z[i]);
130                 theta = 0.5 * h/covar;
131                 t = 1.0 /(fabs(theta) + sqrt(1.0 + theta * theta));
132                 if(theta < 0.0) t = -t;
133 
134                 c = 1.0 /sqrt(1 + t * t);
135                 s = t * c;
136                 tau = s /(1.0 + c);
137                 z[i] -= t * covar;
138                 z[j] += t * covar;
139 
140 #define ROTATE(a,i,j,k,l) {\
141     real g =a[j + i*n];\
142     real h =a[l + k*n];\
143     a[j + i*n] = g - s * (h + g * tau);\
144     a[l + k*n] = h + s * (g - h * tau); }
145                 for(k = 0; k < n; k++) {
146                     if(k != i && k != j){
147                         ROTATE(pca->covariance,FCE_MIN(k,i),FCE_MAX(k,i),FCE_MIN(k,j),FCE_MAX(k,j))
148                     }
149                     ROTATE(eigenvector,k,i,k,j)
150                 }
151                 pca->covariance[j + i * n]=0.0;
152             }
153         }
154         for (i = 0; i < n; i++) {
155             eigenvalue[i] += z[i];
156             z[i]=0.0;
157         }
158     }
159 
160     return  0;
161 }

 

 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM