【ML】支持向量机SVM及Python实现(详细)

目录

一、SVM原理

二、Python实现SVM

2.1 加载数据样本

2.2 划分训练集和测试集

2.3 标准化

2.4 绘制决策边界图函数

2.4.1 设置标记生成器和颜色图

2.4.2 绘制决策曲面

2.4.3 绘制所有样本散点图

2.4.4 绘制测试集样本散点图

2.5 训练线性支持向量机

2.6 测试集进行测试

三、实验结果分析

3.1 终端输出

3.2 训练好的支持向量机SVM的决策面

3.3 SVM对测试集的分类分布结果

四、整体总结

五、总代码


写在前面

本篇文章先简单论述了支持向量机的工作原理,再用Python代码实现了支持向量机。文章中对于使用Python实现SVM进行详细的代码分析,可自行实现。代码中所用到的数据集是sklearn中的鸢尾花数据集,利用花瓣的宽度、长度两个特征属性对鸢尾花进行的划分。此外,若有不足,欢迎在评论区讨论。

一、SVM原理

SVM学习的基本想法是求解能够正确划分训练数据集并且是几何间隔最大的分离超平面。如下图所示,ω·x+b=0 即为分离超平面,对于线性可分的数据集来说,这样的超平面有无穷多个,但是几何间隔最大的分离超平面却是唯一的。

在求解过程中还会涉及到几个名词:惩罚因子、核函数。

(1)惩罚因子:

惩罚因子用C表示,表示对错分样本的惩罚程度。C越大对错分样本的惩罚就越大,趋向于对训练集全分对的情况,这样训练集测试时准确率很高,但是泛化能力弱,容易过拟合;C值越小,对误分类的惩罚减小,允许容错,将他们视为噪声点,泛化能力较强。

(2)核函数:

当数据集在原始特征中不是线性可分的时候,支持向量机采用了引入映射函数的策略:通过映射函数将原始特征空间映射为更高维的空间,在原始空间中不可分的数据在高维空间中可能变成可分,此时再在高维空间中运用SVM。 

下面通过代码更加细致的了解的SVM建立和使用的过程。 

二、Python实现SVM

导入第三方库:

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

2.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]
# np.unique(arr): arr为一维数组/列表,结果返回一个列表,去除arr中重复的元素,并从小到大排序

解释:

        加载鸢尾花数据集load_iris(),数据集中包含150行(150个样本),前四列用于识别鸢尾花的属性,第五列推测出的鸢尾花类别Setosa,Versicolour,Virginica三种,三种类别分别用0、1、2表示,每个样本构成形如[0花萼长度 1宽度 2花瓣长度 3宽度 4鸢尾花类别]。

        .data为加载鸢尾花的四个属性,.data[:,[2,3]]表示加载所样本的索引第2、3列,即加载所有样本花瓣的两个属性。

        .target为加载鸢尾花的分类标签,前50个样本为0类、中间50个样本为1类、后50个样本为2类。

        np.unique(y)可以去除y中重复的元素,并按元素从小到大返回一个列表,该列表表示所有分类标签。

        所以:

        X:本次实验所用到的样本的特征集合;

        y:X数据集中的样本所对应的分类标签;

2.2 划分训练集和测试集


#? 划分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中出现的次数)

解释:

        train_test_split()函数用于划分训练集和测试集。

        X表示待划分的样本特征集;y表示划分出的样本结果;test_size=0.3表示划分30%的测试集和70%的训练集;random_state=1是随机数种子为1,可以保证重复实验的时候每次得到的随机数组是一样的;stratify=y可以使训练集train和测试集test中的数据分类比例与原数据集比例y保持一致。

        下面分别表示打印输出原数据集中各分类标签出现次数:[50 50 50];训练集中各分类标签出现次数:[35 35 35];测试集中各分类标签出现次数:[15 15 15]。

        所以:

        X_train:数据的训练集;

        X_test:数据的测试集;

        y_train:X_train对应的分类标签;

        y_test:X_test对应的分类标签;

2.3 标准化

# 标准化训练集和测试集
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
    目的:
        将特征表现为标准正态分布数据。
        如果某个特征的方差比其他特征大几个数量级,那么它就会在学习算法中占据主导位置,导致学习器不能从其他特征中学习,从而降低精度。
		加快梯度下降求解的速度。
"""

解释:

        首先定义一个标准缩放器sc,通过sc.fit(X_train)计算出训练集的均值和标准差,下面的sc.transform()是使用.fit()计算出的均值和标准差对X_train、X_test两个数据集进行标准化,标准化的操作为对每个元素去均值再除以标准差,将数据的分布转为正态分布,每个特征的值均值=0、方差=1,标准化的目的是加快梯度下降求解的速度,因为如果某个特征的方差比其他特征大几个数量级,那么它就会在学习算法中占据主导位置,导致学习器不能从其他特征中学习,从而降低精度。

        所以:

        X_train_std:训练集标准化后的数据;

        X_test_std:测试集标准化后的数据;

2.4 绘制决策边界图函数

2.4.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))])

解释:

        markers存放的是散点图所需要的标记;

        colors存放的是做决策曲面和散点图所用到的颜色;

        cmap表示自定义一个颜色图。

2.4.2 绘制决策曲面

    #? 绘制决策曲面
    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())

解释:

        上述操作主要完成绘制决策曲面的操作。下面看一下代码实现细节。

        x1_min、x1_max:表示所绘制后的坐标系横轴的范围;

        x2_min、x2_max:表示所绘制后的坐标系纵轴的范围;

        xx1、xx2:表示经过.meshgrid()后所生成的坐标系的网格点;

        Z:表示用已经训练好的svm(下述训练)对网格点进行分类划分,标记网格上不同区域的类别;

        plt.contourf():表示在网格中生成不同分类的边界图,并使用前面我们已经定义好的颜色及smap进行不同分类区域的颜色填充,alpha表示所填充颜色的透明度。

2.4.3 绘制所有样本散点图

    #? 绘制 所有样本 散点图
    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')  # 散点的边缘颜色

解释:

        上述代码所完成的操作为对所有样本按不同的分类在同一坐标系下绘制不同样式的散点图。下面看代码实现细节。

        X[y==cl,0]:表示取X中类别为cl类型的第一列(索引第0列),作为坐标的横坐标;同理纵坐标为同类别的X中的第二列(索引第1列);alpha=0.8表示控制散点的透明度;c=colors[idx]表示控制散点的颜色,颜色使用自定义好的颜色集colors中相对应的颜色;marker=markers[idx]表示控制散点的样式,使用自定义好的标记器markers中相对应的标记;label=cl表示设置散点对应图例的名称用其索引;edgecolor=’black’表示设置散点边缘颜色为黑色。

2.4.4 绘制测试集样本散点图

    #? 绘制 测试样本 散点图
    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】

解释:

        上述代码完成的操作是对测试集中的样本按不同的分类在同一坐标系下绘制样式为“五角星”的散点图。绘制散点图的操作同理于上面“绘制所有样本散点图”的过程; 

2.5 训练线性支持向量机

#? 训练线性支持向量机
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()

解释:

        上述代码训练一个支持向量机,并结果绘图。

        首先将样本标准化后的训练集和测试集进行一个竖直方向的拼接,所以X_combined_std为一个二维的数组,将分类标签的训练集和测试集进行水平方向的拼接。拼接的操作是为了便于下面操作。

        下面进行训练线性支持向量机svm,首先定义了一个线性支持向量分类器,使用的核函数为线性核函数“linear”,惩罚因子C=1。然后通过svm.fit(X_train_std,y_trian)根据给定的训练数据拟合训练出SVM模型svm。

        然后调用plot_decision_regions()函数绘制决策图。其中实参test_idx=range(105,150)是因为样本集中训练集与测试集的划分比例为7:3,共有样本数据150个,所以105:45=7:3,所以从索引第105~索引第149共45个样本为测试集,前105个为训练集。

下面几行代码为设置图像的一些参数。

2.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))         # 输出分类准确率
"""
! (arr1 != arr2).sum():
arr1、arr2为数组类型
(arr1 != arr2): 输出一个列表,元素为bool类型,两数组对应位置不相等为True
.sum(): 统计列表中True的个数
! svm.score(X_test_std, y_test)
返回给定测试集与对应标签的平均准确率
"""

解释:

        svm.predict(X_test_std):对已经训练好的支持向量机对测试集X_test_std进行测试。所以:

        y_pred:存放对测试集X_test_std的预测分类结果;

        (y_test != y_pred).sum():对经过支持向量机分类的结果进行统计分类错误的个数;

        snm.score(X_test_std,y_test):返回给定测试集X_test_std与对应标签的平均准确率。

三、实验结果分析

3.1 终端输出

运行代码得到终端输出结果为: 

终端输出结果:
Class labels: [0 1 2]
Labels counts in y: [50 50 50]
Labels counts in y_train: [35 35 35]
Labels counts in y_test: [15 15 15]
Misclassified samples: 1
Accuracy: 0.98

解释:

        分类的标签有:[0 1 2];

        y中标签的个数统计:[50 50 50];

        y_train中标签个数统计:[35 35 35];

        y_test中标签个数统计:[15 15 15];

        对测试集y_test错分类的样本有:1个;

        svm的分类准确率为:0.98;

3.2 训练好的支持向量机SVM的决策面

上图为支持向量机svm的决策面的分布的情况,从最终的决策面的位置可以看到,红色区域为一类,蓝色区域为一类,绿色区域为一类红正方形、蓝三角形、绿圆圈分别为三类别的样本的分布情况,可以看到决策面对这些样本的划分情况还是能够区分开来不同类别的。

下面查看测试集X_test在三个不同区域的分布情况。

3.3 SVM对测试集的分类分布结果

上图为测试集X_test在三个不同区域(三种类别)的分布情况,其中黄色五角星为测试样本的图例,能够看到决策面对测试集能够很明显的划分出不同类,结合图4-1的终端输出结果可以看到测试集划分的正确率为98%,可以很好的划分出正确结果。

四、整体总结

支持向量机如果想划分效果很好,就需要找到支持向量的分布,再就是找到最大间隔的分离超平面,解决好上诉两个问题就基本可以实现支持向量机。

五、总代码

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)
返回给定测试集与对应标签的平均准确率
"""



"""
终端输出结果:
Class labels: [0 1 2]
Labels counts in y: [50 50 50]
Labels counts in y_train: [35 35 35]
Labels counts in y_test: [15 15 15]
Misclassified samples: 1
Accuracy: 0.98
"""

猜你喜欢

转载自blog.csdn.net/weixin_66845445/article/details/137054240