3.1 内积与余弦相似度 本节导读:深入理解相似度计算的基础数学原理,掌握内积和余弦相似度的计算方法及其在向量检索中的应用,为后续的搜索算法奠定坚实基础。 学习目标 掌握内积和余弦相似度的数学原理 理解不同相似度度量的适用场景 能够在实际项目中选择合适的相似度计算方法 学会优化相似度计算的性能 核心概念 向量空间模型基础 向量空间模型是相似度计算的理论基础,它将文本或文档表示为高维向量空间中的点。在这个空间中,语义相近的文档在向量空间中的距离也相近。
本节导读:深入理解相似度计算的基础数学原理,掌握内积和余弦相似度的计算方法及其在向量检索中的应用,为后续的搜索算法奠定坚实基础。
向量空间模型是相似度计算的理论基础,它将文本或文档表示为高维向量空间中的点。在这个空间中,语义相近的文档在向量空间中的距离也相近。
核心原理:
数学定义:
两个向量的内积定义为对应分量的乘积之和:
A · B = Σ(ai × bi) = a1×b1 + a2×b2 + ... + an×bn
性质分析:
import numpy as np def dot_product_similarity(vector1, vector2): """ 计算两个向量的内积相似度 """ return np.dot(vector1, vector2) # 示例 vec1 = np.array([0.8, 0.6, 0.4]) vec2 = np.array([0.6, 0.8, 0.2]) similarity = dot_product_similarity(vec1, vec2) print(f"内积相似度: {similarity:.4f}")
数学定义:
余弦相似度是内积除以向量长度的乘积:
cos(θ) = (A · B) / (||A|| × ||B||)
其中:
性质分析:
def cosine_similarity(vector1, vector2): """ 计算两个向量的余弦相似度 """ dot_product = np.dot(vector1, vector2) norm1 = np.linalg.norm(vector1) norm2 = np.linalg.norm(vector2) return dot_product / (norm1 * norm2) # 示例 vec1 = np.array([0.8, 0.6, 0.4]) vec2 = np.array([0.6, 0.8, 0.2]) cos_sim = cosine_similarity(vec1, vec2) print(f"余弦相似度: {cos_sim:.4f}")
# 安装必要的库 pip install numpy scikit-learn matplotlib seaborn pip install transformers torch pip install faiss-cpu
import numpy as np from sklearn.metrics.pairwise import cosine_similarity, euclidean_distances from sklearn.preprocessing import normalize import matplotlib.pyplot as plt import seaborn as sns import faiss
首先实现基本的相似度计算函数,并理解其数学原理。
def basic_similarity_calculations(): """ 基础相似度计算演示 """ print("=== 基础相似度计算 ===") # 创建示例向量 vectors = np.array([ [1.0, 0.0, 0.0], # 向量A: 完全x轴方向 [0.0, 1.0, 0.0], # 向量B: 完全y轴方向 [1.0, 1.0, 0.0], # 向量C: x和y轴方向 [0.5, 0.5, 0.0], # 向量D: x和y轴方向,但长度不同 [1.0, 0.0, 0.0] # 向量E: 与向量A相同 ]) vector_names = ['A(1,0,0)', 'B(0,1,0)', 'C(1,1,0)', 'D(0.5,0.5,0)', 'E(1,0,0)'] # 计算各种相似度 print("向量表示:") for i, (name, vec) in enumerate(zip(vector_names, vectors)): print(f"{name}: {vec}") print(f"长度: {np.linalg.norm(vec):.4f}") # 计算内积相似度矩阵 print("\n=== 内积相似度矩阵 ===") dot_sim_matrix = np.zeros((len(vectors), len(vectors))) for i in range(len(vectors)): for j in range(len(vectors)): dot_sim_matrix[i][j] = np.dot(vectors[i], vectors[j]) print("内积相似度矩阵:") print(dot_sim_matrix) # 计算余弦相似度矩阵 print("\n=== 余弦相似度矩阵 ===") cos_sim_matrix = cosine_similarity(vectors) print("余弦相似度矩阵:") print(cos_sim_matrix) # 分析结果 print("\n=== 结果分析 ===") print("向量A和E的关系:") print(f"内积: {dot_sim_matrix[0][4]:.4f}") print(f"余弦相似度: {cos_sim_matrix[0][4]:.4f}") print("结论: 相同向量的内积和余弦相似度都是最大值") print("\n向量A和B的关系:") print(f"内积: {dot_sim_matrix[0][1]:.4f}") print(f"余弦相似度: {cos_sim_matrix[0][1]:.4f}") print("结论: 正交向量的内积为0,余弦相似度也为0") # 执行演示 basic_similarity_calculations()
将文本转换为向量,然后计算文本间的相似度。
from transformers import BertTokenizer, BertModel import torch class TextSimilarityCalculator: def __init__(self, model_name='bert-base-chinese'): """ 初始化文本相似度计算器 """ self.tokenizer = BertTokenizer.from_pretrained(model_name) self.model = BertModel.from_pretrained(model_name) def get_text_vector(self, text): """ 获取文本的BERT向量表示 """ inputs = self.tokenizer(text, return_tensors='pt', truncation=True, max_length=512) with torch.no_grad(): outputs = self.model(**inputs) # 使用[CLS]标记的向量作为文本表示 return outputs.last_hidden_state[:, 0, :].squeeze().numpy() def calculate_text_similarity(self, text1, text2, method='cosine'): """ 计算两个文本的相似度 """ vec1 = self.get_text_vector(text1) vec2 = self.get_text_vector(text2) if method == 'cosine': return cosine_similarity([vec1], [vec2])[0][0] elif method == 'dot': return np.dot(vec1, vec2) else: raise ValueError("不支持的相似度计算方法") def batch_similarity_demo(self): """ 批量文本相似度计算演示 """ print("=== 批量文本相似度计算 ===") # 示例文本 texts = [ "机器学习是人工智能的重要分支", "深度学习属于机器学习的子集", "自然语言处理是AI的重要应用", "数据挖掘从大量数据中发现模式", "我喜欢吃苹果", "苹果公司发布了新产品" ] # 获取所有文本向量 vectors = [] for text in texts: vec = self.get_text_vector(text) vectors.append(vec) vectors = np.array(vectors) # 计算相似度矩阵 cosine_sim_matrix = cosine_similarity(vectors) print("文本和对应向量:") for i, (text, vec) in enumerate(zip(texts, vectors)): print(f"{i}: '{text}' - 长度: {np.linalg.norm(vec):.4f}") print("\n相似度矩阵:") print(" ", " ".join([f"{i:3d}" for i in range(len(texts))])) for i in range(len(texts)): print(f"{i:3d} ", " ".join([f"{cosine_sim_matrix[i][j]:6.3f}" for j in range(len(texts))])) # 分析相似文本 print("\n=== 相似文本分析 ===") max_sim = 0 most_similar_pair = (0, 1) for i in range(len(texts)): for j in range(i+1, len(texts)): sim = cosine_sim_matrix[i][j] if sim > max_sim: max_sim = sim most_similar_pair = (i, j) print(f"最相似文本对:") print(f"文本1: '{texts[most_similar_pair[0]]}'") print(f"文本2: '{texts[most_similar_pair[1]]}'") print(f"相似度: {max_sim:.4f}") # 分析多义词 print("\n=== 多义词分析 ===") apple_texts = [texts[4], texts[5]] apple_vecs = [vectors[4], vectors[5]] apple_sim = cosine_similarity([apple_vecs[0]], [apple_vecs[1]])[0][0] print(f"多义词'苹果'在不同上下文中的相似度: {apple_sim:.4f}") print(f"语义相关性: {'高' if apple_sim > 0.7 else '中' if apple_sim > 0.5 else '低'}") # 执行文本相似度计算演示 try: text_sim_calculator = TextSimilarityCalculator() text_sim_calculator.batch_similarity_demo() except Exception as e: print(f"文本相似度计算出错(需要网络下载模型): {e}") print("跳过BERT模型演示,使用简单文本演示")
使用FAISS库进行大规模向量的相似度计算。
class EfficientSimilaritySearch: def __init__(self, dimension=768): """ 初始化高效相似度搜索 """ self.dimension = dimension self.index = None self.vectors = None def create_index(self, vectors, index_type='HNSW'): """ 创建相似度索引 """ self.vectors = vectors.astype('float32') if index_type == 'HNSW': # HNSW索引 self.index = faiss.IndexHNSWFlat(self.dimension, 32) # M=32 self.index.hnsw.ef = 200 # ef搜索参数 elif index_type == 'IVF': # IVF索引 nlist = min(100, len(vectors) // 10) # 聚类数量 quantizer = faiss.IndexFlatL2(self.dimension) self.index = faiss.IndexIVFFlat(quantizer, self.dimension, nlist) self.index.train(self.vectors) elif index_type == 'PQ': # PQ索引 nlist = min(100, len(vectors) // 10) m = 8 # 子空间数量 quantizer = faiss.IndexFlatL2(self.dimension) self.index = faiss.IndexIVFPQ(quantizer, self.dimension, nlist, m, 8) self.index.train(self.vectors) else: raise ValueError(f"不支持的索引类型: {index_type}") # 添加向量到索引 self.index.add(self.vectors) return self.index def search(self, query_vector, top_k=10): """ 搜索最相似的向量 """ if self.index is None: raise ValueError("索引尚未创建") query_vector = query_vector.astype('float32').reshape(1, -1) distances, indices = self.index.search(query_vector, top_k) return distances[0], indices[0] def benchmark_search_methods(self, vectors, query_vector): """ 对比不同搜索方法的性能 """ print("=== 搜索方法性能对比 ===") methods = ['brute_force', 'hnsw', 'ivf', 'pq'] results = {} for method in methods: if method == 'brute_force': # 暴力搜索 distances = [] for vec in vectors: dist = np.linalg.norm(query_vector - vec) distances.append(dist) indices = np.argsort(distances)[:10] else: # 使用FAISS索引 self.create_index(vectors, method) dists, idxs = self.search(query_vector) indices = idxs results[method] = indices print(f"{method}: 找到的前10个索引: {indices}") return results # 执行高效相似度搜索演示 def efficient_similarity_demo(): print("=== 大规模向量相似度搜索 ===") # 创建大规模测试数据 n_vectors = 10000 dimension = 128 vectors = np.random.random((n_vectors, dimension)).astype('float32') print(f"创建 {n_vectors} 个 {dimension} 维向量") # 创建查询向量 query_vector = np.random.random((1, dimension)).astype('float32') # 创建搜索器 search_engine = EfficientSimilaritySearch(dimension) # 性能测试 import time print("\n=== 性能测试 ===") for method in ['brute_force', 'hnsw']: print(f"测试 {method} 方法...") start_time = time.time() if method == 'brute_force': # 暴力搜索 distances = [] for vec in vectors: dist = np.linalg.norm(query_vector - vec) distances.append(dist) top_indices = np.argsort(distances)[:10] else: # FAISS搜索 search_engine.create_index(vectors, method) dists, idxs = search_engine.search(query_vector) top_indices = idxs end_time = time.time() print(f" 搜索结果前3个: {top_indices[:3]}") print(f" 耗时: {(end_time - start_time)*1000:.2f}ms") # 执行演示 efficient_similarity_demo()
A:内积相似度和余弦相似度的主要区别在于:
计算公式:
值域范围:
对长度的敏感性:
适用场景:
A:选择相似度计算方法需要考虑以下因素:
数据特征:
计算效率:
应用需求:
A:优化策略包括:
使用近似算法:
向量预处理:
并行计算:
索引优化:
标准流程:
# 1. 数据清洗和预处理 def preprocess_texts(texts): cleaned_texts = [] for text in texts: text = text.lower().strip() text = re.sub(r'[^\w\s]', '', text) cleaned_texts.append(text) return cleaned_texts # 2. 向量化 vectorizer = TfidfVectorizer(max_features=1000) vectors = vectorizer.fit_transform(cleaned_texts) # 3. 标准化 from sklearn.preprocessing import normalize normalized_vectors = normalize(vectors, axis=1, norm='l2')
问题:不同维度的向量不能直接比较相似度
# 错误示例 vec1 = np.array([1, 2, 3]) vec2 = np.array([1, 2, 3, 4]) similarity = cosine_similarity([vec1], [vec2]) # 错误! # 正确做法 def ensure_same_dimension(vectors_list): max_dim = max(vec.shape[0] for vec in vectors_list) padded_vectors = [] for vec in vectors_list: if len(vec) < max_dim: padded = np.pad(vec, (0, max_dim - len(vec))) padded_vectors.append(padded) else: padded_vectors.append(vec) return padded_vectors
通过本节的学习,我们深入理解了相似度计算的核心数学原理,掌握了内积和余弦相似度的计算方法及其应用场景。主要收获包括:
相似度计算是向量检索的基石,正确选择和优化相似度计算方法对于构建高效的搜索系统至关重要。下一节我们将继续学习欧氏距离与曼哈顿距离等其他距离度量方法。
关键词:AI搜索技术内幕, 内积相似度, 余弦相似度, 向量检索, 相似度计算, 文本相似性, 机器学习, 实战
难度:进阶
预计阅读:45 分钟