支持向量机SVM及python实现

Posted 某睿鸭

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了支持向量机SVM及python实现相关的知识,希望对你有一定的参考价值。

支持向量机

SVM是一种二分类模型。它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使它有别于感知机;核技巧使他成为实质上的非线性分类器。SVM是求解凸二次规划的问题最优化问题。
SVM可分为:

  • 线性可分支持向量机:数据线性可分,通过硬间隔最大化,学习一个线性分类器。
  • 线性支持向量机:数据近似线性可分,通过软间隔最大化,学习一个分类器。
  • 非线性支持向量机:数据线性不可分,通过使用核技巧及软间隔最大化。

技术:

  • SMO
  • 核函数
  • 序列最小最优化

输入空间为欧氏空间或这是离散集合、特征空间是希尔伯特空间(在一个实向量空间或复向量空间H上的给定的内积 < x , y > < x,y > <x,y>可以按照如下的方式导出一个范数(norm): ∣ x ∣ = < x , y > |x|=\\sqrt{<x,y>} x=<x,y> 。如果其对于这个范数来说是完备的,此空间称为是一个希尔伯特空间),核函数表示将输入从输出空间映射到特征空间得到的特征向量之间的内积,通过。使用核函数可以学习非线性支持向量机,等价于隐式的在高维的特征空间中学习线性支持向量机。

一、线性可分支持向量机

1. 函数间隔和几何间隔


函数间隔可以表示分类预测的正确性及确信度。但是选择分离超平面时,只有函数间隔还不够。因为函数间隔改变时,超平面有可能没有改变。所以我们可以对分离超平面的法向量ω加些约束,如规范化, ∣ ∣ ω ∣ ∣ = 1 ||ω||=1 ω=1,使得间隔是确定的。这时函数间隔变为几何间隔。

从函数间隔与几何间隔的定义可知,函数间隔和几何间隔有下面的关系:

如果 ∣ ∣ ω ∣ ∣ = 1 ||ω||=1 ω=1,那么函数间隔和几何间隔相等。如果超平面参数 ω ω ω b b b成比例的改变(超平面没有改变),函数间隔也按此比例改变,而几何间隔不变。

2. 间隔最大化

间隔最大化的直观解释是:对训练数据集找到几何间隔最大的超平面意味着以充分大的确信度对训练数据集进行分类。也就是说,不仅将正负实例点分开,而且对最难分的实例点(离超平面最近的点)也有足够大的确信度将它们分开。这样的超平面应该对位置的新实例有很好的分类预测能力。




def _f(self, i):
    r = self.b
    for j in range(self.m):
        r += self.alpha[j] * self.Y[j] * self.kernel(self.X[i], self.X[j])
    return r


def _KKT(self, i):
    y_f = self._f(i) * self.Y[i]
    if not (self.alpha[i] >0):
        return False
    elif not(y_f>=0):
        return False
    elif not(self.alpha[0]*y_f == 0):
        return False
    return True

KKT条件详解【点击即可阅读】


二、 线性支持向量机

如何使用SVM处理线性不可分问题呢?这就需要修改硬间隔最大化,使其成为软间隔最大化。
软间隔的优化目标可写为:









三、非线性支持向量机

1. 核技巧

用线性分类方法解决非线性分类问题分为两步:首先是用一个变换将原空间的数据映射到新空间;然后在新空间利用线性分类学习方法从训练数据中学习分类模型。


2. 正定核

不用构造映射函数 ϕ ( 𝑥 ) \\phi(𝑥) ϕ(x)能否直接判断一个给定的函数 K ( x , z ) K(x,z) K(x,z)是不是核函数? 通常所说的核函数指的是正定核函数。

3. 常用核函数

特征空间的好坏对支持向量机的性能至关重要。需要注意的是,在不知道特征映射空间的形式时,我们并不知道什么样的核函数是合适的,而核函数也仅是隐式的定义了这个特征空间。于是,“核函数选择”成为支持向量机的最大变数。

def kernel_fuc(x, x2, kernel_Type):
     """
     核函数
     :param x: 支持向量的特征
     :param x2: 某一例的特征
     :param kernel_Type: 核函数类型和相应参数
     :return:
     """
     m, n = x.shape
     K = np.mat(np.zeros((m, 1)))
     if kernel_Type[0] == "line":  # 线性核
         K = x * x2.T
     elif kernel_Type[0] == "radial":  # 高斯核
         for j in range(m):
             deltaRow = x[j, :] - x2
             # print(deltaRow.T)
             try:
                 K[j] = deltaRow * deltaRow.T
             except:
                 print(deltaRow)
         K = np.exp(K / (-2 * kernel_Type[1] ** 2))  # 返回生成的结果
     elif kernel_Type[0] == "Sigmoid":  # Sigmoid核
         K = np.tanh(kernel_Type[1] * x * x2.T + kernel_Type[2])
     elif kernel_Type[0] == "Laplace":  # 拉普拉斯核
         for j in range(m):
             deltaRow = x[j, :] - x2
             K[j] = deltaRow * deltaRow.T
         K = np.exp(K / (-1 * kernel_Type[1]))
     elif kernel_Type[0] == "poly":
         K = x * x2.T
         K = K ** kernel_Type[1]
     else:
         raise NameError('Houston We Have a Problem -- That Kernel is not recognized')
     return K

字符串核函数

4. 非线性支持向量机

四、 序列最小最优化算法

当训练样本容量很大时,这些算法往往变得非常低效,以致无法使用。所以,如何高效地实现支持向量机学习成为一个重要的问题。本节讲述序列最小优化算法(SMO)。
SMO算法要解如下凸二次规划的对偶问题:

1. 两个变量二次规划的求解方法


def calcEk(self, i):
	# 计算Ek(参考《统计学习方法(第二版)》p145公式7.105)
	# print(self.alphas)
	# print(self.y)
	# print(np.shape(self.alphas))
	# print(np.shape(self.y))
	fXk = float(np.multiply(self.alphas, self.y).T * self.Kernel[:, i] + self.b)
	Ek = fXk - float(self.y[i])
return Ek

alpha2_new_unc = self.alpha[i2] + self.Y[i2] * (E1 - E2) / eta
alpha2_new = self._compare(alpha2_new_unc, L, H)
alpha1_new = self.alpha[i1] + self.Y[i1] * self.Y[i2] *(self.alpha[i2] - alpha2_new)
def _compare(self, _alpha, L, H):
	if _alpha > H:
		return H
	elif _alpha < L:
		return L
	else:
		return _alpha

2. 变量的选择方法

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


def inner(self, i):
        """
        首先检验ai是否满足KKT条件,如果不满足,随机选择aj进行优化,更新ai,aj,b值
        """
        Ei = self.calcEk(i)
        if ((self.y[i] * Ei < -self.eps) and (self.alphas[i] < self.C)) or (
                (self.y[i] * Ei > self.eps) and (self.alphas[i] > 0)):
            # 检验这行数据是否符合KKT条件 参考《统计学习方法(第二版)》p145公式7.111-113
            # 阿喂,这里方法是取了反的,就是不满足的话,才进来
            j, Ej = self.selectJ(i, Ei)
            alpha1old = self.alphas[i].copy()
            alpha2old = self.alphas[j].copy()

            # 以下参考《统计学习方法(第二版)》p144公式
            if (self.y[i] != self.y[j]):
                L = max(0, self.alphas[j] - self.alphas[i])
                H = min(self.C, self.C + self.alphas[j] - self.alphas[i])
            else:
                L = max(0, self.alphas[j] - self.alphas[j] - self.C)
                H = min(self.C, self.alphas[j] + self.alphas[i])
            if L == H:
                print("L==H")
                return 0

            # 参考《统计学习方法(第二版)》p145公式7.107
            eta = 2.0 * self.Kernel[i, j] - self.Kernel[i, i] - self.Kernel[j, j]
            if eta >= 0:
                print("eta>=0")
                return 0
            # 参考《统计学习方法(第二版)》p145公式7.106
            self.alphas[j] -= self.y[j] * (Ei - Ej) / eta
            # 参考《统计学习方法(第二版)》p145公式7.108
            self.alphas[j] = self.clipAlpha(self.alphas[j], H, L)
            # 更新alphas[j]的缓存
            self.updateEk(j)

            # alpha变化大小阀值(自己设定)
            if (abs(self.alphas[j] - alpha2old) < self.eps):
                print("j not moving enough")
                return 0
            # 参考《统计学习方法(第二版)》p145公式7.109
            self.alphas[i] += self.y[i] * self.y[j] * (alpha2old - self.alphas[j])
            # 更新alphas[i]的缓存
            self.updateEk(i)

            # 求解b,参考《统计学习方法(第二版)》p148公式7.115
            b1 = self.b - Ei - self.y[i] * (self.alphas[i] - alpha1old) * self.Kernel[i, i] - self.y[j] * (
                    self.alphas[j] - alpha2old) * self.Kernel[i, j]
            # 求解b,参考《统计学习方法(第二版)》p148公式7.116
            b2 = self.b - Ej - self.y[i] * (self.alphas[i] - alpha1old) * self.Kernel[i, j] - self.y[j] * (
                    self.alphas[j] - alpha2old) * self.Kernel[j, j]
            if 0 < self.alphas[i] < self.C:
                self.b = b1
            elif 0 < self.alphas[j] < self.C:
                self.b = b2
            else:
                self.b = (b1 + b2) / 2.0
            return 1
        else:
            return 0

def SMO(self, X, y):
     iter = 0  # 迭代次数
     entireSet = True  # 未知
     alphaPairsChanged = 0  # alpha的改变量
     while (iter < self.max_iter) and ((alphaPairsChanged > 0) or entireSet):
         alphaPairsChanged = 0
         if entireSet:
             for i in range(self.m):
                 alphaPairsChanged += self.inner(i)
                 print("边界,迭代次数:%d  特征第%d行使得alpha改变,改变了%d" % (iter, i, alphaPairsChanged))
             iter += 1
         else:
             #   mat.A 将矩阵转换为数组类型(numpy 的narray)
             nonBoundIs = np.nonzero((self.alphas.A > 0) * (self.alphas.A < self.C))[0]
             for i in nonBoundIs:
                 # 遍历非边界的数值
                 alphaPairsChanged += self.inner(i)
                 print("非边界,迭代次数:%d  特征第%d行使得alpha改变,改变了%d" % (iter, i, alphaPairsChanged))
             iter += 1

         if entireSet:
             entireSet = False
         elif (alphaPairsChanged == 0):
             entireSet = True
         print("当前迭代次数:%i" % iter)

五、 支持向量回归



六、代码

代码1

本代码来自机器学习初学者(公众号)提供的代码。

import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt


# data
def create_data():
    iris = load_iris()
    df = pd.DataFrame(iris.data, columns=iris.feature_names)
    df['label'] = iris.target
    df.columns = [
        'sepal length', 'sepal width', 'petal length', 'petal width', 'label'
    ]
    data = np.array(df.iloc[:100, [0, 1, -1]])
    for i in range(len(data)):
        if data[i, -1] == 0:
            data[i, -1支持向量机及Python代码实现

机器学习算法及代码实现–支持向量机

独家 | 支持向量机背后的数学 -对于SVM背后的数学和理论解释的快速概览及如何实现

机器学习:通俗理解支持向量机SVM及代码实践

机器学习基础:通俗理解支持向量机SVM及代码实践

机器学习算法-python实现svm支持向量机—理论知识介绍