本文同步发布知乎,知乎主页lynne阿黎请大家不吝关注~
ENT任务(Entity Typing):判断一个实体的类别,粒度可以分为person,location, organization, others。细粒度实体识别根据不同的数据有不同的分类情况,例如Figer dataset有112类。ENT任务对实体链接,关系抽取,对话问答等任务来说都是一个非常必要的先要任务。一般ENT任务都是建立在大量的数据集和对这些数据集进行学习的有监督训练上的。然而本文认为,这些任务都忽视了标签中蕴含文本含义,例如下文中上下文的内容和实体的标签有相关性,正确的标签可以保证文本语义的正确,而错误的标签则会让文本语义有一定误差。
因此本文提出了一种可以计算上下文和标签相似度的Entity Typing Language Model Enhancement模型。那么论文是如何将上下文和标签的相似度结合在一起呢?下面我们将为大家介绍一下模型的细节。
模型主要包含两个模块:实体标注模块(Entity Typing Module)和增强模块(Language Model Enhancement Module)。实体标注模块和我们平时接触的ENT任务相同,预测实体的标签,增强模块的输入也包括标签,同时反向传播的梯度也会传递给实体标注模块。在预测过程增强模块的输入不包含标签。
实体标注模块的输入是一个实体e和他所在的上下文s,其中其中 和别表示e左边和右边的词。该模块的输出是一个向量y表示实体在每个标签上的概率,模型采用交叉熵来计算loss。y的计算方式如下:
符号含义如下:
是sigmoid函数
是实体的向量,可由实体的词向量求和平均得到,这和我们计算句子的sentence embedding的处理非常相似
是上下文的向量,这里主要通过BiLSTM+Self Attention获得,通过BiLSTM模型可以学习到文本的上下文知识,通过Self Attention模型可以学习到文本中比较重要的知识。
文中成为特征,这是WSABIE算法中的概念,在论文Embedding Methods for Fine Grained Entity Type Classification对此有一些应用。简单来说就是将标签和文本映射到同一个向量空间。
WSABIE算法
对于 WSABIE 来说,打标签的过程,就是计算所有标签与当前对象的相似性,并取出相似性最高的标签作为结果。由于对象的 feature 和标签是两种不同的东西,为了计算相似性,WSABIE 将它们映射到 同一个向量空间
在得到这些特征之后我们将其拼接,然后使用sigmoid函数得到标签的概率分布。
文中虽然看起来简洁,但是对于没有相应知识的同学们来说还是有一丢迷惑,因此我们将模块梳理成下图,方便大家有一个大概的了解。
即使看完这部分我对feature的理解还是有一点困惑,这个指的是文本的feature还是label的feature,看样子像是线性映射,那么权重矩阵需要参与训练吗,他的shape是什么样子呢?带着这样的困惑,我去看了源代码。首先我们来看源码中数据生成模块:
def create_input_output(self,row):
s_start = row[0]
s_end = row[1]
e_start = row[2]
e_end = row[3]
labels = row[74:]
features = row[4:74]
seq_context = np.zeros((self.context_length*2 + 1,self.dim))
LM_context = np.zeros((self.context_length*2 + 1,self.dim)) # np.repeat
temp = [ self.id2vec[self.storage[i]] for i in range(e_start,e_end)]
mean_target = np.mean(temp,axis=0)
seq_context_id = [(self.vocab_size-1)
for _ in range(self.context_length*2+1)]
j = max(0,self.context_length - (e_start - s_start))
k = 0
# fix context_id here to combine two vocab_sizes
for i in range(max(s_start,e_start - self.context_length),e_start):
vec = self.id2vec[self.storage[i]]
seq_context[j,:] = vec
LM_context[k, :] = vec
seq_context_id[k] = min(self.storage[i], self.vocab_size-1)
j += 1
k += 1
seq_context[j,:] = np.ones_like(self.pad)
LM_context[k, :] = mean_target
seq_context_id[k] = min(self.storage[e_start], self.vocab_size-1)
mid = k
j += 1
k += 1
for i in range(e_end,min(e_end+self.context_length,s_end)):
vec = self.id2vec[self.storage[i]]
seq_context[j,:] = vec
LM_context[k, :] = vec
seq_context_id[k] = min(self.storage[i], self.vocab_size-1)
j += 1
k += 1
return (seq_context, mean_target, labels,
features, seq_context_id, LM_context, k, mid)
这里feature应该指的是文本,是一个length=70的向量,应该是输入文本的embedding id,通过embedding层进行转换。然后在通过Feature层转成Label一个空间维度的向量。那么feature和其他的特征如何处理呢,这里我们来看Entity Typing模块的代码:
def build_typing_part(self):
# Place Holders
self.keep_prob = tf.placeholder(tf.float32, name="keep_prob")
self.mention_representation = tf.placeholder(tf.float32,
[None,self.emb_dim], name="mention_repr")
self.context = [
tf.placeholder(tf.float32, [None, self.emb_dim], name="context"+str(i))
for i in range(self.context_length*2+1)]
self.target = tf.placeholder(tf.float32,
[None,self.target_dim], name="target")
### dropout and splitting context into left and right
self.mention_representation_dropout = tf.nn.dropout(self.mention_representation,self.keep_prob)
self.left_context = self.context[:self.context_length]
self.right_context = self.context[self.context_length+1:]
# Averaging Encoder
if self.encoder == "averaging":
self.left_context_representation = tf.add_n(self.left_context)
self.right_context_representation = tf.add_n(self.right_context)
self.context_representation = tf.concat(1,[self.left_context_representation,self.right_context_representation])
# LSTM Encoder
if self.encoder == "lstm":
self.left_lstm = tf.nn.rnn_cell.LSTMCell(self.lstm_dim,state_is_tuple=True)
self.right_lstm = tf.nn.rnn_cell.LSTMCell(self.lstm_dim,state_is_tuple=True)
with tf.variable_scope("rnn_left") as scope:
self.left_rnn,_ = tf.nn.rnn(self.left_lstm,self.left_context,dtype=tf.float32)
with tf.variable_scope("rnn_right") as scope:
self.right_rnn,_ = tf.nn.rnn(self.right_lstm,list(reversed(self.right_context)),dtype=tf.float32)
self.context_representation = tf.concat(1,[self.left_rnn[-1],self.right_rnn[-1]])
# Attentive Encoder
if self.encoder == "attentive":
self.left_lstm_F = tf.nn.rnn_cell.LSTMCell(self.lstm_dim,state_is_tuple=True)
self.right_lstm_F = tf.nn.rnn_cell.LSTMCell(self.lstm_dim,state_is_tuple=True)
self.left_lstm_B = tf.nn.rnn_cell.LSTMCell(self.lstm_dim,state_is_tuple=True)
self.right_lstm_B = tf.nn.rnn_cell.LSTMCell(self.lstm_dim,state_is_tuple=True)
with tf.variable_scope("rnn_left") as scope:
self.left_birnn,_,_ = tf.nn.bidirectional_rnn(self.left_lstm_F,self.left_lstm_B,self.left_context,dtype=tf.float32)
with tf.variable_scope("rnn_right") as scope:
self.right_birnn,_,_ = tf.nn.bidirectional_rnn(self.right_lstm_F,self.right_lstm_B,list(reversed(self.right_context)),dtype=tf.float32)
self.context_representation, self.attentions = attentive_sum(self.left_birnn + self.right_birnn, input_dim = self.lstm_dim * 2, hidden_dim = self.att_dim)
# Logistic Regression
if self.feature:
self.features = tf.placeholder(tf.int32,[None,self.feature_input_dim])
self.feature_embeddings = weight_variable('feat_embds', (self.feature_size, self.feature_dim), True)
self.feature_representation = tf.nn.dropout(tf.reduce_sum(tf.nn.embedding_lookup(self.feature_embeddings,self.features),1),self.keep_prob)
self.representation = tf.concat(1, [self.mention_representation_dropout, self.context_representation, self.feature_representation])
else:
self.representation = tf.concat(1, [self.mention_representation_dropout, self.context_representation])
if self.hier:
_d = "Wiki" if self.type == "figer" else "OntoNotes"
S = create_prior("./resource/"+_d+"/label2id_"+self.type+".txt")
assert(S.shape == (self.target_dim, self.target_dim))
self.S = tf.constant(S,dtype=tf.float32)
self.V = weight_variable('hier_V', (self.target_dim,self.rep_dim))
self.W = tf.transpose(tf.matmul(self.S,self.V))
self.logit = tf.matmul(self.representation, self.W)
else:
self.W = weight_variable('hier_W', (self.rep_dim,self.target_dim))
self.logit = tf.matmul(self.representation, self.W)
self.distribution = tf.nn.sigmoid(self.logit)
self.type_loss = tf.nn.sigmoid_cross_entropy_with_logits(self.logit, self.target)
这里主要分成4个模块:Averaging Encoder,LSTM Encoder,Attentive Encoder和Logistic Regression。Averaging Encoder对实体左右向量进行平均和拼接,LSTM Encoder和Attentive Encoder组成上下文的向量,Logistict Regression这里就是将输入文本转成和Label同一空间纬度的向量。
LM增强模块是一个预训练模块,预训练部分模型采用Language Model,文中使用LSTM。输入是训练数据,根据训练数据预测实体的标签。在训练过程中,文中先使用训练数据的文本对Language Model进行预训练,然后对标签转换成embedding,将标签替换实体进行训练。
这样表述大家可能还会有一点迷惑,我们可以看一下论文的实现,文中是先将label的输入转换成embedding,然后将他和上下文的向量拼接在一起,然后输入language model(LSTM),loss采用交叉熵。
# LM in typing
self.sp = tf.contrib.layers.safe_embedding_lookup_sparse(
[self.label_input], self.LM_sp_in, combiner="sum")
self.sp.set_shape([self.context_length*2+1, None, self.emb_dim])
self.LM_in = tf.pack(self.LM_context) + self.sp #!!!!!!!!!!!!!!
with tf.variable_scope("LM", reuse=True) as scope:
self.LM_out_, _ = tf.nn.dynamic_rnn(
self.LM, self.LM_in,
time_major=True, dtype=tf.float32)
self.LM_out_ = tf.nn.dropout(self.LM_out_, self.LM_keep_prob)
with tf.variable_scope("LM2", reuse=True) as scope:
self.LM_out, _ = tf.nn.dynamic_rnn(
self.LM2, self.LM_out_,
time_major=True, dtype=tf.float32)
self.LM_outs = tf.reshape(self.LM_out, [-1, self.emb_dim])
self.LM_logit = tf.matmul(self.LM_outs, self.LM_W) + self.LM_b
self.LM_logit = tf.reshape(self.LM_logit,
[-1, self.context_length*2+1, self.vocab_size])[:, :-1, :]
self.LM_loss = tf.nn.sparse_softmax_cross_entropy_with_logits(
self.LM_logit, self.context_id[:, 1:])
文中对对比了NFGEC(Neural Architectures for Fine-grained Entity Type Classification)的效果,经过了Language Model的增强,发现效果有了很大的提升。
下文是论文的地址和源码以及文中提到的两篇论文,之后我们也将阅读文中提到的相关论文,和大家分享。