SVC
SVM自述
SVM的中文名为支持向量机,属于一种有监督的机器学习算法,可以用于离散因变量的分类和连续因变量的预测,通常情况下该算法相对于其他单一的分类有更好的预测准确率,主要是因为它可以将低维线性不可分的空间转化为高维的线性可分空间,由于该算法具有很高的预测率,所以倍受欢迎。该算法的思想就是利用某种支持向量积所构成的超平面,将不同类别的样本点进行划分,不管是样本点线性可分还是近似线性可分的还是非线性可分的,都可以利用超平面将样本点以较高的准确度分割开来,需要注意的是,如果是非线性可分的,需要介入其他核函数。
优点:
- 训练好的模型具有很好的泛化能力,在一定程度上,可以避免模型的过拟合
- 可以避免模型在运算过程中出现的局部最优
- 相比于一般的算法来说具有更好的预测结果
缺点:
- 模型不适合用于大样本的分类和预测,会消耗大量的计算资源和时间
- 模型对于缺失样本非常敏感,需要在建模前清洗每一个观测样本
- 在对于核函数解决非线性可分的问题时,模型对于核函数的选择会非常敏感
- SVM为黑盒模型,对于计算的结果无法解释
超平面
我们刚才提到SVM实质上就是由某些知识向量构成的最大间隔的超平面,那么我想有很多朋友要问什么是超平面,我们又能如何确定它?那么我们就在下面进行简单的阐述
关于什么是超平面?
举个简单的例子:例如在一维空间中如需要将数据切成两段,需要一个点即可;在二维空间中,对于线性可分的样本点,将其切分为两类,需要一条直线即可;在三维空间中将样本点切分开来,需要一个平面,以此类推在更高的维度空间中,需要构建一个超平面加数据进行划分。(下面就是通过图像来说明超平面是什么)
对于这些超平面,不管它是一个点,一个线,一个面或者是更高维的一些向量,这些超平面对于我们分割数据预测数据来说是非常重要的,那么我们如何确定一个效果最佳的超平面呢?
我们先拿二维数据简单说明:
我们可以看到两个类别的样本点之间存在明显的区分度,完全可以通过线性将其分割开来,我们在图中绘制了两条分割线,这两条直线可以方便的将样本点所属的类别判断出来,虽然直观的可以看出这两条分割直线都没有问题,但是哪一条直线的分类效果更佳呢,我们要知道训练样本的分类效果一致,并不代表测试样本点的分类效果也一样。在直线L1和L2之间还存在着无数多个分割直线,那么对于多条分割直线是否存在一条最优的超平面?
对于L1和L2之间的某条直线Li来说,他们可以将两类样本准确无误的划分开来,我们为了得到这条最佳的直线,需要做:
- 计算两个类别中样本点到直线Li的距离
- 然后从两组距离中各挑选一个最短的,就如图中的d1和d2,选取最短的距离为d1,并以此该距离构造分隔带
- 最后利用无穷多个分割直线Li,构造无穷多个分割带,并从这些分割带中选出最宽的Li
分割带代表了模型划分样本点的能力或可信度,分割带越宽说明模型能够对于样本划分的越清晰,进而保证模型泛化能力越强,分类可信度越高,反之分割带越窄,说明模型的准确率越容易受到异常点的影响,进而以理解为模型的预测能力较弱分类,可信度降低。
下图的分割带比较窄,它的分割能力和可信度也比较低
函数间隔和松弛因子
将圆所代表的负样例样本用-1来表示,图中的实直线表示某条分割面即超平面,两条虚线分别表示因变量Y取值为正1和-1的情况,它们与分割直线平行,可知当在wx+b=-1直线的下面时,则为圆的可能性越高,当在wx+b=1时样本为五角星的可能性越高:
函数间隔=(wx+b)y
其中y表示样本点所属的类别用-1和+1来表示,当wx+b计算的值小于等于-1时,根据分割面可以将样本x对应的y预测为-1,当wx+b计算值大于等于+1时,分割面会将样本点的x对应的y预测为+1,通过乘积可以得到函数间隔。
但是问题来了,样本中会所有的点都会在分割带外吗?
显然答案是否定的。
对于下面的一这张图来说,我们很容易的,可以发现绝大多数样本点都是相信可分的,只有一个异常点落在了分割在内,并且我们可以发现,如果除去这个点,可以得到一个比较理想的分类结果,那么我们需要牺牲少部分的异常点的利益,确保大部分的样本点都能够被良好的线性可分,这里就需要引入一个参数来代表牺牲点的量:松弛参数其惩罚项系数记作C
松弛参数的惩罚项系数C:
- 参数C用于权衡将训练样本的正确分类”与”决策函数的边际最大化”的效力。
- C为浮点数,默认1.0,必须大于等于0.0。
- 如果C值设定比较小,那SVC可能会选择边际较小的,能够更好地有训练点的决策边界,不过模型的训练实践也会更长。
- 如果C的设定值较高,那SVC会尽量最大化边际,决策功能会更简单,但代价是训练的准确度
- 数值一般为小于20.0,可画出学习曲线
- 换句话说,C在SVM中的影响就像正则化参数对逻辑回归的影响
kernel核函数参数
kernel作为SVC类最重要的参数之一,“kernel"在sklearn中可选以下几种选项:
在实际应用中SVM模型对于核函数的选择是非常敏感的,所以需要通过先验的领域知识或者交叉验证的方法,选出合适的核函数,大多数情况下选择高斯核函数是一种相对有效的方法,因为高斯和函数是一种指数函数,它的泰勒展示可以是无穷多维的。高斯径向基核函数基本在任何数据集上都表现不错,属于比较万能的核函数。无论如何先试试看高斯径向基核函数,它适用于核转换到很高的空间的情况,在各种情况下往往效果都很不错,如果高斯径向基效果不好,那我们再试试看其他的核函数。另外,多项式核函数多被用于图像处理之中。
SVC重要参数
SVC(C=1.0, # 指定目标函数中松弛因子的惩罚系数默认为1.0
kernel="rbf", # 用于指定X CM模型的核函数
cache_size=200, # 用于指定核函数运算的内存空间,默认200M
class_weight
# 用于指定因变量类别的权重,如果为字典,则通过字典的形式{class_label:weight}传递每个类别的权重
# 如果为字符串"balanced",每一个分类的权重与实际样本中的比例成反比,各类分类存在严重不均衡时设置会比较好
# 如果为None则表示每个分类的权重相同(默认)
# degree,gamma,cof0这三个参数在下面进行简单的说明,不过用处不大,我们基本都是使用默认。
)
SVC之线性分类实例
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.svm import SVC
x,y=make_blobs(n_samples=50,centers=2,random_state=0)
#x表示数据data y表示target
plt.scatter(x[:,0]#横坐标
,x[:,1]#纵坐标
,cmap="rainbow"#指定颜色
,c=y#指定颜色分类类型
,s=40#指定大小
)
ax=plt.gca()
#获取当前画布信息,不是填充数据,而是有关画布信息
xlim=ax.get_xlim()
ylim=ax.get_ylim()
#xlim为(-1.8524766350764195, 4.214175116277968)
#ylim为(-1.182778025647436, 6.608721805933875)
#画布横纵轴边缘最小最大值
axis1=np.linspace(xlim[0],xlim[1],30)
axis2=np.linspace(ylim[0],ylim[1],30)
#在整个画布上均匀取点30*30个
axisx,axisy=np.meshgrid(axis1,axis2)
#axisx表示(axis1,axis2)形式数据的横坐标,类型为(30,30)
#axisy表示(axis1,axis2)形式数据的纵坐标,类型为(30,30)
xy=np.vstack([axisx.ravel(),axisy.ravel()]).T
#降维后转置,生成(900,2)型的数据
plt.scatter(xy[:,0],xy[:,1],s=1)
#画出900个点,大小为1
svc=SVC(kernel="linear"
,cache_size=1000#允许使用空间大小
).fit(x,y)
#计算决策边界
z=svc.decision_function(xy).reshape(axisx.shape)
#返回每个样本到决策边界距离,对应levels
#为满足contour转换为(30,30)
ax.contour(axisx#横坐标
,axisy#纵坐标
,z#对应(axisx,axisy)的每一点高度,区分决策边界差距
#axisx,axisy组成的每一个坐标对应一个z值Y,用于contour函数对ax画板上这900个点level的确定
# 进而推算出画板上任意位置的level值。
,levels=[-1,0,1]#离决策边界为-1,0,1的三条线
,linestyles=["--","-","--"]
)
#画出过第十个点的边界线
#将第十个点改为黑色
plt.scatter(x[10,0],x[10,1],color="black",s=40)
z1=svc.decision_function(x[10].reshape((1,2)))
#z1与levels同意义
ax.contour(axisx,axisy,z,levels=z1,linestyles="-.")
plt.show()
print(svc.score(x,y))#0.98
SVC之非线性回归实例
import numpy as np
from sklearn.svm import SVC
from sklearn.datasets import make_circles
import matplotlib.pyplot as plt
#x.shape为(100,2) y.shape为(100,)
x,y=make_circles(100 # 一百个样本
,factor=0.1 # 两类数据距离
,noise=0.1 # 噪声比例
,random_state=0)
# 画出这一百个点
plt.scatter(x[:,0],x[:,1],cmap="rainbow",c=y,s=40)
# 以下同上一个代码
ax=plt.gca()
xlim=ax.get_xlim()
ylim=ax.get_ylim()
axisx=np.linspace(xlim[0],xlim[1],30)
axisy=np.linspace(ylim[0],ylim[1],30)
axisx,axisy=np.meshgrid(axisx,axisy)
xy=np.vstack([axisx.ravel(),axisy.ravel()]).T
plt.scatter(xy[:,0],xy[:,1],s=3)
# 实例化模型
svc=SVC(kernel="rbf").fit(x,y)
z=svc.decision_function(xy).reshape(axisx.shape)
ax.contour(axisx,axisy,z,levels=[-1,0,1],linestyles=["--","-","--"])
#取第十个数据点
plt.scatter(x[10,0],x[10,1],color="black",s=40)
z1=svc.decision_function(x[10].reshape((1,2)))
ax.contour(axisx,axisy,z,levels=z1,linestyles="-.")
ax.set_xlabel("x1")
ax.set_ylabel("x2")
plt.show()
结果:
非线性数据升维plt
关于我们之前所提到的SVM可以通过数据集的形态来提升它的维度,以便更好的分类数据,那么我们就在这里举一个将数据提升纬度的例子,可以简单了解一下。
from sklearn.datasets import make_circles
import matplotlib.pyplot as plt
import numpy as np
x,y=make_circles(100,factor=0.1,noise=0.1,random_state=0)
#x.shape为(100,2) y.shape为(100,)
#由于x,y为环形数据用线性回归无法表示
#故需要升维至三维,以二维平面进行分割
#增加z轴,命名r,以x(二维矩阵)为变量生成r的正态分布(二维正态分布)
r=np.exp(-(x**2).sum(axis=1))
rlim=np.linspace(min(r),max(r),100)
ax=plt.subplot(projection="3d")
#建立三维子图
ax.scatter3D(x[:,0],x[:,1],r,c=y,s=40,cmap="rainbow")
#3D建立数据,以x[:,0],x[:,1],r为三维坐标,c=y为颜色分布,
ax.view_init(elev=30,azim=30)
#转换坐标方向角度
ax.set_xlabel("x1")
ax.set_ylabel("x2")
ax.set_zlabel("z")
plt.show()
升维结果:
原数据:
四种核函数在不同数据的展现
import numpy as np
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from sklearn.datasets import make_circles,make_moons,make_blobs,make_classification
n=100
datas=[
make_moons(n_samples=n,noise=0.2,random_state=0)
,make_circles(n_samples=n,noise=0.2,factor=0.5,random_state=0)
,make_blobs(n_samples=n,centers=2,random_state=0)
,make_classification(n_samples=n,n_features=2,n_informative=2,n_redundant=0,random_state=0)
#有效信息 无效信息
]
Kernel=["linear","poly","rbf","sigmoid"]#名字
fig,axes=plt.subplots(4,5,figsize=(20,15))
for cnt,(x,y) in enumerate(datas):
"""cnt为索引,x为数据,y为结果"""
ax=axes[cnt,0]
if cnt==0:
#若cnt为第一行,输出为数据集标题
ax.set_title("Input_data")
ax.scatter(x[:,0]#x轴
,x[:,1]#y轴
,c=y#区分xy
,s=40#大小
,cmap=plt.cm.Paired#自动填充颜色
,edgecolors="black"#边缘为黑色
)
#x,y坐标不显示
ax.set_xticks([])
ax.set_yticks([])
#每个图像的形式
for index,kernel in enumerate(Kernel):
# 定画布中某个子图
ax=axes[cnt,index+1]
#建立模型
svc=SVC(kernel=kernel,gamma=2).fit(x,y)
#模型分数
score=svc.score(x,y)
#绘制数据图
ax.scatter(x[:,0],x[:,1],c=y,cmap=plt.cm.Paired,edgecolors="k",s=20
,zorder=50
#与其他内部图形相对有限展示度
)
#绘制支持向量点(facecolors="none"内部无颜色,edgecolors="k",边界为黑色,所以不明显)
ax.scatter(svc.support_vectors_[:,0]#使用的支持向量机的x
,svc.support_vectors_[:,1]#使用的支持向量机的y
,s=22
,facecolors="none"#内部颜色
,zorder=100
,edgecolors="k"#边界颜色
)
#计算决策边界
#确定面范围
x_min,x_max=x[:,0].min()-0.5,x[:,0].max()+0.5
y_min,y_max = x[:, 1].min() - 0.5, x[:, 1].max() + 0.5
xx=np.linspace(x_min,x_max,30)
yy=np.linspace(y_min,y_max,30)
xx,yy=np.meshgrid(xx,yy)
#上三句可用xx,yy=np.mgrid[x_min:x_max:30j,y_min:y_max:30j]
xy=np.vstack([xx.ravel(), yy.ravel()]).T#可用np.c_
#每个点对应level
z=svc.decision_function(xy).reshape(xx.shape)
#底版颜色确定
ax.pcolormesh(xx
,yy
,z>0
,cmap=plt.cm.Paired#自动生成适合颜色
,shading='auto'#自动
)
ax.contour(xx,yy,z,levels=[-1,0,1],colors=["k","k","k"],linestyles=["--","-","--"])
ax.set_xticks([])
ax.set_yticks([])
if cnt==0:
#向量机名称
ax.set_title(kernel)
ax.text(0.97#添加文本的横坐标位置
,0.05#添加文本的纵坐标位置
,("%.2f" % score)#格式化输出score,去掉不必要的0
,size=10#大小
#文本框类型
,bbox=dict(boxstyle="round",alpha=0.5,facecolor="white")
,transform=ax.transAxes#确定文字位置的坐标轴
,horizontalalignment="right"#以文本框的右侧为基准线
)
#调节fig位置
#subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=None)
fig.subplots_adjust(0.1,0.1,0.9,0.9,0.1,0.1)
plt.show()
class_weight和混淆矩阵
class_weight参数
import pandas as pd
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
import numpy as np
class_1=500
class_2=50
centers=[[0.0,0.0],[2.0,2.0]]#类别中心
clusters_std=[1.5,0.5]#方差
x,y=make_blobs(n_samples=[class_1,class_2]
# 两类样本,标签为1的为class_1个,标签为2的为class_2个
,centers=centers#样本中心
,cluster_std=clusters_std#方差大小
,random_state=0
#,shuffle=False
,shuffle=True)
plt.scatter(x[:,0],x[:,1],c=y,cmap="rainbow",s=10)
ax=plt.gca()
xlim=ax.get_xlim()
ylim=ax.get_ylim()
xx=np.linspace(xlim[0],xlim[1],30)
yy=np.linspace(ylim[0],ylim[1],30)
XX,YY=np.meshgrid(xx,yy)
xy=np.vstack([XX.ravel(),YY.ravel()]).T
#正常模型
svc=SVC(kernel="linear",C=1.0).fit(x,y)
z_svc=svc.decision_function(xy).reshape(XX.shape)
a=ax.contour(XX,YY,z_svc,colors="black",levels=[0],linestyles=["-"])
#调节数据比重的模型
csvc=SVC(kernel="linear"
,class_weight={0:1,1:10}#不好确定,下面进一步讨论
).fit(x,y)
z_csvc=csvc.decision_function(xy).reshape(XX.shape)
b=ax.contour(XX,YY,z_csvc,colors="red",levels=[0],linestyles=["-"])
plt.legend([a.collections[0],b.collections[0]],["no class_weight","class_weight"],loc="upper right")
plt.show()
scv.score(x,y)# 0.9418181818181818
csvc.score(x,y)# 0.9127272727272727
在未进行权重分配的时候,虽然分数达到了94%,但是我们可以看出模型对于少数类样本的分类结果并不是特别的理想,差不多只有一半的数据分类正确,但是在实际中我们对于不均衡的数据样本,更多的是为了正确拿到少类的分类结果,所以我们通过参数来进行自定义权重分配,在我们权重分配以后,虽然将少部分的数据都分类正确,但是紧接着问题又来了,对于样本量比较多的类别部分又都被误认为少部分的结果,从而使得分下降。那么我们是否能够找到一个较好的指标根据我们的需求来在而这之间进行取舍呢?那么接下来我们就要引入混淆矩阵
混淆矩阵的引入
这是一个简单的二阶混淆矩阵,对于正对角线上的均为预测正确的结果,其余为错误的结果,其中1表示少数类,0表示多数类。
准确率是指所有预测正确的结果除以所有样本结果,一般来说越接近1越好。
精确度又叫查准率,表示所有被我们预测为少数类的样本中,真正少数类所占的比例,在支持向量机中,精确度可以形象地表示为决策边界上方所有点中红色点所占的比例精确度越高,代表我们捕捉正确的红色点越多,对于少数类的预测越准确,精确度越低,则代表我们误伤了过多的多数类,精确度是将多数类判错后所需付出成本的衡量。当每一次将多数类判断错误的成本非常高的时候,我们会追求高精确度。
召回率又被称为敏感度,真正与查全率表示所有真实为1的样本中被我们预测正确的样本所占比例,在支持向量其中召回率可以被表示为决策边界上方所有红色的点全部样本中红色的点的比例召回率越高,代表我们捕获了越多的少数类,召回率越低代表我们没有捕获到足够的少数类,如果我们不惜一切代价找出少数人,就要追求较高的召回率。另外要注意的是,召回率和精确度是此消彼长的,我们平衡两个指标表示捕捉少数类的需求和尽量不要让多数类被判断错误。
侧翼度表示所有真实为0的样本中被正确预测为0的样本所占比例,在支持向量机中表示为决策边界下方紫色点占下方所有点的比例
假正率为1减特异度
手动实现五大指标
import pandas as pd
from sklearn.metrics import confusion_matrix,precision_score,recall_score
from sklearn.linear_model import LogisticRegression as LR
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
import numpy as np
class_1=500
class_2=50
centers=[[0.0,0.0],[2.0,2.0]]#类别中心
clusters_std=[1.5,0.5]#方差
x,y=make_blobs(n_samples=[class_1,class_2]
,centers=centers
,cluster_std=clusters_std
,random_state=0
#,shuffle=False
,shuffle=True)
svc=SVC(kernel="linear",C=1.0).fit(x,y)
csvc=SVC(kernel="linear",class_weight={0:1,1:10}).fit(x,y)
#混淆矩阵对样本不平衡的情况
#accuracy准确率(正确占总样本)
((y[y==svc.predict(x)]==1).sum()+(y[y==svc.predict(x)]==0).sum())/len(y)
#precision精确度(预测为数样本结果且正确占预测为数样本结果的比例)
(y[y==svc.predict(x)]==1).sum()/(csvc.predict(x)).sum()
(y[y==csvc.predict(x)]==1).sum()/(csvc.predict(x)).sum()
#Recall召回率(预测为少数样本的结果占总真实少数比例)
(y[y==svc.predict(x)]==1).sum()/(y==1).sum()
(y[y==csvc.predict(x)]==1).sum()/(y==1).sum()
#特异度(预测为多数样本的结果占总真实多数比例)
(y[y==svc.predict(x)]==0).sum()/(y==0).sum()
(y[y==csvc.predict(x)]==0).sum()/(y==0).sum()
#FPR假证率(1-特异度)
#准确率召回率等可用混淆矩阵实现
cm=confusion_matrix(y#真实数据(可用y)
,csvc.predict(x)#预测值(可用csvc.predict(x))
,labels=[1,0]#先少后多
)
#FPR=cm[1,0]/cm[1,:].sum()#假证率
#Recall=cm[0,0]/cm[0,:].sum()#召回率
调用函数实现指标
import pandas as pd
from sklearn.linear_model import LogisticRegression as LR
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
import numpy as np
from sklearn.metrics import confusion_matrix,precision_score,recall_score
class_1=500
class_2=50
centers=[[0.0,0.0],[2.0,2.0]]#类别中心
clusters_std=[1.5,0.5]#方差
x,y=make_blobs(n_samples=[class_1,class_2]
,centers=centers
,cluster_std=clusters_std
,random_state=0
#,shuffle=False
,shuffle=True)
svc=SVC(kernel="linear",C=1.0).fit(x,y)
csvc=SVC(kernel="linear",class_weight={0:1,1:10}).fit(x,y)
#用逻辑回归进行评估概率
lf=LR().fit(x,y)
pro=lf.predict_proba(x)
pro=pd.DataFrame(pro)
for i in range(pro.shape[0]):
#假设0.5为分类边界
if pro.loc[i,1]>0.5:
pro.loc[i,"pro"]=1
else:
pro.loc[i,"pro"]=0
pro["true_target"]=y#真是结果
pro.sort_values(by=1,ascending=False)#排序方式
#准确率召回率等可用混淆矩阵实现
cm=confusion_matrix(pro.loc[:,"true_target"]#真实数据(可用y)
,pro.loc[:,"pro"]#预测值(可用csvc.predict(x))
,labels=[1,0]#先少后多
)
#准确度
precision_score(pro.loc[:,"true_target"]#真实数据
,pro.loc[:,"pro"]#预测值
,labels=[1,0]#先少后多
)
#召回率
recall_score(pro.loc[:,"true_target"]#真实数据
,pro.loc[:,"pro"]#预测值
,labels=[1,0]#先少后多
)
ROC曲线
from sklearn.metrics import confusion_matrix,precision_score,recall_score
from sklearn.svm import SVC
import matplotlib.pyplot as plt
from sklearn.datasets import make_blobs
import numpy as np
class_1=500
class_2=50
centers=[[0.0,0.0],[2.0,2.0]]#类别中心
clusters_std=[1.5,0.5]#方差
x,y=make_blobs(n_samples=[class_1,class_2]
,centers=centers
,cluster_std=clusters_std
,random_state=0
#,shuffle=False
,shuffle=True)
#支持向量机自带概率,但是运算效率低(实例化时需要probability=True)
svc=SVC(kernel="linear",C=1.0,probability=True).fit(x,y)
recall=[]
FPR=[]
pro_range=np.linspace(svc.predict_proba(x)[:,1].min()
,svc.predict_proba(x)[:,1].max(),50
,endpoint=False#不取最后一位
)
for i in pro_range:
y_predict=[]
for j in range(x.shape[0]):
#以i为分界概率
if svc.predict_proba(x)[j,1]>i:
y_predict.append(1)
else:
y_predict.append(0)
cm=confusion_matrix(y,y_predict,labels=[1,0])#混淆矩阵
recall.append(cm[0,0]/cm[0,:].sum())
FPR.append(cm[1,0]/cm[1,:].sum())
recall.sort()
FPR.sort()
plt.plot(FPR,recall,color="red")
plt.plot(pro_range,pro_range,color="black",linestyle="--")
plt.xlabel("FPR")
plt.ylabel("recall")
plt.show()
我们尽量在假正率(横坐标)小的情况下寻找 recall较大的值。对于一条凸形ROC曲线来说,曲线越靠近左上角越好,越往下越坏。曲线如果在虚线下方所证明模型完全无法使用,但是也有可能是一条凹型的ROC曲线,对于一条凹型的ROC曲线来说,应该越靠近右下角越好凹形曲线代表模型预测结果与真实情况完全相反,那么不算非常糟糕,我们可以手动将模型结果逆转就可以得到一条左上方的弧线,最糟糕的是图像和中间和虚线非常靠近,那么我们就无能为力。