使用LangChain FAISS封装器与CLIP模型时余弦相似度得分偏低问题
多模态CLIP+LangChain FAISS向量存储:提升余弦相似度得分的优化方案
问题背景
我基于LangChain的FAISS向量存储封装器和CLIP多模态模型搭建图文检索系统,由于LangChain原生FAISS仅支持文本嵌入模型,因此实现了自定义的CLIPEmbeddings类(继承并实现Embeddings抽象类的方法)。目前系统能通过文本查询匹配到正确的图片,但余弦相似度得分偏低。
当前已采用的配置:
- 使用FAISS的
IndexFlatIP索引 - 开启LangChain FAISS封装器的
normalize_L2=True(通过归一化向量+内积计算余弦相似度) - 测试三种池化策略后,选择文本与图片嵌入均用CLS Token的方案(得分最优,但仍有提升空间)
当前FAISS初始化代码
print("Creating new FAISS index...") index = faiss.IndexFlatIP(DIM) return FAISS( embedding_function=clip_embeddings, index=index, docstore=InMemoryDocstore({}), index_to_docstore_id={}, normalize_L2 = True, # cosine similarity via normalized vectors )
CLS Token池化方案下的测试得分
{ "query": "A sunrise or sunset in the sky with orange shades", "k": 3 } similarity scores from vectorstore.similarity_search_with_score : [np.float32(0.227), np.float32(0.125), np.float32(0.104)] Max Matching score : 0.2272440493106842 ------------------------------------------------------------------------------ { "query": "Beautiful dog with white fur and staring black eyes", "k": 3 } similarity scores from vectorstore.similarity_search_with_score : [np.float32(0.249), np.float32(0.1), np.float32(0.066)] Max Matching score : 0.24914392828941345 ------------------------------------------------------------------------------ { "query": " A black car going on an empty road with trees and windmill in the background ", "k": 3 } similarity scores from vectorstore.similarity_search_with_score : [np.float32(0.184), np.float32(0.133), np.float32(0.054)] Max Matching score : 0.1843710094690323 ------------------------------------------------------------------------------
自定义CLIPEmbeddings代码片段
# Custom CLIPEmbeddings class implementing the methods in abstract class Embeddings # smaller model - openai/clip-vit-base-patch32 class CLIPEmbeddings(Embeddings): def __init__(self, device="cpu"): self.model = CLIPModel.from_pretrained("openai/clip-vit-large-patch14").to(device) self.tokenizer = CLIPTokenizer.from_pretrained("openai/clip-vit-large-patch14") self.processor = CLIPProcessor.from_pretrained("openai/clip-vit-large-patch14") self.device = device # mean pooled output def get_image_embedding(self, image): inputs = self.processor(images=image, return_tensors="pt").to(self.device) with torch.no_grad(): image_features = self.model.get_image_features(**inputs) pooled = image_features.last_hidden_state.mean(dim=1) embedding = self.model.visual_projection(pooled) return embedding.cpu().tolist() # CLS Token def get_text_embedding(self, text: str): inputs = self.tokenizer([text], padding=True, return_tensors="pt").to(self.device) with torch.no_grad(): text_features = self.model.get_text_features(**inputs) embedding = text_features.pooler_output return embedding.cpu().tolist() def embed_documents(self, texts: list[str]) -> list[list[float]]: embeddings: list[list[float]] = [] for text in texts: if text.startswith("img_") and text.endswith(".jpg"): filepath = os.path.join(IMAGE_DIR, text) image = Image.open(filepath).convert("RGB") embedding = self.get_image_embedding(image) else: embedding = self.get_text_embedding(text) embeddings.extend(embedding) return embeddings def embed_query(self, text): embedding = self.embed_documents([text])[0] return embedding
优化方向建议
1. 修正CLIP特征提取逻辑(核心优化点)
CLIP原生的get_image_features和get_text_features方法已经输出了经过视觉/文本投影层+L2归一化的最终特征,不需要额外做池化和投影操作。当前代码中对image_features.last_hidden_state做mean pooling再调用visual_projection,以及取text_features.pooler_output的操作,破坏了CLIP预训练时的图文对齐逻辑,是得分偏低的主要原因。
修正后的特征提取逻辑示例:
def get_image_embedding(self, image): inputs = self.processor(images=image, return_tensors="pt").to(self.device) with torch.no_grad(): # get_image_features直接返回归一化后的最终特征 image_features = self.model.get_image_features(**inputs) return image_features.cpu().tolist() def get_text_embedding(self, text: str): inputs = self.tokenizer([text], padding=True, return_tensors="pt").to(self.device) with torch.no_grad(): # get_text_features直接返回归一化后的最终特征 text_features = self.model.get_text_features(**inputs) return text_features.cpu().tolist()
2. 确保特征归一化一致性
虽然FAISS设置了normalize_L2=True,但建议在特征提取后手动做L2归一化,避免中间步骤的数值误差:
import torch.nn.functional as F # 在get_image_embedding和get_text_embedding中添加归一化 embedding = F.normalize(embedding, p=2, dim=1)
3. 模型与数据优化
- 尝试更大的CLIP模型:比如
openai/clip-vit-giant-patch14,更大的模型通常能提取更精细的特征,提升匹配得分 - 微调CLIP模型:如果有特定领域的图文数据集,可以在预训练CLIP基础上微调,进一步优化图文对齐效果
- 优化查询文本:使用更贴合CLIP训练语料的表述(简洁、具象),避免冗余修饰;也可以对同一查询生成多个变体,取嵌入均值作为查询向量,提升鲁棒性
4. 检查基础配置
- 确认FAISS的
DIM参数与CLIP输出特征维度一致(openai/clip-vit-large-patch14的特征维度为768) - 确保图片预处理与CLIP训练时完全一致,不要额外修改图片尺寸、色彩空间等
内容的提问来源于stack exchange,提问作者Aashishkumar
相关产品推荐
相关产品推荐

