余弦相似度 —— Cosine Similarity

本文深入探讨了余弦相似度与欧氏距离在衡量用户偏好差异时的不同作用机制,通过三维坐标系直观展示了二者在方向与位置上的差异。进一步阐述了在数据分析模型中,欧氏距离侧重于个体特征的绝对差异,而余弦相似度则更关注偏好方向的一致性。最后,提出了调整余弦相似度的概念,以解决在评分系统中因绝对数值导致的相似度误差问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

余弦相似度用向量空间中两个向量夹角的余弦值作为衡量两个个体间差异的大小。相比距离度量,余弦相似度更加注重两个向量在方向上的差异,而非距离或长度上。

与欧几里德距离类似,基于余弦相似度的计算方法也是把用户的喜好作为n-维坐标系中的一个点,通过连接这个点与坐标系的原点构成一条直线(向量),两个用户之间的相似度值就是两条直线(向量)间夹角的余弦值。因为连接代表用户评分的点与原点的直线都会相交于原点,夹角越小代表两个用户越相似,夹角越大代表两个用户的相似度越小。同时在三角系数中,角的余弦值是在[-1, 1]之间的,0度角的余弦值是1,180角的余弦值是-1。

借助三维坐标系来看下欧氏距离和余弦相似度的区别:

从图上可以看出距离度量衡量的是空间各点间的绝对距离,跟各个点所在的位置坐标(即个体特征维度的数值)直接相关;而余弦相似度衡量的是空间向量的夹角,更加的是体现在方向上的差异,而不是位置。如果保持A点的位置不变,B点朝原方向远离坐标轴原点,那么这个时候余弦相似度cosθ是保持不变的,因为夹角不变,而A、B两点的距离显然在发生改变,这就是欧氏距离和余弦相似度的不同之处。

根据欧氏距离和余弦相似度各自的计算方式和衡量特征,分别适用于不同的数据分析模型:欧氏距离能够体现个体数值特征的绝对差异,所以更多的用于需要从维度的数值大小中体现差异的分析,如使用用户行为指标分析用户价值的相似度或差异;而余弦相似度更多的是从方向上区分差异,而对绝对的数值不敏感,更多的用于使用用户对内容评分来区分用户兴趣的相似度和差异,同时修正了用户间可能存在的度量标准不统一的问题(因为余弦相似度对绝对数值不敏感)。

调整余弦相似度 —— Adjusted Cosine Similarity
在余弦相似度的介绍中说到:余弦相似度更多的是从方向上区分差异,而对绝对的数值不敏感。因此没法衡量每个维数值的差异,会导致这样一个情况:比如用户对内容评分,5分制,X和Y两个用户对两个内容的评分分别为(1,2)和(4,5),使用余弦相似度得出的结果是0.98,两者极为相似,但从评分上看X似乎不喜欢这2个内容,而Y比较喜欢,余弦相似度对数值的不敏感导致了结果的误差,需要修正这种不合理性,就出现了调整余弦相似度,即所有维度上的数值都减去一个均值,比如X和Y的评分均值都是3,那么调整后为(-2,-1)和(1,2),再用余弦相似度计算,得到-0.8,相似度为负值并且差异不小,但显然更加符合现实。

<think>首先,用户的问题是关于手动实现矩阵的余弦相似度的计算。用户要求包括计算步骤代码示例。用户提到了“矩阵的余弦相似度”,所以我需要解释如何计算两个向量之间的余弦相似度,并扩展到矩阵中的多个向量。 从系统级指令: - 所有行内数学表达式必须使用 $...$ 格式。 - 独立公式必须使用 $$...$$ 格式并单独成段。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾自然地添加引用标识。 - 回答结构清晰,帮助用户逐步解决问题。 - 引用站内引用(但用户提供了引用[1]、[2]、[3]、[4],这些是关于余弦相似度、Jaccard相似度等的,所以我应该参考这些)。 用户提供的引用: - 引用[1]:解释了余弦相似度,并有一个Python代码示例使用sklearn的cosine_similarity函数。 - 引用[2]:讨论了矩阵相似度的基本概念代码实现,提到了其他方法如Jaccard相似度。 - 引用[3]:关于SSIM(结构相似性指数),但用户的问题是关于余弦相似度的,所以可能不直接相关。 - 引用[4]:关于图像哈希的计算,似乎不相关。 用户的问题焦点是余弦相似度,所以我应该主要参考引用[1][2]。 余弦相似度的定义: - 余弦相似度衡量两个向量之间的夹角余弦值。 - 对于两个向量 $\mathbf{a}$ $\mathbf{b}$,余弦相似度定义为: $$ \text{cosine\_similarity}(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|} $$ 其中 $\mathbf{a} \cdot \mathbf{b}$ 是点积,$\|\mathbf{a}\|$ $\|\mathbf{b}\|$ 是向量的欧几里得范数。 扩展到矩阵: - 如果有一个矩阵,其中每行是一个向量,我们想计算所有对之间的余弦相似度。 - 这类似于sklearn的cosine_similarity函数,它输入一个矩阵,输出一个相似度矩阵。 手动实现步骤: 1. 确保输入是NumPy数组或类似结构,便于向量化操作。 2. 计算每个向量的范数(模长)。 3. 计算点积矩阵(所有对之间的点积)。 4. 将点积矩阵除以范数向量的外积(或广播)。 5. 结果是一个相似度矩阵。 代码示例: - 使用PythonNumPy,因为引用[1]中使用了NumPy(通过sklearn,但底层是NumPy)。 - 手动实现函数,避免使用sklearn。 用户要求“手动实现”,所以我不能直接调用cosine_similarity;我需要从头写。 步骤分解: 1. 输入:一个矩阵 $X$,形状为 (n_samples, n_features),其中每行是一个向量。 2. 计算范数:对每行计算欧几里得范数。 - 范数: $\|\mathbf{x}_i\| = \sqrt{\sum_{j} x_{ij}^2}$ 3. 计算点积矩阵: $S = X X^T$,因为 $X X^T$ 的元素 $S_{ij} = \mathbf{x}_i \cdot \mathbf{x}_j$ 4. 计算范数矩阵:创建一个向量 $\mathbf{n}$ 其中 $n_i = \|\mathbf{x}_i\|$,然后计算外积 $N = \mathbf{n} \mathbf{n}^T$,这样 $N_{ij} = n_i n_j$ 5. 余弦相似度矩阵: $C = S / N$,其中除法是元素级的。 6. 处理除以零的情况:如果某个范数为零,则相似度未定义,通常设为0或NaN。 在代码中,使用NumPy广播来避免显式外积。 代码结构: - 导入NumPy。 - 定义函数:def manual_cosine_similarity(X): - 计算范数:norms = np.linalg.norm(X, axis=1) - 计算点积:dot_product = np.dot(X, X.T) - 计算范数外积:norm_outer = np.outer(norms, norms) - 相似度:similarity = dot_product / norm_outer - 处理零范数:设置分母为1或处理NaN。 - 返回相似度矩阵。 处理除以零: - 如果范数为零,向量是零向量,余弦相似度未定义。通常设为0或NaN。 - 在NumPy中,可以使用np.where来处理。 例如: ```python # 防止除以零 epsilon = 1e-10 # 小值避免除零 norms = np.linalg.norm(X, axis=1, keepdims=True) norms = np.where(norms == 0, epsilon, norms) # 替换零范数为小值 # 然后计算 ``` 但更简单的方法是: ```python norms = np.linalg.norm(X, axis=1) norm_outer = np.outer(norms, norms) # 设置分母为1如果范数为零 norm_outer[norm_outer == 0] = 1 # 或者使用更精细的方法 similarity = dot_product / norm_outer ``` 在点积中,如果两个向量都是零,点积为零,所以相似度应为1(但未定义),通常设为0。 标准做法是:如果两个向量都是零,相似度为1(因为夹角未定义,但认为相同);如果只有一个为零,相似度未定义,常设为0。 在实现中,可以: - 如果范数norms[i] == 0,则设置similarity[i, :] = 0 或 NaN。 但为了简单,可以先计算,然后处理分母为零的情况。 在NumPy中,np.dot除法会自动处理,但最好显式处理。 参考引用[1]的代码,它使用了sklearn,但手动实现类似。 现在,回答结构: 1. 解释余弦相似度的概念。 2. 给出数学公式。 3. 描述手动计算步骤。 4. 提供Python代码示例。 5. 生成相关问题。 使用中文回答。 引用站内引用:在相关部分末尾添加引用标识。 例如,解释概念时,引用[1]定义了余弦相似度。 用户说“上述引用仅供参考”,但我应该尽可能参考,尤其引用[1]直接相关。 生成相关问题:在最后添加相关问题。 相关问题应该基于主题,如: 1. 余弦相似度的优缺点? 2. 如何扩展到其他相似度度量? 3. 实际应用案例? 类似示例中的格式。 完整回答: - 开头介绍。 - 步骤。 - 代码。 - 相关问题。 代码块:在Markdown中用```python```包围。 数学公式:独立公式用$$...$$,行内用$...$。 例如: 余弦相似度定义为:对于两个向量 $\mathbf{a}$ $\mathbf{b}$,有 $$ \text{cosine\_similarity}(\mathbf{a}, \mathbf{b}) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|} $$ 然后扩展到矩阵。 手动实现步骤: 1. 计算每个向量的范数。 2. 计算点积矩阵。 3. 计算范数矩阵。 4. 元素级除法。 5. 处理边界情况。 代码示例。</think>### 手动实现矩阵的余弦相似度计算 余弦相似度是一种衡量两个向量方向相似性的指标,通过计算它们夹角的余弦值来评估相似度,范围在 $[-1, 1]$ 之间(通常取绝对值后范围在 $[0, 1]$)。值越接近 1,表示向量方向越相似;值越接近 0,表示越不相似[^1]。对于矩阵,我们通常计算所有行向量之间的两两相似度,生成一个相似度矩阵。下面我将逐步解释手动实现的计算步骤,并提供完整的 Python 代码示例。 #### 数学基础 给定一个矩阵 $X$,其形状为 $(n, m)$,其中 $n$ 是样本数(行向量数),$m$ 是特征数(列数)。对于任意两个行向量 $\mathbf{x}_i$ $\mathbf{x}_j$,余弦相似度定义为: $$ \text{cosine\_similarity}(\mathbf{x}_i, \mathbf{x}_j) = \frac{\mathbf{x}_i \cdot \mathbf{x}_j}{\|\mathbf{x}_i\| \|\mathbf{x}_j\|} $$ 其中: - $\mathbf{x}_i \cdot \mathbf{x}_j$ 是点积(内积),计算为 $\sum_{k=1}^{m} x_{ik} x_{jk}$。 - $\|\mathbf{x}_i\|$ 是向量 $\mathbf{x}_i$ 的欧几里得范数(模长),计算为 $\sqrt{\sum_{k=1}^{m} x_{ik}^2}$。 扩展到整个矩阵,输出是一个 $n \times n$ 的相似度矩阵 $C$,其中 $C_{ij} = \text{cosine\_similarity}(\mathbf{x}_i, \mathbf{x}_j)$。 #### 手动实现步骤 以下是手动计算矩阵余弦相似度的详细步骤(基于向量化操作,高效且易于实现): 1. **输入矩阵准备**:确保输入矩阵 $X$ 是二维数组,每行代表一个样本向量。 2. **计算向量范数**:对矩阵 $X$ 的每一行计算欧几里得范数 $\|\mathbf{x}_i\|$,得到一个范数向量 $\mathbf{n}$(形状为 $(n,)$)。 3. **计算点积矩阵**:计算 $X$ 与自身的点积矩阵 $S = X X^T$。$S_{ij}$ 直接对应 $\mathbf{x}_i \cdot \mathbf{x}_j$。 4. **计算范数矩阵**:通过范数向量 $\mathbf{n}$ 的外积得到矩阵 $N = \mathbf{n} \mathbf{n}^T$,其中 $N_{ij} = n_i n_j$。 5. **计算相似度矩阵**:执行元素级除法 $C = S / N$。这相当于对每个元素 $C_{ij} = \frac{S_{ij}}{N_{ij}}$。 6. **处理边界情况**:如果某个向量范数为零(即零向量),则相似度未定义。通常设置为 0 或 `NaN`。这里我们使用一个小常数 $\epsilon$ 避免除零错误。 #### Python 代码示例 以下代码使用 NumPy 手动实现余弦相似度计算,不依赖外部库(如 scikit-learn)。代码包含详细注释示例输入输出。 ```python import numpy as np def manual_cosine_similarity(X): """ 手动计算矩阵的余弦相似度。 参数: X: 输入矩阵,形状为 (n_samples, n_features),每行是一个向量。 返回: similarity_matrix: 余弦相似度矩阵,形状为 (n_samples, n_samples)。 """ # 步骤 1: 计算每个向量的范数 (模长) norms = np.linalg.norm(X, axis=1) # 形状 (n_samples,) # 步骤 2: 计算点积矩阵 S = X * X^T dot_product = np.dot(X, X.T) # 形状 (n_samples, n_samples) # 步骤 3: 计算范数矩阵 N = norms * norms^T (外积) norm_outer = np.outer(norms, norms) # 形状 (n_samples, n_samples) # 步骤 4: 处理除零错误 (如果范数为零,设为小值 epsilon) epsilon = 1e-10 # 避免除零的小常数 norm_outer = np.where(norm_outer == 0, epsilon, norm_outer) # 步骤 5: 计算余弦相似度矩阵 C = S / N similarity_matrix = dot_product / norm_outer # 可选: 将自相似度 (对角线) 设为 1(理论上应为 1,但数值误差可能影响) np.fill_diagonal(similarity_matrix, 1.0) return similarity_matrix # 示例输入 X = np.array([ [1, 2, 3], # 向量 A [4, 5, 6], # 向量 B [0, 0, 0] # 零向量 (用于测试边界) ]) # 计算余弦相似度 cosine_sim_matrix = manual_cosine_similarity(X) print("余弦相似度矩阵:") print(cosine_sim_matrix) ``` **输出解释**: ``` 余弦相似度矩阵: [[1. 0.97463185 0. ] [0.97463185 1. 0. ] [0. 0. 1. ]] ``` - **对角线元素 (1.0)**: 表示每个向量与自身的相似度为 1。 - **非对角线元素 (如 0.974)**: 表示向量 $[1,2,3]$ $[4,5,6]$ 的相似度较高(因为方向接近)。 - **零向量处理**: 第三行是零向量,其相似度被设为 0(避免 `NaN`),符合预期[^1]。 #### 关键点说明 - **效率优化**: 使用 NumPy 的向量化操作(如 `np.dot` `np.outer`)比循环更高效,时间复杂度为 $O(n^2 m)$。 - **边界处理**: 通过 $\epsilon$ 处理零向量问题,确保数值稳定性。实际应用中,可添加更多检查(如过滤零向量)。 - **与库函数对比**: 此手动实现结果应与 scikit-learn 的 `cosine_similarity` 一致,但后者更优化(例如处理稀疏矩阵)[^1]。 - **应用场景**: 余弦相似度广泛用于文本分析(如 TF-IDF 向量)、推荐系统图像处理,因为它对向量长度不敏感,只关注方向[^2]。 此实现完全手动,帮助你深入理解底层数学原理。如需进一步优化(如支持大型矩阵),可考虑使用稀疏矩阵或并行计算。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值