文章目录
支持向量机(SVM)
可做线性或者非线性的分类、回归,甚至异常值检测。
适用于:复杂但中小规模数据集的分类问题。
一、线性支持向量机分类
1、硬间隔分类
左图虽红线和紫线都可以区分不同类别,但是两条直线都非常靠近样本,如果有新的样本加入,有比较大的可能会分类错误。
右图为线性SVM结果,其的**思想:决策边界在正确分类的同时离最近的样本尽可能的远。**而这些最近的样本(途中虚线上的点)即为支持向量(support vector)。因此只要没有点在这些点划分的区域之间,决策边界就只由这些支持向量所决定。
注意:SVM对特征缩放敏感==》需先进行
不足:
- 只适用于线性可分数据集;
- 对异常值敏感,eg:如下图所示
2、软间隔分类
在sklearn中的SVM类,用 C 超参数(惩罚系数,松弛因子)
来控制软的程度:较小的 C
会导致更大的 “间隔”,但更多的“间隔”违规。
实现:鸢尾花( Iris) 数据集, 缩放特征, 并训练一个线性 SVM 模型( 使用 LinearSVC 类, 超参数 C=1 , hinge 损失函数) 来检测 Virginica 鸢尾花, 生成的模型。
import numpy as np
from sklearn import datasets
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import LinearSVC
iris = datasets.load_iris()
x = iris["data"][:,(2, 3)] # petal length, petal width
y = (iris["target"] == 2).astype(np.float64)
svm_clf = Pipeline((
("scaler", StandardScaler()),
("linear_svc",LinearSVC(C=1, loss="hinge"))
))
svm_clf.fit(x, y)
predict = svm_clf.predict([[5.5, 1.7]])
print(predict)
输出结果
[1.]
注意:
- SVM分类器不像 Logistic 回归分类器一样有 predict_proba() 方法来计算得分(概率),因为SVM只是靠支持向量来构建决策线。
- loss函数一定要记得填
hinge
,因为默认不是hinge
。
不同实现:
- SVC类
svc(kernel="linear", C=1)
,在较大训练集上慢,不推荐; - SGDClassifier 类,
SGDClassifier(loss="hinge", alpha=1/(m*C))
,这种使用随机梯度下降方法来训练SVM,虽然没有LinearSVC收敛快,但是能够处理数据量庞大的训练集,而且能够在线学习。
二、非线性支持向量机分类
处理非线性数据集方法:
- 增加更多的特征(eg:多项式特征,然后采用线性SVM。例如添加 x 2 、 x 3 x^2、x^3 x2、x3 特征)
- 直接采用多项式的kernel,直接进行非线性SVM的分类。
注意:
- SVC中的
参数C
越大,对于训练集来说,其误差越小,但是很容易发生过拟合;C
越小,则允许有更多的训练集误分类,相当于soft margin。 - SVC中的
参数coef0
反映了高阶多项式相对于低阶多项式对模型的影响,如果发生了过拟合的现象,则可以减小coef0
;如果发生了欠拟合的现象,可以试着增大coef0
1、多项式核(Polynomial Kernel)
from sklearn.datasets import make_moons
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import PolynomialFeatures
polynomial_svm_clf = Pipeline((
# 多项式特征
("poly_features", PolynomialFeatures(degree=3)),
("scaler", StandardScaler()),
("svm_clf", LinearSVC(C=10, loss="hinge"))
))
polynomial_svm_clf.fit(X, y)
增加多项式特征来进行非线性分类。但是如果degree设置的比较小,则比较难分类比较复杂的数据;如果degree设置的比较大,产生了大量模型,导致训练的非常慢。
解决方法:“核技巧(Kernel Trick)”
使用 3 阶( degree=3
)的多项式核训练一个SVM分类器,超参数 coef0
控制I高阶多项式与低阶多项式对模型的影响。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVC
from sklearn.datasets import make_moons
def plot_dataset(X, y, axes):
plt.plot( x[:,0][y==0], x[:,1][y==0], "bs" )
plt.plot( x[:,0][y==1], x[:,1][y==1], "g^" )
plt.axis( axes )
plt.grid( True, which="both" )
plt.xlabel(r"$x_l$")
plt.ylabel(r"$x_2$")
# contour函数是画出轮廓,需要给出X和Y的网格,以及对应的Z,它会画出Z的边界(相当于边缘检测及可视化)
def plot_predict(clf, axes):
x0s = np.linspace(axes[0], axes[1], 100)
x1s = np.linspace(axes[2], axes[3], 100)
x0, x1 = np.meshgrid( x0s, x1s )
X = np.c_[x0.ravel(), x1.ravel()]
y_pred = clf.predict( X ).reshape( x0.shape )
y_decision = clf.decision_function( X ).reshape( x0.shape )
plt.contour( x0, x1, y_pred, cmap=plt.cm.winter, alpha=0.5 )
plt.contour( x0, x1, y_decision, cmap=plt.cm.winter, alpha=0.2 )
x, y = make_moons(n_samples=100, noise=0.15, random_state=2019)
poly_kernel_svm_clf = Pipeline((
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=5))
))
poly_kernel_svm_clf.fit(x, y)
plot_dataset( x, y, [-1.5, 2.5, -1, 1.5])
plot_predict(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plt.show()
输出结果
==》超参数优化:网格搜素
不同参数的影响
from sklearn.svm import SVC
poly_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=0.5))
])
poly_kernel_svm_clf.fit(x, y)
plt.figure(figsize=(9, 3))
plt.subplot(131)
plot_dataset(x, y, [-1.5, 2.5, -1, 1.5])
plot_predict(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
poly_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=1, C=10))
])
poly_kernel_svm_clf.fit(x, y)
plt.subplot(132)
plot_dataset(x, y, [-1.5, 2.5, -1, 1.5])
plot_predict(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
poly_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="poly", degree=3, coef0=100, C=0.5))
])
poly_kernel_svm_clf.fit(x, y)
plt.subplot(133)
plot_dataset(x, y, [-1.5, 2.5, -1, 1.5])
plot_predict(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plt.show()
2、增加相似特征
思路:使用相似函数(similarity function)计算每个样本与特定地表(landmark)的相似度。
此处,定义相似函数:高斯径向基函数(Gaussian Radial Basis Function,RBF),设置
γ
=
0.3
\gamma=0.3
γ=0.3
Gaussian径向基函数:
Φ
γ
(
x
,
l
)
=
e
(
−
γ
∣
∣
x
−
l
∣
∣
2
)
\Phi_\gamma(x,l)=e^{(-\gamma||x-l||^2)}
Φγ(x,l)=e(−γ∣∣x−l∣∣2)
其中,
l
l
l 表示地标,最简单的地标的选择方法:在数据集中的每一个样本的位置创建地标,在转换特征之后,需删除原始特征。缺点:当训练集非常大,转换后特征也非常大。
==》解决方法:高斯 RBF 核
当数据在低维空间中不可分割的时候,可以尝试将它们映射到高维空间,通过核函数来进行这样的映射操作。
rbf_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="rbf", gamma=5, C=0.001))
])
rbf_kernel_svm_clf.fit(x, y)
plt.figure(figsize=(6, 3))
plt.subplot(121)
rbf_kernel_svm_clf.fit(x, y)
plot_dataset(x, y, [-1.5, 2.5, -1, 1.5])
plot_predict(rbf_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
rbf_kernel_svm_clf = Pipeline([
("scaler", StandardScaler()),
("svm_clf", SVC(kernel="rbf", gamma=0.1, C=0.001))
])
plt.subplot(122)
rbf_kernel_svm_clf.fit(x, y)
plot_dataset(x, y, [-1.5, 2.5, -1, 1.5])
plot_predict(rbf_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
plt.
plt.show()
增大 γ
使钟型曲线更窄) , 导致每个样本的影响范围变得更小: 即判定边界最终变得更不规则, 在单个样本周围环绕。 相反的, 较小的 γ
值使钟型曲线更宽, 样本有更大的影响范围, 判定边界最终则更加平滑。
==》 γ
是可调整的超参数: 如果模型过拟合, 你应该减小 γ
值, 若欠拟合, 则增大 γ
( 与超参数 C
相似) 。
其他核函数:字符串核(String Kernels)、SSK核、编辑距离的核函数。
核函数的选择:一般先尝试线性核函数(Linear比SVC(kernel=“linear”)要快得多),尤其是训练集很大或有大量特征的情况下。若训练集不太大,则尝试高斯径向基核,其对大多数情况下都很有效。
3、计算复杂度
LinearSVC
类基于 liblinear
库,是线性 SVM 的优化算法,不支持核技巧,训练时间复杂度大约为
O
(
m
×
n
)
O(m × n)
O(m×n),
m
m
m 为样本个数,
n
n
n 为特征数。
SVC
类基于 libsvm
库,支持核技巧。训练时间复杂度:介于
O
(
m
2
×
n
)
和
O
(
m
3
×
n
)
O(m^2\times n) 和 O(m^3\times n)
O(m2×n)和O(m3×n) 之间。适用于:复杂但小型或中等数量的数据集。可以对特征数量进行缩放,尤其是稀疏特征(sparse features)。
三、SVM回归
SVM也可以用于回归问题,有线性SVM与非线性SVM回归。
SVM回归任务是限制间隔违规情况下,尽量放置更多的样本在“间隔(margin)”上,“间隔(margin)”由超参数 ϵ \epsilon ϵ 控制。在间隔之内添加数据样本不会影响模型的预测,因此这个模型认为是不敏感的( ϵ − i n s e n s i t i v e \epsilon-insensitive ϵ−insensitive)
1、线性SVM回归
np.random.seed(42)
m = 50
X = 2 * np.random.rand(m, 1)
y = (4 + 3 * X + np.random.randn(m, 1)).ravel()
## 找到训练集中所有支持向量的下标
def find_support_vectors(svm_reg, X, y):
y_pred = svm_reg.predict(X)
off_margin = np.abs(y - y_pred) >= svm_reg.epsilon
## 返回 off_margin 中值为 True 的下标
return np.argwhere(off_margin)
def plot_svm_regression(svm_reg, X, y, axes):
x1s = np.linspace(axes[0], axes[1], 100).reshape(-1, 1)
y_pred = svm_reg.predict(x1s)
plt.plot(x1s, y_pred, "r-", linewidth=2, label="$\hat{y}$")
plt.plot(x1s, y_pred - svm_reg.epsilon, "k--")
plt.plot(x1s, y_pred + svm_reg.epsilon, "k--")
plt.plot(X, y, "bo")
plt.scatter(X[svm_reg.support_], y[svm_reg.support_], s=180, facecolors="#FFAAAA")
plt.xlabel(r"$x_1$", fontsize=18)
plt.legend(loc="upper left", fontsize=18)
plt.axis(axes)
svm_reg_1 = LinearSVR(epsilon=1.5, random_state=2019)
svm_reg_2 = LinearSVR(epsilon=0.5, random_state=2019)
svm_reg_1.fit(X, y)
svm_reg_2.fit(X, y)
svm_reg_1.support_ = find_support_vectors(svm_reg_1, X, y)
svm_reg_2.support_ = find_support_vectors(svm_reg_2, X, y)
eps_x1 = 1
eps_y_pred = svm_reg_1.predict([[eps_x1]])
plt.figure(figsize=(8, 3))
plt.subplot(121)
plot_svm_regression(svm_reg_1, X, y, [0, 2, 3, 11])
plt.title(r"$\epsilon={}$".format(svm_reg_1.epsilon), fontsize=18)
plt.ylabel(r"$y$", fontsize=18, rotation=0)
plt.annotate(
'', xy=(eps_x1, eps_y_pred), xycoords='data',
xytext=(eps_x1, eps_y_pred - svm_reg_1.epsilon),
textcoords='data', arrowprops={'arrowstyle':'<->','linewidth':1.5}
)
plt.text(0.9, 5.6, r"$\epsilon$",fontsize=20)
plt.subplot(122)
plot_svm_regression(svm_reg_2, X, y, [0, 2, 3, 11])
plt.title(r"$\epsilon={}$".format(svm_reg_2.epsilon), fontsize=18)
plt.show()
2、非线性SVM回归
多项式回归,指定SVM的kernel为poly即可
from sklearn.svm import SVR
np.random.seed(42)
m = 100
X = 2 * np.random.rand(m, 1) - 1
y = (0.2 + 0.1 * X + 0.5 * X ** 2 + np.random.randn(m, 1)/10).ravel()
#
svm_poly_reg1 = SVR(kernel="poly", degree=2, C=100, epsilon=0.1)
svm_poly_reg2 = SVR(kernel="poly", degree=2, C=0.01, epsilon=0.1)
svm_poly_reg1.fit(X, y)
svm_poly_reg2.fit(X, y)
plt.figure(figsize=(8, 3))
plt.subplot(121)
plot_svm_regression(svm_poly_reg1, X, y, [-1, 1, 0, 1])
plt.title(r"$degree={}, C={}, \epsilon={}$".format(svm_poly_reg1.degree, svm_poly_reg1.C, svm_poly_reg1.epsilon), fontsize=18)
plt.ylabel(r"$y$", fontsize=18, rotation=0)
plt.subplot(122)
plot_svm_regression(svm_poly_reg2, X, y, [-1, 1, 0, 1])
plt.title(r"$degree={}, C={}, \epsilon={}$".format(svm_poly_reg2.degree, svm_poly_reg2.C, svm_poly_reg2.epsilon), fontsize=18)
plt.show()
四、SVM理论
(待补充)