參考:
那如何尋找超平面,確定w和b呢?答案是尋找兩條邊界端或極端劃分直線中間的最大間隔(之所以要尋最大間隔是爲了能更好的劃分不同類的點。
Hard-margin:(無噪聲點情況,硬分隔)
如果訓練數集是線性可分的,我們可以選擇兩個平行的線,來分開兩個數據集,因此我們要確保,這兩條平行線的距離儘可能的大,兩條平行線組成的區域就是叫做Margin
我們可以定義分類函數:
顯然,如果 f(x)=0 ,那麼 x 是位於超平面上的點。我們不妨要求對於所有滿足 f(x)<0 的點,其對應的 y 等於 -1 ,而 f(x)>0 則對應 y=1 的數據點。
我們在進行分類的時候,將數據點 x代入 f(x) 中,如果得到的結果小於
0 ,則賦予其類別 -1 ,如果大於 0 則賦予類別 1
在超平面w*x+b=0確定的情況下,|w*x+b|能夠相對的表示點x到距離超平面的遠近,而w*x+b的符號與類標記y的符號是否一致表示分類是否正確,所以,可以用量y*(w*x+b)的正負性來判定或表示分類的正確性和確信度。即y*(w*x+b)爲正數時,分類正確,值爲點x到超平面的函數間隔,否則錯誤。
我們定義函數間隔functional margin 爲:(是指點x到超平面的函數間隔)
接着,我們定義超平面(w,b)關於訓練數據集T的函數間隔爲:超平面(w,b)關於T中所有樣本點(xi,yi)的函數間隔最小值,其中,x是特徵,y是結果標籤,i表示第i個樣本,有:
然與此同時,問題就出來了。= mini (i=1,...n)
上述定義的函數間隔雖然可以表示分類預測的正確性和確信度,但在選擇分類超平面時,只有函數間隔還遠遠不夠,因爲如果成比例的改變w和b,如將他們改變爲2w和2b,雖然此時超平面沒有改變,但函數間隔的值f(x)卻變成了原來的4倍。
其實,我們可以對法向量w加些約束條件,使其表面上看起來規範化,如此,我們很快又將引出真正定義點到超平面的距離--幾何間隔geometrical margin的概念(很快你將看到,幾何間隔就是函數間隔除以個||w||,即yf(x) / ||w||)。
不過這裏的是帶符號的,我們需要的只是它的絕對值,因此類似地,也乘上對應的類別 y即可,因此實際上我們定義 幾何間隔geometrical margin 爲(注:別忘了,上面的定義,=y(wTx+b)=yf(x) ):
注意對兩的區分,等式右邊的爲幾何間隔,等式左邊的爲函數間隔
我們已經很明顯的看出,函數間隔functional margin 和 幾何間隔geometrical margin 相差一個的縮放因子。按照我們前面的分析,對一個數據點進行分類,當它的 margin 越大的時候,分類的 confidence 越大。對於一個包含 n 個點的數據集,我們可以很自然地定義它的 margin 爲所有這 n 個點的 margin 值中最小的那個。於是,爲了使得分類的 confidence 高,我們希望所選擇的超平面hyper plane能夠最大化這個 margin 值。
那麼這兩個margin用哪一個呢?
1、functional margin 明顯是不太適合用來最大化的一個量,因爲在 hyper plane超平面 固定以後,我們可以等比例地縮放 w 的長度和 b 的值,這樣可以使得的值任意大,亦即 functional margin可以在 hyper plane 保持不變的情況下被取得任意大,
2、而 geometrical margin 則沒有這個問題,因爲除上了這個分母,所以縮放 w 和 b 的時候幾何間隔的值是不會改變的,它只隨着 hyper plane 的變動而變動,因此,這是更加合適的一個 margin這樣一來,我們的 maximum margin classifier 的目標函數可以定義爲:
當然,還需要滿足一些條件,根據 margin 的定義,我們有
這裏的是函數間隔,,我們要先找出一個訓練集到超平面的最小函數間隔,因爲函數間隔是幾何間隔的||w||倍
爲了方便推導和優化的目的,我們可以令=1(函數間隔)
(因爲函數間隔的擴大和縮小不會影響到超平面的位置,所以這裏可以隨意定值,爲了方便我們訂了1),
所以幾何間隔 = 1 / ||w|
所以上述的目標函數可以轉化爲(其中,s.t.,即subject
to的意思,它導出的是約束條件):
通過求解這個問題,我們就可以找到一個 margin 最大的 classifier
因此總結下來,就是我們先要定義一個數據集到超平面的最小函數間隔,即支持向量點,然後在此基礎上求出超平面到支持向量的最大距離間隔。
(一般爲方便運算,直接令最小函數間隔爲1。因爲函數間隔的放大對超平面沒有影響)
注意:上面圖片中的-b和b是一樣的,就是b 取值符號不同而已,後面都是-b了,因爲圖的原因
距離超平面最近的這幾個顏色加深的訓練樣本點,就是“支持向量”
很顯然,由於這些 supporting vector 剛好在邊界上,所以它們滿足,而對於所有不是支持向量的點,也就是在“陣地後方”的點,則顯然有
Soft-margin(有噪聲點的情況,軟分隔)
我們引入了損失函數:
因爲如果預測正確的情況下肯定大於1,所以,當預測正確的情況下,損失函數是0
當預測錯誤的情況下,損失函數就是函數間隔。
我們想要最小化:
參數表示對噪聲的容忍度,如果 λ {\displaystyle \lambda } 過小,那麼和硬分隔沒有區別。。
最簡單的線性可分離數據的實現,這裏主要是要了解實現SVM的具體思路
# -*- coding: utf-8 -*-
import numpy as np
from matplotlib import pyplot
import math
import sys
class SVM(object):
def __init__(self, visual=True):
self.visual = visual
self.colors = {1: 'r', -1: 'b'}
if self.visual:
self.fig = pyplot.figure()
self.ax = self.fig.add_subplot(1, 1, 1)
def train(self, data):
self.data = data
opt_dict = {}
transforms = [[1, 1],
[-1, 1],
[-1, -1],
[1, -1]]
# 找到數據集中最大值和最小值
"""
all_data = []
for y in self.data:
for features in self.data[y]:
for feature in features:
all_data.append(feature)
print(all_data)
self.max_feature_value = max(all_data)
self.min_feature_value = min(all_data)
print(self.max_feature_value, self.min_feature_value)
"""
self.max_feature_value = float('-inf') # -sys.maxint - 1 Python3替換爲了sys.maxsize
self.min_feature_value = float('inf') # sys.maxint
for y in self.data:
for features in self.data[y]:#features 就是一個座標點(x,y)
for feature in features:#x和y分別取出
if feature > self.max_feature_value:
self.max_feature_value = feature
if feature < self.min_feature_value:
self.min_feature_value = feature
print(self.max_feature_value, self.min_feature_value)
# 和梯度下降一樣,定義每一步的大小;開始快,然後慢,越慢越耗時
step_sizes = [self.max_feature_value * 0.1, self.max_feature_value * 0.01, self.max_feature_value * 0.001]
b_range_multiple = 5
b_multiple = 5
lastest_optimum = self.max_feature_value * 10
for step in step_sizes:#改變w下降的速度
w = np.array([lastest_optimum, lastest_optimum])
optimized = False
while not optimized:
# arange用法
# np.arange(1,3,0.3)
#array([ 1. , 1.3, 1.6, 1.9, 2.2, 2.5, 2.8])
#改變超平面和原點的距離,b表示距離,w表示方向
for b in np.arange(self.max_feature_value * b_range_multiple * -1,
self.max_feature_value * b_range_multiple, step * b_multiple):
#四個方向都要嘗試
for transformation in transforms:
w_t = w * transformation# 改變超平面方向
found_option = True
#for循環是判斷當前w方向的預測情況
for i in self.data: # i 爲data的分類,這裏爲1 和-1
for x in self.data[i]:
y = i
#大於等於1才表示預測正確,這個表示只要有一次識別錯誤就不保存值
if not y * (np.dot(w_t, x) + b) >= 1:
found_option = False
# print(x,':',y*(np.dot(w_t, x)+b)) 逐漸收斂
if found_option:
"""
np.linalg.norm(x, ord=None, axis=None, keepdims=False)
這裏我們只對常用設置進行說明,x表示要度量的向量,ord表示範數的種類.
ord=2(默認) 二範數:ℓ2 元素平方和,然後開根號
ord=1 一範數:ℓ1 元素絕對值之和,|x1|+|x2|+…+|xn|
ord=np.inf 無窮範數:ℓ∞ max(|xi|)
"""
opt_dict[np.linalg.norm(w_t)] = [w_t, b]
if w[0] < 0:#這裏的w沒有符號,全是正值,w[0]<0表示當前step下的最優解肯定已經訓練出來了,可以跳出循環了
optimized = True
else:
w = w - step
#找到當前最優的w值,要想1/||w||最大,找到||w||最小值即可
norms = sorted([n for n in opt_dict])
opt_choice = opt_dict[norms[0]]
self.w = opt_choice[0]
self.b = opt_choice[1]
print(self.w, self.b)
#當前下降速度訓練完畢後,對lastest_optimum 進行修改,保存當前w下降後的值
#opt_choice[0]存儲了兩個相同的值
#加上step×2是防止step過大,已經把最優解訓練過,
lastest_optimum = opt_choice[0][0] + step * 2
def predict(self, features):
#np.sign(a),返回數組中各元素的正負符號,用1和-1表示
classification = np.sign(np.dot(features, self.w) + self.b)
if classification != 0 and self.visual:
self.ax.scatter(features[0], features[1], s=300, marker='*', c=self.colors[classification])
return classification
def visualize(self):
for i in self.data:
for x in self.data[i]:
self.ax.scatter(x[0], x[1], s=50, c=self.colors[i])
#通過超平面算出另外的x
def hyperplane(x, w, b, v):
return (-w[0] * x - b + v) / w[1]
data_range = (self.min_feature_value * 0.9, self.max_feature_value * 1.1)
hyp_x_min = data_range[0]
hyp_x_man = data_range[1]
psv1 = hyperplane(hyp_x_min, self.w, self.b, 1)
psv2 = hyperplane(hyp_x_man, self.w, self.b, 1)
self.ax.plot([hyp_x_min, hyp_x_man], [psv1, psv2], c=self.colors[1])
nsv1 = hyperplane(hyp_x_min, self.w, self.b, -1)
nsv2 = hyperplane(hyp_x_man, self.w, self.b, -1)
self.ax.plot([hyp_x_min, hyp_x_man], [nsv1, nsv2], c=self.colors[-1])
db1 = hyperplane(hyp_x_min, self.w, self.b, 0)
db2 = hyperplane(hyp_x_man, self.w, self.b, 0)
self.ax.plot([hyp_x_min, hyp_x_man], [db1, db2], 'y--')
pyplot.show()
if __name__ == '__main__':
data_set = {-1: np.array([[1, 7],
[2, 8],
[3, 8]]),
1: np.array([[5, 1],
[6, -1],
[7, 3]])}
print(data_set)
svm = SVM()
svm.train(data_set)
for predict_feature in [[0, 10], [1, 3], [4, 3], [5.5, 7.5], [8, 3]]:
print(svm.predict(predict_feature))
svm.visualize()
結果:
理論部分參考:http://eric-gcm.iteye.com/blog/1981771
代碼部分參考:http://blog.topspeedsnail.com/archives/10326