研究动机

由于NLP对无监督语料的高效利用,使得零样本预测下游任务成为可能,尤其OpenAi自己的GPT系列取得的令人惊喜的zero-shot效果,本文尝试探索图像上的zero-shot能力。

  1. 当前的计算机视觉系统经过训练后只能对固定的类别进行分类(主流工作都是在有监督的ImageNet固定类别上进行预训练),这严重影响了其在未出现类别上的泛化性和可用性(需要用额外的有标注数据)。

  2. 在NLP方面, 直接对无监督语料进行大规模预训练的方式带来了文本侧编码能力的革命性提升。在迁移下游任务时,具有不使用下游任务的数据实现zero-shot的能力。

  3. Transformer架构使得文本和图像可以很方便的进行融合。

  4. 对比学习的方式,极大的提升了训练效率,使得训练超大规模文本-图像对成为可能。

    因此,直接从有关图像的原始文本中学习是一种很有前途的替代方案,它可以利用更广泛的监督数据。那么,直接基于互联网上的大规模文本数据进行预训练,能够在计算机视觉领域取得突破性进展吗?

模型简介

数据集

有了训练和预测方法,还需要有大规模数据集进行训练。本文最重要的贡献之一就是构造了一个高质量的文本-图像数据集。
构建了一个由4亿对图像-文本组成的新数据集,这些数据集是从互联网上的各种公开资源中收集的。 为了尝试涵盖尽可能广的视觉概念,使用搜索(图像,文本)对作为构建过程的一部分,搜索的文本包含一组 500,000 个查询中的一个(在文章的脚注中说这些query是由英文维基百科中最少被提及100次的词两两组合成的)。 通过在每个查询中包含多达 20,000 个(图像、文本)对来大致平衡结果。 生成的数据集的总字数与用于训练 GPT-2 的 WebText 数据集相似。 将此数据集称为 WebImageText (WIT)。

另外这个数据集我在网上并没有找到,构建的细节在论文中也没有多提及,OpenAI应该是没有将其开源,另外GPT2的WebText是开源的。

训练阶段

image.png
clip模型的想法非常的大道至简,如上图所示:文本端与图像端各一个Encoder层,然后使用生成的Embedding进行对比学习(只有对应的图像-文本对是正样本,其余都视为负样本),进而完成模型的训练。但模型架构并不是一蹴而就的。
首先,作者试验了VirTex的架构,从头开始联合训练图像编码器和文本编码器来预测图像的标题。 然而,在有效扩展这种方法时遇到了困难。因为针对同一个图像而言,使用生成模型预测文本时,文本的选择过多,且有较大的差异。
例如:下图可以描述为:

  1. 一个人站在地平面上观察地球
  2. 人在一个发光的球前驻足
  3. ……

image.png
如果使用这种方式,预测难度会非常高,在有效的训练资源及加速器时长下,可能导致模型难以收敛。在图 2 中,我们展示了一个 6300 万参数的Transformer语言模型,它已经使用了其 ResNet50 图像编码器两倍的计算,学习识别 ImageNet 类的速度比类似于 Joulin 等人预测同一文本的词袋编码的方法慢三倍 (2016)
image.png

模型的训练伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# image_encoder - ResNet or Vision Transformer
# text_encoder - CBOW or Text Transformer
# I[n, h, w, c] - minibatch of aligned images
# T[n, l] - minibatch of aligned texts
# W_i[d_i, d_e] - learned proj of image to embed
# W_t[d_t, d_e] - learned proj of text to embed
# t - learned temperature parameter
# extract feature representations of each modality
I_f = image_encoder(I) #[n, d_i]
T_f = text_encoder(T) #[n, d_t]
# joint multimodal embedding [n, d_e]
I_e = l2_normalize(np.dot(I_f, W_i), axis=1)
T_e = l2_normalize(np.dot(T_f, W_t), axis=1)
# scaled pairwise cosine similarities [n, n]
logits = np.dot(I_e, T_e.T) * np.exp(t)
# symmetric loss function
labels = np.arange(n)
loss_i = cross_entropy_loss(logits, labels, axis=0)
loss_t = cross_entropy_loss(logits, labels, axis=1)
loss = (loss_i + loss_t)/2

从上述代码可以看到:

  1. 文本端可以使用CBOW或者文本Transformer模型(GPT/BERT等)
  2. 图像端可以使用ResNet或者VIT
  3. 训练步骤分为以下三步:
    1. 提取每个模态的特征表示
    2. 每个模态进行正则化
    3. 计算图像-文本对的cos相似度
    4. 计算对比学习Loss

预测阶段

image.png
图像分类任务的预测阶段如上图所示:

  1. 将文本组织为promte语句送入文本编码器
  2. 将图像送入图像编码器
  3. 将两个模态生成的encoder embedding计算cos相似度

因为前两步可以在线下预先完成,所以可以非常方便的进行文本-图像检索任务,只需要在检索时算一个cos值即可。

模型泛化能力

从下图可以直观的看出模型的泛化能力,ResNet101在ImageNet上训练的模型难以泛化到其他数据集中,而clip训练的模型可以保持稳定,其可能学到了文本与图像的内在联系。

image.png

代码实战

在HuggingFace中可以很方便的调用训练好的Clip模型,下面是调用中文Clip模型的代码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from PIL import Image
import requests
import clip
import torch
from transformers import BertForSequenceClassification, BertConfig, BertTokenizer
from transformers import CLIPProcessor, CLIPModel
import numpy as np

device = torch.device("cuda:1")
print(f"device {device}")

query_texts = ["一只猫", "一只狗",'两只猫', '两只老虎','一只老虎'] # 这里是输入文本的,可以随意替换。
# 加载Taiyi 中文 text encoder
text_tokenizer = BertTokenizer.from_pretrained("Taiyi-CLIP-Roberta-102M-Chinese")
text_encoder = BertForSequenceClassification.from_pretrained("Taiyi-CLIP-Roberta-102M-Chinese").eval()
text = text_tokenizer(query_texts, return_tensors='pt', padding=True)['input_ids']

url = "http://images.cocodataset.org/val2017/000000039769.jpg" # 这里可以换成任意图片的url
# 加载CLIP的image encoder
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32").to(device)
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
image = processor(images=Image.open(requests.get(url, stream=True).raw), return_tensors="pt").to(device)

with torch.no_grad():
image_features = clip_model.get_image_features(**image)
text_features = text_encoder(text).logits
# 归一化
image_features = image_features / image_features.norm(dim=1, keepdim=True)
text_features = text_features / text_features.norm(dim=1, keepdim=True)

image_features = image_features.to(device)
text_features = text_features.to(device)

# 计算余弦相似度 logit_scale是尺度系数
logit_scale = clip_model.logit_scale.exp()
logits_per_image = logit_scale * image_features @ text_features.t()
logits_per_text = logits_per_image.t()
probs = logits_per_image.softmax(dim=-1).cpu().numpy()
print(np.around(probs, 3))

TODO

对于下游任务的微调,准备另开一篇文章,验证在下游任务上的性能。

参考:

跟李沐学AI-clip精读
史上最全OpenAI CLIP解读:简单的想法与被低估的实验
HuggingFace-中文Clip模型