导航菜单

  • 1.vector
  • 2.milvus
  • 3.pymilvus
  • 4.rag
  • 5.rag_measure
  • 7.search
  • ragflow
  • heapq
  • HNSW
  • cosine_similarity
  • math
  • typing
  • etcd
  • minio
  • collections
  • jieba
  • random
  • beautifulsoup4
  • chromadb
  • sentence_transformers
  • numpy
  • lxml
  • openpyxl
  • PyMuPDF
  • python-docx
  • requests
  • python-pptx
  • text_splitter
  • all-MiniLM-L6-v2
  • openai
  • llm
  • BPETokenizer
  • Flask
  • RAGAS
  • BagofWords
  • langchain
  • Pydantic
  • abc
  • faiss
  • MMR
  • scikit-learn
  • 1. 什么是余弦相似度?
    • 1.1 生活中的例子
    • 1.2 余弦相似度的核心思想
    • 1.3 为什么需要余弦相似度?
  • 2. 前置知识
    • 2.1 什么是向量?
    • 2.2 什么是余弦
    • 2.3 什么是余弦定理
    • 2.4 什么是向量的点积(内积)?
      • 2.4.1. 直观的生活例子:马里奥赛车加速带
      • 2.4.2. 几何视角:投影(影子的长度)
      • 2.4.3. 代数视角:计算(程序员怎么算)
    • 2.5 什么是向量的模长(长度)?
  • 3. 余弦相似度原理
    • 3.1 数学公式
    • 3.2 数学证明
      • 3.2.1. 设定
      • 3.2.2. 代入余弦定理
      • 3.2.3. 用坐标展开
      • 3.2.4. 化简
      • 3.2.5. 最终结果
    • 3.3 取值范围和含义
    • 3.4 代码实现
  • 4. 实际应用示例
    • 4.1 文本相似度计算
    • 4.2 推荐系统示例
    • 4.3 图像特征相似度
  • 5. 与欧氏距离的对比
    • 5.1 核心区别
    • 5.2 对比示例
    • 5.3 如何选择?
  • 6.常见问题和注意事项
    • 6.1 零向量处理
    • 6.2 向量维度必须相同
    • 6.3 归一化的影响
  • 7. 总结
    • 7.1 核心要点
    • 7.2 优势
    • 7.3 适用场景

1. 什么是余弦相似度? #

1.1 生活中的例子 #

想象一下,你在网上购物,想找和你兴趣相似的人来参考他们的购买记录:

  • 用户A:喜欢买很多书(100本),偶尔买几件衣服(2件)
  • 用户B:也喜欢买很多书(50本),偶尔买几件衣服(1件)

虽然用户A买的书是用户B的2倍,但他们的购买偏好方向是相同的(都喜欢书,不太喜欢衣服)。余弦相似度就能识别出这种相似性,而欧氏距离可能会认为他们差异很大(因为数量差异大)。

1.2 余弦相似度的核心思想 #

余弦相似度通过计算两个向量夹角的余弦值来衡量相似程度:

  • 夹角为0度:两个向量方向完全相同,相似度为1(最相似)
  • 夹角为90度:两个向量垂直(正交),相似度为0(不相关)
  • 夹角为180度:两个向量方向完全相反,相似度为-1(最不相似)

关键优势:余弦相似度不受向量大小(长度)的影响,只关注方向。这使得它特别适合处理:

  • 文本相似度(不同长度的文档)
  • 推荐系统(不同用户的行为强度不同)
  • 特征比较(关注特征的比例而非绝对值)

1.3 为什么需要余弦相似度? #

问题场景:假设你要比较两篇文章的相似度:

  • 文章A:1000字,提到"Python"10次,"编程"5次
  • 文章B:500字,提到"Python"5次,"编程"2.5次

如果用欧氏距离,文章A和B会显得差异很大(因为字数不同)。但实际上,两篇文章的主题比例是相同的(都是Python:编程 = 2:1)。

余弦相似度的优势:

  • 不受文档长度影响
  • 关注内容的比例和方向
  • 对稀疏数据友好(很多值为0的情况)

2. 前置知识 #

在学习余弦相似度之前,我们需要了解一些基础概念。

2.1 什么是向量? #

向量就是一组有序的数字,比如 [1, 2, 3] 就是一个3维向量。在数学和编程中,向量用来表示具有多个特征的对象。

# 导入 numpy 库用于处理向量
import numpy as np

# 创建一个3维向量,表示某个对象的特征
# 比如:[身高, 体重, 年龄] 或者 [喜欢Python的程度, 喜欢Java的程度, 喜欢C++的程度]
vector = np.array([1.2, 3.4, 5.6])

# 打印向量的维度(形状)
print(f"向量维度: {vector.shape}")
# 打印向量内容
print(f"向量内容: {vector}")
# 打印向量的长度(元素个数)
print(f"向量长度: {len(vector)}")

2.2 什么是余弦 #

余弦(Cosine)是一个三角函数,表示一个角的邻边长度与斜边长度之比。在余弦相似度中,我们利用余弦值来衡量两个向量夹角的大小。

数学公式:

对于一个角 $\theta$,其余弦值为:

$$ \cos\theta = \frac{\text{邻边}}{\text{斜边}} $$

2.3 什么是余弦定理 #

余弦定理(Cosine Law) 是平面几何中的一个重要公式,用于描述三角形三边和夹角之间的关系。它是勾股定理的扩展,适用于任意三角形(不只是直角三角形)。

公式:

对于三角形 $ABC$,设 $a,b,c$ 分别为角 $A,B,C$ 对应的三边长度,则有:

$$ c^2 = a^2 + b^2 - 2ab\cos C $$

或者类似地:

$$ a^2 = b^2 + c^2 - 2bc\cos A $$

形象理解:

  • 当角 $C = 90^\circ$,$\cos C = 0$,就变成了勾股定理 $c^2 = a^2 + b^2$。
  • 当角 $C$ 不为直角时,余弦定理可以用来计算未知的边或角。

2.4 什么是向量的点积(内积)? #

点积

点积(Dot Product),也叫内积,是向量运算中最基础、最核心的概念之一。

点积衡量了两个向量在多大程度上指向同一个方向。 或者说:它计算了一个向量在另一个向量方向上的“有效贡献”或“投影”。

我们可以从三个层面来通俗理解它:

2.4.1. 直观的生活例子:马里奥赛车加速带 #

想象你在玩赛车游戏(比如马里奥赛车),地上有一个加速带(箭头指引加速方向,这是一个向量 $\mathbf{B}$)。你的赛车正朝某个方向行驶(这是向量 $\mathbf{A}$)。

点积 $\mathbf{A} \cdot \mathbf{B}$ 就是你实际获得的加速效果:

  • 情况一(完全同向): 你正对着加速带箭头冲上去。
    • 你的方向和加速带完全一致(夹角 $0^\circ$)。
    • 结果: 获得最大加速!点积是正的最大值。
  • 情况二(侧面路过): 你垂直于加速带箭头开过去。
    • 你完全没有利用到它的推力(夹角 $90^\circ$)。
    • 结果: 没有加速,也没减速。点积等于 0。
  • 情况三(完全逆向): 你逆着箭头开。
    • 你和它对着干(夹角 $180^\circ$)。
    • 结果: 被减速或弹回。点积是负数。

结论: 点积告诉你这两个向量配合得有多好。

2.4.2. 几何视角:投影(影子的长度) #

这是最经典的解释。想象有一束光垂直照下来。

公式:$\mathbf{A} \cdot \mathbf{B} = |\mathbf{A}| |\mathbf{B}| \cos\theta$

我们可以把它拆解成:$|\mathbf{A}| \times (\underbrace{|\mathbf{B}| \cos\theta}_{\text{投影}})$

  • 这就相当于:把向量 $\mathbf{B}$ 投影到向量 $\mathbf{A}$ 上(想象 $\mathbf{B}$ 在 $\mathbf{A}$ 身上投下的影子)。
  • 点积 = $\mathbf{A}$ 的长度 乘以 $\mathbf{B}$ 在 $\mathbf{A}$ 上的影子长度。

如果影子和 $\mathbf{A}$ 同向,乘积为正;如果影子是反向的,乘积为负;如果没有影子(垂直),乘积为 0。

2.4.3. 代数视角:计算(程序员怎么算) #

在计算机代码里我们用坐标直接算。

如果 $\mathbf{A} = (x_1, y_1)$,$\mathbf{B} = (x_2, y_2)$。 那么点积就是: $$\mathbf{A} \cdot \mathbf{B} = x_1 x_2 + y_1 y_2$$

简单来说就是:x 乘 x,y 乘 y,然后加起来。 (如果是 3D,就是 $x_1x_2 + y_1y_2 + z_1z_2$)。

2.5 什么是向量的模长(长度)? #

向量的模长就是向量的"长度",就像在坐标系中计算从原点到点的距离。

数学公式:对于向量 A = [a₁, a₂, ..., aₙ] $$ |\mathbf{A}| = \sqrt{a_1^2 + a_2^2 + \cdots + a_n^2} $$

# 导入 numpy 库用于数学计算
import numpy as np

# 定义一个向量
vector = np.array([3, 4])

# 方法1:使用 numpy 的 linalg.norm 函数计算模长
norm_v1 = np.linalg.norm(vector)
print(f"方法1(使用norm): {norm_v1}")

# 方法2:手动计算模长
# 先计算每个元素的平方
squared = vector ** 2
print(f"各元素平方: {squared}")
# 求和
sum_squared = np.sum(squared)
print(f"平方和: {sum_squared}")
# 开平方根
norm_v2 = np.sqrt(sum_squared)
print(f"方法2(手动计算): {norm_v2}")

# 验证:3² + 4² = 9 + 16 = 25,√25 = 5
print(f"验证: 3² + 4² = {3**2 + 4**2}, √{3**2 + 4**2} = {norm_v2}")

# 可视化:在二维坐标系中,向量 [3, 4] 的长度就是 5
print(f"\n在二维坐标系中,从原点到点 (3, 4) 的距离是: {norm_v1}")

3. 余弦相似度原理 #

余弦相似度

3.1 数学公式 #

对于两个向量 A 和 B,余弦相似度的计算公式为:

$$ \cos\theta = \frac{\mathbf{A} \cdot \mathbf{B}}{|\mathbf{A}|\; |\mathbf{B}|} $$

其中:

  • A · B:两个向量的点积(内积)
  • ||A||:向量 A 的模长(长度)
  • ||B||:向量 B 的模长(长度)
  • θ:两个向量之间的夹角

3.2 数学证明 #

公式推导

3.2.1. 设定 #

在三角形 OAB 中(O 是原点):

  • $ a = |\mathbf{A}| $ (对角 A 的边,即 OA 的长度)
  • $ b = |\mathbf{B}| $ (对角 B 的边,即 OB 的长度)
  • $ c = |\mathbf{B} - \mathbf{A}| $ (对角 C 的边,即 AB 的长度)

角 C 在点 O,就是向量 A 与 B 的夹角 θ。

3.2.2. 代入余弦定理 #

余弦定理(对顶点 O 处的角 θ): $$ c^2 = a^2 + b^2 - 2ab\cos\theta $$ 代入: $$ |\mathbf{B} - \mathbf{A}|^2 = |\mathbf{A}|^2 + |\mathbf{B}|^2 - 2|\mathbf{A}|\,|\mathbf{B}|\cos\theta $$

3.2.3. 用坐标展开 #

左边: $$ |\mathbf{B} - \mathbf{A}|^2 = (B_x - A_x)^2 + (B_y - A_y)^2 $$ $$ = A_x^2 + A_y^2 + B_x^2 + B_y^2 - 2(A_x B_x + A_y B_y) $$ $$ = |\mathbf{A}|^2 + |\mathbf{B}|^2 - 2(\mathbf{A} \cdot \mathbf{B}) $$

右边: $$ |\mathbf{A}|^2 + |\mathbf{B}|^2 - 2|\mathbf{A}|\,|\mathbf{B}|\cos\theta $$

3.2.4. 化简 #

两边都有 $|\mathbf{A}|^2 + |\mathbf{B}|^2$,可以抵消,得到:

$$ -2(\mathbf{A} \cdot \mathbf{B}) = -2 |\mathbf{A}| |\mathbf{B}| \cos\theta $$

消去两边的 $-2$,得到:

$$ \mathbf{A} \cdot \mathbf{B} = |\mathbf{A}|\,|\mathbf{B}|\,\cos\theta $$

两边同时除以 $|\mathbf{A}| |\mathbf{B}|$,得到余弦公式:

$$ \cos\theta = \frac{\mathbf{A} \cdot \mathbf{B}}{|\mathbf{A}|\,|\mathbf{B}|} $$

3.2.5. 最终结果 #

$$ \cos\theta = \frac{A_x B_x + A_y B_y}{\sqrt{A_x^2 + A_y^2} \cdot \sqrt{B_x^2 + B_y^2}} $$

3.3 取值范围和含义 #

余弦相似度的取值范围是 [-1, 1]:

  • 1:两个向量方向完全相同(夹角0度),最相似
  • 接近1:两个向量方向非常相似
  • 0:两个向量垂直(夹角90度),不相关
  • 接近-1:两个向量方向相反
  • -1:两个向量方向完全相反(夹角180度),最不相似

3.4 代码实现 #

# 导入 numpy 库用于数学计算
import numpy as np

# 定义函数,计算两个向量的余弦相似度
def cosine_similarity(a, b):
    # 计算两个向量的点积(内积)
    dot_product = np.dot(a, b)
    # 计算第一个向量的模长(长度)
    norm_a = np.linalg.norm(a)
    # 计算第二个向量的模长(长度)
    norm_b = np.linalg.norm(b)
    # 如果模长为0,返回0(避免除以0的错误)
    if norm_a == 0 or norm_b == 0:
        return 0.0
    # 返回点积除以两个模长的乘积,即余弦相似度
    return dot_product / (norm_a * norm_b)

# 示例1:方向相同的向量(相似度应该接近1)
vector_a = np.array([1, 2, 3])
vector_b = np.array([2, 4, 6])  # vector_a 的2倍,方向相同
similarity_1 = cosine_similarity(vector_a, vector_b)
print(f"示例1 - 方向相同的向量:")
print(f"  向量A: {vector_a}")
print(f"  向量B: {vector_b}")
print(f"  余弦相似度: {similarity_1:.4f} (应该接近1.0)")

# 示例2:方向相反的向量(相似度应该接近-1)
vector_c = np.array([1, 2, 3])
vector_d = np.array([-1, -2, -3])  # 完全相反的方向
similarity_2 = cosine_similarity(vector_c, vector_d)
print(f"\n示例2 - 方向相反的向量:")
print(f"  向量C: {vector_c}")
print(f"  向量D: {vector_d}")
print(f"  余弦相似度: {similarity_2:.4f} (应该接近-1.0)")

# 示例3:垂直的向量(相似度应该接近0)
vector_e = np.array([1, 0])
vector_f = np.array([0, 1])  # 在二维空间中垂直
similarity_3 = cosine_similarity(vector_e, vector_f)
print(f"\n示例3 - 垂直的向量:")
print(f"  向量E: {vector_e}")
print(f"  向量F: {vector_f}")
print(f"  余弦相似度: {similarity_3:.4f} (应该接近0.0)")

4. 实际应用示例 #

4.1 文本相似度计算 #

余弦相似度在文本相似度计算中非常常用,可以比较两段文本的相似程度。

# 导入必要的库
import numpy as np
from collections import Counter

# 定义函数,将文本转换为向量(基于词频)
def text_to_vector(text):
    """
    将文本转换为向量(简单的词频向量)

    参数:
        text: 输入文本(字符串)

    返回:
        词频向量(numpy数组)
    """
    # 将文本转换为小写并按空格分割成单词列表
    words = text.lower().split()
    # 统计每个词出现的次数
    word_counts = Counter(words)
    # 获取所有唯一的词
    unique_words = sorted(word_counts.keys())
    # 创建向量,每个位置对应一个词的词频
    vector = np.array([word_counts[word] for word in unique_words])
    # 返回向量和词汇表(用于后续对齐)
    return vector, unique_words

# 定义函数,对齐两个文本的向量(使它们维度相同)
def align_vectors(vec1, words1, vec2, words2):
    """
    对齐两个文本向量,使它们具有相同的维度

    参数:
        vec1: 第一个文本的向量
        words1: 第一个文本的词汇表
        vec2: 第二个文本的向量
        words2: 第二个文本的词汇表

    返回:
        对齐后的两个向量
    """
    # 获取所有唯一的词(两个文本的并集)
    all_words = sorted(set(words1 + words2))
    # 创建对齐后的向量
    aligned_vec1 = np.array([words1.count(word) if word in words1 else 0 for word in all_words])
    aligned_vec2 = np.array([words2.count(word) if word in words2 else 0 for word in all_words])
    # 返回对齐后的向量
    return aligned_vec1, aligned_vec2

# 定义函数,计算文本相似度
def text_similarity(text1, text2):
    """
    计算两个文本的余弦相似度

    参数:
        text1: 第一个文本
        text2: 第二个文本

    返回:
        余弦相似度值
    """
    # 将文本转换为向量
    vec1, words1 = text_to_vector(text1)
    vec2, words2 = text_to_vector(text2)
    # 对齐向量
    aligned_vec1, aligned_vec2 = align_vectors(vec1, words1, vec2, words2)
    # 计算余弦相似度
    dot_product = np.dot(aligned_vec1, aligned_vec2)
    norm1 = np.linalg.norm(aligned_vec1)
    norm2 = np.linalg.norm(aligned_vec2)
    # 避免除以0
    if norm1 == 0 or norm2 == 0:
        return 0.0
    # 返回相似度
    return dot_product / (norm1 * norm2)

# 主程序:测试文本相似度计算
if __name__ == "__main__":
    # 定义三个文本示例
    text1 = "apple banana apple fruit"
    text2 = "banana fruit orange"
    text3 = "car bike vehicle"

    # 计算文本1和文本2的相似度
    sim_12 = text_similarity(text1, text2)
    print(f"文本1: '{text1}'")
    print(f"文本2: '{text2}'")
    print(f"相似度: {sim_12:.4f}")
    print(f"说明: 文本1和文本2都包含'banana'和'fruit',所以相似度较高")

    # 计算文本1和文本3的相似度
    sim_13 = text_similarity(text1, text3)
    print(f"\n文本1: '{text1}'")
    print(f"文本3: '{text3}'")
    print(f"相似度: {sim_13:.4f}")
    print(f"说明: 文本1和文本3没有共同词汇,所以相似度较低")

4.2 推荐系统示例 #

在推荐系统中,可以使用余弦相似度来找到兴趣相似的用户。

# 导入必要的库
import numpy as np

# 定义简单的推荐系统类
class SimpleRecommender:
    def __init__(self):
        # 存储用户画像(每个用户用一个向量表示其偏好)
        self.user_profiles = {}

    def add_user_rating(self, user_id, item_ratings):
        """
        添加用户的评分数据

        参数:
            user_id: 用户ID
            item_ratings: 用户对各个物品的评分列表(向量)
        """
        # 将评分列表转换为numpy数组并存储
        self.user_profiles[user_id] = np.array(item_ratings, dtype=float)

    def cosine_similarity(self, vec1, vec2):
        """
        计算两个向量的余弦相似度

        参数:
            vec1: 第一个向量
            vec2: 第二个向量

        返回:
            余弦相似度值
        """
        # 计算点积
        dot_product = np.dot(vec1, vec2)
        # 计算模长
        norm1 = np.linalg.norm(vec1)
        norm2 = np.linalg.norm(vec2)
        # 避免除以0
        if norm1 == 0 or norm2 == 0:
            return 0.0
        # 返回相似度
        return dot_product / (norm1 * norm2)

    def recommend_similar_users(self, target_user, top_k=3):
        """
        推荐与目标用户兴趣相似的其他用户

        参数:
            target_user: 目标用户ID
            top_k: 返回前k个最相似的用户

        返回:
            相似用户列表,每个元素是(相似度, 用户ID)的元组
        """
        # 检查目标用户是否存在
        if target_user not in self.user_profiles:
            return []
        # 获取目标用户的向量
        target_vector = self.user_profiles[target_user]
        # 存储所有相似度
        similarities = []
        # 遍历所有用户
        for user_id, user_vector in self.user_profiles.items():
            # 跳过目标用户自己
            if user_id != target_user:
                # 计算相似度
                sim = self.cosine_similarity(target_vector, user_vector)
                # 添加到列表
                similarities.append((sim, user_id))
        # 按相似度降序排序
        similarities.sort(reverse=True)
        # 返回前k个最相似的用户
        return similarities[:top_k]

# 主程序:测试推荐系统
if __name__ == "__main__":
    # 创建推荐系统实例
    recommender = SimpleRecommender()

    # 添加用户评分数据
    # 假设有5个物品(比如:电影、书籍等),每个用户对这5个物品的评分
    recommender.add_user_rating('user1', [5, 3, 0, 1, 4])  # 用户1的评分
    recommender.add_user_rating('user2', [4, 2, 1, 2, 5])  # 用户2的评分
    recommender.add_user_rating('user3', [0, 1, 5, 4, 2])  # 用户3的评分

    # 为用户1推荐相似用户
    similar_users = recommender.recommend_similar_users('user1', top_k=2)

    # 打印结果
    print("为用户1推荐的相似用户:")
    for i, (sim, user_id) in enumerate(similar_users, 1):
        print(f"  第 {i} 名: 用户 {user_id}, 相似度 {sim:.4f}")

4.3 图像特征相似度 #

在图像检索中,可以使用余弦相似度来比较图像的特征向量。

# 导入必要的库
import numpy as np

# 定义函数,在图像数据库中查找相似图像
def find_similar_images(query_features, image_database, top_k=5):
    """
    在图像数据库中查找与查询图像最相似的图像

    参数:
        query_features: 查询图像的特征向量
        image_database: 图像数据库(字典,key是图像ID,value是特征向量)
        top_k: 返回前k个最相似的图像

    返回:
        相似图像列表,每个元素是(相似度, 图像ID)的元组
    """
    # 存储所有相似度
    similarities = []
    # 遍历数据库中的每张图像
    for img_id, features in image_database.items():
        # 计算余弦相似度
        dot_product = np.dot(query_features, features)
        norm_query = np.linalg.norm(query_features)
        norm_features = np.linalg.norm(features)
        # 避免除以0
        if norm_query == 0 or norm_features == 0:
            sim = 0.0
        else:
            sim = dot_product / (norm_query * norm_features)
        # 添加到列表
        similarities.append((sim, img_id))
    # 按相似度降序排序
    similarities.sort(reverse=True)
    # 返回前k个最相似的图像
    return similarities[:top_k]

# 主程序:测试图像相似度查找
if __name__ == "__main__":
    # 模拟图像数据库(实际应用中,这些特征向量来自深度学习模型)
    # 假设每张图像用128维向量表示其特征
    image_database = {
        'img1': np.random.rand(128),
        'img2': np.random.rand(128),
        'img3': np.random.rand(128),
        'img4': np.random.rand(128),
        'img5': np.random.rand(128),
    }

    # 创建查询图像的特征向量(与img1相似)
    query_features = image_database['img1'] * 1.5  # 方向相同,大小不同

    # 查找相似图像
    similar_images = find_similar_images(query_features, image_database, top_k=3)

    # 打印结果
    print("查询图像的特征向量(与img1方向相同)")
    print(f"\n找到最相似的 3 张图像:")
    for i, (sim, img_id) in enumerate(similar_images, 1):
        print(f"  第 {i} 名: {img_id}, 相似度 {sim:.4f}")

5. 与欧氏距离的对比 #

5.1 核心区别 #

欧氏距离关注的是向量之间的"直线距离",受向量大小影响。 余弦相似度关注的是向量之间的"角度",不受向量大小影响。

5.2 对比示例 #

让我们通过具体例子来看两者的区别:

# 导入必要的库
import numpy as np

# 定义函数,计算欧氏距离
def euclidean_distance(a, b):
    """
    计算两个向量的欧氏距离

    参数:
        a: 第一个向量
        b: 第二个向量

    返回:
        欧氏距离值
    """
    # 计算两个向量的差
    diff = a - b
    # 计算差的平方和
    squared_diff = np.sum(diff ** 2)
    # 返回平方根,即欧氏距离
    return np.sqrt(squared_diff)

# 定义函数,计算余弦相似度
def cosine_similarity(a, b):
    """
    计算两个向量的余弦相似度

    参数:
        a: 第一个向量
        b: 第二个向量

    返回:
        余弦相似度值
    """
    # 计算点积
    dot_product = np.dot(a, b)
    # 计算模长
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)
    # 避免除以0
    if norm_a == 0 or norm_b == 0:
        return 0.0
    # 返回相似度
    return dot_product / (norm_a * norm_b)

# 主程序:对比欧氏距离和余弦相似度
if __name__ == "__main__":
    # 示例1:方向相同但大小不同的向量
    a = np.array([1, 1])
    b = np.array([2, 2])  # a的2倍,方向相同
    c = np.array([100, 100])  # a的100倍,方向相同

    print("示例1:方向相同但大小不同的向量")
    print(f"向量A: {a}")
    print(f"向量B: {b} (A的2倍)")
    print(f"向量C: {c} (A的100倍)")
    print(f"\nA和B的余弦相似度: {cosine_similarity(a, b):.4f} (接近1.0,因为方向相同)")
    print(f"A和B的欧氏距离: {euclidean_distance(a, b):.4f}")
    print(f"A和C的余弦相似度: {cosine_similarity(a, c):.4f} (接近1.0,因为方向相同)")
    print(f"A和C的欧氏距离: {euclidean_distance(a, c):.4f} (很大,因为大小差异大)")
    print("\n结论:余弦相似度不受大小影响,欧氏距离受大小影响")

    # 示例2:大小相同但方向不同的向量
    d = np.array([3, 4])  # 模长为5
    e = np.array([4, 3])  # 模长也为5,但方向不同

    print(f"\n示例2:大小相同但方向不同的向量")
    print(f"向量D: {d} (模长: {np.linalg.norm(d):.2f})")
    print(f"向量E: {e} (模长: {np.linalg.norm(e):.2f})")
    print(f"D和E的余弦相似度: {cosine_similarity(d, e):.4f} (小于1.0,因为方向不同)")
    print(f"D和E的欧氏距离: {euclidean_distance(d, e):.4f}")
    print("\n结论:两者都能区分方向不同的向量")

5.3 如何选择? #

使用余弦相似度的情况:

  • 关注向量的方向而非大小
  • 文本相似度(文档长度不同)
  • 推荐系统(用户行为强度不同)
  • 稀疏数据(很多值为0)

使用欧氏距离的情况:

  • 需要考虑向量的大小
  • 物理距离(如坐标点之间的距离)
  • 数值差异很重要的情况

6.常见问题和注意事项 #

6.1 零向量处理 #

零向量(所有元素都是0)的模长为0,会导致除以0的错误。需要特殊处理。

# 导入必要的库
import numpy as np

# 定义函数,安全地计算余弦相似度(处理零向量)
def safe_cosine_similarity(a, b):
    """
    安全地计算余弦相似度,处理零向量等特殊情况

    参数:
        a: 第一个向量
        b: 第二个向量

    返回:
        余弦相似度值,如果出现零向量则返回0.0
    """
    # 计算模长
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)
    # 如果任一向量为零向量,返回0.0
    if norm_a == 0 or norm_b == 0:
        return 0.0
    # 计算点积
    dot_product = np.dot(a, b)
    # 计算并返回余弦相似度
    return dot_product / (norm_a * norm_b)

# 主程序:测试零向量处理
if __name__ == "__main__":
    # 正常向量
    vec1 = np.array([1, 2, 3])
    vec2 = np.array([4, 5, 6])
    print(f"正常向量相似度: {safe_cosine_similarity(vec1, vec2):.4f}")

    # 零向量
    vec3 = np.array([0, 0, 0])
    vec4 = np.array([1, 2, 3])
    print(f"零向量相似度: {safe_cosine_similarity(vec3, vec4):.4f} (应该为0.0)")

6.2 向量维度必须相同 #

计算余弦相似度时,两个向量的维度必须相同,否则无法计算点积。

# 导入必要的库
import numpy as np

# 定义函数,检查并计算余弦相似度
def cosine_similarity_with_check(a, b):
    """
    计算余弦相似度,并检查向量维度

    参数:
        a: 第一个向量
        b: 第二个向量

    返回:
        余弦相似度值

    抛出:
        ValueError: 如果向量维度不匹配
    """
    # 转换为numpy数组
    a = np.array(a)
    b = np.array(b)
    # 检查维度是否相同
    if a.shape != b.shape:
        raise ValueError(f"向量维度不匹配: {a.shape} vs {b.shape}")
    # 计算相似度
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)
    if norm_a == 0 or norm_b == 0:
        return 0.0
    return np.dot(a, b) / (norm_a * norm_b)

# 主程序:测试维度检查
if __name__ == "__main__":
    # 维度相同的向量
    vec1 = np.array([1, 2, 3])
    vec2 = np.array([4, 5, 6])
    print(f"维度相同,相似度: {cosine_similarity_with_check(vec1, vec2):.4f}")

    # 维度不同的向量(会报错)
    vec3 = np.array([1, 2])
    vec4 = np.array([3, 4, 5])
    try:
        result = cosine_similarity_with_check(vec3, vec4)
    except ValueError as e:
        print(f"捕获错误: {e}")

6.3 归一化的影响 #

对向量进行归一化(使模长为1)后,余弦相似度的计算可以简化为点积。

# 导入必要的库
import numpy as np

# 定义函数,归一化向量
def normalize_vector(v):
    """
    将向量归一化为单位向量(模长为1)

    参数:
        v: 输入向量

    返回:
        归一化后的向量
    """
    # 计算模长
    norm = np.linalg.norm(v)
    # 如果模长为0,返回原向量(避免除以0)
    if norm == 0:
        return v
    # 返回归一化后的向量
    return v / norm

# 定义函数,使用归一化向量计算余弦相似度
def cosine_similarity_normalized(a, b):
    """
    使用归一化向量计算余弦相似度(更高效)

    参数:
        a: 第一个向量
        b: 第二个向量

    返回:
        余弦相似度值
    """
    # 归一化向量
    a_norm = normalize_vector(a)
    b_norm = normalize_vector(b)
    # 归一化后,余弦相似度就是点积
    return np.dot(a_norm, b_norm)

# 主程序:对比两种方法
if __name__ == "__main__":
    # 定义两个向量
    vec1 = np.array([1, 2, 3])
    vec2 = np.array([4, 5, 6])

    # 方法1:标准方法
    dot_product = np.dot(vec1, vec2)
    norm1 = np.linalg.norm(vec1)
    norm2 = np.linalg.norm(vec2)
    similarity1 = dot_product / (norm1 * norm2)

    # 方法2:归一化后计算
    similarity2 = cosine_similarity_normalized(vec1, vec2)

    # 打印结果
    print(f"方法1(标准方法): {similarity1:.6f}")
    print(f"方法2(归一化方法): {similarity2:.6f}")
    print(f"两种方法结果相同: {np.isclose(similarity1, similarity2)}")
    print("\n说明:归一化后,余弦相似度 = 点积,计算更简单")

7. 总结 #

7.1 核心要点 #

  1. 余弦相似度关注方向:不受向量大小影响,只关注两个向量的夹角
  2. 取值范围:[-1, 1],1表示最相似,-1表示最不相似
  3. 计算公式:点积除以两个向量模长的乘积
  4. 适用场景:文本相似度、推荐系统、图像检索等

7.2 优势 #

  • 不受向量大小影响:适合比较不同长度的文档或不同强度的用户行为
  • 对稀疏数据友好:适合处理很多值为0的稀疏向量
  • 计算效率高:使用numpy可以高效计算
  • 直观易懂:结果范围明确,易于理解和解释

7.3 适用场景 #

适合使用余弦相似度:

  • 文本相似度计算
  • 推荐系统(用户-物品矩阵)
  • 图像特征比较
  • 任何需要衡量方向相似度的任务

不适合使用余弦相似度:

  • 需要考虑向量大小的任务(如物理距离)
  • 向量包含负值且方向不重要的情况
  • 需要精确数值差异的场景

访问验证

请输入访问令牌

Token不正确,请重新输入