BLIP/BLIP-2 实战原理与最小推理示例

副标题 / 摘要 BLIP 以对齐 + 生成的联合目标打通图文理解,BLIP-2 则用 Q-Former 桥接冻结视觉编码器与 LLM。本文提供最小推理示例与工程落地要点,适合入门与实战上手。 预计阅读时长:15~18 分钟 标签:blip、blip2、pytorch、inference SEO 关键词:BLIP, BLIP-2, PyTorch, 多模态, 推理示例 元描述:对比 BLIP 与 BLIP-2 架构目标,并提供最小 PyTorch 推理代码。 目标读者 想快速上手 BLIP/BLIP-2 的入门读者 需要多模态推理 Demo 的工程实践者 关注图文检索与生成落地的产品/研发团队 背景 / 动机 多模态应用最常见的能力是“图像理解 + 文本生成”。 BLIP 提供了统一的多目标训练框架,BLIP-2 则强调低成本适配大语言模型。 理解两者差异,有助于快速做出工程选型。 核心概念 图像编码器:提取视觉特征(CNN/ViT)。 文本解码器:生成描述、回答问题。 Q-Former:BLIP-2 的桥接模块,从视觉特征提取可被 LLM 使用的查询向量。 多目标训练:对比学习(ITC)+ 匹配(ITM)+ 生成(LM)。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 BLIP:一个模型同时学习“图文对齐”和“文本生成”。 BLIP-2:冻结视觉与语言主干,只训练中间桥接层,迁移更快。 基础示例(1) 输入一张图片,输出一句描述: 图片:白色背景的物体 输出:“a white object on a plain background” 基础示例(2) 输入图片 + 问题,输出答案: ...

2026年1月24日 · 3 分钟 · map[name:Jeanphilo]

BLIP 与 BLIP-2 架构和区别:从对齐到生成

副标题 / 摘要 BLIP 用对齐与生成联合训练打通图文理解,BLIP-2 则用 Q-Former 连接视觉编码器与冻结大语言模型。本文以架构与目标为主线,讲清两者差异与工程选择。 预计阅读时长:16~20 分钟 标签:blip、blip2、multimodal SEO 关键词:BLIP, BLIP-2, 架构, 多模态, 图文对齐 元描述:对比 BLIP 与 BLIP-2 的架构、训练目标与落地场景。 目标读者 想快速理解 BLIP/BLIP-2 架构的入门读者 需要评估多模态方案落地路径的工程实践者 关注图文检索与生成的产品/研发团队 背景 / 动机 多模态模型要解决的核心是“视觉与语言对齐”。 BLIP 给出了一套训练目标组合,能同时做检索与生成; BLIP-2 则在大模型时代强调“参数高效 + 模块可替换”。 核心概念 图像编码器:将图像映射到视觉特征空间。 文本编码器/解码器:理解文本或生成文本。 Q-Former:BLIP-2 用于桥接视觉特征与 LLM 的查询变换器。 对齐目标:对比学习 + 匹配 + 生成的组合。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 BLIP:用三类目标(对比、匹配、生成)训练一个“理解 + 生成”多模态模型。 BLIP-2:冻结视觉编码器和大语言模型,仅训练中间桥接模块,实现高效迁移。 基础示例(1) 输入一张图片,BLIP/BLIP-2 输出一条描述。 基础示例(2) 输入“这张图里有什么?”模型返回简短回答。 实践指南 / 步骤 明确任务:检索、描述生成、VQA 或多任务。 选模型:需要端到端微调 → BLIP;希望高效适配 LLM → BLIP-2。 准备数据:图文对、问答对或描述数据。 选择推理接口(Transformers 或自有服务)。 评估指标:检索 Recall@K、caption BLEU/CIDEr、VQA accuracy。 可运行示例(BLIP 与 BLIP-2 推理) # pip install transformers torchvision pillow from transformers import BlipProcessor, BlipForConditionalGeneration from transformers import Blip2Processor, Blip2ForConditionalGeneration from PIL import Image image = Image.new("RGB", (224, 224), color="white") # BLIP caption blip_processor = BlipProcessor.from_pretrained("Salesforce/blip-image-captioning-base") blip_model = BlipForConditionalGeneration.from_pretrained("Salesforce/blip-image-captioning-base") inputs = blip_processor(image, return_tensors="pt") out = blip_model.generate(**inputs, max_new_tokens=20) print(blip_processor.decode(out[0], skip_special_tokens=True)) # BLIP-2 caption blip2_processor = Blip2Processor.from_pretrained("Salesforce/blip2-opt-2.7b") blip2_model = Blip2ForConditionalGeneration.from_pretrained("Salesforce/blip2-opt-2.7b") inputs = blip2_processor(image, return_tensors="pt") out = blip2_model.generate(**inputs, max_new_tokens=20) print(blip2_processor.decode(out[0], skip_special_tokens=True)) C — Concepts(核心思想) 方法类型 BLIP/BLIP-2 属于多模态对齐 + 生成式视觉语言模型范式。 ...

2026年1月24日 · 3 分钟 · map[name:Jeanphilo]

对比学习损失函数系列(1/4):对比损失 Contrastive Loss

副标题 / 摘要 对比损失是度量学习最经典的成对目标:拉近同类、推远异类。本文用公式、几何直觉与最小可运行实验,帮你建立对比学习的第一块基石。 预计阅读时长:15~18 分钟 标签:contrastive-loss、metric-learning、pairwise SEO 关键词:对比损失, Contrastive Loss, 度量学习, 嵌入空间 元描述:讲清对比损失的数学形式、训练细节与工程应用场景。 系列导航 (1/4)对比损失 Contrastive Loss(本文) (2/4)三元组损失 Triplet Loss (3/4)InfoNCE + SimCLR (4/4)CLIP 对比学习目标 目标读者 想入门对比学习/度量学习的初学者 需要在工程中构建相似度模型的开发者 希望通过小实验理解公式含义的实践派 背景 / 动机 在推荐、检索、验证类任务里,我们往往不关心“分类标签”,而关心“相似度”。 对比损失用成对样本表达“相似/不相似”,是把语义关系映射到向量空间的基础方法。 核心概念 嵌入空间:把样本映射为向量,距离代表语义相近程度。 正负样本对:正样本对应“相似”,负样本对对应“不相似”。 Margin:负样本需要被推远的最小距离阈值。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 对比损失做的事很简单: 同类样本对要靠得更近。 异类样本对要至少分开一个 margin。 基础示例(1) 两张同一人的人脸:距离应该变小。 两个不同人的人脸:距离至少大于 margin。 基础示例(2) 同类商品图片:嵌入距离小。 异类商品图片:嵌入距离大。 实践指南 / 步骤 选择特征编码器(如 MLP/CNN)。 构造正负样本对,并标记 y=1/0。 计算成对距离并应用对比损失。 观察正负样本平均距离是否分离。 可运行示例(最小对比损失实验) import random import torch import torch.nn as nn import torch.nn.functional as F random.seed(42) torch.manual_seed(42) def make_data(n=200): c1 = torch.randn(n, 2) * 0.4 + torch.tensor([0.0, 0.0]) c2 = torch.randn(n, 2) * 0.4 + torch.tensor([3.0, 3.0]) x = torch.cat([c1, c2], dim=0) y = torch.cat([torch.zeros(n), torch.ones(n)]).long() return x, y def make_pairs(x, y, num_pairs=1000): pairs = [] labels = [] for _ in range(num_pairs): if random.random() < 0.5: cls = random.randint(0, 1) idx = (y == cls).nonzero().flatten() i, j = idx[torch.randint(len(idx), (2,))] labels.append(1) else: i = (y == 0).nonzero().flatten()[torch.randint((y == 0).sum(), (1,))] j = (y == 1).nonzero().flatten()[torch.randint((y == 1).sum(), (1,))] labels.append(0) pairs.append((x[i], x[j])) return torch.stack([p[0] for p in pairs]), torch.stack([p[1] for p in pairs]), torch.tensor(labels) def contrastive_loss(z1, z2, y, margin=1.0): d = F.pairwise_distance(z1, z2) pos = y * d.pow(2) neg = (1 - y) * F.relu(margin - d).pow(2) return (pos + neg).mean() class Encoder(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Linear(2, 32), nn.ReLU(), nn.Linear(32, 2), ) def forward(self, x): return self.net(x) x, y = make_data() x1, x2, pair_y = make_pairs(x, y, num_pairs=2000) model = Encoder() opt = torch.optim.Adam(model.parameters(), lr=1e-2) for epoch in range(1, 201): z1 = model(x1) z2 = model(x2) loss = contrastive_loss(z1, z2, pair_y.float(), margin=1.0) opt.zero_grad() loss.backward() opt.step() if epoch % 50 == 0: with torch.no_grad(): d = F.pairwise_distance(z1, z2) pos_d = d[pair_y == 1].mean().item() neg_d = d[pair_y == 0].mean().item() print(f"epoch={epoch} loss={loss.item():.4f} pos_d={pos_d:.3f} neg_d={neg_d:.3f}") C — Concepts(核心思想) 方法类型 对比损失属于度量学习 / 表示学习范式,使用成对样本将语义关系映射到向量距离。 ...

2026年1月24日 · 3 分钟 · map[name:Jeanphilo]

对比学习损失函数系列(2/4):三元组损失 Triplet Loss

副标题 / 摘要 Triplet Loss 用“相对排序”表达语义约束:让 anchor 更接近 positive,同时远离 negative。本文包含公式、难例挖掘与最小实验,帮助你把三元组损失用于工程实践。 预计阅读时长:16~20 分钟 标签:triplet-loss、metric-learning、hard-negative SEO 关键词:Triplet Loss, 三元组损失, 度量学习, hard negative 元描述:系统拆解 Triplet Loss 的训练逻辑、采样策略与工程场景。 系列导航 (1/4)对比损失 Contrastive Loss (2/4)三元组损失 Triplet Loss(本文) (3/4)InfoNCE + SimCLR (4/4)CLIP 对比学习目标 目标读者 已了解对比损失,希望理解更强排序约束的读者 需要构建相似度排序系统的工程实践者 想掌握 hard negative mining 逻辑的入门者 背景 / 动机 成对对比只能表达“像 / 不像”,而很多场景需要相对排序: “与 A 更像,而不是 B”。Triplet Loss 用三元组直接编码这种关系, 在检索与验证任务中非常常见。 核心概念 Anchor / Positive / Negative:锚点、同类样本、异类样本。 Margin:要求 anchor 与 negative 至少比 positive 远一个 margin。 Hard Negative Mining:选择最难的负样本提升训练信号。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 Triplet Loss 让“正确的相对关系”成立: ...

2026年1月24日 · 3 分钟 · map[name:Jeanphilo]

对比学习损失函数系列(3/4):InfoNCE 与 SimCLR

副标题 / 摘要 InfoNCE 是现代对比学习的核心损失,SimCLR 则把它推向实用化。本文用公式、步骤与最小实验,带你理解“批内负样本 + 增强视图”的训练逻辑。 预计阅读时长:18~22 分钟 标签:infonce、simclr、self-supervised SEO 关键词:InfoNCE, SimCLR, 对比学习, 自监督 元描述:讲清 InfoNCE 的数学目标与 SimCLR 的训练结构,含可运行代码示例。 系列导航 (1/4)对比损失 Contrastive Loss (2/4)三元组损失 Triplet Loss (3/4)InfoNCE + SimCLR(本文) (4/4)CLIP 对比学习目标 目标读者 希望入门自监督对比学习的读者 需要理解 SimCLR 训练流程的工程实践者 想把对比学习迁移到业务数据的开发者 背景 / 动机 有标注数据昂贵,而无标注数据充足。 InfoNCE 让我们用“正负样本对齐”替代人工标签, SimCLR 则证明:只要数据增强和 batch 够大,效果可以接近监督学习。 核心概念 正样本视图:同一图像的两种增强视图。 批内负样本:同一 batch 中其他样本视为负样本。 投影头:把表示映射到对比空间,提高对比学习效果。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 InfoNCE 的核心是“在一堆负样本里找到正确配对”。 SimCLR 则把“正确配对”定义为同一张图像的两个增强视图。 基础示例(1) 图像 A 经过两种增强得到 A1 与 A2 目标:A1 与 A2 相似度最大化 基础示例(2) A1 在 batch 中看到 B1、C1 等视为负样本 目标:A1 与 A2 的相似度高于 A1 与其他样本 实践指南 / 步骤 设计增强策略(裁剪、翻转、颜色扰动)。 构造两份增强视图作为正样本对。 编码器 + 投影头输出对比向量。 使用 InfoNCE 计算对比损失并训练。 可运行示例(最小 SimCLR 实验) import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import DataLoader from torchvision import datasets, transforms torch.manual_seed(42) class TwoCrops: def __init__(self, base_transform): self.base = base_transform def __call__(self, x): return self.base(x), self.base(x) def info_nce(z1, z2, temp=0.5): z1 = F.normalize(z1, dim=1) z2 = F.normalize(z2, dim=1) logits = z1 @ z2.T / temp labels = torch.arange(z1.size(0), device=z1.device) loss1 = F.cross_entropy(logits, labels) loss2 = F.cross_entropy(logits.T, labels) return (loss1 + loss2) / 2 class Encoder(nn.Module): def __init__(self, out_dim=128): super().__init__() self.backbone = nn.Sequential( nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(), nn.AdaptiveAvgPool2d(1), nn.Flatten(), ) self.proj = nn.Sequential( nn.Linear(32, 128), nn.ReLU(), nn.Linear(128, out_dim), ) def forward(self, x): x = self.backbone(x) return self.proj(x) base_tf = transforms.Compose( [ transforms.RandomResizedCrop(32, scale=(0.6, 1.0)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), ] ) dataset = datasets.FakeData( size=512, image_size=(3, 32, 32), num_classes=10, transform=TwoCrops(base_tf), ) loader = DataLoader(dataset, batch_size=128, shuffle=True) model = Encoder() opt = torch.optim.Adam(model.parameters(), lr=1e-3) for epoch in range(1, 6): total = 0.0 for (x1, x2), _ in loader: z1 = model(x1) z2 = model(x2) loss = info_nce(z1, z2, temp=0.5) opt.zero_grad() loss.backward() opt.step() total += loss.item() print(f"epoch={epoch} loss={total/len(loader):.4f}") C — Concepts(核心思想) 方法类型 InfoNCE 与 SimCLR 属于自监督对比学习,通过增强视图构造正样本对。 ...

2026年1月24日 · 3 分钟 · map[name:Jeanphilo]

对比学习损失函数系列(4/4):CLIP 对比学习目标

副标题 / 摘要 CLIP 把图像与文本放到同一嵌入空间,用双向 InfoNCE 进行对齐。本文从损失函数视角梳理 CLIP 的训练目标,并给出最小可运行示例。 预计阅读时长:14~18 分钟 标签:clip、multimodal、contrastive-learning SEO 关键词:CLIP, 对比学习, 多模态, InfoNCE 元描述:从损失函数角度拆解 CLIP 的双向对齐目标与工程应用。 系列导航 (1/4)对比损失 Contrastive Loss (2/4)三元组损失 Triplet Loss (3/4)InfoNCE + SimCLR (4/4)CLIP 对比学习目标(本文) 目标读者 想理解 CLIP 训练目标与公式的读者 需要在工程中使用图文对齐模型的实践者 希望把对比学习扩展到多模态的开发者 背景 / 动机 相比单模态对比学习,CLIP 的挑战在于“跨模态对齐”。 只要目标函数对齐得当,图像与文本就能通过相似度统一度量。 核心概念 图像/文本编码器:分别把图像与文本映射为向量。 双向对齐:图像检索文本 + 文本检索图像。 温度参数:控制相似度分布的尖锐程度。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 CLIP 的损失可以理解为“图像-文本的双向匹配”。 在一个 batch 中,正确图文对要排在最前面。 基础示例(1) 图像:一只狗 文本:“a photo of a dog” 与 “a red car” 目标:图像与狗文本更相近 基础示例(2) 在相似度矩阵中,对角线应该最大。 实践指南 / 步骤 图像与文本分别编码成向量。 L2 归一化,计算相似度矩阵。 用双向交叉熵训练(图像检索文本 + 文本检索图像)。 监控相似度矩阵是否“对角线突出”。 可运行示例(最小 CLIP 损失) import torch import torch.nn.functional as F torch.manual_seed(42) N, D = 4, 8 image = F.normalize(torch.randn(N, D), dim=-1) text = F.normalize(torch.randn(N, D), dim=-1) logits = image @ text.T / 0.07 labels = torch.arange(N) loss_i = F.cross_entropy(logits, labels) loss_t = F.cross_entropy(logits.T, labels) loss = (loss_i + loss_t) / 2 print(loss.item()) C — Concepts(核心思想) 方法类型 CLIP 属于多模态对比学习,核心是对齐图像与文本的共享嵌入空间。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

CLIP 系列(1/3):原理与对比学习公式——多模态对齐的核心机制

副标题 / 摘要 CLIP 通过对比学习把图像与文本映射到同一嵌入空间。本文以数学公式为主线,解释训练目标、损失函数与相似度计算,帮助你掌握多模态对齐的核心机制。 预计阅读时长:15~20 分钟 标签:clip、contrastive-learning、multimodal、infonce SEO 关键词:CLIP, 对比学习, 多模态, InfoNCE, 图文对齐 元描述:用公式与直觉讲清 CLIP 的对比学习目标、相似度计算与嵌入空间设计。 系列导航 (1/3)原理与对比学习公式(本文) (2/3)PyTorch 完整可复现实战 (3/3)工程化与优化 目标读者 想系统理解 CLIP 原理与数学目标的初学者 需要把对比学习迁移到工程场景的中级开发者 想搭建多模态系统、关注检索与零样本分类的应用型读者 背景 / 动机 传统图像分类需要固定标签集,而现实世界的描述更自然地以语言表达。 CLIP 的价值在于把视觉与语言放到同一空间里,通过相似度完成“检索”和“分类”,让模型具备零样本泛化能力。 要理解 CLIP,核心不是“模型多大”,而是对比学习目标如何让图文对齐。 核心概念 对比学习(Contrastive Learning):让“正样本对”更近,“负样本对”更远。 共享嵌入空间:图像与文本映射到同一向量空间,用相似度统一度量。 温度参数(Temperature):控制相似度分布的“尖锐度”,影响训练稳定性。 对称目标:图像检索文本 + 文本检索图像,双向一致。 A — Algorithm(题目与算法) 用通俗语言说明主题内容 CLIP 做的事很直接: 用图像编码器把图片变成向量 v_i。 用文本编码器把描述变成向量 t_i。 在同一个空间里对齐 v_i 与 t_i,用相似度度量它们“匹配”的程度。 训练时让正确配对的图文更近、错误配对更远。 基础示例(1) 图片:一只狗 文本 A:“一只狗在草地上” 文本 B:“一辆红色汽车” 训练后应满足:sim(图像, 文本A) > sim(图像, 文本B)。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]

CLIP 系列(2/3):PyTorch 完整可复现实战——从数据到训练闭环

副标题 / 摘要 这篇文章给出一个“最小但完整”的 CLIP 训练闭环:CIFAR-10 图像 + 文本提示,配套可直接运行的 PyTorch 脚本,确保你可以本地复现训练与零样本分类。 预计阅读时长:20~25 分钟 标签:clip、pytorch、reproducible、cifar10 SEO 关键词:CLIP, PyTorch, 可复现, CIFAR10, 对比学习 元描述:从数据准备到训练与评估,给出完整可复现的 CLIP PyTorch 实战脚本。 系列导航 (1/3)原理与对比学习公式 (2/3)PyTorch 完整可复现实战(本文) (3/3)工程化与优化 目标读者 想跑通 CLIP 训练闭环的初学者 需要可复现实验模板的工程实践者 希望基于 PyTorch 做多模态原型验证的读者 背景 / 动机 CLIP 的训练流程看起来简单,但“可复现”很难: 缺数据、缺脚本、缺评估,导致很多实验停在“理论上懂了”。 本篇用一个小数据集闭环复现,优先保证你能在本地跑起来。 核心概念 可复现性:固定随机种子、控制数据划分与预处理。 弱标注文本:用类名构造文本提示,模拟图文对齐。 对比损失:双向交叉熵 + 温度参数。 零样本评估:用文本提示作为“类别描述”进行分类。 A — Algorithm(题目与算法) 训练闭环的核心流程 为每张图像生成文本提示(如 a photo of a cat)。 图像与文本分别编码成向量并归一化。 计算相似度矩阵并用对比损失训练。 推理时用“文本提示集合”做零样本分类。 基础示例(1) 图像:一只猫 文本提示集合:cat, dog, car 目标:相似度最高的提示即为预测类别 基础示例(2) 同一 batch 内,对角线是“正确图文对” 训练目标:对角线最大化,非对角线最小化 实践指南 / 步骤 创建环境并安装依赖: python -m venv .venv source .venv/bin/activate pip install torch torchvision tqdm 把下面脚本保存为 clip_cifar10.py。 运行训练(推荐 GPU): python clip_cifar10.py --epochs 10 --batch-size 256 --device cuda 观察输出:loss 逐步下降,零样本准确率逐步上升。 可运行示例(完整 PyTorch 脚本) import argparse import math import random import numpy as np import torch import torch.nn as nn import torch.nn.functional as F from torch.utils.data import Dataset, DataLoader from torchvision import datasets, transforms, models from tqdm import tqdm CIFAR10_CLASSES = [ "airplane", "automobile", "bird", "cat", "deer", "dog", "frog", "horse", "ship", "truck" ] def set_seed(seed: int) -> None: random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False class SimpleTokenizer: def __init__(self, texts): self.pad_token = "<pad>" self.unk_token = "<unk>" self.bos_token = "<bos>" self.eos_token = "<eos>" vocab = { self.pad_token: 0, self.unk_token: 1, self.bos_token: 2, self.eos_token: 3, } for text in texts: for token in text.lower().split(): if token not in vocab: vocab[token] = len(vocab) self.stoi = vocab self.itos = {i: t for t, i in vocab.items()} self.pad_id = self.stoi[self.pad_token] self.unk_id = self.stoi[self.unk_token] self.bos_id = self.stoi[self.bos_token] self.eos_id = self.stoi[self.eos_token] def encode(self, text, max_len=16): tokens = text.lower().split() ids = [self.bos_id] ids.extend(self.stoi.get(t, self.unk_id) for t in tokens) ids.append(self.eos_id) if len(ids) > max_len: ids = ids[:max_len] ids[-1] = self.eos_id return ids def pad_tokens(token_lists, pad_id): max_len = max(len(t) for t in token_lists) tokens = torch.full((len(token_lists), max_len), pad_id, dtype=torch.long) attn = torch.zeros((len(token_lists), max_len), dtype=torch.bool) for i, ids in enumerate(token_lists): tokens[i, : len(ids)] = torch.tensor(ids, dtype=torch.long) attn[i, : len(ids)] = True return tokens, attn class CIFAR10Text(Dataset): def __init__(self, root, train, transform, tokenizer, max_len=16): self.ds = datasets.CIFAR10(root=root, train=train, download=True, transform=transform) self.prompts = [f"a photo of a {name}" for name in CIFAR10_CLASSES] self.tokenizer = tokenizer self.max_len = max_len def __len__(self): return len(self.ds) def __getitem__(self, idx): image, label = self.ds[idx] text = self.prompts[label] token_ids = self.tokenizer.encode(text, max_len=self.max_len) return image, token_ids, label def collate_fn(batch, pad_id): images, token_lists, labels = zip(*batch) images = torch.stack(images) tokens, attn = pad_tokens(token_lists, pad_id) labels = torch.tensor(labels, dtype=torch.long) return images, tokens, attn, labels class ImageEncoder(nn.Module): def __init__(self, embed_dim): super().__init__() self.backbone = models.resnet18(weights=None) self.backbone.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.backbone.maxpool = nn.Identity() self.backbone.fc = nn.Linear(self.backbone.fc.in_features, embed_dim) def forward(self, x): x = self.backbone(x) return F.normalize(x, dim=-1) class TextEncoder(nn.Module): def __init__(self, vocab_size, embed_dim, width=256, layers=2, heads=4, max_len=16): super().__init__() self.token = nn.Embedding(vocab_size, width) self.pos = nn.Embedding(max_len, width) encoder_layer = nn.TransformerEncoderLayer( d_model=width, nhead=heads, dim_feedforward=width * 4, dropout=0.1, batch_first=True, ) self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=layers) self.proj = nn.Linear(width, embed_dim) def forward(self, token_ids, attn_mask): bsz, seq_len = token_ids.shape pos_ids = torch.arange(seq_len, device=token_ids.device).unsqueeze(0).expand(bsz, -1) x = self.token(token_ids) + self.pos(pos_ids) x = self.encoder(x, src_key_padding_mask=~attn_mask) attn = attn_mask.unsqueeze(-1) x = (x * attn).sum(dim=1) / attn.sum(dim=1).clamp(min=1) x = self.proj(x) return F.normalize(x, dim=-1) class CLIPModel(nn.Module): def __init__(self, vocab_size, embed_dim=256, max_len=16): super().__init__() self.image_encoder = ImageEncoder(embed_dim) self.text_encoder = TextEncoder(vocab_size, embed_dim, max_len=max_len) self.logit_scale = nn.Parameter(torch.tensor(math.log(1 / 0.07))) def forward(self, images, token_ids, attn_mask): image_features = self.image_encoder(images) text_features = self.text_encoder(token_ids, attn_mask) logit_scale = self.logit_scale.exp().clamp(max=100) logits = logit_scale * image_features @ text_features.T return logits def clip_loss(logits): labels = torch.arange(logits.size(0), device=logits.device) loss_i = F.cross_entropy(logits, labels) loss_t = F.cross_entropy(logits.T, labels) return (loss_i + loss_t) / 2 @torch.no_grad() def zero_shot_accuracy(model, loader, tokenizer, device, max_len=16): model.eval() prompts = [f"a photo of a {name}" for name in CIFAR10_CLASSES] token_lists = [tokenizer.encode(p, max_len=max_len) for p in prompts] tokens, attn = pad_tokens(token_lists, tokenizer.pad_id) tokens = tokens.to(device) attn = attn.to(device) text_features = model.text_encoder(tokens, attn) correct = 0 total = 0 for images, _, _, labels in loader: images = images.to(device) image_features = model.image_encoder(images) logits = image_features @ text_features.T preds = logits.argmax(dim=1).cpu() correct += (preds == labels).sum().item() total += labels.size(0) return correct / max(total, 1) def main(): parser = argparse.ArgumentParser() parser.add_argument("--epochs", type=int, default=10) parser.add_argument("--batch-size", type=int, default=256) parser.add_argument("--embed-dim", type=int, default=256) parser.add_argument("--max-len", type=int, default=16) parser.add_argument("--lr", type=float, default=3e-4) parser.add_argument("--seed", type=int, default=42) parser.add_argument("--num-workers", type=int, default=2) parser.add_argument("--device", type=str, default="cuda") parser.add_argument("--data-root", type=str, default="./data") args = parser.parse_args() device = args.device if torch.cuda.is_available() and args.device == "cuda" else "cpu" set_seed(args.seed) prompts = [f"a photo of a {name}" for name in CIFAR10_CLASSES] tokenizer = SimpleTokenizer(prompts) train_tf = transforms.Compose( [ transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ] ) test_tf = transforms.Compose( [ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2470, 0.2435, 0.2616)), ] ) train_ds = CIFAR10Text(args.data_root, True, train_tf, tokenizer, args.max_len) test_ds = CIFAR10Text(args.data_root, False, test_tf, tokenizer, args.max_len) train_loader = DataLoader( train_ds, batch_size=args.batch_size, shuffle=True, num_workers=args.num_workers, collate_fn=lambda b: collate_fn(b, tokenizer.pad_id), ) test_loader = DataLoader( test_ds, batch_size=args.batch_size, shuffle=False, num_workers=args.num_workers, collate_fn=lambda b: collate_fn(b, tokenizer.pad_id), ) model = CLIPModel(len(tokenizer.stoi), embed_dim=args.embed_dim, max_len=args.max_len).to(device) optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=args.epochs) for epoch in range(1, args.epochs + 1): model.train() total_loss = 0.0 for images, tokens, attn, _ in tqdm(train_loader, desc=f"Epoch {epoch}"): images = images.to(device) tokens = tokens.to(device) attn = attn.to(device) logits = model(images, tokens, attn) loss = clip_loss(logits) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() * images.size(0) scheduler.step() avg_loss = total_loss / len(train_ds) acc = zero_shot_accuracy(model, test_loader, tokenizer, device, args.max_len) print(f"Epoch {epoch}: loss={avg_loss:.4f}, zero-shot acc={acc:.4f}") if __name__ == "__main__": main() C — Concepts(核心思想) 方法类型 CLIP 属于对比学习 + 多模态表示学习范式,采用图文双塔编码器对齐语义空间。 ...

2026年1月24日 · 5 分钟 · map[name:Jeanphilo]

CLIP 系列(3/3):工程化与优化——检索、索引与部署实践

副标题 / 摘要 当 CLIP 进入真实系统,核心难题从“训练”变成“检索与延迟”。本篇聚焦工程实践:向量索引、批量推理、缓存策略与部署注意事项。 预计阅读时长:18~22 分钟 标签:clip、retrieval、indexing、optimization SEO 关键词:CLIP, 检索, 向量索引, 工程化, 部署 元描述:面向工程落地的 CLIP 实践,覆盖向量索引、推理优化与部署建议。 系列导航 (1/3)原理与对比学习公式 (2/3)PyTorch 完整可复现实战 (3/3)工程化与优化(本文) 目标读者 需要把 CLIP 集成到搜索/推荐系统的工程师 关注推理延迟与检索精度权衡的技术负责人 想构建多模态应用的产品与平台团队 背景 / 动机 训练出 CLIP 只是起点,难点在于规模化: 图文向量如何离线生成?如何快速检索?如何控制成本与延迟? 这些工程问题决定了 CLIP 是否能真正上线。 核心概念 向量索引:从线性搜索升级为近似最近邻(ANN)。 批量推理:以吞吐为导向的批处理与显存优化。 缓存策略:文本向量往往固定,优先缓存。 重排序:先粗排再精排,提高效率。 A — Algorithm(题目与算法) 工程化流程概览 离线生成图像向量库。 离线生成文本提示向量并缓存。 在线输入文本或图像,计算向量。 使用向量索引检索 TopK 候选。 必要时用精排模型重排序。 基础示例(1) 输入:用户输入“red sneakers” 输出:最相似的商品图像 TopK 基础示例(2) 输入:用户上传图片 输出:相似图像或对应文本描述 实践指南 / 步骤 统一向量维度与归一化策略(L2)。 离线批量生成图像向量并落盘。 预先生成并缓存文本向量。 选型索引:小规模用暴力,大规模用 ANN。 监控检索指标(Recall@K、P95 延迟)。 可运行示例(端到端小检索) import torch import torch.nn.functional as F query = F.normalize(torch.randn(1, 512), dim=-1) corpus = F.normalize(torch.randn(100, 512), dim=-1) scores = query @ corpus.T topk = scores.topk(k=3, dim=1).indices print(topk) C — Concepts(核心思想) 方法类型 CLIP 工程化落地属于向量检索 + 分层排序范式,重点是索引结构、缓存策略与推理吞吐。 ...

2026年1月24日 · 2 分钟 · map[name:Jeanphilo]