將LIBSVM用於多分類時根據svmtrain輸出結果得到各OvO分類超平面的法向量w和偏移項b

題目:將LIBSVM用於多分類時根據svmtrain輸出結果得到各OvO分類超平面的法向量w和偏移項b

        在前面曾討論過《由LIBSVM的svmtrain輸出結果得到分類超平面的法向量w和偏移項b》(鏈接https://blog.csdn.net/jbb0523/article/details/80918214),介紹了LIBSVM用於二分類時根據模型返回參數得到超平面方程參數w和b;本篇在此基礎上更進一步,討論將LIBSVM用於多分類時,根據svmtrain輸出結果得到分類超平面的法向量w和偏移項b。由於前面已經介紹了有關SVM的基礎,因此本篇不講SVM基礎;另外,本篇提及較多的西瓜書式(6.9)位於書中123頁6.2節:

        LIBSVM在處理多分類時,採用一對一分解(one vs one, OvO)。有關OvO的細節參見西瓜書的第63頁開始的3.5節。對於共有N個類別的多分類問題,OvO分解將其轉換爲N(N-1)/2個二分類問題,因此這也就共有N(N-1)/2分類超平面。各分類超平面的偏移項(bias)項,也就是b,LIBSVM的模型返回參數直接給出,因此關鍵在於求出各超平面的法向量w。在《由LIBSVM的svmtrain輸出結果得到分類超平面的法向量w和偏移項b》的基礎上,不難知道,w可以根據西瓜書式(6.9)求得,而根據式(6.9)求解w的關鍵在於找出每個x_i對應的alpha_i和y_i。由於多分類時包含N(N-1)/2二分類問題,因此只要能從LIBSVM的模型返回參數中分別找出哪些結果對應哪個OvO二分類問題即可。

        接下來,首先給出MATLAB代碼,然後給予適當的解釋。程序在Matlab R2014a和LIBSVM最新版(v3.24)上測試通過。

%libsvm用於多分類時的中間參數探索@20191130
clear all;close all;clc;
%% 產生多分類數據集(nr_class = 4)
num_examples = 1000;%生成1000個樣本
%控制隨機數生成,保證每次產生的數據集相同
%舊版本Matlab的rand('seed', 0)等價於新版本Matlab的rng(0,'v4')
%舊版本Matlab的rand('state', 0)等價於新版本Matlab的rng(0,'v5uniform')
%本版本程序在MATLAB R2014a編寫,同時支持兩種寫法,但諸如Matlab R2009b僅支持舊版本寫法
rand('state', 0);%rng(0,'v5uniform');
x = rand(num_examples,2);
y = zeros(num_examples,1);
for ii=1:num_examples
    cond1 = (x(ii,1)<x(ii,2));
    cond2 = (x(ii,1)+x(ii,2)<1);
    if cond1&&cond2
        y(ii) = 1;
    end
    if cond1&&~cond2
        y(ii) = 2;
    end
    if ~cond1&&~cond2
        y(ii) = 3;
    end
    if ~cond1&&cond2
        y(ii) = 4;
    end
end
figure;gscatter(x(:,1),x(:,2),y);title('The synthetic multi-class data set');
%% SVM訓練和測試
model = svmtrain(y, x, '-t 0 -q');
[predict_label, accuracy, dec_values] = svmpredict(y, x, model);

%% 得到nr_class*(nr_class-1)/2個分類平面方程f(x)=w*x+b
%緩存訓練模型返回參數
%libsvm使用one vs one (OvO)處理多分類情形,以此處nr_class=4爲例:
%OvO第1個二分類器: Label(1)爲正例(+1), Label(2)爲反例(-1)
%OvO第2個二分類器: Label(1)爲正例(+1), Label(3)爲反例(-1)
%OvO第3個二分類器: Label(1)爲正例(+1), Label(4)爲反例(-1)
%OvO第4個二分類器: Label(2)爲正例(+1), Label(3)爲反例(-1)
%OvO第5個二分類器: Label(2)爲正例(+1), Label(4)爲反例(-1)
%OvO第6個二分類器: Label(3)爲正例(+1), Label(4)爲反例(-1)
nr_class = model.nr_class;%類別標記個數
Label = model.Label;%nr_class個類別標記

total_SVs = model.totalSV;%nr_class*(nr_class-1)/2個OvO問題中的所有支持向量個數
n_SVs = model.nSV;%nr_class*1列向量,每個類別樣本的支持向量個數,n_SVs所有元素之和等於total_SVs
SVs_idx = model.sv_indices;%所有支持向量索引(Support Vectors Index)

%SVs_coef大小爲total_SVs*(nr_class-1)的矩陣,表示每個支持向量在決策函數中的係數
SVs_coef = model.sv_coef;%即西瓜書公式(6.9)中的alpha_i*y_i

%所有支持向量的特徵和類別
x_SVs = x(SVs_idx,:);% or use: SVs=full(model.SVs);
y_SVs = y(SVs_idx);

%SVs_coef包含了所有係數,爲方便使用,接下來按類別分別存儲
%所得SVs_coef_label中,其中SVs_coef_label{ll}的大小爲n_SVs(ll)*(nr_class-1)
%每個類別均參與構建(nr_class-1)個OvO二分類器, 因此SVs_coef爲(nr_class-1)列
%SVs_coef_label{ll}的第j列對應第ll個類別第j次參與構建OvO二分類器
%以此處nr_class=4爲例,與各OvO二分類器對應關係爲:
%OvO第1個二分類器:SVs_coef_label{1}第1列和SVs_coef_label{2}第1列
%OvO第2個二分類器:SVs_coef_label{1}第2列和SVs_coef_label{3}第1列
%OvO第3個二分類器:SVs_coef_label{1}第3列和SVs_coef_label{4}第1列
%OvO第4個二分類器:SVs_coef_label{2}第2列和SVs_coef_label{3}第2列
%OvO第5個二分類器:SVs_coef_label{2}第3列和SVs_coef_label{4}第2列
%OvO第6個二分類器:SVs_coef_label{3}第3列和SVs_coef_label{2}第3列
SVs_coef_label = cell(nr_class,1);
for ll=1:nr_class
    tmp_idx = (y_SVs == Label(ll));
    SVs_coef_label{ll} = SVs_coef(tmp_idx,:);
end

%求平面w^T x + b = 0的法向量b
b_all = -model.rho;

%求平面w^T x + b = 0的法向量w
%其中最複雜的一步就是將每個OvO對應的SVs_coef拿出來,然後套用西瓜書公式(6.9)即可
w_all = [];%最終大小爲size(x,2)*(nr_class*(nr_class-1)/2)
for ii=1:nr_class-1
    ii_cnt = ii;
    for jj=ii+1:nr_class
        %取出當前OvO二分類器對應的SVs_coef,實際SVs_coef有很多爲0, 這是因爲
        %SVs_coef_pair包含來自不同二分類器的支持向量,因爲每個類參與構建多個二分類器
        SVs_coef_pair = [SVs_coef_label{ii}(:,ii_cnt);SVs_coef_label{jj}(:,ii)];
        %當前OvO二分類器對應的支持向量(Support Vector)索引
        idx_pair = ((y_SVs == Label(ii))|(y_SVs == Label(jj)));
        %取出當前OvO二分類器對應的支持向量的特徵
        x_SVs_pair = x_SVs(idx_pair,:);
        tmp_w = sum(diag(SVs_coef_pair)*x_SVs_pair)';%即西瓜書公式(6.9)
        w_all = [w_all,tmp_w];
        ii_cnt = ii_cnt + 1;
    end
end
%% 驗證求得w是否正確
n_ovo = nr_class*(nr_class-1)/2;
for ii = 1:n_ovo
    %分別取出第ii個OvO二分類器的w和b
    w = w_all(:,ii);
    b = b_all(ii);
    %將手動計算出的決策值與svmpredict輸出的決策值對比
    f_x_ours = x * w + b;%由自己求得的w計算決策值f(x)=w^T x + b
    f_x_model = dec_values(:,ii);%模型輸出的決策值
    err = norm(f_x_ours-f_x_model);%
    disp(['第(',num2str(ii),'/',num2str(n_ovo),')個OvO二分類器決策值誤差爲',num2str(err)]);
end
%% 畫出各分類超平面
figure;
%畫出數據集所有樣本
c_class = ['bx';'cx';'rx';'mx'];
for ii=1:nr_class
    idx_class = (y==Label(ii));
    plot(x(idx_class,1),x(idx_class,2),c_class(ii,:));
    hold on;
end
%畫出所有支持向量
plot(x_SVs(:,1),x_SVs(:,2),'ko','MarkerSize',5);hold on;
%畫出各分類超平面
line_class = ['g-';'y-';'k-';'g:';'y:';'k:'];
for ii = 1:n_ovo
    w = w_all(:,ii);
    b = b_all(ii);
    x1 = 0:0.1:1;
    x2 = -1/w(2)*(w(1)*x1+b);
    plot(x1,x2,line_class(ii,:),'LineWidth',2);hold on;
end
xlim([0,1]);ylim([0,1]);
hold off;

        運行該程序之後,Matlab命令行窗口(Command Window)輸出以下結果:

這說明根據計算出來的w和b得到的決策值與svmpredict輸出的決策值相同,也就是說計算出來的w和b是正確的。另外還有以下兩張圖,第1張圖是生成的數據集類別分佈,第2張圖則進一步將標出了支持向量,並畫出了所有6個分類平面:

        LIBSVM中svmtrain返回值的解釋在《由LIBSVM的svmtrain輸出結果得到分類超平面的法向量w和偏移項b》中已經針對二分類問題解釋過,另外還可以參考以下兩篇博客:

        《libsvm中svmtrain的參數和返回值》(https://blog.csdn.net/Cheese_pop/article/details/61200530)

        《SVM多分類問題 libsvm在matlab中的應用》(https://www.cnblogs.com/litthorse/p/9303711.html)

        以下是代碼運行之後svmtrain函數的返回值model結構體的明細:

        其中:(1)nr_class是類別個數,當前構建的數據集類別個數爲4;

        (2)rho就是偏移項(bias)(i.e., b)的相反數,這裏rho共包含6個值,因爲4個類別共可以OvO分解爲4*(4-1)/2=6個二分類問題;

        (3)sv_indices爲所有支持向量的索引,totalSV爲支持向量個數,nSV爲每個類別的支持向量個數;

        (4)Label爲訓練樣本中類別的符號,這裏有一點特別關鍵:在構建數據集時,程序中使用數字1、2、3、4表示四個類別標記,但這裏Label=[3;1;4;2],這表示在LIBSVM訓練時,數字3對應第1個類別,數字1對應第2個類別,數字4對應第3個類別,數字2對應第4個類別,這個順序很關鍵,對應接下來提到的第1類(class1)到第4類(class4);

        (5)sv_coef表示每個支持向量在決策函數中的係數,即西瓜書式(6.9)中的alpha_i*y_i;另外,sv_coef爲541*3的矩陣,行數541顯然對就支持向量個數,列數3則對應於類別個數減1,也就是每個類別的樣本參與構建OvO二分類器的次數;我們可以將sv_coef按對應支持向量的類別分爲nr_class=4組(每個類別包含的支持向量個數參見nSV),示意圖如下:

        值得注意的是,將sv_coef按上圖類拆成的四組的結果,實際上並不是說第1類的第1列141個係數和第2類的第1列的130個係數對應的總共271個樣本都是OvO二分類器(class1,class2)的支持向量,你會發現這些係數有好多數值等於0,這是因爲每個類別的樣本都參與了三個(即類別個數減1)OvO二分類器的構建,該樣本可能僅在構建某一個分類器時爲支持向量(三列之中該行僅有一個值不爲零),也可能在構建某兩個分類器時爲支持向量(三列之中該行僅有兩個值不爲零),當然也可能在構建三個分類器時均爲支持向量(三列之中該行均不爲零);當然,如果該係數爲0,這並不影響根據式(6.9)求解w,因爲alpha_i*y_i=0則該樣本在式(6.9)中不起作用。

        也就是說,OvO拆解過程是按如下方式依次作爲正類和反類的:

        以上正類和反類的安排還可以由sv_coef(即alpha_i*y_i)的符號佐證。由於alpha_i*y_i中的alpha_i≥0,而在SVM中樣本爲正類時y_i=+1,樣本爲反類時y_i=-1;因此sv_coef的符號表示了樣本爲正類還是反類。觀察可以發現,第1類的sv_coeff都大於等於零,即在它參與構建的三個OvO分類器中均扮演正類角色;第2類的sv_coeff第1列小於等於零,其餘兩列都大於等於零,即在它參與構建的第一個OvO分類器中扮演反類角色,在其餘兩個OvO分類器中扮演正類角色;第3類的sv_coeff第1列和第2列都小於等於零,第3列大於等於零,即在它參與構建的第一個和第二個OvO分類器中扮演反類角色,在第三個OvO分類器中扮演正類角色;第4類的sv_coeff都小於等於零,即在它參與構建的三個OvO分類器中均扮演反類角色。

        瞭解了sv_coef的存儲方式之後,直接結合程序註釋就可以看明白程序了。

        最後,注意到程序中svmtrain的參數設置爲'-t 0 -q',即使用的線性核;若使用默認的高斯核,則無法顯式地計算出w;這是因爲w與特徵x的維度相同,高斯核相當於將x映射到了無窮維的空間之中,w也是無窮維的,這時求解決策函數值時只能使用西瓜書式(6.12)的第2個等號之後的求和形式計算,不能根據西瓜書式(6.9)顯式地先計算出w,再代入西瓜書式(6.12)的第1個等號之後的w^T*x+b計算。西瓜書式(6.12)如下:

        到此爲止,針對LIBSVM用於多分類時的模型返回值的探索暫時告一段落。

發佈了181 篇原創文章 · 獲贊 1199 · 訪問量 316萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章