<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Alexander Dávila - Elasticsearch Labs]]></title>
    <description><![CDATA[Articles and tutorials from the Search team at Elastic]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Alexander Dávila - Elasticsearch Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1121c0bf0e8a6e65/6a88da6340a1841030ef456f/search-labs-thumbnail.png</url>
      <link>https://www.elastic.co/cn/search-labs/author/alexander-davila</link>
    </image>
    <link>https://www.elastic.co/cn/search-labs/author/alexander-davila</link>
    <atom:link href="https://www.elastic.co/cn/search-labs/rss/author/alexander-davila.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[cn]]></language>
    <lastBuildDate>Tue, 29 Sep 2026 07:20:12 GMT</lastBuildDate>
  <item>
    <title><![CDATA[使用基于用户行为数据的判断列表在 Elasticsearch 中训练 LTR 模型]]></title>
    <description><![CDATA[了解如何使用 UBI 数据创建判断列表，以便在 Elasticsearch 中自动训练学习排名 (LTR) 模型。]]></description>
    <content:encoded><![CDATA[<p>使用<a href="https://www.elastic.co/docs/solutions/search/ranking/learning-to-rank-ltr"><em><strong>"学习-排名 "</strong></em></a>模型的一大挑战是创建一个高质量的<a href="https://www.elastic.co/search-labs/blog/judgment-lists"><em><strong>判断列表</strong></em></a>来训练模型。传统上，这一过程需要对查询与文档的相关性进行<em><strong>人工</strong></em>评估，为每个文档分配一个等级。这是一个缓慢的过程，不能很好地扩展，也很难维护（想象一下要手工更新一个有数百个条目的列表）。</p><p>现在，如果我们可以使用真实用户与搜索应用程序的交互来创建这些训练数据，会怎样呢？使用<a href="https://www.elastic.co/search-labs/blog/elasticsearch-plugin-user-behavior-insights"><em><strong>UBI</strong></em></a>数据可以让我们做到这一点。创建一个自动系统，捕捉并利用我们的搜索、点击和其他互动来生成判断列表。与人工交互相比，这一过程更容易扩展和重复，而且往往会产生更好的结果。在本博客中，我们将探讨如何查询存储在 Elasticsearch 中的 UBI 数据，以计算有意义的信号，从而为<a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction"><em><strong>LTR</strong></em></a>模型生成训练数据集。</p><p><em><strong>您可以 </strong></em><a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git"><em><strong>在这里</strong></em></a>找到完整的实验 <em><strong>。</strong></em></p><h2>为什么 UBI 数据有助于训练 LTR 模型？</h2><p>与人工标注相比，UBI 数据具有多项优势：</p><ul><li><p><strong>数量：</strong>鉴于 UBI 数据来自真实的互动，我们可以收集到比人工生成的数据多得多的数据。当然，前提是我们有足够的流量来生成这些数据。</p></li><li><p><strong>真实用户意图：</strong>传统上，人工判断列表来自专家对可用数据的评估。另一方面，UBI 数据反映了真实的用户行为。这意味着我们可以生成更好的训练数据，从而提高搜索系统的准确性，因为这些数据是基于用户如何与您的内容进行实际互动并从中发现价值的，而不是对相关内容的理论假设。</p></li><li><p><strong>不断更新：</strong>判断列表需要不断刷新。如果我们根据 UBI 数据创建它们，我们就可以获得最新的数据，从而更新判断列表。</p></li><li><p><strong>成本效益高：</strong>无需手动创建判断列表，可多次高效重复这一过程。</p></li><li><p><strong>自然查询分布</strong>：UBI 数据代表真实的用户查询，可推动更深层次的变化。例如，用户是否使用自然语言在我们的系统中进行搜索？如果是这样，我们可能需要采用语义搜索或混合搜索方法。</p></li></ul><p>不过也有一些警告：</p><ul><li><p><strong>偏见放大： </strong>热门内容更容易获得点击，因为它的曝光率更高。因此，这最终可能会放大热门项目，可能会淹没更好的选择。</p></li><li><p><strong>覆盖面不全： </strong>新内容缺乏互动，因此可能难以在结果中占据重要位置。罕见查询也可能缺乏足够的数据点来创建有意义的训练数据。</p></li><li><p><strong>季节性变化：</strong>如果您预期用户行为会随时间发生巨大变化，那么历史数据可能无法告诉您什么是好结果。</p></li><li><p><strong>任务模糊：</strong>点击并不总能保证用户找到想要的东西。</p></li></ul><h2>成绩计算</h2><h3>LTR 培训的成绩</h3><p>为了训练 LTR 模型，我们需要提供一些数字来表示文档与查询的相关程度。在我们的实施过程中，这个数字是一个连续的分数，从 0.0 到 5.0+，分数越高表示相关性越高。</p><p>为了说明这个分级系统是如何运行的，请看这个手动创建的示例：</p><p>查询</p><p>文件内容</p><p>等级</p><p>说明</p><p>"最佳比萨饼配方"</p><p>"正宗意大利比萨面团配方及步骤图片"</p><p>4.0</p><p>高度相关，正是用户正在寻找的内容</p><p>"最佳比萨饼配方"</p><p>"意大利比萨的历史"</p><p>1.0</p><p>与主题有点吻合，是关于披萨的，但不是食谱</p><p>"最佳比萨饼配方"</p><p>"适合初学者的 15 分钟快速比萨食谱"</p><p>3.0</p><p>这是个不错的结果，但也许还达不到 "最佳 "配方的标准。</p><p>"最佳比萨饼配方"</p><p>"汽车保养指南"</p><p>0.0</p><p>完全不相关，与查询完全无关</p><p>正如我们在这里看到的，等级是一个数字，表示文档与我们的样本查询 "最佳披萨配方 "的相关程度。有了这些分数，我们的 LTR 模型就能知道哪些文档应该在结果中显示得更高。</p><p>如何计算等级是我们训练数据集的核心。有<a href="https://www.elastic.co/search-labs/blog/judgment-lists"> 多种方法</a> 可以做到这一点，每种 方法 都有自己的优缺点。例如，我们可以分配一个二进制分数，1 表示相关 0 表示不相关，或者我们可以只计算每个查询结果文档中的点击次数。</p><p>在这篇博文中，我们将采用一种不同的方法，<em><strong>将用户行为作为我们的输入，并计算出一个等级数字作为输出</strong></em>。我们还将纠正可能出现的偏差，因为无论文件的相关性如何，结果越靠前，点击率越高。</p><h2>计算成绩 - COEC 算法</h2><p>COEC<a href="https://www.wsdm-conference.org/2010/proceedings/docs/p351.pdf">（点击量大于预期点击量</a>）算法是一种根据用户点击量计算判断等级的方法。如前所述，用户倾向于点击位置较高的结果，即使该文档并非与查询最相关；这就是所谓的<a href="https://eugeneyan.com/writing/position-bias/">位置偏差</a>。使用 COEC 算法的核心理念是，并非所有点击都具有同等意义；点击位置 10 的文档表明，该文档与查询的相关性远高于点击位置 1 的文档。引用有关 COEC 算法的研究论文（如上链接）：</p><p><em>"众所周知，搜索结果或广告的点击率（CTR）会随着搜索结果的位置而显著降低"。</em></p><p>您可以<a href="https://www.researchgate.net/publication/200110550_An_experimental_comparison_of_click_position-bias_models">在这里</a>进一步了解位置偏差。</p><p>为了利用 COEC 算法解决这个问题，我们采取了以下步骤：</p><p><strong>1.建立位置基线：</strong>我们计算每个搜索位置（从 1 到 10）的点击率（CTR）。这意味着我们要确定点击位置 1、位置 2 等的用户比例。这一步骤可捕捉用户的自然位置偏差。我们使用以下方法计算点击率：</p><p>在哪里？</p><p>p = 位置。从 1 到 10
Cp = 在所有查询中，任何文档在位置 p 上的总点击次数
Ip = 总印象次数：在所有查询中，任何文档在位置 p 上出现的次数</p><p>在这里，我们希望位置越高，点击越多。</p><p><strong>2.</strong> <strong>计算预期点击量 (EC)：</strong></p><p>该指标根据文档出现的位置和这些位置的点击率，确定文档 "本应 "获得的点击量：</p><p>在哪里？</p><p>Qd = 文档 d 出现的所有查询
pos(d,q)= 文档 d 在查询结果 q 中的位置</p><p>3.<strong>统计实际点击量： </strong>我们统计文档在所有查询中实际获得的总点击量，以下称为<strong>A(d)。</strong></p><p>4.<strong>计算 COEC 分数：</strong>这是实际点击量（A(d)）与预期点击量（EC(d)）之比：</p><p>该指标对位置偏差进行了这样的归一化处理：</p><ul><li><p>得分 1.0 表示文档在出现的位置上表现完全符合预期。</p></li><li><p>得分高于 1.0 表示该文件的位置表现优于预期。因此，这份文件与查询更相关。</p></li><li><p>如果得分低于 1.0，则表示该文件的表现不如预期。因此，该文件与查询的相关性较低。</p></li></ul><p><em><strong>最终结果是一个等级数字，它能反映用户正在寻找什么，并考虑到从与我们搜索系统的实际互动中提取的基于位置的期望值。</strong></em></p><h2>技术实施</h2><p>我们将创建一个脚本来创建判断列表，以训练 LTR 模型。</p><p>该脚本的输入是 Elastic 中索引的 UBI 数据（查询和事件）。</p><p>输出结果是使用 COEC 算法从这些 UBI 文档生成的 CSV 文件中的判断列表。该判断列表可与<a href="https://www.elastic.co/search-labs/blog/elasticsearch-learning-to-rank-introduction">Eland</a>结合使用，以提取相关特征并训练 LTR 模型。</p><h3>快速启动</h3><p>要根据本博客中的样本数据生成判断列表，可以按照以下步骤操作：</p><p>1.克隆版本库：</p>git clone https://github.com/Alex1795/elastic-ltr-judgement_list-blog.git  
cd elastic-ltr-judgement_list-blog<p>2.安装所需程序库</p><p>对于这个脚本，我们需要以下库：</p><ul><li><p><em>熊猫</em>：保存判断列表</p></li><li><p><em>elasticsearch</em>：从我们的弹性部署中获取 UBI 数据</p></li></ul><p>我们还需要 Python 3.11</p>pip install -r requirements.txt<p>3.在<a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/.env-example">.env 文件</a>中更新弹性部署的环境变量</p><ul><li><p>ES_HOST</p></li><li><p>API_KEY</p></li></ul><p>要添加环境变量，请使用</p>source .env<p>4.创建 ubi_queries、ubi_events 索引，并上传样本数据。运行 setup.py 文件：</p>python setup.py<p>5.运行 Python 脚本：</p>python judgement_list-generator.py<p>如果按照这些步骤操作，你应该会看到一个名为 judgment_list.csv 的新文件，它看起来像这样：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94317eda8f7af194/6a170aa46f7f04542f914821/2531090131ac9fe3e4e1d79de9d156fc47a7825a-782x531.png" alt="" /><p>该脚本将使用下图所示的<strong>calculate_relevance_grade()</strong>函数，应用之前讨论过的 COEC 算法计算等级。</p><h2>数据架构</h2><h3>乌比查询</h3><p>我们的 UBI 查询索引包含在搜索系统中执行的查询信息。这是一份样本文件：</p>{
          "client_id": "client_002",
          "query": "italian pasta recipes",
          "query_attributes": {
            "search_type": "recipe",
            "category": "food",
            "cuisine": "italian"
          },
          "query_id": "q002",
          "query_response_id": "qr002",
          "query_response_object_ids": [
            "doc_011",
            "doc_012",
            "doc_013",
            "doc_014",
            "doc_015",
            "doc_016",
            "doc_017",
            "doc_018",
            "doc_019",
            "doc_020"
          ],
          "timestamp": "2024-08-14T11:15:00Z",
          "user_query": "italian pasta recipes"
        }<p>在这里，我们可以看到来自用户（client_id）、查询结果（query_response_object_ids）和查询本身（时间戳、user_query）的数据。</p><h3>Ubi 点击事件</h3><p>我们的 ubi_events 索引拥有用户每次点击结果中的文档时产生的数据。这是一份样本文件：</p>{
          "action_name": "click",
          "application": "recipe_search",
          "client_id": "client_001",
          "event_attributes": {
            "object": {
              "description": "Authentic Italian Pizza Dough Recipe with Step-by-Step Photos",
              "device": "desktop",
              "object_id": "doc_001",
              "position": {
                "ordinal": 1,
                "page_depth": 1
              },
              "user": {
                "city": "New York",
                "country": "USA",
                "ip": "192.168.1.100",
                "location": {
                  "lat": 40.7128,
                  "lon": -74.006
                },
                "region": "NY"
              }
            }
          },
          "message": "User clicked on document doc_001",
          "message_type": "click",
          "query_id": "q001",
          "timestamp": "2024-08-14T10:31:00Z",
          "user_query": "best pizza recipe"
        }<h2>判断列表生成脚本</h2><h3>脚本概述</h3><p>该脚本使用存储在 Elasticsearch 中的来自查询和点击事件的 UBI 数据自动生成判断列表。它执行这些任务：</p><ul><li><p>获取并处理 Elasticsearch 中的 UBI 数据。</p></li><li><p>将 UBI 事件与其查询相关联。</p></li><li><p>计算每个位置的点击率。</p></li><li><p>计算每份文档的预期点击量 (EC)。</p></li><li><p>计算每份文档的实际点击次数。</p></li><li><p>计算每个查询-文档配对的 COEC 分数。</p></li><li><p>生成判断列表并将其写入 CSV 文件。</p></li></ul><p>让我们逐一了解这些功能：</p><h3>connect_too_elasticsearch()</h3>def connect_to_elasticsearch(host, api_key):
    """Create and return Elasticsearch client"""
    try:
        es = Elasticsearch(
            hosts=[host],
            api_key=api_key,
            request_timeout=60
        )
        # Test the connection
        if es.ping():
            print(f"✓ Successfully connected to Elasticsearch at {host}")
            return es
        else:
            print("✗ Failed to connect to Elasticsearch")
            return None
    except Exception as e:
        print(f"✗ Error connecting to Elasticsearch: {e}")
        return None<p>该函数使用主机和 api 密钥返回 Elasticsearch 客户端对象。</p><h3>fetch_ubi_data()</h3>def fetch_ubi_data(es_client: Elasticsearch, queries_index: str, events_index: str,
                   size: int = 10000) -&gt; Tuple[List[Dict], List[Dict]]:
    """
    Fetch UBI queries and events data from Elasticsearch indices.

    Args:
        es_client: Elasticsearch client
        queries_index: Name of the UBI queries index
        events_index: Name of the UBI events index
        size: Maximum number of documents to fetch

    Returns:
        Tuple of (queries_data, events_data)
    """
    logger.info(f"Fetching data from {queries_index} and {events_index}")

    # Fetch queries with error handling
    try:
        queries_response = es_client.search(
            index=queries_index,
            body={
                "query": {"match_all": {}},
                "size": size
            }
        )
        queries_data = [hit['_source'] for hit in queries_response['hits']['hits']]
        logger.info(f"Fetched {len(queries_data)} queries")

    except Exception as e:
        logger.error(f"Error fetching queries from {queries_index}: {e}")
        raise

    # Fetch events (only click events for now) with error handling
    try:
        events_response = es_client.search(
            index=events_index,
            body={
                "query": {
                    "term": {"message_type.keyword": "CLICK_THROUGH"}
                },
                "size": size
            }
        )
        events_data = [hit['_source'] for hit in events_response['hits']['hits']]
        logger.info(f"Fetched {len(events_data)} click events")

    except Exception as e:
        logger.error(f"Error fetching events from {events_index}: {e}")
        raise

    logger.info(f"Data fetch completed successfully - Queries: {len(queries_data)}, Events: {len(events_data)}")

    return queries_data, events_data<p>该函数是数据提取层；它连接 Elasticsearch，使用 match_all 查询获取 UBI 查询，并过滤 UBI 事件，只获取 "CLICK_THROUGH "事件。</p><h3>process_ubi_data()</h3>def process_ubi_data(queries_data: List[Dict], events_data: List[Dict]) -&gt; pd.DataFrame:
    """
    Process UBI data and generate judgment list.

    Args:
        queries_data: List of query documents from UBI queries index
        events_data: List of event documents from UBI events index

    Returns:
        DataFrame with judgment list (qid, docid, grade, keywords)
    """
    logger.info("Processing UBI data to generate judgment list")

    # Group events by query_id
    clicks_by_query = {}
    for event in events_data:
        query_id = event['query_id']
        if query_id not in clicks_by_query:
            clicks_by_query[query_id] = {}

        # Extract clicked document info
        object_id = event['event_attributes']['object']['object_id']
        position = event['event_attributes']['object']['position']['ordinal']

        clicks_by_query[query_id][object_id] = {
            'position': position,
            'timestamp': event['timestamp']
        }

    judgment_list = []

    # Process each query
    for query in queries_data:
        query_id = query['query_id']
        user_query = query['user_query']
        document_ids = query['query_response_object_ids']

        # Get clicks for this query
        query_clicks = clicks_by_query.get(query_id, {})

        # Generate judgment for each document shown
        for doc_id in document_ids:
            grade = calculate_relevance_grade(doc_id, query_clicks, document_ids, queries_data, events_data)

            judgment_list.append({
                'qid': query_id,
                'docid': doc_id,
                'grade': grade,
                'query': user_query
            })

    df = pd.DataFrame(judgment_list)
    logger.info(f"Generated {len(df)} judgment entries for {df['qid'].nunique()} unique queries")

    return df<p>该函数处理判断列表的生成。它通过关联 UBI 事件和查询，开始处理 UBI 数据。然后，它会调用每个文档-查询对的 calculate_relevance_grade() 函数来获取判断列表的条目。最后，它会以 pandas 数据帧的形式返回结果列表。</p><h3>计算相关性等级()</h3>def calculate_relevance_grade(document_id: str, clicks_data: Dict,
                              query_response_ids: List[str], all_queries_data: List[Dict] = None,
                              all_events_data: List[Dict] = None) -&gt; float:
    """
    Calculate COEC (Click Over Expected Clicks) relevance score for a document.

    Args:
        document_id: ID of the document
        clicks_data: Dictionary of clicked documents with their positions for current query
        query_response_ids: List of document IDs shown in search results (ordered by position)
        all_queries_data: All queries data for calculating position CTR averages
        all_events_data: All events data for calculating position CTR averages

    Returns:
        COEC relevance score (continuous value, typically 0.0 to 5.0+)
    """

    # If no global data provided, fall back to simple position-based grading
    if all_queries_data is None or all_events_data is None:
        logger.warning("No global data provided, falling back to position-based grading")
        # Simple fallback logic
        if document_id in clicks_data:
            position = clicks_data[document_id]['position']
            if position &gt; 3:
                return 4.0
            elif position &gt;= 1 and position &lt;= 3:
                return 3.0
        if document_id in query_response_ids:
            position = query_response_ids.index(document_id) + 1
            if position &lt;= 5:
                return 2.0
            elif position &gt;= 6 and position &lt;= 10:
                return 1.0
        return 0.0

    # Calculate rank-aggregated click-through rates
    position_ctr_averages = {}
    position_impression_counts = {}
    position_click_counts = {}

    # Initialize counters
    for pos in range(1, 11):  # Positions 1-10
        position_impression_counts[pos] = 0
        position_click_counts[pos] = 0

    # Count impressions (every document shown contributes)
    for query in all_queries_data:
        for i, doc_id in enumerate(query['query_response_object_ids'][:10]):  # Top 10 positions
            position = i + 1
            position_impression_counts[position] += 1

    # Count clicks by position
    for event in all_events_data:
        if event.get('action_name') == 'click':
            position = event['event_attributes']['object']['position']['ordinal']
            if position &lt;= 10:
                position_click_counts[position] += 1

    # Calculate average CTR per position
    for pos in range(1, 11):
        if position_impression_counts[pos] &gt; 0:
            position_ctr_averages[pos] = position_click_counts[pos] / position_impression_counts[pos]
        else:
            position_ctr_averages[pos] = 0.0

    # Calculate expected clicks for this specific document
    expected_clicks = 0.0

    # Count how many times this document appeared at each position for any query
    for query in all_queries_data:
        if document_id in query['query_response_object_ids']:
            position = query['query_response_object_ids'].index(document_id) + 1
            if position &lt;= 10:
                expected_clicks += position_ctr_averages[position]

    # Count total actual clicks for this document across all queries
    actual_clicks = 0
    for event in all_events_data:
        if (event.get('action_name') == 'click' and
                event['event_attributes']['object']['object_id'] == document_id):
            actual_clicks += 1

    # Calculate COEC score
    if expected_clicks &gt; 0:
        coec_score = actual_clicks / expected_clicks
    else:
        coec_score = 0.0

    logger.debug(
        f"Document {document_id}: {actual_clicks} clicks / {expected_clicks:.3f} expected = {coec_score:.3f} COEC")

    return coec_score<p>这是实现 COEC 算法的函数。它先计算每个位置的点击率，然后比较文档-查询配对的实际点击率，最后计算每个位置的实际 COEC 分数。</p><h3>generate_judgment_statistics()</h3>def generate_judgment_statistics(df: pd.DataFrame) -&gt; Dict:
    """Generate statistics about the judgment list."""
    stats = {
        'total_judgments': len(df),
        'unique_queries': df['qid'].nunique(),
        'unique_documents': df['docid'].nunique(),
        'grade_distribution': df['grade'].value_counts().to_dict(),
        'avg_judgments_per_query': len(df) / df['qid'].nunique() if df['qid'].nunique() &gt; 0 else 0,
        'queries_with_clicks': len(df[df['grade'] &gt; 1]['qid'].unique()),
        'click_through_rate': len(df[df['grade'] &gt; 1]) / len(df) if len(df) &gt; 0 else 0
    }
    return stats<p>它能从判断列表中生成有用的统计数据，如查询总数、唯一文档总数或等级分布。这纯粹是为了提供信息，不会改变最终的判断列表。</p><h2>成果和影响</h2><p>如果您按照快速入门部分的说明进行操作，您应该会看到一个 CSV 文件，其中包含一个有 320 个条目的判断列表（您可以在软件仓库中看到<a href="https://github.com/Alex1795/elastic-ltr-judgement_list-blog/blob/main/judgment_list.csv">输出示例</a>）。有了这些字段：</p><ul><li><p>qid：查询的唯一 ID</p></li><li><p>docid：生成文件的唯一标识符</p></li><li><p>等级：查询文件对的计算等级</p></li><li><p>查询：用户查询</p></li></ul><p> 让我们看看 "意大利菜谱 "的查询结果：</p><p>qid</p><p>docid</p><p>职级</p><p>查询</p><p>Q1 意大利食谱</p><p>基本食谱</p><p>0.0</p><p>意大利食谱</p><p>Q1 意大利食谱</p><p>菜谱_比萨_玛格丽塔</p><p>3.333333</p><p>意大利食谱</p><p>Q1 意大利食谱</p><p>菜谱指南</p><p>10.0</p><p>意大利食谱</p><p>Q1 意大利食谱</p><p>法式羊角面包食谱</p><p>0.0</p><p>意大利食谱</p><p>Q1 意大利食谱</p><p>西班牙海鲜饭食谱</p><p>0.0</p><p>意大利食谱</p><p>Q1 意大利食谱</p><p>希腊穆萨卡菜谱</p><p>1.875</p><p>意大利食谱</p><p>我们可以从结果中看到，查询 "意大利菜谱"：</p><ul><li><p>烩饭食谱无疑是该查询的最佳结果，其点击率比预期高出 10 倍</p></li><li><p>玛格丽塔比萨也是不错的选择。</p></li><li><p>希腊 Mousaka（令人吃惊）也取得了不错的成绩，其表现要好于它在比赛中的位置。这意味着一些寻找意大利食谱的用户转而对这一食谱感兴趣。也许这些用户普遍对地中海菜肴感兴趣。最后，这告诉我们，在我们上面讨论的另外两场 "更好 "的比赛中，这可能是一个很好的结果。</p></li></ul><h2>结论</h2><p>利用 UBI 数据，我们可以自动训练 LTR 模型，从自己的用户中创建高质量的判断列表。UBI 数据提供了一个大数据集，反映了我们的搜索系统是如何被使用的。通过使用 COEC 算法来生成等级，我们可以考虑到固有的偏差，同时，它也反映了用户认为更好的结果。本文概述的方法可应用于实际使用案例，以提供更好的搜索体验，并随着实际使用趋势的变化而变化。</p>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/training-learning-to-rank-models-elasticsearch-ubi-data</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/training-learning-to-rank-models-elasticsearch-ubi-data</guid>
    <category><![CDATA[Python]]></category>
    <category><![CDATA[Elastic Cloud 托管]]></category>
    <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt037eb2f4d380fe65/6a170aa67d8d67397170e6e6/762bf09c28829d626d42c2cfadc719e1dd618d1b-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Wed, 15 Oct 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[揭开独特模式的面纱：Elasticsearch 中重要术语聚合指南]]></title>
    <description><![CDATA[了解如何使用重要术语聚合来发现数据中的洞察力。]]></description>
    <content:encoded><![CDATA[<p>在 Elasticsearch 中，<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">重要术语聚合</a>超出了<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-terms-aggregation">最常见术语的</a>范围，可在数据集中找到统计上不寻常的值。这使我们能够发现有价值的见解和非显而易见的模式。一个重要的术语集合提供了两个有用参数的响应：</p><ul><li><p><strong>bg_count（背景计数）： </strong>在父数据集中找到的文件数</p></li><li><p><strong>doc_count：</strong>结果数据集中找到的文件数</p></li></ul><p>例如，在手机销售数据集中，我们可以像这样查找 iPhone 16 销售的重要术语：</p>GET phone_sales_analysis/_search
{
 "size": 0,
 "query": {
   "term": {
     "phone_model": {
       "value": "iPhone 16"
     }
   }
 },
 "aggs": {
   "significant_cities": {
     "significant_terms": {
       "field": "city_region",
       "size": 1
     }
   }
 }
}<p>然后，答复给了我们：</p>{
 "aggregations": {
   "significant_cities": {
     "doc_count": 122,
     "bg_count": 424,
     "buckets": [
       {
         "key": "Houston",
         "doc_count": 12,
         "score": 0.1946481360617346,
         "bg_count": 14
       }

     ]
   }
 }
}<p>在整个数据集中，休斯顿既不是排名前十的城市，也不是 iPhone 16 的热门城市。不过，重要术语汇总显示，与其他数据相比，<em><strong> 该城市购买 iPhone 16 的比例过高</strong></em>。让我们深入了解这些数字：</p><ul><li><p><strong>在最高层：</strong></p><ul><li><p><strong>doc_count：122 - </strong>查询总共匹配了 122 份文件</p></li><li><p><strong>bg_count：424 - </strong>背景集（所有销售文件）包含 424 份文件</p></li></ul></li><li><p><strong>在休斯顿的水桶里：</strong></p><ul><li><p><strong>doc_count：12 - </strong>休斯顿出现在 122 条查询结果中的 12 条中</p></li><li><p><strong>bg_count：14 - </strong>在背景数据集的 424 份文件中，休斯顿出现在 14 份文件中</p></li></ul></li></ul><p>这告诉我们，在 424 次总购物中，只有 14 次发生在休斯顿，占总购物次数的 3.3% 。然而，如果我们只看 iPhone 16 的销售情况，就会发现 122 件中有 12 件发生在休斯顿，比整个数据集多 3 倍，即 9.8% ；这是非常重要的！</p><p>以下是可视化效果图：每个城市/地区的销售总额。</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted1af6606708267e/6a17f5d614800960e1b488d5/f31335b0b7793650025f941820f238dd35bfb09f-1486x1066.png" alt="" /><p>我们可以看到，休斯顿有 14 笔销售，是数据集中销售额第 14 高的城市。</p><p>现在，如果我们只对 iPhone 16 的销售情况进行筛选，休斯顿就有 12 台，成为该机型销售量第二大的城市：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2882d5e87d02406/6a17f5d71d1b83851e93e5b2/6516040db77e6c62af5541a74c723b18008ad3c6-1472x1038.png" alt="" /><h2>了解重要术语汇总</h2><p>根据 Elastic 文档，<a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation">重要的术语是聚合</a>：</p><p><em>"（查找）在前景集和背景集之间流行度发生显著变化的术语"。</em></p><p>这意味着它使用统计指标，将数据子集（前景集）中某个术语的频率与父数据集（背景集）中同一术语的频率进行比较。这样，评分反映的是统计意义，而不是术语在数据中出现的频率。</p><p>重要术语聚合与普通术语聚合的主要区别在于</p><ul><li><p>重要术语对数据的子集进行比较，而术语聚合只对查询产生的数据集起作用。</p></li><li><p>术语聚合的结果是数据集中最常见的术语，而重要术语的结果则忽略了常见术语，以找出数据集的独特之处。</p></li><li><p>重要术语对性能的影响更大，因为它需要从磁盘而不是内存中获取数据，就像术语聚合所做的那样。</p></li></ul><h2>实际应用（消费者行为分析）</h2><h3>为分析准备数据</h3><p>为了进行分析，我们生成了一个合成的手机销售数据集，其中包括价格、手机规格、购买者的人口统计数据和反馈信息。我们还根据用户的反馈生成了嵌入信息，以便日后进行语义查询。我们使用了 Elasticsearch 上开箱即用的<a href="https://huggingface.co/intfloat/multilingual-e5-small">多语言 e5 小型模型</a>。</p><p></p><p>要在 Elasticsearch 上使用此数据集：</p><ol><li><p>使用 Kibana<a href="https://www.elastic.co/docs/manage-data/ingest/upload-data-files"> 上传数据文件</a> 功能上传 CSV 文件（可从<a href="https://github.com/Alex1795/significant_terms_blog_dataset/blob/main/phone_sales_analysis_dataset.csv"> 此处 下载）。</a></p></li><li><p><a href="https://www.elastic.co/search-labs/blog/chat-with-pdf-elastic-playground#upload-pdfs-to-kibana">如本博客</a>所示，设置一个名为 "嵌入 "的语义字段，使用 <code>multilingual-e5-small model</code></p></li><li><p>使用字段类型默认值完成导入（除<code>purchase_date</code> 和<code>user_feedback)</code> 外，每个字段都使用关键字。请确保添加索引名称<code>phone_sales_analysis</code> ，以便能够按原样运行此处提供的查询。</p></li></ol><p>这项分析的主要重点是发现<em><strong>"iPhone 16 购买者与其他人群的不同之处</strong></em>"，并为营销目的对购买者进行细分。 </p><p>这是数据集中的一份样本文件：</p>{
         "customer_type": "Returning",
         "user_feedback": "I have to say, quality is great for the price. The battery life is really good.",
         "upgrade_frequency": "2 years",
         "storage_capacity": "256GB",
         "occupation": "Technology &amp; Data",
         "color": "Phantom Black",
         "gender": "Male",
         "price_paid": 899,
         "previous_brand_loyalty": "Mixed",
         "location_type": "Urban",
         "phone_model": "Samsung Galaxy S24",
         "city_region": "San Francisco Bay Area",
         "@timestamp": "2024-03-15T00:00:00.000-05:00",
         "income_bracket": "75000-100000",
         "purchase_channel": "Online",
         "feedback_sentiment": "positive",
         "education_level": "Bachelor",
         "embedding": "I have to say, quality is great for the price. The battery life is really good.",
         "customer_id": "C001",
         "purchase_date": "2024-03-15",
         "age": 34,
         "trade_in_model": "iPhone 13"
}<h3>了解人口模式</h3><p>在此，我们将对一般人群进行分析，并将其与 iPhone 16 用户重要术语汇总的有趣发现进行比较。</p><h4>正常模式</h4><p>为了了解正常的购买模式，我们可以汇总不同领域所有文档的数据。为简单起见，我们将重点探讨购买手机的人的职业。我们可以通过向 Elasticsearch 提出请求来实现这一点。</p>GET phone_sales_analysis/_search
{
 "aggs": {
   "occupation_distribution": {
     "terms": {
       "size": 5,
       "field": "occupation"
     }
   }
 },
 "size": 0
}<p>这告诉我们，数据集中的主要职业（按记录数计）是</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec11e3ae5b9cb3c0/6a17f5d9505ac3f28aad8cba/99136ddddd7abad5d74481158a04501b6915441b-1518x480.png" alt="" /><h4>iPhone 16 用户的使用模式</h4><p>为了了解购买了 iPhone 16 的人有什么不同，让我们在同一字段上运行术语聚合，并在查询中使用过滤器找到这些人，就像这样：</p>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}<p>因此，iPhone 16 用户的主要职业是</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26a5415e2f30d164/6a17f5da445de9637a4d028a/36ce86475beb03810c6ad81d7c776d1eec736654-1500x484.png" alt="" /><p>我们可以看到，iPhone 16 用户的职业模式与其他型号手机的用户不同。让我们使用 Kibana 来轻松实现结果的可视化：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e0ddb09fe58e454/6a17f5dce317912cfa2d596b/b70ab05bc962a274e1617b6caf20575c489a62d8-1448x1128.png" alt="" /><p></p><p>在这张图表中，我们可以看到 iPhone 16 的趋势与整个人群的趋势不同。</p><p>我们可以跳过整个分析，通过一个重要项的汇总，来看看 iPhone 16 用户与普通用户的不同之处：</p>GET phone_sales_analysis/_search
{
  "query": {
    "term": {
      "phone_model": "iPhone 16"
    }
  },
  "aggs": {
    "occupation_distribution": {
      "significant_terms": {
        "size": 5,
        "field": "occupation"
      }
    }
  },
  "size": 0
}<p>简而言之，我们得到了这样的答复：</p><p>iPhone 16 的职业值</p><p>文件数量</p><p>bg_count</p><p>职业分布（最高级别）</p><p>122</p><p>424</p><p>医疗&amp; 保健桶</p><p>45</p><p>57</p><p>这些回复清楚地表明，iPhone 16 用户有一个不常见的（读作 "重要！"）问题。与普通人相比，医疗&amp; 保健领域的人数更多。让我们看看回复中的数字意味着什么：</p><ul><li><p><strong>在最高层：</strong></p><ul><li><p><strong>doc_count：122 - </strong>查询总共匹配了 122 份文件</p></li><li><p><strong>bg_count：424 - </strong>背景集（所有销售文件）包含 424 份文件</p></li></ul></li><li><p><strong>在医疗&amp; 保健桶中：</strong></p><ul><li><p><strong>doc_count：45 - </strong>"医疗&amp; 保健" 在 122 条查询结果中出现了 45 条</p></li><li><p><strong>bg_count：57 - </strong>"医疗&amp; 保健" 在背景数据集中的全部 424 份文件中出现 57 份</p></li></ul></li></ul><p>在 424 位买家中，有 57 位在医疗&amp; 保健领域工作，即 13.44% 。但是，当我们查看 iPhone 16 的购买者时，122 位购买者中有 45 位从事医疗&amp; ，即 36.88% 。这意味着在 iPhone 16 用户中，从事医疗&amp; 保健工作的可能性要高出一倍！</p><p>我们可以将同样的分析应用于其他领域（年龄、地点、收入阶层等），从而发现更多有关 iPhone 16 用户独特之处的信息。 </p><h3>消费者细分</h3><p>我们可以利用重要术语聚合来提取产品、类别和客户群之间的关系洞察。为此，我们为感兴趣的类别建立一个父聚合。我们还使用了重要术语和普通术语子分类，以发现对该类别的有趣见解，并将其与该职业中大多数人使用的术语进行比较。</p><p>例如，让我们看看某些工作领域的人喜欢什么：</p><ol><li><p>为了更清楚地进行分析，我们将搜索范围限制在 3 个工作领域：["行政&amp; 支持","技术&amp; 数据","医疗&amp; 保健"]</p></li><li><p>在汇总方面，我们首先按职业进行术语汇总</p></li><li><p>增加一个子分类：按手机型号分类--查找在各个领域工作的用户正在购买哪些手机型号</p></li><li><p>添加第二个子分类：按手机型号分类的重要术语，以找出每个工作领域中的特殊型号</p></li></ol>GET phone_sales_analysis/_search
{
 "query": {
   "terms": {
     "occupation": [
       "Administrative &amp; Support",
       "Technology &amp; Data",
       "Medical &amp; Healthcare"
     ]
   }
 },
 "aggs": {
   "occupations": {
     "terms": {
       "size": 15,
       "field": "occupation"
     },
     "aggs": {
       "general_models": {
         "terms": {
           "field": "phone_model"
         }
       },
       "significant_models": {
         "significant_terms": {
           "field": "phone_model"
         }
       }
     }
   }
 },
 "size": 0
}<p>让我们来分析一下汇总结果：</p><p><strong>职业</strong>行政&amp; 支持</p><p><strong>术语汇总</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3a325cde37b40504/6a17f5dd4b055dbb68432372/a4ad519c9013867a3f4cee032160eadd8a47804a-1506x398.png" alt="" /><p><strong>重要术语汇总</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltef514b8bbb7f429c/6a17f5df3e9e456488ba1612/e5604fa8036667bdfe733576a5e7c6153760dd3a-306x220.png" alt="" /><p>从该表中我们可以推断出，该职业的趋势与整个人口的趋势之间没有显著差异</p><p><strong>职业</strong>：技术&amp; 数据</p><p><strong>术语汇总</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94189446d8f3100e/6a17f5e142022983b029f76e/13b09039bb7d183276451007d2d69dc190b1d3c0-1508x836.png" alt="" /><p></p><p><strong>重要术语汇总</strong></p><p>文件总数424</p><p>该职业的文件：71</p><p>手机型号</p><p>doc_count （本职业中的本模型）</p><p>bg_count （所有文件中都有此模型）</p><p>% 在所有文件中</p><p>% 从事这一职业</p><p>谷歌 Pixel 8</p><p>12</p><p>220</p><p>5.19%</p><p>16.90%</p><p>OnePlus 11</p><p>9</p><p>14</p><p>3.30%</p><p>12.68%</p><p>OnePlus 12 Pro</p><p>3</p><p>3</p><p>0.71%</p><p>4.23%</p><p>谷歌 Pixel 8 Pro</p><p>9</p><p>21</p><p>4.95%</p><p>12.68%</p><p>无手机 2</p><p>5</p><p>8</p><p>1.89%</p><p>7.04%</p><p>三星 Galaxy Z Fold5</p><p>4</p><p>6</p><p>1.42%</p><p>5.63%</p><p>OnePlus 12</p><p>8</p><p>20</p><p>4.72%</p><p>11.27%</p><p><strong>职业</strong>：医疗&amp; 保健</p><p><strong>术语汇总</strong></p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt270b0a861a16488a/6a17f5e23e03d76a934f2df0/b008e996742fc0bb48dc6bacff17cfbc56cf0d73-1492x398.png" alt="" /><p><strong>重要术语汇总</strong></p><p>文件总数424</p><p>该职业的文件：57</p><p>手机型号</p><p>doc_count （本职业中的本模型）</p><p>bg_count （所有文件中都有此模型）</p><p>% 在所有文件中</p><p>% 从事这一职业</p><p>iPhone 16</p><p>45</p><p>122</p><p>28.77%</p><p>78.95%</p><p>iPhone 15 Pro Max</p><p>3</p><p>13</p><p>3.07%</p><p>5.26%</p><p>iPhone 15</p><p>7</p><p>40</p><p>9.43%</p><p>12.28%</p><p>让我们看看这些数据告诉了我们什么故事：</p><ul><li><p>医疗&amp; 医疗保健专业人士更喜欢 iPhone 16，而且普遍倾向于使用苹果手机。</p></li><li><p>技术&amp; 数据专业人士更喜欢高端安卓手机，但不一定使用三星品牌。在这一类别中，iPhone 也有相当大的发展趋势。</p></li><li><p>行政管理&amp; 支持专业人员更喜欢三星和谷歌手机，但没有形成强烈而独特的趋势。</p></li></ul><h3>重要术语汇总和混合搜索</h3><p>混合搜索结合了文本搜索和语义结果，可提供更好的搜索体验。在这种情况下，一个重要的术语聚合可以通过回答问题来深入了解上下文感知搜索的结果：<strong>与所有文档相比，这个数据集有什么特别之处？</strong>为了展示这一特点，让我们看看当用户谈论良好性能时，哪些模型的代表性过高： </p><ul><li><p>让我们建立一个语义查询，通过字段嵌入找到最接近输入 "性能良好 "的用户反馈</p></li><li><p>我们还将在文本字段 user_feedback 中使用相同的术语进行文本搜索</p></li><li><p>我们还将添加一个重要术语查询，以找到在这些结果中出现频率高于完整数据集的手机型号
</p></li></ul>GET phone_sales_analysis/_search
{
 "retriever": {
   "rrf": {
     "retrievers": [
       {
         "standard": {
           "query": {
             "bool": {
               "must": [
                 {
                   "match": {
                     "user_feedback": {
                       "query": "good performance",
                       "operator": "and"
                     }
                   }
                 }
               ]
             }
           }
         }
       },
       {
         "standard": {
           "query": {
             "semantic": {
               "field": "embedding",
               "query": "good performance"
             }
           }
         }
       }
     ],
    "rank_window_size": 20
   }
 },
 "aggs": {
   "Models": {
     "significant_terms": {
       "field": "phone_model"
     }
   }
 }
}<p>让我们来看一个匹配文件的例子：</p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1c3dc221896c8832/6a17f5e4445de91eac4d028e/4cb488097a382f0c28c21540db4f593d23633473-1600x162.png" alt="" /><p>这就是我们得到的答复：</p>{
  "took": 388,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 20,
      "relation": "eq"
    },
    "max_score": 0.016393442,
    "hits": [...]
  },
  "aggregations": {
    "Models": {
      "doc_count": 20,
      "bg_count": 424,
      "buckets": [
        {
          "key": "iPhone 15",
          "doc_count": 5,
          "score": 0.4125,
          "bg_count": 40
        }
      ]
    }
  }
}<p></p><p>这告诉我们，虽然 iPhone 15 在总共 424 篇文档中出现了 40 次（占文档总数的 9.4% ），但在符合语义搜索 "良好表现 "的 20 篇文档（占文档总数的 25% ）中却能找到 5 次。因此，我们可以得出这样的结论：在谈论良好性能时，发现 iPhone 15 的可能性是偶然发现的 2.7 倍。</p><h2>结论</h2><p>重要术语聚合可以通过将数据集与全局文档进行比较，发现数据集的独特细节。这可以揭示数据中意想不到的关系，而不仅仅是出现次数的计算。例如，我们可以在各种使用案例中应用重要术语，从而实现非常有趣的功能：</p><ul><li><p>在<a href="https://www.elastic.co/blog/significant-terms-aggregation#credit"> 侦查 欺诈行为时找出模式 --识别被盗信用卡的常见交易。</a></p></li><li><p>从用户评论中洞察品牌质量--发现差评过多的品牌。</p></li><li><p><a href="https://www.elastic.co/docs/reference/aggregations/search-aggregations-bucket-significantterms-aggregation#_use_on_free_text_fields">发现 </a>分类错误的文档--发现属于某个类别（术语过滤器）但在描述中使用了该类别不常用词的文档（重要术语汇总）。</p></li></ul>]]></content:encoded>
    <link>https://www.elastic.co/search-labs/blog/significant-terms-aggregation-elasticsearch</link>
    <guid isPermaLink="true">https://www.elastic.co/search-labs/blog/significant-terms-aggregation-elasticsearch</guid>
    <category><![CDATA[基础功能]]></category>
    <category><![CDATA[查询 DSL]]></category>
    <dc:creator><![CDATA[Alexander Dávila]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4d95b6134c2a110/6a17f5e6505ac3fd3cad8cbf/13adbc901837835bb56abf15e377127b017cfac8-1536x1024.png" length="0" type="image/png"/>
    <pubDate>Mon, 07 Jul 2025 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>