欢迎大家前往云加社区,获取更多腾讯海量技术实践干货哦~
译者:Waitingalone
本文翻译自Gaurav Bhatt在 http://deeplearn-ai.com 发表的NEURAL TENSOR NETWORK: EXPLORING RELATIONS AMONG TEXT ENTITIES。文中版权、图像代码等数据均归作者所有。为了本土化,翻译内容略作修改。
在这篇文章中,我将介绍神经张量网络(NTN),如在用神经张量网络推理知识库的推理中所描述的那样 。我的NTN实现使用最新版本的Python 2.7,Keras 2.0和Theano 0.9。
什么是知识库完成?
在知识库完成中,任务是确定两个实体对之间的关系。例如,考虑两个实体对 -<cat, tail> 和<supervised learning, machine learning>。如果我们被要求确定给定的两对之间的关系 - <cat,R,tail>和<supervised learning, R, machine learning> - 那么第一个关系可以最好的归结为有型,而第二个关系可以被归结为实例。所以,我们可以将这两个对重新定义为 <cat,has,tail>和<supervised learning,instance of,machine learning>。神经张量网络(NTN)在实体 - 关系对的数据库上训练,用于探究实体之间的附加关系。这是通过将数据库中的每个实体(即每个对象或个体)表示为一个向量来实现的。这些载体可以捕获有关该实体的事实,以及它是如何可能是某种关系的一部分。每个关系都是通过一个新的神经张量网络的参数来定义的,这个神经张量网络可以明确地涉及两个实体向量
使用NTN预测新的关系三元组。
关系推理的神经模型
能够认识到某些事实纯粹是由于其他现有的关系而存在的,是学习常识推理的模型的目标。NTN旨在发现实体<e1,e2>之间的关系,即对于<e1,e2>确定性地预测关系R. 例如,(e1,R,e2) = (Bengal tiger, has part, tail) 这个关系是否真实且具有确定性。神经张量网络(NTN)用一个双线性张量层代替一个标准的线性神经网络层,它直接关联了多个维度上的两个实体向量。该模型通过下列基于NTN的函数计算两个实体处于特定关系的可能性分数:
其中是标准非线性的单元应用,是张量,双线性张量积产生向量,其中每个条目张量的一个切片计算:。其它参数为关系R是一个神经网络的标准形式:和,
可视化神经张量层
NTN使用张量变量 对两个实体之间的关系进行乘法建模。如上所示,NTN是对简单神经层的扩展,增加了这些张量变量。所以,如果我们从上图中删除 ,最后,目标函数被定义为
这是一个简单的实体向量连接,以及偏向项。
培训目标
NTN采用对比式最大余量目标函数进行训练。给定训练样本中的三元组,则通过随机的将第二个实体替换为来创建负样本,其中j是随机索引。最后,目标函数被定义为
其中,是正则化参数。
实施细节
现在,我们看到了NTN的工作,是时候深入实施了。这里要考虑的重要一点是,每个给定的关系都有其自己的一组张量参数。让我简单介绍一下在Keras的帮助下我们需要做些什么。
每个关系都归因于一个单独的Keras模型,它也增加了张量参数。现在,假定张量层是在模型初始化和组合之间添加的。在后面的文章中,我将解释张量层的构造。从上图可以很容易得出结论,我们需要以某种方式处理训练数据,以便它可以同时传递到所有单独的模型。我们想要的只是更新与特定关系相对应的张量参数。然而,Keras 并没有让我们更新一个单独的模型,而剩下的。所以我们需要把数据分成不同的关系。每个训练样本将包含所有关系的一个实例,也就是每个关系的一对实体。
实施NTN层
让我们从实施神经张量层开始。这部分的先决条件是在Keras编写自定义图层。如果您不确定这意味着什么,那么请查看Keras文档的 编写你自己的keras图层。
我们首先用参数inp_size,out_size和activation来初始化NTN类。该inp_size是输入变量的形状,在我们的例子中的实体; 所述out_size是张量参数(K),和激活是要使用的激活函数(默认为tanh)。
from ntn_input import * from keras import activations class ntn_layer(Layer): def __init__(self, inp_size, out_size, activation='tanh', **kwargs): super(ntn_layer, self).__init__(**kwargs) self.k = out_size self.d = inp_size self.activation = activations.get(activation) self.test_out = 0
维的命名保持不变,即k对应于每个关系的张量参数个数,d是实体的形状。
现在,我们需要初始化张量图层参数。为了更好的理解我们在这里做什么,看一下张量网络的下图。
我们初始化四个张量参数,即W,V,b和U如下:
def build(self,input_shape): self.W = self.add_weight(name='w',shape=(self.d, self.d, self.k), initializer='glorot_uniform', trainable=True) self.V = self.add_weight(name='v', shape=(self.k, self.d*2), initializer='glorot_uniform', trainable=True) self.b = self.add_weight(name='b', shape=(self.k,), initializer='zeros', trainable=True) self.U = self.add_weight(name='u', shape=(self.k,), initializer='glorot_uniform',trainable=True) super(ntn_layer, self).build(input_shape)
在这里,我们用glorot_uniform 采样初始化参数 。在实践中,这种初始化比其他初始化导致更好的性能。add_weight 函数的另一个参数是可训练的,如果我们不想更新特定的可调参数,可以设置为false。例如,我们可以将W参数设置为不可训练的,如前所述,NTN模型将表现得像一个简单的神经网络。
一旦参数被初始化,那么是时候实现下面的等式了:
上面的等式给出了每个实体对的分数。正如你所看到的,我们必须迭代k个张量参数(张量模型的切片)。这是通过计算每个迭代的中间产品来完成的,最后,汇总所有这些产品。下面的代码片段为你做这个。请不要更改函数的名称,因为它们与Keras API一致。
def call(self ,x ,mask=None): e1=x[0] # 实体 1 e2=x[1] # 实体 2 batch_size = K.shape(e1)[0] V_out, h, mid_pro = [],[],[] for i in range(self.k): # 计算内部产品 V_out = K.dot(self.V[i],K.concatenate([e1,e2]).T) temp = K.dot(e1,self.W[:,:,i]) h = K.sum(temp*e2,axis=1) mid_pro.append(V_out+h+self.b[i]) tensor_bi_product = K.concatenate(mid_pro,axis=0) tensor_bi_product = self.U*self.activation(K.reshape(tensor_bi_product,(self.k,batch_size))).T self.test_out = K.shape(tensor_bi_product) return tensor_bi_product
最后,要完成NTN层的实现,我们必须添加以下功能。这与NTN无关; Keras使用以下函数进行内部处理。
def compute_output_shape(self, input_shape): return (input_shape[0][0],self.k)
我们已经建立了可以像Keras中的任何其他神经层一样调用的NTN层。让我们看看如何在真实的数据集上使用NTN层。
数据集
我将使用文中提到的Wordbase和Freebase数据集。我已经准备好了数据集(预处理的一部分从GitHub存储库中获取),并且可以进行如下处理。
import ntn_input data_name = 'wordbase' # 'wordbase' or 'freebase' data_path = 'data'+data_name raw_training_data = ntn_input.load_training_data(ntn_input.data_path) raw_dev_data = ntn_input.load_dev_data(ntn_input.data_path) entities_list = ntn_input.load_entities(ntn_input.data_path) relations_list = ntn_input.load_relations(ntn_input.data_path) indexed_training_data = data_to_indexed(raw_training_data, entities_list, relations_list) indexed_dev_data = data_to_indexed(raw_dev_data, entities_list, relations_list) (init_word_embeds, entity_to_wordvec) = ntn_input.load_init_embeds(ntn_input.data_path) num_entities = len(entities_list) num_relations = len(relations_list)
此时您可以打印并查看实体及其对应的关系。现在,我们需要根据关系来划分数据集,以便所有Keras模型都可以同时更新。我已经包括一个预处理功能,为您执行此步骤。此步骤中还添加了否定样本。负样本作为损坏的样本传递给prepare_data函数。如果corrupt_samples = 1,则对应于每个训练样本添加一个负样本。这意味着,整个训练数据集将会翻倍。
import ntn_input
e1,e2,labels_train,t1,t2,labels_dev,num_relations = prepare_data(corrupt_samples)
NTN的定义存储在一个名为ntn的文件中,很容易导入使用。
建立模型
为了训练模型,我们需要定义对比最大边缘损失函数。
def contrastive_loss(y_true, y_pred): margin = 1 return K.mean(y_true * K.square(y_pred) + (1 - y_true) * K.square(K.maximum(margin - y_pred, 0)))
我们应该可以从Keras编译函数中调用这个自定义的丢失函数。
from ntn import * def build_model(num_relations): Input_x, Input_y = [], [] for i in range(num_relations): Input_x.append(Input(shape=(dimx,))) Input_y.append(Input(shape=(dimy,))) ntn, score = [], [] # 存储单独的张量参数 for i in range(num_relations): # 通过每个切片迭代 'k' ntn.append(ntn_layer(inp_size=dimx, out_size=4)([Input_x[i],Input_y[i]])) score.append(Dense(1,activation='sigmoid')(ntn[i])) all_inputs = [Input_x[i]for i in range(num_relations)] all_inputs.extend([Input_y[i]for i in range(num_relations)]) # 聚合所有模型 model = Model(all_inputs,score) model.compile(loss=contrastive_loss,optimizer='adam') return model
最后,我们需要汇总数据以训练模型
e, t, labels_train, labels_dev = aggregate(e1, e2, labels_train, t1, t2, labels_dev, num_relations) model.fit(e, labels_train, nb_epoch=10, batch_size=100, verbose=2)
在这一点上,你可以看到模型开始训练,每个个体模型的损失逐渐减少。此外,为了计算NTN在知识库数据集上的准确性,我们需要计算所有关系的成本,并选择最大分数的成本。正如本文所述,所达到的准确度接近88%(平均)。
下一步是什么?
在这篇文章中,我们看到了建立知识库完成的神经张量网络。在下一篇文章中,我们将看到NTN如何用于解决其他NLP问题,例如基于非事实问题的回答。。
原文链接:http://deeplearn-ai.com/2017/11/21/neural-tensor-network-exploring-relations-among-text-entities/
原文作者:Gaurav Bhatt