LIBSVM使用:二维数据SVM支持向量、决策边界与分类间隔可视化(动图)

SVM二维数据的支持向量、决策边界与分类间隔
| 图源

  LIBSVM是台湾大学林智仁教授等开发的一个简单易用、快速有效的SVM、SVR的开源软件包,有各种语言接口,包括python、matlab和java等。现在有很多的机器学习软件包的SVM,SVR都是基于LIBSVM开发的。软件包里超参优化方法是网格法即按照超参的范围和步长,遍历所有超参的组合可能,最后在训练集上平均准确率最高的那一组参数作为最佳参数。当然,本文不涉及寻优,只是涉及LIBSVM数据转换,模型训练,返回值解析和可视化。这是笔者上学期模式识别课程的一个作业,写了很详细注释,也算是一个存货,放在这里,供大家参考。

  博客发布时间:每周二上午10点

copyright© 意疏:https://blog.csdn.net/sinat_35907936/article/details/115110787


LibSVM


  • 数据格式

  LIBSVM的数据格式如下,一行是一个样本,行首是标签(类别编码,二分类时是-1或者1),然后是特征编码:特征的值一组一组的依次排列,组与组之间用空格分开。这样的数据格式的好处是,某些值为0的特征在数据储存时可以省略,因为有特征编号的存在,读数据的时候知道哪些值应该补全。

label < space > 1: feature 1 < space > 2:Feature 2 < space >...... < space > n: feature n
  • 模型参数解析

  SVM,支持向量机,它最本质的含义其实应该是最优分类超平面,表达式可以由式(1)给出,其中 W ∗ W^* W b ∗ b^* b为其参数。由于在优化完成之后,最优分类超平面(决策面)可以由少量向量完全支持住(确定),这些向量就被称为支持向量,所以这个算法才叫支持向量机。

  支持向量也不是什么新的东西,它就是输入样本特征组成的向量 X X X本身,只是要满足一个关系,二分类且不含松弛因子时的支持向量满足的关系由式(2)给出, y i , X i y_i,X_i yi,Xi为第 i i i个样本的标签和特征组成的向量。

W ∗ X + b ∗ = 0 (1) \tag 1 W^*X+b^* = 0 WX+b=0(1)

y i ( W ∗ X i + b ∗ ) − 1 = 0 (2) \tag 2 y_i(W^*X_i+b^*)-1 = 0 yi(WXi+b)1=0(2)

W ∗ = ∑ i = 1 N α i ∗ y i X i ( α = 0 , X i ∉ S V ) (3) \tag 3 W^* =\sum_{i=1}^N \alpha_i^*y_iX_i(\alpha =0 , X_i \notin SV) W=i=1NαiyiXi(α=0,Xi/SV)(3)

  我们要可视化支持向量、决策面,由式(1)确定、和分类间隔,由式(2)得出,我们就先要获取到支持向量SV,决策面的参数 W ∗ W^* W b ∗ b^* b

   W ∗ W^* W由式(3)确定, α i \alpha_i αi为优化时条件约束的拉格朗日乘子,当样本 X i X_i Xi不在SV中时, α i = 0 \alpha_i =0 αi=0,当样本 X i X_i Xi在SV中时, α i \alpha_i αi 不为0。

  相关参数都存在model中,我们只需要调用以下接口即可获得。

  SV=model.get_ SV()

   y α ∗ y\alpha^* yα=model.get_ sv_ coef()

   b ∗ b^* b=model.rho

  sv_idx = model.get_sv_indices()

  注:sv_indices是指属于支持向量的样本在训练集中的序号,用于找到该支持向量的标签,在可视化时需要根据标签配色。


二维数据点产生与格式转换


  • 二维线性可分与不可分数据点产生

  用sklearn.datasets的make_blobs工具分别产生120个线性可分与120个线性不可分的二维数据点,来作为我们的训练数据,是否线性可分由方差std控制。

import sklearn.datasets
import matplotlib.pyplot as plt


def tod_data(n, std):
    X, Y = sklearn.datasets.make_blobs(n_samples=n, n_features=2, centers=2, cluster_std=std, random_state=1)
    return X, Y

if __name__ == '__main__':
    X1, Y1 = tod_data(120, 2)
    X2, Y2 = tod_data(120, 4)
    plt.figure(0)
    plt.scatter(X1[:, 0], X1[:, 1], marker='h', c=Y1)
    plt.figure(1)
    plt.scatter(X2[:, 0], X2[:, 1], marker='h', c=Y2)

    plt.show()
    print(X2.shape, Y2.shape)
    print(X1.shape, Y1.shape)
    # (120, 2) (120,)
    # (120, 2) (120,)
二维线性可分数据二维线性不可分数据
  • 数组转LIBSVM格式

  上述过程产生的数据中,数据是一个二维数组,标签是一个一维向量,标签的值为0或1。我们把它按照LIBSVM的数据格式要求将它们转换到一个文件中,便于使用LIBSVM的svm_read_problem()函数进行读取。

import os

# X是一个二维数组,Y是一个一维向量,测试集可以随意给一个值如x

# libsvm数据整理
def libsvm_data_arrange(filename, X, Y):
    temp_out = []

    if os.path.exists('libsvm_data') == 0:
        os.mkdir('libsvm_data')
    flag = 1  # 行首标签标志
    with open('libsvm_data//' + filename, mode='w+') as f:
        for i in range(len(X)):
            for j in range(len(X[0])):
                if flag:
                    if Y[i] == 0:
                        Y[i] = -1
                    temp_out.append(str(Y[i]))
                    temp_out.append(str(j + 1) + ':' + str('%.6f' % X[i][j]))
                    flag = 0
                else:
                    temp_out.append(str(j + 1) + ':' + str('%.6f' % X[i][j]))
            # print(temp_out)
            flag = 1
            f.write(' '.join(temp_out) + '\n')
            temp_out = []
    f.close()

  转换后的数据如下,我们是做二分类,所以标签是-1或者1,make_blobs产生的数据标签是0或1所以代码中也做了修改。二维数据只有两个特征,即x轴坐标和y轴坐标,他们也是按照特征编码:特征的值的方式存储的。

在这里插入图片描述


可视化


  • 训练

  我们需要通过训练来找最优分类超平面,调用LIBSVM的svm_train()即可开始训练,非常简单,返回值是SVM的model。model在保存之后其实就是一个文本,里面包含一些参数和所有的支持向量(远小于样本数),如下图。

在这里插入图片描述

model1 = svm_train(y1, x1, '-t 0 -c ' + str(c))

   -t: kernel_type,核函数类型

      0:线性函数,相当于没有变换
      2:RBF(default),最常用

   -c: cost,惩罚系数或者松弛因子。

      1:default

  核函数技术,对输入数据进行非线性变换的一种简化手段,目的是希望原线性不可分的数据,在变换后尽量线性可分。在转换后的数据空间,决策面是线性的,投影到原始数据空间决策面就变成了非线性。非线性的决策面和分类间隔不好画,所以只可视化了线性的,即-t 0

  松弛因子C为线性不可分的样本设计的超参数,它的存在,允许了错误分类和分类间隔内样本的出现,分类间隔是指式(3)确定的两条直线中间的部分。松弛因子的大小用于控制这两种样本的数量。C越大分类间隔越小,这两种样本的数量越小,越容易过拟合。C越小分类间隔越大,这两种样本的数量越多,越容易欠拟合,实际应用中需要用寻优方法找到合适的C值。笔者采用了多个C值来可视化,便于直观的感受C与分类间隔之间的关系。

  由于分类间隔内的样本点,也是支持向量,故,含有松弛因子时支持向量的条件:

   边界上的SV:Margin SV
y i ( W ∗ X i + b ∗ ) − 1 = 0 (4) \tag 4 y_i(W^*X_i+b^*)-1 = 0 yi(WXi+b)1=0(4)

   分类间隔内的SV:Other SV, ( ∣ α i ∣ = C ) (|\alpha_i| =C) (αi=C)
y i ( W ∗ X i + b ∗ ) − 1 < 0 (5) \tag 5 y_i(W^*X_i+b^*)-1 < 0 yi(WXi+b)1<0(5)

  • 可视化

   训练完成之后依次取出需要的值,然后用matplotlib.pyplot可视化。每一个C值都对应一张图片,用ScreenToGif可以将其转换成动图。图中边界上的SV用□标识,分类间隔里的SV用△标识。

   分类间隔:

d i s = 2 ∣ ∣ W ∗ ∣ ∣ 2 (6) \tag 6 dis = \cfrac 2 {||W^*||_2} dis=W22(6)

import os
import sklearn.datasets
from svmutil import *
import numpy as np
import math
import matplotlib.pyplot as plt


# 数据点产生
def tod_data(n, std):
    X, Y = sklearn.datasets.make_blobs(n_samples=n, n_features=2, centers=2, cluster_std=std, random_state=1)
    return X, Y


# X是一个二维数组,Y是一个一维向量,测试集没有Y,可以随意赋一个值如'x'

# libsvm数据整理
def libsvm_data_arrange(filename, X, Y):
    temp_out = []

    if os.path.exists('libsvm_data') == 0:
        os.mkdir('libsvm_data')
    flag = 1
    with open('libsvm_data//' + filename, mode='w+') as f:
        for i in range(len(X)):
            for j in range(len(X[0])):
                if flag:
                    if Y[i] == 0:
                        Y[i] = -1
                    temp_out.append(str(Y[i]))
                    temp_out.append(str(j + 1) + ':' + str('%.6f' % X[i][j]))
                    flag = 0
                else:
                    temp_out.append(str(j + 1) + ':' + str('%.6f' % X[i][j]))
            # print(temp_out)
            flag = 1
            f.write(' '.join(temp_out) + '\n')
            temp_out = []
    f.close()


graph_title = ''
model_name = ''
x1 = []
y1 = []
X1 = []
Y1 = []

Linearly_separable_data = True
Linearly_separable_data = False

# 选择线性可分数据,和线性不可分数据
if Linearly_separable_data:
    X1, Y1 = tod_data(120, 2)

    print(X1, Y1)

    # 将产生的数据点转换成libsvm格式
    libsvm_data_arrange('LSD_data', X1, Y1)
    graph_title = 'Linearly separable data'
    model_name = 'LSD_model'
    # 读数据
    y1, x1 = svm_read_problem(r'libsvm_data//LSD_data')
else:
    X1, Y1 = tod_data(120, 4)
    libsvm_data_arrange('LID_data', X1, Y1)
    graph_title = 'Linearly inseparable data'
    model_name = 'LID_model'
    y1, x1 = svm_read_problem(r'libsvm_data//LID_data')

# 惩罚因子c
C = [1, 0.5, 0.2, 0.1, 0.08, 0.06, 0.04, 0.02, 0.008, 0.005, 0.003]

for n in range(len(C)):

    c = C[n]
    # 训练,选线性模型
    model1 = svm_train(y1, x1, '-t 0 -c ' + str(c))
    print(model1)
    sv_temp = []

    # 创建绘图窗口
    plt.figure(2)
    ax = plt.subplot(1, 1, 1)
    ax.set_xlabel('x1')
    ax.set_ylabel('x2')
    ax.set_title(graph_title)

    sv_x1 = []
    sv_y1 = []
    # 找支持向量SV的坐标
    sv1 = model1.get_SV()
    # 找支持向量SV在训练集中的序号,从1开始,注意与列表序号区别,需要减一,y2[sv_idx[i]-1]
    sv_idx1 = model1.get_sv_indices()
    print(sv_idx1)

    for i in range(len(sv_idx1)):
        sv_y1.append(int(y1[sv_idx1[i] - 1]))  # 通过序号找到标签
        for j in range(len(sv1[0])):
            sv_temp.append(sv1[i][j + 1])  # 特征序号从1,开始,故需j + 1
        # print(x1[sv_idx1[i]-1])
        sv_x1.append(sv_temp)
        sv_temp = []
    sv_x1 = np.array(sv_x1).reshape(i + 1, j + 1)
    # print(sv1)
    # print(sv_y1)

    # 系数alpha*y
    alpha1 = model1.get_sv_coef()

    # 求决策面系数w,由alpha和支持向量坐标得到,画支持向量

    w = 0.0
    for i in range(len(alpha1)):
        # w = w + abs(alpha1[i][0]) * sv_y1[i] * sv_x1[i]  # 求w的公式
        w = w + alpha1[i][0] * sv_x1[i]  # 求w的公式
        # print(alpha1[i][0] * sv_y1[i])

        # 画支持向量散点图
        if abs(alpha1[i][0]) < C[n]:
            plt.scatter(sv_x1[i, 0], sv_x1[i, 1], marker='s', color="none", linewidths=1, s=100, edgecolors='red')
        else:
            plt.scatter(sv_x1[i, 0], sv_x1[i, 1], marker="^", color="none", linewidths=1, s=100, edgecolors='blue')

    # 决策面偏置b
    b = -model1.rho[0]

    # 画数据散点图
    ax.scatter(X1[:, 0], X1[:, 1], marker='o', s=20, c=Y1)
    ax.scatter(sv_x1[:, 0], sv_x1[:, 1], marker='x', s=20, c=sv_y1)

    # 画决策边界与决策间隔

    xx = 15
    yy = 15
    dis_x = np.arange(-xx, xx, 0.1)
    dis_y = np.arange(-yy, yy, 0.1)

    dis_x, dis_y = np.meshgrid(dis_x, dis_y)
    z = w[0] * dis_x + w[1] * dis_y + b
    z1 = w[0] * dis_x + w[1] * dis_y + b + 1
    z2 = w[0] * dis_x + w[1] * dis_y + b - 1

    ax.contour(dis_x, dis_y, z, 0, colors='red')
    ax.contour(dis_x, dis_y, z1, 0)
    ax.contour(dis_x, dis_y, z2, 0)

    # 算分类间隔
    dis1 = 2 / math.sqrt(w[0] * w[0] + w[1] * w[1])
    #  画间隔线段

    # 在下边那条线上找一个点,令dis_x = 0
    margin1_y1 = (1 - b) / w[1]
    theta = math.atan(abs(w[1] / w[0]))
    margin1_x2 = 0 + dis1 * math.cos(theta)
    margin1_y2 = margin1_y1 + dis1 * math.sin(theta)

    ax.scatter(0, margin1_y1, marker="P", color="none", linewidths=1, s=100, edgecolors='blue')
    ax.scatter(margin1_x2, margin1_y2, marker="P", color="none", linewidths=1, s=100, edgecolors='blue')
    ax.plot([0, margin1_x2], [margin1_y1, margin1_y2], c='red')

    # 显示惩罚项系数
    ax.text(xx - 7, yy - 2, 'C= ' + str(c), fontdict={'size': '12', 'color': 'b'})

    # 显示间距
    ax.text(xx - 7, yy - 3.5, "margin=" + str('%.2f' % dis1), fontdict={'size': '12', 'color': 'b'})

    # 显示支持向量类型
    ax.scatter(xx - 6.5, yy - 6, marker='s', color="none", linewidths=1, s=100, edgecolors='red')
    ax.text(xx - 5.5, yy - 6.5, "Margin SV", fontdict={'size': '12', 'color': 'b'})

    plt.scatter(xx - 6.5, yy - 7.5, marker='^', color="none", linewidths=1, s=100, edgecolors='blue')
    ax.text(xx - 5.5, yy - 8, "Other SV", fontdict={'size': '12', 'color': 'b'})

    if os.path.exists('LinearSVM_output') == 0:
        os.mkdir('LinearSVM_output')
    plt.savefig('LinearSVM_output' + '//' + model_name + '_C' + str(n) + '.jpg', dpi=100)
    plt.show()

# 保存模型
if os.path.exists('libsvm_model') == 0:
    os.mkdir('libsvm_model')
svm_save_model('libsvm_model//' + model_name, model1)


SVM二维数据的支持向量、决策边界与分类间隔

copyright© 意疏:https://blog.csdn.net/sinat_35907936/article/details/115110787


参考


https://www.csie.ntu.edu.tw/~cjlin/libsvm/

  • 19
    点赞
  • 57
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
基于支持向量机(Support Vector Machine,SVM)的数据分类预测是一种常用的机器学习方法,用于将数据划分为不同的类别。在这里,我将介绍基于libsvm库实现SVM分类的基本步骤。 1. 数据准备:首先,准备用于训练和测试的数据集。数据集应包含已标记的样本,每个样本都有一组特征和对应的类别标签。 2. 特征提取与选择:根据问题的需求,从原始数据中提取出适当的特征作为输入。特征选择可以帮助提高分类性能,减少不必要的维度。 3. 数据预处理:对数据进行预处理操作,如特征缩放、标准化、归一化等,以确保不同特征之间具有相似的尺度。 4. 建立模型:使用libsvm库,选择适当的核函数和参数配置来建立SVM分类模型。常用的核函数包括线性核、多项式核和高斯径向基函数(RBF)核。 5. 训练模型:将准备好的训练数据输入到libsvm库中,根据选择的核函数和参数进行模型训练。训练过程将找到最优的超平,以最大化不同类别之间的间隔。 6. 模型评估:使用测试数据集对训练好的模型进行评估,计算分类准确率、精确率、召回率、F1值等指标,以评估模型的性能。 7. 模型优化:根据评估结果和需求,调整SVM模型的参数配置,如核函数的参数、正则化参数等,以进一步优化分类性能。 8. 预测分类使用训练好的SVM模型对新的未知样本进行分类预测。将样本的特征输入到模型中,根据模型的判定边界将其分为不同的类别。 libsvm是一个常用的SVM库,提供了多种编程语言的接口,如C++, Java, Python等。通过调用相应的接口函数,可以方便地实现SVM分类任务,并进行模型训练和预测。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值