目录
一、介绍
支持向量机(support vector machine,SVM)是一种用于分类和回归分析的监督学习算法。它通过寻找最优分类超平面对样本进行分类,并且在线性不可分的情况下,通过使用非线性映射将低维度输入空间的样本映射到高维度空间,从而寻找最优分类超平面。主要适用于中小型数据样本、非线性、高维的分类问题,具有较为完善数学理论基础和优越的预测效果。
SVM是一种二分类模型,其基本模型定义为特征空间上的间隔最大的线性分类器,其学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。支持向量机的学习算法是求解凸二次规划的最优化算法。
二、原理
2.1 超平面
超平面是用于在高维空间中分离不同类别数据一种数学概念。在一维空间中,可以利用一个点来区分两类数据点,在二维空间中,超平面是一条直线;在三维空间中,是一个平面,其本质是一个n-1维的子间,将n维空间划分为两个半空间,其中包含这两个不同类别的数据点。
在支持向量机中,超平面被用于构建分类模型以便能够对数据进行有效的分类。。SVM的基本思想是寻找一个最佳的超平面,使在这个超平面上不同类别的数据点之的距离最大化。当数据是线性可分情况下,平面可以完全将不同类别的数据点分隔开来。
2.2 支持向量和间隔
2.2.1支持向量
在SVM中,支持向量是指距离分类超平面最近的那些训练样本点,这样本点决定了最终分类超平面的位置。支持向量可以用来终确定分类超平面,并且对于SVM模型的训练和预测起着重要的作用。
在SVM模型训练完成后,分类超平面的位置完全由支持向量确定,而其他训练数据点则对分类结果没有影响。这是SVM模型在高维空间中表现良好的原因之一,因为它只支持向量影响,而不会受到所有训数据点的影响。
2.2.2间隔
支持向量到分类超平面的距离称为间隔。间隔的大小对于SVM模型来说至关重要。较大的间隔意味着分类器对于新样本具有更好的鲁棒性,因为离超平面较远的点更有可能被正确地分类。相反,较小的间隔可能导致模型对于噪声数据点较为敏感,容易过拟合。
在实践中,间隔是SVM模型优化过程的主要目标之一。通过最大化间隔,可以得到一个能够更好地分类新样本的超平面。同时,在优化求解过程中,间隔还发挥着对支持向量的选择和模型的泛化能力的调整作用。
2.3 硬间隔和软间隔
硬间隔和软间隔是支持向量机(SVM)中的两个关键概念,用于描述分类器对训练数据的容忍程度。
2.3.1 硬间隔(Hard Margin):
硬间隔是指在SVM中,对于线性可分的情况,在不允许任何训练数据点出现在间隔边界内的情况下寻找最大间隔超平面。这意味着要求所有的训练样本都必须正确划分到超平面的两侧,不允许出现分类错误。这种情况下,分类器的鲁棒性很高,但是对于现实数据往往不太适用,因为现实世界中的数据通常不是完全线性可分的。
2.3.2 软间隔(Soft Margin):
软间隔是指在SVM中允许在寻找最大间隔超平面时,部分训练数据点出现在间隔边界内。允许一些数据点在间隔边界内,从而更好地适应现实世界中不完全线性可分的情况。软间隔的目标是寻找一个能够最大化间隔的超平面,并且最小化间隔内的数据点(也就是训练误差)。可以通过引入惩罚项(惩罚松弛变量)来平衡间隔的大小以及分类错误的惩罚。
在实际问题中,一般采用软间隔来构建SVM模型,因为现实世界的数据通常是存在一定程度的噪声或者重叠的。软间隔允许了一定的误差,使得模型更加鲁棒且适应更广泛的数据集。
总结来说,硬间隔要求数据完全线性可分,不允许分类错误,而软间隔允许有一定的训练误差,更适用于现实世界中具有一定噪声和复杂度的数据。
2.4 最大间隔
最大间隔(Maximum Margin)是支持向量机(SVM)算法的一个重要概念,它是SVM在进行分类时所追求的目标。
在SVM中,最大间隔指的是找到一个超平面,使得这个超平面能够将不同类别的样本尽可能地分开,并且离超平面最近的样本点(支持向量)到该超平面的距离尽可能远。换句话说,最大间隔是指在不同类别的样本之间找到一个最宽的“缝隙”,以确保分类的鲁棒性和泛化能力。
划分超平面可以定义为一个线性方程:
其中,w是一个法向量,决定了超平面的方向,X为训练样本,b为位移项,决定了超平面与原点之间的距离。
只要确定了法向量 和位移
,就可以唯一地确定一个划分超平面。划分超平面和它两侧的边际超平面上任意一点的距离为
则最大间隔为:
即
2.5对偶问题:等式约束
引入拉格朗日函数:
可将原本约束条件转换为
分别求偏导得
转换为KKT条件
通过SMO优化求得最优解
2.6 核函数
核函数是一种用于支持向量机(SVM)和其他机器学习算法中的一种数学技术,能处理那些在原始特征空间中线性不可分的问题,即无法通过一个线性超平面来完美分割的数据。它可以将数据从原始特征空间映到一个更高维度的特征空间,从而使得原本线性不可分的数据新的特征间中变得线性可分。这种映射使得支持向量机(SVM)在高维空间中能够更灵活地找到一个线性超平面,从而在原始特征空间中解决非线性问题。
核函数的作用在于计算两个样本点在高维空间中的内积,而无需显式计算映射后的数据。这节省了计算成本,使得SVM在高维空间中的计算变得可行。常用的核函数包括线性核、多项式核、径向基函数(RBF)核等,它们分别对应不同的特征映射方式。
常见的核函数包括:
线性核函数:
线性核函数对原始特征进行线性组合,适用于线性可分的情况。虽然它不引入额外的复杂性,但在处理非线性问题上有限制。
多项式核函数:
多项式核函数引入了多项式特征映射,其中d是多项式的次数,c是常数。它可以处理一定程度的非线性问题,通过调整次数d可以增加模型的复杂性。
径向基函数核(RBF Kernel / Gaussian Kernel):
RBF核函数基于样本点之间的距离,将数据映射到无限维的空间。它在处理非线性问题上非常强大,通过调整参数σ(标准差)可以控制映射的宽度。
三、代码实现及结果分析
3.1 导入所需库
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
3.2 创建数据集
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] = -1
return data[:,:2], data[:,-1]
3.3 创建SVM模型
class SVM:
def __init__(self, max_iter=100, kernel='poly'):
self.max_iter = max_iter
self._kernel = kernel
def init_args(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.init_args(features, labels)
for t in range(self.max_iter):
# train
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):
# linear model
yx = self.Y.reshape(-1, 1)*self.X
self.w = np.dot(yx.T, self.alpha)
return self.w
X, y = create_data()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
svm = SVM(max_iter=800)
print(svm.fit(X_train, y_train))
print(svm.score(X_train, y_train))
print(svm.score(X_test, y_test))
3.4 基于sklearn的代码实现
import numpy as np
import pandas as pd
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
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] = -1
return data[:,:2], data[:,-1]
X, y = create_data()
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25)
plt.scatter(X[:50,0],X[:50,1], label='0')
plt.scatter(X[50:,0],X[50:,1], label='1')
plt.show()
model = SVC()
model.fit(X_train, y_train)
SVC(C=1.0, break_ties=False, cache_size=200, class_weight=None, coef0=0.0,
decision_function_shape='ovr', degree=3, gamma='scale', kernel='rbf',
max_iter=-1, probability=False, random_state=None, shrinking=True,
tol=0.001, verbose=False)
print('train accuracy: ' + str(model.score(X_train, y_train)))
print('test accuracy: ' + str(model.score(X_test, y_test)))
3.5 结果展示
sepal_len sepal_wid petal_len petal_wid label
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
3 4.6 3.1 1.5 0.2 0
4 5.0 3.6 1.4 0.2 0
.. ... ... ... ... ...
145 6.7 3.0 5.2 2.3 2
146 6.3 2.5 5.0 1.9 2
147 6.5 3.0 5.2 2.0 2
148 6.2 3.4 5.4 2.3 2
149 5.9 3.0 5.1 1.8 2
(1)数据分布展示:
(2)训练集和测试集上的准确性
train accuracy: 1.0
test accuracy: 0.96
3.6 结果分析
原生代码首先通过调用create_data()
函数创建数据集X, y
,然后使用train_test_split
函数将数据集划分为训练集和测试集X_train, X_test, y_train, y_test
。
接着创建了一个SVM对象svm
,并通过svm.fit(X_train, y_train)
对训练集进行训练。在训练完成后,通过svm.score(X_train, y_train)
计算了模型在训练集上的准确率,通过svm.score(X_test, y_test)
计算了模型在测试集上的准确率,并最后输出了这两个准确率。
实现代码首先通过调用create_data()函数生成数据集,然后使用train_test_split将数据集划为训练集测试集X_train, X_test, y_train, y_test。
随后,使用plt.scatter函数绘制了尾花数据的散点图,以便可视化展示数据集的标签。接着创建了一个SVC模型model,然后使用model.fit(X_train, y)对训练进行训练。
在训练完成后,model.score(X_train, y_train)计算了模型在训集上的准确率,通过.score(X_test, y_test)`计算了模型在测试集上的准确率最后输出了这两个准确率。
四、结论
优点:
- 高准确度:SVM在处理非线性数据和小样本数据时具有较高的分类准确度。
- 泛化能力强:SVM对于新样本的分类能力较强,表现出较好的泛化能力。
- 可应用于高维度数据:SVM在高维度数据集上表现出色,适用于复杂的数据分类问题。
- 能够处理非线性数据:通过核函数的引入,SVM能够处理非线性分类问题。
缺点:
- 对大规模数据集性能较慢:SVM在处理大规模数据时可能需要较长的训练时间。
- 需要选择合适的核函数和参数:SVM在使用核函数时需要选择合适的核函数类型和参数,不同数据集需要不同的调参策略。
- 对缺失数据敏感:SVM对于数据的缺失比较敏感,需要在数据预处理时进行处理。
- 难以理解与解释:SVM的决策边界不容易通过简单的方式解释,使其在实际应用中的可解释性较差。
综上所述,SVM具有较强的分类能力,尤其适用于非线性、小样本和高维度数据集,但在处理大规模数据和参数选择上需要注意其性能和可解释性。