王克欣作者

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

本期推荐的论文笔记来自 PaperWeekly 社区用户 @marcw。为了解决目标输出的不规范性问题以及提供可解释的语义聚合过程,本文提出了一个神经语义解析器(neural semantic parser)。

本期推荐的论文笔记来自 PaperWeekly 社区用户 @marcw为了解决目标输出的不规范性问题以及提供可解释的语义聚合过程,本文提出了一个神经语义解析器(neural semantic parser)。 

关于作者:王克欣,中科院自动化研究所硕士生,研究方向为自然语言处理

■ 论文 | Learning Structured Natural Language Representations for Semantic Parsing

■ 链接 | https://www.paperweekly.site/papers/963

■ 源码 | https://github.com/cheng6076/scanner

任务介绍

本文介绍的任务,语义解析(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):

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

  • u 为项(包括终结符和非终结符)序列,为 NT,TER 动作的参数

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

对应这里:

交互调用:

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,这里得到的语法树为:

再根据语法树得到 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'。   

这里得到:

  • 动作序列a

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

  • 项序列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)的构建以及状态转移系统的转移过程。 

这里训练的网络结构为:

对每个训练实例,首先将 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:

再将 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):

或(对于动作 TER):

入栈,其中 '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,最后将以下三元组入栈。

#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 表示训练样本集。训练逻辑表达预测的目标函数在下文给出。

测试过程

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

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

给出了问题答案的标注,本文通过实体链接(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)的后验概率

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

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

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

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

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

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

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

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

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

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

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

以及:

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

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

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

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

实验

数据集

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

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

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

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

评价指标

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

实验结果

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:比较的是三个符号系统和一个神经系统基准

中间逻辑表示分析

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

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)


推荐、解读、讨论和报道人工智能前沿论文成果的学术平台。

入门神经语义解析器语义解析编码器-解码器模型
相关数据
注意力机制技术
Attention mechanism

我们可以粗略地把神经注意机制类比成一个可以专注于输入内容的某一子集(或特征)的神经网络. 注意力机制最早是由 DeepMind 为图像分类提出的,这让「神经网络在执行预测任务时可以更多关注输入中的相关部分,更少关注不相关的部分」。当解码器生成一个用于构成目标句子的词时,源句子中仅有少部分是相关的;因此,可以应用一个基于内容的注意力机制来根据源句子动态地生成一个(加权的)语境向量(context vector), 然后网络会根据这个语境向量而不是某个固定长度的向量来预测词。

神经网络技术
Neural Network

(人工)神经网络是一种起源于 20 世纪 50 年代的监督式机器学习模型,那时候研究者构想了「感知器(perceptron)」的想法。这一领域的研究者通常被称为「联结主义者(Connectionist)」,因为这种模型模拟了人脑的功能。神经网络模型通常是通过反向传播算法应用梯度下降训练的。目前神经网络有两大主要类型,它们都是前馈神经网络:卷积神经网络(CNN)和循环神经网络(RNN),其中 RNN 又包含长短期记忆(LSTM)、门控循环单元(GRU)等等。深度学习是一种主要应用于神经网络帮助其取得更好结果的技术。尽管神经网络主要用于监督学习,但也有一些为无监督学习设计的变体,比如自动编码器和生成对抗网络(GAN)。

语料库技术
Corpora

语料库一词在语言学上意指大量的文本,通常经过整理,具有既定格式与标记;事实上,语料库英文 "text corpus" 的涵意即为"body of text"。

前馈神经网络技术
Feed-forward neural network

前馈神经网络(FNN)是人工智能领域中最早发明的简单人工神经网络类型。在它内部,参数从输入层经过隐含层向输出层单向传播。与递归神经网络不同,在它内部不会构成有向环。FNN由一个输入层、一个(浅层网络)或多个(深层网络,因此叫作深度学习)隐藏层,和一个输出层构成。每个层(除输出层以外)与下一层连接。这种连接是 FNN 架构的关键,具有两个主要特征:加权平均值和激活函数。

特征分解技术
Eigen decomposition

线性代数中,特征分解(Eigendecomposition),又称谱分解(Spectral decomposition)是将矩阵分解为由其特征值和特征向量表示的矩阵之积的方法。需要注意只有对可对角化矩阵才可以施以特征分解。

特征工程技术
Feature engineering

特征工程是利用数据所在领域的相关知识来构建特征,使得机器学习算法发挥其最佳的过程。它是机器学习中的一个基本应用,实现难度大且代价高。采用自动特征工程方法可以省去采用人工特征工程的需求。Andrew Ng 说“挖掘特征是困难、费时且需要专业知识的事,应用机器学习其实基本上是在做特征工程。”

知识图谱技术
Knowledge graph

知识图谱本质上是语义网络,是一种基于图的数据结构,由节点(Point)和边(Edge)组成。在知识图谱里,每个节点表示现实世界中存在的“实体”,每条边为实体与实体之间的“关系”。知识图谱是关系的最有效的表示方式。通俗地讲,知识图谱就是把所有不同种类的信息(Heterogeneous Information)连接在一起而得到的一个关系网络。知识图谱提供了从“关系”的角度去分析问题的能力。 知识图谱这个概念最早由Google提出,主要是用来优化现有的搜索引擎。不同于基于关键词搜索的传统搜索引擎,知识图谱可用来更好地查询复杂的关联信息,从语义层面理解用户意图,改进搜索质量。比如在Google的搜索框里输入Bill Gates的时候,搜索结果页面的右侧还会出现Bill Gates相关的信息比如出生年月,家庭情况等等。

知识库技术
Knowledge base

知识库是用于知识管理的一种特殊的数据库,以便于有关领域知识的采集、整理以及提取。知识库中的知识源于领域专家,它是求解问题所需领域知识的集合,包括基本事实、规则和其它有关信息。

基准技术
baseline

一种简单的模型或启发法,用作比较模型效果时的参考点。基准有助于模型开发者针对特定问题量化最低预期效果。

模仿学习技术
Imitation learning

模仿学习(Imitation Learning)背后的原理是是通过隐含地给学习器关于这个世界的先验信息,就能执行、学习人类行为。在模仿学习任务中,智能体(agent)为了学习到策略从而尽可能像人类专家那样执行一种行为,它会寻找一种最佳的方式来使用由该专家示范的训练集(输入-输出对)。

逻辑技术
Logic

人工智能领域用逻辑来理解智能推理问题;它可以提供用于分析编程语言的技术,也可用作分析、表征知识或编程的工具。目前人们常用的逻辑分支有命题逻辑(Propositional Logic )以及一阶逻辑(FOL)等谓词逻辑。

映射技术
Mapping

映射指的是具有某种特殊结构的函数,或泛指类函数思想的范畴论中的态射。 逻辑和图论中也有一些不太常规的用法。其数学定义为:两个非空集合A与B间存在着对应关系f,而且对于A中的每一个元素x,B中总有有唯一的一个元素y与它对应,就这种对应为从A到B的映射,记作f:A→B。其中,y称为元素x在映射f下的象,记作:y=f(x)。x称为y关于映射f的原象*。*集合A中所有元素的象的集合称为映射f的值域,记作f(A)。同样的,在机器学习中,映射就是输入与输出之间的对应关系。

非终结符技术
non-terminal symbol

终结符和非终结符在计算机科学和语言学的领域是用来指定推导规则的元素。在某个形式语法之中,终结符和非终结符是两个不交的集合。

自然语言处理技术
Natural language processing

自然语言处理(英语:natural language processing,缩写作 NLP)是人工智能和语言学领域的分支学科。此领域探讨如何处理及运用自然语言;自然语言认知则是指让电脑“懂”人类的语言。自然语言生成系统把计算机数据转化为自然语言。自然语言理解系统把自然语言转化为计算机程序更易于处理的形式。

解析树技术
parse tree

解析树是一个内部结构,由编译器或解释器在解析一些语言结构时创建,解析也被称为“语法分析”。

先验知识技术
prior knowledge

先验(apriori ;也译作 先天)在拉丁文中指“来自先前的东西”,或稍稍引申指“在经验之前”。近代西方传统中,认为先验指无需经验或先于经验获得的知识。先验知识不依赖于经验,比如,数学式子2+2=4;恒真命题“所有的单身汉一定没有结婚”;以及来自纯粹理性的推断“本体论证明”

目标函数技术
Objective function

目标函数f(x)就是用设计变量来表示的所追求的目标形式,所以目标函数就是设计变量的函数,是一个标量。从工程意义讲,目标函数是系统的性能标准,比如,一个结构的最轻重量、最低造价、最合理形式;一件产品的最短生产时间、最小能量消耗;一个实验的最佳配方等等,建立目标函数的过程就是寻找设计变量与目标的关系的过程,目标函数和设计变量的关系可用曲线、曲面或超曲面表示。

感知器技术
perceptron

感知器是Frank Rosenblatt在1957年就职于Cornell航空实验室时所发明的一种人工神经网络。它可以被视为一种最简单形式的前馈神经网络,是一种二元线性分类器。 Frank Rosenblatt给出了相应的感知机学习算法,常用的有感知机学习、最小二乘法和梯度下降法。

后验概率技术
Posterior probability

在贝叶斯统计中,一个随机事件或者一个不确定事件的后验概率是在考虑和给出相关证据或数据后所得到的条件概率。同样,后验概率分布是一个未知量(视为随机变量)基于试验和调查后得到的概率分布。“后验”在本文中代表考虑了被测试事件的相关证据。

参数技术
parameter

在数学和统计学裡,参数(英语:parameter)是使用通用变量来建立函数和变量之间关系(当这种关系很难用方程来阐述时)的一个数量。

查询技术
Query

一般来说,查询是询问的一种形式。它在不同的学科里涵义有所不同。在信息检索领域,查询指的是数据库和信息系统对信息检索的精确要求

感知技术
perception

知觉或感知是外界刺激作用于感官时,脑对外界的整体的看法和理解,为我们对外界的感官信息进行组织和解释。在认知科学中,也可看作一组程序,包括获取信息、理解信息、筛选信息、组织信息。与感觉不同,知觉反映的是由对象的各样属性及关系构成的整体。

文本分类技术
text classification

该技术可被用于理解、组织和分类结构化或非结构化文本文档。文本挖掘所使用的模型有词袋(BOW)模型、语言模型(ngram)和主题模型。隐马尔可夫模型通常用于词性标注(POS)。其涵盖的主要任务有句法分析、情绪分析和垃圾信息检测。

权重技术
Weight

线性模型中特征的系数,或深度网络中的边。训练线性模型的目标是确定每个特征的理想权重。如果权重为 0,则相应的特征对模型来说没有任何贡献。

隐变量技术
Latent varibles

准确率技术
Accuracy

分类模型的正确预测所占的比例。在多类别分类中,准确率的定义为:正确的预测数/样本总数。 在二元分类中,准确率的定义为:(真正例数+真负例数)/样本总数

导数技术
derivatives