使用Sentence Transformers余弦度量时句子相似度准确率异常求助
长转写文本与短预定义句子相似度偏低的解决办法
我在使用Sentence Transformers结合余弦度量计算句子相似度时遇到问题:将音频转写的长文本与预定义短句集对比,即便预定义句子完整出现在转写文本中,相似度得分仍偏低。推测是转写文本过长、包含大量无关内容导致的,相关代码如下:
for rs in red_section: for s in split: logging.info(f'{s} " --vs-- " {rs}') # score = sentence_similarity_model.get_score(sentence, rs, metric="cosine") sentences = [rs, s] embeddings = sent_model.encode([rs, s], convert_to_tensor=True) cosine_scores = util.cos_sim(embeddings, embeddings) pairs = [] for i in range(len(cosine_scores) - 1): for j in range(i + 1, len(cosine_scores)): pairs.append({'index': [i, j], 'score': cosine_scores[i][j]}) pairs = sorted(pairs, key=lambda x: x['score'], reverse=True) for pair in pairs[0:10]: i, j = pair['index'] logging.info(f'{sentences[i]} --vs-- {sentences[j]}') score = round(pair["score"].item() * 100) logging.info(f'Score ---> {score} %') if score >= 80: logging.info(f"Red Confidence in similarity -> {round(score * 100)} %") for ys in yellow_section: for s in split: logging.info(f'{s} " --vs-- " {ys}') # score = sentence_similarity_model.get_score(sentence, ys, metric="cosine") sentences = [ys, s] embeddings = sent_model.encode([ys, s], convert_to_tensor=True) cosine_scores = util.cos_sim(embeddings, embeddings) pairs = [] for i in range(len(cosine_scores) - 1): for j in range(i + 1, len(cosine_scores)): pairs.append({'index': [i, j], 'score': cosine_scores[i][j]}) pairs = sorted(pairs, key=lambda x: x['score'], reverse=True) for pair in pairs[0:10]: i, j = pair['index'] logging.info(f'{sentences[i]} --vs-- {sentences[j]}') score = round(pair["score"].item() * 100) logging.info(f'Score ---> {score} %') if score >= 80: logging.info(f"Yellow Confidence in similarity -> {score} %") for gs in green_section: for s in split: logging.info(f'{s} " --vs-- " {gs}') # score = sentence_similarity_model.get_score(sentence, gs, metric="cosine") sentences = [gs, s] embeddings = sent_model.encode([gs, s], convert_to_tensor=True) cosine_scores = util.cos_sim(embeddings, embeddings) pairs = [] for i in range(len(cosine_scores) - 1): for j in range(i + 1, len(cosine_scores)): pairs.append({'index': [i, j], 'score': cosine_scores[i][j]}) pairs = sorted(pairs, key=lambda x: x['score'], reverse=True) logging.info(f'Pairsssssss ---> {pairs}') for pair in pairs[0:10]: logging.info(f'Pairrrrrr ---> {pair}') i, j = pair['index'] logging.info(f'{sentences[i]} --vs-- {sentences[j]}') score = round(pair["score"].item() * 100) logging.info(f'Score ---> {score} %') if score >= 80: logging.info(f"Green Confidence in similarity -> {score} %")
问题根源
Sentence Transformers生成的句向量是整句语义的加权平均,长文本里的无关内容会稀释目标句子的语义权重,导致和预定义短句的向量余弦相似度被拉低——相当于把目标句子“淹没”在无关信息里了。
解决思路与优化方案
1. 拆分长文本,用局部片段匹配
不用拿整个长文本去算相似度,先把转写文本拆成小的语义单元(分句、固定长度片段),再用每个片段和预定义句子比对,取最高得分。这样能避开无关内容的干扰,聚焦到包含目标句子的局部片段。
2. 换用匹配专用的模型
Sentence Transformers里有针对短文本匹配/问答任务优化的模型,比如multi-qa-MiniLM-L6-cos-v1或者all-MiniLM-L6-v2,这些模型对短长文本的语义匹配更敏感,替换现有模型能直接提升得分精度。
3. 改用CrossEncoder做直接打分
CrossEncoder模型会直接对句子对进行相似度打分,不像BiEncoder(你现在用的)先生成向量再算余弦,它能更好捕捉局部匹配的语义,适合这种“短句子在长文本里”的场景。
优化后的代码示例
下面是用“拆分长文本+取最高得分”的方式优化后的代码,逻辑更简洁,效果更准确:
from sentence_transformers import SentenceTransformer, util import logging from nltk.tokenize import sent_tokenize import nltk # 先下载分句所需的语料 nltk.download('punkt') # 替换为匹配专用模型 sent_model = SentenceTransformer('multi-qa-MiniLM-L6-cos-v1') def get_max_similarity(short_sent, long_text): # 把长文本拆成独立句子 long_sents = sent_tokenize(long_text) if not long_sents: return 0 # 编码短句子和所有长文本分句 embeddings = sent_model.encode([short_sent] + long_sents, convert_to_tensor=True) # 计算短句子与每个长分句的余弦相似度 cos_scores = util.cos_sim(embeddings[0], embeddings[1:]) # 取最高得分并转成百分比 max_score = round(cos_scores.max().item() * 100) return max_score # 处理red_section for rs in red_section: for s in split: logging.info(f'{s} --vs-- {rs}') score = get_max_similarity(rs, s) logging.info(f'Score ---> {score} %') if score >= 80: logging.info(f"Red Confidence in similarity -> {score} %") # yellow_section和green_section直接复用上面的逻辑即可 for ys in yellow_section: for s in split: logging.info(f'{s} --vs-- {ys}') score = get_max_similarity(ys, s) logging.info(f'Score ---> {score} %') if score >= 80: logging.info(f"Yellow Confidence in similarity -> {score} %") for gs in green_section: for s in split: logging.info(f'{s} --vs-- {gs}') score = get_max_similarity(gs, s) logging.info(f'Score ---> {score} %') if score >= 80: logging.info(f"Green Confidence in similarity -> {score} %")
额外小技巧
- 转写文本常有口语化冗余或错别字,先做简单清洗(比如去掉“嗯”“啊”这类语气词,修正常见错别字)能进一步提升匹配精度。
- 如果转写文本没有明显分句,可以用固定长度的滑动窗口截取片段,再逐个计算相似度。
内容的提问来源于stack exchange,提问作者Niceboy
相关产品推荐
相关产品推荐

