来源:https://medium.com/@paluchasz/understanding-openais-clip-model-6b52bade3fa3
CLIP 是由 OpenAI 在 2021 年发布的,自那时起已成为许多多模态 AI 系统中的基础构件之一。本文深入探讨了 CLIP 是什么、它是如何工作的、如何使用以及其实现方式。
引言
CLIP,即 Contrastive Language-Image Pre-training,对比语言-图像预训练,是一种从自然语言监督中学习的高效方法,于 2021 年在论文 Learning Transferable Visual Models From Natural Language Supervision 中被引入。
简而言之,CLIP 是一个联合的图像和文本 嵌入 模型,通过 4 亿个图像和文本对以自监督的方式进行训练。这意味着它将文本和图像映射到同一个嵌入空间中。例如,一张狗的图片和句子“一张狗的图片”将具有非常相似的嵌入,并在向量空间中彼此接近。这一点非常重要,因为你可以用这样的模型构建许多有趣的应用,例如用描述搜索图像数据库或反之。
作者发现,CLIP 可以用于各种未经过训练的任务。例如,它在多个基准测试上取得了显著的 零样本 性能,如 ImageNet,这是一个图像分类数据集。零样本学习 指的是模型未明确训练于 ImageNet 数据集中的任何 128 万个训练样本。尽管如此,CLIP 的准确度与原始的 ResNet-50 相当,后者是基于该数据进行训练的!
但如何使用 CLIP 进行图像分类呢?以 ImageNet 为例,你可以将其 1000 个可能的类别/对象分别用 CLIP 嵌入,使用提示“一张 {对象} 的照片”(例如“一张狗的照片”或“一张猫的照片”)。这将给你 1000 个不同的嵌入,对应所有可能的类别。接下来,你取你想要分类的图像,比如一张狗的照片,并用 CLIP 嵌入它。最后,你计算图像嵌入与所有文本嵌入之间的 点积。由于 CLIP 经过训练,使得图像和文本在同一个嵌入空间中,并且点积计算嵌入之间的相似度,因此与“一张狗的照片”的点积很可能是最高的。因此,你可以预测该图像是狗。注意,如果你想将 CLIP 转变为一个真正的分类器,你还可以通过 softmax 函数 传递点积,以获得每个类别的预测概率。
上述过程可以在下图的步骤 2 和 3 中看到。
来源:OpenAI 的 博客,展示如何使用 CLIP 进行零样本图像分类。
现在让我们更详细地了解 CLIP 的工作原理。
模型细节
架构
CLIP 模型有两个主要组件,一个文本编码器(嵌入文本)和一个图像编码器(嵌入图像)。对于文本编码器,使用了 Transformer。这种架构自 2017 年以来彻底改变了 NLP 领域,因此它的使用并不令人惊讶。有关更直观的解释,请参见以下 博客。
对于图像编码器,作者尝试了两种不同的模型,一个 ResNet-50 和一个 Vision Transformer (ViT)。ResNet-50 是使用卷积神经网络 (CNNs) 的原始最先进架构,用于图像分类。ViT 是原始 Transformer 的更近期适应,用于图像,其中每个图像可以分割成一系列 patch,并传递到模型中,类似于一系列 token。作者发现 ViT 训练得更快。
最大的 ResNet 模型,RN50x64,在 592 个 V100 GPU 上训练了 18 天,而最大的 Vision Transformer 在 256 个 V100 GPU 上训练了 12 天。
文本和图像编码器都是从头开始训练的。
我们从头开始训练 CLIP,不使用 ImageNet 权重初始化图像编码器,也不使用预训练权重初始化文本编码器。
对于所有架构,如论文所述,进行了一些小的修改。
训练
作者最初尝试训练一个图像字幕模型,该模型可以预测给定图像的确切字幕/描述。
我们最初的方法与VirTex类似,从头开始联合训练一个图像CNN和文本转换器来预测图像的字幕。然而,我们遇到了有效扩展这种方法的困难。
但是,他们发现它无法扩展到在4亿(图像,文本)对上进行训练,因此他们选择了一种对比表示学习方法。对比表示学习的目标是学习一个嵌入空间,在这个空间中,相似的样本对彼此靠近,而不相似的则相隔较远。
在标准的对比学习方法中,你会给模型提供形式为_(锚点,正面,负面)_的示例,其中_锚点_是一个类别的图像,比如说一只狗,_正面_是同一类别的另一张图像,也是一只狗,而_负面_是另一个类别的图像,比如说一只鸟。然后你嵌入这些图像,并以这样的方式训练模型:同一类别的两个嵌入之间的距离(也就是狗的锚点和正面之间的距离)_minimize(anchor, positive)_被最小化,不同类别的两个嵌入之间的距离(狗和鸟)_maximize(anchor, negative)_被最大化。这鼓励模型为同一物体输出非常相似的嵌入,为不同物体输出不相似的嵌入。
对比学习的可视化。来源:https://www.v7labs.com/blog/contrastive-learning-guide
相同的方法也可以应用于文本以及文本和图像的组合。例如,对于CLIP,对于单个训练示例,锚点可以是一张狗的图片,正面可以是字幕“一张狗的图片”,负面可以是字幕“一张鸟的图片”。
CLIP进一步使用多类N对损失,这是上述方法的扩展,但当你为每个锚点有多个正面和负面时。正如论文中描述的:
给定一批N(图像,文本)对,CLIP被训练为预测在一批中N × N可能的(图像,文本)配对实际上发生了哪些。为此,CLIP通过联合训练图像编码器和文本编码器来学习一个多模态嵌入空间,以最大化批中N个真实对的图像和文本嵌入之间的余弦相似性,同时最小化N² − N个不正确配对的嵌入之间的余弦相似性。它在这些相似性分数上优化了一个对称的交叉熵损失。
下面提供的伪代码很好地概括了论文中的核心细节:
步骤包括:
- 使用图像编码器嵌入图像,使用文本编码器嵌入文本。
- 图像和文本嵌入来自不同的模型,具有不同的维度,因此通过(乘以一个学习的投影矩阵)将它们投影到相同的联合多模态嵌入空间中。例如,
np.dot(I_f, W_i)
将大小为_[n, d_i]的矩阵与大小为[d_i, d_e]的矩阵相乘,结果得到一个大小为[n, d_e]的投影矩阵。 - 对新的嵌入向量进行归一化处理。这使得它们变成单位向量。
- 计算点积矩阵。
- 计算每一行和每一列的交叉熵损失,并除以2,因为每对会被计算两次。
提示工程与集成
随着语言模型的兴起,提示工程已成为从生成模型中获得良好输出的非常常见的做法。由于CLIP中的文本编码器是一个Transformer模型,作者发现这对于获得良好的零样本性能也非常关键。作者发现,在他们预训练的数据集中,与图像配对的文本很少只是一个单词,例如用“狗”来表示一个类别标签。更常见的是,文本是一个完整的句子,如图像的标题或描述。因此,作者发现提示“一张{对象}的照片”是一个很好的默认设置,但在某些情况下,更专业的提示效果更好。例如,对于卫星图像,他们发现“一张{对象}的卫星照片”效果很好。
作者还尝试了不同模型的集成。集成是指将几个不同模型对相同输入的预测结合起来以获得最终输出,这是机器学习中解决高方差和低偏差(过拟合)问题的常见技术。在CLIP的情况下,作者通过使用许多不同的提示构建分类器来构建集成。
提示工程和集成在ImageNet上都显示了显著的性能提升。
在ImageNet上,我们集成了80个不同的上下文提示,这比上述单一默认提示的性能提高了3.5%。综合考虑,提示工程和集成将ImageNet的准确性提高了近5%。
局限性
尽管论文深入探讨了许多实验和结果,但重要的是也要提到CLIP并不完美,存在各种局限性。
- 由于前面提到的设计决策,这不是一个生成模型,例如不能进行图像标注。
- 作者指出,CLIP仍然远未达到最先进水平(仅与顶部带有线性层的ResNet相当)。它在某些任务上的泛化能力非常差,例如在简单的MNIST手写数字识别数据集上仅达到88%。这可能是因为其训练中没有类似的图像,但CLIP对此几乎没有解决办法。
- CLIP是在互联网上的图像-文本对上进行训练的。这些图像-文本对未经筛选和整理,导致CLIP模型学习了许多社会偏见。(这与当前大型语言模型(LLMs)的类似担忧相似,这些担忧正通过RLFHF和直接偏好优化等技术来解决)。
- Transformer文本编码器的最大序列长度(可以传递的最大令牌数)在原始实现中被限制为76,因为数据集主要是图像和标题,这些通常是短句。因此,使用现成的预训练模型处理较长文本时效果不佳,因为它们会在76个令牌后被截断,并且模型是针对短文本进行训练的。
实现细节
使用HuggingFace Transformers进行推理
你可以在自己的计算机上使用HuggingFace的Transformers库用几行代码就使用CLIP!首先,导入库并加载预训练模型。
import transformers
model = transformers.CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = transformers.CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
然后创建一个标题/描述列表和一个图像列表。图像可以是URL或PIL图像。
import PIL.Image
images = [PIL.Image("for_example_a_dog_image.jpeg")]
possible_classes = ["an image of a bird", "an image of a dog", "an image of a cat"]
调用处理器,它会对文本和图像进行标记化,并准备它们以便传递给模型。这与标准文本使用情况中调用标记器非常相似。由于我们有一个描述批处理,我们需要使用填充(padding)将它们全部填充到相同的长度,以便能够存储为张量,并使用截断(truncation)将任何长句子截断到最大序列长度(如前所述为76)。然后将标记化的输入传递给模型,模型通过文本和图像编码器传递它们。
with torch.no_grad():
inputs = processor(text=descriptions, images=images, return_tensors="pt", padding=True, truncation=True)
outputs = model(**inputs)
现在我们可以使用两个不同的函数检索点积矩阵。使用logits_per_image
获取形状为_[num_of_images, num_of_text]的点积矩阵,使用logits_per_text
获取形状为[num_of_text, num_of_images]_的矩阵。
dot_products_per_image = outputs.logits_per_image
dot_products_per_text = outputs.logits_per_text
最后,如果我们想要为每个图像获取概率分布,可以将这些结果通过softmax函数。
probabilities = dot_products_per_image.softmax(dim=1)
深入实现细节
Transformers CLIP的源代码可以在github上找到,我发现它是一个非常模块化和优秀的实现。主要模型在CLIPModel
类中实现,你可以在forward
方法中看到主要逻辑,如下所示:
CLIP在最高层次的核心实现。来源:modeling_clip.py
视觉模型和文本模型在嵌入和层归一化方面有一些细微差别,如下所示:
CLIP文本编码器是一个Transformer。来源:modeling_clip.py
CLIP图像编码器是一个视觉Transformer。来源:modeling_clip.py
但它们都共享相同的CLIPEncoder
,这是主要的Transformer编码器。这包括许多子块,它们称为CLIPEncoderLayer
。回想一下,在Transformer架构中,每个编码器和解码器被堆叠在一起N次。对于CLIP,我们不使用解码器,因为我们不生成任何文本。
来自Attention is All You Need的Transformer架构。
每个CLIPEncoderLayer
然后由注意力机制、归一化层和简单的前馈层/多层感知器(MLP)组成。
N个编码器层之一。来源:modeling_clip.py
最后,我通过并注释了多头注意力机制的实现,如下所示——享受吧!
进一步工作
如前所述,CLIP 有多种应用方式,特别是在语义搜索类应用中。例如,我们可以通过使用图像描述来从数据库中检索图像。
CLIP 及其替代品也是自那时起出现的许多多模态模型的构建模块。例如,在 Flamingo 中,一个视觉语言模型,它可以一次性处理一系列文本和图像并生成文本。Flamingo 使用视觉编码器将图像转换为与文本相同的嵌入空间。
来源:Flamingo 论文。有关 Flamingo 如何工作的详细信息,请参阅我的另一篇文章。
作者同时尝试了 CLIP 和他们的类似训练版本。
最后,像 Google 的 Gemini 这样的模型,虽然我们了解不多,但它们很可能采用类似的方法来结合来自各种模态(包括音频和视频)的输入数据!
来源:Gemini 论文
结论
总之,CLIP 是一个联合文本和嵌入模型,可用于多种应用并构建多模态 AI 系统。它也非常容易在 Python 中仅用几行代码在 CPU 上运行。
希望你觉得它有用,感谢阅读!如果你喜欢,你可能还想查看我的关于 Flamingo 的文章——一个很好的后续内容!
标签:编码器,嵌入,CLIP,模型,OpenAI,图像,文本 From: https://www.cnblogs.com/IcyFeather/p/18293814