python支持向量机SVM (sklearn)
newsun-boki Lv3

sklearn支持向量机SVM

原理概述

说实话以前用支持向量机都是直接套进去的,不过现在看了看菜菜提供数学原理发现其实挺有意思(是超有意思!!)。此处就不详述了,这原理到处都是。反正这SVM和决策树一样,有支持向量分类(SVC)和支持向量回归(SVR).

代码

下面是一个SVC的案例

导入库

1
2
3
4
from sklearn.datasets import make_blobs
from sklearn.svm import SVC
import matplotlib.pyplot as plt
import numpy as np

生成数据集

1
2
3
4
5
6
X,y = make_blobs(n_samples=50, centers=2, random_state=0,cluster_std=0.6)

plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plt.xticks([])
plt.yticks([])
plt.show()

在这里插入图片描述

核心代码

模型本身并不难,就是要画出相应的图

1
2
clf = SVC(kernel = "linear").fit(X,y)
print(clf.predict(X))

上述例子预测又对X自己预测了一变。按照核心代码依旧延续sklearn的风格,十分简单。

  • 实例化
  • fit()
  • predict()。

可视化可能优点麻烦,需要用到下面这个函数。这个函数只需输入clf即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def plot_svc_decision_function(model,ax=None):
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
x = np.linspace(xlim[0],xlim[1],30)
y = np.linspace(ylim[0],ylim[1],30)
Y,X = np.meshgrid(y,x)
xy = np.vstack([X.ravel(), Y.ravel()]).T
#decision_function这个函数可以返回给定的x,y点到决策边界(也就是点到SVM所得到划分线的距离)
P = model.decision_function(xy).reshape(X.shape)
ax.contour(X, Y, P,colors="k",levels=[-1,0,1],alpha=0.5,linestyles=["--","-","--"])
ax.set_xlim(xlim)
ax.set_ylim(ylim)

函数大概思路就是首先生成一个网格,然后计算网格中各个点到决策边界的距离,最后绘制等高线(算出的距离相等的一条线)。

则可以写作

1
2
3
clf = SVC(kernel = "linear").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)

在这里插入图片描述

其中灰色实线就是决策边界虚线之间的距离就是边际。而SVM就是要找到边际最大的一条决策边界,而上面那三个穿虚线而过的点就是支持向量(最下面那个红色不算),也就是离决策边界最近的3个点

线性不可分的情况

这就是整个SVM我觉得最关键也最有意思的地方。从上面的图我们能够知道SVM实际上是找到一个超平面将各点分开。如果能找到自然就是线性可分的,那如果找不到呢?

超平面就是比当前空间维度低一个维度的分界,对于二维平面来说是一条线,对于三维空间是一个面

对于这么一个案例

1
2
3
4
5
6
from sklearn.datasets import make_circles
X,y = make_circles(100, factor=0.1, noise=.1)
X.shape
y.shape
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plt.show()

在这里插入图片描述

我们可以看出,不可能存在一条直线,能将红色和蓝色分开,所以它是线性不可分的,怎么办呢?答案是升维!!!

对于原来每一个点,我们使用一个映射函数,使得从原来的二维点变为原来的三维点。对于原来一个点有(x0,y0),
从(x0,y0)变为(x1,y1,z1),其中z1有

1
x1 = x0; y1 = y0; z1 = e^{-(x^2+y^2)} 

先别想这个函数是怎么来的,让我们先看看映射后的结果怎么样
在这里插入图片描述

这个结果非常的amazing啊,红色的点浮起来了,现在只需要用一个平面就能把红色和蓝色隔开,也就是说,升维之后线性可分了!(当时就把我看湿了

下面是上面这个案例的完整代码

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
from sklearn.svm import SVC
import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_circles
X,y = make_circles(100, factor=0.1, noise=.1)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
def plot_svc_decision_function(model,ax=None):
if ax is None:
ax = plt.gca()
xlim = ax.get_xlim()
ylim = ax.get_ylim()
x = np.linspace(xlim[0],xlim[1],30)
y = np.linspace(ylim[0],ylim[1],30)
Y,X = np.meshgrid(y,x)
xy = np.vstack([X.ravel(), Y.ravel()]).T
P = model.decision_function(xy).reshape(X.shape)
ax.contour(X, Y, P,colors="k",levels=[-1,0,1],alpha=0.5,linestyles=["--","-","--"])
ax.set_xlim(xlim)
ax.set_ylim(ylim)
clf = SVC(kernel = "linear").fit(X,y)
plt.scatter(X[:,0],X[:,1],c=y,s=50,cmap="rainbow")
plot_svc_decision_function(clf)
r = np.exp(-(X**2).sum(1))
rlim = np.linspace(min(r),max(r),30)
from mpl_toolkits import mplot3d
def plot_3D(elev=30,azim=30,X=X,y=y):
ax = plt.subplot(projection="3d")
ax.scatter3D(X[:,0],X[:,1],r,c=y,s=50,cmap='rainbow')
ax.view_init(elev=elev,azim=azim)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("r")
plt.show()
#下面这个如果你用的是jupyter就可以互动了
#from ipywidgets import interact,fixed
#interact(plot_3D,elev=[0,30],azip=(-180,180),X=fixed(X),y=fixed(y))
#plt.show()

核函数

所以说上面这个映射函数到底是怎么来的呢,什么是核函数,又和核函数又什么关系呢?
首先SVM最终的决策函数是
在这里插入图片描述

  • f(x_test)是最终的分类,定义为{-1,1}
  • 其中sign是符号函数,大于0取1,小于0取-1。
  • y是标签,二分类有两个标签,取{-1,1}
  • x_i是支持向量
  • x_test是测试向量
  • b是一个常数

很显然我们要计算x_i与x_test之间的内积,当我们升维之后,我们要算的的内积是映射(升维)后的内积
在这里插入图片描述

那么我们定义核函数:
在这里插入图片描述

现在我们就只需要直接把两个向量带入核函数,而不用先映射成高维再算内积。这其实省去了很多麻烦,因为计算映射其实挺复杂的。
注意,正是因为再SVM中我们只用到了高维函数的内积,所以只需要计算核函数即可,有了核函数,我们便能再低维计算高维的内积,(这是一次低维生物对高维发起的伟大挑战)显然我们有,只要映射函数不同,核函数就不同
下面是sklearn中的几个核函数,个人建议用”rbf”,至于每一个核函数对应的映射函数,自己百度吧。
在这里插入图片描述

在这里插入图片描述

并且有

  • 线性核,尤其是多项式核函数在高次项时计算非常缓慢
  • rbf和多项式核函数都不擅长处理量纲不统一的数据集
    所以需要针对其进行标准化
    1
    2
    from sklearn.preprocessing import StandardScaler
    X = StandardScaler().fit_transform(X)

重要参数C(软间隔和硬间隔)

c:

浮点数,默认1,必须大于等于0,可不填
松弛系数的惩罚项系数。如果C值设定比较大,那SVC可能会选择边际较小的,能够更好地分类所有训练点的决策边界,不过模型的训练时间也会更长。如果C的设定值较小,那SVC会尽量最大化边界,决策功能会更简单,但代价是训练的准确度。换句话说,C在SVM中的影响就像正则化参数对逻辑回归的影响.

混淆矩阵

在这里插入图片描述

1是少数类,0是多数类

  • 准确率:Accuracy,所有预测正确的样本除以总样本,(11+00)/(11+10+01+00)
  • 精确率:Precision,预测的少数类中,真正的少数类的占比。11/(11+01)
  • 召回率 :Recall,又被称为敏感度(sensitivity),真正率,查全率,表示所有真实为1的样本中,被我们预测正确的样本所占的比例,11/(11+10).
  • 假负率:False Negative Rate,1-Recall。没召回的占少数类的占比。10/(11+10)
  • 特异度:Specificity,表示所有真实为0的样本中,被正确预测为0的样本所占的比例,00/(01+00)
  • 假正率:False Positive Rate,1 - specificity就是一个模型将多数类判断错误的能力,01/(01+00)
  • ROC曲线,横轴为假正率(FPR),纵轴为召回率(Recall),当曲线越往左上角偏说明效果越好,。
  • AUC面积,它代表了ROC曲线下方的面积,这个面积越大,代表ROC曲线越接近左上角,模型就越好。

    小案例

  • 首先生成数据集并带入模型训练
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from sklearn.svm import SVC
    import matplotlib.pyplot as plt
    import numpy as np
    from sklearn.datasets import make_blobs
    class_1 = 500 #类别1有500个样本
    class_2 = 50 #类别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)
    plt.scatter(X[:, 0], X[:, 1], c=y, cmap="rainbow",s=10)
    clf_proba = SVC(kernel="linear",C=1.0,probability=True).fit(X,y)
    在这里插入图片描述
  • 其次把个点所预测得到的概率放入DataFrame里
    1
    2
    3
    4
    5
    prob = clf_proba.predict_proba(X)
    #将样本和概率放到一个DataFrame中
    import pandas as pd
    prob = pd.DataFrame(prob)
    prob.columns = ["0","1"]
  • 找出最佳阈值并画出ROC曲线并得到相应的FPR和Recall,以及AUC
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
from sklearn.metrics import roc_curve
FPR, recall, thresholds = roc_curve(y,prob.loc[:,"1"], pos_label=1)
#此时的threshold就不是一个概率值,而是距离值中的阈值了,所以它可以大于1,也可以为负
#找到最佳阈值
maxindex = (recall - FPR).tolist().index(max(recall - FPR))
print('thresholds:')
print(thresholds[maxindex])

print('recall:')
print(recall[maxindex])

print('FPR:')
print(FPR[maxindex])
from sklearn.metrics import roc_auc_score as AUC
area = AUC(y,clf_proba.decision_function(X))

plt.scatter(FPR[maxindex],recall[maxindex],c="black",s=30)
#把上述代码放入这段代码中:
plt.figure()
plt.plot(FPR, recall, color='red',label='ROC curve (area = %0.2f)' % area)
plt.plot([0, 1], [0, 1], color='black', linestyle='--')
plt.scatter(FPR[maxindex],recall[maxindex],c="black",s=30)
plt.xlim([-0.05, 1.05])
plt.ylim([-0.05, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('Recall')
plt.title('Receiver operating characteristic example')
plt.legend(loc="lower right")
plt.show()

在这里插入图片描述

  • 最后进行预测,得出结果
    1
    2
    3
    4
    5
    6
    7
    8
    for i in range(prob.shape[0]):
    if prob.loc[i,"1"] > thresholds[maxindex]:
    prob.loc[i,"pred"] = 1
    else:
    prob.loc[i,"pred"] = 0
    prob["y_true"] = y
    prob = prob.sort_values(by="1",ascending=False)
    prob

在这里插入图片描述

  • 得到混淆矩阵来评估
    1
    2
    3
    from sklearn.metrics import confusion_matrix as CM, precision_score as P, recall_score as R
    cm = CM(prob.loc[:,"y_true"],prob.loc[:,"pred"],labels=[1,0])
    cm
    在这里插入图片描述

PS:这个案例用的是核函数是线性核,但又试了一遍还是‘rbf’比较好,
并且其实不一定需要最佳阈值,要结合实际来看。
例如,三星手机发生爆炸,三星想要召回率能达到100%,即宁可把没有问题的手机召回,也不能放过任何一个有问题的手机,阈值也要相应调整。

多分类

多分类其实也很简单,应该是sklearn的多分类很简单,数学原理十分可怕。区别就是输入的Y多了几个分类而已。

1
clf = SVC(decision_function_shape='ovo') clf.fit(X, Y)

上面用的是ovo(one vs one),也就是每一个类两两组合来构建,也可以选择’ovr’速度更快,效果不怎么样。

补充

参数class_weight

如果你想追求最高的召回率,宁可错杀不可放过,那么class_weight = "balanced"

1
clf = SVC(kernel = kernel ,gamma="auto",degree = 1,cache_size = 5000,class_weight = "balanced").fit(Xtrain, Ytrain)

如果还想再高,那么class_weight = {1:10}

  • Post title:python支持向量机SVM (sklearn)
  • Post author:newsun-boki
  • Create time:2021-11-01 23:04:54
  • Post link:https://github.com/newsun-boki2021/11/01/python-svm/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.