支持向量机(SVM)是非常强大的机器学习算法,支持线性和非线性的分类任务、回归任务,甚至可以完成离群值检测任务。SVM是机器学习最受欢迎的算法之一,特别适合于比较复杂的中小型数据集上进行建模,如果数据集比较大SVM就显得比较吃力。
SVM的核心思想是:生成一条决策边界,使得决策边界离最近点的距离越远越好。
输出:
numpy: 1.17.4 matplotlib: 3.1.2 sklearn: 0.21.3输出:
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) 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): w = svm_clf.coef_[0] 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 = 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) fig, axes = plt.subplots(ncols=2, figsize=(12,5), sharey=True) plt.sca(axes[0]) plt.plot(x0, pred_1, "r--", linewidth=2) plt.plot(x0, pred_2, "g-", linewidth=2) plt.plot(x0, pred_3, "g-", 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.sca(axes[1]) 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]) plt.tight_layout() plt.show()输出:
从上图可以看出,两个类别的鸢尾花是线性可分的,即通过一条直线就可以将两种鸢尾花分开。
左图:两条绿色实线能将两个类别正确地分开,但是红色虚线分类是错误的,没有很好地把两类分开。
右图:黑色实线将两类很好地分开,这正是SVM模型所产生的决策边界。这条实现不仅将两个类别正确地分开了,而且这条实线尽可能离最近的点距离最远。
SVM模型中决策边界由间隔边界上的点决定,加入非间隔边界上的数样本不会对决策边界产生影响,而这些间隔边界上的点(样本)组成的向量叫支持向量。
注意:SVM对特征归一化特别敏感,因此使用SVM算法训练模型前一定要对数据进行归一化预处理。未进行归一化和归一化后SVM模型的差别如下图所示:
Xs = np.array([[1, 50], [5, 20], [3, 80], [5, 60]]).astype(np.float64) ys = np.array([0, 0, 1, 1]) svm_clf = SVC(kernel="linear", C=100) # 参数C:正则化系数 svm_clf.fit(Xs, ys) plt.figure(figsize=(12,5)) plt.subplot(121) plt.plot(Xs[:, 0][ys==1], Xs[:, 1][ys==1], "bo") plt.plot(Xs[:, 0][ys==0], Xs[:, 1][ys==0], "ms") plot_svc_decision_boundary(svm_clf, 0, 6) plt.xlabel("$x_0$", fontsize=20) plt.ylabel("$x_1$ ", fontsize=20, rotation=0) plt.title("Unscaled", fontsize=16) plt.axis([0, 6, 0, 90]) from sklearn.preprocessing import StandardScaler scaler = StandardScaler() X_scaled = scaler.fit_transform(Xs) svm_clf.fit(X_scaled, ys) plt.subplot(122) plt.plot(X_scaled[:, 0][ys==1], X_scaled[:, 1][ys==1], "bo") plt.plot(X_scaled[:, 0][ys==0], X_scaled[:, 1][ys==0], "ms") plot_svc_decision_boundary(svm_clf, -2, 2) plt.xlabel("$x_0$", fontsize=20) plt.ylabel("$x'_1$ ", fontsize=20, rotation=0) plt.title("Scaled", fontsize=16) plt.axis([-2, 2, -2, 2]) plt.tight_layout() plt.show()输出:
硬间隔分类:严格要求所有样本都必须在间隔之外。这时就有两个问题:一是这种模型只能用于严格线性可分的数据,二是这种模型对离群值异常敏感。
软间隔分类:容忍一定数量的样本位于分类间隔中,这样可以增加模型的泛化能力和模型的应用场景。
X_outliers = np.array([[3.4, 1.3], [3.2, 0.8]]) y_outliers = np.array([0, 0]) Xo1 = np.concatenate([X, X_outliers[:1]], axis=0) yo1 = np.concatenate([y, y_outliers[:1]], axis=0) Xo2 = np.concatenate([X, X_outliers[1:]], axis=0) yo2 = np.concatenate([y, y_outliers[1:]], axis=0) svm_clf2 = SVC(kernel="linear", C=10**9) svm_clf2.fit(Xo2, yo2) fig, axes = plt.subplots(ncols=2, figsize=(12,5), sharey=True) plt.sca(axes[0]) plt.plot(Xo1[:, 0][yo1==1], Xo1[:, 1][yo1==1], "bs") plt.plot(Xo1[:, 0][yo1==0], Xo1[:, 1][yo1==0], "yo") plt.text(0.3, 1.0, "Impossible!", fontsize=24, color="red") plt.xlabel("Petal length", fontsize=14) plt.ylabel("Petal width", fontsize=14) plt.annotate("Outlier", xy=(X_outliers[0][0], X_outliers[0][1]), xytext=(2.5, 1.7), ha="center", arrowprops=dict(facecolor='black', shrink=0.1), fontsize=16, ) plt.axis([0, 5.5, 0, 2]) plt.sca(axes[1]) plt.plot(Xo2[:, 0][yo2==1], Xo2[:, 1][yo2==1], "bs") plt.plot(Xo2[:, 0][yo2==0], Xo2[:, 1][yo2==0], "yo") plot_svc_decision_boundary(svm_clf2, 0, 5.5) plt.xlabel("Petal length", fontsize=14) plt.annotate("Outlier", xy=(X_outliers[1][0], X_outliers[1][1]), xytext=(3.2, 0.08), ha="center", arrowprops=dict(facecolor='black', shrink=0.1), fontsize=16, ) plt.axis([0, 5.5, 0, 2]) plt.tight_layout() plt.show()输出:
如上所示,左图中有一个黄色离群点,此时就无法用硬间隔SVM进行分类。而右图中也存在一个黄色离群点,但幸好可以用硬间隔SVM分类,但是与之前的图进行对比发现,决策间隔变小了很多很多,也就是说模型的泛化能力下降很多。
以上这种存在离群值的数据是日常实际应用是更常见的情况,而解决这种问题软间隔SVM分类就比较拿手。
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)] # 选择花瓣长度和宽度属性 y = (iris["target"]==2).astype(np.float64) svm_clf = Pipeline([ ("scaler",StandardScaler()), ("linear_svc",LinearSVC(C=1,loss="hinge",random_state=42)) ]) svm_clf.fit(X,y)输出:
Pipeline(memory=None, steps=[('scaler', StandardScaler(copy=True, with_mean=True, with_std=True)), ('linear_svc', LinearSVC(C=1, class_weight=None, dual=True, fit_intercept=True, intercept_scaling=1, loss='hinge', max_iter=1000, multi_class='ovr', penalty='l2', random_state=42, tol=0.0001, verbose=0))], verbose=False) svm_clf.predict([[5.5,1.7]])输出:
array([1.])与逻辑回归分类器不一样,SVM只输出最终的类别,不会输出属于每个类别的概率。
注意:LinearSVC同样会对偏置项进行正则化,因此需要对训练集先做减平均值的操作,这种操作由StandardScaler自动完成。同时需要注意需要将超参数loss的值设置为hinge。最后为了达到更好的性能和效果,需要将超参数dual设置为False,除非训练集中特征数目大于训练样本数。
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)输出:
Pipeline(memory=None, steps=[('scaler', StandardScaler(copy=True, with_mean=True, with_std=True)), ('linear_svc', LinearSVC(C=100, class_weight=None, dual=True, fit_intercept=True, intercept_scaling=1, loss='hinge', max_iter=1000, multi_class='ovr', penalty='l2', random_state=42, tol=0.0001, verbose=0))], verbose=False) # Convert to unscaled parameters 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 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] fig, axes = plt.subplots(ncols=2, figsize=(12,5), sharey=True) plt.sca(axes[0]) 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, 5.9) 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, 5.9, 0.8, 2.8]) plt.sca(axes[1]) 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, 5.99) plt.xlabel("Petal length", fontsize=14) plt.title("$C = {}$".format(svm_clf2.C), fontsize=16) plt.axis([4, 5.9, 0.8, 2.8]) plt.tight_layout() plt.show()输出:
sklearn超参数C指定了这种容忍的程度,C值越小,容忍度越大,反之越小。注意:如果SVM过拟合,可以通过减小C值调整模型。
虽然SVM线性分类器效果很好,但是实际应用中有些数据往往是线性不可分的。解决线性不可分问题的办法之一是添加更多的特征,例如添加多项式特征。
输出:
观察上面左图,数据只有一个特征x1,分别有绿色和蓝色两类点,可以看出无法用一条直线将它们分开。
如果增加一个特征,即x1的平方,此时再观察右图就可以发现这两类点可以用一条直线分开。
感觉增加多项式特征在解决线性不可分问题还是很有用的,现在尝试将其运用在moon数据集上:
from sklearn.datasets import make_moons from sklearn.pipeline import Pipeline from sklearn.preprocessing import PolynomialFeatures X,y = make_moons(n_samples=100, noise=0.15, random_state=42) def plot_dataset(X,y,axes): #plt.figure(figsize=(8,5)) 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("$x_1$",fontsize=20) plt.ylabel("$x_2$",fontsize=20,rotation=0) plot_dataset(X,y,[-1.5,2.5,-1,1.5]) plt.show()输出:
polynomial_svm_clf = Pipeline([ ("poly_features",PolynomialFeatures(degree=3)), ("scaler",StandardScaler()), ("svm_clf",LinearSVC(C=10,loss="hinge", random_state=42)) ]) polynomial_svm_clf.fit(X,y)输出:
Pipeline(memory=None, steps=[('poly_features', PolynomialFeatures(degree=3, include_bias=True, interaction_only=False, order='C')), ('scaler', StandardScaler(copy=True, with_mean=True, with_std=True)), ('svm_clf', LinearSVC(C=10, class_weight=None, dual=True, fit_intercept=True, intercept_scaling=1, loss='hinge', max_iter=1000, multi_class='ovr', penalty='l2', random_state=42, tol=0.0001, verbose=0))], verbose=False) 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.show()输出:
从上述输出结果可以看出,线性不可分的二分类问题经过添加多项式特征后变得可分了。
添加多项式特征的方法比较简单,而且在很多机器学习算法中都可以使用。但是如果多项式次数不够高,则无法处理比较复杂的数据,如果次数太高,则会产生大量的特征从而导致模型训练会很慢很慢。
然而为了解决如上的矛盾,SVM中存在kernel trick(核技巧)的方法,核技巧可以达到添加高次多项式的效果,但不会真正添加到数据中,所以也就不存在维数爆炸的现象。
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)输出:
Pipeline(memory=None, steps=[('scaler', StandardScaler(copy=True, with_mean=True, with_std=True)), ('svm_clf', SVC(C=5, cache_size=200, class_weight=None, coef0=1, decision_function_shape='ovr', degree=3, gamma='auto_deprecated', kernel='poly', max_iter=-1, probability=False, random_state=None, shrinking=True, tol=0.001, verbose=False))], verbose=False) 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)输出:
Pipeline(memory=None, steps=[('scaler', StandardScaler(copy=True, with_mean=True, with_std=True)), ('svm_clf', SVC(C=5, cache_size=200, class_weight=None, coef0=100, decision_function_shape='ovr', degree=10, gamma='auto_deprecated', kernel='poly', max_iter=-1, probability=False, random_state=None, shrinking=True, tol=0.001, verbose=False))], verbose=False) fig, axes = plt.subplots(ncols=2,figsize=(12,5),sharey=True) plt.sca(axes[0]) plot_predictions(poly_kernel_svm_clf,[-1.5,2.45,-1,1.5]) plot_dataset(X,y,[-1.5,2.4,-1,1.5]) plt.title(r"$d=3, r=1, C=5$",fontsize=18) plt.sca(axes[1]) plot_predictions(poly100_kernel_svm_clf, [-1.5,2.45,-1,1.5]) plot_dataset(X,y,[-1.5,2.45,-1,1.5]) plt.title(r"$d=10, r=100, C=5$",fontsize=18) plt.ylabel("") plt.tight_layout() plt.show()输出:
如上图所示,左边是利用SVM三次多项式核训练的模型效果,右边是10次多项式核模型。观察发现,如果模型过拟合,就需要降低degree的值,反之如果欠拟合,就需要增大degree的值。
超参数coef0控制模型受高次多项式和低次多项式影响的程度。
上面介绍了解决线性不可分问题的解决方法之一,即添加多项式特征。
解决线性不可分问题另一个方法是添加利用相似函数计算的特征。 相似函数计算样本与特定目标相似的程度。
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=(12, 5)) 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) plt.tight_layout() plt.show()输出:
上图左图所示,我们分别在x=-2和x=1处添加两个landmarks。
此时,定义相似函数为高斯径向基函数(Gaussian Radial Basis Function,RBF),𝛾=0.3:
x1_example = X1D[3,0] for landmark in (-2,1): k = gaussian_rbf(np.array([[x1_example]]),np.array([[landmark]]),gamma) print("Phi({},{})={}".format(x1_example,landmark,k))
输出:
Phi(-1.0,-2)=[0.74081822] Phi(-1.0,1)=[0.30119421]对于x=-1的样本点,离左边X2距离为1,离X3距离为2,因此新生成的特征给x2=exp(-0.3*0.1^2)=0.74,同理x3=0.30。右图所示即为已经转换后的数据分布情况。此时可以发现,蓝色点和绿色点已经是线性可分的了。
上述做法很巧妙,但是如果选择或确定landmark呢?最简单的办法就是在训练集中每个样本的位置都创建一个landmark。这样做增加了数据线性可分的概率,但是做也有缺点,即增加了大量维数,对m个样本n个特征的数据处理后变成m个样本m个特征的数据集。
输出:
Pipeline(memory=None, steps=[('scaler', StandardScaler(copy=True, with_mean=True, with_std=True)), ('svm_clf', SVC(C=0.001, cache_size=200, class_weight=None, coef0=0.0, decision_function_shape='ovr', degree=3, gamma=5, kernel='rbf', max_iter=-1, probability=False, random_state=None, shrinking=True, tol=0.001, verbose=False))], verbose=False) 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) fig, axes = plt.subplots(nrows=2, ncols=2, figsize=(15,10),sharex=True, sharey=True) for i, svm_clf in enumerate(svm_clfs): plt.sca(axes[i//2,i%2]) plot_predictions(svm_clf,[-1.5, 2.45, -1, 1.5]) plot_dataset(X,y,[-1.5, 2.45, -1, 1.5]) gamma,C = hyperparams[i] plt.title(r"$\gamma = {},C={}$".format(gamma,C),fontsize=16) if i in (0,1): plt.xlabel("") if i in (1,3): plt.ylabel("") plt.tight_layout() plt.show()输出:
如上所示为不同gamma值对决策边界的影响。可以看到gamma值越小,决策边界越平滑。观察分析发现,当模型过拟合时可以尝试减小gamma值,当模型欠拟合时,可以适当增大gamma值。
SVM中存在其它的核,例如String核,常用于文本数据或DNA数据的分类。但最常用的还是高斯核。
在建模时如何选择核呢?首先应该考虑线性核(LinearSVC永远比SVC(kernel="linear")运行快),尤其是数据集比较大或者特征非常多的时候。其次,如果训练集不是很大可以尝试高斯径向基核,通常情况下效果还不错。
LinearSVC类基于liblinear库,是线性SVM优化的库,虽然不支持核技巧,但复杂度几乎与数据样本数量和特征数量呈线性关系,复杂度大概为O(m*n)。
SVC类基于libsvm库,支持核技巧,复杂度通常在O(m^2n)到O(m^3n)之间。比较适合中小型数据集上建模,如果数据量较大时则为非常的可怕,运行非常慢。
超参数tol,即容忍因子,对于大部分分类任务,sklearn中的工具中默认值都是优化过的。
如前所述,SVM用途广泛,不仅支持线性、非线性分类任务,还支持线性、非线性回归任务。
与分类任务不一样,SVM回归要求所有样本尽可能地落在决策间隔内,并且使得间隔越小越好。同样,只有位于决策间隔上的点对决策边界有影响,添加更多的非决策间隔上的样本点对结果没影响。
注意:使用SVR建模前数据同样需要做归一化和中心化。
np.random.seed(42) m = 50 X = 2*np.random.rand(m,1) y = (4+3*X+np.random.randn(m,1)).ravel() X.shape, y.shape输出:
((50, 1), (50,)) from sklearn.svm import LinearSVR svm_reg = LinearSVR(epsilon=1.5, random_state=42) svm_reg.fit(X, y)输出:
LinearSVR(C=1.0, dual=True, epsilon=1.5, fit_intercept=True, intercept_scaling=1.0, loss='epsilon_insensitive', max_iter=1000, random_state=42, tol=0.0001, verbose=0) eps_x1 = 1 eps_y_pred = svm_reg.predict([[eps_x1]]) eps_y_pred输出:
array([6.52640746])用训练好的SVM回归模型进行预测,结果值还不错。
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) 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--") plt.scatter(X[svm_reg.support_],y[svm_reg.support_],s=200,facecolors="red") plt.plot(X,y,"bo") plt.xlabel(r"$x_1$",fontsize=18) plt.legend(fontsize=18) plt.axis(axes) fig, axes = plt.subplots(ncols=2, figsize=(12,5),sharey=True) plt.sca(axes[0]) plot_svm_regression(svm_reg1, X, y, [0,2,3,11]) plt.ylabel(r"$y$",fontsize=18, rotation=0) plt.title(r"$\epsilon={}$".format(svm_reg1.epsilon),fontsize=18) 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=25) plt.sca(axes[1]) plot_svm_regression(svm_reg2, X, y, [0,2,3,11]) plt.title("$\epsilon={}$".format(svm_reg2.epsilon),fontsize=18) plt.tight_layout() plt.show()输出:
如上结果显示,epsilon越大,决策间隔越大,反之越小。
对于非线性回归任务,可以使用带核处理的SVM算法进行建模:
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() X.shape, y.shape输出:
((100, 1), (100,)) from sklearn.svm import SVR svm_poly_reg = SVR(kernel="poly",degree=2,C=100,epsilon=0.1, gamma="scale") svm_poly_reg.fit(X,y)输出:
SVR(C=100, cache_size=200, coef0=0.0, degree=2, epsilon=0.1, gamma='scale', kernel='poly', max_iter=-1, shrinking=True, tol=0.001, verbose=False) svm_poly_reg1 = SVR(kernel="poly", degree=2, C=100, epsilon=0.1, gamma="scale") svm_poly_reg2 = SVR(kernel="poly", degree=2, C=0.01, epsilon=0.1, gamma="scale") svm_poly_reg1.fit(X,y) svm_poly_reg2.fit(X,y)输出:
SVR(C=0.01, cache_size=200, coef0=0.0, degree=2, epsilon=0.1, gamma='scale', kernel='poly', max_iter=-1, shrinking=True, tol=0.001, verbose=False) fit, axes = plt.subplots(ncols=2, figsize=(12,5),sharey=True) plt.sca(axes[0]) 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.sca(axes[1]) 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.tight_layout() plt.show()输出:
SVM算法原理及详细推导过程见:https://blog.csdn.net/Jwenxue/article/details/107045572