前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >机器学习入门 11-4 scikit-learn中的SVM

机器学习入门 11-4 scikit-learn中的SVM

作者头像
触摸壹缕阳光
发布2020-07-08 15:24:54
1.1K0
发布2020-07-08 15:24:54
举报
文章被收录于专栏:AI机器学习与深度学习算法

全文字数:4388字

阅读时间:18分钟

前言

本系列是《玩转机器学习教程》一个整理的视频笔记。本小节主要介绍如何通过sklearn封装的SVM算法实现分类任务,并且设置不同的超参数C的值,通过绘图的方式直观的感受不同的超参数C对模型的影响。

a

数据的标准化

前几个小节介绍了SVM算法的理论部分,本小节主要介绍如何通过sklearn实现SVM算法。SVM算法寻找的决策边界是通过最大化margin求得的,而margin是通过数据点之间的距离来衡量的,所以SVM算法是涉及距离计算的。如果这些数据点的不同特征在不同量纲上,对距离的估计可能会出现问题,在介绍kNN算法的时候提到过,可以对数据进行标准化的处理来消除量纲不同的影响。由于SVM算法涉及到距离的计算,因此为了避免结果出现偏差,在使用SVM算法之前首先要对数据进行标准化的处理。

举一个简单的小例子。下面的特征平面中一共有四个样本点,其中两个样本点属于红色类别,两个样本点属于蓝色类别,并且每个样本点有两个特征,两个特征分别用横轴和纵轴表示。

▲两个类别的特征平面

假如现在样本点的两个特征量纲不同,并且差别比较大。比如在特征平面中,横轴表示范围为0~1,而纵轴表示的范围为0~1万。此时使用SVM算法对这个特征平面中的四个样本点进行分类,得到的决策边界如下图所示。

▲量纲不同下的决策边界

上图中使用绿色箭头标识的样本点和决策边界之间的距离最大,这两个绿色箭头标识的样本点就是所谓的支撑向量。虽然事实上支撑向量距离决策边界比较远,但是直观来看距离比较短,这是由于纵轴上的表示的范围为0-1万比较大,因此即使在纵向范围内很短的距离事实上也是一个很大的数值。此时的margin就是下图中两条过支撑向量的黑色虚线之间的距离。

▲两条虚线之间为margin

一旦横轴和纵轴表示的量纲一致。比如纵轴表示的范围也是0-1之间的话,那么显然此时使用SVM算法得到的决策边界应该是下图那根黑色直线。

▲横纵坐标轴范围一致

在这种情况下,这四个样本点都是支撑向量,此时的margin就变成了下图中两根黑色虚线之间的距离。

▲两条虚线的距离为margin

通过上面的小例子可以看出,对于SVM算法来说,如果样本的不同特征在不同的量纲上就会严重影响SVM算法求解决策边界的最终结果。为了避免这种问题的发生,在具体使用SVM算法之前应该对所有的数据进行标准化的处理。

b

使用sklearn实现SVM算法

使用sklearn封装好的方法来实现SVM算法。这一小节使用的数据集依然是经典的iris鸢尾花数据集,由于暂时只处理二分类问题,因此只取y > 2(y = 0, y = 1两个类别),并且为了方便可视化只选取每个样本的前两个特征。最后将处理后的数据集进行可视化。

前面提到过由于SVM算法对距离比较敏感,因此需要对数据集进行标准化的处理。均值方差归一化Standardscaler函数在sklearn的preprocessing包中,按照Sklearn的使用流程,实例化Standardscaler,通过fit函数求出数据集的均值和方差,最后使用transform函数将传入的数据集按照求出的均值和方差进行均值方差归一化。

由于这一小节目的只是简单演示如何使用Sklearn中封装好的SVM算法进行分类,以及Soft Margin SVM算法中不同的超参数C对分类结果的影响,因此不再划分训练集和测试集。

现在所学的SVM算法其实都是线性SVM,对于线性SVM算法在Sklearn中有一个专门的类LinearSVC,LinearSVC这个类在sklearn.svm模块中。SVC的英文全称为"Support Vector Classifier",故名思议就是使用支撑向量机的思想来进行分类任务。

在实例化LinearSVC类的时候传入上一个小节介绍的超参数C,当:

  • 超参数C越大容错空间越小,模型越接近Hard Margin SVM;
  • 超参数C越小容错空间越大,模型越接近Soft Margin SVM;
  • 为超参数C赋值一个相对比较大的值1e9。

绘制超参数C为1e9时候SVM算法分类的决策边界,这里使用绘制逻辑回归算法决策边界的plot_decision_boundary函数。

传入plot_decision_boundary函数的参数,model为前面训练的LinearSVC模型的实例svc,axis为将x和y轴的范围确定到[-3, 3]之间的列表。

在绘制决策边界的同时将原始的数据点也绘制出来。

可以看出当超参数C为1e9设置的特别大的时候容错空间越小,此时的模型越接近Hard Margin SVM,这也符合前面对超参数C的描述。

  • 接下来给超参数C赋值一个相对比较小的值0.01。

这里依然是传入plot_decision_boundary函数的参数,model为前面训练的LinearSVC模型的实例svc2,axis为将x和y轴的范围确定到[-3, 3]之间的列表。

依然是在绘制决策边界的同时将原始数据点也绘制出来。

对比超参数C为1e9和0.01时候的决策边界,会发现当超参数C为0.01时候的决策边界中有一个蓝色类别的样本点被错误分类,这同样符合前面对超参数C的描述,超参数C越小代表模型的容错空间越大,此时的模型越接近Soft Margin SVM。

对于SVM模型来说同样可以获取训练后求得的决策边界相应的系数coef_和截距intercept_。

SVM算法中的coef_系数值有两个,这是因为对于本小节实验的数据集来说每个样本都有两个特征,每一个特征对应一个系数。此时可以发现返回的coef_系数是一个二维数组,这是因为在sklearn中为我们封装好的SVM算法可以直接处理多分类任务。当处理多分类任务的时候,这个算法相应的就会有多条直线来分割特征平面,每一根直线都会有相应的系数,所以coef_返回的是一个二维数组。不过对于本小节的实验数据集来说,由于只是一个二分类问题,所以只有一根直线,因此二维数组中的第一个元素[4.032, -2.49]列表即为这根直线的系数。

同时还有intercept_截距,返回的是一个一维数组,由于一根直线只有一个截距,就本小节实验的数据集而言,返回的一维数组中只有一个元素。

有了coef_系数和intercept_截距就可以绘制决定margin距离的那两根过支撑向量并且平行于决策边界的直线了。

首先需要重新改造一下plot_decision_boundary函数,复制一份plot_decision_boundary函数代码,为了不覆盖原始的函数将函数名命名为plot_svc_decision_boundary,保留原始的代码不变在新函数中直接添加相应的代码。

为了绘制margin区域中的上下两根直线,首先要取出模型求出的决策边界的系数coef_和截距intercep_。

代码语言:javascript
复制
w = model.coef_[0]
b = model.intercept_[0]

SVM算法训练得到参数w,b,这两个参数就决定了最终的决策边界,不过在具体绘制的时候,需要一个点一个点的进行绘制,所以需要将决策边界的直线方程展开成w0 * x0 + w1 * x1 + b = 0,由于:

  • 横坐标轴表示特征x0
  • 纵坐标轴表示特征x1

因此为了方便将决策边界的直线方程改写成x1 = -w0 / w1 * x0 - b / w1的形式。每当有一个x0都能够计算出对应的x1的值,选取横轴上的一系列值代入方程计算出对应的纵坐标值,将这些点连接起来就可以绘制出决策边界这根直线了。

由于横纵坐标轴的范围设置为[-3, 3]之间,因此通过linspace函数均匀的选取[-3, 3]之间100个横坐标的值,将这100个值存放到命名为plot_x的数组中。

代码语言:javascript
复制
plot_x = np.linspace(axis[0], axis[1], 200)

通过前几个小节的学习大致了解决策边界以及位于决策边界上面和下面两个直线的方程:

  • 决策边界的直线方程:w0 * x0 + w1 * x1 + b = 0;
  • 位于决策边界上面的直线方程:w0 * x0 + w1 * x1 + b = 1;
  • 位于决策边界下面的直线方程:w0 * x0 + w1 * x1 + b = -1;

为了方便依然是将上下两根直线方程进行改写:

  • 位于决策边界上面的直线方程:x1 = -w0 / w1 * x0 - b / w1 + 1 / w1;
  • 位于决策边界下面的直线方程:x1 = -w0 / w1 * x0 - b / w1 - 1 / w1;

由于此时在横轴上取的一系列值命名为plot_x,因此如果想要求出up_y(位于决策边界上面的直线方程中x1改名成up_y)和down_y(位于决策边界下面的直线方程中x1改名成down_y)的值,只需要将上述两个方程中的x0替换成plot_x即可。

代码语言:javascript
复制
up_y = -w[0]/w[1] * plot_x - b/w[1] + 1/w[1]
down_y = -w[0]/w[1] * plot_x - b/w[1] - 1/w[1]

需要注意此时求出的up_y和down_y很有可能已经超过了我们传入函数的axis里面规定的y轴的范围[-3, 3],因此这里进行一个简单的过滤。

代码语言:javascript
复制
up_index = (up_y >= axis[2]) & (up_y <= axis[3])
down_index = (down_y >= axis[2]) & (down_y <= axis[3])

此时的up_index和down_index存放的是满足条件的布尔数组,接下来使用这个布尔数组进行索引来找到up_y和down_y中满足条件的集合。接下来直接绘制出位于决策边界上面和下面的两根直线。

代码语言:javascript
复制
plt.plot(plot_x[up_index], up_y[up_index], color='black')
plt.plot(plot_x[down_index], down_y[down_index], color='black')

完整的plot_svc_decision_boundary函数如下图所示。

代码语言:javascript
复制
def plot_svc_decision_boundary(model, axis):
    """
    绘制分类算法的决策边界
    """
    x0, x1 = np.meshgrid(
        np.linspace(axis[0], axis[1], int((axis[1]-axis[0])*100)).reshape(-1, 1),
        np.linspace(axis[2], axis[3], int((axis[3]-axis[2])*100)).reshape(-1, 1)
    )
    X_new = np.c_[x0.ravel(), x1.ravel()]

    y_predict = model.predict(X_new)
    zz = y_predict.reshape(x0.shape)

    from matplotlib.colors import ListedColormap
    custom_cmap = ListedColormap(['#EF9A9A','#FFF59D','#90CAF9'])

    plt.contourf(x0, x1, zz, linewidth=5, cmap=custom_cmap)

    w = model.coef_[0]
    b = model.intercept_[0]

    # 决策边界方程为 w0 * x0 + w1 * x1 + b = 0
    # 决策边界上面的直线方程为 w0 * x0 + w1 * x1 + b = 1
    # 决策边界下面的直线方程为 w0 * x0 + w1 * x1 + b = -1
    plot_x = np.linspace(axis[0], axis[1], 200)
    up_y = -w[0]/w[1] * plot_x - b/w[1] + 1/w[1]
    down_y = -w[0]/w[1] * plot_x - b/w[1] - 1/w[1]

    up_index = (up_y >= axis[2]) & (up_y <= axis[3])
    down_index = (down_y >= axis[2]) & (down_y <= axis[3])
    plt.plot(plot_x[up_index], up_y[up_index], color='black')
    plt.plot(plot_x[down_index], down_y[down_index], color='black')

现在改进后的plot_svc_decision_boundary函数不仅能够绘制出决策边界,而且还能够绘制出决策边界上面和下面的两根直线。

上图中两根黑色直线之间的距离就是margin,与此同时通过上图可以清晰的看出,两根黑色直线上分别有三个蓝色的点和三个红色的点,这些点就是所谓的支撑向量。由于此时超参数C比较大,因此模型相当于是Hard Margin SVM算法,容错空间比较小,因此在margin区域中间没有任何的样本点。

接下来试试超参数C为0.01时候的svc2,由于超参数C的值设置的非常小,因此模型有很大的容错空间。

此时的margin距离的区域中存在很多样本点,这就是因为超参数C设置的非常小,模型容错空间比较大。

c

小结

当实例化LinearSVC的时候,返回的LinearSVC本身时会发现其中有很多参数:

代码语言:javascript
复制
LinearSVC(C=1000000000.0, class_weight=None, dual=True, fit_intercept=True,
     intercept_scaling=1, loss='squared_hinge', max_iter=1000,
     multi_class='ovr', penalty='l2', random_state=None, tol=0.0001,
     verbose=0)

有两个参数需要注意:

  • multi_class = 'ovr',这个参数表示当遇到多分类问题的时候,算法按照何种方式将二分类问题转换为多分类问题,参数默认值为ovr(One vs Rest, 一对剩余所有),同时也可以是使用ovo(One vs One, 一对一)的方式;
  • penalty = 'l2',这个参数表示的是使用什么方式进行正则化,默认为L2正则化,同时也可以使用l1正则化;

至此这几个小节详细介绍了SVM算法进行分类的原理以及具体的实现。到目前为止,使用SVM算法进行分类都是一种线性的分类方式,但是对高度非线性的数据集,该如何使用SVM算法进行分类呢?下一小节将首先使用多项式特征的方式来处理非线性的问题,紧接着引入SVM算法非常重要的核函数。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI机器学习与深度学习算法 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档