libsvm 2.6 的代碼註釋(支持向量機的神作)

第一節: SVM.h 文件

struct svm_node


int index; 
double value;

}
;


struct svm_node 用來存儲單一向量中的單個特徵,例如

向量x1={ 0.002, 0.345, 4, 5.677}
;
那麼用struct svm_node 來存儲時就使用一個包含5 個svm_node 的數組來存儲此4 維向量,內

映象如下


1 2 3 4 -1 
0.002 0.345 4.000 5.677 空

其中如果value 爲0.00,該特徵將不會被存儲,其中(特徵3)被跳過:

1 2 4 5 -1 
0.002 0.345 4.000 5.677 空

0.00 不保留的好處在於,做點乘的時候,可以加快計算速度,對於稀疏矩陣,更能充分體現這種
數據結構的優勢。但做歸一化時,操作就比較麻煩了。
(類型轉換不再說明)
struct svm_problem


int l; 
double *y; 
struct svm_node **x;

};

struct svm_problem 存儲本次參加運算的所有樣本(數據集),及其所屬類別。在某些數據挖掘
實現中,常用DataSet來實現。
int l; 記錄樣本總數
double *y; 指向樣本所屬類別的數組。在多類問題中,因爲使用了one-agianst-one 方法,可能原始
樣本中y[i] 的內容是1.0,2.0,3.0,…,但參與多類計算時,參加分類的兩類所對應的y[i] 內容是+1, 
和-1。
Struct svm_node **x;指向一個存儲內容爲指針的數組;
如下圖,最右邊的四個長條格同上表,存儲三維數據。(黑邊框的是最主要的部分)

L=
4


Y[3] 
Y[2] 
Y[1] 
Y[0] 
Y*


X*
*

 

上海交通大學模式分析與機器智能實驗室

這樣的數據結構有一個直接的好處,可以用x[i][j] 來訪問其中的某一元素(如果value 爲0.00 
的也全部保留的話)
私下認爲其中有一個敗筆,就是把svm_node* x_space 放到結構外面去了。

enum { C_SVC, NU_SVC, ONE_CLASS, EPSILON_SVR, NU_SVR };/* svm_type */ 
enum { LINEAR, POLY, RBF, SIGMOID }; /* kernel_type */

struct svm_parameter


int svm_type;//SVM類型,見前enum 
int kernel_type;//核函數
double degree; /* for poly */ 
double gamma;/* for poly/rbf/sigmoid */ 
double coef0; /* for poly/sigmoid */

/* these are for training only */

double cache_size; /* in MB *
/
double eps; /* stopping criteria *
/
double C; /* for C_SVC, EPSILON_SVR and NU_SVR *
/
int nr_weight; /* for C_SVC *
/
int *weight_label; /* for C_SVC *
/
double* weight; /* for C_SVC *
/
double nu; /* for NU_SVC, ONE_CLASS, and NU_SVR *
/
double p; /* for EPSILON_SVR *
/
int shrinking; /* use the shrinking heuristics *
/
int probability; /* do probability estimates *
/


}

部分參數解釋,(附核函數
)


1、K (xi , xj ) 

xiT x j

2、K (xi , xj ) 

(γxiTx j 

r)d ,γ> 
0

2

3、K (xi , xj ) 

exp(.
γ

xi 

xj

),γ> 
0

4、K (xi , xj ) 

tanh(γxiT x j 

r)

double degree;//就是2式中的d 
double gamma; //就是2,3,4式中的gamma 
double coef0;//就是2,4式中的r

double cache_size; /* in MB */ 制定訓練所需要的內存,默認是40M,LibSVM2.5 中是4M, 所以自

己做開發選LibSVM2.5 還是不錯的!

double eps;見參考文獻[1]中式3.13 
double C;//沒什麼好說的,懲罰因子,越大訓練的模型越那個…,當然耗的時間越多


上海交通大學模式分析與機器智能實驗室

int nr_weight;//權重的數目,目前在實例代碼中只有兩個值,一個是默認0,另外一個是
svm_binary_svc_probability 函數中使用數值2。
int *weight_label;//權重,元素個數由nr_weight 決定. 
double nu;// 沒什麼好說的,too 
double p;// 沒什麼好說的,three

int shrinking;//指明訓練過程是否使用壓縮。
int probability;//新增,指明是否要做概率估計

struct svm_model


svm_parameter param; // parameter 
int nr_class; // number of classes, = 2 in regression/one class svm 
int l; // total #SV 
svm_node **SV; // SVs (SV[l]) 
double **sv_coef; // coefficients for SVs in decision functions (sv_coef[n-1][l]) 
double *rho; // constants in decision functions (rho[n*(n-1)/2]) 
double *probA; // pariwise probability information 
double *probB;

// for classification only

int *label; // label of each class (label[n]) 
int *nSV; // number of SVs for each class (nSV[n]) 
// nSV[0] + nSV[1] + ... + nSV[n-1] = l 
// XXX 
int free_sv; // 1 if svm_model is created by svm_load_model 
// 0 if svm_model is created by svm_train 
};

結構體svm_model 用於保存訓練後的訓練模型,當然原來的訓練參數也必須保留。
svm_parameter param; // 訓練參數
int nr_class;// 類別數
int l; // 支持向量數
svm_node **SV; // 保存支持向量的指針,至於支持向量的內容,如果是從文件中讀取,內容會
額外保留;如果是直接訓練得來,則保留在原來的訓練集中。如果訓練完成後需要預報,原來的
訓練集內存不可以釋放。
double **sv_coef;//相當於判別函數中的alpha 
double *rho; //相當於判別函數中的b 
double *probA; // pariwise probability information 
double *probB;//均爲新增函數
int *label; // label of each class (label[n]) 
int *nSV; // number of SVs for each class (nSV[n]) 
int free_sv;//見svm_node **SV的註釋


上海交通大學模式分析與機器智能實驗室

//以下接口函數設計得非常合理,最後一節詳細說

//最主要的驅動函數,訓練數

struct svm_model *svm_train(const struct svm_problem *prob, const struct svm_parameter *param)
;


//用SVM做交叉驗

void svm_cross_validation(const struct svm_problem *prob, const struct svm_parameter *param, int
nr_fold, double *target)
;


//保存訓練好的模型到文

int svm_save_model(const char *model_file_name, const struct svm_model *model)
;


//從文件中把訓練好的模型讀到內存

struct svm_model *svm_load_model(const char *model_file_name)
;


/
/


int svm_get_svm_type(const struct svm_model *model)
;


//得到數據集的類別數(必須經過訓練得到模型後纔可以用

int svm_get_nr_class(const struct svm_model *model)
;


//得到數據集的類別標號(必須經過訓練得到模型後纔可以用

void svm_get_labels(const struct svm_model *model, int *label)
;


//LibSvm2.6 新增函

double svm_get_svr_probability(const struct svm_model *model)
;


//用訓練好的模型預報樣本的值,輸出結果保留到數組中。(並非接口函數

void svm_predict_values(const struct svm_model *model, const struct svm_node *x, double* 
dec_values)
;


//預報某一樣本的

double svm_predict(const struct svm_model *model, const struct svm_node *x)
;


// LibSvm2.6 新增函

double svm_predict_probability(const struct svm_model *model, const struct svm_node *x, double* 
prob_estimates)
;


//消除訓練的模型,釋放資

void svm_destroy_model(struct svm_model *model)
;


// LibSvm2.6 新增函

void svm_destroy_param(struct svm_parameter *param)
;


//檢查輸入的參數,保證後面的訓練能正常進行。


上海交通大學模式分析與機器智能實驗室

const char *svm_check_parameter(const struct svm_problem *prob, const struct svm_parameter
*param)
;


// LibSvm2.6 新增函

int svm_check_probability_model(const struct svm_model *model)
;

 

上海交通大學模式分析與機器智能實驗室

第二節: SVM.cpp 文件

.頭文件:
從整個.cpp 文件來看,感覺有些頭文件是多餘的,不知何故,反正多包含頭文件不會犯錯。
後面的typedef, 特別是typedef float Qfloat, 是爲了方便控制內存存儲的精度。

#include <math.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <ctype.h> 
#include <float.h> 
#include <string.h> 
#include <stdarg.h> 
#include "svm.h" 
typedef float Qfloat; 
typedef signed char schar;

//.以下是定義的幾個主要的模板,主要是爲了比較大小,交換數據和完全複製數據。
Min() 和Max() 在<math.h>中提供了相應的函數,這裏的處理,估計是爲了使函數內聯,執行速度
會相對快一些,而且不同的數據類型,存儲方式不同,使用模板會更有針對性,也從另外一方面
提高程序性能。

#ifndef min 
template <class T> inline T min(T x,T y) { return (x<y)?x:y; } 
#endif

#ifndef max 
template <class T> inline T max(T x,T y) { return (x>y)?x:y; } 
#endif

template <class T> inline void swap(T& x, T& y) { T t=x; x=y; y=t; }

//這裏的克隆函數是完全克隆,不同於一般的複製。操作結束後,內部的所有數據和指針完全一
樣。

template <class S, class T> inline void clone(T*& dst, S* src, int n)


dst = new T[n]; 
memcpy((void *)dst,(void *)src,sizeof(T)*n);

}

//這裏使用了define ,非內聯函數

#define INF HUGE_VAL
#define Malloc(type,n) (type *)malloc((n)*sizeof(type)
)

 

上海交通大學模式分析與機器智能實驗室

//以下的函數用作調試。跳過~

#if 1 
void info(char *fmt,...) 
{

va_list ap; 
va_start(ap,fmt)

vprintf(fmt,ap)

va_end(ap)
;



void info_flush() 
{

fflush(stdout); 

#elsevoid info(char *fmt,...) {} 
void info_flush() {} 
#endif

//以下部分爲svm.cpp 中的類繼承和組合圖: (實線表示繼承關係,虛線表示組合關係)

Cache 
Kernel 
ONE_CLASS_
Q


SVC_
Q


SVR_
Q


Solver


Solver_NU

2.1 類Cache 
本類主要負責運算所涉及的內存的管理,包括申請、釋放等。
類定義:

class Cache

{

public: 
Cache(int l,int size); 
~Cache(); 
int get_data(const int index, Qfloat **data, int len); 
void swap_index(int i, int j); // future_option

private: 
int l; 
int size; 
struct head_t

 {


上海交通大學模式分析與機器智能實驗室

head_t *prev, *next; // a cicular list

Qfloat *data;

int len; // data[0,len) is cached in this entry

 };

head_t* head;

head_t lru_head;

void lru_delete(head_t *h);

void lru_insert(head_t *h); 
};

成員變量:

head_t* head; // 變量指針,該指針用來記錄程序所申請的內存,單塊申請到的內存用struct 
head_t來記錄所申請內存的指針,並記錄長度。而且通過雙向的指針,形成鏈表,增加尋址的速
度。記錄所有申請到的內存,一方面便於釋放內存,另外方便在內存不夠時適當釋放一部分已經
申請到的內存。

head_t lru_head; //雙向鏈表的頭。

int l; //樣本總數。

int size; //所指定的全部內存,據說用Mb做單位。

成員函數:

void lru_delete(head_t *h); //從雙向鏈表中刪除某個元素的鏈接,不刪除、不釋放該元素所
涉及的內存。一般是刪除當前所指向的元素。

void lru_insert(head_t *h); //在鏈表後面插入一個新的鏈接;

Head 
Lru_head 
Cache(int l,int size);

構造函數。該函數根據樣本數L,申請L 個head_t 的空間。根據說明,該區域會初始化爲0, 
(表示懷疑)。Lru_head 因爲尚沒有head_t 中申請到內存,故雙向鏈表指向自己。至於size 的
處理,先將原來的byte 數目轉化爲float 的數目,然後扣除L 個head_t 的內存數目。size 爲程序
指定的內存大小4M/40M 。size 不要設得太小。

int get_data(const int index, Qfloat **data, int len);

該函數保證head_t[index] 中至少有len 個float 的內存,並且將可以使用的內存塊的指針放在
data 指針中。返回值爲申請到的內存。

函數首先將head_t[index] 從鏈表中斷開,如果head_t[index] 原來沒有分配內存,則跳過斷開這
步。計算當前head_t[index] 已經申請到的內存,如果不夠,釋放部分內存(懷疑這樣做的動機:
老數據爲什麼就可以釋放,而不真的另外申請一塊?老數據沒用了?),等內存足夠後,重新分
配內存。重新使head_t[index] 進入雙向鏈表。並返回申請到的內存的長度。

//返回值不爲申請到的內存的長度,爲head_t[index] 原來的數據長度h->len 。


上海交通大學模式分析與機器智能實驗室

調用該函數後,程序會計算Q =
∑ 
yiy jK (xi , xj ) 的值,並將其填入data 所指向的內存區

域,如果下次index 不變,正常情況下,不用重新計算該區域的值。若index 不變,則get_data() 
返回值len 與本次傳入的len 一致,從Kernel::get_Q( ) 中可以看到,程序不會重新計算。從而提
高運算速度。

While 循環內的部分基本上難得用到一次。

void swap_index(int i, int j);

交換head_t[i] 和head_t[j] 的內容,先從雙向鏈表中斷開,交換後重新進入雙向鏈表中。對後
面的處理不理解,可能是防止中head_t[i] 和head_t[j] 可能有一方並未申請內存。但h->len > i 和
h->len > j 無法解釋。

for(head_t *h = lru_head.next; h!=&lru_head; h=h->next)


if(h->len > i) 
{


if(h->len > j) 
swap(h->data[i],h->data[j])
;


else

 {

// give up

lru_delete(h)

free(h->data)

size += h->len; 
h->data = 0; 
h->len = 0;




}


2.2 類Kernel 
class Kernel {

public: 
Kernel(int l, svm_node * const * x, const svm_parameter& param); 
virtual ~Kernel();

static double k_function(const svm_node *x, const svm_node *y, const svm_parameter& param)

virtual Qfloat *get_Q(int column, int len) const = 0; 
virtual void swap_index(int i, int j) const // no so const..

{


swap(x[i],x[j]); 
if(x_square) swap(x_square[i],x_square[j]); 

protected: 
double (Kernel::*kernel_function)(int i, int j) const;


上海交通大學模式分析與機器智能實驗室

private: 
const svm_node **x; 
double *x_square;

// svm_parameter

const int kernel_type; 
const double degree; 
const double gamma; 
const double coef0;


static double dot(const svm_node *px, const svm_node *py)

double kernel_linear(int i, int j) const(skipped)
double kernel_poly(int i, int j) const(skipped)
double kernel_rbf(int i, int j) const(skipped)
double kernel_sigmoid(int i, int j) const(skipped)


};

成員變量:

const svm_node **x; // 用來指向樣本數據,每次數據傳入時通過克隆函數來實現,完全重新

分配內存,主要是爲處理多類着想。
double *x_square; //使用RBF 核才使用。
const int kernel_type; //核函數類型. 
const double degree; // kernel_function 
const double gamma; // kernel_function 
const double coef0; // kernel_function

成員函數:

Kernel(int l, svm_node * const * x, const svm_parameter& param); 
構造函數。初始化類中的部分常量、指定核函數、克隆樣本數據。如果使用RBF 核函數,
則計算x-sqare[i].

static double dot(const svm_node *px, const svm_node *py); 
點乘兩個樣本數據,按svm_node 中index ( 一般爲特徵)進行運算,一般來說,index 中1,2,… 
直到-1。返回點乘總和。
例如:x1 = { 1,2,3} , x2 = {4, 5, 6} 總和爲sum = 1*4 + 2*5 + 3*6 ; 在svm_node[3] 中存儲index 
= -1 時,停止計算。

static double k_function(const svm_node *x, const svm_node *y, const svm_parameter& param);

核函數。但只有在預報時纔用到。

其中RBF 部分很有講究。因爲存儲時,0 值不保留。如果所有0 值都保留,第一個while 
就可以都做完了;如果第一個while 做不完,在x,y 中任意一個出現index = -1,第一個while 
就停止,剩下的代碼中兩個while 只會有一個工作,該循環直接把剩下的計算做完。


上海交通大學模式分析與機器智能實驗室

virtual Qfloat *get_Q(int column, int len) const = 0;

純虛函數,將來在子類中實現。相當重要的函數。

virtual void swap_index(int i, int j) 
虛函數,x[i] 和x[j]中所存儲指針的內容。如果x_square 不爲空,則交換相應的內容


double (Kernel::*kernel_function)(int i, int j) const; 
函數指針,根據相應的核函數類型,來決定所使用的函數。在計算矩陣Q 時使用


Q =
∑ 
yiy jK(xi , xj )

1、K (xi , xj ) 

xiT x j

2、K (xi , xj ) 

(γxiTx j 

r)d ,γ> 
0

2

3、K (xi , xj ) 

exp(.
γ

xi 

xj

),γ> 
0

4、K (xi , xj ) 

tanh(γxiT x j 

r)

2.2 類Solver 
class Solver {

public: 
Solver() {}; 
virtual ~Solver() {};

struct SolutionInfo 

double obj; 
double rho; 
double upper_bound_p; 
double upper_bound_n; 
double r; // for Solver_NU


 };

void Solve(int l, const Kernel& Q, const double *b_, const schar *y_

double *alpha_, double Cp, double Cn, double eps, 
SolutionInfo* si, int shrinking)
;


protected: 
int active_size; 
schar *y; 
double *G; // gradient of objective function 
enum { LOWER_BOUND, UPPER_BOUND, FREE }; 
char *alpha_status; // LOWER_BOUND, UPPER_BOUND, FREE 
double *alpha;


上海交通大學模式分析與機器智能實驗室

const Kernel *Q; 
double eps; 
double Cp,Cn; 
double *b; 
int *active_set; 
double *G_bar; // gradient, if we treat free variables as 0 
int l; 
bool unshrinked; // XXX

double get_C(int i) { 

void update_alpha_status(int i) { 

bool is_upper_bound(int i) { return alpha_status[i] == UPPER_BOUND; 

bool is_lower_bound(int i) { return alpha_status[i] == LOWER_BOUND; 

bool is_free(int i) { return alpha_status[i] == FREE; 

void swap_index(int i, int j)

void reconstruct_gradient()

virtual int select_working_set(int &i, int &j)

virtual double calculate_rho()

virtual void do_shrinking()
;


};

成員變量:

int active_size; // 計算時實際參加運算的樣本數目,經過shrink 處理後,該數目會小於全部

樣本總數。
schar *y; //樣本所屬類別,該值只取+1/-1 。雖然可以處理多類,最終是用兩類SVM 完成的。
double *G; //梯度,計算公式如下(公式3.5)[1]

(Qα+ 
p)t =.f (α)t 

Gt

在代碼實現中,用b[i] 來代替公式中的p。
char *alpha_status; //α[i]的狀態,根據情況分爲α[i] 
≤ 
0, α[i] 
≥ 
c 和0 <α[i] 

0 ,分別

對應內部點(非SV),錯分點(BSV)和支持向量(SV)。

double *alpha; //αi

const Kernel *Q; //指定核。核函數和Solver 相互結合,可以產生多種SVC,SVR 
double eps; //誤差限
double *b; //見double *G 的說明。
int *active_set; //

.

double *G_bar; // G ,(這名字取的)。計算公式如下:

.



C ΣQij ,i 

1,...,l

α 
j =C

該值可以在對樣本集做shrink 時,減小重建梯度的計算量。


上海交通大學模式分析與機器智能實驗室





+
∑ 
Qijαj =Σ(l) Qijα 

0<α<Cj=1

int l; //樣本總

bool unshrinked; /
/


成員函數:

double get_C(int i) 
返回對應於樣本的C。設置不同的Cp 和Cn 是爲了處理數據的不平衡。見《 6 Unbalanced 
data 》[1],有時Cp=Cn 。

void swap_index(int i, int j)

完全交換樣本i 和樣本j 的內容,包括所申請的內存的地址


void reconstruct_gradient();

重新計算梯度。G_bar[i] 在初始化時並未加入b[i] ,所以程序首先增加b[i] 。Shrink 後依然參
加運算的樣本位於active_size 和L-1 位置上。在0~active_size 之間的alpha[i] 如果在區間(0,c) 上,
纔有必要更新相應的active_size 和L-1 位置上的樣本的梯度。

virtual int select_working_set(int &i, int &j)

選擇工作集。公式如下:


≡ 
argmax({..f (α)t | yt 

1,αt 

C},{.f (α)t | yt =.1,αt 

0} 

≡ 
argmin({.f (α)t | yt =.1,αt 

C},{..f (α)t | yt 

1,αt 

0}

virtual void do_shrinking();

對樣本集做縮減。大致是當0 <
α < 
C 時,(還有兩種情況)程序認爲該樣本可以不參加下次
迭代。(0 <
α < 
C 時,爲內部點)程序會減小active_size,爲(內部點)增加位置。active_size 
表明了不可以參加下次迭代的樣本的最小標號,在active_size 與L 之間的元素都對分類沒有貢
獻。

程序中k--是爲了消除交換後的影響,使重新換來的樣本也被檢查一次。
如果程序在縮減一次後沒有達到結束條件,就重新構造梯度矢量,並再縮減一次(總覺得這
裏不太嚴密)。

virtual double calculate_rho();

計算
ρ 
值。見3.7[1]節,The calculation of b or 
ρ

Σ0 C yi 1.f (α)i

r1 
=

1

ΣαC, yi=<<
=<<
0,
α1

ρ= 
r1 

r2

2


上海交通大學模式分析與機器智能實驗室

void Solve(int l, const Kernel& Q, const double *b_, const schar *y_, 
double *alpha_, double Cp, double Cn, double eps, 
SolutionInfo* si, int shrinking);

//程序較大,逐步分解 
part 1

// initialize alpha_status

 { 
alpha_status = new char[l]; 
for(int i=0;i<l;i++)

update_alpha_status(i); 

更新一下alpha 的狀態

part 2

// initialize active set (for shrinking)

 { 
active_set = new int[l]; 
for(int i=0;i<l;i++)

active_set[i] = i; 
active_size = l; 
}

爲縮減做準備,將來要做交換

part 3

// initialize gradient

 { 
G = new double[l]; 
G_bar = new double[l]; 
int i; 
for(i=0;i<l;i++) 
{

G[i] = b[i];

G_bar[i] = 0; 
}
for(i=0;i<l;i++
)


if(!is_lower_bound(i))

{
Qfloat *Q_i = Q.get_Q(i,l)

double alpha_i = alpha[i]

int j; 
for(j=0;j<l;j++
)


G[j] += alpha_i*Q_i[j]; 
if(is_upper_bound(i)) 
for(j=0;j<l;j++)


上海交通大學模式分析與機器智能實驗室

G_bar[j] += get_C(i) * Q_i[j];



G_bar[j]的生成公式如下:(注意,其中不包含b[i] 的值)

G(.) = 
C ΣQij ,i 

1,...,l

α 
j =C

因爲第一次建立G(i),所以沒有判斷alpha 的狀態。而是按公式,全部計算了一遍。

get_Q(i,l)返回的值是Qij 矩陣中的第i 列,而不是第i 行,這是需要注意的地方。

再往下是大循環:
如果有必要,先進行篩選,使部分數據不再參加運算;選擇工作集;更新alpha_i, alpha_j, 其更新

new new old old

的思路是保證:αi yi +
α 
j yj =αi yi +
α 
j yj ;對於邊界情況,有特殊處理,主要是考慮

0 ≤αi 
≤ 
Ci 的要求。當某一alpha 小於0時,做適當調整,調整的結果是alpha_i, alpha_j 仍然在

0 ≤αi 
≤ 
Ci 範圍內,同時其和同原來一樣。對於推導過程,可以參考Sequential Minimal

Optimization for SVM

part 4

更新G(i),根據αi ,
α 
j 的變化更新;

// update G

double delta_alpha_i = alpha[i] -old_alpha_i; 
double delta_alpha_j = alpha[j] -old_alpha_j;


for(int k=0;k<active_size;k++


G[k] += Q_i[k]*delta_alpha_i + Q_j[k]*delta_alpha_j; 
}


part 5

_

以下是更新alpha_status 和G ,ahpha 狀態更新較簡單,根據alpha 狀態前後是否有變化,適

.

當更新,更新的內容參考公式G 

C ΣQij ,i 

1,...,l

α 
j =C

// update alpha_status and G_bar

 

bool ui = is_upper_bound(i)

bool uj = is_upper_bound(j)

update_alpha_status(i)
;

 

上海交通大學模式分析與機器智能實驗室

update_alpha_status(j)

int k; 
if(ui != is_upper_bound(i))//更新alpha_i 的影
響 
{


Q_i = Q.get_Q(i,l)

if(ui) 
for(k=0;k<l;k++

G_bar[k] -= C_i * Q_i[k]
;


else
for(k=0;k<l;k++
)


G_bar[k] += C_i * Q_i[k]

}
if(uj != is_upper_bound(j)) //更新alpha_j 的影
響 
{


Q_j = Q.get_Q(j,l)

if(uj) 
for(k=0;k<l;k++

G_bar[k] -= C_j * Q_j[k]
;


else 
for(k=0;k<l;k++) 
G_bar[k] += C_j * Q_j[k]; 

}

part 6

以下計算目標函數值,因爲Gt 

(Qα+ 
p)t ,而目標值爲
12 
α 
TQα+ 
pT 
α 
,故:

// calculate objective value

 { 
double v = 0; 
int i; 
for(i=0;i<l;i++)

v += alpha[i] * (G[i] + b[i]);

si->obj = v/2; 
}

part 7

回送結果。

// put back the solution

 { 
for(int i=0;i<l;i++) 
alpha_[active_set[i]] = alpha[i]; 
}


上海交通大學模式分析與機器智能實驗室

2.3 類Solver_NU 
class Solver_NU : public Solver 

public:

Solver_NU() {}

void Solve(int l, const Kernel& Q, const double *b, const schar *y, 
double *alpha, double Cp, double Cn, double eps, 
SolutionInfo* si, int shrinking)



this->si = si; 
Solver::Solve(l,Q,b,y,alpha,Cp,Cn,eps,si,shrinking)
;


}

private: 
SolutionInfo *si; 
int select_working_set(int &i, int &j); 
double calculate_rho(); 
void do_shrinking();

};

其中函數void Solve()完全調用了Solve::Solve(),this->si = si;一句是因爲C++內部變量訪問的限制
而添加。

成員函數:

int select_working_set(int &i, int &j)

選擇工作集,參考[1],[4],[5], 同時可以參考Solver::select_working_set 


double calculate_rho(); 
計算
ρ 
值,參考[1],[4],[5] (對應libsvm 論文[1] ,其實返回值是b,這可以從後面預測目標值
可以看出。與Solver::calculate_rho 相比,增加了另外一個返回值,r,該值纔是真正的
ρ 
值。

void do_shrinking()

對樣本進行剪裁,參考[1],[4],[5] , 同時可以參考Solver::do_shrinking()


2.4 類SVC_Q 
class SVC_Q: public Kernel 

public:

SVC_Q(const svm_problem& prob, const svm_parameter& param, const schar *y_

:Kernel(prob.l, prob.x, param) 
{

 

上海交通大學模式分析與機器智能實驗室

clone(y,y_,prob.l)

cache = new Cache(prob.l,(int)(param.cache_size*(1<<20)))

}


Qfloat *get_Q(int i, int len) const

 

Qfloat *data; 
int start; 
if((start = cache->get_data(i,&data,len)) < len) 
{


for(int j=start;j<len;j++)

data[j] = (Qfloat)(y[i]*y[j]*(this->*kernel_function)(i,j))

}
return data;


}

void swap_index(int i, int j) const

 

cache->swap_index(i,j)

Kernel::swap_index(i,j)

swap(y[i],y[j])
;


}

~SVC_Q()


delete[ ] y; 
delete cache;


}

private: 
schar *y; 
Cache *cache;

};

說明:

SVC_Q(const svm_problem& prob, const svm_parameter& param, const schar *y_) 
:Kernel(prob.l, prob.x, param) 
該構造函數利用初始化列表Kernel(prob.l, prob.x, param)將樣本數據和參數傳入(非常簡潔)。

get_Q(int i, int len)函數與其他同類相比,在於核函數不同。
swap_index(int i, int j) //交換的東西太多了點

2.5 類ONE_CLASS_Q 
class ONE_CLASS_Q: public Kernel 
{


上海交通大學模式分析與機器智能實驗室

public: 
ONE_CLASS_Q(const svm_problem& prob, const svm_parameter& param) 
:Kernel(prob.l, prob.x, param) 
{

cache = new Cache(prob.l,(int)(param.cache_size*(1<<20)))

}


Qfloat *get_Q(int i, int len) const

 

Qfloat *data; 
int start; 
if((start = cache->get_data(i,&data,len)) < len) 
{


for(int j=start;j<len;j++)

data[j] = (Qfloat)(this->*kernel_function)(i,j)

}
return data;


}

void swap_index(int i, int j) const

 

cache->swap_index(i,j)

Kernel::swap_index(i,j)
;


}

~ONE_CLASS_Q() 

delete cache; 

private: 
Cache *cache; 
};

ONE_CLASS_Q 只處理1 類分類問題(?) ,故不保留y[i] 。編號只有1 類。
get_Q(int i, int len)函數中缺少了y[i],y[j] ,這與One_Class 本身特點有關,只處理一類。
swap_index(int i, int j)少swap(y[i],y[j]); 這句,因爲根本沒有y[i] 可供交換。

2.5 類SVR_Q 
class SVR_Q: public Kernel 

public:

SVR_Q(const svm_problem& prob, const svm_parameter& param) 
:Kernel(prob.l, prob.x, param)

 

上海交通大學模式分析與機器智能實驗室

 

//skipped 
}


void swap_index(int i, int j) const

 

swap(sign[i],sign[j])

swap(index[i],index[j])
;


}

Qfloat *get_Q(int i, int len) const 

//skipped 
}


~SVR_Q(


//skipped 
}


private: 
int l; 
Cache *cache; 
schar *sign; 
int *index; 
mutable int next_buffer; 
Qfloat* buffer[2];

}; 
本類主要是用於做迴歸,同分類有許多不同之處。參考[1],[5]

//以下的函數全爲靜態函數,只能在本文件範圍內被訪問。對照[1] 中公式查看。

2.6 函數solve_c_svc 
static void solve_c_svc(const svm_problem *prob, const svm_parameter* param, 
double *alpha, Solver::SolutionInfo* si, double Cp, double Cn)

在公式

α 
TQα+ 
pTα 
中,pT 爲全-1,另外alpha[i]=0, 保證yT α= 
0 的限制條件,在將來選
2

擇工作集後更新alpha 時,仍能保證該限制條件。

2.7 函數solve_nu_svc 
static void solve_nu_svc( const svm_problem *prob, const svm_parameter *param, 
double *alpha, Solver::SolutionInfo* si)

pT 爲全0,alpha[i] 能保證eT α= 
0, yTα= 
0.

2.8 函數solve_one_class

上海交通大學模式分析與機器智能實驗室

static void solve_one_class(const svm_problem *prob, const svm_parameter *param, 
double *alpha, Solver::SolutionInfo* si)

限制條件eTα= 
vl ,前vl 個alpha 爲1,此後的alpha 全爲0,初始條件滿足限制條件eTα= 
vl

pT 爲全0,y 爲全1

2.9 函數solve_epsilon_svr 
static void solve_epsilon_svr(const svm_problem *prob, const svm_parameter *param, 
double *alpha, Solver::SolutionInfo* si)

2.10 函數solve_nu_svr 
static void solve_nu_svr( const svm_problem *prob, const svm_parameter *param, 
double *alpha, Solver::SolutionInfo* si)

第三節:接口函數、流程

decision_function svm_train_one(const svm_problem *prob, const svm_parameter *param, 
double Cp, double Cn)

訓練一組樣本集,通常參加訓練的樣本集只有兩類。
程序根據相應的參數,選擇所使用的訓練或者擬合算法。(這個地方的代碼居然如此少),最後統
計SV和BSV,最後輸出決策函數。

void sigmoid_train( int l, const double *dec_values, const double *labels,

double& A, double& B) 
LibSVM2.6 新增函數

根據預報值來確定A,B rij 
≈ 
1+ 


Af. +B 
見第8 節[1], 其中A,B 的確定就由本函數確定。

double sigmoid_predict(double decision_value, double A, double B)

LibSVM2.6 新增函數

可以看看,裏面的公式很簡單。

void multiclass_probability(int k, double **r, double *p)

LibSVM2.6 新增函數

(好像比較複雜哦. )

void svm_binary_svc_probability(const svm_problem *prob, const svm_parameter *param, 
double Cp, double Cn, double& probA, double& probB)

LibSVM2.6 新增函數


上海交通大學模式分析與機器智能實驗室

先做交叉驗證,然後用決策值來做概率估計。需要調用sigmoid_train 函數。

double svm_svr_probability( const svm_problem *prob, const svm_parameter *param)

LibSVM2.6 新增函數

先做交叉驗證,然後函數經過計算後,輸出概率值。

svm_model *svm_train(const svm_problem *prob, const svm_parameter *param)

根據選擇的算法,來組織參加訓練的分樣本,以及進行訓練結果的保存。其中會對樣本進行初步
的統計。
一、分類部分:

→統計類別總數,同時記錄類別的標號,統計每個類的樣本數目 
→將屬於相同類的樣本分組,連續存放 
→計算權重C 
→訓練n(n-1)/2個模型 
→初始化nozero數組,便於統計SV 
→//初始化概率數組 
→訓練過程中,需要重建子數據集,樣本的特徵不變,但樣本的類別要改爲+1/-1 
→//如果有必要,先調用svm_binary_svc_probability 
→訓練子數據集svm_train_one 
→統計一下nozero,如果nozero已經是真,就不變,如果爲假,則改爲真 
→輸出模型 
→主要是填充svm_model, 
→清除內存 
二、迴歸部分:

→類別數固定爲2 
→//選擇性地做svm_svr_probability, one-class不做概率估計 
→訓練 
→輸出模型 
→清除內存 
訓練過程函數調用:
svm_train→svm_train_one→solve_c_svc(fox example)→

→Solver s;//這裏調用構造函數,但啥也沒有做。
→s.Solve(l, SVC_Q(*prob,*param,y), minus_ones, y, alpha, Cp, Cn, param->eps, si, 
param->shrinking); 
→調用SVC_Q(Kernel) 類的構造函數,同時也會調用Kernel類的構造函數。在SVC_Q 
類的構造函數中複製目標值(y), 同時申請內存,此時激發Cache類,申請內存,構造雙向列表等。
→Solve函數做完其他部分工作,主要是算法的實現。
void svm_cross_validation(const svm_problem *prob, const svm_parameter *param, int nr_fold, 
double *target)

LibSVM2.6 新增函數,LibSVM2.5中爲示例函數。


上海交通大學模式分析與機器智能實驗室

先隨機打亂次序,然後根據n折的數目,留一份作爲測試集,其他的作爲訓練集,做n次。
隨機打亂次序使用的非標準的撲克洗牌的算法。(LibSVM2.5 裏面隨機排序的結果很亂) 
For example: 
樣本集被分爲10份;第一次,將樣本集的第2~10部分作爲整體進行訓練,得到一個模型,然後
對樣本集的第1部分進行預報,得到一個精度;第二次,將樣本集的第1,3~10作爲整體訓練,
對第二部分進行預報,得到又一個精度,…。最後對10個精度做一下處理(方法很多,不逐一列
出)。

int svm_get_nr_class(const svm_model *model)

獲得樣本類別數;本函數爲典型的馬後炮。

void svm_get_labels(const svm_model *model, int* label)

某類樣本的標號(樣本並不按編號排列,通過標號,可以循序訪問樣本集)。

double svm_get_svr_probability(const svm_model *model)

訪問訓練好的模型中的概率值。

void svm_predict_values(const svm_model *model, const svm_node *x, double* dec_values)

預測樣本數據目標值;
如果是做分類問題,返回一大堆值,供後續的函數做決策;如果是迴歸問題,返回一個值。
其中one-v-one 方法需要做n(n-1)/2 次,產生n(n-1)/2 個預報值。

double svm_predict(const svm_model *model, const svm_node *x) 
預測,分類問題主要使用了One-to-One方法組織n*(n-1)/2 種方法。
如果是分類問題,對預測的n*(n-1)/2 個值,做投票處理,票數最高的是預報的類。
如果是One-Class,根據預報值的符號,返回+1/-1 
如果是迴歸問題,直接返回該double 類型的值。

double svm_predict_probability( 
const svm_model *model, const svm_node *x, double *prob_estimates)

LibSVM2.6 新增函數

跳過。

int svm_save_model(const char *model_file_name, const svm_model *model) 
svm_model *svm_load_model(const char *model_file_name) 
void svm_destroy_model(svm_model* model) 
以上3 個函數均爲LibSVM2.5 示例程序中的函數,現成爲LibSVM2.6 的一部分

看看名字就知道是幹什麼的了,不介紹了


void svm_destroy_param(svm_parameter* param)


LibSVM2.6 新增函


釋放權重係數數組的內存。

//檢查數據


上海交通大學模式分析與機器智能實驗室

const char *svm_check_parameter(const struct svm_problem *prob, const struct svm_parameter 
*param); 
該段代碼檢查參數的合理性。凡對LibSVM 進行增加SVC 類型和核函數,都必須修改該文件。
LibSVM2.5 在該部分代碼會存在內存泄漏,LibSVM2.6 中已經修正。
其中需要注意的是,nu 的取值的範圍,

nMin 
× 
2

nu 
<

nMax 

nMin

其中nMax 爲樣本數最多的類的樣本數,nMin 爲樣本數最少的類的樣本。

int svm_check_probability_model(const svm_model *model)

LibSVM2.6 新增函數

檢查概率模型,主要是檢查一些限制條件。

Margin 
Figure 1: SVM separation of two data classes - SV points circled.

Class 1

f3(x)

f1(x)

Class 2 
Class 3 
f2(x) 
Figure 2: One-against-rest SVM separation of three data classes


上海交通大學模式分析與機器智能實驗室

f1,2(x)

Class 1

f2,3(x)

f1,3(x)

Class 2 
Class 3 
Figure 3: One-against-one SVM separation of three data classes


4

43 21

Figure 4: Decision DAG SVM

其他

一、One-v-Rest 多類方

http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/1vsall/


二、DDAG 多類方

http://www.csie.ntu.edu.tw/~cjlin/libsvmtools/libsvm-2.3dag.zip


1V4 
2V4 1V3 
2V3 1V2 3V4 














Not 1 Not 4 
Not 4 
Not 1 
Not 3 
Not 2

上海交通大學模式分析與機器智能實驗室

參考文獻:

[1]Chih-Chung Chang and Chih-Jen Lin, LIBSVM : a library for support vector machines, 2001. 
Software available at http://www.csie.ntu.edu.tw/~cjlin/libsvm

[2]J. Platt. Fast training of support vector machines using sequential minimal 
optimization. In B. Scholkopf, C. Burges, and A. Smola, editors, Advances in 
kernel methods: support vector learning. MIT Press, 1998.

[3] Sequential Minimal Optimization for SVM 
http://www.datalab.uci.edu/people/xge/svm/smo.pdf 
[4]Chang, C.-C. and C.-J. Lin (2001). Training 
ν 
_-support vector classifiers: Theory and 
algorithms. Neural Computation 13 (9), 2119–2147.

[5]Chang, C.-C. and C.-J. Lin (2002). Training 
ν 
_support vector regression: Theory and 
algorithms. Neural Computation 14 (8), 1959–1977.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章