libsvm代碼閱讀(1):基礎準備與svm.h頭文件

libsvm是國立臺灣大學Chih-Jen Lin開發的一個SVM的函數庫,是當前應用最普遍的svm函數庫,從2000年到2010年,該函數庫的下載量達到250000之多。它的最新版本是version 3.17,主要是對是svm_group_classes作了修改。html

主頁:LIBSVM -- A Library for Support Vector Machines   node

下載地址:zip.file ortar.gz c++

我下載後的解壓文件以下所示:web


libsvm函數包的組織結構以下面試

一、主文件路徑:包含了核心的C/C++程序和例子數據。其中svm.cpp是svm的核心程序,它實現了svm的訓練和測試算法。算法

二、tool子文件路徑:包含了一些檢驗數據格式以及選擇svm參數的tool。編程

三、其餘子文件路徑:主要包含pre-built 二值文件和相關語言的接口。數組

關於libsvm的官方實現文檔LIBSVM: A Library for Support Vector Machines,本相關博文也是以這篇文檔爲基礎的,同時本相關博文還參考了上海交大模式分析與機器智能實驗室的libsvm2.6代碼註釋app

關於svm的理論知識,本文不想多說,若是你還不是很懂,能夠參考我前面的兩篇文章:函數

支持向量機學習筆記:數學過程及經典Tutorial

支持向量機學習筆記:LIBSVM應用(人臉識別)

=======================================================

如今開始看一看libsvm的頭文件svm.h

svm.h中主要是定義了4個結構體,分別是svm_node、svm_problem、svm_parameter、svm_model,而後就是19個函數的聲明,函數的聲明我就不先講了,等到在svm.cpp中碰到後再細說。下面來看一看這幾個結構體:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_1 height=18 name=ZeroClipboardMovie_1 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=1&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. struct svm_node  
  2. {  
  3.     int index;  
  4.     double value;  
  5. };  

這個結構體用於存儲單一貫量中的單個特徵。例如:向量x1={0.002,0.345,4.000,5.677},則用svm_node來存儲就是使用一個包含5個svm_node的數組來存儲這個4維向量,內存中的表示以下:



[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_2 height=18 name=ZeroClipboardMovie_2 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=2&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. struct svm_problem  
  2. {  
  3.     int l; //記錄樣本總數  
  4.     double *y;//指向樣本所屬類別的數組  
  5.     struct svm_node **x;//指向一個存儲內容爲指針的數組  
  6. };  

這個結構體用於存儲本次參加運算的全部樣本及其所屬類別,一個示意圖以下(其中最右邊的4個長方格如同上圖中的表格):



[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_3 height=18 name=ZeroClipboardMovie_3 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=3&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. struct svm_parameter  
  2. {  
  3.     int svm_type;//SVM類型,見前enum  
  4.     int kernel_type;//核函數  
  5.     int degree; /* for poly */  
  6.     double gamma;   /* for poly/rbf/sigmoid */  
  7.     double coef0;   /* for poly/sigmoid */  
  8.   
  9.     /* these are for training only */  
  10.     double cache_size; /* in MB */  
  11.     double eps; /* stopping criteria */  
  12.     double C;   /* for C_SVC, EPSILON_SVR and NU_SVR */  
  13.     int nr_weight;      /* for C_SVC */  
  14.     int *weight_label;  /* for C_SVC */  
  15.     double* weight;     /* for C_SVC */  
  16.     double nu;  /* for NU_SVC, ONE_CLASS, and NU_SVR */  
  17.     double p;   /* for EPSILON_SVR */  
  18.     int shrinking;  /* use the shrinking heuristics */  
  19.     int probability; /* do probability estimates */  
  20. };  

這個結構體用於存儲svm的各個參數,知道就行。


[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_4 height=18 name=ZeroClipboardMovie_4 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=4&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. struct svm_model  
  2. {  
  3.     struct svm_parameter param; /* parameter */  
  4.     int nr_class;       /* number of classes, = 2 in regression/one class svm */  
  5.     int l;          /* total #SV */  
  6.     struct svm_node **SV;       /* SVs (SV[l]) */  
  7.     double **sv_coef;   /* coefficients for SVs in decision functions (sv_coef[k-1][l]) */  
  8.     double *rho;        /* constants in decision functions (rho[k*(k-1)/2]) */  
  9.     double *probA;      /* pariwise probability information */  
  10.     double *probB;  
  11.     int *sv_indices;        /* sv_indices[0,...,nSV-1] are values in [1,...,num_traning_data] to indicate SVs in the training set */  
  12.   
  13.     /* for classification only */  
  14.   
  15.     int *label;     /* label of each class (label[k]) */  
  16.     int *nSV;       /* number of SVs for each class (nSV[k]) */  
  17.                 /* nSV[0] + nSV[1] + ... + nSV[k-1] = l */  
  18.     /* XXX */  
  19.     int free_sv;        /* 1 if svm_model is created by svm_load_model*/  
  20.                 /* 0 if svm_model is created by svm_train */  
  21. };  

這個函數體用於保存訓練後的訓練模型model,在predict中要用到。
=======================================

關於extern 「C」的解析

在閱讀svm.h時發現了以下一段代碼:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_5 height=18 name=ZeroClipboardMovie_5 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=5&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. #ifndef _LIBSVM_H  
  2. #define _LIBSVM_H  
  3. #define LIBSVM_VERSION 317  
  4.   
  5. #ifdef __cplusplus  
  6. extern "C" {  
  7. #endif  

顯然,前3行代碼是爲了防止頭文件被重複引用,那麼5-6行是什麼意思呢?


某企業曾經給出以下的一道面試題:爲何標準頭文件都有相似如下的結構?

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_6 height=18 name=ZeroClipboardMovie_6 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=6&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. #ifndef __INCvxWorksh  
  2. #define __INCvxWorksh   
  3. #ifdef __cplusplus  
  4. extern "C" {  
  5. #endif   
  6. /*...*/   
  7. #ifdef __cplusplus  
  8. }  
  9. #endif   
  10. #endif /* __INCvxWorksh */  

分析
顯然,頭文件中的編譯宏「#ifndef __INCvxWorksh、#define __INCvxWorksh、#endif」 的做用是防止該頭文件被重複引用。

那麼

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_7 height=18 name=ZeroClipboardMovie_7 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=7&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. #ifdef __cplusplus  
  2. extern "C" {  
  3. #endif   
  4. #ifdef __cplusplus  
  5. }  
  6. #endif  

的做用又是什麼呢?

extern "C" 包含雙重含義,從字面上便可獲得:首先,被它修飾的目標是「extern」的;其次,被它修飾的目標是「C」的。讓咱們來詳細解讀這兩重含義。

被extern "C"限定的函數或變量是extern類型的;extern是C/C++語言中代表函數和全局變量做用範圍(可見性)的關鍵字,該關鍵字告訴編譯器,其聲明的函數和變量能夠在本模塊或其它模塊中使用。記住,下列語句:extern int a;僅僅是一個變量的聲明,其並非在定義變量a,並未爲a分配內存空間。變量a在全部模塊中做爲一種全局變量只能被定義一次,不然會出現鏈接錯誤。一般,在模塊的頭文件中對本模塊提供給其它模塊引用的函數和全局變量以關鍵字extern聲明。例如,若是模塊B欲引用該模塊A中定義的全局變量和函數時只需包含模塊A的頭文件便可。這樣,模塊B中調用模塊A中的函數時,在編譯階段,模塊B雖然找不到該函數,可是並不會報錯;它會在鏈接階段中從模塊A編譯生成的目標代碼中找到此函數。

與extern對應的關鍵字是static,被它修飾的全局變量和函數只能在本模塊中使用。所以,一個函數或變量只可能被本模塊使用時,其不可能被extern 「C」修飾。

被extern "C"修飾的變量和函數是按照C語言方式編譯和鏈接的;

未加extern 「C」聲明時的編譯方式

首先看看C++中對相似C的函數是怎樣編譯的。做爲一種面向對象的語言,C++支持函數重載,而過程式語言C則不支持。函數被C++編譯後在符號庫中的名字與C語言的不一樣。例如,假設某個函數的原型爲:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_8 height=18 name=ZeroClipboardMovie_8 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=8&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. void foo( int x, int y );  

該函數被C編譯器編譯後在符號庫中的名字爲_foo,而C++編譯器則會產生像_foo_int_int之類的名字(不一樣的編譯器可能生成的名字不一樣,可是都採用了相同的機制,生成的新名字稱爲「mangled name」)。_foo_int_int這樣的名字包含了函數名、函數參數數量及類型信息,C++就是靠這種機制來實現函數重載的。例如,在C++中,函數void foo( int x, int y )與void foo( int x, float y )編譯生成的符號是不相同的,後者爲_foo_int_float。一樣地,C++中的變量除支持局部變量外,還支持類成員變量和全局變量。用戶所編寫程序的類成員變量可能與全局變量同名,咱們以"."來區分。而本質上,編譯器在進行編譯時,與函數的處理類似,也爲類中的變量取了一個獨一無二的名字,這個名字與用戶程序中同名的全局變量名字不一樣。

未加extern "C"聲明時的鏈接方式

假設在C++中,模塊A的頭文件以下:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_9 height=18 name=ZeroClipboardMovie_9 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=9&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. // 模塊A頭文件 moduleA.h  
  2. #ifndef MODULE_A_H  
  3. #define MODULE_A_H  
  4. int foo( int x, int y );  
  5. #endif  

在模塊B中引用該函數:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_10 height=18 name=ZeroClipboardMovie_10 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=10&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. // 模塊B實現文件 moduleB.cpp  
  2. #include "moduleA.h"  
  3. foo(2,3);  

實際上,在鏈接階段,鏈接器會從模塊A生成的目標文件moduleA.obj中尋找_foo_int_int這樣的符號!

加extern "C"聲明後的編譯和鏈接方式

加extern "C"聲明後,模塊A的頭文件變爲:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_11 height=18 name=ZeroClipboardMovie_11 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=11&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. // 模塊A頭文件 moduleA.h  
  2. #ifndef MODULE_A_H  
  3. #define MODULE_A_H  
  4. extern "C" int foo( int x, int y );  
  5. #endif  

在模塊B的實現文件中仍然調用foo( 2,3 ),其結果是:

(1)模塊A編譯生成foo的目標代碼時,沒有對其名字進行特殊處理,採用了C語言的方式;

(2)鏈接器在爲模塊B的目標代碼尋找foo(2,3)調用時,尋找的是未經修改的符號名_foo。

因此,能夠用一句話歸納extern 「C」這個聲明的真實目的:實現C++與C及其它語言的混合編程
  
extern "C"的慣用法

(1)在C++中引用C語言中的函數和變量,在包含C語言頭文件(假設爲cExample.h)時,需進行下列處理:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_12 height=18 name=ZeroClipboardMovie_12 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=12&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. extern "C"  
  2. {  
  3. #include "cExample.h"  
  4. }  

而在C語言的頭文件中,對其外部函數只能指定爲extern類型,C語言中不支持extern "C"聲明,在.c文件中包含了extern "C"時會出現編譯語法錯誤。筆者編寫的C++引用C函數例子工程中包含的三個文件的源代碼以下:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_13 height=18 name=ZeroClipboardMovie_13 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=13&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. /* c語言頭文件:cExample.h */  
  2. #ifndef C_EXAMPLE_H  
  3. #define C_EXAMPLE_H  
  4. extern int add(int x,int y);  
  5. #endif  
  6. /* c語言實現文件:cExample.c */  
  7. #include "cExample.h"  
  8. int add( int x, int y )  
  9. {  
  10.   return x + y;  
  11. }  
  12. // c++實現文件,調用add:cppFile.cpp  
  13. extern "C"   
  14. {  
  15. #include "cExample.h"  
  16. }  
  17. int main(int argc, char* argv[])  
  18. {  
  19.   add(2,3);   
  20.   return 0;  
  21. }  

若是C++調用一個C語言編寫的.DLL時,當包括.DLL的頭文件或聲明接口函數時,應加extern "C" { }。

(2)在C中引用C++語言中的函數和變量時,C++的頭文件需添加extern "C",可是在C語言中不能直接引用聲明瞭extern "C"的該頭文件,應該僅將C文件中將C++中定義的extern "C"函數聲明爲extern類型。
筆者編寫的C引用C++函數例子工程中包含的三個文件的源代碼以下:

[cpp]   view plain copy 在CODE上查看代碼片 派生到個人代碼片
<EMBED id=ZeroClipboardMovie_14 height=18 name=ZeroClipboardMovie_14 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=14&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">
  1. //C++頭文件 cppExample.h  
  2. #ifndef CPP_EXAMPLE_H  
  3. #define CPP_EXAMPLE_H  
  4. extern "C" int add( int x, int y );  
  5. #endif  
  6. //C++實現文件 cppExample.cpp  
  7. #include "cppExample.h"  
  8. int add( int x, int y )  
  9. {  
  10.    return x + y;  
  11. }  
  12. /* C實現文件 cFile.c 
  13. /* 這樣會編譯出錯:#include "cExample.h" */  
  14. extern int add( int x, int y );  
  15. int main( int argc, char* argv[] )  
  16. {  
  17.   add( 2, 3 );   
  18.   return 0;  
  19. }  

上面關於extern C的解析轉載自:C++中extern 「C」含義深層探索

其餘關於extern C 的知識可參見博文:

C++項目中的extern "C" {}

面試之C++:extern及extern 「C」用法

相關文章
相關標籤/搜索