数学推导+纯Python实现机器学习算法8:线性可分支持向量机

     

Python机器学习算法实现

Author:louwill

     

     前面两讲我们对感知机和神经网络进行了介绍。感知机作为一种线性分类模型,很难处理非线性问题。为了处理非线性的情况,在感知机模型的基础上有了两个方向,一个就是上一讲说到的神经网络,大家也看到了,现在深度学习大放异彩,各种网络功能强大。但实际上在神经网络兴起之前,基于感知机的另一种模型——支持向量机,同样可以解决非线性问题。

     支持向量机一般来说有三种任务类型:线性可分情况,近似线性可分情况以及线性不可分情况。针对这三种分别线性可分支持向量机、线性支持向量机和线性不可分支持向量机。笔者将分三次对这三种支持向量机进行介绍。

640?wx_fmt=png

线性可分支持向量机

     和感知机一样,线性可分支持向量机的训练目标也是寻找一个分离超平面,能将数据分成不同的类。通过感知机的学习我们知道,当训练数据线性可分时,一般存在不止一个线性超平面可以将数据分类,可能有无数多个线性超平面。而线性可分支持向量机则是利用间隔最大化求得一个最优的分离超平面。

     关于函数间隔、几何间隔和支持向量等相关概念,笔者这里不过多阐述,详细细节可参考统计学习方法一书。总之,线性可分支持向量机可被形式化一个凸二次规划问题:

640?wx_fmt=png

     这里多说一句,感知机、最大熵和支持向量机等模型的优化算法都是一些经典的优化问题,对其中涉及的凸优化、拉格朗日对偶性、KKT条件、二次规划等概念,建议各位找到相关材料和教材认真研读,笔者这里不多做表述。

     一般来说,我们可以直接对上述凸二次规划进行求解,但有时候该原始问题并不容易求解,这时候需要引入拉格朗日对偶性,将原始问题转化为对偶问题进行求解。原始二次规划的一般形式为:

640?wx_fmt=png

     引入拉格朗日函数:

640?wx_fmt=png

     定义该拉式函数的最大化函数:

640?wx_fmt=png

     通过证明可得原始问题等价于该拉式函数的极小极大化问题:

640?wx_fmt=png

     原始问题为极小极大化问题,根据拉格朗日对偶性,对偶问题即为极大极小化问题:

640?wx_fmt=png

     为计算该对偶问题的解,我们需要对L(w,b, α)求极小,再对α求极大。具体推导如下。第一步:

640?wx_fmt=png

第二步:

640?wx_fmt=png

第三步:根据KKT条件可得:

640?wx_fmt=png

最后可根据对偶问题求得原始问题的解为:

640?wx_fmt=png

     以上便是线性可分支持向量机的对偶问题推导过程,详细过程可参考相关教材,笔者不做过多展开(主要是打公式太费时间)。

线性可分支持向量机的简单实现

     这里我们基采取和之前感知机模型一样的优化思想来求解线性可分支持向量机的原始问题。

     先准备示例训练数据:

data_dict = {-1:np.array([[1,7],	
                          [2,8],	
                          [3,8],]),	
             	
             1:np.array([[5,1],	
                         [6,-1],	
                         [7,3],])}

     导入相关package并绘图展示:

import numpy as np	
import matplotlib.pyplot as plt	

	
colors = {1:'r',-1:'g'}	
fig = plt.figure()	
ax = fig.add_subplot(1,1,1)	
[[ax.scatter(x[0],x[1],s=100,color=colors[i]) 	
       for x in data_dict[i]] for i in data_dict]	
plt.show()

640?wx_fmt=png

     接下来定义线性可分支持向量机的模型主体和训练部分:

def train(data):	
    # 参数字典 { ||w||: [w,b] }	
    opt_dict = {}	
    # 数据转换列表	
    transforms =  [[1,1],          	
                  [-1,1],	
                  [-1,-1],	
                  [1,-1]]	
    # 从字典中获取所有数据	
    all_data = []	
    for yi in data:	
        for featureset in data[yi]:	
            for feature in featureset:	
                all_data.append(feature)	

	
    # 获取数据最大最小值	
    max_feature_value = max(all_data)	
    min_feature_value = min(all_data)	
    all_data = None	

	
    # 定义一个步长列表	
    step_sizes = [max_feature_value * 0.1,	
                  max_feature_value * 0.01,	
                  max_feature_value * 0.001	
                  ]	

	
    # 参数b的范围设置	
    b_range_multiple = 2	
    b_multiple = 5	
    latest_optimum = max_feature_value*10	

	
    # 基于不同步长训练优化	
    for step in step_sizes:	
        w = np.array([latest_optimum,latest_optimum])	
        # 凸优化	
        optimized = False	
        while not optimized:	
            for b in np.arange(-1*(max_feature_value*b_range_multiple),	
                               max_feature_value*b_range_multiple,	
                               step*b_multiple):	
                for transformation in transforms:	
                    w_t = w*transformation	
                    found_option = True	

	
                    for i in data:	
                        for xi in data[i]:	
                            yi=i	
                            if not yi*(np.dot(w_t,xi)+b) >= 1:	
                                found_option = False	
                                	
                    if found_option:	
                         opt_dict[np.linalg.norm(w_t)] = [w_t,b]	
            if w[0] < 0:	
                optimized = True	
                print('Optimized a step!')	
            else:	
                w = w - step	
        norms = sorted([n for n in opt_dict])	
        #||w|| : [w,b]	
        opt_choice = opt_dict[norms[0]]	
        w = opt_choice[0]	
        b = opt_choice[1]	
        latest_optimum = opt_choice[0][0]+step*2	

	
    for i in data:	
        for xi in data[i]:	
            yi=i	
            print(xi,':',yi*(np.dot(w,xi)+b))	
    return w, b

基于示例数据的训练结果如下:

640?wx_fmt=png

然后定义预测函数:

# 定义预测函数	
def predict(features):	
    # sign( x.w+b )	
    classification = np.sign(np.dot(np.array(features),w)+b)	
    if classification !=0:	
        ax.scatter(features[0], features[1], s=200, marker='^', c=colors[classification])	
        print(classification)	
    return classification

基于示例数据的预测结果:

640?wx_fmt=png

整合成线性可分支持向量机类

整理上述代码并添加结果可视化:

class Hard_Margin_SVM:	
    def __init__(self, visualization=True):	
        self.visualization = visualization	
        self.colors = {1:'r',-1:'g'}	
        if self.visualization:	
            self.fig = plt.figure()	
            self.ax = self.fig.add_subplot(1,1,1)	
            	
    # 定义训练函数	
    def train(self, data):	
        self.data = data	
        # 参数字典 { ||w||: [w,b] }	
        opt_dict = {}	
        	
        # 数据转换列表	
        transforms = [[1,1],	
                      [-1,1],	
                      [-1,-1],	
                      [1,-1]]	
        	
        # 从字典中获取所有数据	
        all_data = []	
        for yi in self.data:	
            for featureset in self.data[yi]:	
                for feature in featureset:	
                    all_data.append(feature)	
                    	
        # 获取数据最大最小值	
        self.max_feature_value = max(all_data)	
        self.min_feature_value = min(all_data)	
        all_data = None	

	
        # 定义一个学习率(步长)列表	
        step_sizes = [self.max_feature_value * 0.1,	
                      self.max_feature_value * 0.01,	
                      self.max_feature_value * 0.001	
                      ]	

	
        	
        # 参数b的范围设置	
        b_range_multiple = 2	
        b_multiple = 5	
        latest_optimum = self.max_feature_value*10	
        	
        # 基于不同步长训练优化	
        for step in step_sizes:	
            w = np.array([latest_optimum,latest_optimum])	
            # 凸优化	
            optimized = False	
            while not optimized:	
                for b in np.arange(-1*(self.max_feature_value*b_range_multiple),	
                                   self.max_feature_value*b_range_multiple,	
                                   step*b_multiple):	
                    for transformation in transforms:	
                        w_t = w*transformation	
                        found_option = True	
                        	
                        for i in self.data:	
                            for xi in self.data[i]:	
                                yi=i	
                                if not yi*(np.dot(w_t,xi)+b) >= 1:	
                                    found_option = False	
                                    # print(xi,':',yi*(np.dot(w_t,xi)+b))	
                                    	
                        if found_option:	
                            opt_dict[np.linalg.norm(w_t)] = [w_t,b]	

	
                if w[0] < 0:	
                    optimized = True	
                    print('Optimized a step!')	
                else:	
                    w = w - step	

	
            norms = sorted([n for n in opt_dict])	
            #||w|| : [w,b]	
            opt_choice = opt_dict[norms[0]]	
            self.w = opt_choice[0]	
            self.b = opt_choice[1]	
            latest_optimum = opt_choice[0][0]+step*2	
            	
        for i in self.data:	
            for xi in self.data[i]:	
                yi=i	
                print(xi,':',yi*(np.dot(self.w,xi)+self.b))            	
    	
    # 定义预测函数	
    def predict(self,features):	
        # sign( x.w+b )	
        classification = np.sign(np.dot(np.array(features),self.w)+self.b)	
        if classification !=0 and self.visualization:	
            self.ax.scatter(features[0], features[1], s=200, marker='^', c=self.colors[classification])	
        return classification	

	
    # 定义结果绘图函数	
    def visualize(self):	
        [[self.ax.scatter(x[0],x[1],s=100,color=self.colors[i]) for x in data_dict[i]] for i in data_dict]	

	
        # hyperplane = x.w+b	
        # v = x.w+b	
        # psv = 1	
        # nsv = -1	
        # dec = 0	
        # 定义线性超平面	
        def hyperplane(x,w,b,v):	
            return (-w[0]*x-b+v) / w[1]	

	
        datarange = (self.min_feature_value*0.9,self.max_feature_value*1.1)	
        hyp_x_min = datarange[0]	
        hyp_x_max = datarange[1]	

	
        # (w.x+b) = 1	
        # 正支持向量	
        psv1 = hyperplane(hyp_x_min, self.w, self.b, 1)	
        psv2 = hyperplane(hyp_x_max, self.w, self.b, 1)	
        self.ax.plot([hyp_x_min,hyp_x_max],[psv1,psv2], 'k')	

	
        # (w.x+b) = -1	
        # 负支持向量	
        nsv1 = hyperplane(hyp_x_min, self.w, self.b, -1)	
        nsv2 = hyperplane(hyp_x_max, self.w, self.b, -1)	
        self.ax.plot([hyp_x_min,hyp_x_max],[nsv1,nsv2], 'k')	

	
        # (w.x+b) = 0	
        # 线性分隔超平面	
        db1 = hyperplane(hyp_x_min, self.w, self.b, 0)	
        db2 = hyperplane(hyp_x_max, self.w, self.b, 0)	
        self.ax.plot([hyp_x_min,hyp_x_max],[db1,db2], 'y--')	

	
        plt.show()

测试效果如下:

data_dict = {-1:np.array([[1,7],	
                          [2,8],	
                          [3,8],]),	
             	
             1:np.array([[5,1],	
                         [6,-1],	
                         [7,3],])}	

	
svm = Hard_Margin_SVM()	
svm.train(data=data_dict)	

	
predict_us = [[0,10],	
              [1,3],	
              [3,4],	
              [3,5],	
              [5,5],	
              [5,6],	
              [6,-5],	
              [5,8],	
              [2,5], 	
              [8,-3]]	

	
for p in predict_us:	
    svm.predict(p)	

	
svm.visualize()

640?wx_fmt=png

     以上就是本节内容,关于近似线性可分以及软间隔最大化问题,笔者将在下一篇推文中介绍。完整代码文件和数据可参考笔者GitHub地址:

https://github.com/luwill/machine-learning-code-writing

参考资料:

https://pythonprogramming.net/

https://github.com/SmirkCao/Lihang/tree/master/CH07

往期精彩:


一个数据科学从业者的学习历程

640?

640?wx_fmt=jpeg

长按二维码.关注机器学习实验室

640?wx_fmt=jpeg

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值