解密SVM系列(四):SVM非線性分類原理實驗
前面幾節我們討論了SVM原理、求解線性分類下SVM的SMO方法。本節將分析SVM處理非線性分類的相關問題。
一般的非線性分類如下左所示(後面我們將實戰下面這種情況):
可以看到在原始空間中你想用一個直線分類面劃分開來是不可能了,除非圓。而當你把資料點對映一下成右圖所示的情況後,現在資料點明顯看上去是線性可分的,那麼在這個空間上的資料點我們再用前面的SVM演算法去處理,就可以得到每個資料點的分類情況了,而這個分類情況也是我們在低維空間的情況。也就是說,單純的SVM是不能處理非線性問題的,說白了只能處理線性問題,但是來了非線性樣本怎麼辦呢?我們是在樣本上做的文章,我把非線性樣本變成線性樣本,再去把變化後的線性樣本拿去分類,經過這麼一圈,就達到了把非線性樣本分開的目的,所以只看開頭和結尾的話發現,SVM竟然可以分非線性問題,其實呢還是分的線性問題。
現在的問題是如何找到這個對映關係對吧,就比如上面那個情況,我們可以人為計算出這種對映,比如一個樣本點是用座標表示的(x1,x2),它有個類標籤,假設為1,那麼把這個點對映到三維中變成
然後按照上面那個公式去把每個點對映成3維座標點後,畫出來是這樣的:
可以看到是線性可分的吧,如果還看不清把視角換個角度(右檢視):
現在能看清楚了吧。
那這是二維的點到三維,對映的關係就是上面的那個關係,那如果是三維到四維,四維到N維呢?這個關係你還想去找嗎?理論上是找的到的,但是實際上人工去找你怎麼去找?你怎麼知道資料的對映關係是這樣的是那樣的?不可能知道。然而我們真的需要找到這種關係嗎?答案是不需要的,返回去看看前三節的關於SVM的理論部分可以看到,無論是計算
就拿
可以看到
其他依次類推,K出來的是一個數比如C1。那麼假設這個樣本點都對映到三維以後了,每個樣本點是一個1*3的向量,那麼計算到這一塊來了,就變成了下面這樣:
最後也是得到一個值比如C2。既然SVM裡面所有涉及到原始資料的地方都是以向量的形式出現的,那麼我們還需要管它的對映關係嗎?因為它也不需要你去計算說具體到比如說三維以後,三維裡面的三個座標值究竟是多少,他需要的是內積以後的一個結果值。那麼好辦了,我就假設有一個黑匣子,輸入原始資料維度下的兩個座標向量,然後經過黑匣子這麼一圈,出來一個值,這個值我們就認為是高維度下的值。而黑匣子的潛在意義就相當於一個高維對映器一樣。更重要的是我們並不需要知道黑匣子究竟是怎麼對映的,只需要知道它的低緯度下的形式就可以了。常用的黑匣子就是徑向基函式,而這個黑匣子在數學上就叫做核函式,例如徑向基函式的外在形式如下所示:
好了既然黑匣子是藏著的,那也就只能說這麼多了。有趣的是上帝給的這個黑匣子不止一個,有好幾個,只是上面的那個普遍效果更好而已。基於此,那麼對於上節的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(