在人工智能领域,图像分类是一项基础且重要的任务。近年来,CLIP(Contrastive Language-Image Pretraining)模型凭借其独特的能力在图像分类等领域备受关注。本文将面向对人工智能、图像分类感兴趣的初学者,以及希望进一步了解 CLIP 模型在图像分类中应用的开发者,浅谈使用CLIP 模型进行图像分类:从基础使用到分类头构建。
有争议的地方,欢迎批评指正
0、前置知识准备
CLIP 模型的核心思想是通过对比学习,将图像和文本映射到同一个特征空间中。在预训练阶段,CLIP 模型会处理大量的图像 - 文本对,学习如何将图像和对应的文本描述关联起来。例如,对于一张猫的图片和 “一只猫” 的文本描述,CLIP 模型会学习到它们在特征空间中的相似表示。当进行图像分类时,我们将待分类的图像和一系列候选文本描述输入到 CLIP 模型中,模型会分别提取图像和文本的特征向量,然后计算它们之间的相似度,相似度最高的文本描述对应的类别,就被认为是图像的类别。
1、正常使用 CLIP 模型进行图像分类
案例:假设我们有一张宠物猫的照片,按照上述教程步骤,将图像输入模型,并准备 “a photo of a cat”“a photo of a dog”“a photo of a bird” 等文本描述。运行代码后,模型会计算图像与各个文本描述的相似度,最终输出的概率分布中,“a photo of a cat” 对应的概率会最高,从而正确将图像分类为猫。
安装所需库:首先确保已经安装了 PyTorch 和clip库。可以使用以下命令进行安装:
pip install torch torchvision clip
导入库和加载模型:在 Python 脚本中导入相关库,并加载 CLIP 模型。这里以 ViT-B/32 版本的 CLIP 模型为例:
import torch
import clip
from PIL import Image
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
准备图像和文本:加载待分类的图像,并对其进行预处理,同时准备好候选的文本描述。
image = Image.open("your_image.jpg")
image = preprocess(image).unsqueeze(0).to(device)
text = clip.tokenize(["a photo of a cat", "a photo of a dog", "a photo of a bird"]).to(device)
进行预测:将图像和文本输入到模型中,计算相似度并获取预测结果。
with torch.no_grad():
image_features = model.encode_image(image)
text_features = model.encode_text(text)
logits_per_image = (100.0 * image_features @ text_features.T)
probs = logits_per_image.softmax(dim=-1).cpu().numpy()
print("Label probs:", probs)
2、为 CLIP 模型额外构建分类头进行图像分类
虽然 CLIP 模型本身可以通过计算图像和文本的相似度进行图像分类,但在一些特定场景下,我们希望模型能够像传统的图像分类模型一样,直接输出类别标签。这时就需要额外构建分类头。分类头本质上是一个小型的神经网络,它接收 CLIP 模型提取的图像特征向量作为输入,然后通过一系列的线性变换和激活函数,将特征向量映射到具体的类别空间中。例如,如果我们有 10 个类别,分类头会将输入的特征向量转换为一个长度为 10 的向量,向量中的每个元素表示图像属于对应类别的概率。
什么时候额外构建分类头
-
微调 CLIP 以提高特定任务性能:
当零样本精度不够时,可以在特定数据集上微调 CLIP,并添加分类头以适应特定领域。
-
多标签分类或细粒度分类:
对于复杂分类任务(如同时预测多个标签),可以添加分类头并使用交叉熵损失进行训练。
-
低资源语言或专业领域:
在零样本性能不佳的情况下,可以通过有监督学习微调分类头。
2.1、CLIP 模型分类头的初始化
在 CLIP 模型中,最常用的初始化分类头的方式是使用预训练权重。因为 CLIP 模型在大规模的图像 - 文本对数据集上进行了预训练,学习到了丰富的视觉和语义特征。使用预训练权重来初始化分类头,可以将这些通用特征迁移到具体的分类任务中,大大减少了训练数据的需求和训练时间,同时也能取得较好的分类性能。
具体来说,将类别名称(如 “dog”)转换为文本提示(如 “A photo of a dog”),通过 CLIP 文本编码器得到嵌入向量,以此初始化分类头。这种方法能有效迁移 CLIP 在大规模文本数据上学习到的语义知识,尤其适用于零样本或小样本场景。
2.2、分类头的初始化过程
CLIP 原生分类逻辑:通过图像特征与文本特征的余弦相似度分类,本质是矩阵点积(归一化后):
分类头等价转换:将分类头定义为线性层 ,若令
,则分类头的输出等价于原生 CLIP 的相似度计算。
-
文本嵌入计算:
-
对每个类别,应用文本模板(如"a photo of a {class}")
-
使用 CLIP 的文本编码器将这些模板文本编码为嵌入向量
-
对这些嵌入向量进行归一化
-
-
权重矩阵构建:
-
将所有类别的嵌入向量堆叠成一个矩阵
-
应用 logit 缩放因子调整权重大小
-
调整矩阵形状以适应分类任务
-
2.3、案例
主要步骤 1:生成类别文本嵌入为每个目标类别(如 CIFAR-10 的 10 个类别)创建文本描述(通常使用类别名,如 "airplane", "automobile" 等),通过 CLIP 的文本编码器生成固定维度的特征向量(512 维)。
# 定义CIFAR-10的类别文本描述
cifar10_classes = ["airplane", "automobile", "bird", "cat", "deer",
"dog", "frog", "horse", "ship", "truck"]
# 生成文本嵌入
text_inputs = clip.tokenize(cifar10_classes).to(device) # 文本分词
with torch.no_grad(): # 不计算梯度,仅获取预训练特征
text_features = model.encode_text(text_inputs) # 形状为 [10, 512]
主要步骤 2:构建分类头并初始化权重将文本嵌入矩阵直接作为分类头线性层的权重矩阵,偏置项通常设为 0(因原生 CLIP 分类无偏置)。
# 构建分类头,输入维度=512(CLIP图像特征维度),输出维度=10(类别数)
classifier = nn.Linear(in_features=512, out_features=10, bias=False).to(device)
# 用文本嵌入初始化权重:W = text_features.T(注意维度匹配)
classifier.weight.data = text_features.T # 权重形状 [10, 512] → 与text_features [10, 512] 转置后一致
完整代码实现(含初始化步骤)
import clip
import torch
import torch.nn as nn
device = "cuda" if torch.cuda.is_available() else "cpu"
model, _ = clip.load("ViT-B/32", device=device, jit=False)
# 1. 生成目标类别文本嵌入
cifar10_classes = ["a photo of an airplane",
"a photo of an automobile",
"a photo of a bird",
"a photo of a cat",
"a photo of a deer",
"a photo of a dog",
"a photo of a frog",
"a photo of a horse",
"a photo of a ship",
"a photo of a truck"]
text_inputs = clip.tokenize(cifar10_classes).to(device)
with torch.no_grad():
text_features = model.encode_text(text_inputs) # [10, 512]
# 对每个嵌入向量进行L2归一化
text_features /= text_features.norm(dim=-1, keepdim=True)
# 2. 冻结图像编码器(可选:若微调分类头,可不冻结图像编码器)
for param in model.visual.parameters():
param.requires_grad = False # 仅训练分类头时冻结主干
# 3. 构建并初始化分类头
classifier = nn.Linear(in_features=512, out_features=10, bias=False).to(device)
classifier.weight.data = text_features.T # 关键初始化步骤
# 4. 组合模型定义
def clip_classifier(image):
image_features = model.visual(image) # [B, 512]
logits = classifier(image_features) # [B, 10],等价于图像特征与文本特征的点积
return logits
2.4、为什么CLIP文本编码器的嵌入可以用作分类头参数
CLIP的联合表示空间
CLIP模型通过对比学习训练,将图像和对应的文本描述映射到一个共享的表示空间中。在这个空间中:
-
相关的图像和文本嵌入会被拉近
-
不相关的图像和文本嵌入会被推远
这意味着当一个图像和一个类别的文本描述语义相关时,它们的嵌入向量在这个空间中会很接近。
零样本分类能力
CLIP的训练方式使得文本嵌入可以作为"类原型",这些文本嵌入成为分类权重后,分类过程实际上是在计算输入图像与每个类别描述之间的相似度。
技术实现上的对应关系
在上面的案例代码中,文本嵌入到分类权重的转换逻辑是:
-
文本嵌入已经被训练成与相应类别的图像嵌入相似
-
分类头是一个线性层,计算图像特征与权重的点积
-
当文本嵌入作为权重时,点积结果正好反映了图像与类别的相似度
归一化的重要性
text_features /= text_features.norm(dim=-1, keepdim=True)
3、总结:
本文围绕 CLIP 模型在图像分类中的应用,从基础原理到工程实现展开探讨:
-
CLIP 核心优势:通过对比学习构建图像 - 文本联合特征空间,无需类别标注数据,直接通过文本描述与图像的相似度匹配实现零样本分类,如用 “a photo of a cat” 等文本标签快速分类宠物图像。
-
基础用法:分四步实现 —— 安装依赖(PyTorch/CLIP 库)、加载模型(以 ViT-B/32 为例)、预处理图像与文本 token、计算相似度并输出概率分布,代码简明易懂,适合快速上手。
-
分类头构建:针对需直接输出类别标签的场景,构建基于线性层的分类头,利用 CLIP 文本编码器生成的类别嵌入向量初始化权重,本质是将 “图文相似度计算” 转化为 “特征空间中的线性映射”。优势在于迁移预训练语义知识,减少训练数据需求,尤其适合小样本任务。
-
技术关键点:文本嵌入需 L2 归一化以匹配 CLIP 原生对比学习的归一化逻辑;冻结图像编码器可固定底层特征提取能力,专注分类头优化;分类头权重与文本嵌入的转置匹配,实现数学上的等价转换。
CLIP 的跨模态特性为图像分类提供了灵活方案:零样本场景直接用文本描述分类,定制化任务可通过分类头结合传统分类逻辑。