论文参考文献

首页 » 常识 » 常识 » 基于飞桨PaddlePaddle的语义角
TUhjnbcbe - 2025/3/22 18:53:00
北京有哪些比较好的白癜风医院 https://disease.39.net/bjzkbdfyy/250319/h8vbiyl.html

自然语言处理中的自然语言句子级分析技术,可以大致分为词法分析、句法分析、语义分析三个层面。

词法分析:第一层面的词法分析(lexicalanalysis)包括汉语分词和词性标注两部分。

句法分析:对输入的文本句子进行分析以得到句子的句法结构的处理过程。语义分析(semanticparsing):语义分析的最终目的是理解句子表达的真实语义。语义角色标注是实现浅层语义分析的一种方式。在一个句子中,谓词是对主语的陈述或说明,指出「做什么」、「是什么」或「怎么样,代表了一个事件的核心,跟谓词搭配的名词称为论元。语义角色是指论元在动词所指事件中担任的角色。主要有:施事者(Agent)、受事者(Patient)、客体(Theme)、经验者(Experiencer)、受益者(Beneficiary)、工具(Instrument)、处所(Location)、目标(Goal)和来源(Source)等。

以上示例表示,「遇到」是谓词(Predicate,通常简写为「Pred」),「小明」是施事者(Agent),「小红」是受事者(Patient),「昨天」是事件发生的时间(Time),「公园」是事情发生的地点(Location)。

语义角色标注(SemanticRoleLabeling,SRL)以句子的谓词为中心,不对句子所包含的语义信息进行深入分析,只分析句子中各成分与谓词之间的关系,即句子的谓词(Predicate)-论元(Argument)结构,并用语义角色来描述这些结构关系,是许多自然语言理解任务(如信息抽取,篇章分析,深度问答等)的一个重要中间步骤。

基于语块(chunk)的SRL方法

基于语块的SRL方法将SRL[1]作为一个序列标注问题来解决。序列标注任务一般都会采用BIO表示方式来定义序列标注的标签集,我们先来介绍这种表示方法。在BIO表示法中,B代表语块的开始,I代表语块的中间,O代表语块结束。通过B、I、O三种标记将不同的语块赋予不同的标签,例如:对于一个由角色A拓展得到的语块组,将它所包含的第一个语块赋予标签B-A,将它所包含的其它语块赋予标签I-A,不属于任何论元的语块赋予标签O。

从上面的例子可以看到,根据序列标注结果可以直接得到论元的语义角色标注结果,是一个相对简单的过程。这种简单性体现在:(1)依赖浅层句法分析,降低了句法分析的要求和难度;(2)没有了候选论元剪除这一步骤;(3)论元的识别和论元标注是同时实现的。这种一体化处理论元识别和论元标注的方法,简化了流程,降低了错误累积的风险,往往能够取得更好的结果。

深度双向LSTM(DB-LSTM)SRL模型

与基于语块的SRL方法类似,在本文中我们也将SRL看作一个序列标注问题,不同的是,我们只依赖输入文本序列,不依赖任何额外的语法解析结果或是复杂的人造特征,利用深度神经网络构建一个端到端学习的SRL系统。

循环神经网络(RecurrentNeuralNetwork)是一种对序列建模的重要模型,在自然语言处理任务中有着广泛地应用。不同于前馈神经网络(Feed-forwardNeuralNetwork),RNN能够处理输入之间前后关联的问题。LSTM是RNN的一种重要变种,常用来学习长序列中蕴含的长程依赖关系,这里我们就使用LSTM来解决SRL问题。

在SRL任务中,输入是「谓词」和「一句话」,目标是从这句话中找到谓词的论元,并标注论元的语义角色。如果一个句子含有n个谓词,这个句子会被处理n次。一个最为直接的模型是下面这样:

构造输入;输入1是谓词,输入2是句子;将输入1扩展成和输入2一样长的序列,用one-hot方式表示;one-hot方式的谓词序列和句子序列通过词表,转换为实向量表示的词向量序列;将步骤2中的2个词向量序列作为双向LSTM的输入,学习输入序列的特征表示;CRF以步骤3中模型学习到的特征为输入,以标记序列为监督信号,实现序列标注;大家可以尝试上面这种方法。这里,我们提出一些改进,引入两个简单但对提高系统性能非常有效的特征:

谓词上下文:上面的方法中,只用到了谓词的词向量表达谓词相关的所有信息,这种方法始终是非常弱的,特别是如果谓词在句子中出现多次,有可能引起一定的歧义。从经验出发,谓词前后若干个词的一个小片段,能够提供更丰富的信息,帮助消解歧义。于是,我们把这样的经验也添加到模型中,为每个谓词同时抽取一个「谓词上下文」片段,也就是从这个谓词前后各取n个词构成的一个窗口片段;谓词上下文区域标记:为句子中的每一个词引入一个0-1二值变量,表示它们是否在「谓词上下文」片段中;修改后的模型如下(下图是一个深度为4的模型结构示意图):

构造输入;输入1是句子序列,输入2是谓词序列,输入3是谓词上下文,从句子中抽取这个谓词前后各n个词,构成谓词上下文,用one-hot方式表示,输入4是谓词上下文区域标记,标记了句子中每一个词是否在谓词上下文中;将输入2~3均扩展为和输入1一样长的序列;输入1~4均通过词表取词向量转换为实向量表示的词向量序列;其中输入1、3共享同一个词表,输入2和4各自独有词表;第2步的4个词向量序列作为双向LSTM模型的输入;LSTM模型学习输入序列的特征表示,得到新的特性表示序列;CRF以第3步中LSTM学习到的特征为输入,以标记序列为监督信号,完成序列标注;

图:SRL任务上的深层双向LSTM模型

接下来,我们就一步一步的实践这个任务:给定一句话和这句话里的一个谓词,通过序列标注的方式,从句子中找到谓词对应的论元,同时标注它们的语义角色。

数据介绍

在本文中,我们选用CoNLLSRL任务开放出的数据集作为示例。需要特别说明的是,CoNLLSRL任务的训练数集和开发集在比赛之后并非免费进行公开,目前,能够获取到的只有测试集,包括WallStreetJournal的23节和Brown语料集中的3节。在本文中,我们以测试集中的WSJ数据为训练集来讲解模型。但是,由于测试集中样本的数量远远不够,如果希望训练一个可用的神经网络SRL系统,请考虑付费获取全量数据。

原始数据中同时包括了词性标注、命名实体识别、语法解析树等多种信息。我们使用test.wsj文件夹中的数据进行训练和测试,并只会用到words文件夹(文本序列)和props文件夹(标注结果)下的数据。本文使用的数据目录如下:

conll05st-release/└──test.wsj├──props#标注结果└──words#输入文本序列

标注信息源自PennTreeBank[2]和PropBank[3]的标注结果。PropBank标注结果的标签和我们在文章一开始示例中使用的标注结果标签不同,但原理是相同的,关于标注结果标签含义的说明,请参考论文[4]。

原始数据需要进行数据预处理才能被飞桨处理,预处理包括下面几个步骤:

将文本序列和标记序列其合并到一条记录中;一个句子如果含有n个谓词,这个句子会被处理n次,变成n条独立的训练样本,每个样本一个不同的谓词;抽取谓词上下文和构造谓词上下文区域标记;构造以BIO法表示的标记;依据词典获取词对应的整数索引。预处理完成之后一条训练样本数据包含9个域,分别是:句子序列、谓词、谓词上下文(占5列)、谓词上下区域标志、标注序列。下表是一条训练样本的示例。

除数据之外,我们同时提供了以下资源:

我们在英文维基百科上训练语言模型得到了一份词向量用来初始化SRL模型。在SRL模型训练过程中,词向量不再被更新。关于语言模型和词向量可以参考:《

基于PaddlePaddle的词向量实战

深度学习基础任务教程系列(二)》。我们训练语言模型的语料共有,,个token,词典大小控制为,词。CoNLL训练语料中有5%的词不在这,个词中,我们将它们全部看作未登录词,用unk表示。

获取词典,打印词典大小:

from__future__importprint_functionimportmath,osimportnumpyasnpimportpaddleimportpaddle.dataset.conll05asconll05importpaddle.fluidasfluidimportsiximporttimewith_gpu=os.getenv(WITH_GPU,0)!=0word_dict,verb_dict,label_dict=conll05.get_dict()word_dict_len=len(word_dict)label_dict_len=len(label_dict)pred_dict_len=len(verb_dict)print(word_dict_len:,word_dict_len)print(label_dict_len:,label_dict_len)print(pred_dict_len:,pred_dict_len)

模型配置说明

定义输入数据维度及模型超参数。

mark_dict_len=2#谓上下文区域标志的维度,是一个0-12值特征,因此维度为2word_dim=32#词向量维度mark_dim=5#谓词上下文区域通过词表被映射为一个实向量,这个是相邻的维度hidden_dim=#LSTM隐层向量的维度:/4depth=8#栈式LSTM的深度mix_hidden_lr=1e-3#linear_chain_crf层的基础学习率IS_SPARSE=True#是否以稀疏方式更新embeddingPASS_NUM=10#训练轮数BATCH_SIZE=10#batchsize大小embedding_name=emb

这里需要特别说明的是,参数hidden_dim=实际指定了LSTM隐层向量的维度为,关于这一点请参考飞桨官方文档中dynamic_lstm的说明。

如上文提到,我们用基于英文维基百科训练好的词向量来初始化序列输入、谓词上下文总共6个特征的embedding层参数,在训练中不更新。

#这里加载PaddlePaddle保存的二进制参数defload_parameter(file_name,h,w):withopen(file_name,rb)asf:f.read(16)#skipheader.returnnp.fromfile(f,dtype=np.float32).reshape(h,w)

训练模型

我们根据网络拓扑结构和模型参数来进行训练,在构造时还需指定优化方法,这里使用最基本的SGD方法(momentum设置为0),同时设定了学习率、正则等。

定义训练过程的超参数

use_cuda=False#在cpu上执行训练save_dirname=label_semantic_roles.inference.model#训练得到的模型参数保存在文件中is_local=True

数据输入层定义

定义了模型输入特征的格式,包括句子序列、谓词、谓词上下文的5个特征、和谓词上下区域标志。

#句子序列word=fluid.layers.data(name=word_data,shape=[1],dtype=int64,lod_level=1)#谓词predicate=fluid.layers.data(name=verb_data,shape=[1],dtype=int64,lod_level=1)#谓词上下文5个特征ctx_n2=fluid.layers.data(name=ctx_n2_data,shape=[1],dtype=int64,lod_level=1)ctx_n1=fluid.layers.data(name=ctx_n1_data,shape=[1],dtype=int64,lod_level=1)ctx_0=fluid.layers.data(name=ctx_0_data,shape=[1],dtype=int64,lod_level=1)ctx_p1=fluid.layers.data(name=ctx_p1_data,shape=[1],dtype=int64,lod_level=1)ctx_p2=fluid.layers.data(name=ctx_p2_data,shape=[1],dtype=int64,lod_level=1)#谓词上下区域标志mark=fluid.layers.data(name=mark_data,shape=[1],dtype=int64,lod_level=1)

定义网络结构

首先预训练并定义模型输入层:

#预训练谓词和谓词上下区域标志predicate_embedding=fluid.layers.embedding(input=predicate,size=[pred_dict_len,word_dim],dtype=float32,is_sparse=IS_SPARSE,param_attr=vemb)mark_embedding=fluid.layers.embedding(input=mark,size=[mark_dict_len,mark_dim],dtype=float32,is_sparse=IS_SPARSE)#句子序列和谓词上下文5个特征并预训练word_input=[word,ctx_n2,ctx_n1,ctx_0,ctx_p1,ctx_p2]#因词向量是预训练好的,这里不再训练embedding表,#参数属性trainable设置成False阻止了embedding表在训练过程中被更新emb_layers=[fluid.layers.embedding(size=[word_dict_len,word_dim],input=x,param_attr=fluid.ParamAttr(name=embedding_name,trainable=False))forxinword_input]#加入谓词和谓词上下区域标志的预训练结果emb_layers.append(predicate_embedding)emb_layers.append(mark_embedding)

定义8个LSTM单元以「正向/反向」的顺序对所有输入序列进行学习。

#共有8个LSTM单元被训练,每个单元的方向为从左到右或从右到左,#由参数`is_reverse`确定#第一层栈结构hidden_0_layers=[fluid.layers.fc(input=emb,size=hidden_dim,act=tanh)forembinemb_layers]hidden_0=fluid.layers.sums(input=hidden_0_layers)lstm_0=fluid.layers.dynamic_lstm(input=hidden_0,size=hidden_dim,candidate_activation=relu,gate_activation=sigmoid,cell_activation=sigmoid)#用直连的边来堆叠L-LSTM、R-LSTMinput_tmp=[hidden_0,lstm_0]#其余的栈结构foriinrange(1,depth):mix_hidden=fluid.layers.sums(input=[fluid.layers.fc(input=input_tmp[0],size=hidden_dim,act=tanh),fluid.layers.fc(input=input_tmp[1],size=hidden_dim,act=tanh)])lstm=fluid.layers.dynamic_lstm(input=mix_hidden,size=hidden_dim,candidate_activation=relu,gate_activation=sigmoid,cell_activation=sigmoid,is_reverse=((i%2)==1))input_tmp=[mix_hidden,lstm]#取最后一个栈式LSTM的输出和这个LSTM单元的输入到隐层映射,#经过一个全连接层映射到标记字典的维度,来学习CRF的状态特征feature_out=fluid.layers.sums(input=[fluid.layers.fc(input=input_tmp[0],size=label_dict_len,act=tanh),fluid.layers.fc(input=input_tmp[1],size=label_dict_len,act=tanh)])#标注序列target=fluid.layers.data(name=target,shape=[1],dtype=int64,lod_level=1)#学习CRF的转移特征crf_cost=fluid.layers.linear_chain_crf(input=feature_out,label=target,param_attr=fluid.ParamAttr(name=crfw,learning_rate=mix_hidden_lr))avg_cost=fluid.layers.mean(crf_cost)#使用最基本的SGD优化方法(momentum设置为0)sgd_optimizer=fluid.optimizer.SGD(learning_rate=fluid.layers.exponential_decay(learning_rate=0.01,decay_steps=100,decay_rate=0.5,staircase=True))sgd_optimizer.minimize(avg_cost)

数据介绍部分提到CoNLL训练集付费,这里我们使用测试集训练供大家学习。conll05.test()每次产生一条样本,包含9个特征,shuffle和组完batch后作为训练的输入。

crf_decode=fluid.layers.crf_decoding(input=feature_out,param_attr=fluid.ParamAttr(name=crfw))train_data=paddle.batch(paddle.reader.shuffle(paddle.dataset.conll05.test(),buf_size=),batch_size=BATCH_SIZE)place=fluid.CUDAPlace(0)ifuse_cudaelsefluid.CPUPlace()

通过feeder来指定每一个数据和data_layer的对应关系,下面的feeder表示conll05.test()产生数据的第0列对应的data_layer是word。

feeder=fluid.DataFeeder(feed_list=[word,ctx_n2,ctx_n1,ctx_0,ctx_p1,ctx_p2,predicate,mark,target],place=place)exe=fluid.Executor(place)

开始训练

main_program=fluid.default_main_program()exe.run(fluid.default_startup_program())embedding_param=fluid.global_scope().find_var(embedding_name).get_tensor()embedding_param.set(load_parameter(conll05.get_embedding(),word_dict_len,word_dim),place)start_time=time.time()batch_id=0forpass_idinsix.moves.xrange(PASS_NUM):fordataintrain_data():cost=exe.run(main_program,feed=feeder.feed(data),fetch_list=[avg_cost])cost=cost[0]ifbatch_id%10==0:print(avg_cost:+str(cost))ifbatch_id!=0:print(secondperbatch:+str((time.time()-start_time)/batch_id))#SetthethresholdlowtospeeduptheCItestiffloat(cost)60.0:ifsave_dirnameisnotNone:fluid.io.save_inference_model(save_dirname,[word_data,verb_data,ctx_n2_data,ctx_n1_data,ctx_0_data,ctx_p1_data,ctx_p2_data,mark_data],[feature_out],exe)breakbatch_id=batch_id+1

应用模型

训练完成之后,需要依据某个我们关心的性能指标选择最优的模型进行预测,可以简单的选择测试集上标记错误最少的那个模型。以下我们给出一个使用训练后的模型进行预测的示例。

首先设置预测过程的参数:

use_cuda=False#在cpu上进行预测save_dirname=label_semantic_roles.inference.model#调用训练好的模型进行预测place=fluid.CUDAPlace(0)ifuse_cudaelsefluid.CPUPlace()exe=fluid.Executor(place)

设置输入,用LoDTensor来表示输入的词序列,这里每个词的形状base_shape都是[1],是因为每个词都是用一个id来表示的。假如基于长度的LoD是[[3,4,2]],这是一个单层的LoD,那么构造出的LoDTensor就包含3个序列,其长度分别为3、4和2。

注意LoD是个列表的列表:

lod=[[3,4,2]]base_shape=[1]#构造假数据作为输入,整数随机数的范围是[low,high]word=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=word_dict_len-1)pred=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=pred_dict_len-1)ctx_n2=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=word_dict_len-1)ctx_n1=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=word_dict_len-1)ctx_0=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=word_dict_len-1)ctx_p1=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=word_dict_len-1)ctx_p2=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=word_dict_len-1)mark=fluid.create_random_int_lodtensor(lod,base_shape,place,low=0,high=mark_dict_len-1)

使用fluid.io.load_inference_model加载inference_program,feed_target_names是模型的输入变量的名称,fetch_targets是预测对象。

[inference_program,feed_target_names,fetch_targets]=fluid.io.load_inference_model(save_dirname,exe)

构造feed字典{feed_target_name:feed_target_data},results是由预测目标构成的列表:

assertfeed_target_names[0]==word_dataassertfeed_target_names[1]==verb_dataassertfeed_target_names[2]==ctx_n2_dataassertfeed_target_names[3]==ctx_n1_dataassertfeed_target_names[4]==ctx_0_dataassertfeed_target_names[5]==ctx_p1_dataassertfeed_target_names[6]==ctx_p2_dataassertfeed_target_names[7]==mark_data

执行预测

results=exe.run(inference_program,feed={feed_target_names[0]:word,feed_target_names[1]:pred,feed_target_names[2]:ctx_n2,feed_target_names[3]:ctx_n1,feed_target_names[4]:ctx_0,feed_target_names[5]:ctx_p1,feed_target_names[6]:ctx_p2,feed_target_names[7]:mark},fetch_list=fetch_targets,return_numpy=False)

输出结果

print(results[0].lod())np_data=np.array(results[0])print(InferenceShape:,np_data.shape)

总结

语义角色标注是许多自然语言理解任务的重要中间步骤。本文我们以语义角色标注任务为例,介绍如何利用飞桨进行序列标注任务。本文所介绍的模型来自我们发表的论文[5]。由于CoNLLSRL任务的训练数据目前并非完全开放,本文中只使用测试数据作为示例。在这个过程中,我们希望减少对其它自然语言处理工具的依赖,利用神经网络数据驱动、端到端学习的能力,得到一个和传统方法可比、甚至更好的模型。在论文中我们证实了这种可能性。关于模型更多的信息和讨论可以在论文中找到。

参考文献

1.SunW,SuiZ,WangM,etal.Chinesesemanticrolelabelingwithshallowparsing[C]//ProceedingsoftheConferenceonEmpiricalMethodsinNaturalLanguageProcessing:Volume3-Volume3.AssociationforComputationalLinguistics,:-.

2.MarcusMP,MarcinkiewiczMA,SantoriniB.BuildingalargeannotatedcorpusofEnglish:ThePennTreebank[J].Computationallinguistics,,19(2):-.

3.PalmerM,GildeaD,KingsburyP.Thepropositionbank:Anannotatedcorpusofsemanticroles[J].Computationallinguistics,,31(1):71-.

4.CarrerasX,MàrquezL.IntroductiontotheCoNLL-sharedtask:Semanticrolelabeling[C]//ProceedingsoftheNinthConferenceonComputationalNaturalLanguageLearning.AssociationforComputationalLinguistics,:-.

5.ZhouJ,XuW.End-to-endlearningofsemanticrolelabelingusingrecurrentneuralnetworks[C]//ProceedingsoftheAnnualMeetingoftheAssociationforComputationalLinguistics..

1
查看完整版本: 基于飞桨PaddlePaddle的语义角