(一)基础理论部分
如果对支持向量机的基础理论推导感兴趣,这里分享一个经典的视频:https://www.youtube.com/watch?v=_PwhiWxHK8owww.youtube.com
如果只是想要了SVM的解基本思想并进行实践,一定要看这个视频:https://www.youtube.com/watch?v=N1vOgolbjScwww.youtube.com
由于视频是在油管发布的,很多小伙伴看不了,油管搬运工又来做体力活了~
现在有一个维度为二维(x,y),类别有2类(Class=0或1)的数据集。将其可视化后效果如下:
我们想要训练出一个分类器,使得新给一个x和y值后,就可以预测该点的类别。
这个分类器事实上可以有多个,只要分类器能够将两个类别完全区分开来,我们就可以说这个分类器是合格的。如下图所示,一条黑色直线就是一个分类器。
但是,这么多分类器,哪个才是最好的呢???
我们认为,下面这条直线是最佳分类器,这条直线也称为超平面(Hyperplane)。
可是,为什么是这条直线呢?
因为,当我们对超平面在保证斜率不变的前提下,进行上下平移时,形成两个分类边界线,这两个边界线之间的距离(宽度)是最大的。使两个边界线之间的距离(宽度)达到尽可能大,这么做的好处是,新样本数据被误分的概率就会更小。
对比,其他超平面形成的分类边界如下:
另外一个问题是,在对超平面进行平移过程中,我们平移到哪里就该停止平移呢?
答约,当我们遇到“支持向量”时就该停止平移,从而形成边界。
什么是“支持向量”?一般认为,超平面在平移过程中,遇到第一个点后就停止平移,则这个点就是支持向量。
(二)Python实战
演示数据集:
提取码:3u2b
数据集是一个蛋糕配方,共有muffin和cupcake两种类型的蛋糕,配方变量为Sugar和Butter。我们需要判断,给定Sugar和Butter值,预测该蛋糕类型。
#导入数据
import pandas as pd
path = "C:\\Users\\Cara\\Desktop\\cupcake or muffin.xlsx"
data = pd.read_excel(path)
print(data.shape)#(18, 3)
print(data.head(5))
'''
Sugar Butter CakeType
0 3 7 muffin
1 12 6 muffin
2 18 6 muffin
3 12 6 muffin
4 3 7 muffin
'''
#认识数据:数据可视化
import seaborn as sns
sns.lmplot(data=data,x='Sugar',y='Butter',palette='Set1',fit_reg=False,hue='CakeType',scatter_kws={'s':150})
'''
lmplot()参数说明:
palette='Set1'设置调色板型号,对应不同绘图风格,色彩搭配。
fit_reg=False表示不显示拟合的回归线。因为lmplot()本身是线性回归绘图函数,默认会绘制点的拟合回归线。
hue='CakeType'表示对样本点按照'CakeType'的取值不同进行分类显示,这样不同类型的蛋糕会用不同颜色显示。若不设置hue参数,则所有点都会显示为一个颜色显示。
scatter_kws={'s':150}:设置点的大小,其中s表示size。
'''
效果如下:
从绘图结果来看,样本数据很适合进行二分类训练,因为两种蛋糕刚好可以互相分离,使用一条直线就可以将两类样本清楚划分。而且,这样的直线不止一条,因此,我们需要找到最优的那条划分直线。
#数据预处理
#将CakeType的值映射到0、1,方便后续模型运算
import numpy as np
label = np.where(data['CakeType']=='muffin',0,1)
print(label)#[0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1]
x = data[['Sugar','Butter']]
#print(x)
#SVM实例化
from sklearn.svm import SVC
#SVC指Support Vector Classifier
svc = SVC(kernel='linear',C=1)
'''
SVC参数说明:
C:惩罚系数,即当分类器错误地将A类样本划分为B类了,我们将给予分类器多大的惩罚。当我们给与非常大的惩罚,即C的值设置的很大,那么分类器会变得非常精准,但是,会产生过拟合问题。
kernel:核函数,如果使用一条直线就可以将属于不同类别的样本点全部划分开,那么我们使用kernel='linear',
如果不能线性划分开,尤其是当数据维度很多时,一般很难找到一条合适的线将不同的类别的样本划分开,那么就尝试使用高斯核函数(也称为径向基核函数-rbf)、多项式核函数(poly)
'''
svc.fit(X=x,y=label)
#根据拟合结果,找出超平面
w = svc.coef_[0]
a = -w[0]/w[1]#超平面的斜率,也是边界线的斜率
xx = np.linspace(5,30)#生成5~30之间的50个数
#print(xx)
'''
[ 5. 5.51020408 6.02040816 6.53061224 7.04081633 7.55102041
8.06122449 8.57142857 9.08163265 9.59183673 10.10204082 10.6122449
11.12244898 11.63265306 12.14285714 12.65306122 13.16326531 13.67346939
14.18367347 14.69387755 15.20408163 15.71428571 16.2244898 16.73469388
17.24489796 17.75510204 18.26530612 18.7755102 19.28571429 19.79591837
20.30612245 20.81632653 21.32653061 21.83673469 22.34693878 22.85714286
23.36734694 23.87755102 24.3877551 24.89795918 25.40816327 25.91836735
26.42857143 26.93877551 27.44897959 27.95918367 28.46938776 28.97959184
29.48979592 30. ]
'''
yy = a * xx - (svc.intercept_[0])/w[1]
#根据超平面,找到超平面的两条边界线
b = svc.support_vectors_[0]
yy_down = a * xx + (b[1]-a*b[0])
b = svc.support_vectors_[-1]
yy_up = a * xx + (b[1]-a*b[0])
#绘制超平面和边界线
#(1)绘制样本点的散点图
sns.lmplot(data=data,x='Sugar',y='Butter',hue='CakeType',palette='Set1',fit_reg=False,scatter_kws={'s':150})
#(2)向散点图添加超平面
from matplotlib import pyplot as plt
plt.plot(xx,yy,linewidth=4,color='black')
#(3)向散点图添加边界线
plt.plot(xx,yy_down,linewidth=2,color='blue',linestyle='--')
plt.plot(xx,yy_up,linewidth=2,color='blue',linestyle='--')
效果如下:
# 调整参数C,看看会有什么不同?
svc = SVC(kernel='linear',C=0.001)
svc.fit(X=x,y=label)
#根据拟合结果,找出超平面
w = svc.coef_[0]
a = -w[0]/w[1]
xx = np.linspace(5,30)
yy = a * xx - (svc.intercept_[0])/w[1]
#根据超平面,找到超平面的两条边界线
b = svc.support_vectors_[0]
yy_down = a * xx + (b[1]-a*b[0])
b = svc.support_vectors_[-1]
yy_up = a * xx + (b[1]-a*b[0])
#绘制超平面和边界线
#(1)绘制样本点的散点图
sns.lmplot(data=data,x='Sugar',y='Butter',hue='CakeType',palette='Set1',fit_reg=False,scatter_kws={'s':150})
#(2)向散点图添加超平面
from matplotlib import pyplot as plt
plt.plot(xx,yy,linewidth=4,color='black')
#(3)向散点图添加边界线
plt.plot(xx,yy_down,linewidth=2,color='blue',linestyle='--')
plt.plot(xx,yy_up,linewidth=2,color='blue',linestyle='--')
效果如下:
可以发现,当C值给的十分小时,模型精确度会下滑地很厉害,很多样本被错分了。
################################################
#SCM做多分类
import pandas as pd
path = "C:\\Users\\Cara\\Desktop\\cupcake and muffin and Cavine.xlsx"
data = pd.read_excel(path)
print(data.shape)#(24, 3)
print(data['CakeType'].value_counts())
'''
cupcake 9
muffin 9
Cavine 6
Name: CakeType, dtype: int64
'''
#认识数据:数据可视化
import seaborn as sns
sns.lmplot(data=data,x='Sugar',y='Butter',palette='Set1',fit_reg=False,hue='CakeType',scatter_kws={'s':150})
从可视化结果来看,样本共有3个类别,且3个类别之间可以使用两条直线进行划分,因此,SVM的kernel参数仍然可以使用linear。
#数据预处理
#将CakeType的值映射到0、1、2,方便后续模型运算
label = data.CakeType.map({'muffin':0,'cupcake':1,'Cavine':2})
#print(label)
x = data[['Sugar','Butter']]
#实例化SVC
from sklearn.svm import SVC
svc = SVC(kernel='linear',C=0.001,decision_function_shape='ovr')
'''
参数说明:
decision_function_shape:设置分类器决策模式,有两种模式:ovr和ovo
ovr表示one vs rest模式,即比较某一个类别和其他所有类别;
ovo表示one vs one模式,即一对一地比较(两两比较)两个类别,如果样本数据有ABC3个类别,则需要比较3次,分别是比较A和B,比较A和C,比较B和C。
ovo模式需要比较的次数会多于ovr模式,但是,ovo模式对于类别不均衡的样本数据具有较好的稳定性。
'''
svc.fit(X=x,y=label)
label_predict = svc.predict(X=x)
#绘制三分类超平面和边界线
#生成坐标
import numpy as np
x_min, x_max = data.Sugar.min() - 0.2, data.Sugar.max() + 0.2
y_min, y_max = data.Butter.min() - 0.2, data.Butter.max() + 0.2
step = 0.01
x_value = np.arange(x_min, x_max, step)
y_value = np.arange(y_min, y_max, step)
#注意!因为x_min到x_max的距离可能并不等于y_min到y_max的距离,所以,即使使用的相同步长(Step),生成的两组数可能长度并不一样。
'''
numpy 中arange()用于生成一组连续的数,可以指定数的起点、终点、和步长。
print(np.arange(0,3,0.05))#生成[0,3)之间,以0.05为步长的一组数
#结果:
[0. 0.05 0.1 0.15 0.2 0.25 0.3 0.35 0.4 0.45 0.5 0.55 0.6 0.65
0.7 0.75 0.8 0.85 0.9 0.95 1. 1.05 1.1 1.15 1.2 1.25 1.3 1.35
1.4 1.45 1.5 1.55 1.6 1.65 1.7 1.75 1.8 1.85 1.9 1.95 2. 2.05
2.1 2.15 2.2 2.25 2.3 2.35 2.4 2.45 2.5 2.55 2.6 2.65 2.7 2.75
2.8 2.85 2.9 2.95]
'''
#基于生成的x值、y值,形成一个个点(二维坐标),用于后续预测这些点的类别
xx, yy = np.meshgrid(x_value,y_value)
'''
numpy中的meshgrid()函数根据输入的两组数生成两个二维数组,并且这两个数组shape是一模一样的。
a,b = np.meshgrid([1,2,3],[4,5,6,7,8])
print(a)
[[1 2 3]
[1 2 3]
[1 2 3]
[1 2 3]
[1 2 3]]
print(b)
[[4 4 4]
[5 5 5]
[6 6 6]
[7 7 7]
[8 8 8]]
'''
Z = svc.predict(np.c_[xx.ravel(), yy.ravel()])#使用分类器对生成的坐标点进行预测类别
Z = Z.reshape(xx.shape)#转换预测结果Z的格式,使得可以与坐标点一一对应
#绘制原始数据的散点图(此时,类型数据是真实值)
ax = sns.scatterplot(data.Sugar, y=data.Butter,hue=label,palette='Set2')
ax.legend(loc="lower right")
#绘制基于预测值的分界面,即分类器的边界
from matplotlib import pyplot as plt
plt.contourf(xx, yy, Z, alpha=0.3)
背景色表示三个类别的边界,是基于训练出的分类器绘制的。散点本身是真实类别信息。
可以看到,由于SVC中的参数C设置为较小的数(C=0.001),所以存在部分点被错误分到其他类别边界内。主要是绿色点被错误划分到橙色点的边界内。
说明:
contourf()函数用于绘制等高线,并会填充轮廓,f指fill
参数:
x为x轴坐标值,在只有两个属性的样本数据集中,x对应样本数据中的属性x1.
y为y轴坐标值,对应样本数据中的属性x2.
z为等高线的高度,一般对应分类问题中的目标变量y,
cmap:设置等高线填充色的映射规则,可选值为:plt.cm.jet、plt.cm.ocean、plt.cm.hsv
更多颜色:https://matplotlib.org/examples/color/colormaps_reference.html
contourf()功能演示:
n = 256
import numpy as np
x = np.linspace(-3, 3, n)#生成x坐标
y = np.linspace(-3, 3, n)#生成y坐标
X, Y = np.meshgrid(x, y)#组合x、y,形成二维坐标
# 计算x,y坐标对应的类别值Z(是连续变量)
def fun(x, y):
Z = (1-x/2+x**3+y**5) * np.exp(-x**2-y**2)
return Z
Z = fun(X,Y)
import matplotlib.pyplot as plt
plt.contourf(X, Y, Z)
plt.xlabel('x')
plt.ylabel('y')
效果如下: