【百度百科】 LIBSVM是臺灣大學林智仁(Lin Chih-Jen)教授等開發設計的一個簡單、易於使用和快速有效的SVM模式識別與回歸的軟體包,他不但提供了編譯好的可在Windows系列系統的執行文件,還提供了源代碼,方便改進、修改以及在其它操作系統上應用;該軟體對SVM所涉及的參數調節相對比較少 ...
【百度百科】
LIBSVM是臺灣大學林智仁(Lin Chih-Jen)教授等開發設計的一個簡單、易於使用和快速有效的SVM模式識別與回歸的軟體包,他不但提供了編譯好的可在Windows系列系統的執行文件,還提供了源代碼,方便改進、修改以及在其它操作系統上應用;該軟體對SVM所涉及的參數調節相對比較少,提供了很多的預設參數,利用這些預設參數可以解決很多問題;並提供了交互檢驗(Cross Validation)的功能。該軟體可以解決C-SVM、ν-SVM、ε-SVR和ν-SVR等問題,包括基於一對一演算法的多類模式識別問題……
如果你對libsvm還不夠瞭解,建議先瀏覽下百度百科等對libsvm的介紹~
【C# Wrapper 動機】
參與過一個項目,使用IDE是VS winform,工具包為EmguCV 2.4.10。我們知道OpenCV2中的svm部分是根據libsvm-2.6編寫的,該版本的libsvm已經能夠estimate預測概率了(libsvm首頁的change log中有詳細說明),但是OpenCV卻捨棄了predictProbability。在具體的項目中,如果可以獲得預測概率信息,那將對提高識別性有很大的幫助。然而,opencv2捨棄了識別概率,包括opencv3,我看源代碼的svm部分也是基於libsvm-2.6修改的,也沒有引進predictProbability。
因而,在EmguCV的ML滿足不了的情況下,萌生了兩個想法:
一是修改OpenCV代碼,然後重新CMake得到cvextern.dll;
二是直接找其它的svm庫。
首先嘗試CMake。像OpenCV這樣的大項目,CMake起來確實不容易,更何況是從零開始學CMake。在時間不允許的條件下,只得走第二條路。找到libsvmSharp後,我如獲至寶。但是,很快我又再度失望了,因為實時性要求滿足不了(EmguCV自帶SVM可以在5ms內完成識別預測,而libsvmSharp需要500ms)。
這是為什麼?
同樣是C#對C++的wrapper,同樣都是基於libsvm,同樣是對C++所編譯的dll的引用,效率竟相差百倍!本著一顆學習的心,我決定一探究竟……
【現有libsvm的C#/.Net版本】
目前,LIBSVM擁有C、Java、Matlab、C#、Ruby、Python、R、Perl、Common LISP、Labview等數十種語言版本。最常使用的是C、Matlab、Java和命令行(c語言編譯的工具)的版本。
首先我們看張libsvm官網首頁上的截圖:
下麵,我們看看現在libsvm有哪些C#版本:
1、SVM.NET by Matthewa Johnson
2009年,劍橋大學的Matthewa Johnson博士將SVM.NET更新到了V2.89,也就是現在的最新版本。無奈現在不FQ竟已經找不到SVM.NET的原生版了。這份神秘感使我覺得,這個C#版本的libsvm應該是質量最高的。
後人有在V2.89的基礎上做一些修改,提出了:SVM.NET with Parallel Optimization。相關描述為:When finding parameters, C and Gamma, in Grid-search algorithm using ParameterSelection.PGrid instead of the original ParameterSelection.Grid will increase the calculation speed.
2、NSVM by Joannes
3年時間,卻只有2下載量,何其慘淡……好吧,或許你也像我一樣主觀臆斷了。
3、KMLib(Kernel Machine Library with GPU SVM solver in .Net) by Krzysztof Sopyła
Key Features
-
- .Net implementation
- Parallel kernel implementation
- SVM CUDA acceleration – kernels and solver
- CUDA SVM with sparse data formats: CSR, Ellpack-R, Sliced-Ellpack
- For non commercial and academic use: Free MIT license when use please cite: Bibtex CUDA SVM CSR
另外,還有一點需要強調的是,它是基於libsvm的java版本轉換過來的。也正因如此,我感覺用起來可能會有點麻煩,故沒有選擇。
4、libsvmSharp by ccerhan
選擇它的理由很簡單,有一定的下載量(從眾心理又開始作祟了!)下載方便,用VS的Nuget package,通過命令“PM> Install-Package LibSVMsharp”即下載到本地。
5、libsvm-net by Nicolas Panel
下載起來同樣十分方便: NuGet package : PM> Install-Package libsvm.net,比起libsvmSharp有更高的人氣。
【分析libsvmSharp】
為什麼libsvmSharp.dll如此低效?
在反編譯後的源代碼中(稍後將介紹如何反編譯C#編譯出來的dll文件),我們可以看到libsvmSharp所用的數據結構有:
1、struct:svm_node、svm_model、svm_problem、svm_parameter;
2、calss:SVMNode、SVMModel、SVMProblem、SVMParameter。
實際上,結構體能做的事情,類完全也能做,似乎結構體沒有存在的必要。
而且,可以看到各類的實現中,有很多“結構體=>類”、“指針=>結構體”、“類=>指針”等這樣的類型轉換。我們知道,C#要引用C++所編譯的dll,用得最多的就是IntPtr這個數據結構。而libsvmSharp低效的原因,也正在於對指針的處理策略選取不當,它只在需要傳指針的時候,硬生生地用Marshal類重新在記憶體中開闢當前數據結構大小的區域,並返回指針,美其名曰convert到指針。這種方式,無論是在時間上還是空間上,都有太多沒必要的浪費。
這裡我們用libsvm中的svm_predict作為例子來講解。
在libsvm.dll(該dll由C++編譯得到)中,函數為:
double svm_predict(const svm_model *model, const svm_node *x)
在libsvmSharp.dll(該dll由C#編譯得到)中,我們這樣聲明它:
[DllImport("libsvm.dll", CallingConvention = CallingConvention.Cdecl)] public static extern double svm_predict(IntPtr model, IntPtr x);
DllImport時,更多關於C++數據結構到C#數據結構的信息請讀者查閱資料獲得。由上可見,IntPtr是個很關鍵的數據結構,由它聲明的變數實際上是一個指針值(即記憶體地址值)。第一個參數IntPtr model,要求傳入model所在記憶體區域的地址,第二個參數IntPtr x,要求傳入特征節點數組所在記憶體區域的地址。下麵,我們看看libsvmSharp是怎麼使用這個函數的:
1 public static double Predict(SVMModel model, SVMNode[] x) 2 { 3 if (model == null) 4 { 5 throw new ArgumentNullException("model"); 6 } 7 if (x == null) 8 { 9 throw new ArgumentNullException("x"); 10 } 11 IntPtr intPtr = SVMModel.Allocate(model); 12 double result = SVM.Predict(intPtr, x); 13 SVMModel.Free(intPtr); 14 return result; 15 } 16 17 public static double Predict(IntPtr ptr_model, SVMNode[] x) 18 { 19 if (ptr_model == IntPtr.Zero) 20 { 21 throw new ArgumentNullException("ptr_model"); 22 } 23 if (x == null) 24 { 25 throw new ArgumentNullException("x"); 26 } 27 List<SVMNode> list = (from a in x 28 select a.Clone()).ToList<SVMNode>(); 29 list.Add(new SVMNode(-1, 0.0)); 30 IntPtr intPtr = SVMNode.Allocate(list.ToArray()); 31 double result = libsvm.svm_predict(ptr_model, intPtr); 32 SVMNode.Free(intPtr); 33 return result; 34 }
細心的你有沒有發現什麼問題?看不懂?畢竟我是斷章取義。然而,請看第11行,每次調用都要重新給model分配記憶體哦!再如,第27、28、29、30行,在熟悉C++的人看來,that's what?參數傳進來的可不是數組名嗎,幹嘛如此大費周章?記憶體不會被玩壞嗎?
一切都是因為C#有指針,但不是那個我們所熟悉的指針。C#沒有像Java一樣完全擯棄指針,但為了代碼安全考慮而弱化指針。C#是面向對象的語言,裡面任何一種數據結構都沒有指針這一屬性,除非你自己在定義數據結構時,將指針作為成員變數。我們所熟悉的EmguCV就是這麼實現對OpenCV的wrapper的。
【開始libsvm的C# Wrapper之旅】
很好,我們可以進入正題了。我將以wrapper libsvm為例,分步驟講解整個過程。讀者可以舉一反三,希望本文可以幫助你加深你對跨語言編程的理解。
1.wrapper第一步(準備)
獲取你要wrapper的dll(由C++編譯得到),最好有源代碼,當然有參考手冊也可以,但是如果除了dll的名字,對該dll一無所知,那或許就無能為力了。
安裝C#的dll反編譯工具,這裡推薦ILSpy。為什麼要安裝?比起自己黑暗中摸索,如果有可以參考借鑒的資源,視而不見是多麼可惜的一件事啊。EmguCV真的稱得上wrapper中的精華。
2. wrapper第二步(DllImport)
首先,VS新建C#工程,項目類別選擇類庫,這樣最後生成解決方案後,便可以在bin/Debug目錄下獲得實用的dll文件了。我將項目命名為libsvmSharpCyc。
其次,添加需要wrapper的C++ dll文件。右鍵單擊解決方案資源管理器中的libsvmSharpCyc,然後添加現有項,把libsvm.dll添加進項目。
接著,新建類,用於DllImport。我建的是LsInvoke.cs,可以像下圖所示這樣,把想要使用的函數方法給Import進來:
該過程中,DllImport要如何使用,感興趣的讀者可自行學習,這裡需要註意的是C++函數中的數據結構到C#中的數據結構是有映射關係的,下麵附上一張dll引用常用轉化表:
C++ C# ===================================== WORD ushort DWORD uint UCHAR int/byte 大部分情況都可以使用int代替,而如果需要嚴格對齊的話則應該用bytebyte UCHAR* string/IntPtr unsigned char* [MarshalAs(UnmanagedType.LPArray)]byte[]/?(Intptr) char* string LPCTSTR string LPTSTR [MarshalAs(UnmanagedType.LPTStr)] string long int ulong uint Handle IntPtr HWND IntPtr void* IntPtr int int int* ref int *int IntPtr unsigned int uint COLORREF uint
3、wrapper第三步(數據結構)
這一步是最為關鍵的一步,在C#中新建數據結構,必須要與C++中的數據結構相一致,否則碰到無法預料的問題。
前文已經簡單地介紹過libsvm的數據結構了。這裡重覆一下:
1 struct svm_node 2 { 3 int index; 4 double value; 5 }; 6 7 struct svm_problem 8 { 9 int l; 10 double *y; 11 struct svm_node **x; 12 }; 13 14 enum { C_SVC, NU_SVC, ONE_CLASS, EPSILON_SVR, NU_SVR }; /* svm_type */ 15 enum { LINEAR, POLY, RBF, SIGMOID, PRECOMPUTED }; /* kernel_type */ 16 17 struct svm_parameter 18 { 19 int svm_type; 20 int kernel_type; 21 int degree; /* for poly */ 22 double gamma; /* for poly/rbf/sigmoid */ 23 double coef0; /* for poly/sigmoid */ 24 25 /* these are for training only */ 26 double cache_size; /* in MB */ 27 double eps; /* stopping criteria */ 28 double C; /* for C_SVC, EPSILON_SVR and NU_SVR */ 29 int nr_weight; /* for C_SVC */ 30 int *weight_label; /* for C_SVC */ 31 double* weight; /* for C_SVC */ 32 double nu; /* for NU_SVC, ONE_CLASS, and NU_SVR */ 33 double p; /* for EPSILON_SVR */ 34 int shrinking; /* use the shrinking heuristics */ 35 int probability; /* do probability estimates */ 36 }; 37 38 // 39 // svm_model 40 // 41 struct svm_model 42 { 43 struct svm_parameter param; /* parameter */ 44 int nr_class; /* number of classes, = 2 in regression/one class svm */ 45 int l; /* total #SV */ 46 struct svm_node **SV; /* SVs (SV[l]) */ 47 double **sv_coef; /* coefficients for SVs in decision functions (sv_coef[k-1][l]) */ 48 double *rho; /* constants in decision functions (rho[k*(k-1)/2]) */ 49 double *probA; /* pariwise probability information */ 50 double *probB; 51 int *sv_indices; /* sv_indices[0,...,nSV-1] are values in [1,...,num_traning_data] to indicate SVs in the training set */ 52 53 /* for classification only */ 54 55 int *label; /* label of each class (label[k]) */ 56 int *nSV; /* number of SVs for each class (nSV[k]) */ 57 /* nSV[0] + nSV[1] + ... + nSV[k-1] = l */ 58 /* XXX */ 59 int free_sv; /* 1 if svm_model is created by svm_load_model*/ 60 /* 0 if svm_model is created by svm_train */ 61 };
對應地,我們在C#中建立數據結構:
public struct svm_node { /// <summary> /// 索引 /// </summary> public int index; /// <summary> /// 值 /// </summary> public double value; /// <summary> /// 構造函數 /// </summary> /// <param name="i"></param> /// <param name="v"></param> public svm_node(int i,double v) { this.index = i; this.value = v; } public bool Equals(svm_node x) { return this.index.Equals(x.index) && this.value.Equals(x.value); } } public struct svm_problem { /// <summary> /// 支持向量個數 /// </summary> public int l; /// <summary> /// 標簽值 /// </summary> public IntPtr y; /// <summary> /// 節點情況 /// </summary> public IntPtr x; } ……
可能有讀者會問,結構體你加構造函數和其它函數幹嘛?這其實是為了日後好簡化代碼。否則,每次對象創建於賦值分開操作有點麻煩。
進行到現在,我們只是完成了數據結構搭建的一小部分,下麵是從EmguCV中學習到的精髓部分!將在下篇作介紹~