持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第12天,点击查看活动详情
在了解SVM分类时,我们可以先回顾一下之前的分类,如下图所示:
图1 不同的线性分类
在图1中我们看见有两个不同模型的线性分类器,紫色的分类器还不错,能够把A和B分开,但是绿色的却不是那么的靠谱。绿色的模型结果是相当糟糕,把A中不少一部分都给分错了,遇见这样的分类器-要么我们跑路,要么让模型赶紧跑路!
Ok,紫色的模型固然不错了,但是我们发现他离A和B类的边缘实例很靠近,也就是如果在训练集上效果还不错的话,有可能在测试集上的结果可能不一定让人满意。
线性SVM分类器
支持向量机(Support Vector Machine,SVM)可以执行线性和非线性的分类、回归任务。这边我们了解他执行线性任务的情况:
用SVM来执行上面的AB分类时,它不仅会分出AB类,而且他还会尽可能地远离最近的分类实例,大概样子会如下图2所示:
图2 SVM分类器
他在两个类之间找出一条很宽的街道(虚线之间),也因此叫做大间隔分类。当街道(虚线)之外的实例再怎么增加也不能够改变这个决策边界。请注意其中有两个圈出的实例,这两个位于街道边缘的实例起着决定街道大小的作用,因此也被称为支持向量。
需要注意一点是:SVM对特征的缩放还是很敏感的,如下图左边横坐标和纵坐标之间的数值差距过大,很有可能SVM在选择上会选择数值上差距小的一部分作为“分割街道”,但是经过特征缩放后,他所选择的“街道”将更加的合理。
图3 SVM对缩放的敏感度
线性SVC区分维吉尼亚鸢尾花
最后还是使用这个鸢尾花的数据集,通过linearSVC()实现区分,同时也了解通过修改其中一个超参数C(正则化程度)会产生什么效果。
# 画决策边界和支持向量所在的边缘
def plot_svc_decision_boundary(svm_clf, xmin, xmax):
# 向量的权重
w = svm_clf.coef_[0]
# 截距
b = svm_clf.intercept_[0]
x0 = np.linspace(xmin, xmax, 200)
# 因为我们选择了长度和宽度两个特征,所以权重就有两个w0和w1
# 完整的决策边界应该是w[0] * x0 + w[1] * x1 + b = 0
# 这边我们以长度(权重是w0)作为横坐标,所以上面的决策边界需要变形成:
# x1 = -w[0]/w[1] * x0 - b/w[1]
decision_boundary = -w[0]/w[1] * x0 - b/w[1]
# 支持向量所在的位置是w[0] * x0 + w[1] * x1 + b = +/- 1
# 和上面一样也需要变形成:
# x1 = -w[0]/w[1] * x0 - b/w[1] +/- 1/w[1]
margin = 1/w[1]
gutter_up = decision_boundary + margin
gutter_down = decision_boundary - margin
# 支持向量的点所在位置
svs = svm_clf.support_vectors_
plt.scatter(svs[:, 0], svs[:, 1], s=180, facecolors='#FFAAAA')
plt.plot(x0, decision_boundary, "k-", linewidth=2)
plt.plot(x0, gutter_up, "k--", linewidth=2)
plt.plot(x0, gutter_down, "k--", linewidth=2)
# 加载数据,现在的X包含有花瓣的长度和宽度,不知道iris["data"]的小伙伴可以看看上一篇
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)]
y = (iris["target"] == 2).astype(np.float64)
scaler = StandardScaler()
# 注意这边的c值
svm_clf1 = LinearSVC(C=1, loss="hinge", random_state=42)
svm_clf2 = LinearSVC(C=100, loss="hinge", random_state=42)
# 特征缩放
scaled_svm_clf1 = Pipeline([
("scaler", scaler),
("linear_svc", svm_clf1),
])
scaled_svm_clf2 = Pipeline([
("scaler", scaler),
("linear_svc", svm_clf2),
])
scaled_svm_clf1.fit(X, y)
scaled_svm_clf2.fit(X, y)
# 在linearSVC中没有自动找支持向量我们需要自己找出
# 获取权重和截距前需要先将标准化的值重新还原
b1 = svm_clf1.decision_function([-scaler.mean_ / scaler.scale_])
b2 = svm_clf2.decision_function([-scaler.mean_ / scaler.scale_])
w1 = svm_clf1.coef_[0] / scaler.scale_
w2 = svm_clf2.coef_[0] / scaler.scale_
svm_clf1.intercept_ = np.array([b1])
svm_clf2.intercept_ = np.array([b2])
svm_clf1.coef_ = np.array([w1])
svm_clf2.coef_ = np.array([w2])
# 成本函数
t = y * 2 - 1
# 找出所有支持向量(w * x0 + w * x1 + b < 1)
support_vectors_idx1 = (t * (X.dot(w1) + b1) < 1).ravel()
support_vectors_idx2 = (t * (X.dot(w2) + b2) < 1).ravel()
svm_clf1.support_vectors_ = X[support_vectors_idx1]
svm_clf2.support_vectors_ = X[support_vectors_idx2]
# 横坐标 X[:, 0][y==1] 等于 先找到所有长度 => 再找到满足y是1的(即标签为维吉尼亚鸢尾)
# 纵坐标同理,非维吉尼亚鸢尾的画法同理
plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^", label="维吉尼亚鸢尾")
plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs", label="非维吉尼亚鸢尾")
# 画出决策边界和支持向量所在的边界
plot_svc_decision_boundary(svm_clf1, 4, 5.9)
# 后面省略一部分的画图步骤
代码量过多,我尽量省略不必要的
图4 不同正则化
我们可以看到当c越大,也就是正则化程度越大时,对于线性SVM分类器的街道的范围越小,增加了c值这也是为了防止过拟合。这里的街道似乎那么明显的将两类分隔开来,而是允许有一部分两类可以在街道内共存,这个就是软间隔分类。不同于硬间隔分类那么无情,非要将两类生死间隔,老死不相往来!