用Scikit-learn和TensorFlow进行机器学习(五)

支持向量机(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。例如添加 x2x3x^2、x^3 特征)
  • 直接采用多项式的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

Gaussian径向基函数:
Φγ(x,l)=e(γxl2)\Phi_\gamma(x,l)=e^{(-\gamma||x-l||^2)}
其中,ll 表示地标,最简单的地标的选择方法:在数据集中的每一个样本的位置创建地标,在转换特征之后,需删除原始特征。缺点:当训练集非常大,转换后特征也非常大。

==》解决方法:高斯 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)mm 为样本个数,nn 为特征数。

SVC 类基于 libsvm 库,支持核技巧。训练时间复杂度:介于 O(m2×n)O(m3×n)O(m^2\times n) 和 O(m^3\times n) 之间。适用于:复杂但小型或中等数量的数据集。可以对特征数量进行缩放,尤其是稀疏特征(sparse features)。

三、SVM回归

SVM也可以用于回归问题,有线性SVM与非线性SVM回归。

SVM回归任务是限制间隔违规情况下,尽量放置更多的样本在“间隔(margin)”上,“间隔(margin)”由超参数 ϵ\epsilon 控制。在间隔之内添加数据样本不会影响模型的预测,因此这个模型认为是不敏感的(ϵinsensitive\epsilon-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理论

(待补充)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章