超详细解读:神经语义解析的结构化表示学习 | 附代码分析

简介:

任务介绍

本文介绍的任务,语义解析(Sematic Parsing),是将自然语言的句子转换为机器可解析的语义表达。具体而言,在这篇文章中,研究的问题为:

给定一个知识库(knowledge base)K,以及标注有对应的匹配知识库的语义表达(grounded meaning representation)G 或者问题答案(denotation)y的自然语言句子(问题)x,要学习得到一个语义解析器,使之可以完成经由一个中间未匹配知识库的语义表达 U,来将 x 映射到对应的 G 的功能。

相关工作

大多数现有工作将语义解析的处理方法分成两类,第一类是通过一个任务相关的文法将自然语言句子直接解析(parse)、匹配知识图谱(grounded to a knowledge base)转为语义表达。第二类方法是首先使用句法分析器将自然语言句子解析为任务无关的中间表达(intermediate representation),然后再将中间表达转换为匹配知识图谱的最终表达。

第一类方法

根据前人工作来看,目前使用编码器-解码器(encoder-decoder)模型处理语义解析的方法依然应当归为第一类方法。这种方法减少了领域相关假设、文法学习以及大量的特征工程工作的需要。

但是这种模型无法去解释语义聚合(meaning composition)是如何进行的,这也是该类模型极大灵活性的代价。而语义聚合的可解释性在处理边界(modeling limitation)问题上有着很大的作用。

另外,由于缺乏任务相关的先验知识的引入,学习任务关于可能推倒(derivation)的考虑上以及目标输出的不规范性(ill-formed,如解析结果少了或多了若干括号)问题上不能得到很好的限制。

第二类方法

第二类方法的一个优势在于产生了可重复利用的中间表达,这种中间表达由于其任务无关性可以一定程度上处理未出现的词(unseen words)以及不同领域的知识迁移(knowledge transfer)。

文章动机与主要贡献

为了解决目标输出的不规范性问题以及提供可解释的语义聚合过程,这篇文章提出了一个神经语义解析器(neural semantic parser)。

与其他方法相比,该模型没有使用外部的解析器(如 dependency parser)以及手工设计的 CCG 文法,而是采用了基于状态转移(transition-based)的方法生成谓词-参数(predicate-argument)形式的中间表达,由此避免了生成不规范形式的语义表达

另外与目前语义解析问题中大多数采用的 CKY 类似的自底向上(bottom-up)解析策略相比,本文提出的方法不需要对自然语言的句子结构的进行人为的特征分解(decomposition),可以利用大量的非局部特征。

基于假设匹配知识图谱后的语义表达与未匹配的语义表达同构(isomorphic)的假设,本文提出的状态转移模型的输出最终可以匹配到某一个知识图谱。

模型细节与代码

整个网络是在标注了对应的逻辑形式或者问题答案的自然语言句子集上进行端到端训练的。这篇文章附上的开源代码只给出了适用于标注逻辑形式的数据集的版本,下面将结合具体训练样本的训练过程以及代码来进行这部分的介绍。

适用标注逻辑形式的数据集的版本

这里的逻辑形式为 Funql,一个训练样本及其标注为:

how many states have a city called rochester

answer(count(state(loc(city(cityid(rochester,_)))))) (*)

下面结合对该样本的训练更新过程进行阐述。

训练过程

输入:

训练样本集:这里是用的语料库为 GEOQUERY,包含 880 个关于美国地理信息的问题和对应的逻辑查询语句);

通用谓词表:这些谓词与领域无关,例如 (∗) 中的 answer,count,state,loc,city,cityid,_ 等,详细的关于通用谓词表见附录-通用谓词)。

输出:训练好的语义分析器,能够将自然语言问题映射到相应的逻辑表达(Funql)。

过程:

1. 将问题的单词加入单词词典(这里和下文提到的词典都是为了之后神经网络的 softmax 输出对应输出字符串而建立的);

 

word_vocab.feed_all(sen)

2. 对逻辑表达进行解析得到语法树,可以从语法树中获得其中包含的非终结符终结符以及能够更直接指导状态转移系统训练的标注信息 U(ungrouned meaning representation)。 

其中 U=(a,u):

d47e62d2b349aca45e42305ed6714efbe5ed61d9 a 为状态转移系统的动作(action)序列,包括 NT,TER 和 ACT(对应原文的 RED),分别代表在栈中加入一个非终结符、在栈中加入一个终结符以及规约;
d47e62d2b349aca45e42305ed6714efbe5ed61d9 u 为项(包括终结符和非终结符)序列,为 NT,TER 动作的参数。

在对逻辑表达解析以及构造语法树时, 首先将逻辑表达转换为另一种方便处理形式:

e12ffb1cc9f74c16ae1ec56b1435ca0fc1f25bde

对应这里:

bef21a516a65f03dc34c29eaf529d170d28dcd4e

交互调用:


 

def parse_list(tokens) #对表达式深入分解(进到括号内),直到不能分解(没有括号)

与:

 

def parse_operands(tokens) #每个括号后紧跟的为运算符,也即动作,对这些动作进行记录


最后得到了语法树的嵌套 list 表示,这里为:

 

<type 'list'>: ['answer', ['count', ['state', ['loc', ['city',
['cityid', 'rochester,', '_']]]]]]

这两个函数的完整代码见附录-代码-parse_list 和附录-代码-parse_operands。

得到所有的终结符和非终结符,加入对应的词典:

 

nt_vocab.feed_all(nt)
ter_vocab.feed_all(ter)

最后参照语法树的嵌套 list 表示,构建语法树

 

def construct_from_list(self, content) #递归深度优先的自底向上构建树,将嵌套
list表示里的list对应子树根节点;list中从第二个元素开始算起,若为字符串则将其对应为
叶结点。

完整代码见附录-代码-construct_from_list,这里得到的语法树为:3b45f5f36af19bb23b0c9a5039dbe13e0bddb17f

再根据语法树得到 U。这部分可以自定向下(top-down)采用先序(pre-order)遍历(本文介绍的),也可以采用自底向上(bottom_up)遍历。

 

def top_down(self, identifier, general_predicates) #对语法树进行先序遍历,
遍历到叶节点时在项序列u里加入对应字符串,在动作序列a里加入'NT';遍历遇到子树根节点时
在项序列u里加入对应字符串content,如果该字符串为通用谓词则在动作序列a里加入'NT(content)',
否则加入'NT';遍历完一个子树进行回溯时在动作序列a里加入'ACT'。

这里得到:

d47e62d2b349aca45e42305ed6714efbe5ed61d9动作序列a

 

<type 'list'>: ['NT(answer)', 'NT(count)', 'NT', 'NT', 'NT',
'NT(cityid)', 'TER', 'TER', 'ACT', 'ACT', 'ACT', 'ACT', 'ACT',
'ACT']

d47e62d2b349aca45e42305ed6714efbe5ed61d9 项序列u
 

<type 'list'>: ['rochester,', '_', 'cityid', 'city', 'loc',
'state', 'count', 'answer']

注:这里的_或是可能出现的 all 也为通用谓词,但在状态转移系统里对应的动作都为 TER。完整代码见附录-代码-top_down。

3. 上一步得到的训练用的信息为:

 

word_tokens[fname].append(sen) #自然语言问题句子对应的单词列表
tree_tokens[fname].append(tree_token) #上一步得到的u
tran_actions[fname].append(action) #上一步得到的a


对应的:

 

word_tokens, tree_tokens, tran_actions #键为{'train','valid','test'}值为
训练用信息的dict


从这三个 dict 中按照序号各拿出一个同序号位置的元素即构成了一个训练用实例 (sen,u,a)。

4. 下面介绍计算图(computing graph)的构建以及状态转移系统的转移过程。

这里训练的网络结构为:

f6161f53f4226acb7d746cae3785fa854f20a1d5

对每个训练实例,首先将 sen 中的单词序列编码映射到一个双向 LSTM 的输出的隐层向量序列 buffer。

 

tok_embeddings =
[self.word_input_layer(self.word_lookup[self.word_vocab[word]]) for
word in words]

#get buffer representation with blstm
forward_sequence, backward_sequence =
self.blstm.predict(tok_embeddings)
buffer = [dy.concatenate([x, y]) for x, y in
zip(forward_sequence,reversed(backward_sequence))]
#输出为两个方向相反的LSTM的输出的拼接(concatenate)

接下来自顶向下的生成语义解析树。

生的方式为根据状态转移系统的 buffer,stack 以及 action 信息来预测下一个动作,并和标注的动作比较得到相应的对数概率加入到 loss 里。

然后根据 stack 的状态得到可行动作(valid action,来限制状态转移系统的动作),并根据当前标注动作分情况(NT,NT_general,TER,ACT)进行子操作,在每个子操作中根据 buffer 信息来预测动作的参数,并和标注的动作的参数来比较得到相应的对数概率加入到 loss 里。

下面分几部分阐述计算图构建以及状态转移过程:

  • 根据状态转移系统的 buffer,stack 以及 action 信息来预测下一个动作

使用 stack 的当前隐层输出 stack_embedding 以及 buffer 隐层向量序列作为输入根据注意力机制来计算的权重,将 buffer 赋权累加(这里有不同的赋权累加方式)得到 buffer 的嵌入表示 buffer_embedding。

再在 stack 中从后往前找到最后入栈的非终结符(对应子树根节点),记录其对应的词向量经线性层(linear layer)输出的嵌入表示 parent_embedding(还未经过隐层单元处理,下文称其为原始表达(raw representation))。

最后将三个嵌入表示拼接得到解析器状态的嵌入表示 parserstate:

9943c1d29dd85fcc3bf133ceb6ec214f26d62786

再将 parser_state 依次经由一层隐层的前馈神经网络、dropout 处理、线性层处理、softmax 处理得到可行动作的对数概率,并和标注的当前动作对比,将对数概率加入到 loss 中。

 

stack_embedding = stack[-1][0].output() # stack[-1] means the last one in the list 'stack'.
action_summary = action_top.output()
word_weights = self.attention(stack_embedding, buffer) # Get the attention weight list of the encoding words.
buffer_embedding, _ = attention_output(buffer, word_weights, 'soft_average')
# Get the attention-weighted word embedding vectors (a vector).

for i in range(len(stack)):
if stack[len(stack)-1-i][1] == 'p':
parent_embedding = stack[len(stack)-1-i][2]
# parent_embedding is just the embeding vector computed from
# the word vector into a linear layer Wx+b.
# Just the word vector without processing
# into the hidden state.
break
parser_state = dy.concatenate([buffer_embedding, stack_embedding, parent_embedding, action_summary])
h = self.mlp_layer(parser_state)

if options.dropout > 0:
h = dy.dropout(h, options.dropout)

if len(valid_actions) > 0:
log_probs = dy.log_softmax(self.act_proj_layer(h), valid_actions)
assert action in valid_actions, "action not in scope"
losses.append(-dy.pick(log_probs, action))
# Append the corresponding log probability of the action
# annotated by the training examples.

  • 根据 buffer 信息来预测动作的参数

对于动作 NT,TER,根据上一步预测动作中得到的注意力机制权重,对 buffer 进行加权累加,得到输出的特征,再经由线性层以及 softmax 处理得到对数概率,与相应标注的非终结符或终结符对比,将得到的对数概率加入 loss 中。

对于状态转移处理,将标注的非终结符或终结符对应的词向量经由线性层得到嵌入表示 nt_embedding(ter_embedding),作为 LSTM 的下一个隐层单元的输入,输入进去得到当前栈的状态 stack_state,将三元组(对于动作 NT):

a7ffa84c2a70093b1cd9e00286f568227b9be9e0

或(对于动作 TER):

e7dafb74a380a2d2fa86845e91323c8e43480a8e

入栈,其中 'p' 和 'c' 分别对应父节点和子节点。

对于动作 NT_general, 因为可以由 action 的内容得到具体的领域无关谓词的字符串,不作参数预测处理。

 

# If the aciton is NT, then caculate the log probability of
# the corresponding annotated term choices.
#generate non-terminal
nt = self.nt_vocab[oracle_tokens.pop(0)]
#no need to predict the ROOT (assumed ROOT is fixed)
if word_weights is not None:
train_selection = self.train_selection if np.random.randint(10)
> 5 else "soft_average"
output_feature, output_logprob = attention_output(buffer,
word_weights, train_selection)
log_probs_nt =
dy.log_softmax(self.nt_proj_layer(output_feature))
losses.append(-dy.pick(log_probs_nt, nt))
# Append the corresponding log probability of the NT
# annotated by the training examples.
if train_selection == "hard_sample":
baseline_feature, _ = attention_output(buffer,
word_weights, "soft_average")
log_probs_baseline =
dy.log_softmax(self.nt_proj_layer(baseline_feature))
r = dy.nobackprop(dy.pick(log_probs_nt, nt) -
dy.pick(log_probs_baseline, nt))
losses.append(-output_logprob * dy.rectify(r))
stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT',
stack_top)
nt_embedding = self.nt_input_layer(self.nt_lookup[nt])
stack_state = stack_state.add_input(nt_embedding)
stack.append((stack_state, 'p', nt_embedding)) # 'p' for parent.

  • 规约过程

规约时,先弹栈至刚将语义解析树的父节点对应的三元组弹出,记录每个三元组中的原始表达(也即词向量经一层线性层处理得到的向量)。

将弹出的得到的叶节点对应的原始表达求平均,再和父节点的原始表达拼接得到组合表达 composed_rep,再将组合表达作为 stack 的 LSTM 的下一个输入,构造新的隐层单元 stack_state,最后将以下三元组入栈。

714861b3ac0c13f5e5c89542862216d3032f8b67

 

#subtree completion
found_p = 0
path_input = []
#keep popping until the parent is found
while found_p != 1 :
top = stack.pop()
top_raw_rep, top_label, top_rep = top[ 2 ], top[ 1 ], top[ 0 ]
# Raw representation, label('c' or 'p') and representation
respectively.
path_input.append(top_raw_rep)
if top_label == 'p' :
found_p = 1
parent_rep = path_input.pop()
composed_rep =
self.subtree_input_layer(dy.concatenate([dy.average(path_input),
parent_rep]))
stack_state, _, _ = stack[ -1 ] if stack else (stack_top, 'ROOT' ,
stack_top)
stack_state = stack_state.add_input(composed_rep)
stack.append((stack_state, 'c' , composed_rep))
reduced = 1

这部分的完整代码见附录-代码-top_down_train。

5. 最后将 loss 反向传播可更新所有的参数。

这里先给出用于训练预测动作序列的目标函数,其中 T 表示训练样本集。训练逻辑表达预测的目标函数在下文给出。

de428f67e9bf729a189bb5a91725120ba7c06f71

测试过程

测试过程与训练过程相比,每次状态转移用到的动作以及动作的参数都是经过类似过程取对数概率最大对应的来预测得到的,而非采用标注信息。

根据问题答案标注获得标注逻辑表示

给出了问题答案的标注,本文通过实体链接(entity linking),将自然语言问题中的实体与知识库的实体进行匹配,可以得到一些候选子图,这些子图可以生成候选的逻辑表示,最终选择可以搜索的到正确答案的逻辑表示作为标注逻辑表示指导训练。

对于没有经过实体链接的训练集,本文使用 7 个手工模版,配合知识库,将训练集的实体标注出来,然后用这些实体去训练结构化感知机(structrued perceptron)来进行实体消歧。

中间逻辑表示与最终逻辑表示

对于有逻辑表示标注的训练样本,最终逻辑表示(也即匹配了知识库的逻辑表示)和中间逻辑表示是一样的;而适用于仅仅标注了答案的训练样本的版本的代码没有完全给出(一些重要的部分没有再代码里实现)。这里结合作者引用的另外一文章来讨论可能的具体实现方式。

Tao Lei, Regina Barzilay, and Tommi Jaakkola. 2016. Rationalizing neural predictions. In Proceedings of the 2016 Conference on Empirical Methods in Natural Language Processing. Austin, Texas, pages 107–117.

中间逻辑表示(ungrounded representation)和最终逻辑表示(grounded representation)之间的映射关系:作者给出了一个假设,假设两种同样语义的逻辑表示是同构的,也即两种表示的语法树相同,只是谓词或者终结符的字符串可能不同。所以在这种假设下,两者之间的转换仅仅变成了一个词汇映射的问题。

映射关系建模:使用双线性神经网络来刻画中间逻辑表示项 ut(ungrounded term)到最终逻辑表示项 gt(grounded term)的后验概率:

024edd1fe4fb7047bbd1f9aecb3f0ee0fb7f80ee

其中,640?tp=webp&wxfrom=5&wx_lazy=1为上文提到的以自然语言问题输入为输入的双向 LSTM 的关于某一个中间逻辑表示项的输出;640?tp=webp&wxfrom=5&wx_lazy=1为最终逻辑表示项的嵌入表示,Wug 为权值矩阵。

特定领域(closed domain)vs. 开放领域(open domain):对于特定领域,作者认为中间层的逻辑表示已经可为语义解析提供足够多的上下文信息,可以直接选择对应上述后验概率最大的最终表达作为输出(在特定领域内,一个中间表示项一般只匹配一个最终表示项);而对于开放领域,如Freebase,这种表示得建模显得有些乏力。

对于开放领域语义解析,本文设计训练了一个额外的排序模型,对可能的最终表示进行排序取序最靠前的解。这个排序模型使用最大熵分类器,以手工设计的关于两种逻辑表示之间的关系的信息表示为特征,优化以使得预测的最终逻辑表示在知识库上搜索的到的答案准确率最高。

目标函数:由于逻辑中间表示项是隐变量(latent variable),作者采用最大化逻辑最终表示项的期望对数似然 p(u|x)logp(g|u,x) 的方式来构建优化目标:

fae2883437e2ce0822434f1dd00d5ac540ed5632

结合上文提到的对动作预测的优化目标 La,可以得到最终的优化目标:

395fc9f862338c20c9ba3abe7559d87ce1250240

目标函数优化:作者采用了另一篇文献优化有隐变量的模型的算法。在这篇引用文章中,处理的问题是文本分类,和本文的模型结构类似,有两层结构(涉及到一个生成器和一个编码器,生成器生成中间变量),中间连接的变量是隐变量。

这篇引用文章的求解任务是想要对文本提取一些原文的片段(对应 gen(⋅),generate)作为梗概信息(rationales),使得这些梗概信息尽量短的前提下尽可能与原始文本作为输入进行文本分类(对应 enc(⋅),encode)的效果一致。这就涉及到了生成梗概信息的问题,而引用文章的作者又试图想把生成器和编码器一起训练(训练enc(gen(x)))。

引用文章的最终的目标函数为:

64a9ea258977cf599283298f7f32eeb3e8778f18

其中,z,x,y 分别表示梗概信息,原始输入以及标注结果;L 惩罚编码器和标注结果的距离,Ω 惩罚中间生成的梗概的长度。由于中间项在训练过程中不能被提供,所以作者试图最小化期望损失(相当于把 z 消掉):

f5e4ad342f73ec86c9643668e456de4037509e4d

其中 θe,θg 分别表示编码器和生成器的参数。作者假设用生成器采样中间项对于问题是可行的。借助恒等式:

d2aadc2839b9879d34bd15240260f19aef1f8ba5

可以求得上述期望损失对两个参数的导数:

badbaab4c73995d0c4d7230f8a6799d88bf31b35

以及:

38dbc07b511ba546895f7b758ed32f39e071d907

这样对 z 使用生成器进行采样,固定 z 就可以求得上述两式里边的微分,进而可以求得对期望损失的微分,最后使用梯度优化的算法来更新参数。

从两个微分等式可以看出,生成 z,可以利用编码器的预测结果指导更新生成器的参数;生成器被优化后,中间项的分布发生了改变,又会指导编码器的优化过程,类似于 EM 算法交互更新来学习隐变量的过程。

类比引用文章的求解方式,可以假定使用之前提到的双向 LSTM 来对逻辑中间表示项进行采样可以满足问题需求,则有:

b3e4331439aac495b0f79850882234bd72dc0b73

其中,BiLSTM(x) 为逻辑中间表示的生成器,θu 为生成器的参数。

实验

数据集

GEOQUERY:关于美国地理的 880 个问题以及知识库,并标注有逻辑表示。这些问题句子虽然组合性(compositional)强,但语言过于简单,词汇量过于小,大多数问题涉及实体不超过一个。

SPADES:包含 93,319 个从数据集 CLUEWEB09 通过随机去除实体获得的问题集合,仅仅标注有问题答案。虽然语料的组合性不强,但规模很大,并且每个句子包含 2 个或多个实体。

WEBQUESTIONS:包含 5810 个问题-答案对。同样组合性不强,但问题均来源于真实的网络数据。

GRAPHQUESTIONS:包含 5166 个问题-答案对,来源于将 500 个 Freebase 的图查询由亚马逊员工转换成的自然语言问题。

评价指标

本文使用准确率以及 F1 值作为评价指标,这里通过计算预测得到的语义表达与真实标注的一致比率来得到。

实验结果

63719adcf9ee3f6d2c1d14d6ca238a256b9d593d

Table 5:下边隔开的一块内的模型均为神经模型。Dong and Lapata (2016) 将语义解析问题视为序列预测问题,使用 LSTM 直接将自然语言句子映射到逻辑形式。

Table 6:这里之前的工作与本系统处理的总体思路类似,均是将自然语言句子映射到中间逻辑表示然后再匹配知识库。

Table 3:隔开的第一块内的模型均为符号系统(symbolic systems)模型。其中Bast and Haussmann (2015) 提出了一个相反的问答模型,不能产生语义表达。Berant and Liang (2015) 借鉴模仿学习(imitation learning)的思想,提出了一个复杂的基于智能体(agenda-based)的解析器。Reddy et al. (2016) 与本文概念上相似,也是通过语义中间表示来训练一个语义解析器,其中这个中间表示基于依存关系解析器的输出构建。

隔开的第二块内的模型为一些神经系统。Xu et al. (2016) 在这个数据集上到了目前最好的性能,他们的系统利用维基百科来过滤掉从 Freebase 抽取的错误候选答案。

Table 4:比较的是三个符号系统和一个神经系统基准。

中间逻辑表示分析

d708f421d6815cfb8670c941477f431f3224e91b

这里进行了一些额外的实验来检验逻辑中间表示的生成质量。

Table 7:作者手工标注了一些逻辑中间表示,以此来观察人的介入对逻辑中间表示的生成效果的影响。三行结果分别对应了不同的匹配标准。

Table 8:为了在其他 3 个数据集上也能够进行类似的实验,作者将评价方式换为对比生成的逻辑中间表示与句法分析器分析结果。作者用 EasyCCG 将自然语言问题句子转换为事件-参数(event-argument)的结构。分析看出对于组合性强的数据集 GRAPHQUESTIONS,本文的系统性能差一些。

Table 9:展示了一些谓词预测结果的事例,并与 EasyCCG 的结果相对比。对于例子:

ceo johnthain agreed to leave_.

本文的系统会难以区分控制(control)动词和真正的谓词。

附录

通用谓词

适用于 GEOQUERY 的通用谓词表如下:(还需要再进行扩展,才能够使用于通用领域)

 

coutryid
cityid
stateid
riverid
placeid
answer
largest
smallest
highest
lowest
longest
shortest
count
fewest
intersect
union
exclude
all
_

代码

parse_list

 

def parse_list(tokens):
# expect '(' always as first token for this function
if len(tokens) == 0 or tokens[0] != '(':
raise SyntaxError("(parse_list) Error: expected '(' token, found <%s>" % str(tokens))
first = tokens.pop(0) # consume the opening '('

# consume the operator and all operands
operator = tokens.pop(0) # operator always after opening ( syntatically
operands, tokens = parse_operands(tokens)
ast = [operator]
ast.extend(operands)

# consume the matching ')'
if len(tokens) == 0 or tokens[0] != ')':
raise SyntaxError("(parse_list) Error: expected ')' token, found <%s>: " % str(tokens))
first = tokens.pop(0)

return ast, tokens

parseo_perands

 

def parse_operands(tokens):
operands = []
while len(tokens) > 0:
# peek at next token, and if not an operand then stop
if tokens[0] == ')':
break

# if next token is a '(', need to get sublist/subexpression
if tokens[0] == '(':
subast, tokens = parse_list(tokens)
operands.append(subast)
continue # need to continue trying to see if more operands after the sublist

# otherwise token must be some sort of an operand
operand = tokens.pop(0) # consume the token and parse it

operands.append(decode_operand(operand))

return operands, tokens

construct_from_list

 

def construct_from_list(self, content):
'''build the tree from hierarchical list'''
if sexp.is_string(content):
identifier = self.node_count
new_node = Node(identifier, content)
self[identifier] = new_node
self.node_count += 1
return identifier
else:
identifier = self.node_count
new_node = Node(identifier, content[0])
self[identifier] = new_node
self.node_count += 1
for child in content[1:]:
child_identifier = self.construct_from_list(child)
self[identifier].add_child(child_identifier)
return identifier

top_down

 

def top_down(self, identifier, general_predicates):
'''
pre-order traversal, return a list of node content and a list of transition actions
ACT: reduce top elements on the stack into a subtree
NT: creates a subtree root node which is to be expanded, and push it to the stack
TER: generates terminal node under the current subtree
'''

data = []
action = []

def recurse(identifier):
node = self[identifier]
if len(node.children) == 0:
data.append(node.content)
action.append('TER')
else:
data.append(node.content)
if node.content in general_predicates:
action.append("{}({})".format('NT', node.content))
else:
action.append('NT')
for child in node.children:
recurse(child)
action.append('ACT')

recurse(identifier)
return data, action

top_down_train

 

def top_down_train(self, words, oracle_actions, oracle_tokens, options, buffer, stack_top, action_top):
stack = []
losses = []

reducable = 0
reduced = 0

#recursively generate the tree until training data is exhausted
while not (len(stack) == 1 and reduced != 0):
valid_actions = []
if len(stack) == 0:
valid_actions += [self._ROOT]
if len(stack) >= 1:
valid_actions += [self._TER, self._NT] + self._NT_general
if len(stack) >= 2 and reducable != 0:
valid_actions += [self._ACT]

action = self.act_vocab[oracle_actions.pop(0)] # Get the action identifier.

word_weights = None

#we make predictions when stack is not empty and _ACT is not the only valid action
if len(stack) > 0 and valid_actions[0] != self._ACT:
stack_embedding = stack[-1][0].output() # stack[-1] means the last one in the list 'stack'.
action_summary = action_top.output()
word_weights = self.attention(stack_embedding, buffer) # Get the attention weight list of the encoding words.
buffer_embedding, _ = attention_output(buffer, word_weights, 'soft_average')
# Get the attention-weighted word embedding vectors (a vector).

for i in range(len(stack)):
if stack[len(stack)-1-i][1] == 'p':
parent_embedding = stack[len(stack)-1-i][2]
# parent_embedding is just the embeding vector computed from
# the word vector into a linear layer Wx+b.
# Just the word vector without processing
# into the hidden state.
break
parser_state = dy.concatenate([buffer_embedding, stack_embedding, parent_embedding, action_summary])
h = self.mlp_layer(parser_state)

if options.dropout > 0:
h = dy.dropout(h, options.dropout)

if len(valid_actions) > 0:
log_probs = dy.log_softmax(self.act_proj_layer(h), valid_actions)
assert action in valid_actions, "action not in scope"
losses.append(-dy.pick(log_probs, action))
# Append the corresponding log probability of the action
# annotated by the training examples.

# Carrying on dealing with the action from the training example.
if action == self._NT:
# If the aciton is NT, then caculate the log probability of
# the corresponding annotated term choices.
#generate non-terminal
nt = self.nt_vocab[oracle_tokens.pop(0)]
#no need to predict the ROOT (assumed ROOT is fixed)
if word_weights is not None:
train_selection = self.train_selection if np.random.randint(10) > 5 else "soft_average"
output_feature, output_logprob = attention_output(buffer, word_weights, train_selection)
log_probs_nt = dy.log_softmax(self.nt_proj_layer(output_feature))
losses.append(-dy.pick(log_probs_nt, nt))
# Append the corresponding log probability of the NT
# annotated by the training examples.

if train_selection == "hard_sample":
baseline_feature, _ = attention_output(buffer, word_weights, "soft_average")
log_probs_baseline = dy.log_softmax(self.nt_proj_layer(baseline_feature))
r = dy.nobackprop(dy.pick(log_probs_nt, nt) - dy.pick(log_probs_baseline, nt))
losses.append(-output_logprob * dy.rectify(r))

stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
nt_embedding = self.nt_input_layer(self.nt_lookup[nt])
stack_state = stack_state.add_input(nt_embedding)
stack.append((stack_state, 'p', nt_embedding)) # 'p' for parent.

elif action in self._NT_general:
nt = self.nt_vocab[oracle_tokens.pop(0)]
stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
nt_embedding = self.nt_input_layer(self.nt_lookup[nt])
stack_state = stack_state.add_input(nt_embedding)
stack.append((stack_state, 'p', nt_embedding))

elif action == self._TER:
#generate terminal
ter = self.ter_vocab[oracle_tokens.pop(0)]
output_feature, output_logprob = attention_output(buffer, word_weights, self.train_selection)
log_probs_ter = dy.log_softmax(self.ter_proj_layer(output_feature))
losses.append(-dy.pick(log_probs_ter, ter))

if self.train_selection == "hard_sample":
baseline_feature, _ = attention_output(buffer, word_weights, "soft_average")
#baseline_feature, _ = attention_output(buffer, word_weights, self.train_selection, argmax=True)
log_probs_baseline = dy.log_softmax(self.ter_proj_layer(baseline_feature))
r = dy.nobackprop(dy.pick(log_probs_ter, ter) - dy.pick(log_probs_baseline, ter))
losses.append(-output_logprob * dy.rectify(r))

stack_state, label, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
ter_embedding = self.ter_input_layer(self.ter_lookup[ter])
stack_state = stack_state.add_input(ter_embedding)
stack.append((stack_state, 'c', ter_embedding)) # 'c' for child.

else:
# Into this branch means having met a reduce(ACT) action.
#subtree completion
found_p = 0
path_input = []
#keep popping until the parent is found
while found_p != 1:
top = stack.pop()
top_raw_rep, top_label, top_rep = top[2], top[1], top[0]
# Raw representation, label('c' or 'p') and representation respectively.
path_input.append(top_raw_rep)
if top_label == 'p':
found_p = 1
parent_rep = path_input.pop()
composed_rep = self.subtree_input_layer(dy.concatenate([dy.average(path_input), parent_rep]))

stack_state, _, _ = stack[-1] if stack else (stack_top, 'ROOT', stack_top)
stack_state = stack_state.add_input(composed_rep)
stack.append((stack_state, 'c', composed_rep))
reduced = 1

action_embedding = self.act_input_layer(self.act_lookup[action])
action_top = action_top.add_input(action_embedding)

reducable = 1

#cannot reduce after an NT
if stack[-1][1] == 'p':
reducable = 0

return dy.esum(losses)



原文发布时间为:2018-05-4
本文作者:王克欣
本文来自云栖社区合作伙伴“ PaperWeekly”,了解相关信息可以关注“ PaperWeekly”。
相关文章
|
1月前
|
存储 Java 编译器
java和c++的主要区别、各自的优缺点分析、java跨平台的原理的深度解析
java和c++的主要区别、各自的优缺点分析、java跨平台的原理的深度解析
75 0
|
3月前
|
SQL 存储 人工智能
探索语义解析技术和AI人工智能大模型的关系
探索语义解析技术和AI人工智能大模型的关系
72 1
|
3月前
|
关系型数据库 MySQL 索引
【MySQL 解析】Hash索引和B+树索引对比分析
【1月更文挑战第11天】【MySQL 解析】Hash索引和B+树索引对比分析
|
3月前
|
移动开发 JSON 监控
网络协议解析:在员工上网监控软件中实现HTTP流量分析
随着企业对员工网络活动的监控需求不断增加,开发一套能够实现HTTP流量分析的网络协议解析系统变得愈发重要。本文将深入探讨如何在员工上网监控软件中实现HTTP流量分析,通过代码示例演示关键步骤。
221 0
|
4月前
|
JSON JavaScript Go
Go 语言学习指南:变量、循环、函数、数据类型、Web 框架等全面解析
掌握 Go 语言的常见概念,如变量、循环、条件语句、函数、数据类型等等。深入了解 Go 基础知识的好起点是查阅 Go 官方文档
444 2
|
2月前
|
网络协议 物联网 5G
K3S 系列文章 -5G IoT 网关设备 POD 访问报错 DNS 'i/o timeout' 分析与解决
K3S 系列文章 -5G IoT 网关设备 POD 访问报错 DNS 'i/o timeout' 分析与解决
|
3月前
|
数据处理 Python
Python学习:迭代器与生成器的深入解析
Python学习:迭代器与生成器的深入解析
21 0
|
4月前
|
域名解析 缓存 网络协议
dpdk课程学习之练习笔记四(dns预备)
dpdk课程学习之练习笔记四(dns预备)
32 0
|
1月前
|
XML 存储 JavaScript
深入学习 XML 解析器及 DOM 操作技术
所有主要的浏览器都内置了一个XML解析器,用于访问和操作XML XML 解析器 在访问XML文档之前,必须将其加载到XML DOM对象中 所有现代浏览器都有一个内置的XML解析器,可以将文本转换为XML DOM对象
72 0
|
1月前
|
网络协议 安全 Linux
DNS 分析神器:dnsenum 保姆级教程(附链接)
DNS 分析神器:dnsenum 保姆级教程(附链接)
60 0

推荐镜像

更多