最近因為研究需要, 打算使用 林智仁 老師 開發的 libsvm 來訓練自己的 corpus 抽出取來的 features, 作為實驗數據的 baseline. 從網站上面可以下載 zip 檔後解壓縮, 可以發現有個 Java 目錄. 耶! 對於 Java 比較熟的我是一大福音. 有興趣可以去讀裡面元代碼的 "svm_train.java", "svm_predict.java" 與 "svm.java" 就可以知道其命令列的使用方法與工作流程.
基本上使用 "svm_train.java", "svm_predict.java" 並可以進行 SVM 的 training 與 predict 這兩個動作便可以滿足大多數的需求, 不過因為那是命令列的用法因此如果你希望把它當作 library 去使用, 可以需要做些封裝. 這邊我根據自己的使用經驗根據 libsvm.jar 的類別設計, 自己封裝了 "training" 與 "predict" 的用法, 並另外導出一個 klibsvm.jar 方便自己後續寫程式來 Integrate libsvm.jar 來做 automation.
Prepare training data:
SVM 是一個可以支援很高維的 machine learning, 而在 training data 你必須事先定義好 features (feature 的大小決定 training 的維度) ; 而每個 feature 會有自己用來 training 的值, 另外 SVM 是用來解 Classification 的問題, 因此你還需要提供一個 Label 的值 (Classification 的類別). 接著這些資料將會被存成 Vector 送進去的 SVM 進行 training. 在 libsvm 的 README 中的 "Installation and Data Format" 有定義要為進去的資料格式如下 :
其中 <label> 就是類別的種類, 使用數字 1, 2 etc 代表; 而 <index1> 則是說明是哪一個 feature, 而 <value1> 則是對應該 feature 的值.
以等下我要說明的範例, 假設我的 training data 是一個 X/Y 二維平面的座標, 我定義了一個方程式 0.7*X^2 - 10 = Y ; 如果我有一堆座標(x,y), 我定義 x 帶入先前方程式得到的值如果大於等於座標的y 值我定義為類別1, 反之為類別2. 因此這邊我可以假設我有兩個 features1->x, feature2->y ; 接著考慮有座標 (-10,48), 因為 0.7*(-10)^2 - 10 = 60 < 48 -> 得到類別2, 因此我有一筆 Training 的 record 為 :
當然你的 training data 不可能只有一筆 record. 接著我們將之 mapping 到 klibsvm ; 我們的一筆 training 紀錄會使用類別 ksvm.data.Record 來代表, 而該類別上的屬性 label 則代表個該筆紀錄預測的類別. 而每一個 training 紀錄可能有多個 feature, 這邊使用類別 ksvm.data.TData 來代表一個 feature. 你可以使用 Record.addFeature() 來添加 feature 到你的 training 紀錄中. 而TData 上面的屬性 index 代表著第幾個 feature ; value 則是該 feature 的值. 因此如果我們要將剛剛的 training 座標 (-10, 48) 傳換成代碼, 可以參考下面 :
- Record record = new Record();
- record.addFeature(1, -10); // 添加 feature x=-10
- record.addFeature(2, 48); // 添加 feature y=48
- record.label=2; // 設定預測類別2
- package ksvm.data;
- import java.util.Iterator;
- public interface IRecordIter extends Iterator
{}
- package ksvm.data;
- import java.io.BufferedReader;
- import java.io.File;
- import java.io.FileReader;
- import java.io.IOException;
- import java.util.StringTokenizer;
- public class BasicRDIter implements IRecordIter{
- private BufferedReader br = null;
- private String nextProcline = null;
- public BasicRDIter(File trainFile)throws IOException{this(new BufferedReader(new FileReader(trainFile)));}
- public BasicRDIter(BufferedReader br){this.br = br; retriveProcline();}
- protected void retriveProcline()
- {
- try
- {
- do
- {
- nextProcline = br.readLine();
- if(nextProcline!=null &&
- (nextProcline.isEmpty() || nextProcline.startsWith("#"))) continue;
- else break;
- }while(nextProcline!=null);
- if(nextProcline==null) br.close();
- }
- catch(IOException e)
- {
- e.printStackTrace();
- nextProcline = null;
- }
- }
- @Override
- public boolean hasNext() {
- return (nextProcline!=null);
- }
- @Override
- public Record next() {
- if(nextProcline!=null)
- {
- //System.out.printf("\t[Test] line=%s\n", nextProcline);
- StringTokenizer st = new StringTokenizer(nextProcline," \t\n\r\f:");
- Record rd = new Record(st);
- retriveProcline();
- return rd;
- }
- return null;
- }
- @Override
- public void remove() {
- throw new java.lang.UnsupportedOperationException("Not support");
- }
- }
- File trainFile = new File("scatters_train.tf");
- BasicRDIter basicRDIter = new BasicRDIter(trainFile); // 1) Prepare training input data iter
現在知道什麼是 training 紀錄 ; 什麼是 features 與 如何從外部載入 training data. 剩下的就是 training 與 predict. 在 training 的部分簡單到不行, 建立類別 ksvm.run.SVMTrain 物件後再將剛剛載入的 training data 傳入其建構子便完成 training 的準備階段. 完整 Training process 代碼如下 :
- public static void main(String[] args) throws IOException{
- File trainFile = new File("scatters_train.tf");
- File modelFile = new File("scatters.model");
- BasicRDIter basicRDIter = new BasicRDIter(trainFile); // 1) Prepare training input data iter
- SVMTrain train = new SVMTrain(basicRDIter); // 2) Prepare SVMTrain object
- //train.param.C = 10;
- if(train.start()) // 3) Start training
- {
- System.out.printf("\t[Info] Training is done!\n");
- train.saveModel(modelFile); //4) Output training model to external file
- }
- else
- {
- System.out.printf("\t[Info] Something wrong while training:\n");
- for(String em:train.errMsg)
- {
- System.out.printf("\t%s\n", em);
- }
- return;
- }
- }
Predict process :
有了 Training model, 後續的應用便是利用它來對給定的 feature set 進行預測並推論每個紀錄應該是屬於哪一個類別. 接著我們可以透過類別 ksvm.demo.ui.ScatterPlotDemo 將我們剛剛的 training data 用視覺化的效果標示於座標軸上 :
- ScatterPlotDemo demo = new ScatterPlotDemo(new File("scatters_train.tf")); // 載入 training data 並繪於座標上
- demo.pack();
- demo.setVisible(true);
接著有了剛剛我們訓練出來的 model (scatters.model) , 接著我們使用類別 ksvm.run.SVMPredict 對測試 data (scatters_test.tf) 進行 predict. 參考範例代碼如下 :
- File testFile = new File("scatters_test.tf");
- File modelFile = new File("scatters.model"); // Training model file
- File resultFile = new File("scatters_test.pid"); // Output predict result file
- SVMPredict svmPredict = new SVMPredict(modelFile);
- svmPredict.start(new BasicRDIter(testFile), resultFile); // Start predicting
你也可以透過下面的代碼, 將測試的結果視覺化到座標上面 :
- File modelFile = new File("scatters.model");
- File testFile = new File("scatters_test.tf");
- SVMPredict svmPredict = new SVMPredict(modelFile);
- ScatterPlotDemo demo = new ScatterPlotDemo(testFile, svmPredict);
- demo.pack();
- demo.setVisible(true);
Supplement :
* [ libsvm ] 碼上會!Java+libSVM 分析動態資料 (144行)
* [ libsvm ] piaip 的 (lib)SVM 簡易入門
大大 我覺得你的文章很好 可以請你再把klibsvm.jar 載點從新掛上嗎 他失效了 我也想試試看 謝瞜
回覆刪除請試試看下面的 link:
刪除https://www.space.ntu.edu.tw/navigate/s/CCB6C37C279644BEA9BD90640EC88C6CQQY
頁面的 link 已經更新, 如果不行再 refresh 一下頁面試試看. ^^
請問可以改參數等等的嗎? g c等等的
回覆刪除如果 gc (Garbage Collect?) 指的是 java 的命令參數, 當然可以!
刪除如果指的是 libsvm 的命令列參數, 則目前支援的有:
-o: Output file. (SINGLE)
-k: Kernel type. 0=linear; 1=polynomial; 2=radial basis; 3=sigmoid; 4=precomputed kernel(0~4 default=2) (SINGLE)
-p: Show predict probability. (SIGN)
-c: Only for training. Signal for doing cross validation. (SIGN)
-n: nr_fold for cross validation. (SINGLE)
-a: Output answer with prediction. (SIGN)
-s: svm_type : set type of SVM (default 0). 0=C-SVC; 1=nu-SVC; 2=one-class SVM; 3=epsilon-SVR; 4=nu-SVR. (SINGLE)
-t: Task type. Support 'train/predict'. Default is . (SINGLE)
-m: In training->Output model path; In prediction->Load in model path. (SINGLE)
-i: Input file. When in train model, this argument is for corpus file; in predict model, this argument is for test file(s). When multiple files is given, separated them with ':'. (SINGLE)
--COST: Cost : set the parameter C of C-SVC, epsilon-SVR, and nu-SVR (default 1) (SINGLE)
大大你好,請問"現在知道什麼是 training 紀錄 ; 什麼是 features 與 如何從外部載入 training data. 剩下的就是 training 與 predict. 在 training 的部分簡單到不行, 建立類別 ksvm.run.SVMTrain 物件後再將剛剛載入的 training data 傳入其建構子便完成 training 的準備階段."
回覆刪除這段我看不太懂 不知道該怎麼操作~請問可以再解釋一次嗎?
"training 紀錄" 指的就是你的 training input format; " features" 指的就是你有興趣的特徵; "training data" 指的就是一堆 "training 紀錄".
刪除以這邊的範例來說, 一個 "training 紀錄" 就是:
2 1:-10.000 2:48.00
上面說的是 Label/Class=2 中有一筆紀錄是 Feature1=-10, Feature2=48.
而一堆這樣 "training 紀錄" 的東西就叫做 training data
所以你要使用 SVM 之前要先決定你的 Target 是什麼? 如水果甜不甜, 接著決定你的 Feature, 如硬度, 顏色深淺. 然後準備 training data:
刪除=====================================
甜 硬度:1 顏色:2
不甜 硬度:4 顏色:2
...
=====================================
因為 SVM 讀不懂中文, 因此你會將中文用數字取代如 "甜"=1, "不甜"=2, 硬度 (Feature1) = 1, 顏色 (Feature2)=2
FYI
感謝你的回覆,但我要問的不是這個><
回覆刪除請問可以跟您留下mail我在跟您討論嗎? 感謝你
這是我的mail: kevin80388@gmail.com
刪除作者已經移除這則留言。
刪除我 SVMTrain裡面放這個
刪除File trainFile = new File("scatters_train.tf");
BasicRDIter basicRDIter = new BasicRDIter(trainFile);
但是new BasicRDIter(trainFile); 這裡錯誤
我現在最大的問題是不知道哪個類別要放在哪跟著主成程式一起使用 :(
刪除可以貼一下你的錯誤訊息嗎? 另外你的 "scatters_train.tf" 是用我的測試檔案, 還是有改成你自己的訓練資料?
刪除使用流程大約是:
1. 使用 ksvm.run.SVMTrain 傳入訓練資料, 並產生訓練模型
2. 使用 ksvm.run.SVMPredict 載入訓練模型, 並對傳入的測試資料進行預測.
BTW, 我的 email 是 puremonkey2001@yahoo.com.tw
一個完整的訓練與測試代碼如下:
刪除File trainFile = new File("scatters_train.tf");
File modelFile = new File("scatters.model");
File testFile = new File("scatters_test.tf");
// Training -> Generate Model
// 1. Feed in training data
// 2. Output training model
BasicRDIter basicRDIter = new BasicRDIter(trainFile); // 1) Prepare training input data iter
SVMTrain train = new SVMTrain(basicRDIter); // 2) Prepare SVMTrain object
//train.param.C = 10;
if(train.start()) // 3) Start training
{
System.out.printf("\t[Info] Training is done!\n");
train.saveModel(modelFile);
}
else
{
System.out.printf("\t[Info] Something wrong while training:\n");
for(String em:train.errMsg)
{
System.out.printf("\t%s\n", em);
}
return;
}
// Predicting ->
// 1. Loading mode
// 2. Feed in testing data and output prediction result .
File resultFile = new File("scatters_test.pid");
SVMPredict svmPredict = new SVMPredict(modelFile);
svmPredict.start(new BasicRDIter(testFile), resultFile);
感謝你的回覆,我再寄MAIL給你跟您討論。謝謝!!!!
回覆刪除大大 我想問一下為什麼在執行 ScatterPlotDemo demo = new ScatterPlotDemo(new File("scatters_train.tf")); 的時候會
回覆刪除出現 java.lang.NoClassDefFoundError: org/jfree/chart/ChartPanel 的錯誤啊??
該套件有使用到 JFreeChart library (http://www.jfree.org/jfreechart/), 請下載後將 jar 加到執行的 classpath 中.
刪除作者已經移除這則留言。
回覆刪除作者大大你好,有辦法可以擷取出TP FP TN FN 出來嗎? 我想要另外算precision 跟 recall, 但我擷取不出來。 謝謝
回覆刪除請參考下面原始碼:
刪除https://github.com/johnklee/klibsvm/blob/master/src/ksvm/run/SVMPredict.java
中的 API:predict (line 90)
謝謝,但我最後是直接拿產出的out檔跟原test做比對,也是有一樣的效果。感恩
刪除