机器学习实验5 支持向量机

  • 实验目的

1、了解SMO求解SVM的过程,调研简化版SMO算法的python实现

2、通过编程得到所给数据对应的SVM分割平面

3、将数据以及所得线性方程结果进行可视化

  • 实验内容:

在《统计学习方法》中找到的smo算法的流程

下面是具体代码步骤:

1、首先要把数据集里的数据进行处理,使用np.loadtxt()把数据读入ndarray,把前两列和最后一列的标签分开,分别存储到data和lables中,并返回。

2、功能函数,som过程中选择第二个alpha,即alpha[j],因为是简化版的,不需要使用启发式优化,只需要随机选择一个和i不同的j即可。

3、通过导数为0,有的限制条件,且,可以根据y1是否等于y2进行分情况讨论,得出二者的限制范围,当超出的时候要进行调整。

当y1 != y2时

当y1 == y2时

4、下面是最主要的smo函数,分开解释

(1)首先是传参和初始化,传入的是特征值data,标签lables,惩罚常数C,容错率tolerance,取消前最大的循环次数epochs。将data,lables转为mat类型,方便后面矩阵运算。声明alphas和b,并置为0。

(2)函数是两层循环,最外层决定循环次数,第二层是遍历数据集中的每一个数据,再随机选取j,二者结合进行优化。

首先是根据SVM对数据点的预测值公式:计算出i点的预测值,并且和标签求差得到Ei。对后面选中的j进行同样的操作。

接着是一个判定条件,如果不满足KKT条件,则需要优化,否则跳过,这里当时写的时候看了好久没看明白,在网上搜索到了资料,所以通过两种如果分类正确就一定不会出现的条件,就可以得到这个判断。

(3)这一步得到eta,-eta是二阶导数,通过数学知识得出,如果eta>=0,即二阶导<=0,没有极小值,无法优化,跳过。后面的a1new,a2new的计算同样要用到eta。

(4)这一步在clipAlpha函数那里解释了,根据多个限制条件,分情况讨论得出alpha的上下界

(5)使用推导出的公式,对alphas[j]进行优化,如果改动很小,则跳过。使用对alphas[i]进行更新。

(6)使用公式

分别解出b1和b2,如果两个都是有效的,那么b1,b2的值是相等的。当两个乘子a1,a2都在边界上,且L != H时,b1,b2之间的值就是和KKT条件一直的阈值。SMO选择他们的中点作为新的阈值:即求均值。

(7)只有100个alpha的误差都在范围之内,即都不再更新了,才能进入下一个递归,不然只能在100个alpha再次循环优化。最后返回alphas和b

5、由alphas和原数据得出w的值,为每个ai*yi*xi的和

6、draw函数将得到的结果可视化。先将点根据标签分别加入两个列表中,然后使用scatter画出。之后画出SVM分割平面,调用get_w求出w,然后根据wT*x+b求出对应y值,使用plt.plot画出分割直线。最后圈出支持向量,根据支持向量满足0 < a < c,找出这样的点,使用scatter画出一个圆圈。

  • 实验原理:

  • 心得体会:

实验中遇到的问题及解决:

  1. 这章的内容相比前几章难度提升很多,且西瓜书上关于smo算法的内容很少,花费很多时间在b站、知乎等网站上学习相关内容。
  2. 实验中需要用到大量矩阵运算和连加求和等,非常容易出现矩阵维度不匹配等情况。通过输出矩阵shape或者在调试器中查看矩阵维度,看是否需要调换运算顺序或者进行转置。
  3. 对于KKT条件的应用掌握不够,如在下面这个判定:如果不满足KKT条件就进行更新,否则跳过。看了好久没看明白。包括如何根据alpha的值判断点的位置等问题。

在网上找到相应的解释

  1. 对超参数的选择不太清楚,根据网上的信息和自己试验,C选择60,tolerance选择0.01效果不错。   
  2. Eta是二阶导数,可以确定是否有极值,数学这部分也有些遗忘,查阅得知,二阶导 < 0有极大值,> 0有极小值,= 0没有极值,可以根据这个进行剪枝,减少计算量。

心得体会:

这次实验是做的时间最长的一次,但是收获很多。在做实验的过程中对SVM的原理及推导进行了复习,弄清了SVM的原理、优点及解决的问题、对偶问题、KKT条件、核函数等内容。

同时也学习到了SMO算法的数学推导和具体流程,其中坐标上升每次通过更新多元函数中的一维,经过多次迭代直到收敛来达到优化函数的目的,每次只是做一维优化,所以每个循环中的优化过程的效率是很高的, 但是迭代的次数会比较多。每次只更新一对变量而固定其他变量。这种方法与之前的反向传播根据梯度更新的算法很不一样,学习到了这种新的思想。

除此以外,我还了解了非简化版的smo,选择第2个变量的过程为内层循环。假设在外层循环中已经找到第1个变量,现在要在内层循环中找第2个变量,第2个变量选择的标准是希望能使有足够大的变化,这样更新效率最高。虽然没有使用程序实现,但是学习到了这个方法和思想是十分有意义的。

通过代码实现SMO,在实践中对于知识进行了进一步的巩固,并且通过matplotlib进行可视化。巩固了所学知识,为以后的学习打下坚实的基础。

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


def get_data(path):
    dataset = np.loadtxt(path)
    data = dataset[:, :2]
    lables = dataset[:, -1]
    return data, lables


def get_j(i, m):
    j = int(random.uniform(0, m))
    while (i == j):
        j = int(random.uniform(0, m))
    return j


def clipAlpha(a, H, L):  # 辅助函数,数值太大或者太小时对其进行调整
    if a > H:
        a = H
    if L > a:
        a = L
    return a


def Smo(data, lables, C, tolerance, epochs):
    m, n = data.shape
    data = np.mat(data)
    lables = np.mat(lables).T
    alphas = mat(np.zeros((m, 1)))
    b = 0
    epoch = 0

    while epoch < epochs:
        has_changed = 0
        for i in range(m):
            fxi = float(multiply(alphas, lables).T * (data * data[i, :].T)) + b
            Ei = fxi - float(lables[i])
            if ((lables[i] * Ei < -tolerance) and (alphas[i] < C)) or (
                    (lables[i] * Ei > tolerance) and (alphas[i] > 0)):
                j = get_j(i, m)
                fxj = float(multiply(alphas, lables).T * (data * data[j, :].T)) + b
                Ej = fxj - float(lables[j])
                alpha_I_old = alphas[i].copy()
                alpha_J_old = alphas[j].copy()
                K_ii, K_jj, K_ij = data[i, :] * data[i, :].T, data[j, :] * data[j, :].T, data[i, :] * data[j, :].T
                eta = 2 * K_ij - K_ii - K_jj
                if eta >= 0:  # 二阶导
                    print("eta >= 0")
                    continue

                if lables[i] != lables[j]:  # 当y1和y2异号,计算alpha的取值范围
                    L = max(0, alphas[j] - alphas[i])
                    H = min(C, C + alphas[j] - alphas[i])
                else:
                    L = max(0, alphas[j] + alphas[i] - C)
                    H = min(C, alphas[j] + alphas[i])
                if L == H:
                    print("L==H")
                    continue

                alphas[j] -= lables[j] * (Ei - Ej) / eta  # 利用公式更新alpha[j]
                alphas[j] = clipAlpha(alphas[j], H, L)
                if (abs(alphas[j] - alpha_J_old) < 0.00001):
                    print("The change in j is not significant")
                    continue
                alphas[i] += lables[j] * lables[i] * (alpha_J_old - alphas[j])  # 利用公式更新alphas[i]

                b1 = b - Ei - lables[i] * (alphas[i] - alpha_I_old) * data[i, :] * data[i, :].T - lables[j] * (
                        alphas[j] - alpha_J_old) * data[i, :] * data[j, :].T
                b2 = b - Ej - lables[i] * (alphas[i] - alpha_I_old) * data[i, :] * data[j, :].T - lables[j] * (
                        alphas[j] - alpha_J_old) * data[j, :] * data[j, :].T
                if 0 < alphas[i] < C:
                    b = b1
                elif 0 < alphas[j] < C:
                    b = b2
                else:
                    b = (b1 + b2) / 2
                has_changed += 1
                print('iteration:{}  i:{}  num:{}'.format(epoch, i, has_changed))
        if has_changed == 0:
            epoch += 1
        else:
            epoch = 0
        print('iteration number: {}'.format(epoch))
    # print(alphas, b)
    return alphas, b


def get_w(alphas, dataset, labels):
    alphas, dataset, labels = np.array(alphas), np.array(dataset), np.array(labels)
    yx = labels.reshape(1, -1).T * np.array([1, 1]) * dataset
    w = np.dot(yx.T, alphas)
    return w.tolist()


def draw(alphas, b, data, lables):
    x1, y1, x2, y2 = [], [], [], []
    for i in range(len(data)):
        if lables[i] == 1:
            x1.append(data[i][0])
            y1.append(data[i][1])
        else:
            x2.append(data[i][0])
            y2.append(data[i][1])
    plt.figure()
    plt.scatter(x1, y1, s=30, c='red')
    plt.scatter(x2, y2, s=30, c='green')

    w = get_w(alphas, data, lables)
    a1, a2 = w
    b = float(b)
    a1 = float(a1[0])
    a2 = float(a2[0])
    y = [(-b - a1 * i) / a2 for i in data[:, 0]]
    plt.plot(data[:, 0], y)

    for i, alpha in enumerate(alphas):
        if abs(alpha) > 0:
            x, y = data[i]
            plt.scatter([x], [y], s=150, c='none', alpha=0.7, linewidth=1.5, edgecolor='red')
    plt.show()


if '__main__' == __name__:
    data, lables = get_data(r"D:\pythonProject\Machine_learning\test5\dataset.txt")
    # print(data, lables)
    alphas, b = Smo(data, lables, 0.6, 0.001, 40)
    # print(alphas.shape)
    draw(alphas, b, data, lables)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值