CLIP微调实战:零样本分类代码实现与避坑指南

1次阅读
没有评论

共计 3361 个字符,预计需要花费 9 分钟才能阅读完成。

image.webp

背景介绍:为什么选择 CLIP 做零样本分类

CLIP(Contrastive Language-Image Pretraining)是 OpenAI 推出的多模态模型,其核心思想是通过对比学习对齐图像和文本的嵌入空间。在零样本分类任务中,CLIP 具有三大独特优势:

CLIP 微调实战:零样本分类代码实现与避坑指南

  • 无需训练数据 :直接使用自然语言描述类别
  • 跨模态理解 :自动建立视觉概念与文本标签的关联
  • 强泛化能力 :在预训练时已见过海量开放域数据

技术选型:微调策略对比

实际应用时通常需要微调以适应特定领域,主流方案有两种:

  1. 全参数微调
  2. 优点:充分调整模型参数,可能达到更高准确率
  3. 缺点:显存占用大,容易过拟合

  4. 适配器微调

  5. 优点:仅训练少量新增参数,显存友好
  6. 缺点:性能上限受限于固定主干网络

对于大多数场景,推荐先尝试适配器微调,当数据量充足(>10 万样本)时再考虑全参数微调。

核心实现详解

数据预处理流程

from PIL import Image
import torchvision.transforms as T

# CLIP 官方推荐的预处理流程
preprocess = T.Compose([T.Resize(224, interpolation=T.InterpolationMode.BICUBIC),
    T.CenterCrop(224),
    T.ToTensor(),
    T.Normalize(mean=(0.48145466, 0.4578275, 0.40821073), 
        std=(0.26862954, 0.26130258, 0.27577711)
    )
])

def load_image(path):
    image = Image.open(path).convert('RGB')
    return preprocess(image)

关键点:
– 必须使用 CLIP 训练时的相同归一化参数
– 输入分辨率固定为 224×224
– 避免使用随机增强以免破坏图文对齐关系

损失函数设计

采用对称交叉熵损失(Symmetrical Cross Entropy):

import torch.nn.functional as F

def clip_loss(image_features, text_features, temperature=0.07):
    # 计算相似度矩阵
    logits = (text_features @ image_features.T) / temperature

    # 双向交叉熵
    labels = torch.arange(len(logits)).to(device)
    loss_i = F.cross_entropy(logits, labels)
    loss_t = F.cross_entropy(logits.T, labels)
    return (loss_i + loss_t)/2

训练策略

推荐使用余弦退火学习率调度配合早停:

from torch.optim import AdamW
from torch.optim.lr_scheduler import CosineAnnealingLR

optimizer = AdamW(model.parameters(), lr=5e-5, weight_decay=0.01)
scheduler = CosineAnnealingLR(optimizer, T_max=100)

# 早停实现示例
best_loss = float('inf')
patience = 3
counter = 0

for epoch in range(100):
    train_loss = train_one_epoch()
    val_loss = validate()

    if val_loss < best_loss:
        best_loss = val_loss
        counter = 0
        torch.save(model.state_dict(), 'best_model.pt')
    else:
        counter += 1
        if counter >= patience:
            break

完整代码示例

import torch
from transformers import CLIPModel, CLIPProcessor

class CustomCLIP(torch.nn.Module):
    def __init__(self, model_name='openai/clip-vit-base-patch32'):
        super().__init__()
        self.clip = CLIPModel.from_pretrained(model_name)
        # 添加适配器层
        self.adapter = torch.nn.Linear(512, 512, bias=False)

    def forward(self, images, texts):
        outputs = self.clip(input_ids=texts['input_ids'],
            attention_mask=texts['attention_mask'],
            pixel_values=images['pixel_values'],
            return_dict=True
        )
        # 只微调解码器部分
        image_embeds = self.adapter(outputs.image_embeds)
        text_embeds = outputs.text_embeds
        return image_embeds, text_embeds

# 使用示例
model = CustomCLIP().to(device)
processor = CLIPProcessor.from_pretrained('openai/clip-vit-base-patch32')

# 准备输入
text_inputs = processor(text=["a photo of a cat", "a photo of a dog"], 
    return_tensors="pt", 
    padding=True
).to(device)

image_inputs = processor(images=[Image.open('cat.jpg')], 
    return_tensors="pt"
).to(device)

# 前向传播
image_embeds, text_embeds = model(image_inputs, text_inputs)
loss = clip_loss(image_embeds, text_embeds)

性能优化技巧

显存优化

  1. 使用梯度检查点

    model.clip.vision_model.set_gradient_checkpointing(True)
    model.clip.text_model.set_gradient_checkpointing(True)

  2. 混合精度训练

    scaler = torch.cuda.amp.GradScaler()
    
    with torch.cuda.amp.autocast():
        image_embeds, text_embeds = model(inputs)
        loss = clip_loss(image_embeds, text_embeds)
    
    scaler.scale(loss).backward()
    scaler.step(optimizer)
    scaler.update()

推理加速

  • 将文本编码预先计算并缓存
  • 使用 ONNX Runtime 进行部署

常见问题及解决方案

类别不平衡

  • 在计算 loss 时添加类别权重
    weights = torch.tensor([0.8, 0.2])  # 根据样本比例设置
    loss = F.cross_entropy(logits, labels, weight=weights)

过拟合预防

  • 添加 Dropout 层
    self.dropout = torch.nn.Dropout(0.1)  # 在适配器后添加 
  • 使用更强的权重衰减(weight_decay=0.1)

提示词工程

  • 统一使用 ”a photo of a {label}” 格式
  • 对抽象概念添加说明,如 ”a diagram showing {concept}”

延伸应用

这套方法可以轻松扩展到其他领域:

  1. 医疗影像分类:使用 ”a medical scan showing {disease}”
  2. 艺术品识别:”a painting in the style of {artist}”
  3. 工业质检:”a defective product with {flaw}”

通过调整提示词模板,同一套代码可以复用到各种零样本分类场景。建议读者在自己的领域数据集上尝试不同的文本描述方式,往往能获得意想不到的效果提升。

正文完
 0
评论(没有评论)