机器学习之SVM算法

206 阅读4分钟
参与拿奖:本文已参与「新人创作礼」活动,一起开启掘金创作之路
ps:代码文末自取
1.相关概念
线性可分:在图中可以通过画出一条直线将两组数据很好的划分开,这条直线称为分割超平面。
间隔:点到分割平面的距离。
分类器或者数据集的间隔:数据集中所有点到分割面最小间隔的2倍。
支持向量:距离分割超平面最近的那些点。
适用数据类型:数值型和标称型数据。
寻求最大间隔
分割超平面可以写作:c=w.T*x+b,点A到超平面距离为:|c|/||w||
函数间隔:label*c
集合间隔:label*c//||w||
使用尖括号表示向量内积<>
松弛变量:引入后允许有些数据点可以处于分割面错误的一侧
2.数据复杂分布的可能情况

image.png


from numpy import *
import matplotlib
import matplotlib.pyplot as plt

xcord0 = []; ycord0 = []; xcord1 = []; ycord1 = []
markers =[]
colors =[]
fr = open('testSet.txt')#this file was generated by 2normalGen.py
for line in fr.readlines():
    lineSplit = line.strip().split('\t')
    xPt = float(lineSplit[0])
    yPt = float(lineSplit[1])
    label = int(lineSplit[2])
    if (label == 0):
        xcord0.append(xPt)
        ycord0.append(yPt)
    else:
        xcord1.append(xPt)
        ycord1.append(yPt)

fr.close()
fig = plt.figure()
ax = fig.add_subplot(221)
xcord0 = []; ycord0 = []; xcord1 = []; ycord1 = []
for i in range(300):
    [x,y] = random.uniform(0,1,2)
    if ((x > 0.5) and (y < 0.5)) or ((x < 0.5) and (y > 0.5)):
        xcord0.append(x); ycord0.append(y)
    else:
        xcord1.append(x); ycord1.append(y)
ax.scatter(xcord0,ycord0, marker='s', s=90)
ax.scatter(xcord1,ycord1, marker='o', s=50, c='red')
plt.title('A')
ax = fig.add_subplot(222)
xcord0 = random.standard_normal(150); ycord0 = random.standard_normal(150)
xcord1 = random.standard_normal(150)+2.0; ycord1 = random.standard_normal(150)+2.0
ax.scatter(xcord0,ycord0, marker='s', s=90)
ax.scatter(xcord1,ycord1, marker='o', s=50, c='red')
plt.title('B')
ax = fig.add_subplot(223)
xcord0 = []; ycord0 = []; xcord1 = []; ycord1 = []
for i in range(300):
    [x,y] = random.uniform(0,1,2)
    if (x > 0.5):
        xcord0.append(x*cos(2.0*pi*y)); ycord0.append(x*sin(2.0*pi*y))
    else:
        xcord1.append(x*cos(2.0*pi*y)); ycord1.append(x*sin(2.0*pi*y))
ax.scatter(xcord0,ycord0, marker='s', s=90)
ax.scatter(xcord1,ycord1, marker='o', s=50, c='red')
plt.title('C')
ax = fig.add_subplot(224)
xcord1 = zeros(150); ycord1 = zeros(150)
xcord0 = random.uniform(-3,3,350); ycord0 = random.uniform(-3,3,350);
xcord1[0:50] = 0.3*random.standard_normal(50)+2.0; ycord1[0:50] = 0.3*random.standard_normal(50)+2.0

xcord1[50:100] = 0.3*random.standard_normal(50)-2.0; ycord1[50:100] = 0.3*random.standard_normal(50)-3.0

xcord1[100:150] = 0.3*random.standard_normal(50)+1.0; ycord1[100:150] = 0.3*random.standard_normal(50)
ax.scatter(xcord0,ycord0, marker='s', s=90)
ax.scatter(xcord1,ycord1, marker='o', s=50, c='red')
plt.title('D')
plt.show()

3.使用SVM实现手写数字二分类

3.1 准备数据集

def getData():
    """
    获取数据集:如果本地没有就直接进行下载
    return:train_dataset,test_dataset
    """
    train_dataset=datasets.MNIST(root="./data/"
                                 ,train=True
                                 # ,transform=transforms.ToPILImage()
                                 ,transform=transforms.ToTensor()
                                 ,download=True
                                 )

    test_dataset=datasets.MNIST(root="./data/"
                                ,train=False
                                # , transform=transforms.ToPILImage()
                                ,transform=transforms.ToTensor()
                                ,download=True
                                )
    # print(train_dataset.data,train_dataset.targets.shape,test_dataset.data,test_dataset.targets.shape)
    return train_dataset,test_dataset

3.2 关于smo算法

构建SMO简化版函数的伪代码如下:
创建一个alpha向量并将其初始化为0向量
当迭代次数小于最大迭代次数时(外循环):
    对数据集中的每个数据向量(内循环):
        如果该数据的向量可以被优化:
            随机选择另外一个数据向量
            同时优化这两个向量
            如果两个向量都不可被优化,推出内循环
    如果所有向量都未被优化,增加迭代次数,继续下一次循环
输入参数分别为 数据集、分类标签、常数c、容错率、退出前最大迭代次数

改进版smo算法:

该改进方法通过一个外循环来选择第一个alpha的值,并且选择过程会在两种方向之间交替进行。
一种方式是:在所有数据集上进行单遍扫描,;另外一种是在非边界alpha中实现单遍扫描。
非边界alpha是指不等于边界0或C的那些值。
选择第一个alpha后,会通过内循环来选择第二个alpha的值,优化过程中h会使用最大步长的方式来获取第二个alpha的值。
计算错误率时会建立一个全局缓存用于保存误差值,从中选取使得步长或者Ei-Ej最大的alpha值。
def smoP(dataMatIn, classLabels, C, toler, maxIter, kTup=('lin', 0)):
    oS = optStruct(mat(dataMatIn), mat(classLabels).transpose(), C, toler) #初始化一个结构类,用于初始化相关变量
    iter = 0 # 迭代次数
    entireSet = True # 是否使用全部数据
    alphaPairsChanged = 0 # alpha
    # 遍历所有的值
    while (iter < maxIter) and ((alphaPairsChanged > 0) or (entireSet)):
        alphaPairsChanged = 0
        if entireSet:
            for i in range(oS.m):
                alphaPairsChanged += innerL(i, oS) # 返回0或者1
            iter += 1
        # 遍历边界值
        else:
            nonBoundIs = nonzero((oS.alphas.A > 0) * (oS.alphas.A < C))[0]
            for i in nonBoundIs:
                alphaPairsChanged += innerL(i, oS)
            iter += 1
        if entireSet:
            entireSet = False
        elif (alphaPairsChanged == 0):
            entireSet = True
    return oS.b, oS.alphas

3.3 运行结果

image.png

完整代码获取

4.小结
支持向量机是一种分类器。
之所以称之为”机“,是因为它会产生一个二值决策结果,即它是一种决策”机“。
支持向量机的泛化错误率较低,具有良好的学习能力,且学到的结果具有推广性。
核方法或者核技巧会将数据(有时为非线性数据)从一个低维空间映射到一个高维空间,可以将一个在低维空间的非线性问题转换到高维空间的线性问题求解。
5.参考资料

[1] 机器学习实战

[2] 书籍源码

[3] jupyter版本