神经张量网络:探索文本实体之间的关系

2017年11月21日

本文翻译自Gaurav Bhatthttp://deeplearn-ai.com 发表的NEURAL TENSOR NETWORK: EXPLORING RELATIONS AMONG TEXT ENTITIES。文中版权、图像代码等数据均归作者所有。为了本土化,翻译内容略作修改。

在这篇文章中,我将介绍神经张量网络(NTN),如在用神经张量网络推理知识库的推理中所描述的那样 。我的NTN实现使用最新版本的Python 2.7,Keras 2.0和Theano 0.9。

用代码直接跳到GitHub仓库

什么是知识库完成?

在知识库完成中,任务是确定两个实体对之间的关系。例如,考虑两个实体对 -<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_sizeout_sizeactivation来初始化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是实体的形状。

现在,我们需要初始化张量图层参数。为了更好的理解我们在这里做什么,看一下张量网络的下图。

我们初始化四个张量参数,即WVbU如下:

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏聊聊技术

原 初学图论-Kahn拓扑排序算法(Kah

3018
来自专栏开发与安全

算法:AOV网(Activity on Vextex Network)与拓扑排序

在一个表示工程的有向图中,用顶点表示活动,用弧表示活动之间的优先关系,这样的有向图为顶点表示活动的网,我们称之为AOV网(Activity on Vextex ...

4087
来自专栏ml

朴素贝叶斯分类器(离散型)算法实现(一)

1. 贝叶斯定理:        (1)   P(A^B) = P(A|B)P(B) = P(B|A)P(A)   由(1)得    P(A|B) = P(B|...

3667
来自专栏刘君君

JDK8的HashMap源码学习笔记

3318
来自专栏聊聊技术

原 数据结构-红黑树(Red-Black

3509
来自专栏xingoo, 一个梦想做发明家的程序员

20120918-向量实现《数据结构与算法分析》

#include <iostream> #include <list> #include <string> #include <vector> #include...

1906
来自专栏学海无涯

Android开发之奇怪的Fragment

说起Android中的Fragment,在使用的时候稍加注意,就会发现存在以下两种: v4包中的兼容Fragment,android.support.v4.ap...

3225
来自专栏MelonTeam专栏

ArrayList源码完全分析

导语: 这里分析的ArrayList是使用的JDK1.8里面的类,AndroidSDK里面的ArrayList基本和这个一样。 分析的方式是逐个API进行解析 ...

4809
来自专栏Phoenix的Android之旅

Java 集合 Vector

List有三种实现,ArrayList, LinkedList, Vector, 它们的区别在于, ArrayList是非线程安全的, Vector则是线程安全...

692
来自专栏拭心的安卓进阶之路

Java 集合深入理解(12):古老的 Vector

今天刮台风,躲屋里看看 Vector ! 都说 Vector 是线程安全的 ArrayList,今天来根据源码看看是不是这么相...

2547

扫码关注云+社区