1. 从零开始:搭建你的第一个CLIP应用环境
嘿,朋友们,我是老张,在AI和智能硬件这行摸爬滚打了十几年。今天咱们不聊那些虚头巴脑的理论,直接上手干。如果你想快速搞出一个能“看懂”图片又能“听懂”人话的应用原型,CLIP绝对是你的首选。它就像一个天生的翻译官,能把图像和文本说到一块去。咱们的目标是,用最短的时间,最少的代码,让你亲眼看到这个模型能干出什么神奇的事儿。
首先,别被“多模态”、“对比学习”这些词吓到。你可以把CLIP想象成一个超级聪明的“连连看”玩家。给它一堆图片和一堆文字描述,它能飞快地把配对的图片和文字连起来,并且记住这种配对的感觉。下次你给它一张全新的图片,或者一句它从没听过的话,它也能凭着感觉,找到最匹配的那个。这就是它“零样本”能力的核心——不用专门教,自己就能举一反三。
好,废话不多说,咱们先把手头的家伙事儿准备好。我强烈推荐使用 Anaconda 来管理你的Python环境,它能帮你省去无数依赖包冲突的麻烦。打开你的终端(或者叫命令行、CMD),跟着我一步步来。
# 1. 创建一个新的虚拟环境,名字叫‘clip_demo’,Python版本用3.8或3.9都行,比较稳定。
conda create -n clip_demo python=3.9 -y
# 2. 激活这个环境。以后所有操作都在这个环境里进行。
conda activate clip_demo
# 3. 安装PyTorch。这是CLIP运行的基石。去PyTorch官网(https://pytorch.org/get-started/locally/)根据你的系统(Windows/Linux/macOS)和有无GPU(CUDA版本)复制安装命令。
# 举个例子,如果你有CUDA 11.7的GPU,就运行:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu117
# 如果你只有CPU(初期学习完全够用),运行:
# pip install torch torchvision torchaudio
# 4. 安装OpenAI的CLIP库。这是官方实现,最靠谱。
pip install ftfy regex tqdm
pip install git+https://github.com/openai/CLIP.git
安装过程中如果遇到网络问题,记得换个源或者科学……呃,我是说,保持网络通畅。装好之后,我们来写个“Hello World”级别的测试脚本,确保一切正常。
# test_clip_install.py
import torch
import clip
from PIL import Image
print(f"PyTorch版本: {torch.__version__}")
print(f"CUDA是否可用: {torch.cuda.is_available()}")
# 尝试加载最小的预训练模型‘ViT-B/32’,它速度快,占内存小,适合快速验证。
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
print("模型加载成功!")
print(f"模型结构: {model}")
运行这个脚本,如果没报错,并且打印出了模型结构,恭喜你,你的CLIP游乐场已经搭建完毕!这里我多说一句,preprocess 这个函数非常重要,它是官方提供的图像预处理管道,任何图片喂给模型前,都必须用它处理一下,转换成模型认识的“语言”。你不需要自己操心归一化、裁剪尺寸这些琐事,它全包了。
2. 核心玩法一:零样本图像分类,不用一张标注图片
传统做图像分类,你得收集成千上万张标好“猫”、“狗”、“汽车”的图片,然后吭哧吭哧训练好几天。有了CLIP,这事儿变得无比简单。你只需要告诉它有哪些类别,它就能直接开干。我当年第一次跑通这个例子的时候,感觉就像变魔术。
我们来实战一下,用CLIP给一些网络图片或者你自己的手机照片分个类。假设我们想区分“狗”、“猫”、“汽车”、“日落”、“披萨”这五类。
import torch
import clip
from PIL import Image
import requests
from io import BytesIO
# 1. 准备模型
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
# 2. 定义我们的候选类别
class_names = ["a dog", "a cat", "a car", "a sunset", "a pizza"]
# 注意,我们用英文短句描述,效果比单纯单词好。CLIP理解的是语义。
# 3. 为每个类别生成文本特征。这是关键一步!
text_inputs = torch.cat([clip.tokenize(f"a photo of a {c}") for c in class_names]).to(device)
with torch.no_grad(): # 不计算梯度,加快推理速度
text_features = model.encode_text(text_inputs)
# 对特征进行归一化,方便后续计算余弦相似度
text_features /= text_features.norm(dim=-1, keepdim=True)
# 4. 加载一张你想要分类的图片
# 方式一:从网络下载一张图片(确保你有网络)
image_url = "https://example.com/your_dog_image.jpg" # 替换成真实的图片URL
response = requests.get(image_url)
image = Image.open(BytesIO(response.content)).convert("RGB")
# 方式二:从本地文件加载
# image = Image.open("./my_pet.jpg").convert("RGB")
# 5. 预处理图片并提取图像特征
image_input = preprocess(image).unsqueeze(0).to(device) # unsqueeze增加一个批次维度
with torch.no_grad():
image_features = model.encode_image(image_input)
image_features /= image_features.norm(dim=-1, keepdim=True)
# 6. 计算“魔法”相似度
# 将图像特征和所有文本特征做点积(相当于余弦相似度,因为特征已经归一化了)
similarity = (100.0 * image_features @ text_features.T).softmax(dim=-1) # softmax转换成概率
values, indices = similarity[0].topk(5) # 取出相似度最高的5个结果
# 7. 打印结果
print("预测结果(从最可能到最不可能):")
for value, index in zip(values, indices):
print(f"{class_names[index]:>16s}: {100 * value.item():.2f}%")
跑一下这段代码,你会发现CLIP给出的概率分布。如果图片真是一只狗,那么“a dog”的概率通常会远高于其他类别。这里有个我踩过的坑:提示词(Prompt)的写法很关键。直接用单词“dog”和用短语“a photo of a dog”,效果可能有显著差异。因为CLIP是在“图像-文本对”上训练的,文本端很多都是完整的句子描述。所以,尽量用自然的、描述性的短语作为类别标签,你会得到更准的结果。
你可以多试几张图,比如找一张汉堡的图片,看看它会不会被误判成“披萨”。这种跨类别的混淆,正好能帮你理解模型语义空间的边界在哪里。
2.1 进阶技巧:提示工程让你的模型更聪明
上面我们用了一个固定的模板 “a photo of a {label}”。这只是一个基线。想让CLIP发挥出120%的实力?你得玩点“提示工程”。简单说,就是给模型更好的“问题描述”。
比如,我们要做一个艺术品分类,里面可能有“油画”、“水彩画”、“素描”。用基线模板可能效果一般,因为CLIP训练数据里有很多真实的照片。我们可以设计更贴合艺术品的提示:
art_prompts = [
“an oil painting of {}”,
“a watercolor painting of {}”,
“a pencil sketch of {}”,
“a digital artwork of {}”,
“a close-up detail of the {} painting”
]
我们的策略是,为每个类别,用多个不同的提示模板生成多个文本特征,然后把它们的特征求平均。这个平均后的特征,比单一提示的特征更鲁棒、更具代表性。这个过程叫做“提示集成”(Prompt Ensemble)。
def get_ensemble_text_features(class_names, model, templates, device):
all_text_features = []
for class_name in class_names:
# 为当前类别,生成所有模板下的文本
texts = [tmpl.format(class_name) for tmpl in templates]
# 分词并编码
text_inputs = torch.cat([clip.tokenize(t) for t in texts]).to(device)
with torch.no_grad():
text_features = model.encode_text(text_inputs)
text_features /= text_features.norm(dim=-1, keepdim=True)
# 对当前类别的多个特征取平均
class_feature = text_features.mean(dim=0, keepdim=True)
all_text_features.append(class_feature)
# 把所有类别的特征堆叠起来
all_text_features = torch.cat(all_text_features, dim=0)
# 再次整体归一化
all_text_features /= all_text_features.norm(dim=-1, keepdim=True)
return all_text_features
# 使用
templates = [“a photo of a {}”, “a pictu


3801

被折叠的 条评论
为什么被折叠?



