【项目1:从零构建多模态表示空间 —— CLIP风格对比学习】 第一章 核心概念拆解 附源码

📅 2026/6/20 12:48:18
【项目1:从零构建多模态表示空间 —— CLIP风格对比学习】 第一章 核心概念拆解 附源码
第一章核心概念拆解——对比学习到底在对比什么阅读提示这是从零构建多模态表示空间专栏的第一章。本章不写一行训练代码而是用 30 分钟把三个核心概念彻底讲透。如果你对 CLIP 只有模糊的印象读完这一章你将清楚地知道为什么对比学习和传统的分类学习完全不同共享嵌入空间到底长什么样为什么只用 5000 张图就能实现 Zero-Shot 分类配套代码demo_ch1_similarity.py和demo_ch1_contrastive_toy.py见本章末尾的代码索引1.1 问题起点为什么需要多模态对齐1.1.1 一个看似简单的问题打开搜索引擎输入“日落海滩”——你期待看到什么橙色的天空、深蓝的海面、金色沙滩上的人影。但问题是搜索引擎看到的是日、落、海、滩四个字符而图片库里是数百万个像素矩阵。两个世界的语言完全不同——一个是离散的符号序列一个是连续的颜色张量。传统做法是这样的图像 → CNN → 标签预测器 → beach, sunset 用户查询 → 关键词匹配 → 找到标签为sunset的图片这条路有两个致命缺陷标签空间是封闭的——日落海滩是好标签但打翻的调色盘般的天空呢像梵高画里那样的黄昏呢标签是离散的——日落和黄昏在语义上很近但在分类器眼里是两个独立类别没有距离概念。CLIPContrastive Language-Image Pre-training在 2021 年给出的答案是让图像和文字活在同一个语义空间里。1.1.2 对齐是什么我用一个比喻来帮助理解你有一个跨国公司法国团队说chat中国团队说猫日本团队说猫ねこ“。虽然表面形式完全不同但它们指向同一个概念。你需要的不是一个翻译词典而是让所有人学会用同一种语言思考——不是法文不是中文不是日文而是一种超越具体语言的语义表示”。多模态对齐Multimodal Alignment干的就是这件事它不要求图像翻译成文本也不要求文本翻译成图像它要求两者映射到同一个语义空间中使得在猫这个概念上图像向量和文本向量指向同一个方向这就是为什么它叫对齐而不是映射或翻译——两个模态是被拉到一起、互相对齐的不是单向的转化。核心洞察 #1对齐 ≠ 翻译。翻译是单向的A→B对齐是双向的——图像和文本互相找到对方。1.2 共享嵌入空间图像和文字的共同语言1.2.1 为什么需要共享空间传统监督学习是这个流程图像 → ResNet → [2048维特征] → FC → [1000个类别] → argmax → golden retriever这里有个关键问题最后的 FC 层把 2048 维的语义空间压缩成了 1000 个离散标签。标签之间没有距离的概念——金毛寻回犬和拉布拉多在输出层是两个互不相关的 one-hot 向量cosine similarity 始终为 0。而 CLIP 的做法完全不同图像 → Image Encoder → [512维嵌入向量] ↘ → 余弦相似度 → 排序 文本 → Text Encoder → [512维嵌入向量] ↗不再有固定数量的类别取而代之的是一个512 维的连续语义空间。在这个空间里金毛寻回犬的图像向量和a photo of a golden retriever的文本向量——余弦相似度接近 1金毛寻回犬的图像向量和a photo of a fighter jet的文本向量——余弦相似度接近 0 甚至为负1.2.2 单位超球面所有嵌入的家这是一个容易被忽略的细节但它是整个框架的基础所有图像嵌入和文本嵌入在输出前都会做 L2 归一化。# 每次 encoder forward 的最后一步来自 model.py 的 ProjectionHeadxself.projection(x)# (N, 512)returnF.normalize(x,dim-1)# 每个向量归一化到单位长度为什么因为归一化后余弦相似度等于内积cos⁡(θ)a⋅b∣∣a∣∣⋅∣∣b∣∣\cos(\theta) \frac{\mathbf{a} \cdot \mathbf{b}}{||\mathbf{a}|| \cdot ||\mathbf{b}||}cos(θ)∣∣a∣∣⋅∣∣b∣∣a⋅b​当∣∣a∣∣∣∣b∣∣1||\mathbf{a}|| ||\mathbf{b}|| 1∣∣a∣∣∣∣b∣∣1时cos⁡(θ)a⋅b\cos(\theta) \mathbf{a} \cdot \mathbf{b}cos(θ)a⋅b。这意味着所有嵌入向量分布在一个 511 维的单位超球面hypersphere上。模型要学习的是向量的方向而不是长度——方向代表语义长度被强制归一化为 1。如果不做归一化会怎样模型有捷径可走它可以通过放大特征范数来作弊。具体来说softmax 分母中的∑exp⁡(sim/τ)\sum \exp(\text{sim} / \tau)∑exp(sim/τ)对范数大的向量更敏感模型可能学到用把正样本的范数变大来降低 loss而不是真正学习语义对齐。L2 归一化堵死了这条捷径。⚠️常见踩坑 #1忘记做 L2 归一化。你会看到一个很好的 loss 曲线但 Zero-Shot 能力接近随机——模型在作弊不是在学语义。1.2.3 动手验证这个空间长什么样我们用一个 80 行的小脚本来直观感受。下面的代码生成随机的图像嵌入和文本嵌入然后计算它们的相似度矩阵。你可以直接运行这个文件demo_ch1_similarity.py—— 完整代码见项目仓库核心逻辑只有这几步# 1. 生成随机嵌入并归一化image_embsF.normalize(torch.randn(N,D),dim-1)# N个图像嵌入text_embsF.normalize(torch.randn(N,D),dim-1)# N个文本嵌入# 2. 计算相似度矩阵sim[i][j] image_i · text_jsim_matriximage_embs text_embs.T# (N, N)# 3. 对角线ij是正样本对其余全部是负样本图 1 展示的是这个相似度矩阵的样子T1 T2 T3 T4 T5 T6 I1 [ 0.45 -0.12 0.08 -0.31 0.22 -0.15 ] ← 图像1 vs 所有文本 I2 [-0.08 0.52 -0.19 0.13 -0.28 0.07 ] I3 [ 0.13 -0.24 0.38 -0.09 0.31 -0.21 ] I4 [-0.35 0.18 -0.07 0.41 -0.14 0.29 ] I5 [ 0.21 -0.11 0.33 -0.25 0.47 -0.19 ] I6 [-0.17 0.06 -0.29 0.35 -0.22 0.39 ] ↑ 对角线 正样本对绿色框 其他 负样本对N² - N 36 - 6 30个关键事实在一个 batch_sizeN 的 batch 里正样本对只有N 对对角线负样本对有N² - N 对所有非对角线位置对比学习的效果极度依赖 batch_size——N 越大负样本越多模型学到的表示质量越高核心洞察 #2对比学习的类别不是固定的。今天这个 batch 里的负样本明天可能是另一个 batch 里的正样本。这让模型学会了在任何上下文里区分语义而不是记住 1000 个类的边界。1.3 对比学习拉近正样本推开负样本1.3.1 从交叉熵到对比——一次范式跃迁我们来做一道思考题传统分类给你一张图判断它是猫还是狗。模型只需要学会猫的特征和狗的特征需要的训练数据猫的图片 × 大量狗的图片 × 大量对比学习给你一张图和一段文字判断它们是否匹配。模型需要学会猫的图片和猫的文字描述之间的语义对应需要的训练数据图文对 × 大量但不需要标注类别区别在于前者学习的是图片→标签的映射后者学习的是图片↔文本的双向对应关系。分类器训练出来只能区分训练时见过的类别而对比模型训练出来可以对任意文本做匹配——即使这个文本描述的概念从未出现在训练数据中。这就是 Zero-Shot 能力的来源。1.3.2 对比学习的核心机制一个玩具示例让我们用一个可控的 2D 示例来直观感受。下面的代码创建 3 类点模拟 3 个不同的语义概念每类有 2 个视角模拟图像和文本然后用对比损失训练它们demo_ch1_contrastive_toy.py—— 完整代码见项目仓库实验设定3 个语义类别猫(红)、狗(蓝)、车(绿)每个类有 2 个视角点圆形 图像视角方形 文本视角同一类的一圆一方 正样本对应该彼此靠近不同类的任意两点 负样本应该彼此远离definfonce_loss(embeddings,pairs,temperature0.07): 对比损失的核心逻辑简化版 对于每对正样本 (i, j) - 分子exp(sim(i, j) / τ) → 希望正样本对相似度尽可能大 - 分母Σ_k exp(sim(i, k) / τ) → 包含所有 N 个点正样本 负样本 - 最终-log(分子/分母) → 最大化正样本相对于所有样本的优势 Nembeddings.shape[0]simembeddings embeddings.T/temperature# (N, N)loss0.0fori,jinpairs:# 第 i 个点的 InfoNCE把 j 当作它的正确匹配loss_i-sim[i,j]torch.logsumexp(sim[i],dim0)loss_j-sim[j,i]torch.logsumexp(sim[j],dim0)loss(loss_iloss_j)/2# 对称损失returnloss/len(pairs)图 2 展示了训练前 vs 训练后的对比训练前训练后同类两个视角彼此远离同类两个视角紧密聚合不同类的点随机混合异类之间明显分离没有任何语义结构同类聚类 异类分离核心洞察 #3对比学习本质上是一个排序任务不是分类任务。它不是问这张图属于哪个类别“而是问在这 N 个文本中哪个与这张图最匹配”。这让它在面对从未见过的概念时依然可以工作。1.3.3 Temperature τ对比学习的敏感度旋钮温度系数 τ 是 InfoNCE 中最容易被忽略但影响巨大的超参数。它的数学作用很简单——除以 τsim(Ii,Tj)/τ\text{sim}(I_i, T_j) / \tausim(Ii​,Tj​)/τ但它的语义作用值得深究。我们来看不同 τ 值下 softmax 分布的变化# 来自 demo_ch1_similarity.py 的演示temperatures[0.01,0.03,0.07,0.2,0.5,1.0]fortauintemperatures:probsF.softmax(sim_matrix/tau,dim1)# 观察τ 越小正样本的概率越集中τ 值Softmax 分布形态梯度特征效果τ → 0极低接近 one-hotargmax只更新最相似的负样本过拟合风险只看最难的负样本忽略全局结构τ 0.07适度尖锐对难负样本赋予更高权重CLIP 经验最优在关注细节和保持全局之间平衡τ → ∞极高接近均匀分布所有负样本等权更新学习无效不区分难易负样本学不到精细语义直觉解释低 τ就像一个严格的考官“我要看清每一个细节别想蒙混过关”——这有利于精细区分但可能钻牛角尖高 τ就像一个随和的考官“差不多就行了”——这无法区分细微语义差异τ 0.07是 CLIP 论文中经过大规模实验找到的甜蜜点# 来自 loss.py —— CLIP 将 τ 设为可学习参数classLearnableTemperature(nn.Module): 对数空间参数化用 log(τ) 而非 τ 作为可学习参数 - exp 保证 τ 始终为正 - 对数空间的梯度更稳定 - 初始化为 log(1/0.07) ≈ 2.66等价于 τ0.07 def__init__(self,init_temperature0.07):super().__init__()self.log_temperaturenn.Parameter(torch.tensor(1.0/init_temperature).log())defforward(self):returnself.log_temperature.exp().clamp(min1e-8)⚠️常见踩坑 #2把 τ 设为 1.0 的默认值然后就不再调。τ 1 让梯度对所有负样本同等对待难以关注困难负样本即语义相似但不完全相同的样本对。从 0.07 开始然后让它可学习。1.4 本章总结三个概念的有机联系让我们把三个概念串在一起多模态对齐目标 │ ├── 让图像和文字互相找到对方 │ ▼ 共享嵌入空间载体 │ ├── 所有模态的向量活在同一个 512 维超球面上 ├── 余弦相似度 内积因为 L2 归一化 │ ▼ 对比学习方法 │ ├── 拉近正样本对角线推开负样本非对角线 ├── InfoNCE 多分类交叉熵 on 动态类别 └── Temperature τ 控制区分度它们不是三个独立的概念而是一个问题的三个层面对齐是要解决什么问题What共享空间是在什么框架下解决Where对比学习是用什么方法解决How本核心洞察速览#洞察1对齐 ≠ 翻译。对齐是双向的——图像和文本互相找到对方2对比学习的类别不是固定的而是 batch 内的动态配对关系3对比学习是排序任务不是分类任务——这赋予了它 Zero-Shot 能力4L2 归一化不是可选优化而是防止模型作弊的必要约束5Temperature 控制的是对负样本的关注锐度不是简单的温度参数6Batch_size 是对比学习中最大的免费午餐——越大越好因为有更多负样本下一步第二章第一章我们回答了对比学习在对比什么但还没有深入到数学细节。下一章将带你彻底理解InfoNCE Loss 的完整推导——包括它是如何从互信息最大化推导出来的、为什么分子分母要用指数函数、以及为什么温度叫温度。本章代码索引demo_ch1_similarity.py—— 嵌入空间与余弦相似度可视化demo_ch1_contrastive_toy.py—— 对比学习 2D 玩具示例项目代码库所有章节共享config.py—— 全局超参数配置loss.py—— InfoNCE Loss 完整实现第 2 章核心model.py—— CLIP 双塔架构第 3 章核心dataset.py—— 数据管线第 4 章核心本文是从零构建多模态表示空间 —— CLIP 风格对比学习专栏的第一章。下一章将深入 InfoNCE Loss 的数学推导。