语义角色标注
来源:互联网 发布:嫁コレ数据提取资料 编辑:程序博客网 时间:2024/05/21 10:00
说明
教程源代码目录在book/label_semantic_roles, 初次使用请参考PaddlePaddle安装教程,更多内容请参考本教程的视频课堂。
背景介绍
自然语言分析技术大致分为三个层面:词法分析、句法分析和语义分析。语义角色标注是实现浅层语义分析的一种方式。在一个句子中,谓词是对主语的陈述或说明,指出“做什么”、“是什么”或“怎么样,代表了一个事件的核心,跟谓词搭配的名词称为论元。语义角色是指论元在动词所指事件中担任的角色。主要有:施事者(Agent)、受事者(Patient)、客体(Theme)、经验者(Experiencer)、受益者(Beneficiary)、工具(Instrument)、处所(Location)、目标(Goal)和来源(Source)等。
请看下面的例子,“遇到” 是谓词(Predicate,通常简写为“Pred”),“小明”是施事者(Agent),“小红”是受事者(Patient),“昨天” 是事件发生的时间(Time),“公园”是事情发生的地点(Location)。
图1. 依存句法分析句法树示例
然而,完全句法分析需要确定句子所包含的全部句法信息,并确定句子各成分之间的关系,是一个非常困难的任务,目前技术下的句法分析准确率并不高,句法分析的细微错误都会导致SRL的错误。为了降低问题的复杂度,同时获得一定的句法结构信息,“浅层句法分析”的思想应运而生。浅层句法分析也称为部分句法分析(partial parsing)或语块划分(chunking)。和完全句法分析得到一颗完整的句法树不同,浅层句法分析只需要识别句子中某些结构相对简单的独立成分,例如:动词短语,这些被识别出来的结构称为语块。为了回避 “无法获得准确率较高的句法树” 所带来的困难,一些研究[1]也提出了基于语块(chunk)的SRL方法。
基于语块的SRL方法将SRL作为一个序列标注问题来解决。序列标注任务一般都会采用BIO表示方式来定义序列标注的标签集,我们先来介绍这种表示方法。在BIO表示法中,B代表语块的开始,I代表语块的中间,O代表语块结束。通过B、I、O 三种标记将不同的语块赋予不同的标签,例如:对于一个角色为A的论元,将它所包含的第一个语块赋予标签B-A,将它所包含的其它语块赋予标签I-A,不属于任何论元的语块赋予标签O。
我们继续以上面的这句话为例,图1展示了BIO表示方法。
图2. BIO标注方法示例
从上面的例子可以看到,根据序列标注结果可以直接得到论元的语义角色标注结果,是一个相对简单的过程。这种简单性体现在:(1)依赖浅层句法分析,降低了句法分析的要求和难度;(2)没有了候选论元剪除这一步骤;(3)论元的识别和论元标注是同时实现的。这种一体化处理论元识别和论元标注的方法,简化了流程,降低了错误累积的风险,往往能够取得更好的结果。
与基于语块的SRL方法类似,在本教程中我们也将SRL看作一个序列标注问题,不同的是,我们只依赖输入文本序列,不依赖任何额外的语法解析结果或是复杂的人造特征,利用深度神经网络构建一个端到端学习的SRL系统。我们以CoNLL-2004 and CoNLL-2005 Shared Tasks任务中SRL任务的公开数据集为例,实践下面的任务:给定一句话和这句话里的一个谓词,通过序列标注的方式,从句子中找到谓词对应的论元,同时标注它们的语义角色。
模型概览
循环神经网络(Recurrent Neural Network)是一种对序列建模的重要模型,在自然语言处理任务中有着广泛地应用。不同于前馈神经网络(Feed-forward Neural Network),RNN能够处理输入之间前后关联的问题。LSTM是RNN的一种重要变种,常用来学习长序列中蕴含的长程依赖关系,我们在情感分析一篇中已经介绍过,这一篇中我们依然利用LSTM来解决SRL问题。
栈式循环神经网络(Stacked Recurrent Neural Network)
深层网络有助于形成层次化特征,网络上层在下层已经学习到的初级特征基础上,形成更复杂的高级特征。尽管LSTM沿时间轴展开后等价于一个非常“深”的前馈网络,但由于LSTM各个时间步参数共享,
然而,训练一个深层LSTM网络并非易事。纵向堆叠多个LSTM单元可能遇到梯度在纵向深度上传播受阻的问题。通常,堆叠4层LSTM单元可以正常训练,当层数达到4~8层时,会出现性能衰减,这时必须考虑一些新的结构以保证梯度纵向顺畅传播,这是训练深层LSTM网络必须解决的问题。我们可以借鉴LSTM解决 “梯度消失梯度爆炸” 问题的智慧之一:在记忆单元(Memory Cell)这条信息传播的路线上没有非线性映射,当梯度反向传播时既不会衰减、也不会爆炸。因此,深层LSTM模型也可以在纵向上添加一条保证梯度顺畅传播的路径。
一个LSTM单元完成的运算可以被分为三部分:(1)输入到隐层的映射(input-to-hidden) :每个时间步输入信息
图3是最终得到的栈式循环神经网络结构示意图。
图3. 基于LSTM的栈式循环神经网络结构示意图
双向循环神经网络(Bidirectional Recurrent Neural Network)
在LSTM中,
为了克服这一缺陷,我们可以设计一种双向循环网络单元,它的思想简单且直接:对上一节的栈式循环神经网络进行一个小小的修改,堆叠多个LSTM单元,让每一层LSTM单元分别以:正向、反向、正向 …… 的顺序学习上一层的输出序列。于是,从第2层开始,
图4. 基于LSTM的双向循环神经网络结构示意图
需要说明的是,这种双向RNN结构和Bengio等人在机器翻译任务中使用的双向RNN结构[3, 4] 并不相同,我们会在后续机器翻译任务中,介绍另一种双向循环神经网络。
条件随机场 (Conditional Random Field)
使用神经网络模型解决问题的思路通常是:前层网络学习输入的特征表示,网络的最后一层在特征基础上完成最终的任务。在SRL任务中,深层LSTM网络学习输入的特征表示,条件随机场(Conditional Random Filed, CRF)在特征的基础上完成序列标注,处于整个网络的末端。
CRF是一种概率化结构模型,可以看作是一个概率无向图模型,结点表示随机变量,边表示随机变量之间的概率依赖关系。简单来讲,CRF学习条件概率
序列标注任务只需要考虑输入和输出都是一个线性序列,并且由于我们只是将输入序列作为条件,不做任何条件独立假设,因此输入序列的元素之间并不存在图结构。综上,在序列标注任务中使用的是如图5所示的定义在链式图上的CRF,称之为线性链条件随机场(Linear Chain Conditional Random Field)。
图5. 序列标注任务中使用的线性链条件随机场
根据线性链条件随机场上的因子分解定理[5],在给定观测序列
其中
这个优化目标可以通过反向传播算法和整个神经网络一起求解。解码时,对于给定的输入序列
深度双向LSTM(DB-LSTM)SRL模型
在SRL任务中,输入是 “谓词” 和 “一句话”,目标是从这句话中找到谓词的论元,并标注论元的语义角色。如果一个句子含有
- 构造输入;
- 输入1是谓词,输入2是句子
- 将输入1扩展成和输入2一样长的序列,用one-hot方式表示;
- one-hot方式的谓词序列和句子序列通过词表,转换为实向量表示的词向量序列;
- 将步骤2中的2个词向量序列作为双向LSTM的输入,学习输入序列的特征表示;
- CRF以步骤3中模型学习到的特征为输入,以标记序列为监督信号,实现序列标注;
大家可以尝试上面这种方法。这里,我们提出一些改进,引入两个简单但对提高系统性能非常有效的特征:
- 谓词上下文:上面的方法中,只用到了谓词的词向量表达谓词相关的所有信息,这种方法始终是非常弱的,特别是如果谓词在句子中出现多次,有可能引起一定的歧义。从经验出发,谓词前后若干个词的一个小片段,能够提供更丰富的信息,帮助消解歧义。于是,我们把这样的经验也添加到模型中,为每个谓词同时抽取一个“谓词上下文” 片段,也就是从这个谓词前后各取
n 个词构成的一个窗口片段; - 谓词上下文区域标记:为句子中的每一个词引入一个0-1二值变量,表示它们是否在“谓词上下文”片段中;
修改后的模型如下(图6是一个深度为4的模型结构示意图):
[图6. SRL任务上的深层双向LSTM模型]
- 构造输入
- 输入1是句子序列,输入2是谓词序列,输入3是谓词上下文,从句子中抽取这个谓词前后各
n 个词,构成谓词上下文,用one-hot方式表示,输入4是谓词上下文区域标记,标记了句子中每一个词是否在谓词上下文中; - 将输入2~3均扩展为和输入1一样长的序列;
- 输入1是句子序列,输入2是谓词序列,输入3是谓词上下文,从句子中抽取这个谓词前后各
- 输入1~4均通过词表取词向量转换为实向量表示的词向量序列;其中输入1、3共享同一个词表,输入2和4各自独有词表;
- 第2步的4个词向量序列作为双向LSTM模型的输入;LSTM模型学习输入序列的特征表示,得到新的特性表示序列;
- CRF以第3步中LSTM学习到的特征为输入,以标记序列为监督信号,完成序列标注;
总结
语义角色标注是许多自然语言理解任务的重要中间步骤。这篇教程中我们以语义角色标注任务为例,介绍如何利用PaddlePaddle进行序列标注任务。教程中所介绍的模型来自我们发表的论文[10]。由于 CoNLL 2005 SRL任务的训练数据目前并非完全开放,教程中只使用测试数据作为示例。在这个过程中,我们希望减少对其它自然语言处理工具的依赖,利用神经网络数据驱动、端到端学习的能力,得到一个和传统方法可比、甚至更好的模型。在论文中我们证实了这种可能性。关于模型更多的信息和讨论可以在论文中找到。
参考文献
- Sun W, Sui Z, Wang M, et al. Chinese semantic role labeling with shallow parsing[C]//Proceedings of the 2009 Conference on Empirical Methods in Natural Language Processing: Volume 3-Volume 3. Association for Computational Linguistics, 2009: 1475-1483.
- Pascanu R, Gulcehre C, Cho K, et al. How to construct deep recurrent neural networks[J]. arXiv preprint arXiv:1312.6026, 2013.
- Cho K, Van Merriënboer B, Gulcehre C, et al. Learning phrase representations using RNN encoder-decoder for statistical machine translation[J]. arXiv preprint arXiv:1406.1078, 2014.
- Bahdanau D, Cho K, Bengio Y. Neural machine translation by jointly learning to align and translate[J]. arXiv preprint arXiv:1409.0473, 2014.
- Lafferty J, McCallum A, Pereira F. Conditional random fields: Probabilistic models for segmenting and labeling sequence data[C]//Proceedings of the eighteenth international conference on machine learning, ICML. 2001, 1: 282-289.
- 李航. 统计学习方法[J]. 清华大学出版社, 北京, 2012.
- Marcus M P, Marcinkiewicz M A, Santorini B. Building a large annotated corpus of English: The Penn Treebank[J]. Computational linguistics, 1993, 19(2): 313-330.
- Palmer M, Gildea D, Kingsbury P. The proposition bank: An annotated corpus of semantic roles[J]. Computational linguistics, 2005, 31(1): 71-106.
- Carreras X, Màrquez L. Introduction to the CoNLL-2005 shared task: Semantic role labeling[C]//Proceedings of the Ninth Conference on Computational Natural Language Learning. Association for Computational Linguistics, 2005: 152-164.
- Zhou J, Xu W. End-to-end learning of semantic role labeling using recurrent neural networks[C]//Proceedings of the Annual Meeting of the Association for Computational Linguistics. 2015.
Paddle代码
训练
import mathimport numpy as npimport paddle.v2 as paddleimport paddle.v2.dataset.conll05 as conll05import paddle.v2.evaluator as evaluatorword_dict, verb_dict, label_dict = conll05.get_dict()word_dict_len = len(word_dict)label_dict_len = len(label_dict)pred_len = len(verb_dict)mark_dict_len = 2word_dim = 32mark_dim = 5hidden_dim = 512depth = 8default_std = 1 / math.sqrt(hidden_dim) / 3.0mix_hidden_lr = 1e-3def d_type(size): return paddle.data_type.integer_value_sequence(size)def db_lstm(): #8 features word = paddle.layer.data(name='word_data', type=d_type(word_dict_len)) predicate = paddle.layer.data(name='verb_data', type=d_type(pred_len)) ctx_n2 = paddle.layer.data(name='ctx_n2_data', type=d_type(word_dict_len)) ctx_n1 = paddle.layer.data(name='ctx_n1_data', type=d_type(word_dict_len)) ctx_0 = paddle.layer.data(name='ctx_0_data', type=d_type(word_dict_len)) ctx_p1 = paddle.layer.data(name='ctx_p1_data', type=d_type(word_dict_len)) ctx_p2 = paddle.layer.data(name='ctx_p2_data', type=d_type(word_dict_len)) mark = paddle.layer.data(name='mark_data', type=d_type(mark_dict_len)) emb_para = paddle.attr.Param(name='emb', initial_std=0., is_static=True) std_0 = paddle.attr.Param(initial_std=0.) std_default = paddle.attr.Param(initial_std=default_std) predicate_embedding = paddle.layer.embedding( size=word_dim, input=predicate, param_attr=paddle.attr.Param(name='vemb', initial_std=default_std)) mark_embedding = paddle.layer.embedding( size=mark_dim, input=mark, param_attr=std_0) word_input = [word, ctx_n2, ctx_n1, ctx_0, ctx_p1, ctx_p2] emb_layers = [ paddle.layer.embedding(size=word_dim, input=x, param_attr=emb_para) for x in word_input ] emb_layers.append(predicate_embedding) emb_layers.append(mark_embedding) hidden_0 = paddle.layer.mixed( size=hidden_dim, bias_attr=std_default, input=[ paddle.layer.full_matrix_projection( input=emb, param_attr=std_default) for emb in emb_layers ]) lstm_para_attr = paddle.attr.Param(initial_std=0.0, learning_rate=1.0) hidden_para_attr = paddle.attr.Param( initial_std=default_std, learning_rate=mix_hidden_lr) lstm_0 = paddle.layer.lstmemory( input=hidden_0, act=paddle.activation.Relu(), gate_act=paddle.activation.Sigmoid(), state_act=paddle.activation.Sigmoid(), bias_attr=std_0, param_attr=lstm_para_attr) #stack L-LSTM and R-LSTM with direct edges input_tmp = [hidden_0, lstm_0] for i in range(1, depth): mix_hidden = paddle.layer.mixed( size=hidden_dim, bias_attr=std_default, input=[ paddle.layer.full_matrix_projection( input=input_tmp[0], param_attr=hidden_para_attr), paddle.layer.full_matrix_projection( input=input_tmp[1], param_attr=lstm_para_attr) ]) lstm = paddle.layer.lstmemory( input=mix_hidden, act=paddle.activation.Relu(), gate_act=paddle.activation.Sigmoid(), state_act=paddle.activation.Sigmoid(), reverse=((i % 2) == 1), bias_attr=std_0, param_attr=lstm_para_attr) input_tmp = [mix_hidden, lstm] feature_out = paddle.layer.mixed( size=label_dict_len, bias_attr=std_default, input=[ paddle.layer.full_matrix_projection( input=input_tmp[0], param_attr=hidden_para_attr), paddle.layer.full_matrix_projection( input=input_tmp[1], param_attr=lstm_para_attr) ], ) return feature_outdef load_parameter(file_name, h, w): with open(file_name, 'rb') as f: f.read(16) # skip header. return np.fromfile(f, dtype=np.float32).reshape(h, w)def main(): paddle.init(use_gpu=False, trainer_count=1) # define network topology feature_out = db_lstm() target = paddle.layer.data(name='target', type=d_type(label_dict_len)) crf_cost = paddle.layer.crf( size=label_dict_len, input=feature_out, label=target, param_attr=paddle.attr.Param( name='crfw', initial_std=default_std, learning_rate=mix_hidden_lr)) crf_dec = paddle.layer.crf_decoding( size=label_dict_len, input=feature_out, label=target, param_attr=paddle.attr.Param(name='crfw')) evaluator.sum(input=crf_dec) # create parameters parameters = paddle.parameters.create(crf_cost) parameters.set('emb', load_parameter(conll05.get_embedding(), 44068, 32)) # create optimizer optimizer = paddle.optimizer.Momentum( momentum=0, learning_rate=2e-2, regularization=paddle.optimizer.L2Regularization(rate=8e-4), model_average=paddle.optimizer.ModelAverage( average_window=0.5, max_average_window=10000), ) trainer = paddle.trainer.SGD( cost=crf_cost, parameters=parameters, update_equation=optimizer, extra_layers=crf_dec) reader = paddle.batch( paddle.reader.shuffle(conll05.test(), buf_size=8192), batch_size=10) feeding = { 'word_data': 0, 'ctx_n2_data': 1, 'ctx_n1_data': 2, 'ctx_0_data': 3, 'ctx_p1_data': 4, 'ctx_p2_data': 5, 'verb_data': 6, 'mark_data': 7, 'target': 8 } def event_handler(event): if isinstance(event, paddle.event.EndIteration): if event.batch_id % 100 == 0: print "Pass %d, Batch %d, Cost %f, %s" % ( event.pass_id, event.batch_id, event.cost, event.metrics) if event.batch_id % 1000 == 0: result = trainer.test(reader=reader, feeding=feeding) print "\nTest with Pass %d, Batch %d, %s" % ( event.pass_id, event.batch_id, result.metrics) if isinstance(event, paddle.event.EndPass): # save parameters with open('params_pass_%d.tar' % event.pass_id, 'w') as f: parameters.to_tar(f) result = trainer.test(reader=reader, feeding=feeding) print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) trainer.train( reader=reader, event_handler=event_handler, num_passes=1, feeding=feeding) test_creator = paddle.dataset.conll05.test() test_data = [] for item in test_creator(): test_data.append(item[0:8]) if len(test_data) == 1: break predict = paddle.layer.crf_decoding( size=label_dict_len, input=feature_out, param_attr=paddle.attr.Param(name='crfw')) probs = paddle.infer( output_layer=predict, parameters=parameters, input=test_data, field='id') assert len(probs) == len(test_data[0][0]) labels_reverse = {} for (k, v) in label_dict.items(): labels_reverse[v] = k pre_lab = [labels_reverse[i] for i in probs] print pre_labif __name__ == '__main__': main()
应用
训练完成之后,需要依据某个我们关心的性能指标选择最优的模型进行预测,可以简单的选择测试集上标记错误最少的那个模型。预测时使用 paddle.layer.crf_decoding,和训练不同的是,该层没有正确的标签层作为输入。如下所示:
predict = paddle.layer.crf_decoding( size=label_dict_len, input=feature_out, param_attr=paddle.attr.Param(name='crfw'))# 选用测试集的一条数据作为示例test_creator = paddle.dataset.conll05.test()test_data = []for item in test_creator(): test_data.append(item[0:8]) if len(test_data) == 1: break# 推断接口paddle.infer返回标签的索引,并查询词典labels_reverse,打印出标记的结果labs = paddle.infer( output_layer=predict, parameters=parameters, input=test_data, field='id')assert len(labs) == len(test_data[0][0])labels_reverse={}for (k,v) in label_dict.items(): labels_reverse[v]=kpre_lab = [labels_reverse[i] for i in labs]print pre_lab
- 语义角色标注
- 一些实用的语义角色标注(SRL)工具使用方法
- 一些实用的语义角色标注(SRL)工具使用方法
- 二十、语义角色标注的基本方法
- NLP+语义分析(四)︱中文语义分析研究现状(CIPS2016、角色标注、篇章分析)
- nlp 总结 分词,词义消歧,词性标注,命名体识别,依存句法分析,语义角色标注
- PaddlePaddle︱开发文档中学习情感分类(CNN、LSTM、双向LSTM)、语义角色标注
- Arcgis标注语义换行
- 浅谈语义角色
- 【制图】ArcGIS 标注语义换行
- 第二章, 微格式:语义标注和常义冲突
- 基于多标签学习的图像语义自动标注
- Web服务分类及语义标注算法研究
- 语义图像标注和检索 Semantic Image Annotation and Retrieval
- 【图像语义分割】Label data的标注--Labelme(python)
- 基于内容的视频语义标注(一)——视频标注的三种方法和不同层次划分标注
- 【命名实体识别】HMM-Viterbi角色标注中国人名识别
- 各别论元角色的定义和句法、语义特点
- iTunes恢复备份时“恢复备份”按钮为灰色
- 深入浅出WPF9
- windows、解决管理员权限
- mico 物联网开发平台学习资料
- jQuery显示与隐藏效果
- 语义角色标注
- 修改desktop.ini 自定义文件夹[高级篇]
- STM32之SPI驱动外部Flash
- 分割java字符串,工具类
- leetcode---permutations---dfs
- html禁用文本位11位数为电话号码
- C#.Net中ADO.net数据增删查改操作
- MIB 浏览器的使用指导
- Core Java Volume I 读书笔记---第六章 接口与内部类