优秀的编程知识分享平台

网站首页 > 技术文章 正文

机器学习100天-Day1602非线性支持向量机分类

nanyue 2024-07-23 13:43:01 技术文章 17 ℃

说明:本文依据《Sklearn 与 TensorFlow 机器学习实用指南》完成,所有版权和解释权均归作者和翻译成员所有,我只是搬运和做注解。

第五章是对支持向量机SVM的系统介绍,阐述支持向量机的核心概念,怎么使用这个强大的模型,以及它是如何工作的。

这应该是第三次做这方面的内容了,贴出另一个比较全面的SVM进阶博客。

https://blog.csdn.net/v_JULY_v/article/details/7624837

支持向量机(SVM)是个非常强大并且有多种功能的机器学习模型,能够做线性或者非线性的分类,回归,甚至异常值检测。机器学习领域中最为流行的模型之一,是任何学习机器学习的人必备的工具。SVM特别适合应用于复杂但中小规模数据集的分类问题

本章使用数据为Sklearn中make_moons数据

源代码已经同步在github中 
https://github.com/jwc19890114/-02-learning-file-100days

2. 非线性支持向量机分类

尽管线性SVM分类器在许多案例上表现得出乎意料的好,但是很多数据集并不是线性可分的。一种处理非线性数据集方法是增加更多的特征,例如多项式特征(正如你在第4章所做的那样);在某些情况下可以变成线性可分的数据。

下左图是一个只有一个特征x1的简单的数据集,该数据集不是线性可分的。但是如果你增加了第二个特征 x2=(x1)^2,产生的 2D 数据集就能很好的线性可分。

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")
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.show()

为了实施这个想法,通过Sklearn你可以创建一个流水线(Pipeline)去包含多项式特征(PolynomialFeatures)变换,然后一个StandardScaler和LinearSVC。

使用卫星数据集(moons datasets)测试一下效果。

可以看到,在pipeline里面已经完成了特征缩放和svm模型设置,在后面引入数据训练即可

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.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()
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)
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中有核技巧,应用后得到的结果与高次数多项式类似。

在这里同样使用卫星数据测试效果

可以使用网格搜索找到最优参数,先进行粗略的网格搜索,然后再最佳值的基础上再进行更细的。

#degree控制多项式核的阶数,分别使用3阶和10阶处理
#coef控制了高阶多项式与低阶多项式对模型的影响
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)
poly1000_kernel_svm_clf = Pipeline([
 ("scaler", StandardScaler()),
 ("svm_clf", SVC(kernel="poly", degree=50, coef0=1000, C=5))
 ])

poly1000_kernel_svm_clf.fit(X, y)
plt.figure(figsize=(20, 4))

plt.subplot(131)
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(132)
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)

plt.subplot(133)
plot_predictions(poly1000_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=50, r=1000, C=5$", fontsize=18)
plt.show()

添加相似特征

另一种解决非线性问题的方法是使用相似函数(similarity funtion)计算每个样本与特定地标(landmark)的相似度。例如,前面使用的一维数据集,在x1=-2和x1=1之间增加两个地标(图 5-8 左图)。接下来,我们定义一个相似函数,即高斯径向基函数(Gaussian Radial Basis Function,RBF),设置γ = 0.3。

何选择地标。最简单的方法是在数据集中的每一个样本的位置创建地标。这将产生更多的维度从而增加了转换后数据集是线性可分的可能性。但缺点是,m个样本,n个特征的训练集被转换成了m个实例,m个特征的训练集。这样一来,如果你的训练集非常大,你最终会得到同样大的特征。与前面多项式特征一样,过高的相似特证数会导致过大计算开销,这里使用高斯RBF核来处理。

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) plt.show()

训练模型

rbf_kernel_svm_clf=Pipeline([
 ("scaler",StandardScaler()),
 ("svm_clf",SVC(kernel="rbf",gamma=5,C=0.001))
])
rbf_kernel_svm_clf.fit(X,y)
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)
plt.show()

221图为目标模型,其他三张图显示了不同超参数gamma和C训练的模型。

  • 增大γ值使钟型曲线更窄,导致每个样本的影响范围变得更小:即判定边界最终变得更不规则,在单个样本周围环绕。
  • 较小γ值使钟型曲线更宽,样本有更大的影响范围,判定边界最终则更加平滑。
  • 所以γ是可调整的超参数:模型过拟合,减小γ值,欠拟合,增大γ(与超参数C相似)。

这部分推的有点快了,我感觉过两天还得折回来看一遍……

Tags:

猜你喜欢

最近发表
标签列表