You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

余弦相似度计算过慢,如何优化语义搜索中的相似度函数?

优化固定向量集的语义搜索余弦相似度计算速度

嘿,我看你遇到的问题是固定300条句子的语义搜索太慢,瓶颈在余弦相似度计算,而且用numba的版本结果不准对吧?刚好你的场景是向量固定,这有几个针对性的优化方案,肯定能把10-12秒的耗时压下来:

方案1:预计算固定向量的模长(最直接的减重复计算)

你现在的代码每次循环都要计算句子向量的模长np.linalg.norm(v2),但300条句子的向量是固定的啊!这完全是重复劳动,预计算一次就能省掉每次查询的300次模长计算。

操作步骤:

  1. 提前运行一次模长计算,把结果存起来(只需要跑一次,因为向量不变):
import numpy as np
# vectors是你的(300,500)向量矩阵
vec_norms = np.linalg.norm(vectors, axis=1)
  1. 修改你的余弦相似度函数和搜索函数,直接用预计算好的模长:
def cosine_similarity(v1, v2, v2_norm):
    mag1 = np.linalg.norm(v1)
    if (not mag1) or (not v2_norm):
        return 0
    return np.dot(v1, v2) / (mag1 * v2_norm)

def semantic_search(cleaned_query, data, vectors, vec_norms):
    query_vec = get_features(cleaned_query)[0].ravel()
    query_norm = np.linalg.norm(query_vec)
    res = []
    for i, d in enumerate(data):
        qvec = vectors[i].ravel()
        sim = cosine_similarity(query_vec, qvec, vec_norms[i])
        if sim > 0.5:
            # 注意:原来的排序用字符串会有问题,转成float再排序更准确
            res.append((format(sim * 100, '.2f'), data[i]))
    return sorted(res, key=lambda x: float(x[0]), reverse=True)[:15]

方案2:用numpy向量化计算,彻底干掉Python循环(最推荐)

Python的for循环本身就慢,numpy的向量化运算底层是C实现的,批量处理300个向量的相似度会快到离谱,直接把耗时从秒级降到毫秒级。

优化后的代码:

# 预计算模长(仅一次)
vec_norms = np.linalg.norm(vectors, axis=1)

def semantic_search(cleaned_query, data, vectors, vec_norms):
    query_vec = get_features(cleaned_query)[0].ravel()
    query_norm = np.linalg.norm(query_vec)
    
    # 处理查询向量为0的特殊情况
    if query_norm == 0:
        return []
    
    # 批量计算所有点积和相似度,不用循环!
    dot_products = np.dot(vectors, query_vec)
    similarities = dot_products / (query_norm * vec_norms)
    
    # 筛选相似度>0.5的结果
    valid_mask = similarities > 0.5
    valid_indices = np.where(valid_mask)[0]
    valid_similarities = similarities[valid_mask]
    valid_sentences = [data[i] for i in valid_indices]
    
    # 按相似度降序排序,取前15个
    sorted_results = sorted(zip(valid_similarities, valid_sentences), key=lambda x: x[0], reverse=True)[:15]
    
    # 转成你需要的百分比格式
    return [(format(sim * 100, '.2f'), sent) for sim, sent in sorted_results]

这个方案的结果和你原来的cosine_similarity完全一致,但速度提升是数量级的,因为完全避开了Python循环的开销。

方案3:修复numba版本的余弦相似度函数(如果想保留numba)

你之前的nb_cosine结果不准是因为它返回的是余弦距离(1-相似度),而不是你需要的余弦相似度!改一下返回值就好了:

import numba as nb
import numpy as np

@nb.jit(nopython=True, fastmath=True)
def nb_cosine(x, y):
    xx, yy, xy = 0.0, 0.0, 0.0
    for i in range(len(x)):
        xx += x[i] * x[i]
        yy += y[i] * y[i]
        xy += x[i] * y[i]
    norm_product = np.sqrt(xx * yy)
    if norm_product == 0:
        return 0.0
    # 这里返回的是相似度,不是距离
    return xy / norm_product

修复后再结合方案1的预计算模长,速度也会比原来的Python循环快很多,而且结果准确。

额外小优化:缓存查询向量的编码结果

你的get_features每次都要创建TensorFlow Session并初始化变量,这也有一定开销。如果同一个查询可能被重复搜索,可以用lru_cache缓存结果:

from functools import lru_cache

# 确保graph是全局变量,否则缓存可能失效
@lru_cache(maxsize=100)
def get_features(texts):
    if type(texts) is str:
        texts = [texts]
    with tf.Session(graph=graph) as sess:
        sess.run([tf.global_variables_initializer(), tf.tables_initializer()])
        return sess.run(embed(texts))

如果查询都是唯一的,这个优化作用不大,但聊胜于无。

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

火山引擎 最新活动