支持向量机(SVM)是非常强大的机器学习算法,支持线性和非线性的分类任务、回归任务,甚至可以完成离群值检测任务。SVM是机器学习最受欢迎的算法之一,特别适合于比较复杂的中小型数据集上进行建模,如果数据集比较大SVM就显得比较吃力。
SVM的核心思想是:生成一条决策边界,使得决策边界离最近点的距离越远越好。
0. 导入所需的库
import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl
%matplotlib inline
import sklearn
for i in (np, mpl, sklearn):
print(i.__name__,": ",i.__version__,sep="")
输出:
numpy: 1.17.4
matplotlib: 3.1.2
sklearn: 0.21.3
1. SVM线性分类任务
from sklearn.svm import SVC
from sklearn import datasets
iris = datasets.load_iris()
X = iris["data"][:, (2, 3)] # 花瓣长度和宽度
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)
输出:
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()
输出:
2. 软间隔分类(Soft Margin Classification)
硬间隔分类:严格要求所有样本都必须在间隔之外。这时就有两个问题:一是这种模型只能用于严格线性可分的数据,二是这种模型对离群值异常敏感。
软间隔分类:容忍一定数量的样本位于分类间隔中,这样可以增加模型的泛化能力和模型的应用场景。
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值调整模型。
3. SVM非线性分类任务
虽然SVM线性分类器效果很好,但是实际应用中有些数据往往是线性不可分的。解决线性不可分问题的办法之一是添加更多的特征,例如添加多项式特征。
3.1 SVM解决非线性可分问题
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=(12, 5))
plt.subplot(121)
plt.grid(True, which='both')
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^")
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.tight_layout()
plt.show()
输出:
观察上面左图,数据只有一个特征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()
输出:
从上述输出结果可以看出,线性不可分的二分类问题经过添加多项式特征后变得可分了。
3.2 多项式核
添加多项式特征的方法比较简单,而且在很多机器学习算法中都可以使用。但是如果多项式次数不够高,则无法处理比较复杂的数据,如果次数太高,则会产生大量的特征从而导致模型训练会很慢很慢。
然而为了解决如上的矛盾,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控制模型受高次多项式和低次多项式影响的程度。
3.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=(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个特征的数据集。
3.4 高斯径向基核
rbf_kernel_svm_clf = Pipeline([
("scaler",StandardScaler()),
("svm_clf",SVC(kernel="rbf",gamma=5,C=0.001))
])
rbf_kernel_svm_clf.fit(X,y)
输出:
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")运行快),尤其是数据集比较大或者特征非常多的时候。其次,如果训练集不是很大可以尝试高斯径向基核,通常情况下效果还不错。
3.5 计算复杂度
LinearSVC类基于liblinear库,是线性SVM优化的库,虽然不支持核技巧,但复杂度几乎与数据样本数量和特征数量呈线性关系,复杂度大概为O(m*n)。
SVC类基于libsvm库,支持核技巧,复杂度通常在O(m^2n)到O(m^3n)之间。比较适合中小型数据集上建模,如果数据量较大时则为非常的可怕,运行非常慢。
超参数tol,即容忍因子,对于大部分分类任务,sklearn中的工具中默认值都是优化过的。
4. SVM回归
如前所述,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()
输出:
5. SVM算法原理
SVM算法原理及详细推导过程见:https://blog.csdn.net/Jwenxue/article/details/107045572