CLIP [OpenAI 21.01]
Learning Transferable Visual Models From Natural Language Supervision
https://arxiv.org/pdf/2103.00020.pdf
https://github.com/openai/CLIP
本来打算放在多模态模型汇总-按需更新里面的,发现CLIP内容比较多,实验很丰富,解读很详细,还是单独介绍一下。
以下都是直觉翻译和认知加工,如有问题,欢迎指正。
花了好几天,仍然没读完...
私以为本文的几个亮点:
- 双流模型,单独提取图片和文本feature;
- 对比学习;
- 将分类模型转换成图文匹配任务,用文本来弱监督图片分类。
背景
一般的目标检测,图片分类等CV任务,都会预设有哪些类别,要识别哪些种类。实际图片信息是很丰富的,除了这些预设的类别,其他的视觉信息没有被充分利用,如果还要识别图上其他类别,就需要再加标签。
如果图片有文本描述,通过文本监督学习更多的图片信息不失为一个好方法。
- Mori et al. (1999) 训练图文对,预测文本中的nouns和adjectives,探索基于图片检索的方式改进内容;
- Quattoni et al. (2007) 训练图文对,预测文本描述,来学习分类权重空间,学习更多视觉表达;
- Srivastava & Salakhutdinov (2012) 训练多模态Deep Boltzmann Machines,数据为top of low-level image和对应文本tag特征,学习深层表达;
- Joulin et al. (2016) 用CNN训练图文对,预测文本表述,来学习图片表达;
- 还有些将标题,描述和hashtag等元数据,和图片一起训练,预测这些元数据的模型;Li et al.(2017)扩展了这个方法,除了预测individual words,还会预测n-grams,并显示这种系统在zero-shot transfer到其他分类数据集上的能力,即基于这些数据集的dictionary学习视觉n-grams,预测目标类别能拿到最高分;
- VirTex, ICMLML等模型展现了transformer模型在语言模型,masked语言模型,对比学习中的潜力。
虽然1~6展示了多模态(图文)学习在学习模态表征上的能力,目前通过自然语言监督学习视觉表达的研究还是比较少。
- 这种方式得到的模型结果不是最佳,比如 Li et al. (2017) 通过zero-shot方式,在ImageNet数据集上,只得到11.5%的准确率,但SOTA是88.4%;
- 文本数据是无限的,但是视觉数据的标签是有限的,打标签很费人力。
本文提出CLIP,Contrastive Language–Image Pre-training,用4亿对来自网络的图文数据集,将文本作为图像标签,进行训练。进行下游任务时,只需要提供和图上的concepts对应的文本描述,就可以进行zero-shot transfer。
模型在30个CV数据集上做了实验,实验任务包括OCR, action recognition in videos, geo-localization, and many types of fine-grained object classification。模型在大部分的任务上都达到最佳。而且,一般不用再做specific training,就可以和其他baseline 模型媲美。
Model
数据:4亿个网络公开的图文对。为覆盖到更多的视觉concepts, 用了50w个query在搜索引擎搜索图片,一个query差不多有2w张图片。
输入:一个batch有N个图像文本对;
模型:对比学习,预测对图文数据,将图片分类任务转换成图文匹配任务:
- 双流,2个encoder分别处理文本和图片数据,text encoder使用Transformer,image encoder用了2种模型,ResNet和Vision Transformer(ViT);
a. 5种ResNet:ResNet-50, ResNet-101, EfficientNet-style的ResNet,包括RN50x4, RN50x16, RN50x64;
b. 3种ViT:ViT-B/32, ViT-B/16, ViT-L/14; - encoder representation直接线性投影到multi-modal embedding space;
- 计算2模态之间的cosine similarity,让N个匹配的图文对相似度最大,不匹配的图文对相似度最小;
- 对称的cross-entropy loss;
- 数据增强:对resized图片进行random square crop。
伪代码如下:
实验
1. Zero-shot Transfer
图片分类的zero-shot指的是对未知类别进行推理。
本文的zero-shot指的是对未知任务进行推理,通过zero-shot transfer衡量任务学习的能力。
Visual N-Grams (Li et al., 2017) 是第一个将zero-shot transfer应用到图片分类任务上的模型。模型用于学习长度为1~5grams的共142806个visual n-grams,对输入的图片,最大化对应的n-grams的probability。
同样的,CLIP在进行zero-shot transfer时,将数据集中的类别标签转换为文字描述,主要步骤如下:
- 输入:一张图片 + 所有类别转换的文本(100个类别就是100个文本描述);
- 转换向量:经过2个encoder,分别输出image和text的feature embedding;
- 计算cosine similarity;
- 预测类别:multinomial logistic regression classifier。
模型结果:以下3个数据集,CLIP的表现都要高于Visual N-Grams。
PROMPT ENGINEERING AND ENSEMBLING
有些CV分类数据集,类别用ID表示,有的有映射的文本描述。像Flower102和GTSRB数据集,缺乏映射关系,导致有些数据不能进行zero-shot transfer;
同义词也会困扰模型:如果文本输入只是一个类别,因为缺乏上下文,text encoder就不能很好的区分词义。比如Oxford-IIIT Pet 数据集中的boxer指代一种狗,但因为缺乏上下文信息,CLIP的text encoder学习的不够充分,以至于将boxer识别成一种运动。
CLIP的训练数据集中,很少出现一张图片对应一个单词的现象。一般来说,都是用一句话来形容图片。为了减少训练集和其他任务数据集的gap,使用prompt template。
Prompt template: “A photo of a {label}.”
比如,类别为狗,则输入文本为“A photo of a dog.”。
通过这种方式,模型在ImageNet上,提高了1.3%的准确率。
如果提供更细粒度的类别信息(可以理解为文本描述中加属性/类别标签),比如上文的“A photo of a {label}.”,细化到“A photo of a {label}, a type of pet.”或者‘A photo of a big {label}”。按照这种方式,在ImageNet数据集上,构建了80种不同形式的prompt template,准确率又提高了3.5%。
ANALYSIS OF ZERO-SHOT CLIP PERFORMANCE
-
zero-shot classifier表现怎么样?
参照模型Linear Probe on ResNet50:ResNet-50 + logistic regression。
下图显示了,在27个数据集中,CLIP在16个数据上表现更好。
zero-shot CLIP怎么做prediction?
zero-shot prediction
基于输入的图片,在类别描述中检索,找到最合适的类别。
"Ref:https://github.com/openai/CLIP"
import os
import clip
import torch
from torchvision.datasets import CIFAR100
# Load the model
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load('ViT-B/32', device)
# Download the dataset
cifar100 = CIFAR100(root=os.path.expanduser("~/.cache"), download=True, train=False)
# Prepare the inputs
image, class_id = cifar100[3637]
image_input = preprocess(image).unsqueeze(0).to(device)
text_inputs = torch.cat([clip.tokenize(f"a photo of a {c}") for c in cifar100.classes]).to(device)
#cifar每个类别,输入图片,检索匹配的类别
# Calculate features
with torch.no_grad():
image_features = model.encode_image(image_input)
text_features = model.encode_text(text_inputs)
# Pick the top 5 most similar labels for the image
image_features /= image_features.norm(dim=-1, keepdim=True)
text_features /= text_features.norm(dim=-1, keepdim=True)
similarity = (100.0 * image_features @ text_features.T).softmax(dim=-1)
values, indices = similarity[0].topk(5)
# Print the result
print("\nTop predictions:\n")
for value, index in zip(values, indices):
print(f"{cifar100.classes[index]:>16s}: {100 * value.item():.2f}%")
"""
Top predictions:
snake: 65.31%
turtle: 12.29%
sweet_pepper: 3.83%
lizard: 1.88%
crocodile: 1.75%
"""
Linear-probe evaluation
通过CLIP的image_encoder得到视觉向量,结合标签做Logistic Regression。
"Ref:https://github.com/openai/CLIP"
import os
import clip
import torch
import numpy as np
from sklearn.linear_model import LogisticRegression
from torch.utils.data import DataLoader
from torchvision.datasets import CIFAR100
from tqdm import tqdm
# Load the model
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load('ViT-B/32', device)
# Load the dataset
root = os.path.expanduser("~/.cache")
train = CIFAR100(root, download=True, train=True, transform=preprocess)
test = CIFAR100(root, download=True, train=False, transform=preprocess)
def get_features(dataset):
all_features = []
all_labels = []
with torch.no_grad():
for images, labels in tqdm(DataLoader(dataset, batch_size=100)):
features = model.encode_image(images.to(device))
all_features.append(features)
all_labels.append(labels)
return torch.cat(all_features).cpu().numpy(), torch.cat(all_labels).cpu().numpy()
# Calculate the image features
train_features, train_labels = get_features(train)
test_features, test_labels = get_features(test)
# Perform logistic regression
classifier = LogisticRegression(random_state=0, C=0.316, max_iter=1000, verbose=1) # c自定义
classifier.fit(train_features, train_labels)
# Evaluate using the logistic regression classifier
predictions = classifier.predict(test_features)
accuracy = np.mean((test_labels == predictions).astype(np.float)) * 100.
print(f"Accuracy = {accuracy:.3f}")
2. Data Overlap Analysis
预训练的数据集是否和下游数据集有重叠?如果有重叠,就会导致数据泄露,下游实验的结果就不可信。本文作者分析了训练集和下游任务的重叠程度,以及这种重叠对模型效果的影响。
- 对每个evaluation的数据集,做一个duplicate detector。对和训练集相似度高的数据进行人工审核,并确定一个threshold,保证高精度的同时最大化召回。通过这个threshold,将数据集ALL(evaluation的数据集)分解成2个子集:
a. Overlap:和训练集的相似度高于threshold的数据;
b. Clean:相似度低于threshold的数据。 - 计算CLIP zero-shot在ALL,Overlap和Clean这三种数据集上的准确率,ALL-Clean的准确率差距作为衡量标准;
- 因为overlap的程度比较轻,因此进行binomial significance test,使用Clean的准确率作为null hypothesis,计算Overlap的p-value。
作者在35个数据集上做了分析,其中,有9个数据集和训练集没有数据重叠。像e MNIST, CLEVR和GTSRB这类偏synthetic或者specialized的数据,往往不会被视为正常的图片,和训练集基本没有重叠。
重叠率最高的是Country211,重叠率达21.5%,它是从YFCC100M数据集剥离出来的,虽然重叠程度很高,但这种重叠率只带来0.2%的准确率的提升。也许是因为Country211主要是为了衡量geo-localization的ability,但在训练集中,文本描述并没有提及到image的location。
虽然但是,作者也提到是不是duplicate detector做的不够好。
- detector选择threshold时,考虑保精度同时要求有高召回,但也没有办法去检查完召回到的400million的样本;
- Overlap和Clean数据集分布可能发生很大变化,比如Kinetics-700数据集,重叠的数据集都是black transition frames,所以上图左,Detected Data Overlap,Kinetics-700的准确率,相比Clean,降低了20%。
Limitation
- 不是和SOTA的比较:以上的数据分析,都是和a linear classifier on top of ResNet-50 features进行比较,大部分的数据集,都有对应的SOTA模型。为了达到SOTA,zero-shot CLIP估计要提高1000x的算力,当前情况不支持;
- 在部分fine-grained分类上表现不佳:
a. 前面实验分析发现,模型不能很好的区分cars,species of flowers, 以及variants of aircraft;
b. abstract和systematic任务表现不好,比如统计图上object的数量;
c. 在训练集中基本不会出现的比较novel的任务,表现欠佳,比如classifying
the distance to the nearest car in a photo; - 训练集中没有出现的图片类型(out-of-distribution),表现不好,比如OCR识别数字效果可以,但是MNIST的准确率只有88%;
...