首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【大模型学习 | CLIP 原理&实现】

【大模型学习 | CLIP 原理&实现】

原创
作者头像
九年义务漏网鲨鱼
修改2025-07-15 12:54:46
修改2025-07-15 12:54:46
1.8K1
举报

Learning Transferable Visual Models From Natural Language Supervision

作者在摘要中指出,传统的监督式学习方法限制了视觉模型的泛化能力,特别是在迁移到新任务或新类别时的能力有限。以往的图像识别任务通常依赖于人为定义的分类标签进行训练,这种方式不仅数据成本高,而且模型更容易过拟合于训练类别。为了解决这一问题,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 这样具有明确语言监督的图文对齐任务中,线性映射已经足够表达语义关系,且更稳定、更易训练;

代码语言:python
复制
# 线性映射文本、图像# 线性映射文本、图像# 线性映射文本、图像# 线性映射文本、图像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)

宽度

增加通道数

等比例增加 d_model

深度

增加层数

固定不变

分辨率

增加输入图像尺寸

不适用(文本长度固定)

三、代码实现

代码语言:python
复制
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 probabilities
  • QAT动态量化
代码语言:python
复制
from 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")
  • 微调
代码语言:python
复制
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 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Learning Transferable Visual Models From Natural Language Supervision
    • 一、数据缺陷
    • 二、预训练方法
    • 三、代码实现
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档