使用UMAP转换新数据
本教程将逐步介绍一个简单的案例,在这个案例中,我们期望高维向量中的总体分布在训练数据和测试数据之间保持一致。有关这种情况可能出错的原因以及如何使用参数化UMAP修复的更多详细信息,请参阅使用参数化UMAP转换新数据。
为了演示这一功能,我们将使用 scikit-learn 和其中包含的 digits数据集(有关digits数据集的示例,请参见如何使用UMAP)。首先,让我们加载完成此任务所需的所有模块。
import numpy as np
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.svm import SVC
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline
sns.set(context='notebook', style='white', rc={'figure.figsize':(14,10)})
digits = load_digits()
为了保持一切公正,我们使用sklearn的train_test_split来
分离出一个训练集和测试集(按不同的数字类型分层)。默认情况下,train_test_split会
将25%的数据用于测试,这在本例中似乎是合适的。
X_train, X_test, y_train, y_test = train_test_split(digits.data,
digits.target,
stratify=digits.target,
random_state=42)
现在,为了对我们正在查看的内容有一个基准概念,让我们训练几个不同的分类器,然后看看它们在测试集上的表现如何。在这个例子中,让我们尝试一个支持向量分类器和一个KNN分类器。理想情况下,我们应该调整超参数(可能使用k折交叉验证进行网格搜索),但为了这个简单演示的目的,我们将简单地使用两个分类器的默认参数。
svc = SVC().fit(X_train, y_train)
knn = KNeighborsClassifier().fit(X_train, y_train)
接下来的问题是这些分类器在测试集上的表现如何。
方便的是,sklearn 提供了一个 score 方法,可以输出测试集上的准确率。
svc.score(X_test, y_test), knn.score(X_test, y_test)
(0.62, 0.9844444444444445)
现在的目标是利用UMAP作为预处理步骤,可以将其整合到管道中。因此,我们显然需要加载umap模块。
import umap
要使用UMAP作为数据转换器,我们首先需要使用训练数据来拟合模型。这与如何使用UMAP示例中使用fit方法的方式完全相同。在这种情况下,我们只需将训练数据传递给它,它将学习一个适当的(默认情况下是二维的)嵌入。
trans = umap.UMAP(n_neighbors=5, random_state=42).fit(X_train)
由于我们嵌入到二维空间,我们可以可视化结果以确保我们从这种方法中获得潜在的好处。这只是一个生成散点图的问题,其中数据点根据它们所属的类别进行着色。请注意,一旦我们将模型拟合到某些数据,嵌入的训练数据可以作为UMAP模型的.embedding_属性访问。
plt.scatter(trans.embedding_[:, 0], trans.embedding_[:, 1], s= 5, c=y_train, cmap='Spectral')
plt.title('Embedding of the training set by UMAP', fontsize=24);
这看起来非常有希望!大多数类别都得到了非常清晰的分离,这给了我们一些希望,认为它可能有助于分类器的性能。值得注意的是,这是一个完全无监督的数据转换;我们本可以使用训练标签信息,但这是后续教程的主题。
我们现在可以在嵌入的训练数据上训练一些新模型(再次是SVC和KNN分类器)。这看起来和以前完全一样,但现在我们传递的是嵌入的数据。请注意,在与模型训练相同的输入上调用transform将简单地返回embedding_属性,因此sklearn管道将按预期工作。
svc = SVC().fit(trans.embedding_, y_train)
knn = KNeighborsClassifier().fit(trans.embedding_, y_train)
现在我们想要处理测试数据,这些数据是模型(UMAP或分类器)从未见过的。为此,我们使用标准的sklearn API,并利用transform方法,这次将新的未见过的测试数据传递给它。我们将把这个结果赋值给test_embedding,以便我们可以更仔细地查看将现有的UMAP模型应用于新数据的结果。
%time test_embedding = trans.transform(X_test)
CPU times: user 867 ms, sys: 70.7 ms, total: 938 ms
Wall time: 335 ms
请注意,转换操作非常高效——耗时不到半秒。与其他一些转换器相比,这稍微慢了一些,但对于许多用途来说已经足够快了。请注意,随着训练集和/或测试集的大小增加,性能将按比例减慢。还值得注意的是,由于Numba JIT的开销,第一次调用transform可能会比较慢——后续运行将非常快。
下一个重要的问题是转换对我们的测试数据做了什么。 原则上,我们有一个新的二维表示的测试集,理想情况下,这应该基于训练集的现有嵌入。我们可以通过可视化数据(因为我们在二维中)来检查这一点,看看这是否正确。像之前一样的简单散点图就足够了。
plt.scatter(test_embedding[:, 0], test_embedding[:, 1], s= 5, c=y_test, cmap='Spectral')
plt.title('Embedding of the test set by UMAP', fontsize=24);
结果看起来符合我们的预期;测试数据已经被嵌入到两个维度中,位置正是我们根据上述可视化的训练数据嵌入所预期的(按类别)。这意味着我们现在可以尝试使用那些在嵌入的训练数据上训练的模型,通过传递新转换的测试集来测试它们。
svc.score(trans.transform(X_test), y_test), knn.score(trans.transform(X_test), y_test)
(0.9844444444444445, 0.9844444444444445)
结果相当不错。虽然KNN分类器的准确性没有提高,但考虑到数据,提高的空间并不大。另一方面,SVC的准确性已经提高到与KNN分类器相当的水平。当然,我们可能通过更好地设置SVC超参数来达到这个准确性水平,但这里的重点是我们可以将UMAP用作标准的sklearn转换器,作为sklearn机器学习管道的一部分。
只是为了好玩,我们可以运行相同的实验,但这次减少到十维(我们无法再可视化)。实际上,在这种情况下几乎没有增益——对于数字数据集,UMAP的两个维度已经足够,更多的维度不会有帮助。另一方面,对于更复杂的数据集,更多的维度可能允许更忠实的嵌入,值得注意的是,我们不仅限于两个维度。
trans = umap.UMAP(n_neighbors=5, n_components=10, random_state=42).fit(X_train)
svc = SVC().fit(trans.embedding_, y_train)
knn = KNeighborsClassifier().fit(trans.embedding_, y_train)
svc.score(trans.transform(X_test), y_test), knn.score(trans.transform(X_test), y_test)
(0.9822222222222222, 0.9822222222222222)
我们看到,在这种情况下,我们实际上略微降低了我们的准确率分数(请注意,这种评分可能存在潜在的噪声)。然而,对于更有趣的数据集,更高维度的嵌入可能会带来显著的收益——这绝对值得探索,作为在包含UMAP的管道中进行网格搜索的参数之一。