一、引言
支持向量机(support vector machine,SVM)是有监督学习中最有影响力的机器学习算法之一,该算法的诞生可追溯至上世纪 60 年代, 前苏联学者 Vapnik 在解决模式识别问题时提出这种算法模型,它是一种常见的机器学习方法,常用于分类(线性和非线性分类问题),回归问题。
二、支持向量机
2.1 SVM概念
支持向量机(SVM)是一类按监督学习方式对数据进行二元分类的广义线性分类器,其决策边界是对学习样本求解的最大边距超平面,可以将问题化为一个求解凸二次规划的问题。与逻辑回归和神经网络相比,支持向量机,在学习复杂的非线性方程时提供了一种更为清晰,更加强大的方式。
具体来说就是在线性可分时,在原空间寻找两类样本的最优分类超平面。在线性不可分时,加入松弛变量并通过使用非线性映射将低维度输入空间的样本映射到高维度空间使其变为线性可分,这样就可以在该特征空间中寻找最优分类超平面。
2.2、线性可分与线性不可分
线性可分如图左,不可分右
在线性可分问题中,可以通过求解一个凸优化问题来寻找最优的超平面。这个问题的目标是最大化间隔,并且要满足所有正样本和负样本的约束条件。
然而,实际应用中,许多问题并不是线性可分的,即使使用直线或平面也无法完全将不同类别的样本分开。这时候就需要引入一些方法来处理线性不可分问题。
一种常用的方法是引入松弛变量(slack variable),它允许样本出现错误分类。通过引入松弛变量,可以允许一些样本位于超平面的错误一侧,从而使得分类器具有容错能力。
线性不可分问题可以通过引入软间隔 SVM(Soft Margin SVM)来解决。软间隔 SVM 允许一些样本位于超平面的边界区域,同时尽可能地减小松弛变量的数量。这样可以在保持分类器简单性和泛化能力的同时,允许一些错误的分类。
另外,当线性不可分问题无法通过低维空间的超平面进行划分时,可以使用核函数。核函数可以将样本映射到高维特征空间,使得非线性问题在高维空间中变得线性可分。常用的核函数有线性核、多项式核、高斯径向基函数(RBF)核等。
2.3 寻找最大间隔
(1)分隔超平面
二维空间一条直线的方程为,y=ax+b,推广到n维空间,就变成了超平面方程,即 w是权重,b是截距,训练数据就是训练得到权重和截距。
(2)如何找到最好的参数
支持向量机的核心思想: 最大间隔化, 最不受到噪声的干扰。如上图所示,分类器A比分类器B的间隔(蓝色阴影)大,因此A的分类效果更好。
SVM划分的超平面:f(x) = 0,w为法向量,决定超平面方向,假设超平面将样本正确划分
f(x) ≥ 1,y = +1
f(x) ≤ −1,y = −1
间隔:r=2/|w|
约束条件:
2.4 核函数
支持向量机算法分类和回归方法的中都支持线性性和非线性类型的数据类型。非线性类型通常是二维平面不可分,为了使数据可分,需要通过一个函数将原始数据映射到高维空间,从而使得数据在高维空间很容易可分,需要通过一个函数将原始数据映射到高维空间,从而使得数据在高维空间很容易区分,这样就达到数据分类或回归的目的,而实现这一目标的函数称为核函数。
工作原理:当低维空间内线性不可分时,可以通过高位空间实现线性可分。但如果在高维空间内直接进行分类或回归时,则存在确定非线性映射函数的形式和参数问题,而最大的障碍就是高维空间的运算困难且结果不理想。映射可以看作是一种拉伸,把低维数据拉伸到了高维。虽然现在我们到了高维空间号称线性可分,但是有几个困难:
不知道什么样的映射函数是完美的。
难以在各种映射函数中找到一个合适的。
高维空间计算量比较大。这样就会产生维灾难,计算内积是不现实的。
幸运的是,在计算中发现,我们需要的只是两个向量在新的映射空间中的内积结果,而映射函数到底是怎么样的其实并不需要知道。于是这样就引入了核函数的概念。核函数事先在低维上计算,而将实质上的分类效果表现在了高维上,也就是包含映射,内积,相似度的逻辑。消除掉把低维向量往高维映射的过程。避免了直接在高维空间内的复杂计算。
核函数方法处理非线性问题的基本思想:按一定的规则进行映射,使得原来的数据在新的空间中变成线性可分的,从而就能使用之前推导的线性分类算法进行处理。计算两个向量在隐式映射过后的空间中的内积的函数叫做核函数。
值得一提的是,我们希望样本在特征空间内线性可分,因此特征空间的好坏对支持向量机的性能至关重要,需要注意的是,在不知道特征映射的形式时,我们并不知道什么样的核函数是合适的,而核函数也仅是隐式的定义了这个特征空间。于是,核函数的选择就成为了SVM最大的变数,如果核函数选择的不合适,则意味着将样本映射到了一个不合适的特征空间,很可能导致性能不佳。
二.代码实现
1.加载数据集,并进行特征划分
iris = datasets.load_iris()
X = iris.data[:, [2, 3]] # 按花瓣划分
# X = iris.data[:,[0,1]] #按花萼划分
y = iris.target
print('Class labels:', np.unique(y)) # 分类标签列表 [0 1 2]
结果:
2.划分数据集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)
print('Labels counts in y:', np.bincount(y)) # 原数据集中各分类标签出现次数 [50 50 50]
print('Labels counts in y_train:', np.bincount(y_train)) # 训练集中各分类标签出现次数 [35 35 35]
print('Labels counts in y_test:', np.bincount(y_test)) # 测试集中各分类标签出现次数 [15 15 15] 35:15=7:3
结果:
3.数据预处理:数据标准化
sc = StandardScaler() # 定义一个标准缩放器
sc.fit(X_train) # 计算均值、标准差
X_train_std = sc.transform(X_train) # 使用计算出的均值和标准差进行标准化
X_test_std = sc.transform(X_test) # 使用计算出的均值和标准差进行标准化
4.绘制决策边界
X_combined_std = np.vstack((X_train_std, X_test_std)) # 竖直堆叠
y_combined = np.hstack((y_train, y_test)) # 水平拼接
def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
# 设置标记生成器和颜色图
markers = ('s', '^', 'o', 'x', 'v') # 标记生成器
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan') # 定义颜色图
cmap = ListedColormap(colors[:len(np.unique(y))])
# 绘制决策曲面
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 # x轴范围 x1_min ~ x1_max
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 # y轴范围 x2_min ~ x2_max
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), # 生成网络点坐标矩阵
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape) # 对不同分类进行标记
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap) # 生成边界图
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
# 绘制 所有样本 散点图
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], # 散点的x坐标(分类标签==cl)
y=X[y == cl, 1], # 散点的y坐标(分类标签==cl)
alpha=0.8, # 散点的透明度
c=colors[idx], # 散点的颜色
marker=markers[idx], # 散点的样式
label=cl, # 散点的图例名称
edgecolor='black') # 散点的边缘颜色
# 绘制 测试样本 散点图
if test_idx: # 默认test_idx=None 如果未设置该参数,则不绘制测试样本
X_test, y_test = X[test_idx, :], y[test_idx]
plt.scatter(X_test[:, 0], # 散点的横坐标
X_test[:, 1], # 散点的纵坐标
c='y', # 散点的颜色【黄色】
edgecolor='black', # 散点的边缘颜色【黑色】
alpha=1.0, # 散点的透明度【1】
linewidth=1, # 散点的边缘线宽【1】
marker='*', # 散点的样式【圆圈】
s=150, # 散点的面积【150】
label='test set') # 散点的图例名称【test set】
5.训练SVM模型
svm = SVC(kernel='linear', C=1.0, random_state=1) # 定义线性支持向量分类器 (linear为线性核函数)
svm.fit(X_train_std, y_train) # 根据给定的训练数据拟合训练SVM模型
plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150)) # 绘制决策边界
plt.xlabel('petal length [standardized]') # x轴标签
plt.ylabel('petal width [standardized]') # y轴标签
plt.legend(loc='upper left') # 图例位于左上方
plt.tight_layout() # 使子图填充整个图像区域
# plt.savefig('images/03_11.png', dpi=300)
plt.show()
6.预测训练集
y_pred = svm.predict(X_test_std) # 用训练好的分类器svm预测数据X_test_std的标签
print('Misclassified samples: %d' % (y_test != y_pred).sum()) # 输出错误分类的样本数
print('Accuracy: %.2f' % svm.score(X_test_std, y_test)) # 输出分类准确率
全部代码
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from matplotlib.colors import ListedColormap
from sklearn.svm import SVC
# 加载样本数据及其分类标签
iris = datasets.load_iris()
X = iris.data[:, [2, 3]] # 按花瓣划分
# X = iris.data[:,[0,1]] #按花萼划分
y = iris.target
print('Class labels:', np.unique(y)) # 分类标签列表 [0 1 2]
# np.unique(arr): arr为一维数组/列表,结果返回一个列表,去除arr中重复的元素,并从小到大排序
# 划分70%训练集和30%测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1, stratify=y)
"""
train_test_split()函数: 用于将数据集划分为训练集train和测试集test
X: 待划分的样本特征集
y: 数据集X对应的标签
test_size: 0~1表示测试集样本占比、整数表示测试集样本数量
random_state: 随机数种子。在需要重复实验的时候保证得到一组一样的随机数据。每次填1(其他参数一样),每次得到的随机数组一样;每次填0/不填,每次都不一样
stratify=y: 划分数据集时保证每个类别在训练集和测试集中的比例与原数据集中的比例相同
"""
print('Labels counts in y:', np.bincount(y)) # 原数据集中各分类标签出现次数 [50 50 50]
print('Labels counts in y_train:', np.bincount(y_train)) # 训练集中各分类标签出现次数 [35 35 35]
print('Labels counts in y_test:', np.bincount(y_test)) # 测试集中各分类标签出现次数 [15 15 15] 35:15=7:3
# np.bincount(arr): 返回一个数组array,长度=max(arr[i])+1,array[i]=count(arr[i])。(长度=arr中最大元素值+1,每个元素值=它当前索引值在arr中出现的次数)
# 标准化训练集和测试集
sc = StandardScaler() # 定义一个标准缩放器
sc.fit(X_train) # 计算均值、标准差
X_train_std = sc.transform(X_train) # 使用计算出的均值和标准差进行标准化
X_test_std = sc.transform(X_test) # 使用计算出的均值和标准差进行标准化
"""
! StandardScaler()
均值:对每个特征求均值,即对每列求均值
去均值:每个特征的值减去对应特征的均值
标准差:去均值后平方和,然后除以总值的数量,最后开根号
标准分数:去均值、除以标准差
1、中心化:去均值,将整体数据平移,中心为(0,0)
2、缩放:标准分数,进行缩放
标准化:去均值、除以标准差。将数据的分布转为正态分布。每个特征的值 均值=0、方差=1
目的:
将特征表现为标准正态分布数据。
如果某个特征的方差比其他特征大几个数量级,那么它就会在学习算法中占据主导位置,导致学习器不能从其他特征中学习,从而降低精度。
加快梯度下降求解的速度。
"""
# 绘制决策边界图 函数
def plot_decision_regions(X, y, classifier, test_idx=None, resolution=0.02):
# 设置标记生成器和颜色图
markers = ('s', '^', 'o', 'x', 'v') # 标记生成器
colors = ('red', 'blue', 'lightgreen', 'gray', 'cyan') # 定义颜色图
cmap = ListedColormap(colors[:len(np.unique(y))])
# 绘制决策曲面
x1_min, x1_max = X[:, 0].min() - 1, X[:, 0].max() + 1 # x轴范围 x1_min ~ x1_max
x2_min, x2_max = X[:, 1].min() - 1, X[:, 1].max() + 1 # y轴范围 x2_min ~ x2_max
xx1, xx2 = np.meshgrid(np.arange(x1_min, x1_max, resolution), # 生成网络点坐标矩阵
np.arange(x2_min, x2_max, resolution))
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
Z = Z.reshape(xx1.shape) # 对不同分类进行标记
plt.contourf(xx1, xx2, Z, alpha=0.3, cmap=cmap) # 生成边界图
plt.xlim(xx1.min(), xx1.max())
plt.ylim(xx2.min(), xx2.max())
# 绘制 所有样本 散点图
for idx, cl in enumerate(np.unique(y)):
plt.scatter(x=X[y == cl, 0], # 散点的x坐标(分类标签==cl)
y=X[y == cl, 1], # 散点的y坐标(分类标签==cl)
alpha=0.8, # 散点的透明度
c=colors[idx], # 散点的颜色
marker=markers[idx], # 散点的样式
label=cl, # 散点的图例名称
edgecolor='black') # 散点的边缘颜色
# 绘制 测试样本 散点图
if test_idx: # 默认test_idx=None 如果未设置该参数,则不绘制测试样本
X_test, y_test = X[test_idx, :], y[test_idx]
plt.scatter(X_test[:, 0], # 散点的横坐标
X_test[:, 1], # 散点的纵坐标
c='y', # 散点的颜色【黄色】
edgecolor='black', # 散点的边缘颜色【黑色】
alpha=1.0, # 散点的透明度【1】
linewidth=1, # 散点的边缘线宽【1】
marker='*', # 散点的样式【圆圈】
s=150, # 散点的面积【150】
label='test set') # 散点的图例名称【test set】
"""
print(np.arange(x1_min, x1_max, resolution).shape) # 265
print(np.arange(x2_min, x2_max, resolution).shape) # 258
print(xx1.shape) # 258*265 # 258个相同的x值(x1范围),因为有258个不同y值,说明一个x值对应258个不同y【x坐标竖直复制258份】
print(xx2.shape) # 258*265 # 265个相同的y值(x2范围),因为有265个不同x值,说明一个y值对应265个不同x【y坐标水平复制265份】
print(xx1.ravel().shape) # 68370 =258*265
print(xx2.ravel().shape) # 68370 =258*265
print(np.array([xx1.ravel(), xx2.ravel()]).shape) # 2*68370
print(np.array([xx1.ravel(), xx2.ravel()]).T.shape) # 68370*2
Z = classifier.predict(np.array([xx1.ravel(), xx2.ravel()]).T)
print(Z.shape) # 1*68370
Z = Z.reshape(xx1.shape)
print(Z.shape) # 258*265 # 预测出网格点的每个分类,reshape成258行预测结果,每行265个具体预测值
x=X[y == 0, 0] # 分类y=0的x坐标(第二个位置参数=1时为y坐标)
print(X.shape) # 150*2
print(x.shape) # 1*50
print(X[range(105, 150), :].shape) # 45*2
"""
# Training a svm model using the standardized training data
X_combined_std = np.vstack((X_train_std, X_test_std)) # 竖直堆叠
y_combined = np.hstack((y_train, y_test)) # 水平拼接
"""
np.vstack(tup): tup为一个元组,返回一个竖直堆叠后的数组
np.hstack(tup): tup为一个元组,返回一个水平拼接后的数组
"""
# 训练线性支持向量机
svm = SVC(kernel='linear', C=1.0, random_state=1) # 定义线性支持向量分类器 (linear为线性核函数)
svm.fit(X_train_std, y_train) # 根据给定的训练数据拟合训练SVM模型
plot_decision_regions(X_combined_std, y_combined, classifier=svm, test_idx=range(105, 150)) # 绘制决策边界
plt.xlabel('petal length [standardized]') # x轴标签
plt.ylabel('petal width [standardized]') # y轴标签
plt.legend(loc='upper left') # 图例位于左上方
plt.tight_layout() # 使子图填充整个图像区域
# plt.savefig('images/03_11.png', dpi=300)
plt.show()
# 使用测试集进行数据预测
y_pred = svm.predict(X_test_std) # 用训练好的分类器svm预测数据X_test_std的标签
print('Misclassified samples: %d' % (y_test != y_pred).sum()) # 输出错误分类的样本数
print('Accuracy: %.2f' % svm.score(X_test_std, y_test)) # 输出分类准确率
"""
! (arr1 != arr2).sum():
arr1、arr2为数组类型
(arr1 != arr2): 输出一个列表,元素为bool类型,两数组对应位置不相等为True
.sum(): 统计列表中True的个数
! svm.score(X_test_std, y_test)
返回给定测试集与对应标签的平均准确率
"""
三、总结
SVM优缺点
优点:
高效的处理高维特征空间:SVM通过将数据映射到高维空间中,可以处理高维特征,并在低维空间中进行计算,从而有效地处理高维数据。
适用于小样本数据集:SVM是一种基于边界的算法,它依赖于少数支持向量,因此对于小样本数据集具有较好的泛化能力。
可以处理非线性问题:SVM使用核函数将输入数据映射到高维空间,从而可以解决非线性问题。常用的核函数包括线性核、多项式核和径向基函数(RBF)核。
避免局部最优解:SVM的优化目标是最大化间隔,而不是仅仅最小化误分类点。这使得SVM在解决复杂问题时能够避免陷入局部最优解。
缺点:
对大规模数据集的计算开销较大:SVM的计算复杂度随着样本数量的增加而增加,特别是在大规模数据集上的训练时间较长。
对于非线性问题选择合适的核函数和参数较为困难:在处理非线性问题时,选择适当的核函数和相应的参数需要一定的经验和领域知识。
对缺失数据敏感:SVM在处理含有缺失数据的情况下表现不佳,因为它依赖于支持向量的定义。
难以解释模型结果:SVM生成的模型通常是黑盒模型,难以直观地解释模型的决策过程和结果。