作者在摘要中指出,传统的监督式学习方法限制了视觉模型的泛化能力,特别是在迁移到新任务或新类别时的能力有限。以往的图像识别任务通常依赖于人为定义的分类标签进行训练,这种方式不仅数据成本高,而且模型更容易过拟合于训练类别。为了解决这一问题,CLIP 提出了一个新的预训练框架:利用网络上现成的大规模图文对(如标题+图像)作为监督信号,将图像与自然语言描述进行匹配,从而在无需特定分类标签的情况下,学习具有通用性的视觉表征。
🔴 目前的图像数据存在着质量低、数据量不足的情况,例如 ① MS-COCO 虽然是高质量的标注数据,但也只有十万张图像,在目前的视觉系统来说属于小数据量; ② YFCC100M 有 一亿张图像,但是质量低,过滤后只有1500万张图像,与 ImageNet数据大小相同;现有视觉系统尚未能充分利用这些语言资源中的监督信息,限制了自然语言在视觉模型中的潜力发挥。
🟢 作者构建了一个4亿对的数据集WIT(图像,文本对),根据50万个关键词搜集相关的图文对,最后数据的文本数与GPT-2训练所需数据集大小相似;
🔴 作者首先尝试了直接采用联合学习的方式,采用CNN和Transformer来预测图像标题;(Transformer采用了6300万个参数,识别图像的类别会比训练一个词袋模型慢三倍);这两种方法来学习识别图像都有一个相同点:预测图像的准确文字;

🟢 作者提出一种图文匹配的更简单的代理任务,而不是精确预测每个单词;给定 $N \times N$ 的图文对,在 CLIP 中,图像和文本分别通过两个独立的编码器(视觉 Transformer 和文本 Transformer)进行编码,基于余弦相似度学习多模态的嵌入空间,最大化配对图文之间的相似度,最小化不匹配对的相似度,并通过symeertric entropy loss优化相似得分:
🟢 训练模型并没有采用预训练权重模型,而是从头训练;并通过线性映射将不同模态编码器的表示映射到嵌入空间中;
🧠 为什么不采用非线性映射?
作者并没有发现线性映射和非线性映射的差别,并推测非线性映射在一些纯自监督任务中(如 SimCLR、MoCo)可能会适应图像中更细节的特征,形成一种“共适应(co-adaptation)”,但在 CLIP 这样具有明确语言监督的图文对齐任务中,线性映射已经足够表达语义关系,且更稳定、更易训练;
# 线性映射文本、图像# 线性映射文本、图像# 线性映射文本、图像# 线性映射文本、图像te'z
I_e = l2_normalize(np.dot(I_f, W_i), axis=1)
T_e = l2_normalize(np.dot(T_f, W_t), axis=1)
# 图文相似矩阵向量
logits = np.dot(I_e, T_e.T) * np.exp(t)师弟
# symeertric entropy loss
loss_i = cross_entropy_loss(logits, labels, axis=0) # 图像 → 文本,即图像作为query
loss_t = cross_entropy_loss(logits, labels, axis=1) # 文本 → 图像,即文本作为query
loss = (loss_i + loss_t) / 2🟢 在图像特征提取模型上,作者采用了两种架构: ① ResNet modifications (We also replace the global average pooling layer with an attention pooling mechanism.) ② Vit (with only the minor modification of adding an additional layer normalization)
🧠 为什么在将 patch + 位置编码输入 Transformer 之前加入layer normalization?
稳定训练过程:合并后的向量(patch + 位置编码)可能存在数值不一致(例如 patch embedding 和 position embedding 的分布不同),LayerNorm 有助于规范化 patch + 位置编码的数值分布,防止某些 token 占主导。
🟢 文本编码器:① CBOW ② Transformer 12层 512维 8个注意力头
维度 | 视觉编码器(如 ResNet) | 文本编码器(Transformer) |
|---|---|---|
宽度 | 增加通道数 | 等比例增加 |
深度 | 增加层数 | 固定不变 |
分辨率 | 增加输入图像尺寸 | 不适用(文本长度固定) |

from PIL import Image
import requests
from transformers import CLIPProcessor, CLIPModel
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-large-patch14")
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
inputs = processor(text=["a photo of a cat", "a photo of a dog"], images=image, return_tensors="pt", padding=True)
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image # this is the image-text similarity score
probs = logits_per_image.softmax(dim=1) # we can take the softmax to get the label probabilitiesfrom transformers import CLIPModel
import torch
import torch.quantization
model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14")
# 对 Linear 层做动态量化
quantized_model = torch.quantization.quantize_dynamic(
model,
{torch.nn.Linear}, # 可以加上 torch.nn.LayerNorm 等
dtype=torch.qint8
)
# 保存模型
torch.save(quantized_model.state_dict(), "clip_quantized.pth")import torch
from torch import nn
from torch.utils.data import DataLoader, Dataset
from transformers import CLIPProcessor, CLIPModel
from PIL import Image
# 1. 加载模型和 processor
model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
# 2. 冻结图像和文本编码器(只训练投影头)
for param in model.vision_model.parameters():
param.requires_grad = False
for param in model.text_model.parameters():
param.requires_grad = False
# 3. 自定义小数据集(图像路径 + 对应文本)
class MyDataset(Dataset):
def __init__(self, image_paths, texts):
self.image_paths = image_paths
self.texts = texts
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
image = Image.open(self.image_paths[idx]).convert("RGB")
text = self.texts[idx]
return image, text
# 替换为你自己的图像路径和描述文本
image_paths = ["cat.jpg", "dog.jpg", "apple.jpg"]
texts = ["a photo of a cat", "a photo of a dog", "a photo of an apple"]
dataset = MyDataset(image_paths, texts)
dataloader = DataLoader(dataset, batch_size=2, shuffle=True)
# 4. 定义优化器(只优化 projection 层)
optimizer = torch.optim.AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=5e-5)
device = "cuda" if torch.cuda.is_available() else "cpu"
model.to(device)
# 5. 训练 loop
for epoch in range(10):
model.train()
total_loss = 0
for images, texts in dataloader:
inputs = processor(text=texts, images=images, return_tensors="pt", padding=True, truncation=True).to(device)
outputs = model(**inputs)
logits_per_image = outputs.logits_per_image # [batch, batch]
logits_per_text = outputs.logits_per_text # [batch, batch]
ground_truth = torch.arange(len(images)).to(device)
# CLIP 的双向对比损失
loss_i = nn.CrossEntropyLoss()(logits_per_image, ground_truth)
loss_t = nn.CrossEntropyLoss()(logits_per_text, ground_truth)
loss = (loss_i + loss_t) / 2
loss.backward()
optimizer.step()
optimizer.zero_grad()
total_loss += loss.item()
print(f"Epoch {epoch+1}, Loss: {total_loss:.4f}")原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。