機器學習理論篇之SVM(python實現)


SVM就是試圖尋找能將訓練樣本區分開的最優劃分超平面,直觀上我們可以看出H3纔是做好的,到樣本兩邊距離相等且最大,,這裏的最好是劃分的超平面對訓練樣本的“容忍性”最好。。。
在樣本空間中,劃分的超平面可以用如下方程來描述:
 W(T)x+b=0,
其中,w位法向量,決定裏超平面的方向,b爲位移項,決定了超平面與原點的距離。
我們這次使用的結果標籤是y=-1,y=1,替換在logistic迴歸中使用的y=0和y=1。同時將替換成w和b。以前的,其中認爲。現在我們替換爲b,後面替換(即)。這樣,我們讓,進一步。也就是說除了y由y=0變爲y=-1,只是標記不同外,別的與logistic迴歸的形式化表示沒區別
    再明確下假設函數
    上面提到過我們只需考慮的正負問題,而不用關心g(z),因此我們這裏將g(z)做一個簡化,將其簡單映射到y=-1和y=1上。映射關係如下:


樣本空間中任意點x到超平面的(W,b)的距離可以寫爲:

clip_image070

參考:


那如何尋找超平面,確定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) )

注意對兩的區分,等式右邊的爲幾何間隔,等式左邊的爲函數間隔

函數間隔y*(wx+b)=y*f(x)實際上就是|f(x)|,只是人爲定義的一個間隔度量;而幾何間隔|f(x)|/||w||纔是直觀上的點到超平面距離。
我們已經很明顯的看出,函數間隔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(有噪聲點的情況,軟分隔)

我們引入了損失函數:

{\displaystyle \max \left(0,1-y_{i}({\vec {w}}\cdot {\vec {x}}_{i}-b)\right).}因爲如果預測正確的情況下肯定大於1,所以,當預測正確的情況下,損失函數是0

當預測錯誤的情況下,損失函數就是函數間隔。

我們想要最小化:


{\displaystyle \left[{\frac {1}{n}}\sum _{i=1}^{n}\max \left(0,1-y_{i}({\vec {w}}\cdot {\vec {x}}_{i}-b)\right)\right]+\lambda \lVert {\vec {w}}\rVert ^{2},}

\lambda參數表示對噪聲的容忍度,如果 λ {\displaystyle \lambda } \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()
結果:


使用Python實現Support Vector Machine算法

理論部分參考:http://eric-gcm.iteye.com/blog/1981771

代碼部分參考:http://blog.topspeedsnail.com/archives/10326

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