系列文章

  1. 三元组事件抽取与简单代码实现

  2. 事件抽取技术综述与简单代码实现

目录

  • 系列文章
  • 事件抽取的定义
  • 使用jieba分词
  • 基于ltp依存句法分析和语义角色标注的事件三元组抽取方法
    • 下载pyltp包
    • 下载完整模型
    • 代码
  • 实验结果对比

一个简单的示例代码,基于词性的三元组事件抽取。

本文代码大多来源于另一位作者的工贡献:https://github.com/liuhuanyong/EventTriplesExtraction
可直接从github获取代码。如果你觉得有帮助,别忘了给原作者点个star

篇博客主要是描述一下这个项目如何使用(比较多坑)和三个实验对比结果,并提供一个建议可行的无监督事件抽取样例。

事件抽取的定义

事件是发生在某个特定的时间点或时间段、某个特定的地域范围内,由一个或者多个角色参与的一个或者多个动作组成的事情或者状态的改变。事件的发生可能是因为一个动作或者系统状态的改变。事件抽取目的是检测文本中报告的事件是否存在,如果存在则从自然语言文本中进行事件元素的抽取,最后以结构化的方式展现出来。封闭域中的事件抽取首先抽取表明事件类型的触发词,然后根据模板或其它工具提取事件。例如以下的一个例子:[9月3日],[明星张三]在[厦门]会见[协会会长李四]。“会见”一词触发了会见事件,根据会见事件预先定义的模板提取出事件(会见)、地点(厦门)、参与者(明星张三、协会会长李四)这些元素,由此完成了一个事件的提取。

使用jieba分词

保留新闻正文的文本,然后使用jieba分词进行分词和词性标注,然后通过几种预定义的词性模板进行三元组事件抽取。由于事件中存在许多词属少见低频词,故增添自定义词表

新闻原文:

2021年5月29日至6月5日,由中国科学院大学学生会主办,果壳篮球协会协办的中国科学院大学篮球3v3挑战赛圆满落幕,共有来自各个校区的15支男子队伍及4支女子队伍报名参赛。比赛分为两个男子组和女子组两个组别,分小组赛和淘汰赛两个赛段。2021年5月29日,经过一天的紧张较量,女子组所有比赛落下帷幕。随着选手们在场上的一次次精准投篮、巧妙传球,场下喝彩声阵阵。值得一提的是,四支女子队伍中有两支队伍均为校队成员,她们也顺利进入决赛,进行最后的对决,比赛气氛被推向高潮。最终女子组比赛结果如下:“小聋瞎队”获第一名,“打完就解散队”获第二名,“女生1队”获第三名;凭借出色的个人表现,“小聋瞎队”来自中科院微电子所的张雨萌获得MVP(最有价值球员)。2021年6月5日,男子组八强赛在玉泉路校区篮球场拉开帷幕。相较于小组赛,八强赛的比赛更加精彩刺激。参赛队员们在场上积极进攻,全面防守,时而强打内线,时而高举高打,给大家带来了一场场精彩的对决,场下观众喝彩阵阵。经过近2小时的激烈角逐,最终,“一星四射队”和“糊人不唬人队”进入决赛,将进行冠亚军的争夺。中场休息时,来自中国科学院大学WINGS舞社的同学带来了精彩的表演。随着裁判员一声令下,本次3v3篮球赛决赛一触即发。双方球员迅速进入状态。他们默契配合、巧妙进攻,完成了一次又一次精彩的投篮。场外的观众也不甘示弱,一声声喝彩与鼓励响彻球场。最终经过一番激烈的角逐,“一星四射队”以21:15获取本届篮球3v3比赛的冠军。

# coding=utf-8
import re, os
import jieba.posseg as pseg
import jiebaclass ExtractEvent:def __init__(self):self.map_dict = self.load_mapdict()self.minlen = 2self.maxlen = 30self.keywords_num = 20self.limit_score = 10self.IP = "(([NERMQ]*P*[ABDP]*)*([ABDV]{1,})*([NERMQ]*)*([VDAB]$)?([NERMQ]*)*([VDAB]$)?)*"self.IP = "([NER]*([PMBQADP]*[NER]*)*([VPDA]{1,}[NEBRVMQDA]*)*)"self.MQ = '[DP]*M{1,}[Q]*([VN]$)?'self.VNP = 'V*N{1,}'self.NP = '[NER]{1,}'self.REN = 'R{2,}'self.VP = 'P?(V|A$|D$){1,}'self.PP = 'P?[NERMQ]{1,}'self.SPO_n = "n{1,}"self.SPO_v = "v{1,}"self.stop_tags = {'u', 'wp', 'o', 'y', 'w', 'f', 'u', 'c', 'uj', 'nd', 't', 'x'}self.combine_words = {"首先", "然后", "之前", "之后", "其次", "接着"}"""构建映射字典"""def load_mapdict(self):tag_dict = {'B': 'b'.split(),  # 时间词'A': 'a d'.split(),  # 时间词'D': "d".split(),  # 限定词'N': "n j s zg en l r".split(),  #名词"E": "nt nz ns an ng".split(),  #实体词"R": "nr".split(),  #人物'G': "g".split(),  #语素'V': "vd v va i vg vn g".split(), #动词'P': "p f".split(),  #介词"M": "m t".split(),  #数词"Q": "q".split(), #量词"v": "V".split(), #动词短语"n": "N".split(), #名词介宾短语}map_dict = {}for flag, tags in tag_dict.items():for tag in tags:map_dict[tag] = flagreturn map_dict"""根据定义的标签,对词性进行标签化"""def transfer_tags(self, postags):tags = [self.map_dict.get(tag[:2], 'W') for tag in postags]return ''.join(tags)"""抽取出指定长度的ngram"""def extract_ngram(self, pos_seq, regex):ss = self.transfer_tags(pos_seq)def gen():for s in range(len(ss)):for n in range(self.minlen, 1 + min(self.maxlen, len(ss) - s)):e = s + nsubstr = ss[s:e]if re.match(regex + "$", substr):yield (s, e)return list(gen())'''抽取ngram'''def extract_sentgram(self, pos_seq, regex):ss = self.transfer_tags(pos_seq)def gen():for m in re.finditer(regex, ss):yield (m.start(), m.end())return list(gen())"""指示代词替换,消解处理"""def cite_resolution(self, words, postags, persons):if not persons and 'r' not in set(postags):return words, postagselif persons and 'r' in set(postags):cite_index = postags.index('r')if words[cite_index] in {"其", "他", "她", "我"}:words[cite_index] = persons[-1]postags[cite_index] = 'nr'elif 'r' in set(postags):cite_index = postags.index('r')if words[cite_index] in {"为何", "何", "如何"}:postags[cite_index] = 'w'return words, postags"""抽取量词性短语"""def extract_mqs(self, wds, postags):phrase_tokspans = self.extract_sentgram(postags, self.MQ)if not phrase_tokspans:return []phrases = [''.join(wds[i[0]:i[1]])for i in phrase_tokspans]return phrases'''抽取动词性短语'''def get_ips(self, wds, postags):ips = []phrase_tokspans = self.extract_sentgram(postags, self.IP)if not phrase_tokspans:return []phrases = [''.join(wds[i[0]:i[1]])for i in phrase_tokspans]phrase_postags = [''.join(postags[i[0]:i[1]]) for i in phrase_tokspans]for phrase, phrase_postag_ in zip(phrases, phrase_postags):if not phrase:continuephrase_postags = ''.join(phrase_postag_).replace('m', '').replace('q','').replace('a', '').replace('t', '')if phrase_postags.startswith('n') or phrase_postags.startswith('j'):has_subj = 1else:has_subj = 0ips.append((has_subj, phrase))return ips"""分短句处理"""def split_short_sents(self, text):return [i for i in re.split(r'[,,]', text) if len(i)>2]"""分段落"""def split_paras(self, text):return [i for i in re.split(r'[\n\r]', text) if len(i) > 4]"""分长句处理"""def split_long_sents(self, text):return [i for i in re.split(r'[;。:; :??!!【】▲丨|]', text) if len(i) > 4]"""移出噪声数据"""def remove_punc(self, text):text = text.replace('\u3000', '').replace("'", '').replace('“', '').replace('”', '').replace('▲','').replace('” ', "”")tmps = re.findall('[\(|(][^\((\))]*[\)|)]', text)for tmp in tmps:text = text.replace(tmp, '')return text"""保持专有名词"""def zhuanming(self, text):books = re.findall('[<《][^《》]*[》>]', text)return books"""对人物类词语进行修正"""def modify_nr(self, wds, postags):phrase_tokspans = self.extract_sentgram(postags, self.REN)wds_seq = ' '.join(wds)pos_seq = ' '.join(postags)if not phrase_tokspans:return wds, postagselse:wd_phrases = [' '.join(wds[i[0]:i[1]]) for i in phrase_tokspans]postag_phrases = [' '.join(postags[i[0]:i[1]]) for i in phrase_tokspans]for wd_phrase in wd_phrases:tmp = wd_phrase.replace(' ', '')wds_seq = wds_seq.replace(wd_phrase, tmp)for postag_phrase in postag_phrases:pos_seq = pos_seq.replace(postag_phrase, 'nr')words = [i for i in wds_seq.split(' ') if i]postags = [i for i in pos_seq.split(' ') if i]return words, postags"""对人物类词语进行修正"""def modify_duplicate(self, wds, postags, regex, tag):phrase_tokspans = self.extract_sentgram(postags, regex)wds_seq = ' '.join(wds)pos_seq = ' '.join(postags)if not phrase_tokspans:return wds, postagselse:wd_phrases = [' '.join(wds[i[0]:i[1]]) for i in phrase_tokspans]postag_phrases = [' '.join(postags[i[0]:i[1]]) for i in phrase_tokspans]for wd_phrase in wd_phrases:tmp = wd_phrase.replace(' ', '')wds_seq = wds_seq.replace(wd_phrase, tmp)for postag_phrase in postag_phrases:pos_seq = pos_seq.replace(postag_phrase, tag)words = [i for i in wds_seq.split(' ') if i]postags = [i for i in pos_seq.split(' ') if i]return words, postags'''对句子进行分词处理'''def cut_wds(self, sent):WordList = ["一星四射队","中科院微电子所","小聋瞎队","打完就解散队","女生1队","2021年","5月29日","糊人不唬人队","男子队伍","女子队伍"]for UserWord in WordList:jieba.add_word(UserWord)wds = list(pseg.cut(sent))postags = [w.flag for w in wds]words = [w.word for w in wds]return self.modify_nr(words, postags)"""移除噪声词语"""def clean_wds(self, words, postags):wds = []poss =[]for wd, postag in zip(words, postags):if postag[0].lower() in self.stop_tags:continuewds.append(wd)poss.append(postag[:2])return wds, poss"""检测是否成立, 肯定需要包括名词"""def check_flag(self, postags):if not {"v", 'a', 'i'}.intersection(postags):return 0return 1"""识别出人名实体"""def detect_person(self, words, postags):persons = []for wd, postag in zip(words, postags):if postag == 'nr':persons.append(wd)return persons"""识别出名词性短语"""def get_nps(self, wds, postags):phrase_tokspans = self.extract_sentgram(postags, self.NP)if not phrase_tokspans:return [],[]phrases_np = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans]return phrase_tokspans, phrases_np"""识别出介宾短语"""def get_pps(self, wds, postags):phrase_tokspans = self.extract_sentgram(postags, self.PP)if not phrase_tokspans:return [],[]phrases_pp = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans]return phrase_tokspans, phrases_pp"""识别出动词短语"""def get_vps(self, wds, postags):phrase_tokspans = self.extract_sentgram(postags, self.VP)if not phrase_tokspans:return [],[]phrases_vp = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans]return phrase_tokspans, phrases_vp"""抽取名动词性短语"""def get_vnps(self, s):wds, postags = self.cut_wds(s)if not postags:return [], []if not (postags[-1].endswith("n") or postags[-1].endswith("l") or postags[-1].endswith("i")):return [], []phrase_tokspans = self.extract_sentgram(postags, self.VNP)if not phrase_tokspans:return [], []phrases_vnp = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans]phrase_tokspans2 = self.extract_sentgram(postags, self.NP)if not phrase_tokspans2:return [], []phrases_np = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans2]return phrases_vnp, phrases_np"""提取短语"""def phrase_ip(self, content):spos = []events = []content = self.remove_punc(content)paras = self.split_paras(content)for para in paras:long_sents = self.split_long_sents(para)for long_sent in long_sents:persons = []short_sents = self.split_short_sents(long_sent)for sent in short_sents:words, postags = self.cut_wds(sent)person = self.detect_person(words, postags)words, postags = self.cite_resolution(words, postags, persons)words, postags = self.clean_wds(words, postags)#print(words,postags)ips = self.get_ips(words, postags)persons += personfor ip in ips:events.append(ip[1])wds_tmp = []postags_tmp = []words, postags = self.cut_wds(ip[1])verb_tokspans, verbs = self.get_vps(words, postags)pp_tokspans, pps = self.get_pps(words, postags)tmp_dict = {str(verb[0]) + str(verb[1]): ['V', verbs[idx]] for idx, verb in enumerate(verb_tokspans)}pp_dict = {str(pp[0]) + str(pp[1]): ['N', pps[idx]] for idx, pp in enumerate(pp_tokspans)}tmp_dict.update(pp_dict)sort_keys = sorted([int(i) for i in tmp_dict.keys()])for i in sort_keys:if i < 10:i = '0' + str(i)wds_tmp.append(tmp_dict[str(i)][-1])postags_tmp.append(tmp_dict[str(i)][0])wds_tmp, postags_tmp = self.modify_duplicate(wds_tmp, postags_tmp, self.SPO_v, 'V')wds_tmp, postags_tmp = self.modify_duplicate(wds_tmp, postags_tmp, self.SPO_n, 'N')if len(postags_tmp) < 2:continueseg_index = []i = 0for wd, postag in zip(wds_tmp, postags_tmp):if postag == 'V':seg_index.append(i)i += 1spo = []for indx, seg_indx in enumerate(seg_index):if indx == 0:pre_indx = 0else:pre_indx = seg_index[indx-1]if pre_indx < 0:pre_indx = 0if seg_indx == 0:spo.append(('', wds_tmp[seg_indx], ''.join(wds_tmp[seg_indx+1:])))elif seg_indx > 0 and indx < 1:spo.append((''.join(wds_tmp[:seg_indx]), wds_tmp[seg_indx], ''.join(wds_tmp[seg_indx + 1:])))else:spo.append((''.join(wds_tmp[pre_indx+1:seg_indx]), wds_tmp[seg_indx], ''.join(wds_tmp[seg_indx + 1:])))spos += sporeturn events, sposif __name__ == '__main__':import timehandler = ExtractEvent()start = time.time()content1 = """环境很好,位置独立性很强,比较安静很切合店名,半闲居,偷得半日闲。点了比较经典的菜品,味道果然不错!烤乳鸽,超级赞赞赞,脆皮焦香,肉质细嫩,超好吃。艇仔粥料很足,香葱自己添加,很贴心。金钱肚味道不错,不过没有在广州吃的烂,牙口不好的慎点。凤爪很火候很好,推荐。最惊艳的是长寿菜,菜料十足,很新鲜,清淡又不乏味道,而且没有添加调料的味道,搭配的非常不错!"""content2 = """近日,一条男子高铁吃泡面被女乘客怒怼的视频引发热议。女子情绪激动,言辞激烈,大声斥责该乘客,称高铁上有规定不能吃泡面,质问其“有公德心吗”“没素质”。视频曝光后,该女子回应称,因自己的孩子对泡面过敏,曾跟这名男子沟通过,但对方执意不听,她才发泄不满,并称男子拍视频上传已侵犯了她的隐私权和名誉权,将采取法律手段。12306客服人员表示,高铁、动车上一般不卖泡面,但没有规定高铁、动车上不能吃泡面。高铁属于密封性较强的空间,每名乘客都有维护高铁内秩序,不破坏该空间内空气质量的义务。这也是乘客作为公民应当具备的基本品质。但是,在高铁没有明确禁止食用泡面等食物的背景下,以影响自己或孩子为由阻挠他人食用某种食品并厉声斥责,恐怕也超出了权利边界。当人们在公共场所活动时,不宜过分干涉他人权利,这样才能构建和谐美好的公共秩序。一般来说,个人的权利便是他人的义务,任何人不得随意侵犯他人权利,这是每个公民得以正常工作、生活的基本条件。如果权利可以被肆意侵犯而得不到救济,社会将无法运转,人们也没有幸福可言。如西谚所说,“你的权利止于我的鼻尖”,“你可以唱歌,但不能在午夜破坏我的美梦”。无论何种权利,其能够得以行使的前提是不影响他人正常生活,不违反公共利益和公序良俗。超越了这个边界,权利便不再为权利,也就不再受到保护。在“男子高铁吃泡面被怒怼”事件中,初一看,吃泡面男子可能侵犯公共场所秩序,被怒怼乃咎由自取,其实不尽然。虽然高铁属于封闭空间,但与禁止食用刺激性食品的地铁不同,高铁运营方虽然不建议食用泡面等刺激性食品,但并未作出禁止性规定。由此可见,即使食用泡面、榴莲、麻辣烫等食物可能产生刺激性味道,让他人不适,但是否食用该食品,依然取决于个人喜好,他人无权随意干涉乃至横加斥责。这也是此事件披露后,很多网友并未一边倒地批评食用泡面的男子,反而认为女乘客不该高声喧哗。现代社会,公民的义务一般分为法律义务和道德义务。如果某个行为被确定为法律义务,行为人必须遵守,一旦违反,无论是受害人抑或旁观群众,均有权制止、投诉、举报。违法者既会受到应有惩戒,也会受到道德谴责,积极制止者则属于应受鼓励的见义勇为。如果有人违反道德义务,则应受到道德和舆论谴责,并有可能被追究法律责任。如在公共场所随地吐痰、乱扔垃圾、脱掉鞋子、随意插队等。此时,如果行为人对他人的劝阻置之不理甚至行凶报复,无疑要受到严厉惩戒。当然,随着社会的发展,某些道德义务可能上升为法律义务。如之前,很多人对公共场所吸烟不以为然,烟民可以旁若无人地吞云吐雾。现在,要是还有人不识时务地在公共场所吸烟,必然将成为众矢之的。再回到“高铁吃泡面”事件,要是随着人们观念的更新,在高铁上不得吃泡面等可能产生刺激性气味的食物逐渐成为共识,或者上升到道德义务或法律义务。斥责、制止他人吃泡面将理直气壮,否则很难摆脱“矫情”,“将自我权利凌驾于他人权利之上”的嫌疑。在相关部门并未禁止在高铁上吃泡面的背景下,吃不吃泡面系个人权利或者个人私德,是不违反公共利益的个人正常生活的一部分。如果认为他人吃泡面让自己不适,最好是请求他人配合并加以感谢,而非站在道德制高点强制干预。只有每个人行使权利时不逾越边界,与他人沟通时好好说话,不过分自我地将幸福和舒适凌驾于他人之上,人与人之间才更趋于平等,公共生活才更趋向美好有序。"""content7 = """2021年5月29日至6月5日,由中国科学院大学学生会主办,果壳篮球协会协办的中国科学院大学篮球3v3挑战赛圆满落幕,共有来自各个校区的15支男子队伍及4支女子队伍报名参赛。比赛分为两个男子组和女子组两个组别,分小组赛和淘汰赛两个赛段。2021年5月29日,经过一天的紧张较量,女子组所有比赛落下帷幕。随着选手们在场上的一次次精准投篮、巧妙传球,场下喝彩声阵阵。值得一提的是,四支女子队伍中有两支队伍均为校队成员,她们也顺利进入决赛,进行最后的对决,比赛气氛被推向高潮。最终女子组比赛结果如下:“小聋瞎队”获第一名,“打完就解散队”获第二名,“女生1队”获第三名;凭借出色的个人表现,“小聋瞎队”来自中科院微电子所的张雨萌获得MVP(最有价值球员)。2021年6月5日,男子组八强赛在玉泉路校区篮球场拉开帷幕。相较于小组赛,八强赛的比赛更加精彩刺激。参赛队员们在场上积极进攻,全面防守,时而强打内线,时而高举高打,给大家带来了一场场精彩的对决,场下观众喝彩阵阵。经过近2小时的激烈角逐,最终,“一星四射队”和“糊人不唬人队”进入决赛,将进行冠亚军的争夺。中场休息时,来自中国科学院大学WINGS舞社的同学带来了精彩的表演。随着裁判员一声令下,本次3v3篮球赛决赛一触即发。双方球员迅速进入状态。他们默契配合、巧妙进攻,完成了一次又一次精彩的投篮。场外的观众也不甘示弱,一声声喝彩与鼓励响彻球场。最终经过一番激烈的角逐,“一星四射队”以21:15获取本届篮球3v3比赛的冠军。"""events, spos = handler.phrase_ip(content7)spos = [i for i in spos if i[0] and i[2]]for spo in spos:print(spo)

输出结果:

基于ltp依存句法分析和语义角色标注的事件三元组抽取方法

这种方法需要安装pyltp包,坑比较多,很多时候没法直接在win系统上pip install。我这儿简单说一下安装发方法,你也可以查看其他博客获取详细方法。

首先下载安装包(类似于前端),然后下载模型并在代码中指定路径(类似于后端)

下载pyltp包

按照
https://github.com/DraculaXly/Python/tree/master/Wheel从百度云下载whl文件进行本地安装
这种方法的环境要求:python 3.7.3 + VC14
pip install [whl文件的全路径]

安装成功

下载完整模型

请先下载完整的 LTP 模型文件
https://pan.baidu.com/share/link?shareid=1988562907&uk=2738088569#list/path=%2F

如果按照上面的方法,之间下载3.4.0版本
如果你在windows系统运行,还会遇到以下问题:max out of length(大概)
需要从http://ltp.ai/download.html中下载的pisrl_win.model(点击即可下载)替换下载的pisrl.model(不支持win系统)

代码

先定义一个py文件:sentence_parser.py

把下面代码中的“D:\Python37\Lib\site-packages\pyltp-0.2.1.dist-info\ltp_data_v3.4.0”换成你模型解压后位置所在的绝对路径

#!/usr/bin/env python3
# coding: utf-8
# File: sentence_parser.py
# Author: lhy<lhy_in_blcu@126.com,https://huangyong.github.io>
# Date: 18-3-10import os
from pyltp import Segmentor, Postagger, Parser, NamedEntityRecognizer, SementicRoleLabeller
class LtpParser:def __init__(self):LTP_DIR = "D:\Python37\Lib\site-packages\pyltp-0.2.1.dist-info\ltp_data_v3.4.0"self.segmentor = Segmentor()self.segmentor.load(os.path.join(LTP_DIR, "cws.model"))self.postagger = Postagger()self.postagger.load(os.path.join(LTP_DIR, "pos.model"))self.parser = Parser()self.parser.load(os.path.join(LTP_DIR, "parser.model"))self.recognizer = NamedEntityRecognizer()self.recognizer.load(os.path.join(LTP_DIR, "ner.model"))self.labeller = SementicRoleLabeller()self.labeller.load(os.path.join(LTP_DIR, 'pisrl_win.model'))'''语义角色标注'''def format_labelrole(self, words, postags):arcs = self.parser.parse(words, postags)roles = self.labeller.label(words, postags, arcs)roles_dict = {}for role in roles:roles_dict[role.index] = {arg.name:[arg.name,arg.range.start, arg.range.end] for arg in role.arguments}return roles_dict'''句法分析---为句子中的每个词语维护一个保存句法依存儿子节点的字典'''def build_parse_child_dict(self, words, postags, arcs):child_dict_list = []format_parse_list = []for index in range(len(words)):child_dict = dict()for arc_index in range(len(arcs)):if arcs[arc_index].head == index+1:   #arcs的索引从1开始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]  # 提取依存父节点idrelation = [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)):# ['ATT', '李克强', 0, 'nh', '总理', 1, 'n']a = [relation[i], words[i], i, postags[i], heads[i], rely_id[i]-1, postags[rely_id[i]-1]]format_parse_list.append(a)return child_dict_list, format_parse_list'''parser主函数'''def parser_main(self, sentence):words = list(self.segmentor.segment(sentence))postags = list(self.postagger.postag(words))arcs = self.parser.parse(words, postags)child_dict_list, format_parse_list = self.build_parse_child_dict(words, postags, arcs)roles_dict = self.format_labelrole(words, postags)return words, postags, child_dict_list, roles_dict, format_parse_listif __name__ == '__main__':parse = LtpParser()sentence = '李克强总理今天来我家了,我感到非常荣幸'words, postags, child_dict_list, roles_dict, format_parse_list = parse.parser_main(sentence)print(words, len(words))print(postags, len(postags))print(child_dict_list, len(child_dict_list))print(roles_dict)print(format_parse_list, len(format_parse_list))

然后定义一个文件:triple_extraction.py ,这个文件和上面文件存在依赖关系。

#!/usr/bin/env python3
# coding: utf-8
# File: triple_extraction.py
# Author: lhy<lhy_in_blcu@126.com,https://huangyong.github.io>
# Date: 18-3-12
from sentence_parser import *
import reclass TripleExtractor:def __init__(self):self.parser = LtpParser()'''文章分句处理, 切分长句,冒号,分号,感叹号等做切分标识'''def split_sents(self, content):return [sentence for sentence in re.split(r'[??!!。;;::\n\r]', content) if sentence]'''利用语义角色标注,直接获取主谓宾三元组,基于A0,A1,A2'''def ruler1(self, words, postags, roles_dict, role_index):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) ifpostags[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) ifpostags[word_index][0] not in ['w', 'u', 'x'] and words[word_index]])if s  and o:return '1', [s, v, o]# elif 'A0' in role_info:#     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']])#     if s:#         return '2', [s, v]# elif 'A1' in role_info:#     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']])#     return '3', [v, o]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 = 0if tmp == 1:# 如果语义角色标记为空,则使用依存句法进行抽取# if postags[index] == 'v':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_e(words, postags, child_dict_list, child_dict['SBV'][0])e2 = self.complete_e(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_e(words, postags, child_dict_list, head - 1)r = words[index]e2 = self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0])temp_string = r + e2if temp_string == e1[:len(temp_string)]:e1 = e1[len(temp_string):]if temp_string not in e1:svos.append([e1, r, e2])# 含有介宾关系的主谓动补关系if 'SBV' in child_dict and 'CMP' in child_dict:e1 = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0])cmp_index = child_dict['CMP'][0]r = words[index] + words[cmp_index]if 'POB' in child_dict_list[cmp_index]:e2 = self.complete_e(words, postags, child_dict_list, child_dict_list[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]) + prefixreturn prefix + words[word_index] + postfix'''程序主控函数'''def triples_main(self, content):sentences = self.split_sents(content)svos = []for sentence in sentences:words, postags, child_dict_list, roles_dict, arcs = self.parser.parser_main(sentence)svo = self.ruler2(words, postags, child_dict_list, arcs, roles_dict)svos += svoreturn svos'''测试'''
def test():content1 = """环境很好,位置独立性很强,比较安静很切合店名,半闲居,偷得半日闲。点了比较经典的菜品,味道果然不错!烤乳鸽,超级赞赞赞,脆皮焦香,肉质细嫩,超好吃。艇仔粥料很足,香葱自己添加,很贴心。金钱肚味道不错,不过没有在广州吃的烂,牙口不好的慎点。凤爪很火候很好,推荐。最惊艳的是长寿菜,菜料十足,很新鲜,清淡又不乏味道,而且没有添加调料的味道,搭配的非常不错!"""content2 = """近日,一条男子高铁吃泡面被女乘客怒怼的视频引发热议。女子情绪激动,言辞激烈,大声斥责该乘客,称高铁上有规定不能吃泡面,质问其“有公德心吗”“没素质”。视频曝光后,该女子回应称,因自己的孩子对泡面过敏,曾跟这名男子沟通过,但对方执意不听,她才发泄不满,并称男子拍视频上传已侵犯了她的隐私权和名誉权,将采取法律手段。12306客服人员表示,高铁、动车上一般不卖泡面,但没有规定高铁、动车上不能吃泡面。高铁属于密封性较强的空间,每名乘客都有维护高铁内秩序,不破坏该空间内空气质量的义务。这也是乘客作为公民应当具备的基本品质。但是,在高铁没有明确禁止食用泡面等食物的背景下,以影响自己或孩子为由阻挠他人食用某种食品并厉声斥责,恐怕也超出了权利边界。当人们在公共场所活动时,不宜过分干涉他人权利,这样才能构建和谐美好的公共秩序。一般来说,个人的权利便是他人的义务,任何人不得随意侵犯他人权利,这是每个公民得以正常工作、生活的基本条件。如果权利可以被肆意侵犯而得不到救济,社会将无法运转,人们也没有幸福可言。如西谚所说,“你的权利止于我的鼻尖”,“你可以唱歌,但不能在午夜破坏我的美梦”。无论何种权利,其能够得以行使的前提是不影响他人正常生活,不违反公共利益和公序良俗。超越了这个边界,权利便不再为权利,也就不再受到保护。在“男子高铁吃泡面被怒怼”事件中,初一看,吃泡面男子可能侵犯公共场所秩序,被怒怼乃咎由自取,其实不尽然。虽然高铁属于封闭空间,但与禁止食用刺激性食品的地铁不同,高铁运营方虽然不建议食用泡面等刺激性食品,但并未作出禁止性规定。由此可见,即使食用泡面、榴莲、麻辣烫等食物可能产生刺激性味道,让他人不适,但是否食用该食品,依然取决于个人喜好,他人无权随意干涉乃至横加斥责。这也是此事件披露后,很多网友并未一边倒地批评食用泡面的男子,反而认为女乘客不该高声喧哗。现代社会,公民的义务一般分为法律义务和道德义务。如果某个行为被确定为法律义务,行为人必须遵守,一旦违反,无论是受害人抑或旁观群众,均有权制止、投诉、举报。违法者既会受到应有惩戒,也会受到道德谴责,积极制止者则属于应受鼓励的见义勇为。如果有人违反道德义务,则应受到道德和舆论谴责,并有可能被追究法律责任。如在公共场所随地吐痰、乱扔垃圾、脱掉鞋子、随意插队等。此时,如果行为人对他人的劝阻置之不理甚至行凶报复,无疑要受到严厉惩戒。当然,随着社会的发展,某些道德义务可能上升为法律义务。如之前,很多人对公共场所吸烟不以为然,烟民可以旁若无人地吞云吐雾。现在,要是还有人不识时务地在公共场所吸烟,必然将成为众矢之的。再回到“高铁吃泡面”事件,要是随着人们观念的更新,在高铁上不得吃泡面等可能产生刺激性气味的食物逐渐成为共识,或者上升到道德义务或法律义务。斥责、制止他人吃泡面将理直气壮,否则很难摆脱“矫情”,“将自我权利凌驾于他人权利之上”的嫌疑。在相关部门并未禁止在高铁上吃泡面的背景下,吃不吃泡面系个人权利或者个人私德,是不违反公共利益的个人正常生活的一部分。如果认为他人吃泡面让自己不适,最好是请求他人配合并加以感谢,而非站在道德制高点强制干预。只有每个人行使权利时不逾越边界,与他人沟通时好好说话,不过分自我地将幸福和舒适凌驾于他人之上,人与人之间才更趋于平等,公共生活才更趋向美好有序。"""content7 = """2021年5月29日至6月5日,由中国科学院大学学生会主办,果壳篮球协会协办的中国科学院大学篮球3v3挑战赛圆满落幕,共有来自各个校区的15支男子队伍及4支女子队伍报名参赛。比赛分为两个男子组和女子组两个组别,分小组赛和淘汰赛两个赛段。2021年5月29日,经过一天的紧张较量,女子组所有比赛落下帷幕。随着选手们在场上的一次次精准投篮、巧妙传球,场下喝彩声阵阵。值得一提的是,四支女子队伍中有两支队伍均为校队成员,她们也顺利进入决赛,进行最后的对决,比赛气氛被推向高潮。最终女子组比赛结果如下:“小聋瞎队”获第一名,“打完就解散队”获第二名,“女生1队”获第三名;凭借出色的个人表现,“小聋瞎队”来自中科院微电子所的张雨萌获得MVP(最有价值球员)。2021年6月5日,男子组八强赛在玉泉路校区篮球场拉开帷幕。相较于小组赛,八强赛的比赛更加精彩刺激。参赛队员们在场上积极进攻,全面防守,时而强打内线,时而高举高打,给大家带来了一场场精彩的对决,场下观众喝彩阵阵。经过近2小时的激烈角逐,最终,“一星四射队”和“糊人不唬人队”进入决赛,将进行冠亚军的争夺。中场休息时,来自中国科学院大学WINGS舞社的同学带来了精彩的表演。随着裁判员一声令下,本次3v3篮球赛决赛一触即发。双方球员迅速进入状态。他们默契配合、巧妙进攻,完成了一次又一次精彩的投篮。场外的观众也不甘示弱,一声声喝彩与鼓励响彻球场。最终经过一番激烈的角逐,“一星四射队”以21:15获取本届篮球3v3比赛的冠军。"""extractor = TripleExtractor()svos = extractor.triples_main(content7)print('svos', svos)test()

事件抽取结果:

实验结果对比

我自定定义了有效抽取比例P,类似于召回率R,定义为所有抽取事件中有效事件数量/所有抽取的事件数量。
做了个表格对比结果:

简单写了一下,有问题欢迎评论区留言或私信。

三元组事件抽取与简单代码实现相关推荐

  1. ACE2005数据集介绍、预处理及事件抽取

    ACE2005数据集介绍.预处理及事件抽取 参考链接:https://www.jianshu.com/p/71ed0d780210(感谢作者鲜芋牛奶西米爱solo,这篇博客介绍的非常详细) https ...

  2. PaddleNLP实战——LIC2021事件抽取任务基线(附代码)

    PaddleNLP实战--LIC2021事件抽取任务基线 目录 PaddleNLP实战--LIC2021事件抽取任务基线 一.篇章级事件抽取基线 评测方法 1.1 快速复现基线Step1:数据预处理并 ...

  3. java 手机声音提醒功能_Android基于广播事件机制实现简单定时提醒功能代码

    本文实例讲述了Android基于广播事件机制实现简单定时提醒功能代码.分享给大家供大家参考,具体如下: 1.Android广播事件机制 Android的广播事件处理类似于普通的事件处理.不同之处在于, ...

  4. 事件抽取文献整理(2020-2021)

    前言 之前研究事件抽取领域(NLP一个小领域信息抽取的子领域), 之前整理过一些文献. 事件抽取文献整理(2020-2021) + 事件抽取文献整理(2019) + 事件抽取文献整理(2018) + ...

  5. 【百度LIC2020事件抽取赛道】赛后小结(小白篇,大佬略过)

    讲在前面 这次比赛对我来说是首次参加百度举办的比赛,也是第一个事件抽取方向的比赛,整体来说熟悉事件抽取的模型,以及相关的操作为主,最高得分F1,0.796分,算是低分,在这里对整个比赛过程,以及自己的 ...

  6. 事件抽取中的“门面技术”:事件名称生成浅谈

    6月10日,"网信中国"微信公众号发布消息称:微博热搜榜.热门话题榜暂停更新一周,这使得很多热榜平台都受到波及,而在吃瓜之余,我们更进一步地思考热点榜单以及热点名称生成背后的技术, ...

  7. 篇章级的事件抽取——阅读笔记

    一.<DCFEE> <DCFEE: A Document-level Chinese Financial Event Extraction System based on Autom ...

  8. NLP工具——自制zero-shot事件抽取器

    NLP工具--自制zero-shot事件抽取器 0. 简介 1. 抽取全部潜在的事件 2. 抽取特定类型的事件 3. 结语 0. 简介 在事件抽取任务中,数据的获取是一件非常关键工作,由于数据标注的成 ...

  9. One-shot就能做事件抽取?ChatGPT在信息抽取上的强大应用

    One-shot就能做事件抽取?ChatGPT在信息抽取上的强大应用 0. 前言 1. 灵感 2. 实验 3. 结论 0. 前言 近期,OpenAI发布的chat GPT可谓是各种刷屏,很多人都在关注 ...

  10. NLP关系抽取和事件抽取

    关系抽取 关系抽取又称实体关系抽取,以实体识别为前提,在实体识别之后,判断给定文本中的任意两个实体是否构成事先定义好的关系,是文本内容理解的重要支撑技术之一,对于问答系统,智能客服和语义搜索等应用都十 ...

最新文章

  1. nginx mysql占用率高_nginx/mysql查看内存占用
  2. CASE_02 基于FPGA的数字钟万年历
  3. 基于django rest framework的mock server实践
  4. vray阴天室内_阴天有话:第1部分
  5. ASP.NET MVC 后台中 设置 js提示
  6. skywalking前端_skywalking实现分布式系统链路追踪
  7. tp5 linux定时,TP5 用cron实现linux定时任务
  8. 如何安装 macOS Monterey Public Beta?
  9. Java添加水印文字
  10. 软件需求文档模板及说明
  11. 假装认真的LaTeX学习笔记(2)—— LaTeX中如何修改字体(fontspec宏包使用方法)
  12. Mybatis注解实现DAO层
  13. 实在不行来瓶敌敌畏吧!Scrapy框架爬取某农药肥料网站+异步Mysql数据库存储
  14. 《Kotlin从零到精通Android开发》面世啦
  15. 【论文阅读】POI2Vec: Geographical Latent Representation for Predicting Future Visitors
  16. 键盘按钮keyCode对照表
  17. 关于币圈炒币 你必须要知道的风险
  18. 1218学习笔记——kvm上vm+vtpm(IMA)
  19. 国内主要遥感卫星数据介绍
  20. python专科就业前景_Python就业前景怎么样?

热门文章

  1. 上海电力学院计算机软件技术大作业,计算机硬件技术大作业报告2.doc
  2. openGL之API学习(七十)windows的opengl扩展wgl
  3. 给定字符串 s 和 t ,判断 s 是否为 t 的子序列
  4. goldendict无法导入字典
  5. 基于matlab的汽车牌照识别系统
  6. Banner 怎么实现轮播不同尺寸的图片
  7. 阿里云香港云服务器ECS适合什么场景?
  8. python操作格林威治时间
  9. python未知长度数组,python – 从具有未知维数的numpy数组中提取超立方体块
  10. ios : Provision Profile 添加设备 device的 udid