一、前言
句法分析是自然语言中关键技术之一,其主要任务时确定句法结构、句子中的词汇之间的依存关系;因而主要包括两方面的内容:一、确定语言的语法体系,即对句子结构给与形式化定义;二、句法分析技术,根据语法体系,推导出句子的句法结构,以及句法单位与句法单位之间的关系。
语义依存分析是分析句子中的各语言单位的关联,并将语义关联以依存的结构进行展示;语义依存分析目标是跨越句子表层句法结构的束缚,直接获取深层的语义信息。浅层语义分析是依赖于句法分析的结果,而深层的语义分析并不都跟句法分析结果产生影响,这就是语义分析与句法分析的不同之处。
语义角色与语义分析之间存在着关联性,语义角色只关注的是谓词的论元以及谓词与论元之间的关系;语义分析不仅关注谓词与论元的关系,还关注谓词与谓词、论元与论元、论元内部的语义关系。其语义分析更加的全面。
语义依存关系主要分为三类:1、主要语义角色,每一种语义角色对应存在一个嵌套关系和反关系;2、事件关系,描述两个事件的关系;3、语义依附标记,标记说话者语气等依附信息。
目前哈工大的分词pyltp提供了相应的模块,pyltp包的主要功能为:分句(SentenceSplitter)、分词(cws.model)、词性标注(pos.model)、实体命名(ner.model...)、依存句法分析(parser.model)、语义角色分析(srl)、语义依存分析(暂不提供这个功能)
二、基于依存句法的三元组抽取
1、对于每个词生成一个该词的依存句法的儿子节点,主要存储关系和对应儿子词的位置;
2、对没歌词生成一个该词的父子数组的依存结构,主要是记录该词的词性、父节点的词性以及他们之间的关系;
3、循环每个词,找到具有动宾关系、定语后置动宾关系、介宾的主谓动补关系,并进行提取;
4、对于提取主宾中的词,需要在里面寻找具有相关依存结构的词,剔除不需要的词;
三、基于语义角色标注的三元组抽取
利用ltp的语义角色对句子进行标注,统计是否存在svo的结构,如果存在,则提取,否则,换依存句法进行提取。
四、具体代码如下
# 主要是基于依存句法的三元信息抽取 import os from pyltp import Segmentor,Postagger,Parser,NamedEntityRecognizer,SementicRoleLabeller class LtpParser: def __init__(self): LTP_PATH = "D:\workspace\project\\NLPcase\\eventTripExtraction\\ltp_data" # 分词 self.segmentor = Segmentor() self.segmentor.load(os.path.join(LTP_PATH,'cws.model')) # 词性标注 self.postagger = Postagger() self.postagger.load(os.path.join(LTP_PATH,'pos.model')) # 依存句法 self.parser = Parser() self.parser.load(os.path.join(LTP_PATH,'parser.model')) # 命名实体识别 self.recognizer = NamedEntityRecognizer() self.recognizer.load(os.path.join(LTP_PATH,'ner.model')) # 语义角色标注 self.labeller = SementicRoleLabeller() self.labeller.label(os.path.join(LTP_PATH,'pisrl.model')) def format_labeller(self,words,postags): '''语义角色标注''' arcs = self.parser.parse(words,postags) roles = self.labeller.label(words,postags,arcs) roles_dict = {} for role in roles: #role.index代表谓词的索引 #role.arguments 代表关于该谓词的若干语义角色 # arg.name表示语义角色类型 # arg.range.start表示该语义角色起始词的开始位置索引 # arg.range.end表示该语义角色起始词的结束位置索引 roles_dict[role.index] = {arg.name:{arg.name,arg.range.start,arg.range.end} for arg in role.arguments} return roles_dict def build_parser_child_dict(self,words,postags,arcs): '''句法分析,为句子中的每个词语维护一个保存句法依存儿子节点的字典''' child_dict_list = [] format_parser_list = [] for index in range(len(words)):# 循环每个词 child_dict = dict() # 存储的格式为{词关系(ATT): 词} for arc_index in range(len(arcs)): #arc_index为当前词的索引,arcs[arc_index]为一个元祖(arc.head,arc.relation) if arcs[arc_index].head ==index+1:# 去找到父节点为当第一个for里面索引的词,即当前词的子节点 if arcs[arc_index].relation in child_dict: child_dict[arcs[arc_index].relation].append(arc_index) else: child_dict[arcs[arc_index].relation] = [] child_dict[arcs[arc_index].relation].append(arc_index) child_dict_list.append(child_dict) rely_id = [arc.head for arc in arcs]# 提取依存父节点id relation = [arc.relation for arc in arcs]# 提取关系依存 heads = ['Root' if id==0 else words[id-1] for id in rely_id]# 匹配依存父节点词语 for i in range(len(words)): a = [relation[i],words[i],i,postags[i],heads[i],rely_id[i]-1,postags[rely_id[i]-1]] format_parser_list.append(a) return child_dict_list,format_parser_list def parser_main(self,sent): '''依存关系分析的主函数''' words = list(self.segmentor.segment(sent)) postags = list(self.postagger.postag(words)) arcs = self.parser.parse(words,postags) child_dict_list, format_parser_list = self.build_parser_child_dict(words,postags,arcs) roles_dict = self.format_labeller(words,postags) return words,postags,child_dict_list,format_parser_list,roles_dict
from eventTripExtraction.parser_extraction import * import re class TripleExtractor: def __init__(self): self.parser = LtpParser() # 对文章进行分句处理 def split_sents(self,content): return [sent for sent in re.split(r'[??!!。;;::\n\r]',content) if sent] def ruler1(self,words,postags,roles_dict,role_index): '''利用语义角色标注,直接获取主谓宾三元组,基于A0,A1,A2''' v = words[role_index]# 动词 role_info = roles_dict[role_index]# 获得角色的信息 if 'A0' in role_info.keys() and 'A1' in role_info.keys(): s =''.join([words[word_index] for word_index in range(role_info['A0'][1],role_info['A0'][2]+1) if postags[word_index][0] not in ['w','u','x'] and words[word_index] ]) o = ''.join([words[word_index] for word_index in range(role_info['A1'][1],role_info['A1'][2]+1) if postags[word_index][0] not in ['w','u','x'] and words[word_index] ]) if s and o: return '1',[s,v,0] return '4',[] def ruler2(self,words,postags,child_dict_list,arcs,roles_dict): '''基于依存结构进行关系提取''' svos = [] for index in range(len(postags)): tmp = 1 # 先利用语义角色进行标注,进行提取 if index in roles_dict: flag,triple = self.ruler1(words,postags,roles_dict,index) if flag=='1': svos.append(triple) tmp = 0 if tmp ==1:#说明语义角色提取失败,利用依存句法进行提取 if postags[index]: child_dict = child_dict_list[index] # 抽取以谓词为中心的事实三元组 # 主谓宾关系 if 'SBV' in child_dict and 'VOB' in child_dict: r = words[index] e1 = self.complete(words,postags,child_dict_list,child_dict['SVB'][0]) e2 = self.complete(words, postags, child_dict_list, child_dict['VOB'][0]) svos.append([e1,r,e2]) # 定语后置,动宾关系 relation = arcs[index][0]# 向上的 head = arcs[index][2]# 向上关系的索引 if relation == 'ATT': if 'VOB' in child_dict:# 向下的关系 e1 = self.complete(words,postags,child_dict_list,head-1) r = words[index] e2 = self.complete(words,postags,child_dict_list,child_dict['VOB'][0]) # 重新组合对应的关系 tmp_str = r+e2 if tmp_str == e1[:len(tmp_str)]: e1 = e1[len(tmp_str)] if tmp_str not in e1: svos.append([e1,r,e2]) # 包含有介宾关系的主谓动补关系 if 'SBV' in child_dict and 'CMP' in child_dict: e1 = self.complete(words,postags,child_dict_list,child_dict['SVB'][0]) cmp_index= child_dict['CMP'][0] r = words[index]+words[cmp_index] if 'POB' in child_dict_list[cmp_index]: e2 = self.complete(words,postags,child_dict_list,child_dict[cmp_index]['POB'][0]) svos.append([e1,r,e2]) return svos def complete_e(self,words,postags,child_dict_list,word_index): '''对找出的主语或者宾语,进行识别''' child_dict = child_dict_list[word_index] prefix = '' if 'ATT' in child_dict: for i in range(len(child_dict['ATT'])): prefix += self.complete_e(words,postags,child_dict_list,child_dict['ATT'][i]) postfix = '' if postags[word_index] == 'v': if 'VOB' in child_dict: postfix += self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0]) if 'SBV' in child_dict: prefix = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0]) + prefix return prefix+words[word_index]+postfix def triple_main(self,content): sents = self.split_sents(content) svos=[] for sent in sents: words, postags, child_dict_list, format_parser_list, roles_dict = self.parser.parser_main(sent) svo = self.ruler2(words,postags,child_dict_list,format_parser_list,roles_dict) svos +=svo return svos
五、参考文献:
https://www.cnblogs.com/CheeseZH/p/5768389.html
https://github.com/liuhuanyong/EventTriplesExtraction
https://blog.csdn.net/sinat_33741547/article/details/79258045
https://blog.csdn.net/mingzai624/article/details/78061506