SVM实现

作者: 单调不减 | 来源:发表于2019-05-18 20:23 被阅读0次

    1、Linear SVC

    支持向量机(SVM)是一种功能强大且功能多样的机器学习模型,能够执行线性或非线性分类,回归,甚至异常值检测。 它是机器学习中最受欢迎的模型之一。SVM特别适用于复杂但小型或中型数据集的分类。

    下面我们尝试在经典的iris数据集上实现SVM,首先是SVC。

    一如既往的,我们先配置环境:

    # To support both python 2 and python 3
    # 让这份笔记同步支持 python 2 和 python 3
    from __future__ import division, print_function, unicode_literals
    
    # Common imports
    import numpy as np
    import os
    
    # to make this notebook's output stable across runs
    # 让笔记全程输入稳定
    np.random.seed(42)
    
    # To plot pretty figures
    # 导入绘图工具
    %matplotlib inline
    import matplotlib
    import matplotlib.pyplot as plt
    plt.rcParams['axes.labelsize'] = 14
    plt.rcParams['xtick.labelsize'] = 12
    plt.rcParams['ytick.labelsize'] = 12
    
    # Where to save the figures
    # 设定图片保存路径,这里写了一个函数,后面直接调用即可
    PROJECT_ROOT_DIR = "C:\Hands-on"
    CHAPTER_ID = "Support Vector Machines"
    IMAGES_PATH = os.path.join(PROJECT_ROOT_DIR, "images", CHAPTER_ID)
    def save_fig(fig_id, tight_layout=True, fig_extension="png", resolution=300):
        path = os.path.join(IMAGES_PATH, fig_id + "." + fig_extension)
        print("Saving figure", fig_id)
        if tight_layout:
            plt.tight_layout()
        plt.savefig(path, format=fig_extension, dpi=resolution)
    
    # Ignore useless warnings (see SciPy issue #5998)
    # 忽略无用警告
    import warnings
    warnings.filterwarnings(action="ignore", message="^internal gelsd")
    

    接下来导入iris数据集,并根据需要取petal length和petal width两个特征,以及0和1两类的数据(即去掉了2类的数据),以便于我们基于二维特征向量做二分类。用sklearn中的SVC建立模型并拟合数据。

    from sklearn.svm import SVC
    from sklearn import datasets
    
    iris = datasets.load_iris()
    X = iris["data"][:, (2, 3)]  # petal length, petal width
    y = iris["target"]
    
    setosa_or_versicolor = (y == 0) | (y == 1)
    X = X[setosa_or_versicolor]
    y = y[setosa_or_versicolor]
    
    # SVM Classifier model
    svm_clf = SVC(kernel="linear", C=float("inf"))
    svm_clf.fit(X, y)
    
    out:
    SVC(C=inf, cache_size=200, class_weight=None, coef0=0.0,
      decision_function_shape='ovr', degree=3, gamma='auto_deprecated',
      kernel='linear', max_iter=-1, probability=False, random_state=None,
      shrinking=True, tol=0.001, verbose=False)
    

    从输出中我们可以看到SVC的参数,其含义可以参考文档https://ogrisel.github.io/scikit-learn.org/sklearn-tutorial/modules/generated/sklearn.svm.SVC.html

    下面我们画出SVC求得的分界线以及几个其他模型产生的分界线,以此证明SVC的优越性。

    # Bad models
    x0 = np.linspace(0, 5.5, 200)
    pred_1 = 5*x0 - 20
    pred_2 = x0 - 1.8
    pred_3 = 0.1 * x0 + 0.5
    
    def plot_svc_decision_boundary(svm_clf, xmin, xmax):
        #coef_代表各特征的权重,这里我们有两个类,所以权重只有一行(因为只有一条分界线)
        #这里coef_[0]就是取第一行的系数,
        w = svm_clf.coef_[0]
        #intercept_代表截距,这里分界线只有一条,因此只有一个截距
        b = svm_clf.intercept_[0]
    
        # At the decision boundary, w0*x0 + w1*x1 + b = 0
        # => x1 = -w0/w1 * x0 - b/w1
        x0 = np.linspace(xmin, xmax, 200)
        decision_boundary = -w[0]/w[1] * x0 - b/w[1]
        #这里margin的分母是w[1]只是因为我们取x1做为纵轴
        #若取x0作为纵轴,则这里分母为w[0]
        margin = 1/w[1]
        gutter_up = decision_boundary + margin
        gutter_down = decision_boundary - margin
        
        svs = svm_clf.support_vectors_
        #使用scatter()函数,并向它传递一对x和y坐标,它将在指定位置绘制一个点
        #这里实际上是绘制出了所有支持向量对应的点
        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)
    
    plt.figure(figsize=(12,2.7))
    
    plt.subplot(121)
    plt.plot(x0, pred_1, "g--", linewidth=2)
    plt.plot(x0, pred_2, "m-", linewidth=2)
    plt.plot(x0, pred_3, "r-", linewidth=2)
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs", label="Iris-Versicolor")
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo", label="Iris-Setosa")
    plt.xlabel("Petal length", fontsize=14)
    plt.ylabel("Petal width", fontsize=14)
    plt.legend(loc="upper left", fontsize=14)
    plt.axis([0, 5.5, 0, 2])
    
    plt.subplot(122)
    plot_svc_decision_boundary(svm_clf, 0, 5.5)
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "bs")
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "yo")
    plt.xlabel("Petal length", fontsize=14)
    plt.axis([0, 5.5, 0, 2])
    
    save_fig("large_margin_classification_plot")
    plt.show()
    

    上面我们在做SVC的过程中直接采用了原数据,这是因为我们选取的特征量纲相同且数值范围类似,事实上数据点在不同的维度上的量纲不同,会使得距离的计算有问题,数据标准化应该作为数据预处理的第一个步骤,否则我们得到的模型效果会很差。下面展示了一个简单的例子:

    import numpy as np
    
    X=np.array([[1,500],[3,300],[2,700],[4,600]])
    y=np.array([1,1,0,0])
    svm_clf = SVC(kernel="linear", C=float("inf"))
    svm_clf.fit(X, y)
    
    plt.figure(figsize=(8,4))
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^", label="1")
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs", label="0")
    plot_svc_decision_boundary(svm_clf, 0, 5)
    plt.xlabel("x0", fontsize=14)
    plt.ylabel("x1", fontsize=14)
    plt.legend(loc="upper left", fontsize=14)
    plt.title("$C = {}$".format(svm_clf.C), fontsize=16)
    plt.axis([0, 5, 0, 1000])
    

    样本的两种特征,如果相差太大,使用 SVM 经过计算得到的决策边界几乎为一条水平的直线——因为两种特征的数据量纲相差太大,水平方向的距离可以忽略,因此,得到的最大的 Margin 就是两条虚线的垂直距离。

    为了避免上述情况的出现,我们应在训练模型前先对训练数据进行标准化。

    我们可以直接对数据进行标准化:

    from sklearn.preprocessing import StandardScaler
    
    standardScaler = StandardScaler()
    #用于计算训练数据的均值和方差, 后面就会用均值和方差来转换训练数据
    standardScaler.fit(X)
    #这一步再用scaler中的均值和方差来转换X,使X标准化
    X_standard = standardScaler.transform(X)
    

    有没有更方便的做法呢?这里我们介绍一下sklearn中的Pipeline,Pipeline 的中间过程由sklearn相适配的转换器(transformer)构成,最后一步是一个estimator(模型)。中间的节点都可以执行fit和transform方法,这样预处理都可以封装进去;最后节点只需要实现fit方法,通常就是我们的模型。

    下面我们用Pipeline将StandardScaler和LinearSVC组合起来:

    scaler=StandardScaler()
    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)
    

    以上代码使用了LinearSVM,LinearSVC和使用SVC且kernel传入linear结果是一致的。但是由于LinearSVC只能计算线性核,而SVC可以计算任意核,所以,他们的底层计算方式不一样,这使得同样使用线性核的SVC,用LinearSVC的计算速度,要比用SVC且kernel传入linear参数快很多。

    然后参数列表中我们使用了hinge loss,并把误差项的系数分别设置为1和100,从而训练两个Soft-Margin SVM模型并进行比较。

    但需要注意的是,我们把数据标准化以后求得的各特征的权重以及截距和用于原数据的权重和截距不同,为了画出分界线,我们需要先把求得的参数值还原成标准化之前的参数值:

    #Convert to unscaled parameters
    
    #decision_function()的功能:计算样本点到分割超平面的函数距离
    #scaler.mean_:每个特征的均值,scaler.scale_:每个特征的标准差
    
    #标准化后模型拟合的分界线方程为w*(x-mean)/scale+b=0
    #要想在原量纲尺度的坐标轴上画图,我们需要调整参数
    #上式变形后得到:w*x/scale+b-w*mean/scale=0
    #所以新的w1=w/scale,新的b1=b-w*mean/scale(这恰好是-scaler.mean_/scaler.scale_到分界线的距离)
    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])
    
    # Find support vectors (LinearSVC does not do this automatically)
    t = y * 2 - 1#(0,1)-->(-1,1)
    #这里求解的支持向量其实是违反了margin的那部分点
    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]
    

    更新好参数,我们就可以画图了:

    plt.figure(figsize=(12,3.2))
    
    plt.subplot(121)
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^", label="Iris-Virginica")
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs", label="Iris-Versicolor")
    plot_svc_decision_boundary(svm_clf1, 4, 6)
    plt.xlabel("Petal length", fontsize=14)
    plt.ylabel("Petal width", fontsize=14)
    plt.legend(loc="upper left", fontsize=14)
    plt.title("$C = {}$".format(svm_clf1.C), fontsize=16)
    plt.axis([4, 6, 0.8, 2.8])
    
    plt.subplot(122)
    plt.plot(X[:, 0][y==1], X[:, 1][y==1], "g^")
    plt.plot(X[:, 0][y==0], X[:, 1][y==0], "bs")
    plot_svc_decision_boundary(svm_clf2, 4, 6)
    plt.xlabel("Petal length", fontsize=14)
    plt.title("$C = {}$".format(svm_clf2.C), fontsize=16)
    plt.axis([4, 6, 0.8, 2.8])
    
    save_fig("regularization_plot")
    
    • 在左侧,使用较低的C值,间隔要大得多,但很多实例最终会出现在间隔之内。
    • 在右侧,使用较高的C值,分类器会减少误分类,最终会有较小间隔。

    2、SVC with kernel

    讨论完线性可分的情形,下面我们要考虑更复杂的情况,许多数据集都不能线性分离。处理非线性数据集的一种方法是添加更多特征,例如多项式特征。在某些情况下,这可能会得到线性可分的数据集。

    我们可以通过一个简单的例子说明这一点:

    X1D = np.linspace(-4, 4, 9).reshape(-1, 1)
    X2D = np.c_[X1D, X1D**2]
    y = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])
    
    plt.figure(figsize=(11, 4))
    
    
    plt.subplot(121)
    #显示网格
    plt.grid(True, which='both')
    #绘制平行于x轴的水平参考线
    #plt.axhline(y=0.0, c="r", ls="--", lw=2)
    #y:水平参考线的出发点
    #c:参考线的线条颜色
    #ls:参考线的线条风格
    #lw:参考线的线条宽度
    plt.axhline(y=0, color='k')
    plt.plot(X1D[:, 0][y==0], np.zeros(4), "bs")
    plt.plot(X1D[:, 0][y==1], np.zeros(5), "g^")
    #ax = plt.gca()获得子图的对象
    #get_yaxis()获取y坐标轴上的值
    #set_ticks可以设置刻度等
    plt.gca().get_yaxis().set_ticks([])
    plt.xlabel(r"$x_1$", fontsize=20)
    plt.axis([-4.5, 4.5, -0.2, 0.2])
    
    
    plt.subplot(122)
    plt.grid(True, which='both')
    plt.axhline(y=0, color='k')
    plt.axvline(x=0, color='k')
    plt.plot(X2D[:, 0][y==0], X2D[:, 1][y==0], "bs")
    plt.plot(X2D[:, 0][y==1], X2D[:, 1][y==1], "g^")
    plt.xlabel(r"$x_1$", fontsize=20)
    plt.ylabel(r"$x_2$", fontsize=20, rotation=0)
    plt.gca().get_yaxis().set_ticks([0, 4, 8, 12, 16])
    plt.plot([-4.5, 4.5], [6.5, 6.5], "r--", linewidth=3)
    plt.axis([-4.5, 4.5, -1, 17])
    
    plt.subplots_adjust(right=1)
    plt.title('Figure 5-5. Adding features to make a dataset linearly separable')
    save_fig("higher_dimensions_plot", tight_layout=False)
    plt.show()
    

    左图表示只有一个特征x_1的简单数据集。可以看到,此数据集不是线性可分的。 但是,如果你添加一个二次特征x_2=x_1^2得到的2D数据集可线性分离

    下面我们对一个线性不可分的数据集moons dataset上测试一下,首先生成数据:

    from sklearn.datasets import make_moons
    #生成半环形图
    X, y = make_moons(n_samples=100, noise=0.15, random_state=42)
    
    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_1$", fontsize=20)
        plt.ylabel(r"$x_2$", fontsize=20, rotation=0)
    
    plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
    plt.show()
    

    接下来我们使用PolynomialFeatures来添加多项式项的特征,并对添加了特征的数据使用LinearSVC训练模型。

    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=100,loss="hinge"))
                                ))
    
    polynomial_svm_clf.fit(X,y)
    

    最后我们将模型得到的分界线可视化:

    def plot_predictions(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.contourf(x0, x1, y_pred, cmap=plt.cm.brg, alpha=0.2)
        plt.contourf(x0, x1, y_decision, cmap=plt.cm.brg, alpha=0.1)
    
    plot_predictions(polynomial_svm_clf, [-1.5, 2.5, -1, 1.5])
    plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
    plt.title('Figure 5-6. Linear SVM classifier using polynomial features')
    
    save_fig("moons_polynomial_svc_plot")
    plt.show()
    

    添加多项式特征很容易实现,并且可以适用于各种类型的机器学习算法(不仅仅是SVM),但是

    • 在低次多项式时它不能处理非常复杂的数据集。
    • 在高次多项式下它会产生大量的特征,使得模型太慢。

    幸运的是,当使用SVM时,我们可以应用核技巧。即使使用非常高的多项式,也无需实际添加它们。因此没有特征数量的组合爆炸。 这个技巧由SVC类实现。 我们在moons dataset上测试它:

    from sklearn.svm import SVC
    
    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)
    
    
    poly100_kernel_svm_clf = Pipeline([
            ("scaler", StandardScaler()),
            ("svm_clf", SVC(kernel="poly", degree=10, coef0=100, C=5))
        ])
    
    poly100_kernel_svm_clf.fit(X, y)
    

    上面的代码使用 3 次多项式内核训练SVM分类器。coef0 表示核函数的常数项(其它参数的意义参考https://www.cnblogs.com/pinard/p/6117515.html),多项式核函数中这个参数对应K(x,z)=(γx∙z+r)^d中的r。一般需要通过交叉验证选择合适的r

    将上面两个模型分类结果可视化:

    plt.figure(figsize=(11, 4))
    
    plt.subplot(121)
    plot_predictions(poly_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
    plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
    plt.title(r"$d=3, r=1, C=5$", fontsize=18)
    
    plt.subplot(122)
    plot_predictions(poly100_kernel_svm_clf, [-1.5, 2.5, -1, 1.5])
    plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
    plt.title(r"$d=10, r=100, C=5$", fontsize=18)
    
    
    save_fig("moons_kernelized_polynomial_svc_plot")
    plt.show()
    

    上面我们直接制定了超参数的值,解决实际问题时这样的模型不见得是最好的,我们需要寻找合适的超参数值,找到正确的超参数值的常用方法是使用网格搜索。

    • 首先进行非常粗略的网格搜索通常会更快,
    • 然后围绕找到的最佳值进行更精细的网格搜索。

    以上就是使用多项式核函数解决非线性可分问题的示范。除了多项式核函数,我们还可以添加使用相似度函数计算的特征,该函数计算每个样本与特定地标(landmark)的相似度。例如,让我们来看看前面讨论过的一维数据集,并在x1=-2和x1=1之间增加两个地标。接下来,我们定义一个相似函数,即高斯径向基函数(RBF),设置γ = 0.3。

    def gaussian_rbf(x, landmark, gamma):
        return np.exp(-gamma * np.linalg.norm(x - landmark, axis=1)**2)
    
    gamma = 0.3
    
    x1s = np.linspace(-4.5, 4.5, 200).reshape(-1, 1)
    x2s = gaussian_rbf(x1s, -2, gamma)
    x3s = gaussian_rbf(x1s, 1, gamma)
    
    XK = np.c_[gaussian_rbf(X1D, -2, gamma), gaussian_rbf(X1D, 1, gamma)]
    yk = np.array([0, 0, 1, 1, 1, 1, 1, 0, 0])
    
    plt.figure(figsize=(11, 4))
    
    plt.subplot(121)
    plt.grid(True, which='both')
    plt.axhline(y=0, color='k')
    plt.scatter(x=[-2, 1], y=[0, 0], s=150, alpha=0.5, c="red")
    plt.plot(X1D[:, 0][yk==0], np.zeros(4), "bs")
    plt.plot(X1D[:, 0][yk==1], np.zeros(5), "g^")
    plt.plot(x1s, x2s, "g--")
    plt.plot(x1s, x3s, "b:")
    plt.gca().get_yaxis().set_ticks([0, 0.25, 0.5, 0.75, 1])
    plt.xlabel(r"$x_1$", fontsize=20)
    plt.ylabel(r"Similarity", fontsize=14)
    #在图形中添加注释,主要是起到提示作用
    plt.annotate(r'$\mathbf{x}$',
                 xy=(X1D[3, 0], 0),
                 xytext=(-0.5, 0.20),
                 ha="center",
                 arrowprops=dict(facecolor='black', shrink=0.1),
                 fontsize=18,
                )
    plt.text(-2, 0.9, "$x_2$", ha="center", fontsize=20)
    plt.text(1, 0.9, "$x_3$", ha="center", fontsize=20)
    plt.axis([-4.5, 4.5, -0.1, 1.1])
    
    plt.subplot(122)
    plt.grid(True, which='both')
    plt.axhline(y=0, color='k')
    plt.axvline(x=0, color='k')
    plt.plot(XK[:, 0][yk==0], XK[:, 1][yk==0], "bs")
    plt.plot(XK[:, 0][yk==1], XK[:, 1][yk==1], "g^")
    plt.xlabel(r"$x_2$", fontsize=20)
    plt.ylabel(r"$x_3$  ", fontsize=20, rotation=0)
    plt.annotate(r'$\phi\left(\mathbf{x}\right)$',
                 xy=(XK[3, 0], XK[3, 1]),
                 xytext=(0.65, 0.50),
                 ha="center",
                 arrowprops=dict(facecolor='black', shrink=0.1),
                 fontsize=18,
                )
    plt.plot([-0.1, 1.1], [0.57, -0.1], "r--", linewidth=3)
    plt.axis([-0.1, 1.1, -0.1, 1.1])
        
    plt.subplots_adjust(right=1)
    
    save_fig("kernel_method_plot")
    plt.show()
    

    从上面的例子可以看出,如何选择地标很关键。最简单的方法是在数据集中每个实例的位置创建一个地标。这样创建了许多维度,从而增加了转换训练集可线性分离的机会。 缺点是具有m个实例和n个特征的训练集被转换为具有m个实例和m个特征的训练集(假设删除了原始特征)。如果训练集非常大,那么最终会获得相同数量的特征。

    就像多项式特征方法一样,相似特征方法可用于任何机器学习算法,但计算所有附加特征可能计算成本很高,特别是在大型训练集上。然而,核技巧可以获得类似的结果,就像添加了许多相似性特征一样,而不必实际添加它们。 接下来我们尝试使用SVC类中的RBF内核。

    from sklearn.svm import SVC
    
    gamma1, gamma2 = 0.1, 5
    C1, C2 = 0.001, 1000
    hyperparams = (gamma1, C1), (gamma1, C2), (gamma2, C1), (gamma2, C2)
    
    svm_clfs = []
    for gamma, C in hyperparams:
        rbf_kernel_svm_clf = Pipeline([
                ("scaler", StandardScaler()),
                ("svm_clf", SVC(kernel="rbf", gamma=gamma, C=C))
            ])
        rbf_kernel_svm_clf.fit(X, y)
        svm_clfs.append(rbf_kernel_svm_clf)
    
    plt.figure(figsize=(11, 7))
    
    for i, svm_clf in enumerate(svm_clfs):
        plt.subplot(221 + i)
        plot_predictions(svm_clf, [-1.5, 2.5, -1, 1.5])
        plot_dataset(X, y, [-1.5, 2.5, -1, 1.5])
        gamma, C = hyperparams[i]
        plt.title(r"$\gamma = {}, C = {}$".format(gamma, C), fontsize=16)
    
    save_fig("moons_rbf_svc_plot")
    plt.show()
    

    上图显示了使用不同的超参数值gammaγ(γ)和 C 训练的模型。

    • 增加 gamma(γ)使钟形曲线变窄,因此每个实例的影响范围都较小:决策边界最终变得更不规则,在个别实例周围摆动。
    • 减少 gamma γ 值使钟形曲线变宽,因此实例具有更大的影响范围,并且决策边界更加平滑。

    所以γ就像一个正则化超参数:

    • 如果你的模型过拟合,你应该减少它,
    • 如果它是欠拟合,你应该增加它(类似于C超参数)。

    我个人的理解是,这里的γ和KNN方法中的K的作用类似。γ设置较大使钟形曲线变窄,就相当于K变小,因为只有距离当前地标(即均值点)最近的那部分点才有比较高的相似性;γ设置较小使钟形曲线变宽,就相当于K变大,因为距离当前地标(即均值点)较近的那部分点都有比较高的相似性。

    3、SVR

    SVM 算法应用广泛:不仅仅支持线性和非线性的分类任务,还支持线性和非线性的回归任务。技巧在于逆转我们的目标:限制间隔违规的情况下,不是试图在两个类别之间找到尽可能大的“街道”(即间隔)。SVM 回归任务是限制间隔违规情况下,尽量放置更多的样本在“街道”上。

    “街道”的宽度由超参数ϵ控制。接下来我们在一些随机生成的线性数据上,展示两个线性 SVR的训练情况。一个有较大的间隔(ϵ=1.5),另一个间隔较小(ϵ=0.5)。

    我们使用Scikit-Learn的LinearSVR类来执行线性SVR。

    首先随机生成有扰动的线性数据:

    np.random.seed(42)
    m = 50
    X = 2 * np.random.rand(m, 1)
    y = (4 + 3 * X + np.random.randn(m, 1)).ravel()
    

    然后我们训练两个线性 SVR,间隔ϵ分别设置为1.5和0.5,并找到所有“街道”外的样本点,记录下其索引。

    from sklearn.svm import LinearSVR
    
    svm_reg1 = LinearSVR(epsilon=1.5, random_state=42)
    svm_reg2 = LinearSVR(epsilon=0.5, random_state=42)
    svm_reg1.fit(X, y)
    svm_reg2.fit(X, y)
    
    def find_support_vectors(svm_reg, X, y):
        y_pred = svm_reg.predict(X)
        off_margin = (np.abs(y - y_pred) >= svm_reg.epsilon)
        #返回非0的数组元组的索引
        return np.argwhere(off_margin)
    
    svm_reg1.support_ = find_support_vectors(svm_reg1, X, y)
    svm_reg2.support_ = find_support_vectors(svm_reg2, X, y)
    

    将上述结果可视化:

    def plot_svm_regression(svm_reg, X, y, axes):
        x1s = np.linspace(axes[0], axes[1], 100).reshape(100, 1)
        y_pred = svm_reg.predict(x1s)
        plt.plot(x1s, y_pred, "k-", linewidth=2, label=r"$\hat{y}$")
        plt.plot(x1s, y_pred + svm_reg.epsilon, "k--")
        plt.plot(x1s, y_pred - svm_reg.epsilon, "k--")
        #s表示点的轮廓的宽窄,facecolors表示点的轮廓颜色
        plt.scatter(X[svm_reg.support_], y[svm_reg.support_], s=200, facecolors='#FFAAAA')
        plt.plot(X, y, "bo")
        plt.xlabel(r"$x_1$", fontsize=18)
        plt.legend(loc="upper left", fontsize=18)
        plt.axis(axes)
    
    plt.figure(figsize=(9, 4))
    plt.subplot(121)
    plot_svm_regression(svm_reg1, X, y, [0, 2, 3, 11])
    plt.title(r"$\epsilon = {}$".format(svm_reg1.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_reg1.epsilon),
            textcoords='data', arrowprops={'arrowstyle': '<->', 'linewidth': 1.5}
        )
    plt.text(0.91, 5.6, r"$\epsilon$", fontsize=20)
    plt.subplot(122)
    plot_svm_regression(svm_reg2, X, y, [0, 2, 3, 11])
    plt.title(r"$\epsilon = {}$".format(svm_reg2.epsilon), fontsize=18)
    save_fig("svm_regression_plot")
    plt.show()
    

    可以看到,添加更多的数据样本在间隔之内并不太会影响模型的预测,因此,这个模型认为是不敏感的(ϵ-insensitive)。

    要处理非线性回归任务,我们依然使用Scikit-Learn的SVR类,和SVC类似,我们要添加非线性的内核并通过C进行正则化。

    #生成数据
    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()
    
    #同时训练两个模型
    from sklearn.svm import SVR
    
    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=(9, 4))
    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)
    save_fig("svm_with_polynomial_kernel_plot")
    plt.show()
    

    左图几乎没有正则化(即大的C值),右图有更多的正则化(即小的C值)。从图形来看左图效果更佳,这是很自然的,因为我们的核函数是二次的,而样本本身就是二次函数上的点加随机扰动,所以模型是不需要正则化的。

    相关文章

      网友评论

          本文标题:SVM实现

          本文链接:https://www.haomeiwen.com/subject/cgjnaqtx.html