本节导读:本节将深入探讨GraphRAG系统中的核心检索技术——基于图的语义检索,从理论基础到实践应用,帮助你掌握如何利用知识图谱的结构化信息实现精准的语义检索。我们将系统讲解图遍历算法、PageRank 排序、图神经网络嵌入和混合检索策略,并通过完整的代码示例展示如何在生产环境中落地这些技术。
基于图的语义检索是GraphRAG系统的核心技术之一,通过利用知识图谱中的实体关系结构和语义信息,实现比传统文本检索更精准、更智能的语义理解和匹配。与传统的基于 BM25 或向量的文本检索不同,图语义检索不仅关注查询词与文档的语义相似度,还利用实体之间的结构关系进行推理和扩展,能够回答需要多跳推理的复杂问题。
基于知识图谱的结构化信息,通过图遍历、路径分析、关系推理等技术,实现语义层面的信息检索和匹配。图语义检索的核心优势在于能够发现隐式关系——即使两个实体在文本中没有直接共现,只要它们在知识图谱中存在连通路径,检索系统就能发现它们之间的关联。
利用知识图谱中的路径信息进行语义推理,通过分析实体间的连接关系和路径特征,发现深层的语义关联。例如,查询"马斯克的工作单位"时,系统通过"马斯克→CEO→特斯拉→总部→德克萨斯州"这条路径,可以回答马斯克所在的城市,而无需在原始文本中显式出现这一信息。
结合图结构检索和传统文本检索,发挥各自优势,提供更全面的检索能力和更好的用户体验。图检索擅长处理结构化查询和关系推理,而向量检索擅长处理自然语言的模糊查询,两者的融合是 GraphRAG 检索质量的关键保障。
# 安装核心依赖 pip install networkx numpy scikit-learn gensim pip install torch-geometric # 图神经网络库 pip install sentence-transformers # 语义向量模型
图遍历是图语义检索的基础操作,用于从查询实体出发,沿着知识图谱的边探索与之相关的实体和信息。
BFS 从起始节点出发,逐层向外扩展,适合发现与查询实体直接相关的邻域信息。在知识图谱检索中,BFS 常用于获取查询实体的"一跳"和"二跳"邻居。
import networkx as nx from typing import List, Dict, Any class GraphRetriever: """基于 NetworkX 的图语义检索器""" def __init__(self, kg_path: str = None): self.graph = nx.DiGraph() if kg_path: self.load_graph(kg_path) def load_graph(self, kg_path: str): """从三元组文件加载知识图谱""" with open(kg_path, 'r', encoding='utf-8') as f: for line in f: parts = line.strip().split('\t') if len(parts) >= 3: head, relation, tail = parts[0], parts[1], parts[2] self.graph.add_edge(head, tail, relation=relation) # 可选:添加反向边以支持双向遍历 self.graph.add_edge(tail, head, relation=f"rev_{relation}") def bfs_search(self, start_entity: str, max_hops: int = 2) -> List[Dict]: """广度优先搜索:获取指定跳数范围内的子图""" if start_entity not in self.graph: return [] visited = {start_entity} current_level = [start_entity] results = [] for hop in range(1, max_hops + 1): next_level = [] for node in current_level: for neighbor in self.graph.successors(node): if neighbor not in visited: visited.add(neighbor) next_level.append(neighbor) edge_data = self.graph[node][neighbor] results.append({ "entity": neighbor, "hop": hop, "relation": edge_data.get("relation", "unknown"), "from_entity": node }) current_level = next_level if not current_level: break return results def find_paths(self, start: str, end: str, max_length: int = 4) -> List[List]: """查找两实体之间的所有路径(限制长度)""" try: paths = list(nx.all_simple_paths(self.graph, start, end, cutoff=max_length)) # 为路径添加关系标注 annotated_paths = [] for path in paths: annotated = [] for i in range(len(path) - 1): rel = self.graph[path[i]][path[i+1]].get("relation", "?") annotated.append(f"{path[i]} -[{rel}]->") annotated.append(path[-1]) annotated_paths.append(annotated) return annotated_paths except nx.NetworkXNoPath: return []
DFS 适合探索知识图谱中的深层路径和复杂关联。在检索场景中,DFS 可用于发现两个实体之间是否存在长距离但有意义的关系路径。
def dfs_find_connections(self, start: str, target_relation: str, max_depth: int = 3): """DFS 查找通过特定关系连接的实体""" results = [] def _dfs(current, depth, path): if depth > max_depth: return for neighbor in self.graph.successors(current): edge = self.graph[current][neighbor] if edge.get("relation") == target_relation: results.append({ "entity": neighbor, "path": path + [current], "depth": depth }) else: _dfs(neighbor, depth + 1, path + [current]) _dfs(start, 1, []) return results
PageRank 最初由 Google 用于网页排名,其核心思想是:一个节点的重要性不仅取决于指向它的链接数量,还取决于指向它的节点本身的重要性。在知识图谱中,PageRank 可以用来衡量实体的重要程度,从而在检索结果排序时赋予更重要的实体更高的权重。
个性化 PageRank(Personalized PageRank, PPR)是 PageRank 的扩展,允许指定一个或多个"偏好"节点,使排序结果偏向与这些节点相关的实体。这在知识图谱检索中非常有用:给定查询实体,计算 PPR 可以得到与该实体最相关的其他实体排名。
import numpy as np from collections import defaultdict class PageRankCalculator: """PageRank 和个性化 PageRank 计算器""" def __init__(self, graph: nx.DiGraph): self.graph = graph self.nodes = list(graph.nodes()) self.n = len(self.nodes) self.node_index = {node: i for i, node in enumerate(self.nodes)} def pagerank(self, damping: float = 0.85, max_iter: int = 100, tol: float = 1e-6) -> Dict[str, float]: """标准 PageRank 计算""" # 初始化:每个节点的 PageRank 值为 1/N pr = np.ones(self.n) / self.n # 构建转移矩阵 transition = np.zeros((self.n, self.n)) out_degrees = defaultdict(list) for u, v in self.graph.edges(): out_degrees[u].append(v) for u in self.nodes: neighbors = out_degrees[u] if neighbors: for v in neighbors: transition[self.node_index[v]][self.node_index[u]] = 1.0 / len(neighbors) else: # 悬挂节点:均匀分布到所有节点 for j in range(self.n): transition[j][self.node_index[u]] = 1.0 / self.n # Power iteration for _ in range(max_iter): new_pr = damping * transition @ pr + (1 - damping) * np.ones(self.n) / self.n if np.linalg.norm(new_pr - pr, ord=1) < tol: break pr = new_pr return {node: pr[self.node_index[node]] for node in self.nodes} def personalized_pagerank(self, personalization: Dict[str, float], damping: float = 0.85, max_iter: int = 100) -> Dict[str, float]: """ 个性化 PageRank:以查询相关实体为偏好节点 Args: personalization: {实体名: 偏好权重},权重之和应为1 """ # 将偏好向量映射到索引空间 p = np.zeros(self.n) total = sum(personalization.values()) for node, weight in personalization.items(): if node in self.node_index: p[self.node_index[node]] = weight / total pr = p.copy() # 构建转移矩阵(同标准 PageRank) transition = np.zeros((self.n, self.n)) out_degrees = defaultdict(list) for u, v in self.graph.edges(): out_degrees[u].append(v) for u in self.nodes: neighbors = out_degrees[u] if neighbors: for v in neighbors: transition[self.node_index[v]][self.node_index[u]] = 1.0 / len(neighbors) else: for j in range(self.n): transition[j][self.node_index[u]] = 1.0 / self.n for _ in range(max_iter): new_pr = damping * transition @ pr + (1 - damping) * p if np.linalg.norm(new_pr - pr, ord=1) < 1e-6: break pr = new_pr return {node: pr[self.node_index[node]] for node in self.nodes} def get_top_entities(self, pr_scores: Dict[str, float], top_k: int = 10) -> List: """获取 PageRank 排名最高的实体""" sorted_entities = sorted(pr_scores.items(), key=lambda x: x[1], reverse=True) return sorted_entities[:top_k]
SimRank 基于一个直觉:如果两个节点的邻居"相似",那么这两个节点也"相似"。SimRank 通过迭代计算节点间的相似度,适合度量知识图谱中实体之间的语义相近程度。
def simrank(self, max_iter: int = 10, decay: float = 0.8) -> np.ndarray: """SimRank 相似度矩阵计算""" # 初始化相似度矩阵:对角线为1,其余为0 S = np.eye(self.n) for _ in range(max_iter): new_S = np.zeros((self.n, self.n)) for i in range(self.n): for j in range(i + 1, self.n): node_i, node_j = self.nodes[i], self.nodes[j] in_neighbors_i = list(self.graph.predecessors(node_i)) in_neighbors_j = list(self.graph.predecessors(node_j)) if not in_neighbors_i or not in_neighbors_j: similarity = 0.0 else: total = 0.0 for ni in in_neighbors_i: for nj in in_neighbors_j: total += S[self.node_index[ni]][self.node_index[nj]] similarity = decay / (len(in_neighbors_i) * len(in_neighbors_j)) * total new_S[i][j] = similarity new_S[j][i] = similarity new_S += np.eye(self.n) # 自身相似度为1 S = new_S return S
图神经网络(Graph Neural Network, GNN)通过消息传递机制学习节点的低维向量表示,使得语义相近的实体在向量空间中也更接近。GNN 嵌入是当前图语义检索的前沿方向。
图 3-1 图神经网络嵌入与检索流程
import torch import torch.nn as nn import torch.nn.functional as F from torch_geometric.nn import SAGEConv, global_mean_pool class GraphSAGEncoder(nn.Module): """ GraphSAGE 编码器:通过采样和聚合邻居信息学习节点嵌入 GraphSAGE(Sample and Aggregate)的核心思想: 不是固定全图卷积,而是对每个节点的邻居进行采样, 然后通过聚合函数(mean/LSTM/pooling)生成节点表示。 """ def __init__(self, in_channels: int, hidden_channels: int, out_channels: int, num_layers: int = 2, dropout: float = 0.3): super().__init__() self.num_layers = num_layers self.dropout = dropout self.convs = nn.ModuleList() self.convs.append(SAGEConv(in_channels, hidden_channels)) for _ in range(num_layers - 2): self.convs.append(SAGEConv(hidden_channels, hidden_channels)) if num_layers > 1: self.convs.append(SAGEConv(hidden_channels, out_channels)) self.norms = nn.ModuleList([ nn.LayerNorm(hidden_channels if i < num_layers - 1 else out_channels) for i in range(num_layers) ]) def forward(self, x, edge_index): """前向传播""" for i, (conv, norm) in enumerate(zip(self.convs, self.norms)): x = conv(x, edge_index) x = norm(x) x = F.relu(x) x = F.dropout(x, p=self.dropout, training=self.training) return x class KnowledgeGraphEmbedding: """知识图谱嵌入管理器""" def __init__(self, node_dim: int = 768, hidden_dim: int = 256, embed_dim: int = 128): self.encoder = GraphSAGEncoder(node_dim, hidden_dim, embed_dim) self.entity_embeddings = None # 训练完成后存储 def train(self, graph, entity_texts, epochs: int = 100, lr: float = 0.001): """ 训练 GNN 编码器 Args: graph: NetworkX 图对象 entity_texts: {实体: 文本描述},用于初始化节点特征 """ from sentence_transformers import SentenceTransformer encoder = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') # 初始化节点特征:使用文本描述的向量表示 node_features = [] for node in graph.nodes(): text = entity_texts.get(node, node) vec = encoder.encode(text) node_features.append(vec) x = torch.tensor(node_features, dtype=torch.float) # 构建边索引 edge_index = [] for u, v in graph.edges(): edge_index.append([list(graph.nodes()).index(u), list(graph.nodes()).index(v)]) edge_index = torch.tensor(edge_index, dtype=torch.long).t().contiguous() # 使用无监督对比学习训练 optimizer = torch.optim.Adam(self.encoder.parameters(), lr=lr) for epoch in range(epochs): self.encoder.train() optimizer.zero_grad() embeddings = self.encoder(x, edge_index) # 对比学习损失:正例对(有边连接的节点)距离近,负例对距离远 loss = self._contrastive_loss(embeddings, edge_index) loss.backward() optimizer.step() if (epoch + 1) % 20 == 0: print(f"Epoch {epoch+1}/{epochs}, Loss: {loss.item():.4f}") # 存储最终的实体嵌入 self.encoder.eval() with torch.no_grad(): self.entity_embeddings = self.encoder(x, edge_index).numpy() def search(self, query_entity: str, entity_list: List[str], top_k: int = 5) -> List: """基于向量相似度检索最相关的实体""" from sklearn.metrics.pairwise import cosine_similarity query_idx = entity_list.index(query_entity) query_vec = self.entity_embeddings[query_idx:query_idx+1] similarities = cosine_similarity(query_vec, self.entity_embeddings)[0] top_indices = similarities.argsort()[::-1][:top_k + 1] results = [] for idx in top_indices: if entity_list[idx] != query_entity: results.append({ "entity": entity_list[idx], "similarity": float(similarities[idx]) }) if len(results) >= top_k: break return results
将图结构检索与向量语义检索结合,是 GraphRAG 检索质量提升的核心手段。混合检索的关键在于如何有效地融合两路结果。
from sklearn.metrics.pairwise import cosine_similarity class HybridRetriever: """混合检索器:图结构检索 + 向量语义检索""" def __init__(self, graph_retriever: GraphRetriever, entity_embeddings: dict, text_vectors: np.ndarray, doc_list: List[str]): self.graph_retriever = graph_retriever self.entity_embeddings = entity_embeddings # {实体: 向量} self.text_vectors = text_vectors # 文档向量矩阵 self.doc_list = doc_list def retrieve(self, query: str, top_k: int = 10, graph_weight: float = 0.6, vector_weight: float = 0.4) -> List[Dict]: """ 混合检索:融合图结构和向量语义两路结果 Args: query: 用户查询文本 top_k: 返回结果数量 graph_weight: 图检索结果权重 vector_weight: 向量检索结果权重 """ # 第一路:图结构检索 graph_results = self._graph_search(query) # 第二路:向量语义检索 vector_results = self._vector_search(query) # 融合排序 merged = self._reciprocal_rank_fusion( graph_results, vector_results, graph_weight, vector_weight ) return merged[:top_k] def _graph_search(self, query: str) -> List[Dict]: """基于实体的图结构检索""" # 1. 识别查询中的实体 entities = self._extract_query_entities(query) if not entities: return [] # 2. 对每个实体执行图遍历 all_results = [] for entity in entities[:3]: # 最多3个实体 neighbors = self.graph_retriever.bfs_search(entity, max_hops=2) for item in neighbors: all_results.append({ "entity": item["entity"], "score": 1.0 / item["hop"], # 跳数越少分数越高 "source": "graph" }) return all_results def _vector_search(self, query: str) -> List[Dict]: """基于向量相似度的语义检索""" from sentence_transformers import SentenceTransformer encoder = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2') query_vec = encoder.encode([query]) similarities = cosine_similarity(query_vec, self.text_vectors)[0] top_indices = similarities.argsort()[::-1][:20] results = [] for idx in top_indices: results.append({ "doc": self.doc_list[idx], "score": float(similarities[idx]), "source": "vector" }) return results def _reciprocal_rank_fusion(self, results_a, results_b, weight_a, weight_b, k=60): """倒数排名融合(RRF):两路结果的融合排序算法""" scores = {} # 计算图检索的 RRF 分数 for rank, item in enumerate(results_a): key = item.get("entity") or item.get("doc", str(item)) scores[key] = scores.get(key, 0) + weight_a / (k + rank + 1) # 计算向量检索的 RRF 分数 for rank, item in enumerate(results_b): key = item.get("entity") or item.get("doc", str(item)) scores[key] = scores.get(key, 0) + weight_b / (k + rank + 1) # 按融合分数排序 merged = sorted(scores.items(), key=lambda x: x[1], reverse=True) return [{"content": item[0], "score": item[1]} for item in merged]
A:平衡策略包括:
A:效率优化方法:
A:准确性提升策略:
A:实时检索策略:
A:图神经网络的主要优势在于:
但需要注意,GNN 的训练成本远高于传统算法,且需要一定的训练数据。对于中小规模的图谱,传统的 PageRank + 向量检索方案可能已经足够。
本节详细介绍了GraphRAG系统中基于图的语义检索技术,涵盖了从基础的图遍历算法(BFS/DFS)到 PageRank 排序、SimRank 相似度计算、图神经网络嵌入等高级技术,并展示了如何通过倒数排名融合将图结构检索与向量语义检索有机结合。通过多种检索策略的融合,GraphRAG 系统能够同时处理结构化查询和自然语言查询,实现精准、高效的语义信息检索能力。核心要点是:图检索负责"理解关系",向量检索负责"理解语义",混合检索则将两者优势互补。
关键词:图语义检索, PageRank, 图神经网络, GNN, 混合检索, GraphSAGE, 相似度计算
难度:进阶
预计阅读:45分钟