基於SIFT+Kmeans+LDA的圖片分類器的實現


PS: 很久沒做CV的事情了,這是很早以前剛入門時候的一篇,以后再有CV相關工作會發布在新的個人站點:http://my.phirobot.com/blog/category/cv.html CV分類下。


 

 posted @ 2012-04-24 20:36 from [FreedomShe]

題記:2012年4月1日回到家,南大計算機研究僧復試以后,等待着的就是獨坐家中無聊的瀟灑。不知哪日,無意中和未來的同學潘潘聊到了圖像處理,聊到了她的論文《基於LDA的行人檢測》,出於有一年半工作經驗的IT男人的本能,就一起開始學習研究這篇“論文”了。眾所周知,老師給學生設置論文題目的,起初都是很模糊的——自己沒有思考清楚實踐上的可行性和具體思路,僅從理論了解上就給學生設置一些“難以實現”的論文任務。幾經修改和商討,最后的論文實際上就是“基於SIFT+Kmeans+LDA的圖片分類器的實現”了。至此,代碼已經編寫完畢,圖片分類的效果還算滿意。

——copyright:由於是一起學習研究的結果,相關所有內容潘潘童鞋可以以第一作者身份使用!

1. 實現思路
2. 軟件環境
3. Step1——SIFT應用
4. Step2——Kmeans應用
5. Step3——數詞頻的實現
6. Step4——LDA應用
7. 參考


一、實現思路

  分類器的功能是:輸入一組圖片,給定需要分類的類別數lda_k(>1);輸出lda_k個文件夾,每個文件夾內的圖片為一類圖片。

  第一步是SIFT特征提取:輸入圖片,輸出圖片的特征點集,即feature列表,每個feature代表一個圖片的某個局部特征,每個feature的數據結構由一個128維浮點數組表示。至此,可以將一幅圖片轉換成一個feature集。

  第二步是Kmeans聚類:輸入是所有圖片的feature集的綜合,給定參數km_k代表需要聚類的類別數;輸出是km_k個feature,在LDA的視角看來就是“單詞表”,用“單詞表”中的一個“單詞”(類中的質心feature)代表kmeans聚類里面一類的所有feature。

  第三步是統計詞頻:(對每個圖片)輸入是圖片的feature集和“單詞表”,分別計算該圖片feature集中每個feature對應的“單詞”,並統計每個“單詞”在該feature集中出現的次數即詞頻;輸出是詞頻統計數據。

  最后一步是LDA訓練潛在主題:輸入是所有圖片文件的詞頻統計數據,以及給定的需要訓練出來的主題類別數lda_k;LDA輸出參數較多,其中最有用的就是文檔-主題條件概率矩陣(theta矩陣),即舉證中每個元素表示P(主題k|文檔m)——在文檔m中,主題是k的概率——通過該概率即可判斷當前文檔最可能的主題,實現了將所有文檔分類為lda_k個主題。

  總之,理論上LDA研究的實體是一組文檔,每個文檔由若干單詞組成,通過無監督學習,能夠發現lda_k個主題,並且確定theta矩陣——文檔確定的情況下生成主題k的概率,以及phi矩陣——主題確定的情況下生成單詞v的概率。分類器通過SIFT算法將圖片轉換為若干feature,即將圖片看成是“文檔”feature看成是“單詞”。而僅通過SIFT處理后的feature並不能直接單做“單詞”作為LDA的輸入,因為幾乎每個feature都不一樣,還需要Kmeans算法對所有圖片的feature集的總和做一次聚類,得到km_k個類別的中心feature,即生成km_k個“單詞”的“單詞表”,並以此中心feature代替一個類別內的所有其他feature,從而將一個圖片“文檔”中的所有feature均在“單詞表”中能夠找到代表它的“單詞”,這樣圖片就真正轉換為了LDA能夠處理的“文檔”。

[Go Top]


二、軟件環境

  VS2010,MFC,C++。
  安裝並配置Opencv,參見VS2010+Opencv-2.4.0的配置攻略
  下載並集成SIFT源碼,參見在VS2010中應用SIFT(C)源碼
  下載並集成LDA源碼,參見在VS2010中應用LDA(C)源碼

  Kmeans為Opencv自帶函數,無需應用其他源碼。建立好自己的工程,集成算法源碼后,工程文件夾大致結構應為下圖所示:

  設計好自己的例程界面,並關聯好響應函數和成員變量,本例程界面如下:

[Go Top]


三、Step1——SIFT應用

  在該步驟內,程序依據“圖片源目錄”給出的圖片目錄路徑,掃描目錄內的所有圖片文件,對每個執行如下操作:

...
n = _sift_features(img, &features, SIFT_INTVLS/*3*/, SIFT_SIGMA/*1.6*/, SIFT_CONTR_THR/*0.04*/,
    SIFT_CURV_THR/*10*/, SIFT_IMG_DBL/*1*/, SIFT_DESCR_WIDTH/*4*/, SIFT_DESCR_HIST_BINS/*8*/); //SIFTfeature提取
...
export_features(out_file_name, features, n); //將features導出為文件
...
if(勾選了“保存SIFT特征圖”)
{
  draw_features(img, features, n); //在img圖片上標記出features
  cvSaveImage(out_img_name, img, NULL); //將標記后的圖片保存
}
...

  其中最主要的三個函數就是_sift_features(…), export_features(…), draw_features(…)均為sift源碼所提供。(注:feature有兩種類型——OXFD和LOWE,本程序只涉及LOWE類型,所有OXFD相關格式均自動忽略。)

  _sift_features(…)函數第一個參數img為傳入圖片的IplImage指針格式,為Opencv所定義的圖片數據結構;features后面的參數均為SIFT算法的輸入參數,具體含義見作者的源碼注釋。

需要注意和理解的是features這個參數,其指向的為一個結構體feature的數組,feature結構為:

/**
Structure to represent an affine invariant image feature.  The fields
x, y, a, b, c represent the affine region around the feature:
a(x-u)(x-u) + 2b(x-u)(y-v) + c(y-v)(y-v) = 1
*/
struct feature
{
    double x;                      /**< x coord */
    double y;                      /**< y coord */
    double a;                      /**< Oxford-type affine region parameter */
    double b;                      /**< Oxford-type affine region parameter */
    double c;                      /**< Oxford-type affine region parameter */
    double scl;                    /**< scale of a Lowe-style feature */
    double ori;                    /**< orientation of a Lowe-style feature */
    int d;                         /**< descriptor length */
    double descr[FEATURE_MAX_D];   /**< descriptor */
    int type;                      /**< feature type, OXFD or LOWE */
    int category;                  /**< all-purpose feature category */
    struct feature* fwd_match;     /**< matching feature from forward image */
    struct feature* bck_match;     /**< matching feature from backmward image */
    struct feature* mdl_match;     /**< matching feature from model */
    CvPoint2D64f img_pt;           /**< location in image */
    CvPoint2D64f mdl_pt;           /**< location in model */
    void* feature_data;            /**< user-definable data */
};

  其中x,y表示feature在圖片中的坐標,scl, ori表示在圖中標記特征的強度和方向,descr是最重要的特征信息即128維的特征向量(FEATURE_MAX_D==128)。實際上所保存的特征文件里面也只保存了feature的這些信息。features就是feature的一個數組,包含了SIFT算法所提取的圖片的所有feature,返回值n表示features數組中有多少個feature元素。

  export_features(…)就是將上一步中的features保存為文件,文件格式如下:

441 128
178.616459 111.621902 34.241822 1.292619
 0 0 0 0 0 0 0 0 12 0 0 0 1 3 0 8 140 4 0 0
 0 0 0 30 43 3 0 0 0 0 0 2 0 0 0 0 2 9 0 0
 94 0 0 0 8 72 6 37 172 15 0 0 0 2 1 119 122 5 0 0
 0 0 0 12 0 0 0 0 0 2 0 0 57 5 0 0 2 73 9 14
 172 133 0 0 0 11 3 14 128 45 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 4 1 2 2 4 13 0 1 172 152 2 4 4 6 0 2
 145 116 0 0 0 0 0 0
220.732993 432.886679 20.895674 -1.740551
 6 0 0 1 168 133 17 5 60 0 0 1 37 20 24 25 3 0 0 0
 14 26 17 3 0 0 0 0 0 0 0 0 7 0 0 0 168 144 2 1
 123 0 0 0 80 38 2 15 13 0 0 0 12 44 4 4 0 0 0 0
 0 0 0 0 2 0 0 1 168 67 0 0 121 13 0 2 144 16 0 3
 22 2 0 4 30 7 0 0 0 0 0 0 1 0 0 0 0 0 0 0
 168 81 0 0 87 9 0 1 125 38 2 5 24 2 0 3 20 16 2 2
 0 0 0 0 1 3 0 0
……

  其中第一行的第一個int表示文件中feature的個數,第二個int表示feature的維數。接下來每段132個浮點數(后128個近似為整數)為一個feautre,前4個分別為x, y, scr, ori,后128個為表征feature的128維向量。

  draw_features(…)就是將feature信息示例性地標注在圖上了,圖片如下:

  cvSaveImage(…)是Opencv的函數,將加過標注的圖片保存為文件。

  這一步實際上要做的重點就是輸入為圖片,輸出為SIFT算法后為每張圖片生成的feature文件集:

  這樣每張圖片就相當於一個“文檔”,而feature就是“文檔”中的“預備單詞”。

[Go Top]


四、Step2——Kmeans應用

  Step1里面的feature只是“預備單詞”,在成為單詞之前還要通過Step2生成“單詞表”和Step3將“文檔”中的“預備單詞”找到“單詞表”中最相近的“單詞”替換之(並不是真正操作上的替換,只是當成“單詞表”中的“單詞”統計出來而已)。

  在Step2中,關鍵操作如下:

...
CvMat *samples=cvCreateMat(featureNum, dims, CV_32FC1); //包含所有圖片的所有feature信息的矩陣,featureNum個feature,每個feature為dims(128)維向量,每一維的元素類型為32位浮點數
CvMat *clusters=cvCreateMat(featureNum, 1, CV_32SC1); //每個feature所在“質心”的指針(實際上本例程中沒有用到該信息)
CvMat *centers=cvCreateMat(k, dims, CV_32FC1); //“質心”信息的數組,k個“質心”每個質心都是dims(128)維向量,每一維的元素類型為32位浮點數
cvSetZero(clusters); //將矩陣初始化為0
cvSetZero(centers); //將矩陣初始化為0
while(file.ReadString(strLine)) 
{
    ...
    n = import_features(CIni::CStrToChar(fileName), FEATURE_LOWE, &features); //導入feature文件,n為導入的feature個數
    ...
    //將feature文件內所有feature信息存入samples矩陣結構內
    for(int i = 0; i < n; i++)
    {
        for(int j = 0; j < dims; j++)
        {
            samples->data.fl[temp++] = features[i].descr[j];
        }
    }
}
cvKMeans2(samples, k, clusters,cvTermCriteria(CV_TERMCRIT_EPS,10,1.0), 3, (CvRNG *)0, KMEANS_USE_INITIAL_LABELS, centers); //Kmeans聚類
...
cvSave(CIni::CStrToChar(ini.getWordListFilePath()), centers); //保存單詞表
...

  其中關鍵函數當然是import_features(...)和cvKMeans2(...),前者是sift源碼里的方法,用來導入feature文件使之成為內存數據結構,后者是Opencv里的kmeans算法之一(cvKMeans2(...)內部調用了kmeans(...))。

  在說明他們之前首先要了解Opencv內部通用數據結構——用來存儲矩陣類型的CvMat,其聲明如下:

typedef struct CvMat
{
    int type;
    int step;

    /* for internal use only */
    int* refcount;
    int hdr_refcount;

    union
    {
        uchar* ptr;
        short* s;
        int* i;
        float* fl;
        double* db;
    } data;

#ifdef __cplusplus
    union
    {
        int rows;
        int height;
    };

    union
    {
        int cols;
        int width;
    };
#else
    int rows;
    int cols;
#endif
}

  其中最重要的就是用來存儲元素信息的一維數組data,data為一個聯合體,包含了各種通用類型。data雖然是一維結構,但通過rows, cols標識,可以將data看成二維的矩陣結構,type就標識了元素類型,而step就標識了一行的步長(占用字節數)。
  cvCreateMat(int rows, int cols, int type);包含了三個參數,rows和cols分別表示data二維矩陣的行數和列數,type表示元素的類型。元素類型通過宏來定義,CV_32FC1中的32表示元素的位數,F表示是浮點數,Cn表示通道數,C1表示1通道,C3表示3通道(只有在類似RGB的三元表示一個像素點顏色時才用到多通道,而通道實際上就是說一個元素應該由幾列來表示)。
  有了上面的知識,就知道了samples矩陣是featureNum行,128列的矩陣,而矩陣元素類型為32位浮點數。featureNum的值為所有圖片所有feature的總和,也就是說把所有的feature文件里面的feature信息都讀入到samples矩陣內用來作為Kmeans的輸入。clusters矩陣是featureNum行1列的矩陣,實際上就是featureNum個int元素的數組,每個int元素都相當於一個下標,標識對應feature(一行)屬於centers中的哪個類(“質心”),而本程序里面沒有使用到clusters提供的信息,featuresNum個feature分別對應於哪個“質心”是在Step3算詞頻里面實現的。centers就是保存“單詞表”信息的矩陣了,k行128列的32位浮點矩陣,即k個feature,每個feature都是一類feature的“質心”——該類所有feature中最中間那個(通過歐式距離來計算的)feature。

  關鍵操作來了,import_features(...)就是將feature文件讀入內存,使之成為feature的內存數據結構。
  int import_features(char* filename, int type, struct feature** feat)第一個參數就是feature文件的路徑,第二個參數是feature的類型,本文是FEATURE_LOWE類型,第三個參數就是傳出參數feature數組了,返回值為讀入內存的feature個數。在接下來的操作中就需要把features里面的數據全部拷貝到samples矩陣結構內,這樣所有文件的feature信息都拷貝到samples內后就完成了kmeans傳入數據的工作。

  cvKMeans2(...)是Opencv在kmeans(...)之上封裝的一個函數,參數含義可以參照Hongquan的博客中的《OpenCV中kmean算法的實現》,本文需要說明的幾個參數是:第一個參數samples——傳入所有圖片feature信息的總和;第二個參數k即kmeans的聚類個數(本程序通過界面的類別數設置);第七個參數flags表示生成隨機數的方式,可能每次運行程序對相同的輸入輸出的單詞表都不同,那么就跟這個參數相關;第八個參數centers,這就是我們需要的輸出了,即“單詞表”信息,每一行就是一個feature也就是一個“單詞”。

  這樣,讀入Step1中輸出的所有feature文件,然后綜合起來並輸入參數k(聚類類別數),然后就可以生成一張包含k個“單詞”的“單詞表”了,用Opencv的cvSave(...)函數把這張“單詞表”保存為yml文件,就是本步驟的主要輸出了。輸出“單詞表”文件wordList.yml格式類似:

%YAML:1.0
wordList: !!opencv-matrix
   rows: 80
   cols: 128
   dt: f
   data: [ 1.64593754e+001, 2.11062511e+001, 30.,
       3.48312492e+001, 2.39375000e+001, 1.28156252e+001,
       1.25531254e+001, 1.16812506e+001, 4.92906265e+001,
       3.61625023e+001, 2.94968758e+001, 23., 1.70375004e+001,
       1.12718754e+001, 1.71968746e+001, 3.13437500e+001,
       3.27593765e+001, 2.25156250e+001, 2.02625008e+001,
       2.26406250e+001, 2.73750000e+001, 1.90531254e+001,
       2.41375008e+001, 2.85156250e+001, 1.92875004e+001,
       ............................................. ]

[Go Top]


五、Step3——數詞頻的實現

  前面已經說過,Step2是生成“單詞表”,而Step3就是通過計算歐式距離來確定原始feature文檔中每個feature對應“單詞表”中的哪個“單詞”,然后統計出來,生成對應的統計文件——詞頻文件wordF.data。主要操作如下:

...
FileStorage readfs(CIni::CStrToChar(ini.getWordListFilePath()), FileStorage::READ); //以只讀的形式打開yml。
Mat wordList; //單詞表矩陣
readfs[CIni::CStrToChar(CIni::removeSuffix(CIni::getFileNameFromPath(ini.getWordListFilePath())))] >> wordList; //讀取單詞表
...
while(“對於每個feature文檔”) 
{
  ...
  int n = import_features(CIni::CStrToChar(fileName), FEATURE_LOWE, &features);
  ...
  for(int i = 0; i < n; i++)
  {
    ...
    distMin = normL2Sqr_(pa, pb, dims); //計算歐式距離
    ...
    for(int j = 1; j < wordNum; j++)
    {
      ...
      dist = normL2Sqr_(pa, pb, dims); //計算歐式距離
      ...
    }
  }
}
...

  顯而易見,前三個操作就是利用Opencv的函數,將Step2中生成的“單詞表”讀入矩陣結構(Mat)的變量wordList中。而對於每個feature文檔,通過sift源碼的import_features(...)函數讀入以feature結構體為元素的數組features中。接下來對於每個feature計算它與wordList中哪個“單詞”最接近,最后統計出該文檔中包含的單詞及其個數。

  normL2Sqr_(pa, pb, dims)是Opencv中計算歐式距離的函數,其中pa指向一個feature,而pb指向單詞表中的一個“單詞”(“單詞”也是feature),dims表示待計算數據的維數,在這里就是128了。

  經過上面的操作,統計好每個圖片文檔中每個“單詞”出現的次數后,統一保存為一個詞頻文檔,即將所有圖片文檔的詞頻信息保存在一個文件wordF.data內,其文件結構為:

72 0:3 1:4 2:3 3:39 4:6 5:10 6:1 7:4 8:4 9:30 10:8 11:3 13:5 14:7 15:20 16:3 17:2 18:5 19:6 20:7 21:2 22:8 23:11 24:15 25:8 26:9 27:9 28:4 29:3 30:14 31:2 34:4 35:4 36:3 37:4 38:8 39:7 40:15 42:7 44:6 45:4 46:8 47:15 48:9 49:3 50:3 51:1 53:4 54:2 55:3 56:2 57:3 58:9 59:1 60:1 61:8 62:4 63:4 64:7 65:3 66:1 68:3 69:1 70:4 71:1 72:1 73:1 74:6 76:2 77:6 78:5 79:3
60 0:2 1:1 3:29 4:3 5:13 6:1 7:1 8:5 9:30 10:4 11:2 13:1 14:2 15:17 17:1 18:4 19:2 20:1 22:3 23:3 24:4 25:2 26:4 27:4 28:1 29:2 30:8 31:1 32:3 34:1 35:3 36:4 37:4 38:4 39:6 40:15 42:3 44:3 45:2 47:13 48:6 49:3 51:1 52:2 54:2 56:1 57:2 58:13 61:2 63:3 64:6 66:1 67:2 71:1 73:1 74:1 76:1 77:2 78:1 79:4
75 1:3 2:2 3:30 4:4 5:7 6:1 7:3 8:2 9:32 10:9 11:3 12:3 13:9 14:2 15:22 16:5 17:2 18:5 19:1 20:3 21:3 22:1 23:11 24:6 25:2 26:11 27:6 28:3 29:9 30:5 31:2 32:5 34:3 35:6 36:2 37:10 38:2 39:8 40:20 42:8 43:1 44:7 45:4 46:2 47:14 48:3 49:3 50:3 51:4 52:2 53:5 54:2 55:2 56:1 57:3 58:12 59:1 60:2 61:1 62:4 64:2 65:4 66:4 67:3 68:1 69:1 70:5 71:3 72:2 73:1 74:3 76:5 77:2 78:6 79:4
73 0:2 1:2 2:1 3:29 4:8 5:6 6:4 7:6 8:3 9:26 10:10 11:2 12:2 13:6 14:1 15:16 16:2 17:8 18:5 19:3 20:2 21:6 22:5 23:4 24:5 25:5 26:12 27:2 28:1 29:10 30:6 31:1 32:2 33:1 34:2 35:9 36:2 37:7 39:5 40:16 42:6 43:1 44:4 46:2 47:17 48:9 49:2 50:3 52:5 53:1 54:2 55:3 56:2 57:3 58:9 59:2 60:1 62:2 63:3 64:1 65:4 66:2 67:1 68:3 69:3 70:7 72:2 73:1 74:3 75:1 76:7 78:8 79:5
70 0:1 1:5 3:23 4:5 5:2 6:2 7:4 8:5 9:27 10:11 11:2 12:1 13:6 14:4 15:15 16:4 17:4 18:5 19:6 20:4 21:4 22:6 23:7 24:4 25:2 26:12 27:3 28:2 29:13 30:7 31:1 32:4 33:2 34:5 35:12 36:4 37:8 38:3 40:21 42:6 43:1 44:5 45:4 46:3 47:14 48:6 49:5 50:3 51:1 52:6 53:2 54:3 55:2 57:7 58:12 59:2 62:3 63:4 65:5 66:3 67:3 69:1 70:5 71:2 72:3 73:4 74:3 76:6 78:8 79:2
70 0:1 1:2 2:2 3:25 4:2 5:6 7:1 8:3 9:35 10:7 12:1 13:4 14:4 15:17 16:2 17:7 18:7 19:3 20:3 21:2 22:6 23:5 24:1 25:2 26:9 27:4 28:1 29:7 31:1 32:2 34:3 35:4 36:1 37:8 38:1 39:1 40:19 42:6 44:5 45:2 47:15 48:7 49:5 51:2 52:4 53:1 54:2 55:2 56:2 57:3 58:16 59:2 60:1 61:3 62:4 63:6 65:3 66:3 67:2 68:1 69:1 70:3 71:3 72:4 73:3 74:4 76:2 77:3 78:7 79:9

  其中每一行代表一個文檔(圖片文檔),%d:%d的結構表示單詞ID:單詞個數,第一個數字表示后面的元素項個數。這些數據就是Step3統計詞頻的輸出了,也是Step4LDA運算的輸入。

[Go Top]


六、Step4——LDA應用

  作者對LDA的實現並不是像sift和kmeans那樣由一個函數通過參數傳入傳出來給出,而是作者的main函數的一個實現過程。本例程LDA的主要過程為:

...
int topic_num = lda_k; //LDA分類數
struct corpus *cps;
struct est_param param;
...
cps = read_corpus(data); //讀取訓練集
init_param(cps,&param,topic_num); //初始化參數
//迭代計算
while (1)
{
  //對每個文檔使用sampling方法計算
  for (int m=0; m<cps->num_docs; m++)
  {
    ...
    for (int l=0; l<cps->docs[m].length; l++)
    {
      for (int c=0; c<cps->docs[m].words[l].count; c++)
      {
        param.z[m][word_index] = sampling(m,word_index,cps->docs[m].words[l].id,topic_num,cps,&param,alpha,beta,p,s_talpha,vbeta); //sampling計算
        ...
      }
    }
  }
  if ((iter_time >= burn_in_num) && (iter_time % SAMPLE_LAG == 0))
  {
    calcu_param(&param, cps,topic_num,alpha,beta); //計算參數
    ...
  }
  //迭代結束條件
  if (sample_time ==sample_num)
  {
    break;
  }
}
average_param(&param, cps,topic_num,alpha,beta,sample_num); //計算theta,phi平均值
save_model(cps,&param,model_name,alpha,beta,topic_num,sample_num); //保存結果數據
...

  對於沒有接觸過LDA的人要看懂函數過程還是很困難的,但對於有語言功底的程序員,如果只是應用LDA過程,只需要了解幾個主要的LDA概念就行了。(其他部分請參考作者代碼注釋)

1、LDA的輸入。LDA的輸入除了Step3生成的詞頻統計信息外,還需要一些參數。這些參數一般設置為通用參數就行了,本例程只有lda_k為界面輸入,表示LDA訓練的主題個數。
  read_corpus(data)方法中,data為字符串,表示輸入數據的路徑,該方法將文件讀取為struct corpus的格式。corpus結構體表示的是一個文檔集,其結構可以從聲明中看出:

struct word
{
    int id;
    int count;
};

struct document
{
    int id; //文檔id
    int num_term; //文檔包含的單詞個數(count的總和)
    int length; //文檔包含的單詞類別個數(id:count結構的個數)
    struct word* words;
};

struct corpus
{
    struct document* docs;
    int num_docs; //文檔個數
    int num_terms; //單詞表中單詞總數(實際上是所有單詞中最大id+1)
};

  對比Step3中生成的詞頻文件,不難看懂上面這些結構體的意義。

2、LDA的輸出。LDA的輸出包含很多數據,由save_model(...)函數輸出為文件:


  lda.other文件保存參數alpha, beta, topic_num, sample_num的值。lda.topic_assgin文件保存z矩陣,z[m][n]==k表示文檔m中的單詞n所對應的主題為k。lda.theta文件保存theta矩陣,theta[m][k]表示在文檔為m時,生成主題k的概率,即條件概率p(主題k|文檔m)。lda.phi文件保存phi矩陣,phi[k][v]表示在主題為k時,生成單詞v的概率,即條件概率p(單詞v|主題k)。

  其中需要關注的只有theta和phi兩個矩陣,而本例程只用到了theta矩陣的信息。theta矩陣和phi矩陣的例子如下表:

P(Topic_k|Doc_m)

Topic1

Topic2

Doc1

0.45

0.55

Doc2

0.1

0.9

Doc3

0.6

0.4

                

P(Word_v|Topic_k)

Word1

Word2

Word3

Word4

Topic1

0.45

0.15

0.2

0.2

Topic2

0.1

0.5

0.3

0.1

theta矩陣   phi矩陣

  其中的Topic都是由LDA通過無監督學習得到的潛在主題,只需要用戶告訴LDA主題數目就行了。從上面的表格中不難看出,在theta矩陣中,我們知道了每個文檔生成每個主題的概率,通過比較概率大小就可以確定文檔所對應的主題了。

3、LDA算法及其運算所需結構體struct est_param。

struct est_param
{
    int **z; //z[m][n] stands for topic assigned to nth word in mth document
    double **theta; // theta[m][k] stands for the topic mixture proportion for document m
    double **phi; // phi[k][v] stands for the probability of vth word in vocabulary is assigned to topic k 
    // count statistics
    int **nd; //nd[m][k] stands for the number of words assigned to kth topic in mth document
    int **nw; //nw[k][t] stands for the number of kth topic assigned to tth term
    int *nd_sum; //nd_sum[m] total number of word in mth document
    int *nw_sum; //nw_sum[k] total number of terms assigned to kth topic
};

  該結構體內theta和phi二維數組是我們熟悉的輸出,z數組也是前面提到的輸出之一,后面的四個變量是LDA運算中間過程的必備臨時變量。LDA算法,如果了解了相關的變量名和LDA過程,通過下面這幅圖是不難了解算法過程的:

  綜上,知道了每個圖片文檔對應哪個主題的概率最大后,就可以根據主題個數新建lda_k個文件夾,然后把分類到對應主題的圖片拷貝過去,從而實現了對圖片的LDA分類。分類效果如圖:

[Go Top]


七、參考

【Opencv】
Opencv下載:http://sourceforge.net/projects/opencvlibrary/files/
Opencv教程:http://www.opencv.org.cn/index.php

【SIFT】
SIFT源碼下載:http://blogs.oregonstate.edu/hess/code/sift/ 
SIFT源碼使用方法:http://www.open-open.com/lib/view/1325332699514

 【Kmeans】Kmeans為Opencv自帶函數。
OpenCV中kmean算法的實現:http://blog.hongquan.me/?p=8

【LDA】
LDA源碼下載:http://code.google.com/p/lsa-lda/ 
gibbs的LDA實現:http://sourceforge.net/projects/gibbslda/

 【Bag-of-words模型】
SIFT算法的應用--目標識別之Bag-of-words模型:http://blog.csdn.net/v_JULY_v/article/details/6555899

[Go Top]


源碼下載:http://www.pudn.com/downloads464/sourcecode/graph/texture_mapping/detail1949366.html


免責聲明!

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



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