机器学习之SVM

SVM

概述

SVM(支持向量机),是一种分类算法,用一个超平面将数据分成两份。N维的数据需要(N-1)维的超平面进行分割。
如图所示:


svm.png

SVM工作原理

将黑色数据和黄色数据分割开的不只一条直线,上面就有红色和绿色两条,都能将样本数据分隔开来。但显然红色比绿色好。
红线和绿色都称之为超平面。最好的那条,称之为最大超平面。SVM就是要找最大超平面。

设最大超平面为:Wx_i+b,然后通过一个决策函数进行预测。则对于所有的Wx_i+b > 0预测为1(即正类),对所有的Wx_i+b <= 0,预测为-1(负类)

最大超平面:
超平面到两边数据集的距离最大。


cpm.png

由计算可知,最大距离l=\frac{2}{||W||},所以我们的目标是||W||最小。

因此得出SVM最优化问题:
min(\frac{||W||^2}{2})
s.t. 1-y_i(Wx_i+b) <= 0; i = 1,2,3...m

上面\frac{||W||^2}{2} 与 \frac{2}{||W||} 目标都是要||W||最小,只是后面为了方面计算,写成前一种

最优化问题求解

拉格朗日乘子法

拉格朗日乘子法表示:
L(x,y,λ) = f(x,y)+λg(x,y)

求解最大超平面,就是要求解我们上面的SVM主问题。主问题是一个带约束的,所以引入拉格朗日乘子来求解。

1.引入拉格朗日乘子 a_1,a_2,...a_m >=0,得拉格朗日函数:
L(W,b,a) = \frac{1}{2}||W||^2+\sum_{i=1}^{m}{} a_i(1-y_i(Wx_i+b))
并且有:设g(x) = 1-y_i(Wx_i+b),λ=(a_1,a_2..a,m)
g(x) <=0
λ >=0
λg(x) = 0
2.求L(W,b,a) 对W,b的极小。对W,b 求偏导,并令偏导等于0,得:
W = \sum_{i=1}^{m}{}a_iy_ix_i
\sum_{i=1}^{m}a_iy_i=0

3.利用2中得到的式子带入1并化简得:
L(W,b,a) = \sum_{i=1}^{m}{}a_i-\frac{1}{2}\sum_{i=1}^{m}{}\sum_{j=1}{m}{}a_ia_jy_iy_j(x_i·x_j)

对偶学习法

上面是要求W,b对于L(W,b,a)的极小值,转成对偶问题就是,求min_{W,b}L(W,b,a)对a的极大值

max_amin_{W,b}L(W,b,a) = min_a[\frac{1}{2}\sum_{i=1}^{m}{}\sum_{j=1}{m}{}a_ia_jy_iy_j(x_i·x_j)-\sum_{i=1}^{m}{}a_i]
s.t. \sum_{i=1}^{m}{} a_iy_i = 0; a_i >= 0

现在就是要上式求解最优的a_1,a_2...a_m。这是一个动态规划问题,使用SMO算法求解。

SMO算法

SMO算法的大概思想就是先取两个参数,如a_1,a_2,假设 a_3,a_4...a_m是固定的。先只优化a_1和a_2这两个参数。
反复迭代,最终得到最优的解。

SMO计算推导


smo.png

实现

Package

import numpy as np
from utils import calc_accuracy_class
from utils import fl_score
from sklearn import datasets
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.svm import LinearSVC
import pandas as pd 
from numpy import random

加载数据

X,y = datasets.load_iris(return_X_y=True)
#将数据分为训练集和测试集
train_X,test_X,train_y,test_y = train_test_split(X,y,test_size = 0.20,random_state = 1)
print(f"train_X的大小为:{train_X.shape}")
print(f"tain_y的大小为:{train_y.shape}")
print(f"test_X的大小为:{test_X.shape}")
print(f"test_y的大小为:{test_y.shape}")
train_X的大小为:(120, 4)
tain_y的大小为:(120,)
test_X的大小为:(30, 4)
test_y的大小为:(30,)

先使用skearn看下效果

skmodel =LinearSVC(max_iter=1000,multi_class="crammer_singer")
skmodel.fit(train_X,train_y)
#预测
print("==== 训练集 ====== ")
pred_y = skmodel.predict(train_X)
fl_score(pred_y,train_y)

print("===== 测试集 ===== ")
pred_y = skmodel.predict(test_X)
fl_score(pred_y,test_y)

==== 训练集 ====== 
0 类的fl_score 为:1.0
1 类的fl_score 为:0.9577464788732395
2 类的fl_score 为:0.967032967032967
===== 测试集 ===== 
0 类的fl_score 为:1.0
1 类的fl_score 为:1.0
2 类的fl_score 为:1.0


/anaconda3/envs/tensorflow_env/lib/python3.7/site-packages/sklearn/svm/base.py:931: ConvergenceWarning: Liblinear failed to converge, increase the number of iterations.
  "the number of iterations.", ConvergenceWarning)

接下来,自己实现

SMO算法

def select_j_rand(i, m):
    '''
    随机选择第二个参数的index j
    '''
    j = i
    while (j == i):
        j = random.randint(0, m-1)
    return j

def clipped_alpha(alpha_new_x, yi, yj, alpha_i, alpha_j, C):
    '''
    裁剪alpha
    '''
    L = H = 0
    if yi != yj:
        k = alpha_j - alpha_i
        # 确定下界
        L = max(k, 0)
        # 确定上界
        H = min(C, C+k)
    else:
        k = alpha_i+alpha_j
        L = max(0, k - C)
        H = min(k, C)
    # alpha_new 必须介于L和H之间
    alpha_new_x = max(L, alpha_new_x)
    alpha_new_x = min(H, alpha_new_x)

    return alpha_new_x, L, H
def smo_simple(X_train, y_train, C, max_iter):
    m, n = X_train.shape
    alpha = np.random.rand(m)
    b = 0
    iters = 0
    cost_plot = list()
    iteration = range(1,max_iter+1)
    while iters < max_iter:
        iters += 1
        for i in range(0, m):
            # 求f(xi)的值
            fxi = np.sum(alpha * y_train *
                         np.dot(X_train, X_train[i].transpose()))+b
            # 求实际值与预测值之间的差
            Ei = fxi - y_train[i]

            # 随机选取第二个参数j, j!=i
            j = select_j_rand(i, m)
            fxj = np.sum(alpha * y_train *
                         np.dot(X_train, X_train[j].transpose()))+b
            Ej = fxj - y_train[j]

            # Kii+Kjj-2Kij
            eta = np.dot(X_train[i], X_train[i].transpose())+np.dot(X_train[j],
                                                                    X_train[j].transpose()) - 2*np.dot(X_train[i], X_train[j].transpose())

            if(eta == 0) :
                continue
            alpha_j_new = (
                y_train[j]*(Ei - Ej))/eta + alpha[j]

            alpha_i_old = alpha[i].copy()
            alpha_j_old = alpha[j].copy()

            # 根据约束条件裁剪alpha_j_new
            alpha_j_new,L,H =clipped_alpha(
                alpha_j_new, y_train[i], y_train[j], alpha[i], alpha[j], C)
            if ( L == H ) :
              #  print("第"+repr(iters)+"次循环:L=H="+repr(L)+",i="+repr(i))
                continue



            alpha[j] = alpha_j_new

            if(abs(alpha_j_new - alpha_j_old) < 0.0001) :
                #print("第"+repr(iters)+"次循环,改变很小,无需优化"+repr(j))
                continue
            # 根据alpha_j 求alpha_i
            alpha_i_new = alpha[i] + (alpha_j_old - alpha_j_new)*y_train[i]*y_train[j]

          # 更新alpha
            alpha[i] = alpha_i_new


            # 更新b
            b1 = (alpha_i_old-alpha[i])*y_train[i]*np.dot(X_train[i], X_train[i].transpose())+(
                alpha_j_old-alpha[j])*y_train[j] * np.dot(X_train[j], X_train[i].transpose())+b-Ei

            b2 = (alpha_i_old-alpha[i])*y_train[i]*np.dot(X_train[i], X_train[j].transpose())+(
                alpha_j_old-alpha[j])*y_train[j] * np.dot(X_train[j], X_train[j].transpose())+b-Ej

            if 0< alpha[i] and C > alpha[i]:
                b = b1
            elif 0< alpha[j] and C > alpha[j]:
                b = b2
            else:
                b = (b1+b2)/2
    return alpha,b

SVM基础模型

SVM是用来解决2分类问题的。所以一个SVM模型只能区分两类。若要区分多类,则需要多个SVM模型。
若有N个分类,则需要N或(N-1)个SVM模型

class BasicSVMModel:
    def __init__(self,category):
        self.category = category
        self.X = None
        self.y = None
        self.w = None
        self.b = None
        self.C = None
        
    def fit(self,train_X,train_y,C):
        self.X = train_X
        self.y = train_y
        self.C = C
        m = np.size(self.X, axis=0)  # 样本个数
        # 使用SMO算法最优化对偶问题,求得最优的alpha
        max_iter = 1000
        optimize_alpha,b = smo_simple(train_X, train_y, C, max_iter)
        # 求w
        self.w = np.dot(train_X.transpose(), optimize_alpha*train_y)
        # 求b,由KKT条件得:(1-y(wx+b)) = 0。求所有支持向量的均值
        self.b = np.sum(train_y - np.dot(train_X, self.w))/m
    
    
    #决策
    def sign(self,X):
        f_x =  np.dot(X, self.w) + self.b
        # 决策函数决策,返回1, -1 。1 表示正类。即属于self.category
        sign_fx = np.where(f_x > 0, 1, -1)
        return sign_fx
        
class SVMModel:
    def __init__(self):
        self.model_list =[]#因为一个SVM只能做二分类,所以通过构造N-1个基本的SVM来进行多分类
        
        
    def fit(self, train_X, train_y):
        '''
        训练
        '''
        m = X.shape[0]  # 样本个数
        # 获取所有y的取值,然后将y的结果集拆分成n个结果集,每个结果集判断一种y的取值,是为1,不是为-1
        y_set = set(train_y)
        for y in y_set:
            y_sub_train = np.where(train_y == y, 1, -1)
            basic_model = BasicSVMModel(y)
            C = 1.5
            basic_model.fit(train_X, y_sub_train,C)
            self.model_list.append(basic_model)
    
    def predict(self, X):
        m = np.size(X, axis=0)  # 样本个数
        y_pred = np.zeros(m)
        print("===== 预测 ====== ")
        # 让样本经过每一个模型进行预测
        need_pred_X = X
        unjudge_samples = np.array(range(0,m))
        for index, model in enumerate(self.model_list):
            sign_x = model.sign(need_pred_X)
            if  index == (len(self.model_list) - 1):
                # 最后一个模型,无需再分类
                y_pred[unjudge_samples] = model.category
            else:
                negtive_columns=np.where(sign_x != 1)
                positive_columns=np.where(sign_x == 1)

                need_pred_X = X[negtive_columns]
                y_pred[unjudge_samples[positive_columns]] = model.category
                unjudge_samples = unjudge_samples[negtive_columns]
        return y_pred.astype(int)
model = SVMModel()
model.fit(train_X,train_y)

pred_y = model.predict(train_X)
fl_score(np.squeeze(pred_y),train_y)
calc_accuracy_class(pred_y,train_y)
===== 预测 ====== 
0 类的fl_score 为:0.8297872340425533
1 类的fl_score 为:0.2692307692307693
2 类的fl_score 为:0.7446808510638298





0.675
pred_y = model.predict(test_X)
fl_score(np.squeeze(pred_y),test_y)
calc_accuracy_class(pred_y,test_y)
===== 预测 ====== 
0 类的fl_score 为:0.8148148148148148
1 类的fl_score 为:0.25
2 类的fl_score 为:0.5882352941176471





0.6

总结

对于1类的分类效果并不好,可以找找原因。

对于SVM,除了线性可分,还有线性不可分,借助核函数,能够区分非线性的数据。
SVM相比逻辑回归,朴素贝叶斯,会复杂一点。知识点比较多,有些东西目前只知道是这么回事,但具体为什么是这么回事,说不太清。
还有待学习。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容