1 介绍
本篇论文以卷积的方式对知识图谱进行补全,其方法采用CNN的形式进行知识图谱补全,将三元组统一处理,进行评估,评估出得分最高的一组,该方法在论文ConvE的基础上进行改进,其在逻辑实现的过程中十分简单,通过相应的模型图即可知道其原理。
2 模型
2.1 模型图
2.2 模型解释
- 将三元组进行embedding,将 ( h , r , t ) (h, r, t) (h,r,t)三元组转化为 ( v h , v r , v t ) \left(\boldsymbol{v}_{h}, \boldsymbol{v}_{r}, \boldsymbol{v}_{t}\right) (vh,vr,vt)三元组的向量形式, 向量的维度为K维。
- 对三元组进行拼接,形成一个matrix, A = [ v h , v r , v t ] ∈ R k × 3 \boldsymbol{A}=\left[\boldsymbol{v}_{h}, \boldsymbol{v}_{r}, \boldsymbol{v}_{t}\right] \in \mathbb{R}^{k \times 3} A=[vh,vr,vt]∈Rk×3。
- 对matrix进行卷积, ω ∈ R 1 × 3 \omega \in \mathbb{R}^{1\times3} ω∈R1×3作为卷积核
- 对卷积产生的向量进行concat,对拼接的向量进行全链接,判断正确与否
- 其公式如下
2.3 损失函数
softplus函数:
S
o
f
t
p
l
u
s
(
x
)
=
l
o
g
(
1
+
e
x
)
Softplus(x)=log(1+e^x)
Softplus(x)=log(1+ex),图像参考:机器学习中的数学——激活函数(十):Softplus函数, 如图:
对于损失函数的解释,在训练时,对数据正负例进行取反操作,使正例损失函数值趋近于0,负例的损失函数不断减少。
3 核心代码
class ConvKB(nn.Module):
def __init__(self, config):
super(ConvKB,self).__init__()
self.config = config
self.device = device
#进行实体和关系embedding
self.ent_embeddings = nn.Embedding(config.ent_num, config.dim)
self.rel_embeddings = nn.Embedding(config.rel_num, config.dim)
self.bn_1 = nn.BatchNorm2d(1)
self.conv = nn.Conv2d(1, config.out_channels, (1, 3))#采用1x3的卷积核
self.bn_2 = nn.BatchNorm2d(self.config.out_channels)
self.dropout1 = nn.Dropout(0.5)
self.non_linearity = nn.ReLU()
#全连接层,将dim*out_channel数据进行线性变换
self.fc_layer1 = nn.Linear((config.dim)*config.out_channels,1)
#定义损失函数
self.criterion = nn.Softplus()
#加载embedding的参数,如果没有则采用nn.init.xavier_uniform_方式进行初始化
def init(self, ent2vec, rel2vec):
self.ent_embeddings.weight.data.copy_(torch.from_numpy(ent2vec))
self.rel_embeddings.weight.data.copy_(torch.from_numpy(rel2vec))
nn.init.xavier_uniform_(self.fc_layer1.weight.data)
#nn.init.xavier_uniform_(self.fc_layer2.weight.data)
nn.init.xavier_uniform_(self.conv.weight.data)
def _calc(self, h, r, t):
h = h.unsqueeze(1)#batch*1*dim
r = r.unsqueeze(1)
t = t.unsqueeze(1)
conv_input = torch.cat([h, r, t], dim = 1)# batch*3*dim
#print(conv_input.shape)
conv_input = conv_input.transpose(1, 2)#batch*dim*3
conv_input = conv_input.unsqueeze(1)#batch*1*dim*3,增加维度1是因为CNN需要通道为1
out_conv = self.bn_1(conv_input)
out_conv = self.conv(out_conv)
out_conv = self.bn_2(out_conv)
out_conv = self.non_linearity(out_conv)
out_conv = out_conv.view(-1,(self.config.dim)*self.config.out_channels)
input_fc = self.dropout1(out_conv)
score = self.fc_layer1(input_fc)
score = score.squeeze(1)#注意这里,在调试时没有注意,导致训练出错,难以检测出来
return score
def loss(self, score, regul, labels):
#损失函数,取反,使正列趋近于0,负例不断优化,逐渐变小
return torch.mean(self.criterion(-1*score*labels))+self.config.lambd*regul
def forward(self, h, r, t, labels):
h_embed = self.ent_embeddings(h)
r_embed = self.rel_embeddings(r)
t_embed = self.ent_embeddings(t)
score = self._calc(h_embed, r_embed, t_embed)
#正则化
l2_reg = torch.mean(h_embed**2) + torch.mean(r_embed**2) + torch.mean(t_embed**2)
for W in self.conv.parameters():
l2_reg += W.norm(2)
for W in self.fc_layer1.parameters():
l2_reg += W.norm(2)
return self.loss(score,l2_reg, labels)
#预测函数,与_calc函数类似
def predict(self, h, r, t):
h_embed = self.ent_embeddings(h)
r_embed = self.rel_embeddings(r)
t_embed = self.ent_embeddings(t)
score = self._calc(h_embed, r_embed, t_embed)
return score.cpu().data.numpy()