如何反向推导余弦相似度评分函数以生成对应目标评分的向量?
从已知向量和目标余弦相似度生成对应向量
问题描述
我有一个计算向量余弦相似度的Python程序,支持批量计算并按评分排序。现在需要为该程序编写系统测试,需要从已知向量vectorA和目标余弦相似度评分出发,生成任意一个符合条件的vectorB。
例如已知:
vectorA = [10, 100, 1000] vectorB = [200, 200, 200]
二者的余弦相似度约为0.638,但现在需要反向推导:给定vectorA和目标评分,生成满足条件的vectorB。
参考的余弦相似度计算函数:
def cosine_score(vectorA, vectorB): AB = sum([vectorA[i] * vectorB[i] for i in range(len(vectorA))]) norm_A = sum([x**2 for x in vectorA])**0.5 norm_B = sum([x**2 for x in vectorB])**0.5 return AB / (norm_A * norm_B)
解决方案
利用余弦相似度的几何意义(向量夹角的余弦值),我们可以通过**"基准向量+垂直分量"**的方法快速构造符合要求的vectorB,步骤如下:
核心思路
余弦相似度公式:
$$\cos\theta = \frac{A \cdot B}{||A|| \times ||B||}$$
已知$A$和目标评分$s=\cos\theta$,我们可以构造$B = s \cdot A + C$,其中$C$是与$A$垂直的向量(即$A \cdot C = 0$),这样就能保证$\cos\theta=s$。
具体步骤
- 计算vectorA的模长:
$$||A|| = \sqrt{\sum_{i=0}^{n-1} A_i^2}$$ - 构造与vectorA垂直的基础向量C0:
找到vectorA中第一个非零元素$A_k$,构造C0:将C0的第k个元素设为$-A_{k+1}$,第k+1个元素设为$A_k$,其余元素为0(若vectorA只有1维,直接取C0=[0],但1维向量的余弦相似度只能是1或-1)。
比如对于$A=[10,100,1000]$,第一个非零元素是10(索引0),则$C0=[100, -10, 0]$,验证$A \cdot C0 = 10100 + 100(-10) + 1000*0 = 0$,确实垂直。 - 缩放垂直向量C到目标长度:
为了让构造的B模长等于A的模长(方便计算,也可自定义其他模长),计算缩放系数:
$$t = ||A|| \times \sqrt{1-s^2} / ||C0||$$
得到最终的垂直分量$C = t \cdot C0$。 - 生成目标vectorB:
$$B = s \cdot A + C$$
Python实现代码
import math def cosine_score(vectorA, vectorB): AB = sum([vectorA[i] * vectorB[i] for i in range(len(vectorA))]) norm_A = math.sqrt(sum([x**2 for x in vectorA])) norm_B = math.sqrt(sum([x**2 for x in vectorB])) return AB / (norm_A * norm_B) def generate_vector_b(vectorA, target_score): n = len(vectorA) if n == 1: # 1维向量的余弦相似度只能是1或-1 if target_score == 1: return [vectorA[0] * 2] elif target_score == -1: return [-vectorA[0] * 2] else: raise ValueError("1维向量无法生成目标余弦相似度(只能是1或-1)") # 计算vectorA的模长 norm_A = math.sqrt(sum(x**2 for x in vectorA)) # 构造与vectorA垂直的基础向量C0 c0 = [0]*n # 找到第一个非零元素的索引 first_non_zero = 0 while first_non_zero < n and vectorA[first_non_zero] == 0: first_non_zero += 1 # 构造垂直向量:交换相邻两个元素的位置并取反其中一个 if first_non_zero < n-1: c0[first_non_zero] = vectorA[first_non_zero + 1] c0[first_non_zero + 1] = -vectorA[first_non_zero] else: # 处理最后一个元素非零的情况(前面全为0) c0[0] = vectorA[first_non_zero] c0[first_non_zero] = -vectorA[0] # 计算C0的模长 norm_c0 = math.sqrt(sum(x**2 for x in c0)) if norm_c0 == 0: raise ValueError("vectorA全为0,无法生成有效向量B") # 缩放C0到目标长度 t = norm_A * math.sqrt(1 - target_score**2) / norm_c0 c = [x * t for x in c0] # 生成vectorB vectorB = [target_score * x + c[i] for i, x in enumerate(vectorA)] return vectorB # 测试示例 if __name__ == "__main__": vectorA = [10, 100, 1000] target = 0.638 vectorB = generate_vector_b(vectorA, target) print(f"生成的vectorB: {vectorB}") print(f"实际余弦相似度: {cosine_score(vectorA, vectorB):.3f}")
验证说明
运行上述代码后,生成的vectorB与vectorA的余弦相似度会精确匹配目标值(误差来自浮点数计算,可忽略)。如果不需要vectorB的模长与vectorA一致,也可以直接取$B = s \cdot A + k \cdot C0$(k为任意非零常数),同样能满足目标相似度。
内容的提问来源于stack exchange,提问作者CCranney
相关产品推荐
相关产品推荐

