使用 MMR 搜索平衡相关性和多样性
Thierry Damiba
·2025 年 9 月 4 日

多样性是生活的调味品!然而,通常情况下,用户会发现搜索引擎的结果过于相似,无法获得价值。您在喜欢的购物网站上搜索一件黑色夹克,结果却得到了 5 件黑色全拉链飞行员夹克。搜索一条黑色连衣裙,结果却得到了 5 条无肩带连衣裙。传统的向量搜索侧重于返回最相关的项目,这会创建一个类似结果的回音室。

问题:“黑色连衣裙”的搜索结果只返回无肩带连衣裙
Qdrant 原生的最大边际相关性 (MMR) 通过平衡相似结果和多样化结果来解决这个问题。MMR 不会显示相同项目的变体,而是确保每个结果都为您的搜索添加一些新颖之处。
MMR 适用于任何领域和模式,但今天我们将通过使用 DeepFashion 数据集的时尚搜索来探索它。这种视觉方法使多样性的好处立竿见影,但无论您是搜索文档、构建推荐引擎还是为 AI 系统检索上下文,相同的原则都适用。
如需了解 MMR 在基于文本的电影推荐方面的不同视角,请查看 Tarun Jain 的实现指南。
什么是最大边际相关性 (MMR)?

请注意右侧食品菜肴的多样性,使用 MMR MMR 通过根据两个标准重新排序搜索结果来解决冗余问题
- 与您的查询的相关性(它与您要查找的内容匹配的程度如何?)
- 与已选择项目的多样性(它与您已有的项目有何不同?)
该算法首先选择最相关的项目,然后对于每个后续项目,它平衡相关性与已选择结果的相似性。一个 lambda 参数控制这种平衡
- λ = 1.0:纯粹的相关性(常规向量搜索)
- λ = 0.5:平衡方法
- λ = 0.0:纯粹的多样性
MMR 在 Qdrant 中作为最近邻查询的一个参数实现。下面的代码示例中可以找到。
为什么时尚发现需要向量搜索?

搜索“黑色连衣裙”只返回无肩带连衣裙,这表明搜索结果需要多样性
时尚搜索非常适合演示 MMR,因为视觉相似性并不总是与购物意图匹配。当有人搜索“黑色夹克”时,他们可能想探索
- 🏃 飞行员夹克(运动型)
- 👔 西装外套(专业型)
- 🧥 皮夹克(时尚前卫型)
- 👖 牛仔夹克(休闲型)
您的典型搜索可能会显示四件看起来几乎相同的黑色夹克。MMR 会为您提供一两件飞行员夹克以及多样化的替代品,帮助用户发现更广泛的款式。
让我们来看一个实际的例子。
设置环境
我们这个时尚发现项目需要几个库
pip install qdrant-client # Vector Search Engine
pip install fastembed # Fast, lightweight embedding generation with CLIP
pip install datasets # For DeepFashion data access
探索 DeepFashion 数据集
DeepFashion 数据集包含超过 40,000 张不同类别和款式的服装图片。它包含丰富的元数据,例如类别、颜色和款式属性,非常适合测试多样性算法。
该数据集包括逼真的时尚摄影:模特穿着的物品、平铺产品照片和详细图像。具有相似名称的各种物品使该数据集非常适合测试 MMR 是否能够区分视觉上相似但服务于不同时尚用途的物品。
第 1 步:加载和处理时尚数据
from datasets import load_dataset
from fastembed import ImageEmbedding, TextEmbedding
from qdrant_client import QdrantClient, models
import uuid
def load_fashion_data(sample_size=10):
"""Load fashion items from DeepFashion-with-masks dataset"""
dataset = load_dataset("SaffalPoosh/deepFashion-with-masks", split="train")
sample_dataset = dataset.shuffle(seed=42).select(range(sample_size))
fashion_items = []
for i, item in enumerate(sample_dataset):
# Extract all available metadata
metadata = {}
for key, value in item.items():
if key not in ["images", "mask", "mask_overlay"] and value is not None:
metadata[key] = value
fashion_items.append({
"image": item["images"], # PIL Image object
**metadata
})
return fashion_items
fashion_items = load_fashion_data(sample_size=100)
第 2 步:创建时尚嵌入
我们将使用 CLIP 创建既能理解视觉相似性又能理解时尚语义含义的嵌入
# Create image embeddings for all fashion items
image_model = ImageEmbedding(model_name="Qdrant/clip-ViT-B-32-vision")
embeddings = list(image_model.embed([item["image"] for item in fashion_items]))
第 3 步:设置 Qdrant 进行视觉时尚搜索
创建客户端并设置您的集合
# Initialize Qdrant client, get your credentials at https://qdrant.org.cn
client = QdrantClient(
host="your-qdrant url",
api_key="<your-qdrant-api-key>")
collection_name = "fashion_discovery"
# Create collection optimized for CLIP embeddings
client.recreate_collection(
collection_name=collection_name,
vectors_config=models.VectorParams(
size=512, # CLIP embedding dimension
distance=models.Distance.COSINE
)
)
# Upload fashion items with embeddings
points = []
for i, (item, embedding) in enumerate(zip(fashion_items, embeddings)):
# Remove PIL image from payload
payload = {k: v for k, v in item.items() if k != "image"}
payload["item_id"] = i
point = models.PointStruct(
id=str(uuid.uuid4()),
vector=embedding.tolist(),
payload=payload
)
points.append(point)
client.upsert(collection_name=collection_name, points=points)
第 4 步:实现时尚搜索功能
现在让我们创建搜索函数来比较标准相似性与 MMR 多样性。我们将使用每个函数查询:“黑色夹克”
标准搜索
标准搜索为您提供所有相似款式:飞行员夹克。
# Standard fashion search — often returns similar looking items
def fashion_search_standard(query_text, limit=5):
text_model = TextEmbedding(model_name="Qdrant/clip-ViT-B-32-text")
query_embedding = list(text_model.embed([query_text]))[0]
results = client.query_points(
collection_name=collection_name,
query=query_embedding.tolist(),
limit=limit,
with_payload=True
)
return results
query_text = "black jacket"
# Standard search
standard_results = fashion_search_standard(query_text)
print("\nSTANDARD FASHION SEARCH: 'black jacket'")
for i, point in enumerate(standard_results, 1):
payload = point.payload
print(f"{i}️⃣ Score: {point.score:.4f} | {payload.get('item_description', 'Fashion Item')}")
print(f" Style: {payload.get('style', 'N/A')} | Color: {payload.get('color', 'N/A')}")
print()

标准搜索返回所有“飞行员夹克”——请注意结果缺乏多样性
STANDARD SEARCH RESULTS: "black jacket"
1️⃣ Black Bomber Jacket with Orange lining
Style: casual | Color: black | Type: bomber jacket
Score: 0.9234
`
2️⃣ Slim-fit Bomber Jacket with Zipper pocket
Style: modern | Color: black | Type: bomber jacket
Score: 0.9156
3️⃣ Lightweight Bomber Jacket with ribbed cuffs
Style: minimalist | Color: black | Type: bomber jacket
Score: 0.9089
4️⃣ Quilted Bomber Jacket with padded lining
Style: classic | Color: black | Type: bomber jacket
Score: 0.9012
5️⃣ Streamlined bomber jacket with sleek zipper
Style: contemporary | Color: black | Type: bomber jacket
Score: 0.8967
MMR 搜索
MMR 为您提供多样化的款式:连帽衫、防风夹克和西装外套。
# MMR fashion search — balances relevance with style diversity
def fashion_search_mmr(query_text, limit=5, diversity=0.5):
text_model = TextEmbedding(model_name="Qdrant/clip-ViT-B-32-text")
query_embedding = list(text_model.embed([query_text]))[0]
results = client.query_points(
collection_name=collection_name,
query=models.NearestQuery(
nearest=query_embedding.tolist(),
mmr=models.Mmr(
diversity=diversity, # 0.0 - relevance; 1.0 - diversity
candidates_limit=100 # num of candidates to preselect
)
),
limit=limit,
with_payload=True
)
return results
query_text = "black jacket"
# MMR search with diversity=0.5
mmr_results = fashion_search_mmr(query_text, diversity=0.5)
print("\nMMR FASHION SEARCH: 'black jacket' (diversity=0.5)")
for i, point in enumerate(mmr_results.points, 1):
payload = point.payload
print(f"{i}️⃣ Score: {point.score:.4f} | {payload.get('item_description', 'Fashion Item')}")
print(f" Style: {payload.get('style', 'N/A')} | Color: {payload.get('color', 'N/A')}")
print()

MMR 搜索返回不同款式——飞行员夹克、连帽衫、防风夹克和西装外套,以获得更好的多样性
MMR SEARCH RESULTS: "black jacket" (diversity=0.5)
1️⃣ Black Bomber Jacket with Orange lining
Style: casual | Color: black | Type: bomber jacket
Score: 0.9234 | Selected: Most relevant
2️⃣ Black zip-up hoodie with drawstring
Style: relaxed | Color: black | Type: hoodie
Score: 0.8456 | Selected: Different style (hoodie vs bomber)
3️⃣ Lightweight Bomber jacket with ribbed cuffs
Style: minimalist | Color: black | Type: bomber jacket
Score: 0.9089 | Selected: Different aesthetic (minimalist)
4️⃣ Black Windbreaker with high collar
Style: sporty | Color: black | Type: windbreaker
Score: 0.8234 | Selected: Different function (windbreaker)
5️⃣ Black tailored blazer with notch lapel
Style: classic | Color: black | Type: coat
Score: 0.7891 | Selected: Different formality (blazer)
请记住,MMR 是逐个选择结果的。您在 Qdrant 中看到的分数代表每个项目与原始查询之间的相似度。最终排名不会按分数排序;它将按 MMR 算法选择每个项目的顺序排序。
第 5 步:使用 MMR 进行高级时尚筛选
将 MMR 与 Qdrant 的元数据筛选相结合,实现有针对性的时尚发现。在此示例中,我们将筛选两个类别。我们将搜索休闲外套,但筛选休闲和运动。这使我们能够使结果多样化,同时确保只显示户外和运动示例。
def filtered_fashion_search(query_text, metadata_filter=None, limit=5, diversity=0.4):
text_model = TextEmbedding(model_name="Qdrant/clip-ViT-B-32-text")
query_embedding = list(text_model.embed([query_text]))[0]
# Build filter for metadata fields
filter_conditions = []
if metadata_filter:
for key, value in metadata_filter.items():
filter_conditions.append(
models.FieldCondition(
key=key,
match=models.MatchValue(value=value)
)
)
query_filter = models.Filter(must=filter_conditions) if filter_conditions else None
results = client.query_points(
collection_name=collection_name,
query=models.NearestQuery(
nearest=query_embedding.tolist(),
mmr=models.Mmr(
diversity=diversity, # 0.0 - relevance; 1.0 - diversity
candidates_limit=100 # num of candidates to preselect
)
),
query_filter=query_filter,
limit=limit,
with_payload=True
)
return results
实际示例:筛选女士衬衫
# Example: Filter for women's blouses
results = filtered_fashion_search(
"professional attire",
metadata_filter={"gender": "WOMEN", "cloth_type": "Blouses_Shirts"},
diversity=0.5)
print("FILTERED SEARCH (Women's Professional Attire):")
for point in results.points:
payload = point.payload
print(f"Score: {point.score:.4f}")
print(f"Gender: {payload.get('gender', 'N/A')}")
print(f"Cloth Type: {payload.get('cloth_type', 'N/A')}")
print()
示例输出
FILTERED SEARCH (Women's Professional Attire):
1️⃣ White Cotton Button-Down Shirt
Score: 0.8934 | Gender: WOMEN | Type: Blouses_Shirts
Style: classic professional
2️⃣ Navy Silk Blouse with Bow Tie
Score: 0.8567 | Gender: WOMEN | Type: Blouses_Shirts
Style: elegant business
3️⃣ Striped Long-Sleeve Shirt
Score: 0.8234 | Gender: WOMEN | Type: Blouses_Shirts
Style: modern casual-professional
我们取得了什么成就
- 使用 CLIP 进行视觉嵌入将时尚图像转换为可搜索的向量
- MMR 重新排名消除了看起来重复的推荐
- 多样性控制让我们调整探索与相关性
- 样式筛选将语义搜索与结构化元数据相结合,以实现有针对性的发现
结果是一种时尚搜索,它实际上可以帮助用户发现新样式,而不是显示相同项目的变体。
下一步
此管道开辟了多种可能性
- 视觉相似性与款式多样性:上传照片并查找不同款式的相似商品
- 搭配完成:给定一件商品,找到能创造完整搭配的多样化商品
- 季节性推荐:平衡颜色偏好与季节适宜性
- 个人造型 AI:学习用户偏好并在其品味范围内推荐多样化商品
亲自尝试
MMR 将时尚搜索从“这里有相似商品”转变为“这里有您可能会喜欢的多种选择”。原生 Qdrant 实现处理所有底层事务,同时让您对相关性-多样性平衡进行精细控制。
从 diversity=0.5 开始,然后根据您想要更多探索(更低的多样性)还是更精确(更高的多样性)进行调整。
在 Qdrant Cloud 的免费层上使用您自己的时尚数据集进行尝试。