知识图谱补全技术-ConvE篇
文章目录
前言
ConvE(Convolutional Knowledge Graph Embedding)是一种用于知识图谱补全的模型,它通过卷积神经网络(CNN)来学习知识图谱中的实体和关系的嵌入表示,从而预测缺失的三元组。其主要思想是将传统的知识图谱嵌入方法与卷积神经网络相结合,以提高模型的表达能力和推理能力。
一、ConvE模型原理
ConvE 的核心思想是将实体和关系嵌入重塑为矩阵形式,通过卷积神经网络提取实体和关系之间的复杂交互特征,并通过全连接层将卷积后的特征映射回实体空间,进行尾实体的预测。
与以往的线性模型(如 TransE、DistMult)相比,ConvE 引入了非线性操作(卷积和激活函数),可以更好地捕捉实体和关系之间的复杂模式。这种卷积操作允许模型对局部交互进行更深入的分析,从而增强模型的表达能力。
模型原理图如下图:
1.模型架构与流程
1.1 实体和关系的嵌入表示
ConvE 模型的第一步是为每个实体和关系生成一个嵌入向量。假设知识图谱中有 N 个实体和 M 个关系,每个实体和关系通过嵌入矩阵映射到一个固定的低维向量空间。具体表示为:头实体 h 的嵌入表示为向量 ℎ∈𝑅𝑑 ,关系 r 的嵌入表示为向量 𝑟∈𝑅𝑑 ,尾实体 t 的嵌入表示为向量 𝑡∈𝑅𝑑,其中 𝑑 为嵌入的维度。
1.2 嵌入的重塑与卷积操作
在得到实体和关系的嵌入后,ConvE 模型通过以下方式进行卷积操作:
1.2.1 嵌入重塑(Reshaping):
ConvE 通过将实体和关系的嵌入向量重塑为矩阵,通常是将维度为 𝑑d的向量重塑为 𝑘×𝑘的矩阵形式。例如,一个维度为 100 的嵌入向量可以重塑为一个 10×10 的矩阵。重塑后的头实体嵌入 ℎ′ 和关系嵌入 𝑟′ 被拼接为一个 2𝑘×𝑘 的矩阵:
拼接后的矩阵包含了头实体和关系的嵌入信息。
1.2.2 卷积操作:
通过将拼接后的矩阵输入卷积神经网络,使用卷积核对局部区域进行感知和特征提取。卷积层可以通过局部的权重共享机制,捕捉头实体和关系之间的局部交互特征。这一步通过卷积层来学习实体和关系之间复杂的特征模式。
1.2.3 非线性激活:
卷积后的输出通常通过ReLU等非线性激活函数进行处理,使得模型能够捕捉到更多的非线性特征。
1.3 特征拉平与全连接层
卷积操作之后,生成的特征图通过拉平(flatten)操作被转换为一个一维向量。这个向量接着通过全连接层进行映射,输出一个固定维度的向量(通常与嵌入维度相同)。
这一层的输出向量可以看作是头实体和关系的卷积特征,它们被用于与知识图谱中所有实体进行相似度计算。
1.4 得分计算与尾实体预测
模型最后通过计算该特征向量与所有实体嵌入向量的点积,得到每个实体作为候选尾实体的得分。点积越大,表示该实体作为尾实体的可能性越高。
假设计算得分的函数为:
其中 f是卷积和全连接层后的输出向量,t 是候选尾实体的嵌入向量。最后通过对得分排序,选出得分最高的实体作为尾实体的预测结果。
2.损失函数与训练
ConvE模型通常使用负采样策略来生成负样本,并使用排名损失(Ranking Loss)或交叉熵损失(Cross-Entropy Loss)来优化模型。通过最小化正样本的得分与负样本的得分差异,模型能够学习到更准确的实体和关系嵌入。
典型的损失函数形式为:
其中,𝐷是训练数据集,𝐸是所有实体的集合。
3.ConvE的优缺点
优点:
非线性增强:与传统的线性模型(如 TransE、DistMult)相比,ConvE 通过卷积神经网络引入了非线性操作,增强了模型的表示能力,能够捕捉更加复杂的实体关系交互。
局部感知能力:卷积操作可以捕捉头实体和关系之间的局部特征,而不是简单地进行向量之间的乘积或加法,从而能更好地处理复杂关系。
参数高效:ConvE 使用了共享权重的卷积核,参数量相比一些全连接的嵌入方法更少,同时还能取得更好的表现。
缺点:
计算复杂度高:卷积操作和全连接层的加入使得模型的训练和推理时间相对较长,尤其是在处理大规模知识图谱时。
超参数敏感:ConvE 模型对卷积核大小、嵌入维度、dropout 比例等超参数比较敏感,模型性能依赖于精细的超参数调优。
二、ConvE算法
1.准备好三元组数据集csv格式
csv文件内容三元组格式如图
2.安装必要的库
安装了以下Python包:
torch
pandas
scikit-learn
numpy
3.运行步骤
确保所有文件在同一目录下;
编辑train.py和completion.py中的文件路径,确保使用正确的数据文件路径;
运行data_preprocessing.py检查数据列名是否正确;
运行train.py训练模型;
运行completion.py进行知识补全。
4.部分代码展示
data_preprocessing.py
import pandas as pd
from sklearn.model_selection import train_test_split
import torch
def load_and_preprocess_data(file_path):
# 加载数据
data = pd.read_csv(file_path)
# 创建实体和关系的索引映射
entities = pd.concat([data['头实体'], data['尾实体']]).unique()
relations = data['关系'].unique()
entity_to_id = {entity: idx for idx, entity in enumerate(entities)}
relation_to_id = {relation: idx for idx, relation in enumerate(relations)}
# 将实体和关系映射为索引
data['头实体'] = data['头实体'].map(entity_to_id)
data['关系'] = data['关系'].map(relation_to_id)
data['尾实体'] = data['尾实体'].map(entity_to_id)
# 划分训练集、验证集和测试集
train_data, test_data = train_test_split(data, test_size=0.2, random_state=42)
train_data, valid_data = train_test_split(train_data, test_size=0.2, random_state=42)
# 转换为PyTorch张量
train_triples = torch.LongTensor(train_data.values)
valid_triples = torch.LongTensor(valid_data.values)
.......
data_preprocessing.py 文件的主要作用是加载并预处理数据。主要流程包括:
从CSV文件中读取三元组数据。
为每个实体和关系创建唯一的ID映射(将字符串实体/关系映射为整数ID)。
将这些ID映射应用到数据集,转换为模型能够理解的数值型数据。
划分数据集为训练集、验证集和测试集,保证模型可以进行训练和评估。
最终输出PyTorch张量格式的数据,以便后续模型能够直接使用这些数据。
model.py
import torch
import torch.nn as nn
import torch.nn.functional as F
class ConvE(nn.Module):
def __init__(self, num_entities, num_relations, embedding_dim, input_dim=(10, 10), dropout=0.3):
super(ConvE, self).__init__()
self.embedding_dim = embedding_dim
self.entity_embeddings = nn.Embedding(num_entities, embedding_dim)
self.relation_embeddings = nn.Embedding(num_relations, embedding_dim)
# ConvE的特定卷积层和Dropout层
self.input_dim = input_dim # 输入维度,例如(10, 10)
self.inp_drop = nn.Dropout(dropout) # 输入的dropout
self.hidden_drop = nn.Dropout(dropout) # 隐藏层的dropout
self.feature_map_drop = nn.Dropout2d(dropout) # 卷积特征图的dropout
# 定义卷积层:输入通道1,输出通道32,卷积核大小(3, 3)
self.conv = nn.Conv2d(1, 32, (3, 3), stride=1, padding=1)
# 计算卷积层输出的展平大小,确保与全连接层的输入维度匹配
conv_output_width = input_dim[1] + embedding_dim // input_dim[0] # 计算卷积后的宽度
conv_output_height = input_dim[0] # 高度保持不变
conv_output_size = 32 * conv_output_height * conv_output_width # 32个卷积输出通道,乘以输出的高度和宽度
# 全连接层,输入大小是conv_output_size,输出大小是embedding_dim
self.fc = nn.Linear(conv_output_size, embedding_dim)
# 初始化实体和关系嵌入权重
nn.init.xavier_uniform_(self.entity_embeddings.weight)
nn.init.xavier_uniform_(self.relation_embeddings.weight)
def forward(self, head, relation, tail=None):
# 获取头实体和关系的嵌入
h_emb = self.entity_embeddings(head)
r_emb = self.relation_embeddings(relation)
# 判断是否传入了所有实体(用于评估)或单个尾实体(用于训练)
if tail is None:
# 当没有传入 tail 时,假设传入的是所有实体,需扩展关系的维度以匹配实体数量
if head.shape[0] > 1: # 如果传入的是多个实体
r_emb = r_emb.expand_as(h_emb)
# 调整形状以适应卷积操作
h_emb = h_emb.view(-1, 1, *self.input_dim) # 将实体嵌入重塑为2D形状
r_emb = r_emb.view(-1, 1, *self.input_dim) # 将关系嵌入重塑为2D形状
.......
model.py 文件的核心内容包括:
定义了实体和关系的嵌入层。
定义了2D卷积层,用来处理头实体和关系的拼接嵌入,提取局部特征。
使用全连接层将卷积后的输出映射回实体嵌入空间,进行相似度计算,输出预测分数。
使用矩阵乘法计算所有实体的得分,最后用于预测尾实体或头实体。
train.py
import torch
import torch.optim as optim
import torch.nn.functional as F # 导入torch.nn.functional
import numpy as np
from model import ConvE
from data_preprocessing import load_and_preprocess_data
# 加载数据
file_path = '三元组数据集.csv'#替换为你实际的三元组数据集路径
train_triples, valid_triples, test_triples, entity_to_id, relation_to_id = load_and_preprocess_data(file_path)
# 初始化模型参数
num_entities = len(entity_to_id)
num_relations = len(relation_to_id)
embedding_dim = 100
# 创建 ConvE 模型和优化器
model = ConvE(num_entities, num_relations, embedding_dim)
optimizer = optim.Adam(model.parameters(), lr=0.001)
# 对比损失函数
def margin_ranking_loss(pos_score, neg_score, margin=1.0):
return torch.mean(F.relu(neg_score - pos_score + margin))
# 训练模型
num_epochs = 1000
margin = 1.5 # 对比损失的 margin
for epoch in range(num_epochs):
model.train()
optimizer.zero_grad()
# 随机选择负样本
neg_tail = torch.randint(0, num_entities, (len(train_triples),))
neg_triples = torch.stack((train_triples[:, 0], train_triples[:, 1], neg_tail), dim=1)
# 计算正样本的得分
pos_score = model(train_triples[:, 0], train_triples[:, 1])
# 计算负样本的得分
neg_score = model(neg_triples[:, 0], neg_triples[:, 1])
# 获取正样本对应的尾实体得分
pos_score = pos_score.gather(1, train_triples[:, 2].view(-1, 1)).squeeze()
# 获取负样本对应的尾实体得分
neg_score = neg_score.gather(1, neg_triples[:, 2].view(-1, 1)).squeeze()
# 使用对比损失函数代替原有的损失函数
loss = margin_ranking_loss(pos_score, neg_score, margin)
loss.backward()
optimizer.step()
.......
train.py 文件负责模型的训练和评估。主要包括以下步骤:
定义模型和优化器,使用Adam优化算法。
随机生成负样本,用于与正样本进行对比,计算损失。
每轮训练过程中,通过正负样本的得分计算损失,并进行反向传播更新模型。
训练结束后,通过 evaluate_model 函数评估模型在测试集上的性能。
运行结果如图:
completion.py
import torch
from model import ConvE
from data_preprocessing import load_and_preprocess_data
# 加载数据和模型
file_path = '三元组数据集.csv'#替换为你实际的三元组数据集路径
train_triples, valid_triples, test_triples, entity_to_id, relation_to_id = load_and_preprocess_data(file_path)
# 初始化模型参数
num_entities = len(entity_to_id)
num_relations = len(relation_to_id)
embedding_dim = 100
# 创建和加载 ConvE 模型
model = ConvE(num_entities, num_relations, embedding_dim)
model.load_state_dict(torch.load('conve_model.pth'))
model.eval()
# 知识补全函数
def predict_tail(head, relation, k=5):
head_idx = torch.LongTensor([entity_to_id[head]])
relation_idx = torch.LongTensor([relation_to_id[relation]])
all_entities = torch.arange(num_entities)
scores = model(head_idx, relation_idx)
_, topk_indices = torch.topk(scores, k + 1, largest=False) # 多取一个结果,以便过滤
topk_entities = [list(entity_to_id.keys())[int(idx)] for idx in topk_indices.view(-1).tolist()]
if head in topk_entities:
topk_entities.remove(head)
return topk_entities[:k]
def predict_head(tail, relation, k=5):
tail_idx = torch.LongTensor([entity_to_id[tail]])
relation_idx = torch.LongTensor([relation_to_id[relation]])
all_entities = torch.arange(num_entities)
scores = model(all_entities, relation_idx)
_, topk_indices = torch.topk(scores, k + 1, largest=False)
.......
completion.py 文件主要用于知识补全任务。
加载预训练的模型参数。
定义补全函数 predict_tail 和 predict_head,分别用于预测尾实体和头实体。
使用模型的输出分数,对所有实体进行排序,找到最匹配的头或尾实体。
运行结果如下图(数据保密,故作打码处理)
总结
ConvE通过卷积神经网络和非线性操作,有效增强了知识图谱中实体和关系嵌入的交互特性,提升了知识图谱补全任务的表现。相比于传统的线性模型,ConvE在捕捉复杂关系上的表现更优,但同时也需要较高的计算资源和超参数调优技巧。
代码购买链接:https://mbd.pub/o/bread/mbd-ZpuXkp9t