You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Vespa.AI中用rank-feature优化相关性评分及BM25异常排查

Vespa候选人检索:BM25(skills)评分异常及排序调优问题

问题背景

基于技能与行业角色检索候选人档案,采用词法+语义结合的排序配置,但Vespa生成的相关性评分效果不佳,同时存在异常:候选人档案的skills字段包含Java、Python,但matchfeatures与summaryfeature中的bm25(skills)值均为0,需排查解决。

相关输出结果

{
  "root": {
    "id": "toplevel",
    "relevance": 1,
    "fields": {
      "totalCount": 143
    },
    "coverage": {
      "coverage": 100,
      "documents": 143,
      "full": true,
      "nodes": 1,
      "results": 1,
      "resultsFull": 1
    },
    "children": [
      {
        "id": "id:candidate_profile:candidate_profile::a866fa7f-7e13-48fe-bdca-5a60a3198fd9",
        "relevance": 0.01639344262295082,
        "source": "candidate_profile",
        "fields": {
          "matchfeatures": {
            "bm25(profile_summary)": 5.470910610067547,
            "bm25(skills)": 0,
            "firstPhase": 0.8789145673605757,
            "nativeRank(profile_summary)": 0.08308099301928237,
            "semantic": 0.8789145673605757
          },
        "skills": [
            "HTML",
            "CSS",
            "Java Script",
            "React Js",
            "Python",
            "Web Designing",
            "Leadership",
            "Teamwork",
            "Observation",
            "Time management",
            "Communication",
            "Avid fitness enthusiast",
            "Volunteering",
            "Sports",
            "English",
            "Hindi"
          ],
        "summaryfeatures": {
            "bm25(latest_industry)": 0,
            "bm25(latest_job_title)": 0,
            "bm25(latest_role)": 0,
            "bm25(profile_summary)": 5.470910610067547,
            "bm25(skills)": 0,
            "embedding_sum": 55.06214759836439,
            "latest_industry_sum": 40.86598728704121,
            "latest_role_sum": 0,
            "skill_sum": 52.88688380786334,
            "vespa.summaryFeatures.cached": 0
          }
        }
    }
    ]
 }
}

执行的Vespa查询语句

"yql" : " select * from candidate_profile WHERE userQuery() or (all_role_title matches 'Software Developer') AND (skills matches 'python' OR skills matches 'java') AND (latest_role_title matches 'Senior Developer') or ({scoreThreshold:0.032 ,targetHits: 4}nearestNeighbor(embedding, e))",
"input.query(e)" : 'embed(e5, "query: Candidate who is working as Software Developer, Senior Developer has the following skills python, java.")',
"query": " Candidate who is working as Software Developer, Senior Developer has the following skills python, java.",
"ranking" : "common"

自定义排序配置

rank-profile common {
        weight skills : 500
        weight latest_role : 500
        weight latest_industry : 500
        weight latest_job_title : 400

        inputs {
            query(e) tensor<float>(x[384])
        }
        function semantic() {
            expression: max(0, cos(distance(field, embedding)))
        }
        function semantic_skills() {
            expression: max(0, cos(distance(field, skills_embedding)))
        }
        function semantic_latest_role() {
            expression: max(0, cos(distance(field, latest_role_embedding)))
        }
        function semantic_latest_job_title() {
            expression: max(0, cos(distance(field, latest_job_title_embedding)))
        }
        function semantic_latest_industry() {
            expression: max(0, cos(distance(field, latest_industry_embedding)))
        }
        function keyword_match(){
            expression: bm25(skills) + bm25(latest_role) + bm25(latest_industry) + bm25(latest_job_title)
        }
        first-phase {
            expression:  sum(keyword_match + semantic)
        }

        rank-properties {
            fieldMatch(skills).occurrenceImportance: 0.5
            fieldMatch(skills).proximityCompletenessImportance: 0.9
            bm25(skills).k1: 1.5
            bm25(skills).b: 0.85
            fieldMatch(profile_summary).occurrenceImportance: 0.5
            fieldMatch(profile_summary).proximityCompletenessImportance: 0.9
            bm25(profile_summary).k1: 1.5
            bm25(profile_summary).b: 0.85
        }

        summary-features: embedding_sum skill_sum latest_role_sum latest_industry_sum bm25(profile_summary) bm25(skills) bm25(latest_role) bm25(latest_industry) bm25(latest_job_title)

        function embedding_score() {
            expression: attribute(embedding) * query(e)
        }
        function embedding_sum() {
            expression: sum(embedding_score)
        }
        function skill_score(){
            expression : attribute(skills_embedding) * query(e)
        }
        function skill_sum(){
            expression : sum(skill_score)
        }
        function latest_role_score(){
            expression : attribute(latest_role_embedding)  * query(e)
        }
        function latest_role_sum(){
            expression : sum(latest_role_score)
        }
        function latest_industry_score(){
            expression : attribute(latest_industry_embedding) * query(e)
        }
        function latest_industry_sum(){
            expression : sum(latest_industry_score)
        }

        match-features {
            bm25(skills)
            bm25(profile_summary)
            nativeRank(profile_summary)
            semantic
            firstPhase
            
        }
        global-phase {
            expression {
            reciprocal_rank(semantic)
            }
        }
}

问题排查与解决方案

一、BM25(skills)评分异常为0的排查

  1. 字段索引配置检查
    确认schema中skills字段的配置:必须设置为可索引类型(如array<string>),且包含indexing: summary | index。如果仅设置为summary,Vespa无法为其计算BM25得分。

  2. 查询匹配与分词一致性

    • 字段中存储的是Python,但查询用的是python,需确认是否配置了大小写不敏感的分词器(如在schema中为skills字段指定lowercase分词器)。
    • 检查userQuery()的解析规则:是否将查询中的技能关键词映射到skills字段?可通过Vespa的/query/v1接口查看查询解析树,确认是否生成了针对skills字段的查询项。
  3. BM25计算前提验证
    BM25得分依赖于文档频率、词频等统计信息。若索引中没有这些统计数据(如刚创建索引未生成统计),或查询词未与字段中的词匹配(如分词不匹配),都会导致BM25得分为0。可通过vespa-visit工具查看文档的索引项,确认skills字段的词是否被正确索引。

  4. 查询逻辑优先级问题
    当前YQL中使用了多个or连接条件,可能导致skills matches 'python'的匹配未被触发(因为userQuery()或nearestNeighbor已经命中文档)。可单独测试skills matches 'python'的查询,查看bm25(skills)是否有值,验证匹配逻辑是否正常。

二、排序逻辑调优建议

  1. 权重配置生效修正
    当前rank-profile中定义的weight skills : 500等权重未在keyword_match函数中使用,需将权重与BM25得分相乘,比如:

    function keyword_match(){
        expression: bm25(skills)*5 + bm25(latest_role)*5 + bm25(latest_industry)*5 + bm25(latest_job_title)*4
    }
    

    注意:权重值需根据实际得分范围归一化,避免某一特征占比过高。

  2. 语义特征整合
    当前仅使用了semantic(基于embedding字段的语义得分),但定义的semantic_skills、semantic_latest_role等字段级语义特征未被利用,建议将其加权后加入第一阶段得分:

    first-phase {
        expression: sum(keyword_match + semantic*0.5 + semantic_skills*2 + semantic_latest_role*2)
    }
    

    权重需根据业务需求调整,比如技能匹配的语义得分应更高。

  3. 特征归一化处理
    BM25得分(通常0-10)与余弦距离得分(0-1)范围差异大,直接相加会导致语义特征占比失衡。可对BM25得分进行归一化,例如:

    function normalized_bm25_skills() {
        expression: bm25(skills) / 10  # 假设BM25最大值为10
    }
    

    再将归一化后的特征与语义得分加权组合。

  4. 全局阶段逻辑调整
    当前global-phase使用reciprocal_rank(semantic),会覆盖第一阶段的综合得分,导致排序仅依赖语义得分的排名。若不需要全局阶段的重排序,可移除该配置;若需要,需调整为结合词法与语义的综合得分。

  5. 分阶段测试验证

    • 先测试纯词法排序(仅keyword_match),确认词法匹配的准确性。
    • 再测试纯语义排序(仅语义特征),验证语义模型的效果。
    • 逐步叠加特征,调整权重,找到最优的组合方案。

内容的提问来源于stack exchange,提问作者Deepak

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 01:02:03