机器学习算法(4)——SVM算法(SMO 算法的推导与实现)


 

目录

 

1.简介

2.分类

2.1、线性可分支持向量机(也称为硬间隔支持向量机)

2.2、线性支持向量机(也称为软间隔支持向量机)

2.3、非线性支持向量机

2.4、函数间隔与几何间隔

2.4.1、函数间隔

2.4.2、几何间隔

2.4.3、函数间隔与几何间隔的关系

2.5、几类支持向量机的算法

2.5.1、线性可分支持向量机学习算法——最大间隔法

2.5.2、线性可分支持向量机学习算法

2.5.3、线性支持向量机学习算法

2.5.4、非线性支持向量机学习算法(核函数)

3.序列最小最优化算法——SMO 算法

3.1、SMO 算法的第一部分——2个变量二次规划的求解方法

3.2、SMO的第二部分——变量的选择方法

<1>、第一个变量的选择

<2>、第一个变量的选择

<3>、计算阈值b和差值Ei

3.3、SMO 算法

4、SMO 算法的Python实现

4.1、选择第一个参数并更新

4.2、选择第二个变量

4.3、误差的计算

5、完整训练代码及数据集链接

5.1、完整代码及训练结果

5.2、全部文件链接(github)

参考书籍及网络资源:


1.简介

支持向量机是一种基于分类边界的方法。其基本原理是(以二维数据为例):如果训练数据分布在二维平面上的点,它们按照其分类聚集在不同的区域。基于分类边界的分类算法的目标是,通过训练,找到这些分类之间的边界(直线的――称为线性划分,曲线的――称为非线性划分)。对于多维数据(如 N 维),可以将它们视为 N 维空间中的点,而分类边界就是 N 维空间中的面,称为超面(超面比 N维空间少一维)。线性分类器使用超平面类型的边界,非线性分类器使用超曲面。
          支持向量机的原理是将低维空间的点映射到高维空间,使它们成为线性可分,再使用线性划分的原理来判断分类边界。在高维空间中是一种线性划分,而在原有的数据空间中,是一种非线性划分。SVM 在解决小样本、非线性及高维模式识别问题中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中。

2.分类

2.1、线性可分支持向量机(也称为硬间隔支持向量机)

当训练数据集可分时,通过硬间隔最大化,学得一个线性可分支持向量机。

2.2、线性支持向量机(也称为软间隔支持向量机)

当训练数据集近似线性可分时,通过软间隔最大化学得一个线性支持向量机。

2.3、非线性支持向量机

当训练数据集不可分时,通过使用核技巧以及软间隔最大化,学得一个非线性支持向量机。

2.4、函数间隔与几何间隔

2.4.1、函数间隔

2.4.2、几何间隔

2.4.3、函数间隔与几何间隔的关系

2.5、几类支持向量机的算法

2.5.1、线性可分支持向量机学习算法——最大间隔法

2.5.2、线性可分支持向量机学习算法

2.5.3、线性支持向量机学习算法

2.5.4、非线性支持向量机学习算法(核函数)

3.序列最小最优化算法——SMO 算法

3.1、SMO 算法的第一部分——2个变量二次规划的求解方法

3.2、SMO的第二部分——变量的选择方法

SMO 算法在每个子问题中选择2个变量优化,其中至少一个变量是违反KKT条件的。

<1>、第一个变量的选择

          SMO 称选择第1个变量的过程中是外层循环;

          外层循环在训练样本中选取违反KKT条件最严重的样本点,并将其对应的变量作为第一个变量,

具体地,检验样本点(xi, yi)是否满足KKT条件:

\alpha_i=0,<=>y_ig(x_i)\geq 1

0<\alpha<C,<=>y_ig(x_i)=1

\alpha_i=C,<=>y_ig(x_i)\leqslant 1

其中,

g(x_i)=\sum_{i=1}^{N} \alpha_j y_j K(x_i,x_j)+b

该检验是在允许范围内进行的,在检验过程中,外层循环首先遍历所有满足条件的样本点,即在间隔边界上的支持向量点,检验他们是否满足KKT条件,那么遍历整个训练数据集,检验他们是否满足KKT条件。

<2>、第一个变量的选择

SMO 称选择第2个变量的过程中是内层循环;

                假设在外层循环中已经找到了第一个变量,现在要在内层循环中找到第二个变量,第二个变量的选择是变量

        必须有足够的变化。

<3>、计算阈值b和差值Ei

3.3、SMO 算法

4、SMO 算法的Python实现

4.1、选择第一个参数并更新

首先,判断选择的第一变量是否满足KKT条件,在判断的过程中计算误差值Ei,当检查完第一个变量后,需要选择第二个变量,对于第二个变量,选择的标准是使得其改变最大。具体规程如下:

# 选择并更新参数
def choose_and_update(svm, alpha_i):
    # 计算第一个样本的误差error_i
    error_i = cal_error(svm, alpha_i)

    # 判断选择出的第一个变量是否违反了 KKT 条件
    if (svm.train_y[alpha_i]*error_i < -svm.toler) and (svm.alphas[alpha_i] < svm.C) or (svm.train_y[alpha_i]*error_i >
        svm.toler) and (svm.alphas[alpha_i] > 0):
        # 1.选择第二个变量
        alpha_j, error_j = select_second_sample_j(svm, alpha_i, error_i)
        alpha_i_old = svm.alphas[alpha_i].copy()
        alpha_j_old = svm.alphas[alpha_j].copy()

        # 2.计算上下界
        if svm.train_y[alpha_i] != svm.train_y[alpha_j]:
            L = max(0, svm.alphas[alpha_j] - svm.alphas[alpha_i])
            H = min(svm.C, svm.C + svm.alphas[alpha_j] - svm.alphas[alpha_i])
        else:
            L = max(0, svm.alphas[alpha_j] + svm.alphas[alpha_i] - svm.C)
            H = min(svm.C, svm.alphas[alpha_j] + svm.alphas[alpha_i])
        if L == H:
            return 0

        # 3.计算 eta
        eta = 2.0 * svm.kernel_mat[alpha_i, alpha_j] - svm.kernel_mat[alpha_i, alpha_i] - svm.kernel_mat[alpha_j, alpha_j]
        if eta > 0:
            return 0

        # 4.更新 alpha_j
        svm.alphas[alpha_j] -= svm.train_y[alpha_j] * (error_i - error_j)/eta

        # 5.确定最终的 alpha_j
        if svm.alphas[alpha_j] > H:
            svm.alphas[alpha_j] = H
        if svm.alphas[alpha_j] < L:
            svm.alphas[alpha_j] = L

        # 6. 判断是否结束
        if abs(alpha_j_old - svm.alphas[alpha_j]) < 0.00001:
            update_error_tmp(svm, alpha_j)
            return 0

        # 7.更新 alpha_i
        svm.alphas[alpha_i] += svm.train_y[alpha_i] * svm.train_y[alpha_j] * (alpha_j_old - svm.alphas[alpha_j])

        # 8.更新 b
        b1 = svm.b - error_i - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_i]\
             -svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_i, alpha_j]
        b2 = svm.b - error_j - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_j]\
             -svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_j, alpha_j]
        if 0 < svm.alphas[alpha_i] < svm.C:
            svm.b = b1
        elif 0 < svm.alphas[alpha_j] < svm.C:
            svm.b = b2
        else:
            svm.b = (b1 + b2)/2.0

        # 9.更新 error
        update_error_tmp(svm, alpha_j)
        update_error_tmp(svm, alpha_i)
        return 1
    else:
        return 0

4.2、选择第二个变量

对于第二个变量的选择,选择的标准是使得误差值改变最大。

# 选择第二个变量
def select_second_sample_j(svm, alpha_i, error_i):
    svm.error_tmp[alpha_i] = [1, error_i]
    candidateAlphaList = np.nonzero(svm.error_tmp[:, 0].A)[0]
    maxStep = 0
    alpha_j = 0
    error_j = 0

    if len(candidateAlphaList) > 1:
        for alpha_k in candidateAlphaList:
            if alpha_k == alpha_i:
                continue
            error_k = cal_error(svm, alpha_k)
            if abs(error_k-error_i) > maxStep:
                maxStep = abs(error_k - error_i)
                alpha_j = alpha_k
                error_j = error_k
    else:
        alpha_j = alpha_i
        while alpha_j == alpha_i:
            alpha_j = int(np.random.uniform(0, svm.n_samples))
        error_j = cal_error(svm, alpha_j)
    return alpha_j, error_j

4.3、误差的计算

# 计算误差
def cal_error(svm, alpha_k):
    output_k = float(np.multiply(svm.alphas, svm.train_y).T * svm.kernel_mat[:, alpha_k]+svm.b)
    error_k = output_k - float(svm.train_y[alpha_k])
    return error_k

5、完整训练代码及数据集链接

5.1、完整代码及训练结果

# -*- coding: utf-8 -*-
# @Time    : 2019-1-15 18:58
# @Author  : Chaucer_Gxm
# @Email   : gxm4167235@163.com
# @File    : SVM_Train.py
# @GitHub  : https://github.com/Chaucergit/Code-and-Algorithm
# @blog    : https://blog.csdn.net/qq_24819773
# @Software: PyCharm
import numpy as np
import _pickle as pickle


class SVM:
    def __init__(self, dataSet, labels, C, toler, kernel_option):
        self.train_x = dataSet
        self.train_y = labels
        self.C = C
        self.toler = toler
        self.n_samples = np.shape(dataSet)[0]
        self.alphas = np.mat(np.zeros((self.n_samples, 1)))
        self.b = 0
        self.error_tmp = np.mat(np.zeros((self.n_samples, 2)))
        self.kernel_opt = kernel_option
        self.kernel_mat = calc_kernel(self.train_x, self.kernel_opt)


# 核函数矩阵
def calc_kernel(train_x, kernel_option):
    m = np.shape(train_x)[0]
    kernel_matrix = np.mat(np.zeros((m, m)))
    for i in range(m):
        kernel_matrix[:, i] = cal_kernel_value(train_x, train_x[i, :], kernel_option)
    return kernel_matrix


# 定义样本之间的核函数的值
def cal_kernel_value(train_x, train_x_i, kernel_option):
    kernel_type = kernel_option[0]
    m = np.shape(train_x)[0]
    kernel_value = np.mat(np.zeros((m, 1)))
    if kernel_type == 'rbf':
        sigma = kernel_option[1]
        if sigma == 0:
            sigma = 1.0
        for i in range(m):
            diff = train_x[i, :] - train_x_i
            kernel_value[i] = np.exp(diff * diff.T/(-2.0 * sigma**2))
    else:
        kernel_value = train_x * train_x_i.T
    return kernel_value


def SVM_training(train_x, train_y, C, toler, max_iter, kernel_option=('rbf', 0.431029)):
    # 1.初始化 SVM 分类器
    svm = SVM(train_x, train_y, C, toler, kernel_option)
    # 2.开始训练 SVM 分类器
    entireSet = True
    alpha_pairs_changed = 0
    iteration = 0
    while (iteration < max_iter) and((alpha_pairs_changed > 0) or entireSet):
        print('\t 迭代次数为:', iteration)
        alpha_pairs_changed = 0
        if entireSet:
            for x in range(svm.n_samples):
                alpha_pairs_changed += choose_and_update(svm, x)
            iteration += 1
        else:
            bound_samples = []
            for i in range(svm.n_samples):
                if 0 < svm.alphas[i, 0] < svm.C:
                    bound_samples.append(i)
            for x in bound_samples:
                alpha_pairs_changed += choose_and_update(svm, x)
            iteration += 1
        if entireSet:
            entireSet = False
        elif alpha_pairs_changed == 0:
            entireSet = True
    return svm


# 选择并更新参数
def choose_and_update(svm, alpha_i):
    # 计算第一个样本的误差error_i
    error_i = cal_error(svm, alpha_i)

    # 判断选择出的第一个变量是否违反了 KKT 条件
    if (svm.train_y[alpha_i]*error_i < -svm.toler) and (svm.alphas[alpha_i] < svm.C) or (svm.train_y[alpha_i]*error_i >
        svm.toler) and (svm.alphas[alpha_i] > 0):
        # 1.选择第二个变量
        alpha_j, error_j = select_second_sample_j(svm, alpha_i, error_i)
        alpha_i_old = svm.alphas[alpha_i].copy()
        alpha_j_old = svm.alphas[alpha_j].copy()

        # 2.计算上下界
        if svm.train_y[alpha_i] != svm.train_y[alpha_j]:
            L = max(0, svm.alphas[alpha_j] - svm.alphas[alpha_i])
            H = min(svm.C, svm.C + svm.alphas[alpha_j] - svm.alphas[alpha_i])
        else:
            L = max(0, svm.alphas[alpha_j] + svm.alphas[alpha_i] - svm.C)
            H = min(svm.C, svm.alphas[alpha_j] + svm.alphas[alpha_i])
        if L == H:
            return 0

        # 3.计算 eta
        eta = 2.0 * svm.kernel_mat[alpha_i, alpha_j] - svm.kernel_mat[alpha_i, alpha_i] - svm.kernel_mat[alpha_j, alpha_j]
        if eta > 0:
            return 0

        # 4.更新 alpha_j
        svm.alphas[alpha_j] -= svm.train_y[alpha_j] * (error_i - error_j)/eta

        # 5.确定最终的 alpha_j
        if svm.alphas[alpha_j] > H:
            svm.alphas[alpha_j] = H
        if svm.alphas[alpha_j] < L:
            svm.alphas[alpha_j] = L

        # 6. 判断是否结束
        if abs(alpha_j_old - svm.alphas[alpha_j]) < 0.00001:
            update_error_tmp(svm, alpha_j)
            return 0

        # 7.更新 alpha_i
        svm.alphas[alpha_i] += svm.train_y[alpha_i] * svm.train_y[alpha_j] * (alpha_j_old - svm.alphas[alpha_j])

        # 8.更新 b
        b1 = svm.b - error_i - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_i]\
             -svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_i, alpha_j]
        b2 = svm.b - error_j - svm.train_y[alpha_i]*(svm.alphas[alpha_i]-alpha_i_old)*svm.kernel_mat[alpha_i, alpha_j]\
             -svm.train_y[alpha_j]*(svm.alphas[alpha_j]-alpha_j_old)*svm.kernel_mat[alpha_j, alpha_j]
        if 0 < svm.alphas[alpha_i] < svm.C:
            svm.b = b1
        elif 0 < svm.alphas[alpha_j] < svm.C:
            svm.b = b2
        else:
            svm.b = (b1 + b2)/2.0

        # 9.更新 error
        update_error_tmp(svm, alpha_j)
        update_error_tmp(svm, alpha_i)
        return 1
    else:
        return 0


# 计算误差
def cal_error(svm, alpha_k):
    output_k = float(np.multiply(svm.alphas, svm.train_y).T * svm.kernel_mat[:, alpha_k]+svm.b)
    error_k = output_k - float(svm.train_y[alpha_k])
    return error_k


# 选择第二个变量
def select_second_sample_j(svm, alpha_i, error_i):
    svm.error_tmp[alpha_i] = [1, error_i]
    candidateAlphaList = np.nonzero(svm.error_tmp[:, 0].A)[0]
    maxStep = 0
    alpha_j = 0
    error_j = 0

    if len(candidateAlphaList) > 1:
        for alpha_k in candidateAlphaList:
            if alpha_k == alpha_i:
                continue
            error_k = cal_error(svm, alpha_k)
            if abs(error_k-error_i) > maxStep:
                maxStep = abs(error_k - error_i)
                alpha_j = alpha_k
                error_j = error_k
    else:
        alpha_j = alpha_i
        while alpha_j == alpha_i:
            alpha_j = int(np.random.uniform(0, svm.n_samples))
        error_j = cal_error(svm, alpha_j)
    return alpha_j, error_j


# 重新计算误差值
def update_error_tmp(svm, alpha_k):
    error = cal_error(svm, alpha_k)
    svm.error_tmp[alpha_k] = [1, error]


def load_data_libsvm(filename):
    df = open(filename)
    features = []
    labels = []
    for line in df.readlines():
        lines = line.strip().split(' ')    # lines = ['+1', '1:0.708333', '2:1', '3:1', '4:-0.320755', '5:-0.105023', '6:-1', '7:1', '8:-0.419847', '9:-1', '10:-0.225806', '12:1', '13:-1']
        # print(lines[1][1])
        labels.append(float(lines[0]))
        index = 0
        tmp = []
        for i in range(1, len(lines)):
            x = lines[i].strip().split(':')
            # print(x[1])
            if int(x[0])-1 == index:
                tmp.append(float(x[1]))
            else:
                while int(x[0])-1 > index:
                    tmp.append(0)
                    index += 1
                tmp.append(float(x[1]))
            index += 1
        while len(tmp) > 13:
            tmp.append(0)
        features.append(tmp)
    df.close()
    return np.mat(features), np.mat(labels).T


def cal_accuracy(svm, test_x, test_y):
    n_samples = np.shape(test_x)[0]
    correct = 0.0
    for i in range(n_samples):
        predict = svm_predict(svm, test_x[i, :])
        if np.sign(predict) == np.sign(test_y[i]):
            correct += 1
    accuracy = correct / n_samples
    return accuracy


def svm_predict(svm, test_sample_x):
    kernel_value = cal_kernel_value(svm.train_x, test_sample_x, svm.kernel_opt)
    predict = kernel_value.T * np.multiply(svm.train_y, svm.alphas) + svm.b
    return predict


# 保存模型
def save_model(svm_model, model_file):
    with open(model_file, 'wb') as file:
        pickle.dump(svm_model, file)


def main():
    # 1.导入数据集
    print('********* 导入数据集 **********')
    train_data, label_data = load_data_libsvm('heart_scale')
    print(train_data.shape, label_data.shape)
    # 2.训练 SVM 模型
    print('********* 训练 SVM 模型 **********')
    C = 0.6
    toler = 0.001
    maxIter = 500
    svm_model = SVM_training(train_data, label_data, C, toler, maxIter)
    # 3.计算模型的准确性
    print('********* 计算模型的准确性 **********')
    accuracy = cal_accuracy(svm_model, train_data, label_data)
    print('训练精度为:%.3f%%' % (accuracy*100))
    # 4.保存最终的模型
    print('********* 保存最终的模型 **********')
    save_model(svm_model, "model_file")

if __name__ == '__main__':
    main()

训练结果:

********* 导入数据集 **********
(270, 13) (270, 1)
********* 训练 SVM 模型 **********
	 迭代次数为: 0
	 迭代次数为: 1
	 迭代次数为: 2
	 迭代次数为: 3
	 迭代次数为: 4
********* 计算模型的准确性 **********
训练精度为:97.037%
********* 保存最终的模型 **********

5.2、全部文件链接(github)

https://github.com/Chaucergit/Code-and-Algorithm/tree/master/ML/4.SVM-SMO


参考书籍及网络资源:

[1].统计方法.李航

[2].Python机器学习算法.赵志勇

[3].利用Python进行数据分析.WesKinney著,唐学韬等译

[4].https://zh.wikipedia.org/wiki/%E6%94%AF%E6%8C%81%E5%90%91%E9%87%8F%E6%9C%BA

  • 0
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值