k-Nearest Neighbor (kNN) 搜索

k 近邻(kNN)算法在dense_vector 类型的字段上执行相似性搜索。这种搜索方式被称为"approximate kNN" 更为恰当,它接受一个向量或嵌入作为搜索项,并在索引中找到接近的条目。

在本节中,您将学习如何使用上一节创建的文档嵌入执行 kNN 搜索。

knn 查询

在本教程的全文搜索部分,您了解了传递给 Elasticsearch 客户端search() 方法的查询选项。搜索向量时,使用knn选项。

下面是app.pyhandle_search() 函数的新版本,它可根据用户在搜索表单中输入的查询执行 kNN 搜索。

@app.post('/')
def handle_search():
    query = request.form.get('query', '')
    filters, parsed_query = extract_filters(query)
    from_ = request.form.get('from_', type=int, default=0)

    results = es.search(
        knn={
            'field': 'embedding',
            'query_vector': es.get_embedding(parsed_query),
            'num_candidates': 50,
            'k': 10,
        },
        size=5,
        from_=from_
    )
    return render_template('index.html', results=results['hits']['hits'],
                           query=query, from_=from_,
                           total=results['hits']['total']['value'])

在这一版本的函数中,query 选项被knn 所取代。用于分页的sizefrom_ 选项保持不变,函数和index.html模板中的其他内容也与之前相同。

knn 搜索选项可接受一些参数来配置搜索:

  • field:要搜索的索引字段。字段必须具有dense_vector 类型。

  • query_vector:要搜索的嵌入。这应该是从搜索文本中生成的嵌入。

  • num_candidates:每个分区要考虑的候选文件数量。Elasticsearch 会从每个分片检索这么多候选项,将它们合并为一个列表,然后找到最接近的"k" 作为结果返回。

  • k:返回结果的数量。这个数字对性能有直接影响,因此应尽可能小。该选项中传递的值必须小于num_candidates

根据上面代码中的设置,将返回 10 个最佳匹配结果。

欢迎试用新版本的应用程序。下面有两个很好的例子,可以让我们了解这种搜索方式有多有用:

  • 搜索"holiday" ,这是英式英语中与"vacation" 相对应的美式英语,kNN 搜索返回的最高结果是"Vacation Policy" ,尽管 holiday 这个词本身并没有出现在文档中。

  • 搜索"猫和狗" 或任何其他与宠物相关的词条,都会将"Office Pet Policy" 文件列为首选结果,尽管该文件摘要并未提及任何特定宠物。

在 kNN 查询中使用过滤器

本教程全文部分所定义的搜索查询允许用户在搜索文本的任意位置使用category:<category-name> 语法要求使用特定类别。app.py中的extract_filters() 函数负责查找这些过滤表达式,并将其从搜索查询中分离出来。在上一节的handle_search() 函数版本中,没有使用filters 变量,因此类别筛选器被忽略。

幸运的是,knn 选项也支持过滤功能。过滤器选项实际上接受相同类型的过滤器,因此过滤器可以直接插入knn 查询,与extract_filters() 函数返回的过滤器完全一样:

@app.post('/')
def handle_search():
    query = request.form.get('query', '')
    filters, parsed_query = extract_filters(query)
    from_ = request.form.get('from_', type=int, default=0)

    results = es.search(
        knn={
            'field': 'embedding',
            'query_vector': es.get_embedding(parsed_query),
            'k': 10,
            'num_candidates': 50,
            **filters,
        },
        size=5,
        from_=from_
    )
    return render_template('index.html', results=results['hits']['hits'],
                           query=query, from_=from_,
                           total=results['hits']['total']['value'])

聚合在 kNN 查询中也能很好地发挥作用,因此也可以将其添加回去:

@app.post('/')
def handle_search():
    query = request.form.get('query', '')
    filters, parsed_query = extract_filters(query)
    from_ = request.form.get('from_', type=int, default=0)

    results = es.search(
        knn={
            'field': 'embedding',
            'query_vector': es.get_embedding(parsed_query),
            'k': 10,
            'num_candidates': 50,
            **filters,
        },
        aggs={
            'category-agg': {
                'terms': {
                    'field': 'category.keyword',
                }
            },
            'year-agg': {
                'date_histogram': {
                    'field': 'updated_at',
                    'calendar_interval': 'year',
                    'format': 'yyyy',
                },
            },
        },
        size=5,
        from_=from_
    )
    aggs = {
        'Category': {
            bucket['key']: bucket['doc_count']
            for bucket in results['aggregations']['category-agg']['buckets']
        },
        'Year': {
            bucket['key_as_string']: bucket['doc_count']
            for bucket in results['aggregations']['year-agg']['buckets']
            if bucket['doc_count'] > 0
        },
    }
    return render_template('index.html', results=results['hits']['hits'],
                           query=query, from_=from_,
                           total=results['hits']['total']['value'], aggs=aggs)

该版本的handle_search() 功能与全文搜索版本的功能相同,只是使用了矢量搜索而非基于关键字的搜索。

在下一节中,你将学习如何合并这两种不同搜索方法的结果。

准备好打造最先进的搜索体验了吗?

足够先进的搜索不是一个人的努力就能实现的。Elasticsearch 由数据科学家、ML 操作员、工程师以及更多和您一样对搜索充满热情的人提供支持。让我们联系起来,共同打造神奇的搜索体验,让您获得想要的结果。

亲自试用