AI知识图谱与图神经网络完全教程

教程简介

本教程系统讲解AI知识图谱与图神经网络的核心概念、技术方法和实战应用。涵盖知识图谱构建流程、图数据库选型(Neo4j/Nebula Graph)、GCN/GAT/GraphSAGE等图神经网络模型、Node2Vec/TransE图嵌入技术、GraphRAG与LLM结合、以及金融/医疗/电商领域的知识图谱应用案例。通过实战案例带你从零构建领域知识图谱并接入大语言模型。

AI知识图谱与图神经网络完全教程

一、概述

在人工智能的发展历程中,如何让机器真正"理解"知识一直是核心挑战之一。传统的深度学习模型擅长处理欧几里得空间中的数据(如图像、文本序列),但对于复杂的关系型数据——实体之间千丝万缕的联系——往往力不从心。**知识图谱(Knowledge Graph)图神经网络(Graph Neural Network, GNN)**的出现,为这一问题提供了强有力的解决思路。

知识图谱是一种以图结构组织和表示知识的语义网络,它将现实世界中的实体(如人物、地点、事件)作为节点,将实体间的关系作为边,构建出一个庞大的语义网络。自2012年Google提出"Knowledge Graph"概念以来,知识图谱已经在搜索引擎、推荐系统、智能问答、金融风控等领域得到了广泛应用。

图神经网络则是近年来深度学习在图结构数据上的重要突破。它通过在图上定义卷积、注意力等操作,能够自动学习节点、边和子图的低维表示(即图嵌入),从而将图结构信息融入机器学习模型中。GNN的出现极大地推动了知识图谱的表示学习和推理能力。

本教程将从知识图谱的基础概念出发,系统讲解知识图谱的构建流程、图数据库选型、图神经网络的核心模型、图嵌入技术、知识图谱与大语言模型(LLM)的结合,以及在金融、医疗、电商等领域的实战应用。通过本教程,你将掌握从零构建一个完整的知识图谱系统并接入LLM的全流程。


二、知识图谱核心概念

2.1 实体(Entity)

实体是知识图谱中最基本的元素,代表现实世界中的具体或抽象事物。例如:

  • 人物:张三、李四、爱因斯坦
  • 地点:北京、上海、珠穆朗玛峰
  • 组织:清华大学、阿里巴巴、OpenAI
  • 概念:机器学习、深度学习、自然语言处理

每个实体通常具有唯一的标识符(ID)和一组属性(Attributes),如名称、类型、描述等。

2.2 关系(Relation)

关系描述了两个实体之间的语义联系。关系是有方向的,通常用一个动词或动词短语来表示。例如:

  • 张三 就职于 阿里巴巴
  • 北京 位于 中国
  • 爱因斯坦 提出 相对论

2.3 三元组(Triple)

知识图谱中的知识通常以三元组的形式存储,即 (头实体, 关系, 尾实体) 或记作 (h, r, t)。例如:

(张三, 就职于, 阿里巴巴)
(北京, 位于, 中国)
(爱因斯坦, 提出, 相对论)

三元组是知识图谱的最小知识单元,大量三元组的集合构成了完整的知识图谱。

2.4 本体(Ontology)

本体定义了知识图谱中的概念层次约束规则,包括:

  • 类层次:实体类型的继承关系(如"科学家"是"人"的子类)
  • 属性定义:实体和关系可以拥有的属性
  • 约束条件:关系的定义域(Domain)和值域(Range)

本体为知识图谱提供了语义框架,确保数据的一致性和可推理能力。

2.5 知识图谱的表示形式

知识图谱可以用以下几种方式表示:

邻接矩阵表示:用矩阵 \(A \in \mathbb{R}^{N \times N}\) 表示图结构,其中 \(A_{ij}\) 表示节点 \(i\) 和节点 \(j\) 之间是否存在边。

邻接表表示:用字典或列表存储每个节点的邻居节点,适合稀疏图。

RDF三元组表示:以 (主语, 谓语, 宾语) 的形式存储,遵循W3C标准。

# Python中表示知识图谱三元组
triples = [
    ("张三", "就职于", "阿里巴巴"),
    ("张三", "毕业于", "清华大学"),
    ("阿里巴巴", "总部位于", "杭州"),
    ("清华大学", "位于", "北京"),
]

# 用NetworkX构建图
import networkx as nx
import matplotlib.pyplot as plt

G = nx.DiGraph()
for h, r, t in triples:
    G.add_edge(h, t, relation=r)

# 可视化
pos = nx.spring_layout(G, seed=42)
plt.figure(figsize=(10, 8))
nx.draw(G, pos, with_labels=True, node_color='lightblue',
        node_size=2000, font_size=12, font_family='SimHei',
        arrows=True, edge_color='gray')
edge_labels = nx.get_edge_attributes(G, 'relation')
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels,
                             font_family='SimHei', font_size=10)
plt.title("简单知识图谱示例")
plt.savefig("kg_example.png", dpi=150, bbox_inches='tight')
plt.show()

三、知识图谱构建流程

知识图谱的构建是一个系统性工程,通常包括以下几个关键步骤:

3.1 信息抽取(Information Extraction)

信息抽取是从非结构化文本中自动提取实体、关系和属性的过程。

3.1.1 命名实体识别(NER)

命名实体识别是识别文本中具有特定意义的实体,如人名、地名、组织名等。

# 使用spaCy进行命名实体识别
import spacy

nlp = spacy.load("zh_core_web_sm")
text = "张三毕业于清华大学,目前在阿里巴巴担任高级工程师。"
doc = nlp(text)

for ent in doc.ents:
    print(f"实体: {ent.text}, 类型: {ent.label_}, 位置: {ent.start_char}-{ent.end_char}")

# 输出:
# 实体: 张三, 类型: PERSON, 位置: 0-2
# 实体: 清华大学, 类型: ORG, 位置: 5-9
# 实体: 阿里巴巴, 类型: ORG, 位置: 15-19

3.1.2 关系抽取(Relation Extraction)

关系抽取是识别实体之间的语义关系。常用方法包括:

  • 基于规则的方法:使用预定义的模式匹配
  • 监督学习方法:使用标注数据训练分类器
  • 远程监督方法:利用已有知识图谱自动标注训练数据
  • 大模型方法:使用LLM进行零样本或少样本关系抽取
# 使用LLM进行关系抽取
import openai

def extract_relations(text):
    prompt = f"""从以下文本中抽取实体关系三元组,输出JSON格式:
    
文本:{text}

输出格式:
[{{"subject": "实体1", "predicate": "关系", "object": "实体2"}}]
"""
    response = openai.ChatCompletion.create(
        model="gpt-4",
        messages=[{"role": "user", "content": prompt}],
        temperature=0
    )
    return response.choices[0].message.content

text = "任正非于1987年创立了华为技术有限公司,总部位于深圳。"
relations = extract_relations(text)
print(relations)
# 输出:
# [
#   {"subject": "任正非", "predicate": "创立", "object": "华为技术有限公司"},
#   {"subject": "华为技术有限公司", "predicate": "总部位于", "object": "深圳"},
#   {"subject": "任正非", "predicate": "创立时间", "object": "1987年"}
# ]

3.2 实体对齐(Entity Alignment)

实体对齐是将不同数据源中指向同一真实世界实体的记录进行匹配。例如,"北京"和"Beijing"应该对齐为同一实体。

# 基于字符串相似度的实体对齐
from difflib import SequenceMatcher

def entity_similarity(entity1, entity2):
    """计算两个实体名称的相似度"""
    return SequenceMatcher(None, entity1, entity2).ratio()

# 使用预训练嵌入的实体对齐
from sentence_transformers import SentenceTransformer
import numpy as np

model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')

def embedding_alignment(entities_source, entities_target, threshold=0.8):
    """基于语义嵌入的实体对齐"""
    emb_source = model.encode(entities_source)
    emb_target = model.encode(entities_target)
    
    # 计算余弦相似度矩阵
    similarities = np.dot(emb_source, emb_target.T)
    
    alignments = {}
    for i, src in enumerate(entities_source):
        best_idx = np.argmax(similarities[i])
        if similarities[i][best_idx] >= threshold:
            alignments[src] = entities_target[best_idx]
    
    return alignments

source_entities = ["北京", "上海", "深圳"]
target_entities = ["Beijing", "Shanghai", "Guangzhou"]
alignments = embedding_alignment(source_entities, target_entities)
print(alignments)
# {'北京': 'Beijing', '上海': 'Shanghai'}

3.3 知识融合与消歧

知识融合是将来自多个来源的知识进行整合,解决冲突和冗余。主要包括:

  • 实体消歧:确定实体的具体含义(如"苹果"指苹果公司还是水果)
  • 关系融合:统一不同来源的关系定义
  • 属性融合:合并同一实体的不同属性值

3.4 知识存储

构建完成的知识图谱需要存储在专门的数据库中,以便高效查询和推理。常见的存储方案包括:

  • RDF存储:使用SPARQL查询语言
  • 图数据库:使用Cypher或Gremlin查询语言
  • 关系数据库:将三元组存储在关系表中

四、图数据库选型

4.1 Neo4j

Neo4j是最流行的图数据库,使用Cypher查询语言,具有出色的可视化能力和开发者生态。

优势

  • 成熟的生态系统和丰富的文档
  • 直观的Cypher查询语言
  • 强大的可视化工具(Neo4j Browser, Bloom)
  • ACID事务支持
  • 活跃的社区支持

劣势

  • 社区版功能有限,企业版收费
  • 分布式能力相对较弱
  • 大规模数据下的性能可能不如原生分布式图数据库
# 使用py2neo连接Neo4j
from py2neo import Graph, Node, Relationship

# 连接Neo4j数据库
graph = Graph("bolt://localhost:7687", auth=("neo4j", "password"))

# 创建节点
zhangsan = Node("Person", name="张三", age=30)
alibaba = Node("Company", name="阿里巴巴", location="杭州")
tsinghua = Node("University", name="清华大学", location="北京")

# 创建关系
works_at = Relationship(zhangsan, "WORKS_AT", alibaba, since=2020)
graduated_from = Relationship(zhangsan, "GRADUATED_FROM", tsinghua, year=2018)

# 写入图数据库
graph.create(zhangsan)
graph.create(alibaba)
graph.create(tsinghua)
graph.create(works_at)
graph.create(graduated_from)

# 查询:找出张三的所有关系
query = """
MATCH (p:Person {name: '张三'})-[r]->(target)
RETURN type(r) AS relation, target.name AS target_name
"""
results = graph.run(query)
for record in results:
    print(f"张三 --[{record['relation']}]--> {record['target_name']}")

4.2 JanusGraph

JanusGraph是一个开源的分布式图数据库,支持多种存储后端(Cassandra, HBase, BerkeleyDB)和索引后端(Elasticsearch, Solr)。

优势

  • 完全开源
  • 支持大规模分布式部署
  • 灵活的存储后端选择
  • 支持Gremlin查询语言
  • 与Apache TinkerPop生态兼容

劣势

  • 部署和运维复杂度较高
  • 文档相对较少
  • 性能调优需要较多经验

4.3 Nebula Graph

Nebula Graph是国产开源分布式图数据库,专为超大规模图数据设计。

优势

  • 原生分布式架构,支持水平扩展
  • 毫秒级查询延迟
  • 支持千亿节点和万亿边的超大规模图
  • nGQL查询语言(类似Cypher)
  • 活跃的中国社区

劣势

  • 生态系统相比Neo4j还在发展中
  • 可视化工具不如Neo4j丰富
# 使用nebula-python连接Nebula Graph
from nebula3.gclient.net import ConnectionPool
from nebula3.Config import Config

# 配置连接
config = Config()
config.max_connection_pool_size = 10
connection_pool = ConnectionPool()
connection_pool.init([('127.0.0.1', 9669)], config)

# 获取会话
session = connection_pool.get_session('root', 'nebula')

# 创建图空间
session.execute('CREATE SPACE IF NOT EXISTS knowledge_graph '
                '(vid_type=FIXED_STRING(32), partition_num=10, replica_factor=1);')
session.execute('USE knowledge_graph;')

# 创建标签和边类型
session.execute('CREATE TAG IF NOT EXISTS person(name string, age int);')
session.execute('CREATE TAG IF NOT EXISTS company(name string, location string);')
session.execute('CREATE EDGE IF NOT EXISTS works_at(since int);')

# 插入数据
session.execute('INSERT VERTEX person(name, age) VALUES "zhangsan":("张三", 30);')
session.execute('INSERT VERTEX company(name, location) VALUES "alibaba":("阿里巴巴", "杭州");')
session.execute('INSERT EDGE works_at(since) VALUES "zhangsan"->"alibaba":(2020);')

# 查询
result = session.execute('GO FROM "zhangsan" OVER works_at YIELD '
                         'dst(edge) AS company, properties(edge).since AS since;')
print(result)

4.4 选型建议

特性 Neo4j JanusGraph Nebula Graph
开源 部分开源 完全开源 完全开源
分布式 企业版支持 原生支持 原生支持
查询语言 Cypher Gremlin nGQL
学习曲线
社区活跃度 中高
适用场景 中小规模图 大规模图 超大规模图

五、图神经网络基础

5.1 图卷积网络(GCN)

图卷积网络(Graph Convolutional Network)是将卷积操作推广到图结构数据的经典方法。GCN通过聚合邻居节点的特征来更新当前节点的表示。

核心公式

\(H^{(l+1)} = \sigma(\tilde{D}^{-\frac{1}{2}} \tilde{A} \tilde{D}^{-\frac{1}{2}} H^{(l)} W^{(l)})\)

其中:

  • \(\tilde{A} = A + I\) 是加入自环的邻接矩阵
  • \(\tilde{D}\)\(\tilde{A}\) 的度矩阵
  • \(H^{(l)}\) 是第 \(l\) 层的节点特征矩阵
  • \(W^{(l)}\) 是可学习的权重矩阵
  • \(\sigma\) 是激活函数
import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv
from torch_geometric.data import Data

class GCN(torch.nn.Module):
    def __init__(self, num_features, hidden_channels, num_classes):
        super(GCN, self).__init__()
        self.conv1 = GCNConv(num_features, hidden_channels)
        self.conv2 = GCNConv(hidden_channels, num_classes)
    
    def forward(self, x, edge_index):
        # 第一层图卷积
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, p=0.5, training=self.training)
        # 第二层图卷积
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)

# 创建图数据
edge_index = torch.tensor([[0, 1, 1, 2, 2, 3],
                            [1, 0, 2, 1, 3, 2]], dtype=torch.long)
x = torch.randn(4, 16)  # 4个节点,每个节点16维特征
y = torch.tensor([0, 1, 0, 1], dtype=torch.long)  # 节点标签

data = Data(x=x, edge_index=edge_index, y=y)

# 训练模型
model = GCN(num_features=16, hidden_channels=32, num_classes=2)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-4)

model.train()
for epoch in range(200):
    optimizer.zero_grad()
    out = model(data.x, data.edge_index)
    loss = F.nll_loss(out, data.y)
    loss.backward()
    optimizer.step()
    if epoch % 50 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item():.4f}')

5.2 图注意力网络(GAT)

图注意力网络(Graph Attention Network)引入了注意力机制,允许不同邻居节点对当前节点有不同的影响权重。

核心思想:对于节点 \(i\) 和其邻居节点 \(j\),注意力系数计算如下:

\(e_{ij} = \text{LeakyReLU}(\vec{a}^T [W h_i \| W h_j])\)

\(\alpha_{ij} = \frac{\exp(e_{ij})}{\sum_{k \in \mathcal{N}(i)} \exp(e_{ik})}\)

其中 \(\|\) 表示拼接操作,\(\vec{a}\) 是可学习的注意力向量。

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch_geometric.nn import GATConv

class GAT(nn.Module):
    def __init__(self, num_features, hidden_channels, num_classes, heads=8):
        super(GAT, self).__init__()
        self.conv1 = GATConv(num_features, hidden_channels, heads=heads, dropout=0.6)
        # 第二层使用1个注意力头,输出维度为num_classes
        self.conv2 = GATConv(hidden_channels * heads, num_classes, heads=1,
                             concat=False, dropout=0.6)
    
    def forward(self, x, edge_index):
        x = F.dropout(x, p=0.6, training=self.training)
        x = F.elu(self.conv1(x, edge_index))
        x = F.dropout(x, p=0.6, training=self.training)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)

# 使用与GCN相同的数据
model = GAT(num_features=16, hidden_channels=8, num_classes=2, heads=8)
optimizer = torch.optim.Adam(model.parameters(), lr=0.005, weight_decay=5e-4)

model.train()
for epoch in range(200):
    optimizer.zero_grad()
    out = model(data.x, data.edge_index)
    loss = F.nll_loss(out, data.y)
    loss.backward()
    optimizer.step()

5.3 GraphSAGE

GraphSAGE(SAmple and aggreGatE)是一种归纳学习方法,通过采样和聚合邻居节点特征来生成节点嵌入。与GCN的转导学习不同,GraphSAGE可以为训练时未见过的新节点生成嵌入。

核心思想

  1. 采样:对每个节点固定采样 \(K\) 个邻居
  2. 聚合:使用聚合函数(均值、LSTM、池化)整合邻居信息
  3. 更新:将聚合的邻居信息与当前节点特征拼接,通过神经网络更新
import torch
import torch.nn as nn
from torch_geometric.nn import SAGEConv

class GraphSAGE(nn.Module):
    def __init__(self, num_features, hidden_channels, num_classes):
        super(GraphSAGE, self).__init__()
        self.conv1 = SAGEConv(num_features, hidden_channels)
        self.conv2 = SAGEConv(hidden_channels, num_classes)
    
    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, p=0.5, training=self.training)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)

# GraphSAGE支持mini-batch训练,适合大规模图
from torch_geometric.loader import NeighborLoader

# 创建mini-batch数据加载器
loader = NeighborLoader(
    data,
    num_neighbors=[15, 10],  # 每层采样的邻居数量
    batch_size=32,
    input_nodes=torch.arange(data.num_nodes),
)

model = GraphSAGE(num_features=16, hidden_channels=64, num_classes=2)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

model.train()
for epoch in range(10):
    total_loss = 0
    for batch in loader:
        optimizer.zero_grad()
        out = model(batch.x, batch.edge_index)
        loss = F.nll_loss(out[:batch.batch_size], batch.y[:batch.batch_size])
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print(f'Epoch {epoch}, Loss: {total_loss:.4f}')

5.4 GNN模型对比

模型 聚合方式 是否归纳学习 适用场景
GCN 加权平均 否(转导) 小规模图分类/节点分类
GAT 注意力加权 否(转导) 需要区分邻居重要性的场景
GraphSAGE 多种可选 是(归纳) 大规模图、动态图
GIN 求和+MLP 否(转导) 图级别的判别任务

六、图嵌入技术

6.1 Node2Vec

Node2Vec是一种通过随机游走学习节点嵌入的方法。它在图上执行有偏随机游走,然后使用类似Word2Vec的Skip-gram模型学习节点表示。

两个关键参数

  • p(返回参数):控制游走返回前一个节点的概率,p越小越倾向于深度优先搜索
  • q(进出参数):控制游走向外探索的概率,q越小越倾向于广度优先搜索
from node2vec import Node2Vec
import networkx as nx

# 创建图
G = nx.karate_club_graph()

# 训练Node2Vec模型
node2vec = Node2Vec(G, dimensions=64, walk_length=30, num_walks=200,
                    p=1, q=1, workers=4)
model = node2vec.fit(window=10, min_count=1, batch_words=4)

# 获取节点嵌入
node_id = 0
embedding = model.wv[str(node_id)]
print(f"节点 {node_id} 的嵌入向量(前10维): {embedding[:10]}")

# 查找相似节点
similar_nodes = model.wv.most_similar(str(node_id))
print(f"与节点 {node_id} 最相似的节点: {similar_nodes}")

# 使用嵌入进行下游任务(如节点分类)
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
import numpy as np

# 准备数据
labels = [G.nodes[n]['club'] == 'Mr. Hi' for n in G.nodes]
embeddings = np.array([model.wv[str(n)] for n in G.nodes])

X_train, X_test, y_train, y_test = train_test_split(
    embeddings, labels, test_size=0.3, random_state=42)

clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)
print(f"节点分类准确率: {clf.score(X_test, y_test):.2%}")

6.2 TransE

TransE是最经典的知识图谱嵌入模型。它将关系建模为头实体到尾实体的翻译向量:

\(h + r \approx t\)

其中 \(h\), \(r\), \(t\) 分别是头实体、关系和尾实体的嵌入向量。

import torch
import torch.nn as nn
import torch.nn.functional as F

class TransE(nn.Module):
    def __init__(self, num_entities, num_relations, embedding_dim, margin=1.0):
        super(TransE, self).__init__()
        self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
        self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
        self.margin = margin
        
        # 初始化
        nn.init.xavier_uniform_(self.entity_embeddings.weight)
        nn.init.xavier_uniform_(self.relation_embeddings.weight)
        # 关系嵌入归一化
        with torch.no_grad():
            self.relation_embeddings.weight.data = F.normalize(
                self.relation_embeddings.weight.data, p=2, dim=1)
    
    def forward(self, positive_triplets, negative_triplets):
        """
        positive_triplets: (batch_size, 3) -> [head, relation, tail]
        negative_triplets: (batch_size, 3) -> [head, relation, tail](损坏的三元组)
        """
        # 正样本得分
        pos_h = self.entity_embeddings(positive_triplets[:, 0])
        pos_r = self.relation_embeddings(positive_triplets[:, 1])
        pos_t = self.entity_embeddings(positive_triplets[:, 2])
        pos_score = torch.norm(pos_h + pos_r - pos_t, p=2, dim=1)
        
        # 负样本得分
        neg_h = self.entity_embeddings(negative_triplets[:, 0])
        neg_r = self.relation_embeddings(negative_triplets[:, 1])
        neg_t = self.entity_embeddings(negative_triplets[:, 2])
        neg_score = torch.norm(neg_h + neg_r - neg_t, p=2, dim=1)
        
        # Margin ranking loss
        loss = torch.relu(self.margin + pos_score - neg_score).mean()
        
        # 归一化实体嵌入
        with torch.no_grad():
            F.normalize(self.entity_embeddings.weight, p=2, dim=1, out=self.entity_embeddings.weight)
        
        return loss
    
    def score(self, h, r, t):
        """计算三元组得分(越小越好)"""
        h_emb = self.entity_embeddings(h)
        r_emb = self.relation_embeddings(r)
        t_emb = self.entity_embeddings(t)
        return torch.norm(h_emb + r_emb - t_emb, p=2, dim=1)

# 训练示例
num_entities = 1000
num_relations = 50
embedding_dim = 128

model = TransE(num_entities, num_relations, embedding_dim)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# 模拟训练数据
for epoch in range(100):
    positive_triplets = torch.randint(0, num_entities, (256, 3))
    # 生成负样本:随机替换头实体或尾实体
    negative_triplets = positive_triplets.clone()
    mask = torch.rand(256) > 0.5
    negative_triplets[mask, 0] = torch.randint(0, num_entities, (mask.sum(),))
    negative_triplets[~mask, 2] = torch.randint(0, num_entities, ((~mask).sum(),))
    
    loss = model(positive_triplets, negative_triplets)
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
    
    if epoch % 20 == 0:
        print(f"Epoch {epoch}, Loss: {loss.item():.4f}")

6.3 TransR

TransR是TransE的改进版本,它认为实体和关系应该在不同的语义空间中建模。TransR为每个关系引入一个投影矩阵 \(M_r\),将实体从实体空间投影到关系空间:

\(h_r = M_r h, \quad t_r = M_r t\)

\(h_r + r \approx t_r\)

class TransR(nn.Module):
    def __init__(self, num_entities, num_relations, entity_dim, relation_dim, margin=1.0):
        super(TransR, self).__init__()
        self.entity_embeddings = nn.Embedding(num_entities, entity_dim)
        self.relation_embeddings = nn.Embedding(num_relations, relation_dim)
        # 每个关系对应一个投影矩阵
        self.projection_matrices = nn.Embedding(num_relations, entity_dim * relation_dim)
        self.margin = margin
        self.entity_dim = entity_dim
        self.relation_dim = relation_dim
        
        nn.init.xavier_uniform_(self.entity_embeddings.weight)
        nn.init.xavier_uniform_(self.relation_embeddings.weight)
        nn.init.xavier_uniform_(self.projection_matrices.weight)
    
    def project(self, entity_emb, relation_id):
        """将实体从实体空间投影到关系空间"""
        proj_matrix = self.projection_matrices(relation_id)
        proj_matrix = proj_matrix.view(-1, self.entity_dim, self.relation_dim)
        # (batch, 1, entity_dim) x (batch, entity_dim, relation_dim) -> (batch, 1, relation_dim)
        projected = torch.bmm(entity_emb.unsqueeze(1), proj_matrix)
        return projected.squeeze(1)
    
    def forward(self, positive_triplets, negative_triplets):
        pos_h = self.entity_embeddings(positive_triplets[:, 0])
        pos_r = self.relation_embeddings(positive_triplets[:, 1])
        pos_t = self.entity_embeddings(positive_triplets[:, 2])
        
        pos_h_proj = self.project(pos_h, positive_triplets[:, 1])
        pos_t_proj = self.project(pos_t, positive_triplets[:, 1])
        pos_score = torch.norm(pos_h_proj + pos_r - pos_t_proj, p=2, dim=1)
        
        neg_h = self.entity_embeddings(negative_triplets[:, 0])
        neg_r = self.relation_embeddings(negative_triplets[:, 1])
        neg_t = self.entity_embeddings(negative_triplets[:, 2])
        
        neg_h_proj = self.project(neg_h, negative_triplets[:, 1])
        neg_t_proj = self.project(neg_t, negative_triplets[:, 1])
        neg_score = torch.norm(neg_h_proj + neg_r - neg_t_proj, p=2, dim=1)
        
        loss = torch.relu(self.margin + pos_score - neg_score).mean()
        return loss

七、知识图谱与LLM结合:GraphRAG

7.1 GraphRAG概述

GraphRAG(Graph-based Retrieval-Augmented Generation)是将知识图谱与大语言模型相结合的技术范式。传统RAG主要基于文本块的向量检索,而GraphRAG利用知识图谱的结构化知识来增强检索和生成过程。

GraphRAG的核心优势

  • 结构化知识:利用图结构捕获实体间的复杂关系
  • 多跳推理:支持跨多个实体和关系的推理
  • 全局理解:能够回答需要图谱全局信息的综合性问题
  • 可解释性:推理路径可追溯

7.2 GraphRAG架构

import networkx as nx
from sentence_transformers import SentenceTransformer
import numpy as np
import openai

class GraphRAG:
    def __init__(self, graph: nx.DiGraph, model_name='paraphrase-multilingual-MiniLM-L12-v2'):
        self.graph = graph
        self.embedding_model = SentenceTransformer(model_name)
        self.entity_embeddings = {}
        self._build_entity_index()
    
    def _build_entity_index(self):
        """构建实体嵌入索引"""
        entities = list(self.graph.nodes())
        entity_descriptions = []
        for entity in entities:
            # 获取实体的属性和关系作为描述
            attrs = self.graph.nodes[entity]
            neighbors = list(self.graph.neighbors(entity))
            desc = f"{entity} " + " ".join([str(v) for v in attrs.values()])
            for neighbor in neighbors[:5]:  # 取前5个邻居
                edge_data = self.graph.edges[entity, neighbor]
                desc += f" {edge_data.get('relation', 'related_to')} {neighbor}"
            entity_descriptions.append(desc)
        
        embeddings = self.embedding_model.encode(entity_descriptions)
        for i, entity in enumerate(entities):
            self.entity_embeddings[entity] = embeddings[i]
    
    def retrieve_subgraph(self, query, top_k=5, max_hops=2):
        """检索与查询相关的子图"""
        # 1. 语义匹配找到相关实体
        query_embedding = self.embedding_model.encode([query])[0]
        
        similarities = {}
        for entity, emb in self.entity_embeddings.items():
            sim = np.dot(query_embedding, emb) / (
                np.linalg.norm(query_embedding) * np.linalg.norm(emb) + 1e-8)
            similarities[entity] = sim
        
        # 选择最相关的实体
        top_entities = sorted(similarities.items(), key=lambda x: x[1], reverse=True)[:top_k]
        
        # 2. 扩展子图(多跳)
        subgraph = nx.DiGraph()
        for entity, score in top_entities:
            subgraph.add_node(entity, relevance_score=score)
            # BFS扩展
            visited = {entity}
            queue = [(entity, 0)]
            while queue:
                current, depth = queue.pop(0)
                if depth >= max_hops:
                    continue
                for neighbor in self.graph.neighbors(current):
                    if neighbor not in visited:
                        visited.add(neighbor)
                        edge_data = self.graph.edges[current, neighbor]
                        subgraph.add_node(neighbor)
                        subgraph.add_edge(current, neighbor, **edge_data)
                        queue.append((neighbor, depth + 1))
        
        return subgraph
    
    def subgraph_to_context(self, subgraph):
        """将子图转换为文本上下文"""
        context_parts = []
        for h, t, data in subgraph.edges(data=True):
            relation = data.get('relation', 'related_to')
            context_parts.append(f"{h} --[{relation}]--> {t}")
        return "\n".join(context_parts)
    
    def answer(self, query):
        """使用GraphRAG回答问题"""
        # 1. 检索相关子图
        subgraph = self.retrieve_subgraph(query)
        context = self.subgraph_to_context(subgraph)
        
        # 2. 使用LLM生成回答
        prompt = f"""基于以下知识图谱信息回答问题。如果知识图谱中没有相关信息,请说明。

知识图谱信息:
{context}

问题:{query}

回答:"""
        
        response = openai.ChatCompletion.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.3
        )
        return response.choices[0].message.content

# 使用示例
G = nx.DiGraph()
triples = [
    ("张三", "就职于", "阿里巴巴"),
    ("张三", "研究方向", "自然语言处理"),
    ("阿里巴巴", "开发了", "通义千问"),
    ("通义千问", "是", "大语言模型"),
    ("自然语言处理", "是", "人工智能子领域"),
]
for h, r, t in triples:
    G.add_edge(h, t, relation=r)

graph_rag = GraphRAG(G)
answer = graph_rag.answer("张三和通义千问有什么关系?")
print(answer)

八、图问答系统

图问答系统(Graph QA)是基于知识图谱进行自然语言问答的系统。相比传统的基于文本检索的QA系统,图QA能够利用结构化知识进行精确的多跳推理。

8.1 基于SPARQL/Cypher的问答

将自然语言问题转换为图查询语言(如Cypher),然后在知识图谱上执行查询。

class GraphQA:
    def __init__(self, graph, llm_client):
        self.graph = graph
        self.llm = llm_client
    
    def natural_language_to_cypher(self, question):
        """将自然语言问题转换为Cypher查询"""
        schema = self._get_graph_schema()
        
        prompt = f"""你是一个Cypher查询专家。根据以下图数据库schema和用户问题,生成Cypher查询语句。

图数据库Schema:
{schema}

用户问题:{question}

请只输出Cypher查询语句,不要输出其他内容:"""
        
        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0
        )
        return response.choices[0].message.content.strip()
    
    def _get_graph_schema(self):
        """获取图数据库的schema信息"""
        node_labels = set()
        edge_types = set()
        for node, data in self.graph.nodes(data=True):
            node_labels.add(data.get('label', 'Entity'))
        for u, v, data in self.graph.edges(data=True):
            edge_types.add(data.get('relation', 'RELATED_TO'))
        
        schema = f"节点类型: {', '.join(node_labels)}\n"
        schema += f"关系类型: {', '.join(edge_types)}\n"
        return schema
    
    def execute_query(self, cypher_query):
        """在NetworkX图上模拟执行Cypher查询"""
        # 这里简化为基于规则的查询执行
        results = []
        for h, t, data in self.graph.edges(data=True):
            results.append({
                'source': h,
                'relation': data.get('relation', ''),
                'target': t
            })
        return results
    
    def answer(self, question):
        """完整的问答流程"""
        # 1. 生成查询
        cypher = self.natural_language_to_cypher(question)
        print(f"生成的查询: {cypher}")
        
        # 2. 执行查询
        results = self.execute_query(cypher)
        
        # 3. 生成自然语言回答
        context = str(results)
        prompt = f"""根据以下查询结果回答用户问题。

查询结果:{context}

用户问题:{question}

请用自然语言回答:"""
        
        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.3
        )
        return response.choices[0].message.content

九、知识图谱推理与补全

9.1 链接预测

链接预测是知识图谱补全的核心任务,旨在预测图中缺失的边(关系)。

import torch
import torch.nn as nn
from torch_geometric.nn import RGCNConv

class RGCNLinkPredictor(nn.Module):
    """使用R-GCN进行链接预测"""
    def __init__(self, num_entities, num_relations, hidden_dim):
        super().__init__()
        self.rgcn1 = RGCNConv(num_entities, hidden_dim, num_relations)
        self.rgcn2 = RGCNConv(hidden_dim, hidden_dim, num_relations)
        self.relation_embeddings = nn.Embedding(num_relations, hidden_dim)
    
    def forward(self, x, edge_index, edge_type):
        x = self.rgcn1(x, edge_index, edge_type).relu()
        x = self.rgcn2(x, edge_index, edge_type)
        return x
    
    def predict(self, h_emb, r_emb, t_emb):
        """计算三元组得分"""
        score = (h_emb * r_emb * t_emb).sum(dim=-1)
        return torch.sigmoid(score)

# 训练循环
def train_link_prediction(model, train_data, optimizer):
    model.train()
    total_loss = 0
    for batch in train_data:
        optimizer.zero_grad()
        
        node_embeddings = model(batch.x, batch.edge_index, batch.edge_type)
        
        # 正样本
        pos_h = node_embeddings[batch.pos_triplets[:, 0]]
        pos_r = model.relation_embeddings(batch.pos_triplets[:, 1])
        pos_t = node_embeddings[batch.pos_triplets[:, 2]]
        pos_scores = model.predict(pos_h, pos_r, pos_t)
        
        # 负样本
        neg_h = node_embeddings[batch.neg_triplets[:, 0]]
        neg_r = model.relation_embeddings(batch.neg_triplets[:, 1])
        neg_t = node_embeddings[batch.neg_triplets[:, 2]]
        neg_scores = model.predict(neg_h, neg_r, neg_t)
        
        # Binary cross-entropy loss
        loss = -torch.log(pos_scores + 1e-10).mean() - torch.log(1 - neg_scores + 1e-10).mean()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    
    return total_loss

9.2 规则学习

基于规则的知识图谱推理通过学习逻辑规则来进行推理。例如:

  • isParent(X, Y) ∧ isParent(Y, Z) → isGrandparent(X, Z)
  • worksAt(X, Y) ∧ locatedIn(Y, Z) → livesIn(X, Z)
class RuleLearner:
    """基于AMIE算法的规则学习器"""
    def __init__(self, graph, min_support=0.01, min_confidence=0.5):
        self.graph = graph
        self.min_support = min_support
        self.min_confidence = min_confidence
    
    def find_rules(self):
        """发现知识图谱中的逻辑规则"""
        rules = []
        relations = set()
        for _, _, data in self.graph.edges(data=True):
            relations.add(data.get('relation', ''))
        
        # 遍历所有关系对,寻找路径规则
        for r1 in relations:
            for r2 in relations:
                if r1 == r2:
                    continue
                # 查找形如 (X, r1, Y) ∧ (Y, r2, Z) → (X, r?, Z) 的规则
                support, confidence, inferred_rel = self._evaluate_rule(r1, r2)
                if support >= self.min_support and confidence >= self.min_confidence:
                    rules.append({
                        'body': [r1, r2],
                        'head': inferred_rel,
                        'support': support,
                        'confidence': confidence
                    })
        
        return sorted(rules, key=lambda x: x['confidence'], reverse=True)
    
    def _evaluate_rule(self, r1, r2):
        """评估规则的支持度和置信度"""
        # 找到所有 (X, r1, Y) 的三元组
        r1_triples = [(h, t) for h, t, d in self.graph.edges(data=True)
                       if d.get('relation') == r1]
        r2_triples = [(h, t) for h, t, d in self.graph.edges(data=True)
                       if d.get('relation') == r2]
        
        # 找到路径 X -r1-> Y -r2-> Z
        paths = []
        for x, y in r1_triples:
            for y2, z in r2_triples:
                if y == y2:
                    paths.append((x, z))
        
        if not paths:
            return 0, 0, None
        
        # 统计 X 和 Z 之间已有的关系
        inferred_relations = {}
        for x, z in paths:
            if self.graph.has_edge(x, z):
                for _, _, data in self.graph.edges(x, z):
                    rel = data.get('relation', '')
                    inferred_relations[rel] = inferred_relations.get(rel, 0) + 1
        
        if not inferred_relations:
            return 0, 0, None
        
        # 选择最频繁的关系作为推理结果
        best_rel = max(inferred_relations, key=inferred_relations.get)
        support = inferred_relations[best_rel] / len(self.graph.edges())
        confidence = inferred_relations[best_rel] / len(paths)
        
        return support, confidence, best_rel

十、行业应用案例

10.1 金融知识图谱

金融知识图谱在风险管理、反欺诈、投资分析等领域有广泛应用。

核心实体类型:公司、个人、产品、事件、监管机构

核心关系类型:投资、任职、交易、担保、供应链

# 金融知识图谱示例
class FinancialKnowledgeGraph:
    def __init__(self):
        self.graph = nx.DiGraph()
    
    def add_company(self, company_id, name, industry, registered_capital):
        self.graph.add_node(company_id, type='company', name=name,
                           industry=industry, registered_capital=registered_capital)
    
    def add_person(self, person_id, name, title=None):
        self.graph.add_node(person_id, type='person', name=name, title=title)
    
    def add_relation(self, source, target, relation, **attrs):
        self.graph.add_edge(source, target, relation=relation, **attrs)
    
    def detect_risk_chains(self, company_id, max_depth=3):
        """检测公司关联风险链"""
        risk_paths = []
        visited = set()
        
        def dfs(node, path, depth):
            if depth > max_depth:
                return
            if node in visited:
                return
            visited.add(node)
            
            node_data = self.graph.nodes[node]
            # 检查风险指标
            if node_data.get('type') == 'company':
                # 检查是否涉及诉讼、处罚等
                for _, target, data in self.graph.edges(node, data=True):
                    if data.get('relation') in ['涉及诉讼', '受到处罚', '被列入失信']:
                        risk_paths.append(path + [(node, data['relation'], target)])
            
            for neighbor in self.graph.neighbors(node):
                edge_data = self.graph.edges[node, neighbor]
                dfs(neighbor, path + [(node, edge_data.get('relation', ''), neighbor)], depth + 1)
            
            visited.remove(node)
        
        dfs(company_id, [], 0)
        return risk_paths
    
    def calculate_company_risk_score(self, company_id):
        """计算公司风险评分"""
        risk_score = 0
        risk_factors = []
        
        # 1. 检查关联公司的风险
        risk_chains = self.detect_risk_chains(company_id)
        risk_score += len(risk_chains) * 10
        if risk_chains:
            risk_factors.append(f"发现{len(risk_chains)}条风险关联链")
        
        # 2. 检查股东变更频率
        shareholder_changes = sum(1 for _, _, d in self.graph.edges(company_id, data=True)
                                  if d.get('relation') == '股权变更')
        risk_score += shareholder_changes * 5
        if shareholder_changes > 3:
            risk_factors.append(f"近期有{shareholder_changes}次股权变更")
        
        # 3. 检查担保链长度
        guarantee_chain_length = self._get_guarantee_chain_length(company_id)
        if guarantee_chain_length > 5:
            risk_score += 20
            risk_factors.append(f"担保链过长({guarantee_chain_length}层)")
        
        return {
            'risk_score': min(risk_score, 100),
            'risk_factors': risk_factors
        }
    
    def _get_guarantee_chain_length(self, node, visited=None):
        """计算担保链长度"""
        if visited is None:
            visited = set()
        if node in visited:
            return 0
        visited.add(node)
        
        max_length = 0
        for _, target, data in self.graph.edges(node, data=True):
            if data.get('relation') == '担保':
                length = 1 + self._get_guarantee_chain_length(target, visited)
                max_length = max(max_length, length)
        
        return max_length

10.2 医疗知识图谱

医疗知识图谱整合了疾病、症状、药物、基因等多维度医学知识,支持智能诊断、药物推荐和医学研究。

class MedicalKnowledgeGraph:
    def __init__(self):
        self.graph = nx.DiGraph()
    
    def add_disease(self, disease_id, name, description, icd_code=None):
        self.graph.add_node(disease_id, type='disease', name=name,
                           description=description, icd_code=icd_code)
    
    def add_symptom(self, symptom_id, name, severity=None):
        self.graph.add_node(symptom_id, type='symptom', name=name, severity=severity)
    
    def add_drug(self, drug_id, name, dosage_form, manufacturer=None):
        self.graph.add_node(drug_id, type='drug', name=name,
                           dosage_form=dosage_form, manufacturer=manufacturer)
    
    def add_gene(self, gene_id, name, chromosome=None):
        self.graph.add_node(gene_id, type='gene', name=name, chromosome=chromosome)
    
    def add_relation(self, source, target, relation, **attrs):
        self.graph.add_edge(source, target, relation=relation, **attrs)
    
    def differential_diagnosis(self, symptom_ids):
        """根据症状进行鉴别诊断"""
        # 找出包含这些症状的疾病
        disease_scores = {}
        
        for symptom_id in symptom_ids:
            # 找到与该症状相关的所有疾病
            for disease_id, _, data in self.graph.in_edges(symptom_id, data=True):
                if data.get('relation') in ['常见症状', '典型症状', '伴随症状']:
                    weight = {'典型症状': 3, '常见症状': 2, '伴随症状': 1}.get(data['relation'], 1)
                    disease_scores[disease_id] = disease_scores.get(disease_id, 0) + weight
        
        # 按得分排序
        sorted_diseases = sorted(disease_scores.items(), key=lambda x: x[1], reverse=True)
        
        results = []
        for disease_id, score in sorted_diseases[:5]:
            disease_info = self.graph.nodes[disease_id]
            # 获取该疾病的其他症状(用于鉴别)
            related_symptoms = []
            for _, symptom_id, data in self.graph.out_edges(disease_id, data=True):
                if data.get('relation') in ['常见症状', '典型症状']:
                    related_symptoms.append(self.graph.nodes[symptom_id]['name'])
            
            results.append({
                'disease': disease_info['name'],
                'confidence': score / (len(symptom_ids) * 3),
                'related_symptoms': related_symptoms,
                'description': disease_info.get('description', '')
            })
        
        return results
    
    def drug_interaction_check(self, drug_ids):
        """检查药物相互作用"""
        interactions = []
        for i in range(len(drug_ids)):
            for j in range(i + 1, len(drug_ids)):
                d1, d2 = drug_ids[i], drug_ids[j]
                # 检查是否存在相互作用关系
                if self.graph.has_edge(d1, d2):
                    edge_data = self.graph.edges[d1, d2]
                    if edge_data.get('relation') in ['药物相互作用', '禁忌配伍']:
                        interactions.append({
                            'drug1': self.graph.nodes[d1]['name'],
                            'drug2': self.graph.nodes[d2]['name'],
                            'type': edge_data['relation'],
                            'severity': edge_data.get('severity', '未知'),
                            'description': edge_data.get('description', '')
                        })
        return interactions
    
    def find_treatment_path(self, disease_id):
        """查找疾病治疗路径"""
        treatment_path = []
        
        # 1. 一线治疗药物
        first_line_drugs = []
        for _, drug_id, data in self.graph.out_edges(disease_id, data=True):
            if data.get('relation') == '一线治疗':
                first_line_drugs.append(self.graph.nodes[drug_id])
        
        # 2. 二线治疗药物
        second_line_drugs = []
        for _, drug_id, data in self.graph.out_edges(disease_id, data=True):
            if data.get('relation') == '二线治疗':
                second_line_drugs.append(self.graph.nodes[drug_id])
        
        # 3. 相关检查
        examinations = []
        for _, exam_id, data in self.graph.out_edges(disease_id, data=True):
            if data.get('relation') in ['需要检查', '确诊检查']:
                examinations.append(self.graph.nodes[exam_id])
        
        return {
            'disease': self.graph.nodes[disease_id]['name'],
            'first_line_treatment': first_line_drugs,
            'second_line_treatment': second_line_drugs,
            'required_examinations': examinations
        }

10.3 电商知识图谱

电商知识图谱整合了商品、用户、品牌、品类等知识,支持智能推荐、商品搜索和用户画像。

class EcommerceKnowledgeGraph:
    def __init__(self):
        self.graph = nx.DiGraph()
    
    def add_product(self, product_id, name, category, brand, price, attributes=None):
        self.graph.add_node(product_id, type='product', name=name, category=category,
                           brand=brand, price=price, **(attributes or {}))
    
    def add_user(self, user_id, demographics=None):
        self.graph.add_node(user_id, type='user', **(demographics or {}))
    
    def add_brand(self, brand_id, name, country=None):
        self.graph.add_node(brand_id, type='brand', name=name, country=country)
    
    def add_relation(self, source, target, relation, **attrs):
        self.graph.add_edge(source, target, relation=relation, **attrs)
    
    def knowledge_based_recommendation(self, user_id, top_n=10):
        """基于知识图谱的推荐"""
        user_data = self.graph.nodes[user_id]
        
        # 1. 获取用户历史购买和浏览的商品
        purchased_products = set()
        viewed_products = set()
        
        for _, target, data in self.graph.out_edges(user_id, data=True):
            if data.get('relation') == '购买':
                purchased_products.add(target)
            elif data.get('relation') == '浏览':
                viewed_products.add(target)
        
        # 2. 分析用户偏好
        preferred_categories = {}
        preferred_brands = {}
        price_range = []
        
        for product_id in purchased_products | viewed_products:
            product = self.graph.nodes[product_id]
            cat = product.get('category', '未知')
            brand = product.get('brand', '未知')
            price = product.get('price', 0)
            
            weight = 3 if product_id in purchased_products else 1
            preferred_categories[cat] = preferred_categories.get(cat, 0) + weight
            preferred_brands[brand] = preferred_brands.get(brand, 0) + weight
            if price > 0:
                price_range.append(price)
        
        # 3. 基于偏好推荐商品
        candidates = {}
        for node, data in self.graph.nodes(data=True):
            if data.get('type') != 'product':
                continue
            if node in purchased_products or node in viewed_products:
                continue
            
            score = 0
            # 品类匹配
            if data.get('category') in preferred_categories:
                score += preferred_categories[data['category']] * 2
            # 品牌匹配
            if data.get('brand') in preferred_brands:
                score += preferred_brands[data['brand']]
            # 价格匹配
            if price_range:
                avg_price = sum(price_range) / len(price_range)
                price_diff = abs(data.get('price', 0) - avg_price) / avg_price
                if price_diff < 0.3:  # 价格差异在30%以内
                    score += 5
            
            if score > 0:
                candidates[node] = score
        
        # 4. 利用知识图谱关系增强推荐
        # 通过商品的相似商品、同品牌商品等关系扩展
        for product_id in list(candidates.keys())[:5]:
            for _, related, data in self.graph.out_edges(product_id, data=True):
                if data.get('relation') in ['相似商品', '配套商品', '同品牌']:
                    if related not in purchased_products and related not in viewed_products:
                        candidates[related] = candidates.get(related, 0) + 3
        
        # 返回TopN推荐
        sorted_candidates = sorted(candidates.items(), key=lambda x: x[1], reverse=True)
        return [
            {'product_id': pid, 'name': self.graph.nodes[pid]['name'],
             'score': score}
            for pid, score in sorted_candidates[:top_n]
        ]

十一、实战案例:构建领域知识图谱并接入LLM

11.1 项目概述

本案例将构建一个人工智能领域知识图谱,包含AI技术、公司、人物、论文等实体,并接入LLM实现智能问答。

11.2 数据准备与图谱构建

import json
import networkx as nx
from sentence_transformers import SentenceTransformer
import numpy as np

class AIKnowledgeGraphBuilder:
    """AI领域知识图谱构建器"""
    
    def __init__(self):
        self.graph = nx.DiGraph()
        self.entity_types = {
            'technology': '技术',
            'company': '公司',
            'person': '人物',
            'paper': '论文',
            'dataset': '数据集',
            'framework': '框架',
            'concept': '概念'
        }
    
    def build_from_structured_data(self, data_file):
        """从结构化数据构建知识图谱"""
        with open(data_file, 'r', encoding='utf-8') as f:
            data = json.load(f)
        
        for entity in data['entities']:
            self.graph.add_node(
                entity['id'],
                type=entity['type'],
                name=entity['name'],
                description=entity.get('description', ''),
                **entity.get('attributes', {})
            )
        
        for relation in data['relations']:
            self.graph.add_edge(
                relation['source'],
                relation['target'],
                relation=relation['type'],
                **relation.get('attributes', {})
            )
        
        print(f"知识图谱构建完成: {self.graph.number_of_nodes()} 个实体, "
              f"{self.graph.number_of_edges()} 条关系")
    
    def build_from_text(self, text, llm_client):
        """从文本中自动构建知识图谱"""
        prompt = f"""从以下文本中抽取AI领域的实体和关系,输出JSON格式:

文本:{text}

输出格式:
{{
    "entities": [
        {{"id": "唯一ID", "type": "技术/公司/人物/论文/数据集/框架/概念", "name": "名称", "description": "描述"}}
    ],
    "relations": [
        {{"source": "源实体ID", "target": "目标实体ID", "type": "关系类型"}}
    ]
}}"""
        
        response = llm_client.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0
        )
        
        extracted = json.loads(response.choices[0].message.content)
        
        for entity in extracted['entities']:
            self.graph.add_node(entity['id'], **entity)
        
        for relation in extracted['relations']:
            self.graph.add_edge(relation['source'], relation['target'],
                              relation=relation['type'])
        
        return extracted
    
    def add_ai_domain_knowledge(self):
        """添加AI领域基础知识"""
        # 核心技术
        technologies = [
            ("tech_nlp", "自然语言处理", "NLP是AI的重要分支,研究人机交互中的语言理解与生成"),
            ("tech_cv", "计算机视觉", "CV研究如何让计算机理解和处理图像与视频"),
            ("tech_ml", "机器学习", "ML是AI的核心方法,通过数据驱动的方式学习规律"),
            ("tech_dl", "深度学习", "DL是机器学习的子领域,使用多层神经网络学习特征表示"),
            ("tech_rl", "强化学习", "RL通过与环境交互学习最优策略"),
            ("tech_gnn", "图神经网络", "GNN用于处理图结构数据的深度学习方法"),
            ("tech_transformer", "Transformer", "基于自注意力机制的神经网络架构"),
            ("tech_llm", "大语言模型", "LLM是基于Transformer的大规模预训练语言模型"),
        ]
        
        for tech_id, name, desc in technologies:
            self.graph.add_node(tech_id, type='technology', name=name, description=desc)
        
        # 公司
        companies = [
            ("comp_openai", "OpenAI", "ChatGPT和GPT系列模型的开发公司"),
            ("comp_google", "Google", "开发了BERT、PaLM、Gemini等模型"),
            ("comp_meta", "Meta", "开源了LLaMA系列模型"),
            ("comp_anthropic", "Anthropic", "开发了Claude系列模型"),
            ("comp_baidu", "百度", "开发了文心一言大模型"),
            ("comp_alibaba", "阿里巴巴", "开发了通义千问大模型"),
        ]
        
        for comp_id, name, desc in companies:
            self.graph.add_node(comp_id, type='company', name=name, description=desc)
        
        # 关系
        relations = [
            ("tech_dl", "tech_ml", "是子领域"),
            ("tech_nlp", "tech_dl", "使用技术"),
            ("tech_cv", "tech_dl", "使用技术"),
            ("tech_gnn", "tech_dl", "是子领域"),
            ("tech_llm", "tech_transformer", "基于架构"),
            ("tech_llm", "tech_nlp", "应用于"),
            ("comp_openai", "tech_llm", "开发了"),
            ("comp_openai", "tech_transformer", "贡献于"),
            ("comp_google", "tech_transformer", "提出"),
            ("comp_google", "tech_llm", "开发了"),
            ("comp_meta", "tech_llm", "开发了"),
            ("comp_anthropic", "tech_llm", "开发了"),
            ("comp_baidu", "tech_llm", "开发了"),
            ("comp_alibaba", "tech_llm", "开发了"),
        ]
        
        for src, tgt, rel in relations:
            self.graph.add_edge(src, tgt, relation=rel)
        
        print(f"AI领域知识添加完成: {self.graph.number_of_nodes()} 个实体, "
              f"{self.graph.number_of_edges()} 条关系")

11.3 接入LLM实现智能问答

class AIGraphQA:
    """基于AI知识图谱的智能问答系统"""
    
    def __init__(self, graph: nx.DiGraph, llm_client):
        self.graph = graph
        self.llm = llm_client
        self.embedding_model = SentenceTransformer('paraphrase-multilingual-MiniLM-L12-v2')
        self._build_index()
    
    def _build_index(self):
        """构建语义索引"""
        self.entities = list(self.graph.nodes(data=True))
        self.entity_names = [data.get('name', node) for node, data in self.entities]
        self.entity_embeddings = self.embedding_model.encode(self.entity_names)
    
    def retrieve(self, query, top_k=5):
        """语义检索相关实体"""
        query_emb = self.embedding_model.encode([query])[0]
        similarities = np.dot(self.entity_embeddings, query_emb) / (
            np.linalg.norm(self.entity_embeddings, axis=1) * np.linalg.norm(query_emb) + 1e-8)
        
        top_indices = np.argsort(similarities)[::-1][:top_k]
        results = []
        for idx in top_indices:
            node_id, data = self.entities[idx]
            results.append({
                'id': node_id,
                'name': data.get('name', ''),
                'type': data.get('type', ''),
                'description': data.get('description', ''),
                'similarity': float(similarities[idx])
            })
        return results
    
    def get_entity_context(self, entity_id, max_depth=2):
        """获取实体的上下文信息"""
        context = []
        visited = set()
        
        def dfs(node, depth):
            if depth > max_depth or node in visited:
                return
            visited.add(node)
            
            node_data = self.graph.nodes[node]
            context.append({
                'id': node,
                'name': node_data.get('name', ''),
                'type': node_data.get('type', ''),
                'description': node_data.get('description', '')
            })
            
            for neighbor in self.graph.neighbors(node):
                edge_data = self.graph.edges[node, neighbor]
                context.append({
                    'relation': edge_data.get('relation', '相关'),
                    'from': node_data.get('name', node),
                    'to': self.graph.nodes[neighbor].get('name', neighbor)
                })
                dfs(neighbor, depth + 1)
        
        dfs(entity_id, 0)
        return context
    
    def answer(self, question):
        """智能问答"""
        # 1. 检索相关实体
        relevant_entities = self.retrieve(question, top_k=3)
        
        # 2. 获取上下文
        all_context = []
        for entity in relevant_entities:
            context = self.get_entity_context(entity['id'])
            all_context.extend(context)
        
        # 3. 格式化上下文
        context_text = self._format_context(all_context)
        
        # 4. 使用LLM生成回答
        prompt = f"""你是一个AI领域专家。基于以下知识图谱信息回答用户问题。

知识图谱信息:
{context_text}

用户问题:{question}

请基于知识图谱信息给出准确、详细的回答。如果知识图谱中没有相关信息,请明确说明。"""

        response = self.llm.chat.completions.create(
            model="gpt-4",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.3
        )
        
        return response.choices[0].message.content
    
    def _format_context(self, context):
        """格式化上下文信息"""
        lines = []
        for item in context:
            if 'relation' in item:
                lines.append(f"- {item['from']} --[{item['relation']}]--> {item['to']}")
            else:
                lines.append(f"- [{item['type']}] {item['name']}: {item['description']}")
        return '\n'.join(lines)

11.4 完整运行示例

# 构建知识图谱
builder = AIKnowledgeGraphBuilder()
builder.add_ai_domain_knowledge()

# 创建问答系统
import openai
qa_system = AIGraphQA(builder.graph, openai)

# 测试问答
questions = [
    "Transformer架构对大语言模型有什么影响?",
    "OpenAI开发了哪些AI技术?",
    "图神经网络和深度学习是什么关系?",
    "国内有哪些公司在做大语言模型?",
]

for q in questions:
    print(f"\n问题: {q}")
    print(f"回答: {qa_system.answer(q)}")
    print("-" * 80)

十二、最佳实践

12.1 知识图谱设计原则

  1. 明确领域范围:在构建之前明确知识图谱的领域和用途,避免范围过大导致质量下降
  2. 设计合理的本体:本体设计是知识图谱的基础,需要领域专家参与
  3. 保证数据质量:建立数据质量评估机制,定期清洗和更新
  4. 增量构建:采用增量方式构建,而非一次性全量构建

12.2 GNN模型选择指南

  1. 数据规模:小规模图用GCN/GAT,大规模图用GraphSAGE
  2. 任务类型:节点分类用GCN/GAT,图分类用GIN,链接预测用R-GCN
  3. 是否需要归纳学习:如果需要处理新节点,选择GraphSAGE
  4. 注意力需求:如果需要区分邻居重要性,选择GAT

12.3 性能优化

# 大规模图的处理优化
import torch
from torch_geometric.loader import NeighborSampler

class OptimizedGNN(torch.nn.Module):
    """支持大规模图训练的GNN模型"""
    def __init__(self, in_channels, hidden_channels, out_channels):
        super().__init__()
        self.conv1 = SAGEConv(in_channels, hidden_channels)
        self.conv2 = SAGEConv(hidden_channels, out_channels)
    
    def forward(self, x, adjs):
        """使用采样的邻接矩阵进行前向传播"""
        for i, (edge_index, _, size) in enumerate(adjs):
            x_target = x[:size[1]]
            x = self.conv1((x, x_target) if i == 0 else (x, x_target), edge_index)
            if i < len(adjs) - 1:
                x = F.relu(x)
                x = F.dropout(x, p=0.5, training=self.training)
        return x.log_softmax(dim=-1)

# 使用NeighborSampler进行mini-batch训练
train_loader = NeighborSampler(
    data.edge_index,
    node_idx=train_mask,
    sizes=[15, 10],  # 每层采样数量
    batch_size=1024,
    shuffle=True,
    num_workers=4
)

十三、常见问题

Q1: 知识图谱和传统数据库有什么区别?

知识图谱强调语义关系推理能力,而传统数据库主要关注数据的存储和查询。知识图谱能够发现隐含的关系,支持多跳推理,这是传统关系数据库难以做到的。

Q2: 如何评估知识图谱的质量?

常用的质量评估指标包括:

  • 完整性:是否覆盖了领域内的关键知识
  • 准确性:三元组的正确率
  • 一致性:是否存在矛盾的知识
  • 时效性:知识是否及时更新

Q3: GNN训练时遇到过拟合怎么办?

常见解决方案:

  • 增加Dropout层
  • 使用更小的模型(减少层数和隐藏维度)
  • 数据增强(边扰动、节点特征masking)
  • 使用Early Stopping
  • 正则化(L2正则、权重衰减)

Q4: 如何处理大规模知识图谱的存储和查询?

  • 使用分布式图数据库(如Nebula Graph)
  • 采用图分区策略
  • 使用索引加速查询
  • 考虑使用缓存机制

Q5: GraphRAG相比传统RAG有什么优势?

GraphRAG能够利用图结构的拓扑信息进行多跳推理,而传统RAG只能基于向量相似度检索文本块。GraphRAG在需要理解实体间复杂关系的问题上表现更好。


十四、总结

本教程系统地介绍了AI知识图谱与图神经网络的核心概念、技术方法和实战应用。关键要点:

  1. 知识图谱是组织和表示知识的强大工具,三元组是其基本知识单元
  2. 图数据库的选择需要根据数据规模、查询需求和团队技术栈来决定
  3. 图神经网络(GCN、GAT、GraphSAGE)为图数据的深度学习提供了强大工具
  4. 图嵌入技术(Node2Vec、TransE、TransR)能够学习节点和关系的低维表示
  5. GraphRAG将知识图谱与LLM结合,实现了更强大的知识增强生成
  6. 行业应用中,知识图谱在金融风控、医疗诊断、电商推荐等场景展现出巨大价值

随着大语言模型和图学习技术的不断发展,知识图谱将在AI系统中扮演越来越重要的角色。掌握这些技术,将为你构建更智能的AI系统提供坚实的基础。


参考资源

内容声明

本文内容为AI技术学习教程,仅供学习参考。如涉及技术问题,欢迎通过 xurj005@163.com 与我们交流。

目录