CLIP论文学习
1.CLIP模型架构
CLIP的核心思想是通过对比学习,让模型学习图像和文本之间的关联。它分别对图像和文本进行编码,将它们映射到同一个特征空间,使得相关的图像和文本在该空间中距离更近。
2.模型训练原理(实例)
# image_encoder - ResNet or Vision Transformer
# text_encoder - CBOW or Text Transformer
# I[n, h, w, c] - minibatch of aligned images
# T[n, l] - minibatch of aligned texts
# W_i[d_i, d_e] - learned proj of image to embed
# W_t[d_t, d_e] - learned proj of text to embed
# t - learned temperature parameter
# extract feature representations of each modality
# 分别提取图像特征和文本特征
I_f = image_encoder(I) #[n, d_i]
T_f = text_encoder(T) #[n, d_t]
# joint multimodal embedding [n, d_e]
# 对两个特征进行线性投射,得到相同维度的特征,并进行l2归一化
I_e = l2_normalize(np.dot(I_f, W_i), axis=1)
T_e = l2_normalize(np.dot(T_f, W_t), axis=1)
# scaled pairwise cosine similarities [n, n]
# 计算缩放的余弦相似度:[n, n]
logits = np.dot(I_e, T_e.T) * np.exp(t)
# symmetric loss function
# 对称的对比学习损失:等价于N个类别的cross_entropy_loss
labels = np.arange(n) # 对角线元素的labels
loss_i = cross_entropy_loss(logits, labels, axis=0)
loss_t = cross_entropy_loss(logits, labels, axis=1)
loss = (loss_i + loss_t)/2
2.1数据流动以及实例
图片示例
- 图片1:
一张高清照片,画面中有一只毛茸茸的白色猫,正慵懒地坐在米色的沙发上,周围有一些抱枕。
- 图片2:
一张运动场景照片,一个穿着蓝色篮球服的人正在篮球场上高高跃起投篮,背景是观众和记分牌。
对应句子示例
- 句子1:
“一只白色的猫惬意地坐在沙发上”
- 句子2:
“一个人穿着蓝色球服在篮球场上投篮”
数学公式及数据流向展示
1. 输入数据
- 图像输入:
- 假设小批量中有 n = 2 n = 2 n=2 张图像,每张图像大小为 h = 224 h = 224 h=224 像素高, w = 224 w = 224 w=224 像素宽, c = 3 c = 3 c=3(RGB 三个通道),则图像输入 I I I 的形状为 I [ n , h , w , c ] = I [ 2 , 224 , 224 , 3 ] I[n, h, w, c]=I[2, 224, 224, 3] I[n,h,w,c]=I[2,224,224,3]。
- 文本输入:
- 同样 n = 2 n = 2 n=2 条文本,每条文本经过分词后长度 l = 10 l = 10 l=10,则文本输入 T T T 的形状为 T [ n , l ] = T [ 2 , 10 ] T[n, l]=T[2, 10] T[n,l]=T[2,10]。
2. 特征提取
- 图像特征提取:
- 使用图像编码器
i
m
a
g
e
_
e
n
c
o
d
e
r
image\_encoder
image_encoder 对输入图像
I
I
I 进行编码,公式为
I f = i m a g e _ e n c o d e r ( I ) I_f = image\_encoder(I) If=image_encoder(I)
得到图像特征 I f I_f If。假设图像编码器输出的特征维度 d i = 2048 d_i = 2048 di=2048,则 I f I_f If 的形状为 [ n , d i ] = [ 2 , 2048 ] [n, d_i]=[2, 2048] [n,di]=[2,2048]。 - 数据流向: I [ 2 , 224 , 224 , 3 ] → i m a g e _ e n c o d e r I f [ 2 , 2048 ] I[2, 224, 224, 3]\xrightarrow{image\_encoder}I_f[2, 2048] I[2,224,224,3]image_encoderIf[2,2048]
- 使用图像编码器
i
m
a
g
e
_
e
n
c
o
d
e
r
image\_encoder
image_encoder 对输入图像
I
I
I 进行编码,公式为
- 文本特征提取:
- 使用文本编码器
t
e
x
t
_
e
n
c
o
d
e
r
text\_encoder
text_encoder 对输入文本
T
T
T 进行编码,公式为
T f = t e x t _ e n c o d e r ( T ) T_f = text\_encoder(T) Tf=text_encoder(T)
得到文本特征 T f T_f Tf。假设文本编码器输出的特征维度 d t = 768 d_t = 768 dt=768,则 T f T_f Tf 的形状为 [ n , d t ] = [ 2 , 768 ] [n, d_t]=[2, 768] [n,dt]=[2,768]。 - 数据流向: T [ 2 , 10 ] → t e x t _ e n c o d e r T f [ 2 , 768 ] T[2, 10]\xrightarrow{text\_encoder}T_f[2, 768] T[2,10]text_encoderTf[2,768]
- 使用文本编码器
t
e
x
t
_
e
n
c
o
d
e
r
text\_encoder
text_encoder 对输入文本
T
T
T 进行编码,公式为
3. 特征投影与归一化
- 图像特征投影与归一化:
- 投影矩阵
W
i
W_i
Wi 的形状为
[
d
i
,
d
e
]
=
[
2048
,
512
]
[d_i, d_e]=[2048, 512]
[di,de]=[2048,512],先进行线性变换
I p r o j = np.dot ( I f , W i ) I_{proj}=\text{np.dot}(I_f, W_i) Iproj=np.dot(If,Wi)
得到投影后的图像特征 I p r o j I_{proj} Iproj,形状为 [ 2 , 512 ] [2, 512] [2,512]。 - 再进行
l
2
l2
l2 归一化
I e = l 2 _ n o r m a l i z e ( I p r o j , a x i s = 1 ) I_e = l2\_normalize(I_{proj}, axis = 1) Ie=l2_normalize(Iproj,axis=1)
得到最终的图像嵌入特征 I e I_e Ie,形状仍为 [ 2 , 512 ] [2, 512] [2,512]。 - 数据流向: I f [ 2 , 2048 ] → np.dot ( . , W i [ 2048 , 512 ] ) I p r o j [ 2 , 512 ] → l 2 _ n o r m a l i z e I e [ 2 , 512 ] I_f[2, 2048]\xrightarrow{\text{np.dot}(., W_i[2048, 512])}I_{proj}[2, 512]\xrightarrow{l2\_normalize}I_e[2, 512] If[2,2048]np.dot(.,Wi[2048,512])Iproj[2,512]l2_normalizeIe[2,512]
- 投影矩阵
W
i
W_i
Wi 的形状为
[
d
i
,
d
e
]
=
[
2048
,
512
]
[d_i, d_e]=[2048, 512]
[di,de]=[2048,512],先进行线性变换
- 文本特征投影与归一化:
- 投影矩阵
W
t
W_t
Wt 的形状为
[
d
t
,
d
e
]
=
[
768
,
512
]
[d_t, d_e]=[768, 512]
[dt,de]=[768,512],先进行线性变换
T p r o j = np.dot ( T f , W t ) T_{proj}=\text{np.dot}(T_f, W_t) Tproj=np.dot(Tf,Wt)
得到投影后的文本特征 T p r o j T_{proj} Tproj,形状为 [ 2 , 512 ] [2, 512] [2,512]。 - 再进行
l
2
l2
l2 归一化
T e = l 2 _ n o r m a l i z e ( T p r o j , a x i s = 1 ) T_e = l2\_normalize(T_{proj}, axis = 1) Te=l2_normalize(Tproj,axis=1)
得到最终的文本嵌入特征 T e T_e Te,形状仍为 [ 2 , 512 ] [2, 512] [2,512]。 - 数据流向: T f [ 2 , 768 ] → np.dot ( . , W t [ 768 , 512 ] ) T p r o j [ 2 , 512 ] → l 2 _ n o r m a l i z e T e [ 2 , 512 ] T_f[2, 768]\xrightarrow{\text{np.dot}(., W_t[768, 512])}T_{proj}[2, 512]\xrightarrow{l2\_normalize}T_e[2, 512] Tf[2,768]np.dot(.,Wt[768,512])Tproj[2,512]l2_normalizeTe[2,512]
- 投影矩阵
W
t
W_t
Wt 的形状为
[
d
t
,
d
e
]
=
[
768
,
512
]
[d_t, d_e]=[768, 512]
[dt,de]=[768,512],先进行线性变换
4. 相似度计算
- 计算缩放的余弦相似度
l o g i t s = np.dot ( I e , T e T ) × np.exp ( t ) logits=\text{np.dot}(I_e, T_e^T)\times\text{np.exp}(t) logits=np.dot(Ie,TeT)×np.exp(t)
其中 I e I_e Ie 形状为 [ 2 , 512 ] [2, 512] [2,512], T e T T_e^T TeT 形状为 [ 512 , 2 ] [512, 2] [512,2],相乘得到一个 [ 2 , 2 ] [2, 2] [2,2] 的矩阵。假设温度参数 t = 0.07 t = 0.07 t=0.07,则将相似度得分乘以 np.exp ( 0.07 ) \text{np.exp}(0.07) np.exp(0.07) 进行缩放。 - 数据流向: I e [ 2 , 512 ] → np.dot ( . , T e T [ 512 , 2 ] ) sim_matrix [ 2 , 2 ] → × np.exp ( 0.07 ) l o g i t s [ 2 , 2 ] I_e[2, 512]\xrightarrow{\text{np.dot}(., T_e^T[512, 2])} \text{sim\_matrix}[2, 2]\xrightarrow{\times\text{np.exp}(0.07)}logits[2, 2] Ie[2,512]np.dot(.,TeT[512,2])sim_matrix[2,2]×np.exp(0.07)logits[2,2]
5. 损失计算
- 首先创建标签
l a b e l s = np.arange ( n ) = [ 0 , 1 ] labels=\text{np.arange}(n)=[0, 1] labels=np.arange(n)=[0,1] - 从图像视角计算交叉熵损失
l o s s i = c r o s s _ e n t r o p y _ l o s s ( l o g i t s , l a b e l s , a x i s = 0 ) loss_i = cross\_entropy\_loss(logits, labels, axis = 0) lossi=cross_entropy_loss(logits,labels,axis=0) - 从文本视角计算交叉熵损失
l o s s t = c r o s s _ e n t r o p y _ l o s s ( l o g i t s , l a b e l s , a x i s = 1 ) loss_t = cross\_entropy\_loss(logits, labels, axis = 1) losst=cross_entropy_loss(logits,labels,axis=1) - 最终损失
l o s s = l o s s i + l o s s t 2 loss=\frac{loss_i + loss_t}{2} loss=2lossi+losst - 数据流向: l o g i t s [ 2 , 2 ] , l a b e l s [ 2 ] → c r o s s _ e n t r o p y _ l o s s ( a x i s = 0 ) l o s s i logits[2, 2], labels[2]\xrightarrow{cross\_entropy\_loss(axis = 0)}loss_i logits[2,2],labels[2]cross_entropy_loss(axis=0)lossi; l o g i t s [ 2 , 2 ] , l a b e l s [ 2 ] → c r o s s _ e n t r o p y _ l o s s ( a x i s = 1 ) l o s s t logits[2, 2], labels[2]\xrightarrow{cross\_entropy\_loss(axis = 1)}loss_t logits[2,2],labels[2]cross_entropy_loss(axis=1)losst; l o s s i , l o s s t → . + . 2 l o s s loss_i, loss_t\xrightarrow{\frac{. +.}{2}}loss lossi,losst2.+.loss
通过不断最小化这个损失 l o s s loss loss,CLIP 模型可以学习到图像和文本之间的关联,使得相关的图像和文本在特征空间中更加接近。
3.zero-shot
假设有一张图片,图片里是一只毛茸茸、长着长耳朵、眼睛又大又圆的动物,实际上这是一只兔子。要使用 CLIP 模型对这张图片进行零样本分类,分类的类别有“兔子”“猫咪”“狗狗”。
3.1.步骤一:文本编码
- 生成文本描述:为每个类别生成对应的文本描述,如下:
- t 1 t_1 t1 = “一张兔子的图片”
- t 2 t_2 t2 = “一张猫咪的图片”
- t 3 t_3 t3 = “一张狗狗的图片”
- 文本编码过程:设文本模型为
E
t
E_t
Et,它会将输入的文本转换为固定长度的特征向量。假设文本模型
E
t
E_t
Et 把输入文本映射到一个 512 维的向量空间。当把
t
1
t_1
t1 输入到文本模型
E
t
E_t
Et 中,会得到一个 512 维的向量
v
t
1
\mathbf{v}_{t_1}
vt1,例如:
v t 1 = [ 0.1 , 0.2 , ⋯ , 0.3 ] \mathbf{v}_{t_1}=[0.1, 0.2, \cdots, 0.3] vt1=[0.1,0.2,⋯,0.3]
同理,对于 t 2 t_2 t2 和 t 3 t_3 t3,分别得到:
v t 2 = [ 0.4 , 0.5 , ⋯ , 0.6 ] \mathbf{v}_{t_2}=[0.4, 0.5, \cdots, 0.6] vt2=[0.4,0.5,⋯,0.6]
v t 3 = [ 0.7 , 0.8 , ⋯ , 0.9 ] \mathbf{v}_{t_3}=[0.7, 0.8, \cdots, 0.9] vt3=[0.7,0.8,⋯,0.9]
3.2.步骤二:图像编码
- 将包含兔子的那张图片输入到视觉模型
E
i
E_i
Ei 中。视觉模型
E
i
E_i
Ei 会对图片进行特征提取,同样将其映射到 512 维的向量空间,得到图像特征向量
v
i
\mathbf{v}_i
vi,假设:
v i = [ 0.15 , 0.22 , ⋯ , 0.33 ] \mathbf{v}_i=[0.15, 0.22, \cdots, 0.33] vi=[0.15,0.22,⋯,0.33]
3.3.步骤三:相似度计算
使用余弦相似度公式 sim ( v 1 , v 2 ) = v 1 ⋅ v 2 ∥ v 1 ∥ ∥ v 2 ∥ \text{sim}(\mathbf{v}_1,\mathbf{v}_2)=\frac{\mathbf{v}_1\cdot\mathbf{v}_2}{\|\mathbf{v}_1\|\|\mathbf{v}_2\|} sim(v1,v2)=∥v1∥∥v2∥v1⋅v2 来计算图像特征向量与每个文本特征向量之间的相似度。
-
计算图像与“兔子”文本描述的相似度 s 1 s_1 s1:
首先计算分子 v i ⋅ v t 1 \mathbf{v}_i\cdot\mathbf{v}_{t_1} vi⋅vt1,点积运算就是对应元素相乘再相加,即:
v i ⋅ v t 1 = 0.15 × 0.1 + 0.22 × 0.2 + ⋯ + 0.33 × 0.3 \mathbf{v}_i\cdot\mathbf{v}_{t_1}=0.15\times0.1 + 0.22\times0.2+\cdots+0.33\times0.3 vi⋅vt1=0.15×0.1+0.22×0.2+⋯+0.33×0.3
然后计算分母,向量的模 ∥ v ∥ = ∑ i = 1 n v i 2 \|\mathbf{v}\|=\sqrt{\sum_{i = 1}^{n}v_i^2} ∥v∥=∑i=1nvi2,所以
∥ v i ∥ = 0.1 5 2 + 0.2 2 2 + ⋯ + 0.3 3 2 \|\mathbf{v}_i\|=\sqrt{0.15^2 + 0.22^2+\cdots+0.33^2} ∥vi∥=0.152+0.222+⋯+0.332
∥ v t 1 ∥ = 0. 1 2 + 0. 2 2 + ⋯ + 0. 3 2 \|\mathbf{v}_{t_1}\|=\sqrt{0.1^2 + 0.2^2+\cdots+0.3^2} ∥vt1∥=0.12+0.22+⋯+0.32
最后得到 s 1 = v i ⋅ v t 1 ∥ v i ∥ ∥ v t 1 ∥ s_1=\frac{\mathbf{v}_i\cdot\mathbf{v}_{t_1}}{\|\mathbf{v}_i\|\|\mathbf{v}_{t_1}\|} s1=∥vi∥∥vt1∥vi⋅vt1,假设计算结果 s 1 = 0.8 s_1 = 0.8 s1=0.8 -
计算图像与“猫咪”文本描述的相似度 s 2 s_2 s2:
同样的方法,分子 v i ⋅ v t 2 = 0.15 × 0.4 + 0.22 × 0.5 + ⋯ + 0.33 × 0.6 \mathbf{v}_i\cdot\mathbf{v}_{t_2}=0.15\times0.4 + 0.22\times0.5+\cdots+0.33\times0.6 vi⋅vt2=0.15×0.4+0.22×0.5+⋯+0.33×0.6
分母 ∥ v i ∥ \|\mathbf{v}_i\| ∥vi∥ 不变,
∥ v t 2 ∥ = 0. 4 2 + 0. 5 2 + ⋯ + 0. 6 2 \|\mathbf{v}_{t_2}\|=\sqrt{0.4^2 + 0.5^2+\cdots+0.6^2} ∥vt2∥=0.42+0.52+⋯+0.62
得到 s 2 = v i ⋅ v t 2 ∥ v i ∥ ∥ v t 2 ∥ s_2=\frac{\mathbf{v}_i\cdot\mathbf{v}_{t_2}}{\|\mathbf{v}_i\|\|\mathbf{v}_{t_2}\|} s2=∥vi∥∥vt2∥vi⋅vt2,假设计算结果 s 2 = 0.3 s_2 = 0.3 s2=0.3 -
计算图像与“狗狗”文本描述的相似度 s 3 s_3 s3:
分子 v i ⋅ v t 3 = 0.15 × 0.7 + 0.22 × 0.8 + ⋯ + 0.33 × 0.9 \mathbf{v}_i\cdot\mathbf{v}_{t_3}=0.15\times0.7 + 0.22\times0.8+\cdots+0.33\times0.9 vi⋅vt3=0.15×0.7+0.22×0.8+⋯+0.33×0.9
分母 ∥ v i ∥ \|\mathbf{v}_i\| ∥vi∥ 不变,
∥ v t 3 ∥ = 0. 7 2 + 0. 8 2 + ⋯ + 0. 9 2 \|\mathbf{v}_{t_3}\|=\sqrt{0.7^2 + 0.8^2+\cdots+0.9^2} ∥vt3∥=0.72+0.82+⋯+0.92
得到 s 3 = v i ⋅ v t 3 ∥ v i ∥ ∥ v t 3 ∥ s_3=\frac{\mathbf{v}_i\cdot\mathbf{v}_{t_3}}{\|\mathbf{v}_i\|\|\mathbf{v}_{t_3}\|} s3=∥vi∥∥vt3∥vi⋅vt3,假设计算结果 s 3 = 0.2 s_3 = 0.2 s3=0.2
3.4.步骤四:分类决策
使用公式 j ∗ = arg max j = 1 , 2 , 3 s j j^*=\arg\max_{j = 1,2,3} s_j j∗=argj=1,2,3maxsj 来找到最大相似度对应的索引。比较 s 1 = 0.8 s_1 = 0.8 s1=0.8, s 2 = 0.3 s_2 = 0.3 s2=0.3, s 3 = 0.2 s_3 = 0.2 s3=0.2,可以发现 s 1 s_1 s1 最大,所以 j ∗ = 1 j^* = 1 j∗=1,这意味着模型将这张图片分类为“兔子”。
3.5.代码
import torch
import clip
from PIL import Image
# 加载CLIP模型和预训练权重
device = "cuda" if torch.cuda.is_available() else "cpu"
model, preprocess = clip.load("ViT-B/32", device=device)
# 定义分类类别
categories = ["兔子", "猫咪", "狗狗"]
text_descriptions = [f"一张{category}的图片" for category in categories]
text_tokens = clip.tokenize(text_descriptions).to(device)
# 加载待分类的图像
image_path = "test_image.jpg"
image = Image.open(image_path).convert("RGB")
image_input = preprocess(image).unsqueeze(0).to(device)
# 文本编码
with torch.no_grad():
text_features = model.encode_text(text_tokens)
text_features /= text_features.norm(dim=-1, keepdim=True)
# 图像编码
image_features = model.encode_image(image_input)
image_features /= image_features.norm(dim=-1, keepdim=True)
# 相似度计算
similarities = (100.0 * image_features @ text_features.T).softmax(dim=-1)
# 分类决策
predicted_index = similarities.argmax().item()
predicted_category = categories[predicted_index]