0%

支持向量机实验

实验目的

  1. 熟悉和掌握支持向量机
  2. 熟悉和掌握核函数处理非线性性问题
  3. 了解和掌握第三方机器学习库Scikit-learn中的模型调用

实验要求

  1. 采用Python、Matlab等高级语言进行编程,推荐优先选用Python语言
  2. 本次实验可以直接调用Scikit-learn、PyTorch等成熟框架的第三方实现
  3. 代码可读性强:变量、函数、类等命名可读性强,包含必要的注释

实验原理

支持向量机(Support Vector Machine)是Cortes和Vapnik于1995年首先提出的,它在解决小样本、非线性及高维模式识别中表现出许多特有的优势,并能够推广应用到函数拟合等其他机器学习问题中。

小样本,并不是说样本的绝对数量少(实际上,对任何算法来说,更多的样本几乎总是能带来更好的效果),而是说与问题的复杂度比起来,SVM算法要求的样本数是相对比较少的。

非线性,是指SVM擅长应付样本数据线性不可分的情况,主要通过松弛变量(也有人叫惩罚变量)和核函数技术来实现,这一部分是SVM的精髓,以后会详细讨论。

高维模式识别是指样本维数很高,例如文本的向量表示,如果没有经过另一系列文章(《文本分类入门》)中提到过的降维处理,出现几万维的情况很正常,其他算法基本就没有能力应付了,SVM却可以,主要是因为SVM 产生的分类器很简洁,用到的样本信息很少(仅仅用到那些称之为“支持向量”的样本),使得即使样本维数很高,也不会给存储和计算带来大麻烦

目前在引入SVM概述时主要有两个方式:

一是:

支持向量机方法是建立在统计学习理论的VC 维理论和结构风险最小原理基础上的,根据有限的样本信息在模型的复杂性(即对特定训练样本的学习精度,Accuracy)和学习能力(即无错误地识别任意样本的能力)之间寻求最佳折衷,以期获得最好的推广能力(或称泛化能力)。

所谓VC维是对函数类的一种度量,可以简单的理解为问题的复杂程度,VC维越高,一个问题就越复杂。正是因为SVM关注的是VC维,后面我们可以看到,SVM解决问题的时候,和样本的维数是无关的(甚至样本是上万维的都可以,这使得SVM很适合用来解决文本分类的问题,当然,有这样的能力也因为引入了核函数)。

结构风险是什么呢?机器学习本质上就是一种对问题真实模型的逼近,但毫无疑问,真实模型一定是不知道的。我们选择了一个假设之后,真实误差无从得知,但我们可以用某些可以掌握的量来逼近它。最直观的想法就是使用分类器在样本数据上的分类的结果与真实结果之间的差值来表示。这个差值叫做经验风险Remp(w)。此时的情况便是选择了一个足够复杂的分类函数,能够精确的记住每一个样本,但对样本之外的数据一律分类错误。统计学习因此而引入了泛化误差界的概念,就是指真实风险应该由两部分内容刻画,一是经验风险,代表了分类器在给定样本上的误差;二是置信风险,代表了我们在多大程度上可以信任分类器在未知数据分类的结果。很显然,第二部分是没有办法精确计算的,因此只能给出一个估计的区间,也使得整个误差只能计算上界,而无法计算准确的值(所以叫做泛化误差界,而不叫泛化误差)。

置信风险与两个量有关,一是样本数量,显然给定的样本数量越大,我们的学习结果越有可能正确,此时置信风险越小;二是分类函数的VC维,显然VC维越大,推广能力越差,置信风险会变大。

泛化误差界的公式为: $\mathrm{R}(\mathrm{w}) \leqslant \operatorname{Remp}(\mathrm{w})+\Phi(\mathrm{n} / \mathrm{h})$ 。公式中 $\mathrm{R}(\mathrm{w})$ 就是真实风险, $\operatorname{Remp}(\mathrm{w})$ 就是经验风险, $\Phi(\mathrm{n} / \mathrm{h})$ 就是置信风险。统计学习的目标从经验风险最小化变为了寻求经验风险与置信风险的和最小, 即结构风险最小。SVM 正是这样一种努力最小化结构风险的算法。

从logistic回归出发,引出了SVM,既揭示了模型间的联系,也让人觉得过渡更自然。

Logistic回归目的是从特征学习出一个0/1分类模型,而这个模型是将特性的线性组合作为自变量,由于自变量的取值范围是负无穷到正无穷。因此,使用logistic函数(或称作sigmoid函数)将自变量映射到(0,1)上,映射后的值被认为是属于y=1的概率。

实验内容

iris数据集支持向量机分类实验

  1. 从iris数据集中取出[sepal length,sepal width]两个属性作为样本特征,取前两类样本训练支持向量机进行二分类实验。注意每一类取前30个样本作为训练集,剩余的20个样本作为测试集。
  2. 借助matplotlib 画出原始训练数据分布的散点图(x=“sepal length”,y=“sepal width”,点的颜色代表不同类别)
  3. 调用sklearn的SVM模型包,实现分类算法

结果展示:

  • 用训练的模型对测试数据进行分类,得到测试错误率
  • 使用不同的误差惩罚系数取值(分别为0.01、0.1、1.0、10和100)和核函数(分别为“linear”和“poly”)来组合,运行模型,并比较模型在最后的测试正确率以及可视化测试数据的SVM决策间隔。(提示:可调用sklearn库中的DecisionBoundaryDisplay)。

基于核函数的SVM非线性分类实验

  1. 利用sklearn生成非线性数据
    1
    2
    from sklearn.datasets import make_moons
    x, y = make_moons(n_samples=100, noise=0.2, random_state=0)
  2. 借助matplotlib 画出生成非线性数据的散点图
  3. 分别选取核函数(“linear”,“poly”,”rbf”),根据API手册了解不同核函数对应的超参数,通过调节超参数展示绘制的决策平面。参考sklearn API

结果展示:展示使用三种不同核函数,在不同超参数下的决策平面

手写SVM模型

尝试手写SVM模型,实现鸢尾花数据集分类

实验代码和结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC, LinearSVC
from sklearn.datasets import make_blobs
import pandas as pd
from sklearn.datasets import load_iris
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']
first_40_rows = df.head(30)
rows_50_to_90 = df[50:80]
dt = pd.concat([first_40_rows, rows_50_to_90])
data = np.array(dt.iloc[:80, [0, 1, -1]])
test1=df[30:50]
test2=df[80:100]
test=pd.concat([test1,test1])
test=np.array(test.iloc[:100, [0, 1, -1]])
TX, Ty = test[:,:-1], test[:,-1]
Ty = np.array([1 if i == 1 else -1 for i in Ty])
X, y = data[:,:-1], data[:,-1]
y = np.array([1 if i == 1 else -1 for i in y])
ker=['linear','poly']
C=[0.01,0.1,1,10,100]
for i in ker:
for j in C:
modelSVM = SVC(kernel=i, C=j) # SVC 建模:使用 SVC类,线性核函数
# modelSVM = LinearSVC(C=100) # SVC 建模:使用 LinearSVC类,运行结果同上
modelSVM.fit(X, y) # 用样本集 X,y 训练 SVM 模型
print('分类准确度:{:.4f}'.format(modelSVM.score(TX, Ty))) # 对训练集的分类准确度

# 绘制分割超平面和样本集分类结果
plt.scatter(X[:,0], X[:,1], c=y, s=30, cmap=plt.cm.Paired) # 散点图,根据 y值设置不同颜色
ax = plt.gca() # 移动坐标轴
xlim = ax.get_xlim() # 获得Axes的 x坐标范围
ylim = ax.get_ylim() # 获得Axes的 y坐标范围
xx = np.linspace(xlim[0], xlim[1], 30) # 创建等差数列,从 start 到 stop,共 num 个
yy = np.linspace(ylim[0], ylim[1], 30)
YY, XX = np.meshgrid(yy, xx)
xy = np.vstack([XX.ravel(), YY.ravel()]).T # 将网格矩阵展平后重构为数组
Z = modelSVM.decision_function(xy).reshape(XX.shape)
ax.contour(XX, YY, Z, colors='k', levels=[-1, 0, 1], alpha=0.5,
linestyles=['--', '-', '--']) # 绘制决策边界和分隔
ax.scatter(modelSVM.support_vectors_[:, 0], modelSVM.support_vectors_[:, 1], s=100,
linewidth=1, facecolors='none', edgecolors='k') # 绘制 支持向量
plt.title(f"SVM Decision Boundary with kernel={i} and C={j}")
plt.show()

alt text
alt text
alt text
alt text
alt text
alt text
alt text
alt text
alt text
alt text
alt text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import make_moons
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score

# 生成非线性数据
x, y = make_moons(n_samples=100, noise=0.2, random_state=0)

# 画出散点图
plt.scatter(x[:, 0], x[:, 1], c=y, cmap=plt.cm.Paired)

plt.show()

# 将数据集划分为训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(x, y, test_size=0.2, random_state=42)

# 数据标准化
scaler = StandardScaler()
x_train = scaler.fit_transform(x_train)
x_test = scaler.transform(x_test)

# 不同核函数的超参数设置
kernel_params = [{'kernel': 'poly', 'degree': 3, 'C': 1.0},
{'kernel': 'rbf', 'gamma': 0.5, 'C': 1.0},
{'kernel': 'linear', 'C': 1.0},
]

# 绘制决策平面
plt.figure(figsize=(15, 5))
for i, params in enumerate(kernel_params, 1):
# 创建SVM模型
svm_model = SVC(**params)
svm_model.fit(x_train, y_train)

# 在训练集上绘制决策平面

plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train, cmap=plt.cm.Paired)
plt.title(f"SVM Decision Boundary ({params['kernel']} kernel)")

# 绘制决策平面
h = 0.02
x_min, x_max = x_train[:, 0].min() - 1, x_train[:, 0].max() + 1
y_min, y_max = x_train[:, 1].min() - 1, x_train[:, 1].max() + 1
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))

Z = svm_model.predict(np.c_[xx.ravel(), yy.ravel()])
Z = Z.reshape(xx.shape)

plt.contourf(xx, yy, Z, cmap=plt.cm.Paired, alpha=0.6)
plt.show()

alt text
alt text
alt text
alt text

小结或讨论

SVM的优缺点:

优点:

可用于线性/非线性分类,也可以用于回归,泛化错误率低,也就是说具有良好的学习能力,且学到的结果具有很好的推广性。

可以解决小样本情况下的机器学习问题,可以解决高维问题,可以避免神经网络结构选择和局部极小点问题。

SVM是最好的现成的分类器,现成是指不加修改可直接使用。并且能够得到较低的错误率,SVM可以对训练集之外的数据点做很好的分类决策。

缺点:

对参数调节和和函数的选择敏感。