大模型应用:从粗排到大模型生成:交叉熵、余弦重排序的全流程实践.97

简介: 本文系统阐述检索排序“粗排→算法精排(交叉熵/余弦重排序)→大模型生成”三级架构,强调80%检索质量由排序算法决定:粗排快速召回,精排精准打分,重排序提升多样性,大模型仅负责最终润色与个性化生成,实现高效协同。

一、前言

       在推荐系统、问答系统、搜索引擎等依赖“检索 - 排序”的 AI 场景中,一个普遍被验证的结论是:检索质量的 80% 由排序算法决定,仅 20% 依赖大模型的最终优化。这并非否定大模型的价值,而是明确了“先算法精排、后大模型赋能”的核心逻辑,排序算法解决“把优质候选集筛选出来”的基础问题,大模型解决“把筛选后的结果打磨得更贴合人类需求”的增值问题。

       今天我们结合核心概念、基础原理、执行流程,详细的梳理“粗排→算法精排(交叉熵排序 / 余弦重排序)→大模型生成”的完整体系,建立对检索排序的完整认知。

97.2-从粗排到大模型生成2.jpg

二、基础知识

1. 检索排序的核心目标

检索排序的本质是“从海量候选集中,按照与用户需求的匹配度输出最优结果序列”。比如:

  • 推荐系统:从百万商品中,按“用户购买意愿”排序输出 Top10 商品;
  • 问答系统:从千万知识库片段中,按“回答用户问题的准确性”排序输出 Top5 答案;
  • 搜索引擎:从亿级网页中,按“与搜索词的相关性”排序输出 Top20 网页。

       这个过程中,排序是核心,如果排序算法把优质候选集排在后面,哪怕大模型再强,也只能巧妇难为无米之炊;反之,排序算法筛选出高质量候选集后,大模型只需做精细化优化,就能让结果更贴合需求。

2. 检索排序的三层架构

2.1 粗排

粗排的核心目标是快速缩小候选集范围,要在海量数据中,用低成本、高效率的方式筛选出 Top N候选集,比如从100万条数据中筛选出1000条。

  • 特点:速度快、计算成本低,排序精度不高;
  • 常用方法:
  • 简单规则排序:如按“点击量”、“收藏量”、“发布时间”等单一维度排序;
  • 轻量级模型:如逻辑回归(LR)、FM(因子分解机),仅使用少量核心特征(如用户 ID、物品 ID、点击次数);
  • 向量检索:用预训练的轻量级 Embedding 模型生成用户 / 物品向量,通过余弦相似度快速筛选出相似度较高的候选集。

2.2 算法精排

精排是检索排序的核心环节,为了提升排序准确性,也是决定 80% 检索质量的关键。它的目标是“在粗排的候选集基础上,用更复杂的算法和更丰富的特征,精准计算每个候选项的匹配度,输出 Top K 候选集”,比如从1000条中筛选出100条。

  • 特点:计算精度高、使用特征丰富,速度略慢于粗排,但候选集已缩小,整体耗时可控;
  • 核心精排算法:
  • 交叉熵排序:基于“预测匹配标签”的损失函数优化排序模型,核心是让模型学会区分“正样本(匹配的候选项)”和“负样本(不匹配的候选项)”;
  • 余弦重排序:基于向量余弦相似度,对精排结果做二次优化,解决“算法排序后仍存在的局部相似度偏差”。

2.3 大模型生成

大模型生成是精细化优化结果的最后一公里,目标是“在精排的 Top K 候选集基础上,结合用户需求生成更贴合人类语言习惯、更精准的最终结果”。

  • 特点:不负责“筛选候选集”,只负责“优化候选集的呈现形式、内容细节”;
  • 核心作用:
  • 内容润色:将精排后的答案片段整合成流畅、易懂的自然语言;
  • 意图对齐:结合用户的上下文需求,调整结果的优先级,比如用户问“新手怎么学 Python”,精排可能返回“基础语法”、“项目实战”、“视频教程”等候选项,大模型可根据“新手”这个关键词,优先强调“基础语法”并补充入门建议;
  • 个性化生成:根据用户的历史偏好、语言风格,生成个性化的结果,比如对技术小白用通俗语言,对专业人士用技术术语。

97.3-检索排序全流程效果对比图 full_process_quality.png

2.4 大模型的核心作用

2.4.1 内容整合与润色

  • 排序算法输出的是“候选项列表”,如10个答案片段,而用户需要的是完整、流畅的回答;
  • 大模型能将碎片化的候选项整合成结构化、易理解的内容。

2.4.2 意图对齐与个性化

  • 排序算法基于通用特征排序,如关键词匹配、历史点击,但无法精准捕捉用户的隐性意图;
  • 大模型能通过理解用户问题的上下文,调整结果的优先级和内容。

2.4.3 自然语言交互与生成

  • 排序算法输出的是“排序分数”或“候选项列表”,无法直接与用户交互;
  • 大模型能以自然语言的形式输出结果,提升用户体验。

2.4.4 少量样本的排序优化

  • 当标注数据不足时,大模型可通过提示词工程实现少量样本的排序优化,比如给大模型输入“以下是用户问题和候选答案,请按匹配度排序,并说明理由”;
  • 大模型能基于自身的知识,对精排结果做微调。

2.5 排序算法与大模型的协同

未来的检索排序系统,会是“排序算法 + 大模型”的深度协同,而非谁替代谁:

  • 排序算法的“模型化”:用大模型提取特征,如BERT提取文本特征,提升排序算法的精度;
  • 大模型的“轻量化”:针对排序后的候选集,使用轻量化大模型,降低生成成本;
  • 端到端训练:将粗排、精排、大模型生成整合到一个框架中,通过联合训练优化整体效果,如用强化学习,将用户对大模型生成结果的反馈,反向优化排序算法的参数。

3. 基础数学原理

3.1 余弦相似度

       余弦相似度是余弦重排序的核心,用于衡量两个向量之间的 “方向相似度”,值越大表示越相似(范围:[-1,1])。假设存在两个向量 A=(a1, a2,..., an)和B=(b1 ,b2 ,...,bn),余弦相似度计算公式为:

  • 通俗理解:把向量想象成“箭头”,余弦相似度就是两个箭头之间的夹角,夹角越小,相似度越高;夹角为 0° 时,相似度 = 1,表示完全相同;夹角为 90° 时,相似度 = 0,表示完全无关。
  • 应用场景:在排序中,我们会把“用户需求”和“候选项”都转换成向量,通过计算余弦相似度衡量匹配度。

3.2 交叉熵损失

       交叉熵是交叉熵排序的核心,用于衡量“模型预测概率分布”与“真实标签分布”之间的差异,值越小表示模型预测越准确。对于二分类场景(正样本/负样本),交叉熵损失公式为:

  • 符号说明:
  • yi:真实标签(正样本 = 1,负样本 = 0);
  • pi:模型预测为正样本的概率;
  • N:样本数量。
  • 通俗理解:
  • 如果样本是正样本(yi =1),模型预测概率pi越接近 1,损失越小;
  • 如果是负样本(yi =0),pi越接近 0,损失越小。
  • 交叉熵排序的核心是通过最小化这个损失,让模型学会给正样本更高的排序分数。

4. 排序算法决定80%的检索质量

举一个直观的例子:

  • 坏情况:粗排 + 精排算法差,从100万条候选集中筛选出的100条里,只有10条是真正匹配用户需求的;此时哪怕大模型再强,也只能从这10条里选,最终结果的质量上限很低。
  • 好情况:粗排 + 精排算法好,筛选出的100条里有90条是匹配的;此时大模型只需做简单的润色或排序微调,就能输出高质量结果。

       大模型的核心能力是“生成和理解自然语言”,而非“从海量数据中筛选优质候选集”,筛选候选集需要的是“高效的算法 + 精准的特征工程”,这正是排序算法的强项。因此,检索质量的核心瓶颈在排序算法,而非大模型。

三、精排算法原理

1. 交叉熵排序原理

交叉熵排序是“有监督的排序算法”,核心是训练一个模型,通过交叉熵损失优化模型参数,让模型能给“匹配的候选项”输出更高的分数,给不匹配的输出更低的分数。

97.4-交叉熵损失与预测概率的关系 cross_entropy_loss.png

图示说明:正样本概率越接近 1、负样本概率越接近 0,损失越小

1.1 核心流程

97.5-交叉熵排序核心流程 deepseek_mermaid_20260224_c0a15b.png

1.2 关键步骤

步骤 1:数据准备(正负样本标注)

  • 正样本:与用户需求匹配的候选项,比如用户问“Python 怎么安装”,答案“Python 官网下载安装包的步骤” 就是正样本;
  • 负样本:与用户需求不匹配的候选项,比如 “Java 安装步骤” 就是负样本;
  • 标注原则:
  • 正样本数量可以少,但必须精准;
  • 负样本要多样化,比如随机负样本、难负样本(看似匹配但实际不匹配的样本),提升模型的区分能力。

步骤 2:特征工程(排序算法的灵魂)

特征是模型判断“匹配度”的依据,交叉熵排序常用的特征分为三类:

  • 用户特征:用户 ID、历史点击或收藏记录、用户画像,如年龄、职业、兴趣标签;
  • 候选项特征:候选项 ID、内容特征(如文本长度、关键词、分类标签)、统计特征(如点击量、点赞量);
  • 交叉特征:用户与候选项的交互特征,如用户是否点击过该候选项、用户兴趣标签与候选项分类的匹配数。

举例:对于问答系统的答案排序,特征可以是:

  • 答案特征:答案包含的关键词数量、答案长度、答案的历史采纳率
  • 用户特征:用户是否是新手、用户的历史问题领域、用户的点赞偏好
  • 交叉特征:答案关键词与用户问题关键词的重合数、答案领域与用户历史问题领域的匹配度

步骤 3:模型构建

交叉熵排序的模型可以是简单的线性模型(如 LR),也可以是复杂的深度学习模型(如 DNN、Transformer),核心是 “输入特征→输出匹配概率”。

以简单的 LR 模型为例:

  • 模型公式:p=σ(W⋅X+b)
  • X:特征向量;
  • W:权重矩阵;
  • b:偏置项;
  • σ:sigmoid 函数,将输出映射到 [0,1],作为匹配概率。
  • 模型输出的p就是“候选项与用户需求匹配的概率”,概率越高,排序越靠前。

步骤 4:损失计算与参数优化

将模型输出的概率p代入交叉熵损失公式,通过梯度下降算法最小化损失,更新权重W和偏置b。

梯度下降的核心逻辑:

  • 计算损失对W和b的梯度,实际就是求导数;
  • 按梯度的反方向更新参数:W=W−η⋅∇W(η是学习率);
  • 重复迭代,直到损失收敛,不再明显下降。

步骤 5:模型评估

排序模型的评估不能用“准确率”,而是用“排序指标”,常用的有:

  • MRR(Mean Reciprocal Rank):平均倒数排名,计算“第一个正确候选项的排名倒数的平均值”,值越大越好,范围:(0,1];
  • NDCG@K(Normalized Discounted Cumulative Gain):归一化折损累计增益,衡量前 K 个结果的排序质量,值越大越好,范围:[0,1];

核心逻辑:排名越靠前的正确候选项,贡献的“增益”越高,排名靠后的增益被“折损”。

步骤 6:线上推理

训练好的模型部署后,输入粗排后的候选集特征,模型输出每个候选项的匹配概率,按概率从高到低排序,得到精排结果。

1.3 优缺点

  • 优点:
  • 1. 有监督学习,能充分利用标注数据,排序精度高;
  • 2. 支持复杂特征和复杂模型,适配不同场景,包括推荐、问答、搜索;
  • 3. 损失函数直观,优化目标明确,最小化预测与真实标签的差异。
  • 缺点:
  • 1. 依赖高质量的标注数据,标注成本高;
  • 2. 模型训练和线上推理的计算成本高于规则排序;
  • 3. 对特征工程的依赖度高,特征质量差会直接导致排序效果差。

2. 余弦重排序原理

余弦重排序是“无监督的二次排序算法”,核心是在交叉熵排序或其他精排算法的结果基础上,通过计算候选项之间的余弦相似度,调整排序顺序,解决算法排序后仍存在的局部相似度偏差。

2.1 余弦重排序的作用

交叉熵排序是“基于单个候选项与用户需求的匹配度”排序,但忽略了“候选项之间的相似性”,比如:

  • 交叉熵排序输出的 Top5 结果中,第 2、3、4 名是高度相似的,比如都是“Python 安装的 Windows 步骤”,而第 5 名是“Python 安装的 Mac 步骤”,对使用 Mac 的用户更有价值;
  • 此时如果直接输出,会导致结果冗余,且可能把更有价值的候选项排在后面;
  • 余弦重排序通过计算候选项之间的相似度,合并冗余项、提升差异化高且匹配的候选项排名。

97.6-余弦重排序前后分数对比 cosine_re_ranking.png

图示说明:余弦重排序对 “高相似度候选答案” 的分数惩罚效果,答案分数会下降

2.2 核心流程

97.7-余弦重排序核心流程 deepseek_mermaid_20260224_bca5d6.png

2.3 关键步骤

步骤 1:候选项向量化

将每个候选项(如答案文本、商品描述)转换成固定维度的向量(Embedding),常用方法:

  • 轻量级模型:Word2Vec、TF-IDF+PCA;
  • 深度学习模型:BERT、RoBERTa、Sentence-BERT,专为句子向量设计,效果最优。

步骤 2:计算相似度矩阵

假设有K个候选项,生成K×K的相似度矩阵S,其中S_i,j表示第i个候选项和第j个候选项的余弦相似度。

步骤 3:相似度加权

核心思路:“惩罚”与前面候选项高度相似的项,“奖励”差异化高的项。常用加权公式:

  • 符号说明:
  • :重排序后的分数;
  • :交叉熵排序的原始分数;
  • :惩罚系数,范围:0~1,越大惩罚越重;
  • :第i个候选项与前面所有候选项的最大相似度。
  • 通俗理解:如果第i个候选项和前面的候选项高度相似(maxSi,j大),则score_i′会降低;如果相似度低,则score_i′基本不变。

97.8-候选答案余弦相似度矩阵 similarity_matrix.png

图示说明:这张热力图是余弦重排序的“决策依据”,它直观告诉我们:哪些候选答案是高度相似的,需要被“惩罚”。

  • 矩阵里的数值范围是 0~1,越接近 1,代表两个答案的语义或内容越像;
  • 答案 4 和答案 5 的相似度是 0.92,几乎快一模一样了,而它们和答案1/2/3的相似度只有 0.1 左右,完全不相关。

步骤 4:重新排序

按score_i′从高到低排序,得到余弦重排序后的结果。

2.4 优缺点

  • 优点:
  • 无监督学习,无需标注数据,部署成本低;
  • 能有效解决结果冗余问题,提升排序的多样性;
  • 计算速度快,候选集已缩小到 Top K,K通常≤100;
  • 缺点:
  • 依赖高质量的 Embedding 向量,向量质量差会导致重排序效果差;
  • 惩罚系数α需要人工调参,不同场景的最优值不同;
  • 仅做二次优化,无法弥补粗排、精排的核心误差。

3. 两者协同优势

将两种算法结合,能发挥 1+1>2 的效果:

  • 交叉熵排序解决“核心匹配度”问题:确保筛选出的候选项与用户需求高度匹配;
  • 余弦重排序解决“结果冗余”问题:确保排序结果的多样性,避免重复推荐或回答;
  • 两者结合后,精排结果既精准又多样,为后续大模型生成打下坚实基础。

四、执行流程

以“问答系统答案排序”为例,完整流程如下:

97.9-智能问答系统多级排序流程 deepseek_mermaid_20260225_572604.png

流程说明:

  • 1. 用户输入问题:接收用户自然语言查询,如“新手怎么学Python?”
  • 2. 粗排(Top 1000):从海量知识库中快速检索,筛选出1000个相关答案,确保召回率
  • 3. 交叉熵排序(Top 100):用更精确的模型计算语义匹配度,筛选出100个高质量答案
  • 4. 余弦重排序(Top 10):计算答案间的语义相似度,增加多样性,避免内容重复
  • 5. 大模型生成:整合Top 10答案的核心信息,生成连贯、完整的自然语言回复
  • 6. 输出给用户:将最终生成的答案展示给用户

五、示例:从粗排到大模型生成

我们以问答系统示例,实现从粗排、算法精排(交叉熵排序、余弦重排序)、大模型生成的完整示例;

步骤 1:数据准备

我们模拟一个简单的问答知识库,包含“Python 学习”相关的答案:

import pandas as pd
import numpy as np
# 模拟问答知识库
knowledge_base = pd.DataFrame({
    "answer_id": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
    "answer_text": [
        "Python安装需要下载官网安装包,安装时勾选Add Python to PATH",
        "零基础学Python先学基础语法:变量、循环、条件判断",
        "Python新手推荐廖雪峰教程、菜鸟教程、B站黑马视频",
        "Python小项目推荐:计算器、简易爬虫、天气查询工具",
        "Java安装需要下载JDK,配置环境变量",  # 负样本
        "Python进阶学习:函数、类、模块、异常处理",
        "Python办公自动化:Excel处理、Word生成、邮件发送",
        "C++基础语法:变量、循环、指针",  # 负样本
        "Python环境管理:Anaconda安装与使用",
        "Python调试技巧:print调试、pdb调试工具",
        "Python付费课程推荐:XX训练营、XX网课",
        "机器学习入门:Python+Scikit-learn实现线性回归",
        "Python书籍推荐:《Python编程:从入门到实践》",
        "HTML基础语法:标签、属性、布局",  # 负样本
        "Python代码规范:PEP8规范、注释编写"
    ],
    "label": [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1]  # 1=正样本,0=负样本
})
# 用户问题
user_question = "新手怎么学Python?"

image.gif

步骤 2:粗排(向量检索)

使用 Sentence-BERT 生成向量,FAISS 做向量检索:

from sentence_transformers import SentenceTransformer
from modelscope import snapshot_download
import faiss
cache_dir = "D:\\modelscope\\hub"
model_dir = snapshot_download(
    model_id="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_dir=cache_dir,
    revision="master"  # 或指定分支/commit
)
# 1. 加载Embedding模型(轻量级Sentence-BERT)
embedding_model = SentenceTransformer("D:\\modelscope\\hub\\sentence-transformers\\paraphrase-multilingual-MiniLM-L12-v2")
# 2. 知识库文本向量化
answer_texts = knowledge_base["answer_text"].tolist()
answer_embeddings = embedding_model.encode(answer_texts, convert_to_numpy=True)
# 3. 构建FAISS索引
dimension = answer_embeddings.shape[1]  # 向量维度:384
index = faiss.IndexFlatL2(dimension)  # 暴力检索(适合小数据集)
index.add(answer_embeddings)
# 4. 用户问题向量化
question_embedding = embedding_model.encode([user_question], convert_to_numpy=True)
# 5. 向量检索:筛选Top 10候选集(粗排)
k = 15  # 增大候选集,确保有足够的正负样本
distances, indices = index.search(question_embedding, k)
# 6. 提取粗排结果
rough_ranking_results = knowledge_base.iloc[indices[0]]
print("粗排结果:")
print(rough_ranking_results[["answer_id", "answer_text", "label"]])

image.gif

输出结果:

粗排结果:

   answer_id                                answer_text                                                label

12         13               Python书籍推荐:《Python编程:从入门到实践》           1

5           6                    Python进阶学习:函数、类、模块、异常处理                1

2           3                Python新手推荐廖雪峰教程、菜鸟教程、B站黑马视频       1

1           2                零基础学Python先学基础语法:变量、循环、条件判断      1

10         11                    Python付费课程推荐:XX训练营、XX网课                   1

11         12           机器学习入门:Python+Scikit-learn实现线性回归                  1

3           4                Python小项目推荐:计算器、简易爬虫、天气查询工具      1

9          10                 Python调试技巧:print调试、pdb调试工具                        1

0           1  Python安装需要下载官网安装包,安装时勾选Add Python to PATH  1

14         15                     Python代码规范:PEP8规范、注释编写                       1

7           8                           C++基础语法:变量、循环、指针                             0

6           7            Python办公自动化:Excel处理、Word生成、邮件发送            1

8           9                   Python环境管理:Anaconda安装与使用                            1

13         14                          HTML基础语法:标签、属性、布局                          0

4           5                       Java安装需要下载JDK,配置环境变量                          0

标签分布:{1: 12, 0: 3}

步骤 3:交叉熵排序(精排)

使用逻辑回归模型,基于交叉熵损失训练,对粗排结果做精排:

from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import StandardScaler
# 1. 特征工程:提取文本匹配特征
def extract_features(question_embedding, answer_embeddings, distances, answer_texts, user_question):
    """提取特征:向量相似度、向量距离、关键词重合数"""
    # (1)向量余弦相似度
    question_vec = question_embedding[0].reshape(1, -1)
    cosine_sim = cosine_similarity(question_vec, answer_embeddings)[0]
    # (2)向量距离(L2)
    # distances 已经是L2距离了
    # (3)关键词重合数(简单中文分词:按字符提取)
    # 提取常见中文关键词
    import re
    question_keywords = set(re.findall(r'[\u4e00-\u9fa5]+|[a-zA-Z]+', user_question))
    # 过滤掉标点和停用词
    stop_words = {'怎么', '如何', '是', '的', '吗'}
    question_keywords = question_keywords - stop_words
    keyword_overlap = []
    for text in answer_texts:
        answer_keywords = set(re.findall(r'[\u4e00-\u9fa5]+|[a-zA-Z]+', text))
        overlap = len(question_keywords & answer_keywords)
        keyword_overlap.append(overlap)
    # (4)特征合并:相似度越大越好,距离越小越好
    # 距离取负值,使得距离越小分数越高
    features = np.column_stack([cosine_sim, -distances, keyword_overlap])
    return features
# 2. 为粗排结果提取特征
rough_answer_texts = rough_ranking_results["answer_text"].tolist()
rough_labels = rough_ranking_results["label"].values
rough_answer_embeddings = answer_embeddings[indices[0]]
features = extract_features(question_embedding, rough_answer_embeddings, distances[0], rough_answer_texts, user_question)
print(f"\n特征矩阵形状: {features.shape}")
print(f"特征列含义: [余弦相似度, -L2距离, 关键词重合数]")
print(f"前3个样本的特征:")
print(features[:3])
import re
question_keywords = set(re.findall(r'[\u4e00-\u9fa5]+|[a-zA-Z]+', user_question))
print(f"问题关键词: {question_keywords}")
# 3. 训练交叉熵排序模型(逻辑回归,损失函数为交叉熵)
# 使用 lbfgs 求解器,支持二分类
ranking_model = LogisticRegression(solver='lbfgs', random_state=42, max_iter=1000)
# 检查是否有足够的正负样本
if len(set(rough_labels)) < 2:
    print("\n警告:粗排结果中只有一个类别,无法训练排序模型!")
    print("直接使用粗排距离作为排序分数")
    rough_ranking_results["fine_score"] = -distances[0]  # 距离越小分数越高
else:
    print(f"\n标签分布: {rough_labels}")
    ranking_model.fit(features, rough_labels)
    print(f"模型系数: {ranking_model.coef_}")
    print(f"模型截距: {ranking_model.intercept_}")
    # 4. 预测匹配概率(精排分数)
    fine_ranking_scores = ranking_model.predict_proba(features)[:, 1]  # 正样本概率
    print(f"预测分数: {fine_ranking_scores[:5]}")
    rough_ranking_results["fine_score"] = fine_ranking_scores
# 5. 精排结果:按分数排序,取Top 5
fine_ranking_results = rough_ranking_results.sort_values(by="fine_score", ascending=False).head(10)
print("\n交叉熵精排结果(Top 10):")
print(fine_ranking_results[["answer_id", "answer_text", "fine_score", "label"]])

image.gif

输出结果:

特征矩阵形状: (15, 3)

特征列含义: [余弦相似度, -L2距离, 关键词重合数]

前3个样本的特征:

[[  0.77090418  -7.90510368   1.        ]

[  0.67816496 -10.6061821    1.        ]

[  0.61285162 -12.32204628   1.        ]]

问题关键词: {'新手怎么学', 'Python'}

标签分布: [1 1 1 1 1 1 1 1 1 1 0 1 1 0 0]

模型系数: [[0.0898246  0.34052198 0.78514702]]

模型截距: [8.38018606]

预测分数: [0.9985615  0.996369   0.99346778 0.99234575 0.99102127]

交叉熵精排结果(Top 10):

   answer_id                                answer_text  fine_score  label

12         13               Python书籍推荐:《Python编程:从入门到实践》    0.998562      1

5           6                    Python进阶学习:函数、类、模块、异常处理    0.996369      1

2           3                Python新手推荐廖雪峰教程、菜鸟教程、B站黑马视频    0.993468      1

1           2                零基础学Python先学基础语法:变量、循环、条件判断    0.992346      1

10         11                    Python付费课程推荐:XX训练营、XX网课    0.991021      1

11         12           机器学习入门:Python+Scikit-learn实现线性回归    0.982300      1

3           4                Python小项目推荐:计算器、简易爬虫、天气查询工具    0.979337      1

9          10                 Python调试技巧:print调试、pdb调试工具    0.962889      1

0          1  Python安装需要下载官网安装包,安装时勾选Add Python to PATH    0.957313    1

14         15                     Python代码规范:PEP8规范、注释编写    0.937097      1

步骤 4:余弦重排序

对精排结果做余弦重排序,优化多样性:

def cosine_re_ranking(candidates, embedding_model, alpha=0.8):
    """
    余弦重排序
    参数:
    - candidates: 精排结果DataFrame,包含answer_text和fine_score
    - embedding_model: Sentence-BERT模型
    - alpha: 惩罚系数
    返回:
    - re_ranked_candidates: 重排序后的结果
    """
    # 1. 候选项向量化
    answer_texts = candidates["answer_text"].tolist()
    embeddings = embedding_model.encode(answer_texts, convert_to_numpy=True)
    
    # 2. 计算相似度矩阵
    similarity_matrix = cosine_similarity(embeddings)
    
    # 3. 初始化重排序分数
    re_ranking_scores = candidates["fine_score"].values.copy()
    
    # 4. 遍历每个候选项,计算加权分数
    for i in range(1, len(re_ranking_scores)):
        # 取当前项与前面所有项的最大相似度
        max_similarity = np.max(similarity_matrix[i, :i])
        # 加权分数
        re_ranking_scores[i] = re_ranking_scores[i] * (1 - alpha * max_similarity)
    
    # 5. 更新分数并排序
    candidates["re_score"] = re_ranking_scores
    re_ranked_candidates = candidates.sort_values(by="re_score", ascending=False)
    
    return re_ranked_candidates
# 执行余弦重排序
re_ranked_results = cosine_re_ranking(fine_ranking_results, embedding_model, alpha=0.8)
print("\n余弦重排序结果:")
print(re_ranked_results[["answer_id", "answer_text", "fine_score", "re_score"]])

image.gif

输出结果:

余弦重排序结果:

   answer_id                                answer_text             fine_score               re_score

12         13           Python书籍推荐:《Python编程:从入门到实践》    0.998562  0.998562

0           1  Python安装需要下载官网安装包,安装时勾选Add...    0.957313  0.527326

9          10                 Python调试技巧:print调试、pdb调试工具    0.962889  0.486215

11         12           机器学习入门:Python+Scikit-learn实现线性回归    0.982300  0.478992

3           4        Python小项目推荐:计算器、简易爬虫、天气查询工具    0.979337  0.471634

14         15                     Python代码规范:PEP8规范、注释编写    0.937097  0.461750

1           2        零基础学Python先学基础语法:变量、循环、条件判断    0.992346  0.440093

10         11                    Python付费课程推荐:XX训练营、XX网课    0.991021  0.414431

5           6                    Python进阶学习:函数、类、模块、异常处理    0.996369  0.402303

2           3         Python新手推荐廖雪峰教程、菜鸟教程、B站黑马视频    0.993468  0.401953

步骤 5:大模型生成最终回答

以调用千问大模型为例,整合重排序结果生成最终回答:

import dashscope
from dashscope import Generation
import os

# 配置通义千问API Key(需替换为自己的)
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
def generate_answer_with_llm(question, candidates):
    """
    调用大模型生成最终回答
    参数:
    - question: 用户问题
    - candidates: 重排序后的候选答案
    """
    # 构建Prompt
    candidate_texts = candidates["answer_text"].tolist()
    prompt = f"""
    请你结合以下候选答案,为用户问题“{question}”生成一个通俗易懂、结构清晰的回答。要求:
    1. 优先保留核心步骤,去掉重复内容;
    2. 语言风格适合新手,避免专业术语;
    3. 结构分为“基础准备”“学习步骤”“推荐资源”三部分;
    4. 回答长度控制在300字以内。
    
    候选答案:
    """
    for i, text in enumerate(candidate_texts, 1):
        prompt += f"{i}. {text}\n"
    
    # 调用通义千问
    response = Generation.call(
        model='qwen-turbo',
        messages=[{'role': 'user', 'content': prompt}],
        result_format='message',
        temperature=0.7,
        top_p=0.8
    )
    
    # 提取生成结果
    if response.status_code == 200:
        return response.output.choices[0].message.content
    else:
        return f"生成失败:{response.code} - {response.message}"

# 生成最终回答
final_answer = generate_answer_with_llm(user_question, re_ranked_results)
print("\n大模型生成的最终回答:")
print(final_answer)

image.gif

输出结果:

大模型生成的最终回答:

### 新手怎么学Python?

#### 基础准备

首先,你需要准备一台可以运行Python的电脑,并且下载安装Python。你可以访问Python官方网站下载安装包,并在安装过程中勾选“Add Python to PATH”,这样你就可以直接在命令行中使用Python了。

#### 学习步骤

1. **学习基础语法**:了解Python的基本语法,包括变量、循环、条件判断等。

2. **阅读书籍**:推荐《Python编程:从入门到实践》,这本书适合初学者,内容通俗易懂。

3. **在线课程**:可以选择一些免费的零基础课程,比如廖雪峰教程、菜鸟教程或者B站上的黑马视频。

4. **实践项目**:通过做一些小项目来巩固学习成果,比如计算器、简易爬虫或天气查询工具。

5. **进阶学习**:当你对基础语法有一定了解后,可以进一步学习函数、类、模块和异常处理等内容。

#### 推荐资源

- **书籍**:《Python编程:从入门到实践》

- **在线课程**:廖雪峰教程、菜鸟教程、B站黑马视频

- **调试工具**:print调试和pdb调试工具

- **小项目**:计算器、简易爬虫、天气查询工具

通过以上步骤和资源,你可以系统地学习Python,并逐步提高自己的编程能力。

六、总结

       通过对粗排→交叉熵精排→余弦重排序→大模型生成这套流程的深入了解,明白检索系统的核心,真的不在大模型,而在排序算法。真正决定 80% 质量的,是前面的排序环节:粗排负责快速缩小范围,交叉熵负责精准打分,余弦重排序解决冗余和多样性问题。这三步把靠谱的候选内容筛出来,后面大模型才有的发挥。如果前面算法拉胯,给大模型一堆垃圾内容,再强的模型也救不回来。简单点就是先把向量检索、精排模型跑通,再用大模型做最后一公里的润色和整合,性价比极高。

       我们实际操作优先把特征、相似度、排序损失函数吃透,把精排效果做上去;重排序用来提升多样性,大模型只做锦上添花。不要一上来就全链路大模型,又慢又贵还不稳定。总而言之:排序算法是地基,大模型是装修。地基打好,房子才稳;装修到位,住着才舒服。把这套“算法为主、大模型为辅”的思路用熟,不管是做项目还是面试,都会非常有竞争力。

附录:完整的示例代码

import pandas as pd
import numpy as np
# 模拟问答知识库
knowledge_base = pd.DataFrame({
    "answer_id": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
    "answer_text": [
        "Python安装需要下载官网安装包,安装时勾选Add Python to PATH",
        "零基础学Python先学基础语法:变量、循环、条件判断",
        "Python新手推荐廖雪峰教程、菜鸟教程、B站黑马视频",
        "Python小项目推荐:计算器、简易爬虫、天气查询工具",
        "Java安装需要下载JDK,配置环境变量",  # 负样本
        "Python进阶学习:函数、类、模块、异常处理",
        "Python办公自动化:Excel处理、Word生成、邮件发送",
        "C++基础语法:变量、循环、指针",  # 负样本
        "Python环境管理:Anaconda安装与使用",
        "Python调试技巧:print调试、pdb调试工具",
        "Python付费课程推荐:XX训练营、XX网课",
        "机器学习入门:Python+Scikit-learn实现线性回归",
        "Python书籍推荐:《Python编程:从入门到实践》",
        "HTML基础语法:标签、属性、布局",  # 负样本
        "Python代码规范:PEP8规范、注释编写"
    ],
    "label": [1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1]  # 1=正样本,0=负样本
})
# 用户问题
user_question = "新手怎么学Python?"
from sentence_transformers import SentenceTransformer
from modelscope import snapshot_download
import faiss
cache_dir = "D:\\modelscope\\hub"
model_dir = snapshot_download(
    model_id="sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2",
    cache_dir=cache_dir,
    revision="master"  # 或指定分支/commit
)
# 1. 加载Embedding模型(轻量级Sentence-BERT)
embedding_model = SentenceTransformer("D:\\modelscope\\hub\\sentence-transformers\\paraphrase-multilingual-MiniLM-L12-v2")
# 2. 知识库文本向量化
answer_texts = knowledge_base["answer_text"].tolist()
answer_embeddings = embedding_model.encode(answer_texts, convert_to_numpy=True)
# 3. 构建FAISS索引
dimension = answer_embeddings.shape[1]  # 向量维度:384
index = faiss.IndexFlatL2(dimension)  # 暴力检索(适合小数据集)
index.add(answer_embeddings)
# 4. 用户问题向量化
question_embedding = embedding_model.encode([user_question], convert_to_numpy=True)
# 5. 向量检索:筛选Top 10候选集(粗排)
k = 15  # 增大候选集,确保有足够的正负样本
distances, indices = index.search(question_embedding, k)
# 6. 提取粗排结果
rough_ranking_results = knowledge_base.iloc[indices[0]]
print("粗排结果:")
print(rough_ranking_results[["answer_id", "answer_text", "label"]])
print(f"\n标签分布:{rough_ranking_results['label'].value_counts().to_dict()}")
from sklearn.linear_model import LogisticRegression
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity
from sklearn.preprocessing import StandardScaler
# 1. 特征工程:提取文本匹配特征
def extract_features(question_embedding, answer_embeddings, distances, answer_texts, user_question):
    """提取特征:向量相似度、向量距离、关键词重合数"""
    # (1)向量余弦相似度
    question_vec = question_embedding[0].reshape(1, -1)
    cosine_sim = cosine_similarity(question_vec, answer_embeddings)[0]
    # (2)向量距离(L2)
    # distances 已经是L2距离了
    # (3)关键词重合数(简单中文分词:按字符提取)
    # 提取常见中文关键词
    import re
    question_keywords = set(re.findall(r'[\u4e00-\u9fa5]+|[a-zA-Z]+', user_question))
    # 过滤掉标点和停用词
    stop_words = {'怎么', '如何', '是', '的', '吗'}
    question_keywords = question_keywords - stop_words
    keyword_overlap = []
    for text in answer_texts:
        answer_keywords = set(re.findall(r'[\u4e00-\u9fa5]+|[a-zA-Z]+', text))
        overlap = len(question_keywords & answer_keywords)
        keyword_overlap.append(overlap)
    # (4)特征合并:相似度越大越好,距离越小越好
    # 距离取负值,使得距离越小分数越高
    features = np.column_stack([cosine_sim, -distances, keyword_overlap])
    return features
# 2. 为粗排结果提取特征
rough_answer_texts = rough_ranking_results["answer_text"].tolist()
rough_labels = rough_ranking_results["label"].values
rough_answer_embeddings = answer_embeddings[indices[0]]
features = extract_features(question_embedding, rough_answer_embeddings, distances[0], rough_answer_texts, user_question)
print(f"\n特征矩阵形状: {features.shape}")
print(f"特征列含义: [余弦相似度, -L2距离, 关键词重合数]")
print(f"前3个样本的特征:")
print(features[:3])
import re
question_keywords = set(re.findall(r'[\u4e00-\u9fa5]+|[a-zA-Z]+', user_question))
print(f"问题关键词: {question_keywords}")
# 3. 训练交叉熵排序模型(逻辑回归,损失函数为交叉熵)
# 使用 lbfgs 求解器,支持二分类
ranking_model = LogisticRegression(solver='lbfgs', random_state=42, max_iter=1000)
# 检查是否有足够的正负样本
if len(set(rough_labels)) < 2:
    print("\n警告:粗排结果中只有一个类别,无法训练排序模型!")
    print("直接使用粗排距离作为排序分数")
    rough_ranking_results["fine_score"] = -distances[0]  # 距离越小分数越高
else:
    print(f"\n标签分布: {rough_labels}")
    ranking_model.fit(features, rough_labels)
    print(f"模型系数: {ranking_model.coef_}")
    print(f"模型截距: {ranking_model.intercept_}")
    # 4. 预测匹配概率(精排分数)
    fine_ranking_scores = ranking_model.predict_proba(features)[:, 1]  # 正样本概率
    print(f"预测分数: {fine_ranking_scores[:5]}")
    rough_ranking_results["fine_score"] = fine_ranking_scores
# 5. 精排结果:按分数排序,取Top 5
fine_ranking_results = rough_ranking_results.sort_values(by="fine_score", ascending=False).head(10)
print("\n交叉熵精排结果(Top 10):")
print(fine_ranking_results[["answer_id", "answer_text", "fine_score", "label"]])
def cosine_re_ranking(candidates, embedding_model, alpha=0.8):
    """
    余弦重排序
    参数:
    - candidates: 精排结果DataFrame,包含answer_text和fine_score
    - embedding_model: Sentence-BERT模型
    - alpha: 惩罚系数
    返回:
    - re_ranked_candidates: 重排序后的结果
    """
    # 1. 候选项向量化
    answer_texts = candidates["answer_text"].tolist()
    embeddings = embedding_model.encode(answer_texts, convert_to_numpy=True)
    
    # 2. 计算相似度矩阵
    similarity_matrix = cosine_similarity(embeddings)
    
    # 3. 初始化重排序分数
    re_ranking_scores = candidates["fine_score"].values.copy()
    
    # 4. 遍历每个候选项,计算加权分数
    for i in range(1, len(re_ranking_scores)):
        # 取当前项与前面所有项的最大相似度
        max_similarity = np.max(similarity_matrix[i, :i])
        # 加权分数
        re_ranking_scores[i] = re_ranking_scores[i] * (1 - alpha * max_similarity)
    
    # 5. 更新分数并排序
    candidates["re_score"] = re_ranking_scores
    re_ranked_candidates = candidates.sort_values(by="re_score", ascending=False)
    
    return re_ranked_candidates
# 执行余弦重排序
re_ranked_results = cosine_re_ranking(fine_ranking_results, embedding_model, alpha=0.8)
print("\n余弦重排序结果:")
print(re_ranked_results[["answer_id", "answer_text", "fine_score", "re_score"]])

import dashscope
from dashscope import Generation
import os

# 配置通义千问API Key(需替换为自己的)
dashscope.api_key = os.environ.get('DASHSCOPE_API_KEY')
def generate_answer_with_llm(question, candidates):
    """
    调用大模型生成最终回答
    参数:
    - question: 用户问题
    - candidates: 重排序后的候选答案
    """
    # 构建Prompt
    candidate_texts = candidates["answer_text"].tolist()
    prompt = f"""
    请你结合以下候选答案,为用户问题“{question}”生成一个通俗易懂、结构清晰的回答。要求:
    1. 优先保留核心步骤,去掉重复内容;
    2. 语言风格适合新手,避免专业术语;
    3. 结构分为“基础准备”“学习步骤”“推荐资源”三部分;
    4. 回答长度控制在300字以内。
    
    候选答案:
    """
    for i, text in enumerate(candidate_texts, 1):
        prompt += f"{i}. {text}\n"
    
    # 调用通义千问
    response = Generation.call(
        model='qwen-turbo',
        messages=[{'role': 'user', 'content': prompt}],
        result_format='message',
        temperature=0.7,
        top_p=0.8
    )
    
    # 提取生成结果
    if response.status_code == 200:
        return response.output.choices[0].message.content
    else:
        return f"生成失败:{response.code} - {response.message}"

# 生成最终回答
final_answer = generate_answer_with_llm(user_question, re_ranked_results)
print("\n大模型生成的最终回答:")
print(final_answer)

image.gif

相关文章
|
8天前
|
缓存 人工智能 自然语言处理
我对比了8个Claude API中转站,踩了不少坑,总结给你
本文是个人开发者耗时1周实测的8大Claude中转平台横向评测,聚焦Claude Code真实体验:以加权均价(¥/M token)、内部汇率、缓存支持、模型真实性及稳定性为核心指标。
3425 20
|
20天前
|
人工智能 自然语言处理 安全
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
本文介绍了Claude Code终端AI助手的使用指南,主要内容包括:1)常用命令如版本查看、项目启动和更新;2)三种工作模式切换及界面说明;3)核心功能指令速查表,包含初始化、压缩对话、清除历史等操作;4)详细解析了/init、/help、/clear、/compact、/memory等关键命令的使用场景和语法。文章通过丰富的界面截图和场景示例,帮助开发者快速掌握如何通过命令行和交互界面高效使用Claude Code进行项目开发,特别强调了CLAUDE.md文件作为项目知识库的核心作用。
17975 60
Claude Code 全攻略:命令大全 + 实战工作流(建议收藏)
|
1天前
|
SQL 人工智能 弹性计算
阿里云发布 Agentic NDR,威胁检测与响应进入智能体时代
欢迎前往阿里云云防火墙控制台体验!
1156 2
|
4天前
|
人工智能 JSON BI
DeepSeek V4 来了!超越 Claude Sonnet 4.5,赶紧对接 Claude Code 体验一把
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro 的真实体验与避坑记录 本文记录我将 Claude Code 对接 DeepSeek 最新模型(V4Pro)后的真实体验,测试了 Skills 自动化查询和积木报表 AI 建表两个场景——有惊喜,也踩
1851 8
|
15天前
|
人工智能 JavaScript Ubuntu
低成本搭建AIP自动化写作系统:Hermes保姆级使用教程,长文和逐步实操贴图
我带着怀疑的态度,深度使用了几天,聚焦微信公众号AIP自动化写作场景,写出来的几篇文章,几乎没有什么修改,至少合乎我本人的意愿,而且排版风格,也越来越完善,同样是起码过得了我自己这一关。 这个其实OpenClaw早可以实现了,但是目前我觉得最大的区别是,Hermes会自主总结提炼,并更新你的写作技能。 相信就冲这一点,就值得一试。 这篇帖子主要就Hermes部署使用,作一个非常详细的介绍,几乎一步一贴图。 关于Hermes,无论你赞成哪种声音,我希望都是你自己动手行动过,发自内心的选择!
3170 29
|
3天前
|
人工智能 缓存 BI
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
JeecgBoot AI专题研究 把 Claude Code 接入 DeepSeek V4Pro,跑完 Skills —— OA 审批、大屏、报表、部署 5 大实战场景后的真实体验 ![](https://oscimg.oschina.net/oscnet/up608d34aeb6bafc47f
1476 3
Claude Code + DeepSeek V4-Pro 真实评测:除了贵,没别的毛病
|
4天前
|
机器学习/深度学习 缓存 测试技术
DeepSeek-V4开源:百万上下文,Agent能力比肩顶级闭源模型
DeepSeek-V4正式开源!含V4-Pro(1.6T参数)与V4-Flash(284B参数)双版本,均支持百万token上下文。首创混合注意力架构,Agent能力、世界知识与推理性能全面领先开源模型,数学/代码评测比肩顶级闭源模型。
1734 6
|
5天前
|
人工智能 测试技术 API
阿里Qwen3.6-27B正式开源:网友直呼“太牛了”!
阿里云千问3.6系列重磅开源Qwen3.6-27B稠密大模型!官网:https://t.aliyun.com/U/JbblVp 仅270亿参数,编程能力媲美千亿模型,在SWE-bench等权威基准中表现卓越。支持多模态理解、本地部署及OpenClaw等智能体集成,已开放Hugging Face与ModelScope下载。