支持向量机svm是一种二分类模型,它的基本模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使它有区别于感知机; 其次感知机还有核技巧,这使它有成为非线性分类器.感知机的学习策略就是间隔最大化.
支持向量机分类: 线性可分支持向量机(linear support vector machine in linearly separable case)、线性支持向量机(linear support vector machine)及非线性支持向量机(non-linear support vector machine)
假设输入空间与特征空间为两个不同的空间。输入空间为欧 氏空间或离散集合,特征空间为欧氏空间或希尔伯特空间。线性可分支持向量机、线性支 持向量机假设这两个空间的元素一一对应,并将输入空间中的输入映射为特征空间中的特 征向量。 学习的目标是在特征空间中找到一个分离超平面,能将实例分到不同的类。分离超平 面对应于方程w·x+b=0,它由法向量w和截距b决定,可用(w,b)来表示。分离超平面将特 征空间划分为两部分,一部分是正类,一部分是负类。法向量指向的一侧为正类,另一侧 为负类。 超平面为:
分类决策函数为(线性可分向量机):
函数间隔:一般来说距离超平面的远近可以表示分类预测的确信度.
能够相对的表示点x距离超平面的远近.而
的符号与y的符号一致性表示是否分类正确.所以
. 几何间隔:函数间隔可以表示分类预测的正确性及确信度。但是选择分离超平面时,只有函数间 隔还不够。因为只要成比例地改变w和b,例如将它们改为2w和2b,超平面并没有改变, 但函数间隔却成为原来的2倍。我们可以对分离平面的法向量加约束,比如规范化
.
其中,||w||为w的L2范数 间隔最大化:支持向量机的基本思想就是求解能够正确划分训练集的并且几何间隔最大的分离超平面.几何间隔最大的分离超平面是唯一的. 支持向量:在线性可分的情况下,训练数据集的样本点钟毓分离超平面距离最近的样本点位支持向量(support vector)
都是支持向量,
其中
之间的为间隔.
为间隔边界 如下图所示: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VSfKumso-1638437424565)(https://raw.githubusercontent.com/errolyan/tuchuang/master/uPic/n9XEPb.png)]
线性可分问题的支持向量机学习方法,对于线性不可分训练数据是不适用的. 训练数据集:
线性不可分就是
不能满足函数间隔大于等于1的约束条件.为了解决这个问题,可以对每个样本点(x_i,y_i)引进一个松弛变量
,使函数 间隔加上松弛变量大于等于1。
分离超平面:
点到直线距离:
为2-范数:
直线为超平面,样本可表示为:
函数间隔:
几何间隔:
,当数据被正确分类时,几何间隔就是点到超平面的距离
为了求几何间隔最大,SVM基本问题可以转化为求解:(
为几何间隔,(
为函数间隔)
为了方便计算
目标函数:
推导过程如下:
# -*- coding:utf-8 -*-
# /usr/bin/python
import time
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
from tqdm import tqdm
class SVM():
def __init__(self,maxIter,kernel="linear"):
'''参数'''
self.maxIter = maxIter
self._kernel = kernel
def initArgs(self,features,labels):
'''初始化参数'''
self.m, self.n = features.shape
self.X = features
self.Y = labels
self.b = 0.0
# 将Ei保存在一个列表里
self.alpha = np.ones(self.m)
self.E = [self._E(i) for i in range(self.m)]
# 松弛变量
self.C = 1.0
def _KKT(self,i):
y_g = self._g(i) * self.Y[i]
if self.alpha[i] == 0:
return y_g >= 1
elif 0 < self.alpha[i] < self.C:
return y_g == 1
else:
return y_g <= 1
# g(x)预测值,输入xi(X[i])
def _g(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 kernel(self, x1, x2):
if self._kernel == 'linear':
return sum([x1[k] * x2[k] for k in range(self.n)])
elif self._kernel == 'poly':
return (sum([x1[k] * x2[k] for k in range(self.n)]) + 1) ** 2
return 0
# E(x)为g(x)对输入x的预测值和y的差
def _E(self, i):
return self._g(i) - self.Y[i]
def _init_alpha(self):
# 外层循环首先遍历所有满足0<a<C的样本点,检验是否满足KKT
index_list = [i for i in range(self.m) if 0 < self.alpha[i] < self.C]
# 否则遍历整个训练集
non_satisfy_list = [i for i in range(self.m) if i not in index_list]
index_list.extend(non_satisfy_list)
for i in index_list:
if self._KKT(i):
continue
E1 = self.E[i]
# 如果E2是+,选择最小的;如果E2是负的,选择最大的
if E1 >= 0:
j = min(range(self.m), key=lambda x: self.E[x])
else:
j = max(range(self.m), key=lambda x: self.E[x])
return i, j
def _compare(self, _alpha, L, H):
if _alpha > H:
return H
elif _alpha < L:
return L
else:
return _alpha
def fit(self, features, labels):
self.initArgs(features, labels)
for i in tqdm(range(self.maxIter)):# train
time.sleep(0.2)
i1, i2 = self._init_alpha()
# 边界
if self.Y[i1] == self.Y[i2]:
L = max(0, self.alpha[i1] + self.alpha[i2] - self.C)
H = min(self.C, self.alpha[i1] + self.alpha[i2])
else:
L = max(0, self.alpha[i2] - self.alpha[i1])
H = min(self.C, self.C + self.alpha[i2] - self.alpha[i1])
E1 = self.E[i1]
E2 = self.E[i2]
# eta=K11+K22-2K12
eta = self.kernel(self.X[i1], self.X[i1]) + self.kernel(self.X[i2], self.X[i2]) - 2 * self.kernel(
self.X[i1], self.X[i2])
if eta <= 0:
# print('eta <= 0')
continue
alpha2_new_unc = self.alpha[i2] + self.Y[i2] * (E2 - E1) / 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)
b1_new = -E1 - self.Y[i1] * self.kernel(self.X[i1], self.X[i1]) * (alpha1_new - self.alpha[i1]) - self.Y[
i2] * self.kernel(self.X[i2], self.X[i1]) * (alpha2_new - self.alpha[i2]) + self.b
b2_new = -E2 - self.Y[i1] * self.kernel(self.X[i1], self.X[i2]) * (alpha1_new - self.alpha[i1]) - self.Y[
i2] * self.kernel(self.X[i2], self.X[i2]) * (alpha2_new - self.alpha[i2]) + self.b
if 0 < alpha1_new < self.C:
b_new = b1_new
elif 0 < alpha2_new < self.C:
b_new = b2_new
else:
# 选择中点
b_new = (b1_new + b2_new) / 2
# 更新参数
self.alpha[i1] = alpha1_new
self.alpha[i2] = alpha2_new
self.b = b_new
self.E[i1] = self._E(i1)
self.E[i2] = self._E(i2)
return 'train done!'
def predict(self,data):
r = self.b
for i in range(self.m):
r += self.alpha[i] * self.Y[i] * self.kernel(data, self.X[i])
return 1 if r > 0 else -1
def score(self,X_test,y_test):
right_count = 0
for i in range(len(X_test)):
result = self.predict(X_test[i])
if result == y_test[i]:
right_count += 1
return right_count / len(X_test)
def _weight(self):
# 线性模型
yx = self.Y.reshape(-1, 1) * self.X
self.w = np.dot(yx.T, self.alpha)
return self.w
data = pd.read_csv('dataset.csv')
print(data.label.value_counts())
print("data\n",data)
# 数据可视化 验证线性可分性
plt.scatter(data[:50]['sepal length'], data[:50]['sepal width'], label='0')
plt.scatter(data[50:100]['sepal length'], data[50:100]['sepal width'], label='1')
plt.xlabel('sepal length')
plt.ylabel('sepal width')
plt.legend()
plt.show()
plt.savefig("show.png")
data = np.array(data.iloc[:100, [0, 1, -1]])
# 训练模型
X_train, X_test, y_train, y_test = train_test_split(data[:,:-1], data[:,-1], test_size=0.25)
svm = SVM(maxIter=150)
svm.fit(X_train,y_train)
result = svm.score(X_test, y_test)
print(result)