文章来源:https://medium.com/voxel51/how-to-cluster-images-6e09bdff7361
2024 年 4 月 10 日
使用 FiftyOne、Scikit-learn和特征嵌入
在 2024 年深度学习的计算密集型环境中,集群一词最常出现在讨论 GPU 集群时--高度优化的矩阵乘法机器的大规模集合,用于训练同样大规模的生成模型。每个人都专注于训练更大、更好的模型,挑战人工智能模型性能的极限,并将最新的架构进展应用于他们的数据。
在本文中,我们将介绍聚类的基础知识,并向你展示如何使用开源机器学习库 Scikit-learn 和 FiftyOne 来构建可视化数据!
什么是聚类?
集群的构建模块
想象一下,你有一大堆各种形状和大小的乐高积木散落在地板上。到了该把乐高积木收起来的时候,你却发现自己没有一个足够大的垃圾桶来存放所有的乐高积木。幸运的是,你找到了四个较小的垃圾桶,每个都能容纳大致相同数量的乐高积木。你可以把各种乐高积木随意扔进每个垃圾桶,然后就可以收工了。但这样一来,下次你再去找某个特定的乐高积木时,就得花很多时间四处寻找了。
相反,你有一个更好的主意:把相似的乐高积木放在同一个垃圾桶里,会为你节省很多时间和麻烦。但是,你打算用什么标准来把乐高积木放进垃圾桶呢?是给不同颜色的乐高积木分配不同的盒子?还是把所有的方形乐高积木放在一个盒子里,圆形乐高积木放在另一个盒子里?这真的取决于你有哪些乐高积木!简而言之,这就是聚类。
更正式地说,聚类或聚类分析是一套对数据点进行分组的技术。聚类算法接收一堆对象,然后为每个对象分配任务。不过,与分类法不同的是,聚类算法并不是从一个类别列表开始对对象进行分类,迫使对象落入预设的桶中。相反,聚类试图根据数据发现不同的类别。换句话说,聚类的目的是发现数据中的结构,而不是预测预先存在的结构中的标签。
最后一点值得重复:聚类不是预测标签。与分类、检测和分割任务不同,聚类任务没有基本真实标签。我们将这种算法称为无监督算法,与有监督和自监督学习任务形成鲜明对比。
更重要的是,聚类是无需训练的。聚类算法会吸收数据点(对象)的特征,并利用这些特征将对象分成若干组。一旦成功,这些组就会突出独特的特征,让你了解数据的结构。
聚类是如何工作的?
聚类是一个包含技术的总称!
聚类算法有多种类型,根据其用于分配聚类成员资格的标准而有所区别。最常见的几种聚类算法是:
基于中心点的聚类:例如 K-means 聚类和均值移动聚类等技术。这些方法试图找到中心点来定义每个聚类,这些中心点被称为 "中心点",其目的是最大限度地提高聚类内各点之间的一致性。这种聚类方法适用于大型数据集,但对异常值和随机初始化比较敏感。通常情况下,我们会进行多次运行,然后选出最好的一次。你可能会发现,像 K-means 这样的技术在处理高维数据(即 "维度诅咒")时很吃力,如果与均匀流形逼近与投影(UMAP)等降维技术搭配使用,就能更好地发现结构。
基于密度的聚类:DBSCAN、HDBSCAN 和 OPTICS 等技术根据特征空间的疏密程度来选择聚类。从概念上讲,这些算法将高密度区域视为聚类,当点在特征空间中足够分散时,就会将聚类分解。像 DBSCAN 这样基于密度的简单技术在处理高维数据时可能会遇到困难,因为在高维数据中,数据可能并不是密集分布的。不过,HDBSCAN 等更复杂的技术可以克服其中的一些限制,并从高维特征中发现显著的结构。
分层聚类: 这些技术旨在:
- 从单个点入手构建聚类,然后迭代地将聚类组合成更大的复合体,或
- 解构聚类,从一个聚类中的所有对象开始,反复将聚类分解成更小的组成部分。
随着数据集的增长,聚合聚类(Agglomerative Clustering)等构建技术的计算成本会变得越来越高,但对于中小型数据集和低维特征来说,其性能还是相当可观的。
我应该对哪些特征进行聚类?
对于我们开始讨论的乐高积木,特征(长度、宽度、高度、曲率等)是独立的实体,我们可以将其视为数据表中的列。在对这些数据进行归一化处理后,我们就可以将一行数值作为特征向量传入每个乐高积木块的聚类算法中。从历史上看,聚类技术有很多类似的应用,可以对数据表或时间序列中的数值进行简单的预处理。
由于一些简单的原因,图像等非结构化数据并不能很好地融入这一框架:
- 图像的大小(长宽比和分辨率)可能不同
- 原始像素值可能非常嘈杂
- 像素之间的相关性可能是高度非线性的
如果我们大费周章地调整和标准化所有图像的尺寸、归一化像素值、去噪并将多维数组扁平化为 "特征向量",那么将这些经过处理的像素数组视为特征将给无监督聚类算法带来巨大的压力,使其难以发现结构。这对于像 MNIST 这样的简单数据集来说是可行的,但在实际应用中却往往行不通。
幸运的是,我们拥有强大的非线性函数逼近工具--深度神经网络!将我们的注意力限制在图像领域,我们有 CLIP 和 DINOv2 这样的模型,它们的输出是输入数据的有意义表示,我们还有为图像分类等特定任务训练的模型,我们通常从中提取网络第二层到最后一层的输出。还有变异自动编码器(VAE)网络,通常从中提取中间层的表示!
不同的模型有不同的架构,并在不同的数据集上针对不同的任务进行训练。
使用 FiftyOne 和 Scikit-learn 对图像进行聚类
设置和安装
我们将利用两个开源机器学习库:Scikit-learn 和 fiftyone,前者预装了大多数常见聚类算法的实现,后者简化了非结构化数据的管理和可视化:
pip install -U scikit-learn fiftyone
FiftyOne 聚类插件让我们的生活更加轻松。它在 scikit-learn 的聚类算法和我们的图像之间提供了连接组织,并将所有这些都封装在 FiftyOne 应用程序的一个简单用户界面中。我们可以通过 CLI 安装该插件:
fiftyone plugins download https://github.com/jacobmarks/clustering-plugin
我们还需要另外两个库: OpenAI 的 CLIP GitHub repo 可以让我们使用 CLIP 模型生成图像特征,而 umap-learn 库则可以让我们对这些特征应用一种名为 "统一曲面逼近和投影(UMAP)"的降维技术,从而将其可视化为二维图像:
pip install umap-learn git+https://github.com/openai/CLIP.git
请注意,这两个库中的任何一个都不是严格意义上的必需库--你可以使用 FiftyOne Model Zoo 中暴露嵌入的任何模型生成特征,也可以使用 PCA 或 tSNE 等其他技术进行降维。
安装好所有必要的库后,在 Python 进程中导入相关的 FiftyOne 模块,然后从 FiftyOne Dataset Zoo 中加载一个数据集(如果你愿意,也可以加载你的数据!)。在本文中,我们将使用 MS COCO 数据集的验证分割(5000 个样本):
import fiftyone as fo
import fiftyone.brain as fob
import fiftyone.zoo as foz
from fiftyone import ViewField as F
# load dataset from the zoo
dataset = foz.load_zoo_dataset("coco-2017", split="validation")
# delete labels to simulate starting with unlabeled data
dataset.select_fields().keep_fields()
# rename and persist to database
dataset.name = "clustering-demo"
dataset.persistent = True
# launch the app to visualize the dataset
session = fo.launch_app(dataset)
如果你在 Jupyter Notebook 中工作,可以通过 auto=False,然后在浏览器中打开一个标签页,指向 session.url 指向的位置(通常是 http://localhost:5151/),以查看应用程序的全貌。
创建特征
现在我们有了数据,必须生成用于聚类的特征。在本演示中,我们将查看两种不同的特征:由 CLIP 视觉转换器生成的 512 维向量,以及通过 UMAP 降维例程运行这些高维向量生成的二维向量。
要在 FiftyOne 样本集上运行降维,我们将使用 FiftyOne Brain 的 compute_visualization() 函数,该函数通过方法关键字参数支持 UMAP、PCA 和 tSNE。我们可以使用数据集的 compute_embeddings() 方法生成 CLIP 嵌入,然后明确地将其传递到我们的降维例程中。不过,我们可以一石二鸟,隐式告诉 compute_visualization()使用 CLIP 计算内嵌,并将这些内嵌存储在字段 "clip_embeddings "中,然后使用这些内嵌获得二维表示:
res = fob.compute_visualization(
dataset,
model="clip-vit-base32-torch", "clip-vit-base32-torch",
embeddings="clip_embeddings",
method="umap",
brain_key="clip_vis",
batch_size=10
)
dataset.set_values("clip_umap", res.current_points)
有了 brain_key 参数,我们就可以通过程序或 FiftyOne 应用程序中的名称来访问这些结果。最后一行将我们生成的二维向量数组存储到数据集的新字段 "clip_umap "中。
刷新应用程序并打开嵌入式面板,我们就能看到数据集的二维表示,图中的每个点都对应一幅图像:
计算和可视化聚类
有了特征向量,我们就可以使用 FiftyOne 聚类插件为数据添加结构。在 FiftyOne 应用程序中,按下键盘上的回车键并输入 compute_clusters。点击下拉菜单中的条目,打开聚类模式。
输入 run_key(类似于上面的 brain_key),以访问聚类运行的结果。在此过程中,请注意输入表单的动态更新。此时,你需要做出两个关键决定:根据哪些特征进行聚类以及采用哪种聚类算法!
选择 "kmeans "作为聚类方法,"clip_umap "作为特征向量。将聚类数目设为 20,其他参数均使用默认值。点击回车,让聚类算法运行。应该只需要几秒钟。
计算完成后,请注意样本上的新字段包含整数的字符串表示,表示给定样本被分配到哪个聚类。你可以直接过滤这些值,并在样本网格中一次查看一个聚类:
更有趣的是,我们在嵌入图中用这些集群标签着色:
通过可视化聚类,你可以检查聚类例程是否正确,还可以直观地了解数据结构。在这个例子中,我们可以看到一个泰迪熊聚类,它与数据的其他部分分离得相当好。这个聚类例程还发现了农场动物与大象和斑马等外来动物之间的界限。
现在,创建一个新的聚类运行,将聚类数量增加到 30 个(别忘了在这个新字段中给嵌入着色)。根据随机性(所有例程的初始化都是随机的),大象和斑马现在很有可能占据它们自己的聚类。
回到初始聚类集,让我们来研究嵌入图中的最后一个区域。请注意,有几张踢足球的图片被归入了一个主要由网球图片组成的聚类中。这是因为我们在聚类过程中传递的是二维降维向量,而不是嵌入向量本身。虽然二维投影有助于可视化,UMAP 等技术也能很好地保留结构,但相对距离并没有完全保留,因此会丢失一些信息。
假设我们将 CLIP 内嵌直接输入聚类计算,并使用相同的超参数。在这种情况下,这些足球图像就会与其他足球图像以及飞盘和棒球等其他野外运动图像被分配到同一个聚类中。
关键的启示是,高维特征并不比低维特征好,反之亦然。每一种选择都需要权衡利弊。这就是为什么你应该尝试不同的技术、超参数和特征。
为了更清楚地说明这一点,我们使用 HDBSCAN 作为聚类算法,它不允许我们指定聚类的数量,取而代之的是 min_cluster_size 和 max_cluster_size 等参数,以及合并聚类的标准。我们将使用 CLIP 嵌入作为特征,作为一个粗略的起点,我们只希望聚类的元素数在 10 到 300 之间。如果聚类太大,可能没有帮助;如果聚类太小,可能会捕捉到噪音而不是信号。当然,具体数值取决于数据集!
当我们根据聚类标签着色时,结果看起来有点乱。不过,当我们单独查看每个聚类的图像时,就会发现我们在数据集中识别出了一些非常有趣的样本集合。
对于 HDBSCAN,所有背景图像的标签均为"-1"。这些图像不会合并到任何最终聚类中。
跟踪聚类运行
在测试特征、聚类技术和超参数的各种组合时,你可能会想记录下生成特定聚类所使用的 "配置"。幸运的是,FiftyOne 聚类插件可以使用自定义运行为你处理所有这些问题。该插件提供了一个操作符 get_clustering_run_info,让你可以通过 run_key 选择一个运行,并在应用程序中查看该运行的所有参数的格式化打印输出:
你也可以通过将 run_key 传递给数据集的 get_run_info()方法,以编程方式访问该信息!
用 GPT-4V 标记集群
到目前为止,我们的聚类只有数字,这只是一种美化的内务管理工具。但是,如果我们针对数据集中的某些特定特征进行聚类,我们应该能够识别出这些特征,并用它们来松散地标记我们的样本。天真地说,我们可以逐个查看我们的聚类,只选择特定聚类中的图像并将其可视化,然后尝试自己标记聚类。
或者......我们可以使用多模态大型语言模型来完成这项工作!FiftyOne 聚类插件提供了这一功能,它利用 GPT-4V 的多模态理解能力为每个聚类贴上概念标签。
要使用此功能,你必须拥有 OpenAI API 密钥环境变量(必要时创建账户),设置方法如下:
export OPENAI_API_KEY=sk-...
该功能通过标签簇(label_clusters_with_gpt4v)操作符提供,操作符会从每个簇中随机选取五幅图像,将它们输入 GPT-4V 并给出特定任务提示,然后处理结果。
根据集群的数量(GPT-4V 的速度可能较慢,这与集群数量成线性关系),你可能需要委托执行操作,方法是选中操作员模式对话框中的复选框,然后从命令行启动任务:
fiftyone delegated launch
结论
在本文中,我们介绍了如何使用 scikit-learn 和 FiftyOne 将深度神经网络与流行的聚类算法相结合,为非结构化数据提供结构。在这一过程中,我们看到了特征向量、算法和超参数的选择会对聚类计算的最终结果产生很大影响,无论是聚类选择的内容还是聚类识别数据结构的效果。