About Me

我的相片
台北市, Taiwan
我是方選,
方白科技(finebind tech.)共同創辦人,
臺大資管所畢,
希望能幫助更多的人!

FB: http://fb.com/function1122
LINE: http://bit.ly/1foeZft (手機開啟點擊網址自動加入)

最新15則讀者回應

最新文章

FUNction's 上課筆記

Label Cloud

Blog Archive

FeedBurner

追蹤者

碼上會!Java+libSVM 分析動態資料 (144行)

FUNction 於 2011年1月14日 下午3:17 發表

沒想到「碼上會」還會有第二集,標題有點聳動,但在這裡的「動態資料」指的是從資料庫load 出資料(一般libSVM都是標準格式的讀檔案來分析)。此外這篇還涉及處理一個文字探勘(Text Mining)中重要問題 ─ 資料分散(Sparse data)。由於網路上沒有其他實作,所以我想使用以下講解的程式碼,可以讓你加快libSVM的分析效能,也可以提升不少工作效率(不用再輸出多個檔案就能交叉測試)。

libsvm-java
▲將libSVM嵌入你的java程式,從資料庫中撈出資料直接訓練

libSVM簡介
這是台大林智仁老師所開發的SVM工具,應該是世界上最好用且最主流的SVM,許多國內外的研究都透過他完成的。其實SVM之所以會成為主流,是因為它的效率較高,而且結果也不錯,甚至有學者認為SVM可以取代類神經網路(林國峰等,2009)。沿襲「碼上會」的精簡風格,廢話就不多說,想更了解這套工具可以參考以下連結:

大概就這樣吧,想要知道怎麼在Java 中使用,其實可以直接看這篇XD

讓Java 能使用libSVM
為了讓Java 能使用libSVM,必須先下載libSVM的.jar。你可以到官網的這裡下載,也可以點這個連結下載.zip檔案,並解壓縮。解壓縮之後會成為一個資料夾(名稱如libsvm-3.0),你可以瀏覽到裡面的Java 資料夾,就可以找到libsvm.jar。

讓Java能使用libSVM.jar的方法,可以參考上一集中「來吧!我們來斷詞」的第二張圖,將專案的程式庫「新增外部JAR」,瀏覽到libsvm.jar即可。若在建置專案的時候忘記新增,也可以在Eclipse(我假設你用Eclipse啦,因為我現在用Eclipse開發)左側欄中專案名稱上按右鍵,選擇內容,也可以到達設定的對話視窗。

144行Java 程式「碼上會」
好,以下我假設你會用Eclipse建置專案,並且會新增JAR,並新增一個Class。這裡假設我們的Class叫做「libSVMDemo」。建立好類別後,我們先把可能會用到的參考都先import進程式,如下:

import java.io.IOException;
import java.sql.*;
import java.util.Vector;

import libsvm.*;

由於我們會使用到資料庫,所以要import sql.*;;此外也一定要用到libsvm.*啦!接著,我們要在public class libSVMDemo中插入三個全域變數:

svm_parameter _param;
svm_problem _prob;
String _model_file;

其中,

  • svm_parameter:SVM的參數
  • svm_problem:之後會儲存要被分析的資料
  • _model_file:model檔案的路徑


loadData():將資料庫的資料轉換成SVM可以解決的問題


這裡我們使用SQLite作為demo的資料庫,其實在Java中,只需使用SqliteJDBC就可以直接增刪改查資料庫,為了順利進行,你可以在這裡下載JDBC的.jar,並請記得如法炮製將該.jar加入專案中(不用import,只要加入專案路徑即可)。如果想要檢視資料,可以使用免安裝的SQLite Administrator。簡述一下本範例中的訓練資料:共5000筆,使用3000個維度,但是每筆只使用兩個維度,而且可能維度還相同(非常的分散)。



這隻程式先讀取資料庫的資料,將讀出之資料轉換成SVM可以解決的問題。由於SVM分成訓練(Training)階段與測試(Testing)階段,通常拿大部分的資料(前4700筆)作訓練,再拿其他沒被訓練過的(後300筆)作測試,了解SVM的效能,因此程式一開始使用參數「is_training」判斷是哪個階段,撈出不同的資料。我們來看程式:



protected void loadData(boolean is_training){
String limit;
if(is_training){ //訓練階段
System.out.print("Loading training data...");
limit = " WHERE id <= 4700";
}else{ //測試階段
System.out.print("Loading testing data...");
limit = " WHERE id > 4700";
}

int max_index = 0; //紀錄資料中最大的維度(用來產生gamma參數)
_prob = new svm_problem();
Vector vy = new Vector();
Vector vx = new Vector();

try{
Class.forName("org.sqlite.JDBC"); //連接資料庫
Connection conn = DriverManager.getConnection("jdbc:sqlite:sparseData.s3db");
Statement stat = conn.createStatement();
ResultSet rs = stat.executeQuery("SELECT * FROM data"+limit);

while (rs.next()) {
vy.addElement(rs.getDouble("label")); //labal為資料的標籤,為-1或+1
//SVM通常就是解決是好(+1)或壞(-1)的問題

int rdk1 = rs.getInt("rdk1"), rdk2 = rs.getInt("rdk2");
//讀取兩個維度的資料,這裡只有「有這個維度(true)」與「沒有這個維度(false)」
//因此兩個欄位只填寫維度的index,例如填寫30,就代表第30維有資料
//假設rdk1=30,rdk2=2789,則這個node只有這兩個維的值是true,
//其他2998維(比如第31, 32...維)的值都是false
if(rdk1 == rdk2){ //兩個維度的index相等,只放一個
svm_node[] x = new svm_node[1];
x[0] = new svm_node();
x[0].index = rdk1;
x[0].value = 1;
max_index = Math.max(max_index, rdk1);
vx.addElement(x);
}else{
if(rdk2 < rdk1){ //如果第二個index比第一個小,交換
rdk1 = rdk2;
rdk2 = rs.getInt("rdk1");
}

svm_node[] x = new svm_node[2]; //建立SVM node的陣列
x[0] = new svm_node();
x[0].index = rdk1; //維度的index例如30
x[0].value = 1; //有值,為true
x[1] = new svm_node();
x[1].index = rdk2; //維度的index例如2789
x[1].value = 1;
max_index = Math.max(max_index, rdk2); //記下目前用到最大的維度
vx.addElement(x); //儲存SVM node的陣列
}
}
rs.close();
conn.close();
}catch(Exception e){
e.printStackTrace();
}

if(max_index > 0) _param.gamma = 1.0/max_index; // 1/num_features
_prob.l = vy.size(); //svm node的數量
_prob.x = new svm_node[_prob.l][];
for(int i=0;i<_prob.l;i++) _prob.x[i] = vx.elementAt(i); //儲存每個node的向量
_prob.y = new double[_prob.l];
for(int i=0;i<_prob.l;i++) _prob.y[i] = vy.elementAt(i); //儲存每個node的label

System.out.println("Done!!");
}

這邊有兩件事需要非常非常鄭重強調:

  • 由於資料非常分散,所以我們只儲存「有值」的維度,沒有值得維度連new都沒有。根據筆者的經驗,若參考這篇SVM範例的方式建立SVM node,當資料維度一大,系統記憶體非常容易爆掉,出現java.lang.OutOfMemoryError: Java heap space的例外,就算增加配置給JVM的記憶起,只是跑久一點才爆,會更遺憾XD
  • 第二件事是,你會發現我在存入node之前有先對維度比大小,再由小到大插入。這嚴重關係到SVM預測的準確度。如果沒有由小到大插入,SVM一樣會動,但是準確率大概會降低個一半,這點是我測試超久之後才發現的嘔心瀝血之言。


好了,到這裡你已經會把資料庫的資料轉換為SVM的問題,接著的步驟都簡單到不行了!



training():對SVM問題進行訓練


當資料轉換為SVM問題後,我們只需要簡單的幾個步驟就可以將SVM問題訓練成SVM model,請看程式:



protected void training(){
loadData(true); //這邊呼叫loadData(),使用true參數是因為在training階段
//透過loadData,將資料庫的資料儲存在全域變數_prob裡面

System.out.print("Training...");
_model_file = "svm_model.txt"; //指定SVM model儲存的檔案名稱

try{
svm_model model = svm.svm_train(_prob, _param); //訓練SVM model
System.out.println("Done!!");
svm.svm_save_model(_model_file, model); //將訓練結果寫入檔案
}catch(Exception e){
e.printStackTrace();
}
}

到這裡已經建立預測的model了,你是否覺得你的智商被汙辱了呢?



testing():對剩下的資料進行測試

當訓練完之後,我們必須對其他資料進行測試,看看這個model預測的準確率。請看以下程式:



protected void testing(){
loadData(false); //讀取剩下的300分資料,轉換成SVM問題(存在_prob裡)

svm_model model;
int correct = 0, total = 0;
try {
model = svm.svm_load_model(_model_file); //載入model

for(int i=0;i<_prob.l;i++){ //對problem 裡的每個SVM node
double v;
svm_node[] x = _prob.x[i]; //取出svm node
v = svm.svm_predict(model, x); //把node餵給預測器
//這時預測器會依照model與node內的向量資訊,產生預測的數值(-1或1)
total++;
if(v == _prob.y[i]) correct++; //如果跟正確答案一樣,則正確數加一
}

double accuracy = (double)correct/total*100;
System.out.println("Accuracy = "+accuracy+"% ("+correct+"/"+total+")");
} catch (IOException e) {
e.printStackTrace();
}
}

到這裡,其實SVM的部分已經搞定了,你應該已經躍躍欲試的想拿來預測了吧(?),不過還請看完...

libSVMDemo()建構子:產生參數與呼叫訓練/測試方法

直接看程式吧:



libSVMdemo(){
// default values
_param = new svm_parameter();

_param.svm_type = svm_parameter.C_SVC;
_param.kernel_type = svm_parameter.LINEAR;
_param.degree = 3;
_param.gamma = 0; // 1/num_features
_param.coef0 = 0;
_param.nu = 0.5;
_param.cache_size = 100;
_param.C = 1;
_param.eps = 1e-3;
_param.p = 0.1;
_param.shrinking = 1;
_param.probability = 0;
_param.nr_weight = 0;
_param.weight_label = new int[0];
_param.weight = new double[0];

training();
testing();
}

雖然這部分簡單到爆炸,但是我還是要囉嗦兩句:

  • 要注意的是kernel_type參數,這個跟預測的準確率有極高的相關性,一般一分為二我們會使用Linear的Kernel
  • 另外一個可以試的參數是C,在這裡是1,可以把他往下調,看看結果準確率是否有提高。


附件下載:libSVMdemo.zip (解開為Eclipse專案資料夾,裡面包含資料庫、該有的jar)



結語

跟上集「碼上會」一樣,這篇也只是協助你快速上手libSVM,並簡單介紹幾個我遇到的問題的解決方法,希望對未來研究的後輩能有所貢獻。對你有幫助或有任何問題指教,也請不吝惜告知,謝謝!

繼續閱讀全文 碼上會!Java+libSVM 分析動態資料 (144行)