0

使用 MMR 搜索平衡相关性和多样性

Thierry Damiba

·

2025 年 9 月 4 日

Balancing Relevance and Diversity with MMR Search

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

Similar black dresses

问题:“黑色连衣裙”的搜索结果只返回无肩带连衣裙

Qdrant 原生的最大边际相关性 (MMR) 通过平衡相似结果和多样化结果来解决这个问题。MMR 不会显示相同项目的变体,而是确保每个结果都为您的搜索添加一些新颖之处。

MMR 适用于任何领域和模式,但今天我们将通过使用 DeepFashion 数据集的时尚搜索来探索它。这种视觉方法使多样性的好处立竿见影,但无论您是搜索文档、构建推荐引擎还是为 AI 系统检索上下文,相同的原则都适用。

如需了解 MMR 在基于文本的电影推荐方面的不同视角,请查看 Tarun Jain 的实现指南

什么是最大边际相关性 (MMR)?

Standard Search returns all “bomber jacket”

请注意右侧食品菜肴的多样性,使用 MMR MMR 通过根据两个标准重新排序搜索结果来解决冗余问题

  1. 与您的查询的相关性(它与您要查找的内容匹配的程度如何?)
  2. 与已选择项目的多样性(它与您已有的项目有何不同?)

该算法首先选择最相关的项目,然后对于每个后续项目,它平衡相关性与已选择结果的相似性。一个 lambda 参数控制这种平衡

  • λ = 1.0:纯粹的相关性(常规向量搜索)
  • λ = 0.5:平衡方法
  • λ = 0.0:纯粹的多样性

MMR 在 Qdrant 中作为最近邻查询的一个参数实现。下面的代码示例中可以找到。

为什么时尚发现需要向量搜索?

Fashion search demonstration

搜索“黑色连衣裙”只返回无肩带连衣裙,这表明搜索结果需要多样性

时尚搜索非常适合演示 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]))

创建客户端并设置您的集合

# 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 returns all “bomber jacket”

标准搜索返回所有“飞行员夹克”——请注意结果缺乏多样性

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 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 Search returns different styles

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

我们取得了什么成就

  1. 使用 CLIP 进行视觉嵌入将时尚图像转换为可搜索的向量
  2. MMR 重新排名消除了看起来重复的推荐
  3. 多样性控制让我们调整探索与相关性
  4. 样式筛选将语义搜索与结构化元数据相结合,以实现有针对性的发现

结果是一种时尚搜索,它实际上可以帮助用户发现新样式,而不是显示相同项目的变体。


下一步

此管道开辟了多种可能性

  • 视觉相似性与款式多样性:上传照片并查找不同款式的相似商品
  • 搭配完成:给定一件商品,找到能创造完整搭配的多样化商品
  • 季节性推荐:平衡颜色偏好与季节适宜性
  • 个人造型 AI:学习用户偏好并在其品味范围内推荐多样化商品

亲自尝试

MMR 将时尚搜索从“这里有相似商品”转变为“这里有您可能会喜欢的多种选择”。原生 Qdrant 实现处理所有底层事务,同时让您对相关性-多样性平衡进行精细控制。

从 diversity=0.5 开始,然后根据您想要更多探索(更低的多样性)还是更精确(更高的多样性)进行调整。

Qdrant Cloud 的免费层上使用您自己的时尚数据集进行尝试。

如果您喜欢这篇文章,请在 LinkedInTwitter 上关注我,以随时了解更多涉及向量搜索和检索优化的指南。

免费开始使用 Qdrant

开始使用