径向基函数核的显式特征映射近似

机器学习领域,核方法是一种强大的技术,它允许在高维空间中进行有效的计算,而无需显式地映射数据到这个空间。径向基函数(RBF)核是一种流行的核函数,它通过计算数据点之间的欧氏距离的高斯变换来工作。然而,在大规模数据集上,精确的核方法可能会变得计算成本高昂。为了解决这个问题,可以近似RBF核的特征映射,从而在保持分类性能的同时减少计算量。

本文将展示如何使用两种流行的核近似方法:RBFSampler和Nystroem,来近似RBF核的特征映射。将在手写数字数据集上使用支持向量机(SVM)进行分类,并比较使用线性SVM、近似映射的线性SVM以及核化SVM的结果。将展示不同数量的蒙特卡洛采样(对于使用随机傅里叶特征的RBFSampler)和不同大小的训练子集(对于Nystroem)对近似映射的影响。

需要注意的是,由于本文使用的数据集规模不够大,因此无法完全展示核近似的优势,因为精确的SVM仍然相当快。然而,通过增加采样维度可以明显提高分类结果的准确性,但这也会增加计算成本。这意味着在运行时间和准确性之间存在一个权衡,这个权衡由参数n_components来决定。值得注意的是,通过使用随机梯度下降(SGDClassifier)可以大大加速解决线性SVM和近似核SVM的问题,但对于核化SVM的情况则不容易实现。

Python包和数据集导入

首先,需要导入必要的Python包和数据集。以下是一个示例代码,展示了如何导入所需的库和手写数字数据集:

from time import time import matplotlib.pyplot as plt import numpy as np from sklearn import datasets, pipeline, svm from sklearn.decomposition import PCA from sklearn.kernel_approximation import Nystroem, RBFSampler # 加载手写数字数据集 digits = datasets.load_digits(n_class=9)

接下来,需要将图像数据展平,将其转换为(样本,特征)矩阵的形式。然后,将数据集分为训练集和测试集,分别用于训练模型和评估模型的性能。

n_samples = len(digits.data) data = digits.data / 16.0 data -= data.mean(axis=0) # 使用数据的前半部分进行训练 data_train, targets_train = data[:n_samples//2], digits.target[:n_samples//2] # 使用数据的后半部分进行测试 data_test, targets_test = data[n_samples//2:], digits.target[n_samples//2:]

现在,将创建一个支持向量分类器,并使用RBFSampler和Nystroem方法来近似RBF核的特征映射。然后,将使用这些近似映射来训练线性SVM,并评估其在测试集上的性能。

# 创建SVM分类器 kernel_svm = svm.SVC(gamma=0.2) linear_svm = svm.LinearSVC(random_state=42) # 创建核近似和线性SVM的管道 feature_map_fourier = RBFSampler(gamma=0.2, random_state=1) feature_map_nystroem = Nystroem(gamma=0.2, random_state=1) fourier_approx_svm = pipeline.Pipeline([ ("feature_map", feature_map_fourier), ("svm", svm.LinearSVC(random_state=42)), ]) nystroem_approx_svm = pipeline.Pipeline([ ("feature_map", feature_map_nystroem), ("svm", svm.LinearSVC(random_state=42)), ])

将比较使用线性SVM、近似映射的线性SVM和核化SVM在不同采样步骤和训练子集大小下的性能。这将帮助理解在运行时间和准确性之间的权衡。

# 训练和预测使用线性和核SVM kernel_svm_time = time() kernel_svm.fit(data_train, targets_train) kernel_svm_score = kernel_svm.score(data_test, targets_test) kernel_svm_time = time() - kernel_svm_time linear_svm_time = time() linear_svm.fit(data_train, targets_train) linear_svm_score = linear_svm.score(data_test, targets_test) linear_svm_time = time() - linear_svm_time sample_sizes = 30 * np.arange(1, 10) fourier_scores = [] nystroem_scores = [] fourier_times = [] nystroem_times = [] for D in sample_sizes: fourier_approx_svm.set_params(feature_map__n_components=D) nystroem_approx_svm.set_params(feature_map__n_components=D) start = time() nystroem_approx_svm.fit(data_train, targets_train) nystroem_times.append(time() - start) start = time() fourier_approx_svm.fit(data_train, targets_train) fourier_times.append(time() - start) fourier_score = fourier_approx_svm.score(data_test, targets_test) nystroem_score = nystroem_approx_svm.score(data_test, targets_test) nystroem_scores.append(nystroem_score) fourier_scores.append(fourier_score)

最后,将绘制分类准确性和训练时间的图表,以直观地展示不同方法的性能。

plt.figure(figsize=(16, 4)) accuracy = plt.subplot(121) timescale = plt.subplot(122) accuracy.plot(sample_sizes, nystroem_scores, label="Nystroem approx. kernel") timescale.plot(sample_sizes, nystroem_times, "--", label="Nystroem approx. kernel") accuracy.plot(sample_sizes, fourier_scores, label="Fourier approx. kernel") timescale.plot(sample_sizes, fourier_times, "--", label="Fourier approx. kernel") # 绘制精确RBF和线性核的水平线 accuracy.plot([sample_sizes[0], sample_sizes[-1]], [linear_svm_score, linear_svm_score], label="linear svm", ) timescale.plot([sample_sizes[0], sample_sizes[-1]], [linear_svm_time, linear_svm_time], "--", label="linear svm", ) accuracy.plot([sample_sizes[0], sample_sizes[-1]], [kernel_svm_score, kernel_svm_score], label="rbf svm", ) timescale.plot([sample_sizes[0], sample_sizes[-1]], [kernel_svm_time, kernel_svm_time], "--", label="rbf svm", ) # 绘制数据特征维度为64的垂直线 accuracy.plot([64, 64], [0.7, 1], label="n_features") # 图例和标签 accuracy.set_title("Classification accuracy") timescale.set_title("Training times") accuracy.set_xlim(sample_sizes[0], sample_sizes[-1]) accuracy.set_xticks(()) accuracy.set_ylim(np.min(fourier_scores), 1) timescale.set_xlabel("Sampling steps = transformed feature dimension") accuracy.set_ylabel("Classification accuracy") timescale.set_ylabel("Training time in seconds") accuracy.legend(loc="best") timescale.legend(loc="best") plt.tight_layout() plt.show()

通过上述代码,可以在手写数字数据集上近似RBF核的特征映射,并使用SVM进行分类。比较了不同采样步骤和训练子集大小下的性能,以理解在运行时间和准确性之间的权衡。

决策面的可视化

为了进一步理解这些分类器的行为,可以将决策面投影到数据的第一和第二主成分上。这将帮助直观地比较RBF核SVM和线性SVM的决策面。需要注意的是,这种可视化只是一个有趣的决策面切片,因为数据实际上是在64维空间中的。特别是,一个数据点(表示为一个点)不一定被分类到它所在的区域,因为它不会位于第一和第二主成分所跨越的平面上。

pca = PCA(n_components=8, random_state=42).fit(data_train) X = pca.transform(data_train) # 生成第一和第二主成分的网格 multiples = np.arange(-2, 2, 0.1) first = multiples[:, np.newaxis] * pca.components_[0, :] second = multiples[:, np.newaxis] * pca.components_[1, :] grid = first[np.newaxis, :, :] + second[:, np.newaxis, :] flat_grid = grid.reshape(-1, data.shape[1]) # 绘制决策面 plt.figure(figsize=(18, 7.5)) plt.rcParams.update({"font.size": 14}) titles = ["SVC with rbf kernel", "SVC (linear kernel) with Fourier rbf feature map n_components=100", "SVC (linear kernel) with Nystroem rbf feature map n_components=100"] for i, clf in enumerate((kernel_svm, nystroem_approx_svm, fourier_approx_svm)): plt.subplot(1, 3, i + 1) Z = clf.predict(flat_grid) Z = Z.reshape(grid.shape[:-1]) levels = np.arange(10) lv_eps = 0.01 plt.contourf(multiples, multiples, Z, levels=levels - lv_eps, cmap=plt.cm.tab10, vmin=0, vmax=10, alpha=0.7) plt.axis("off") plt.scatter(X[:, 0], X[:, 1], c=targets_train, cmap=plt.cm.tab10, edgecolors=(0, 0, 0), vmin=0, vmax=10) plt.title(titles[i]) plt.tight_layout() plt.show()

通过上述代码,可以将RBF核SVM和线性SVM的决策面投影到数据的第一和第二主成分上。这将帮助直观地比较这两种分类器的决策面。需要注意的是,这种可视化只是一个有趣的决策面切片,因为数据实际上是在64维空间中的。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485