1. 程式人生 > >解密SVM系列(四):SVM非線性分類原理實驗

解密SVM系列(四):SVM非線性分類原理實驗

前面幾節我們討論了SVM原理、求解線性分類下SVM的SMO方法。本節將分析SVM處理非線性分類的相關問題。

一般的非線性分類如下左所示(後面我們將實戰下面這種情況):
這裡寫圖片描述
可以看到在原始空間中你想用一個直線分類面劃分開來是不可能了,除非圓。而當你把資料點對映一下成右圖所示的情況後,現在資料點明顯看上去是線性可分的,那麼在這個空間上的資料點我們再用前面的SVM演算法去處理,就可以得到每個資料點的分類情況了,而這個分類情況也是我們在低維空間的情況。也就是說,單純的SVM是不能處理非線性問題的,說白了只能處理線性問題,但是來了非線性樣本怎麼辦呢?我們是在樣本上做的文章,我把非線性樣本變成線性樣本,再去把變化後的線性樣本拿去分類,經過這麼一圈,就達到了把非線性樣本分開的目的,所以只看開頭和結尾的話發現,SVM竟然可以分非線性問題,其實呢還是分的線性問題。

現在的問題是如何找到這個對映關係對吧,就比如上面那個情況,我們可以人為計算出這種對映,比如一個樣本點是用座標表示的(x1,x2),它有個類標籤,假設為1,那麼把這個點對映到三維中變成(x21,2x1x2,x22)。對每個點我都這麼去對映,假設一個原始點樣本集是這樣的:
這裡寫圖片描述
然後按照上面那個公式去把每個點對映成3維座標點後,畫出來是這樣的:
這裡寫圖片描述
可以看到是線性可分的吧,如果還看不清把視角換個角度(右檢視):
這裡寫圖片描述
現在能看清楚了吧。

那這是二維的點到三維,對映的關係就是上面的那個關係,那如果是三維到四維,四維到N維呢?這個關係你還想去找嗎?理論上是找的到的,但是實際上人工去找你怎麼去找?你怎麼知道資料的對映關係是這樣的是那樣的?不可能知道。然而我們真的需要找到這種關係嗎?答案是不需要的,返回去看看前三節的關於SVM的理論部分可以看到,無論是計算α

呀,還是b呀等等,只要涉及到原始資料點的,都是以內積的形式出來的,也就是說是一個點的向量與另一個點的向量相乘的,向量內積出來是一個值。

就拿α的更新來說,如下:

αnew2=αold2y2(E1E2)ηEi=uiyiη=2K(x1,x2)K(x1,x1)K(x2,x2)
可以看到α的更新用到原始資料點的就在η的計算上。而η的計算可以看到K表示的是內積,就是兩個點的內積或者自己內積。假設現在兩個點的座標非別是X1=(x1,y1),X2=(x2,y2),那麼
K(X1,X2)=XT1X2=x1x2+y1y2=C1
其他依次類推,K出來的是一個數比如C1。那麼假設這個樣本點都對映到三維以後了,每個樣本點是一個1*3的向量,那麼計算到這一塊來了,就變成了下面這樣:K
(X1,X2)=XT1X2=(x1,y1,z1)T(x2,y2,z2)=x1y1+x2y2
+x3y3=C2

最後也是得到一個值比如C2。既然SVM裡面所有涉及到原始資料的地方都是以向量的形式出現的,那麼我們還需要管它的對映關係嗎?因為它也不需要你去計算說具體到比如說三維以後,三維裡面的三個座標值究竟是多少,他需要的是內積以後的一個結果值。那麼好辦了,我就假設有一個黑匣子,輸入原始資料維度下的兩個座標向量,然後經過黑匣子這麼一圈,出來一個值,這個值我們就認為是高維度下的值。而黑匣子的潛在意義就相當於一個高維對映器一樣。更重要的是我們並不需要知道黑匣子究竟是怎麼對映的,只需要知道它的低緯度下的形式就可以了。常用的黑匣子就是徑向基函式,而這個黑匣子在數學上就叫做核函式,例如徑向基函式的外在形式如下所示:
K(X1,X2)=exp(||X1X2||2σ)
σ是需要預先設定的引數。至於這個黑匣子把初始資料對映到多少維了,誰知道呢,既然是黑匣子,那就是看不到的,上帝給了人類這麼一個黑匣子就已經很夠意思了。可以看到的是原始資料結果黑匣子算了以後,出來就是一個值了,而這個值就認為是高維度下的資料通過內積計算而來的值。當然上帝還留了一個窗戶,就是σ,相傳σ選取的越小,資料對映的維度越大,小到一定程度,維度空間大到無窮維。反之越大,對映的維度空間就越小,但是會不會小到低於原始空間維度呢?誰知道了,然而通過實驗我發現,大到一定程度,樣本點分的亂七八糟,並且σ正好在一定範圍的時候效果非常好,這個範圍既不是極小的範圍,也不是極大的範圍,那這暗示了什麼呢?也就是說非線性原始樣本是有一個屬於他自己的最佳高維空間的,大了小了似乎都不好。

好了既然黑匣子是藏著的,那也就只能說這麼多了。有趣的是上帝給的這個黑匣子不止一個,有好幾個,只是上面的那個普遍效果更好而已。基於此,那麼對於上節的SMO演算法,如果拿來求解非線性資料的話,我們只需要將其中對應的內積部分改成核函式的形式即可。一個數據核函式程式如下:

function result = Kernel(data1,data2,sigma)
% data裡面每一行資料是一個樣本(的行向量)
[m1,~] = size(data1);
[m2,~] = size(data2);
result = zeros(m1,m2);
for i = 1:m1
    for j = 1:m2
        result(i,j) = exp(-norm(data1(i,:)-data2(j,:))/(2*sigma^2));
    end
end

有了此核函式,我們用上節的隨機遍歷α的方式(這個函式程式碼少一點)來實驗一下非線性樣本,非線性樣本如下:
這裡寫圖片描述
然後把主程式對應的部分用上述核函式代替:

%%
% * svm 簡單演算法設計
%
%% 載入資料
% * 最終data格式:m*n,m樣本數,n維度
% * label:m*1  標籤必須為-1與1這兩類
clc
clear
% close all
data = load('data_test1.mat');
data = data.data;
train_data = data(1:end-1,:)';
label = data(end,:)';
[num_data,d] = size(train_data);
data = train_data;
%% 定義向量機引數
alphas = zeros(num_data,1);
% 係數
b = 0;
% 鬆弛變數影響因子
C = 0.6;
iter = 0;
max_iter = 80;
% 核函式的引數
sigma = 4;
%%
while iter < max_iter
    alpha_change = 0;
    for i = 1:num_data
        %輸出目標值
        pre_Li = (alphas.*label)'*Kernel(data,data(i,:),sigma) + b;
        %樣本i誤差
        Ei = pre_Li - label(i);
        % 滿足KKT條件
        if (label(i)*Ei<-0.001 && alphas(i)<C)||(label(i)*Ei>0.001 && alphas(i)>0)
           % 選擇一個和 i 不相同的待改變的alpha(2)--alpha(j)
            j = randi(num_data,1);  
            if j == i
                temp = 1;
                while temp
                    j = randi(num_data,1);
                    if j ~= i
                        temp = 0;
                    end
                end
            end
            % 樣本j的輸出值
            pre_Lj = (alphas.*label)'*Kernel(data,data(j,:),sigma) + b;
            %樣本j誤差
            Ej = pre_Lj - label(j);
            %更新上下限
            if label(i) ~= label(j) %類標籤相同
                L = max(0,alphas(j) - alphas(i));
                H = min(C,C + alphas(j) - alphas(i));
            else
                L = max(0,alphas(j) + alphas(i) -C);
                H = min(C,alphas(j) + alphas(i));
            end
            if L==H  %上下限一樣結束本次迴圈
                continue;end
            %計算eta
            eta = 2*Kernel(data(i,:),data(j,:),sigma)- ...
                Kernel(data(i,:),data(i,:),sigma)...
                - Kernel(data(j,:),data(j,:),sigma);
            %儲存舊值
            alphasI_old = alphas(i);
            alphasJ_old = alphas(j);
            %更新alpha(2),也就是alpha(j)
            alphas(j) = alphas(j) - label(j)*(Ei-Ej)/eta;
            %限制範圍
            if alphas(j) > H
                alphas(j) = H;
            elseif alphas(j) < L
                    alphas(j) = L;
            end
            %如果alpha(j)沒怎麼改變,結束本次迴圈
            if abs(alphas(j) - alphasJ_old)<1e-4
                continue;end
            %更新alpha(1),也就是alpha(i)
            alphas(i) = alphas(i) + label(i)*label(j)*(alphasJ_old-alphas(j));
            %更新系數b
            b1 = b - Ei - label(i)*(alphas(i)-alphasI_old)*...
                Kernel(data(i,:),data(i,:),sigma) - label(