├── .gitignore ├── README.md ├── __pycache__ └── sentence_parser.cpython-36.pyc ├── baidu_svo_extract.py ├── pattern_event_triples.py ├── sentence_parser.py └── triple_extraction.py /.gitignore: -------------------------------------------------------------------------------- 1 | /ltp_data 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EventTriplesExtraction 2 |   EventTriplesExtraction based on dependency parser and semantic role labeling, 基于依存句法与语义角色标注的事件三元组抽取 3 |   文本表示一直是个重要问题,如何以清晰,简介的方式对一个文本信息进行有效表示是个长远的任务 4 | 我尝试过使用关键词,实体之间的关联关系,并使用textgrapher的方式进行展示,但以词作为文本信息单元表示这种效果不是特别好,所以,本项目想尝试从事件三元组的方式出发,对文本进行表示. 5 | 6 | # 方法1-基于ltp依存句法分析和语义角色标注的事件三元组抽取 7 | 8 | from triples_extraction import * 9 | extractor = TripleExtractor() 10 | svos = extractor.triples_main(content) 11 | print('svos', svos) 12 | 13 | # 方法1-测试结果 14 | content = '李克强总理今天来我家了,我感到非常荣幸' 15 | svos = [ 16 | ['李克强总理', '来', '我家'], 17 | ['我', '感到', '荣幸'] 18 | ] 19 | 20 | content = ''' 以色列国防军20日对加沙地带实施轰炸,造成3名巴勒斯坦武装人员死亡。此外,巴勒斯坦人与以色列士兵当天在加沙地带与以交界地区发生冲突,一名巴勒斯坦人被打死。当天的冲突还造成210名巴勒斯坦人受伤。 21 | 当天,数千名巴勒斯坦人在加沙地带边境地区继续“回归大游行”抗议活动。部分示威者燃烧轮胎,并向以军投掷石块、燃烧瓶等,驻守边境的以军士兵向示威人群发射催泪瓦斯并开枪射击。''' 22 | svos = [ 23 | ['以色列国防军', '实施', '轰炸'], 24 | ['冲突', '发生', '巴勒斯坦人与以色列士兵'], 25 | ['当天冲突', '造成', '受伤'], 26 | ['数千名巴勒斯坦人', '继续', '回归大游行抗议活动'], 27 | ['部分示威者', '投掷', '石块'], 28 | ['驻守边境以军士兵', '发射', '催泪瓦斯'] 29 | ] 30 | 31 | # 方法2-基于百度DDParser依存句法分析的事件三元组抽取 32 | 33 | from baidu_svo_extract import * 34 | extractor = SVOParser() 35 | svos = extractor.triples_main(content2) 36 | print('svos', svos) 37 | 38 | # 方法2-测试结果 39 | content = '李克强总理今天来我家了,我感到非常荣幸' 40 | svos = [ 41 | ['总理李克强', '来', '我家'], 42 | ['我', '感到', '荣幸']] 43 | ] 44 | 45 | content = ''' 以色列国防军20日对加沙地带实施轰炸,造成3名巴勒斯坦武装人员死亡。此外,巴勒斯坦人与以色列士兵当天在加沙地带与以交界地区发生冲突,一名巴勒斯坦人被打死。当天的冲突还造成210名巴勒斯坦人受伤。 46 | 当天,数千名巴勒斯坦人在加沙地带边境地区继续“回归大游行”抗议活动。部分示威者燃烧轮胎,并向以军投掷石块、燃烧瓶等,驻守边境的以军士兵向示威人群发射催泪瓦斯并开枪射击。''' 47 | svos = [ 48 | ['20日', '实施', '轰炸'] 49 | ['当天冲突', '造成', '210名'] 50 | ['巴勒斯坦人', '回归', '大游行'] 51 | ['部分示威者', '燃烧', '轮胎'] 52 | ] 53 | 54 | # 方法3-基于词性模板规则的事件三元组抽取 55 | 56 | from pattern_event_triples import * 57 | extractor = ExtractEvent() 58 | events, spos = handler.phrase_ip(content1) 59 | spos = [i for i in spos if i[0] and i[2]] 60 | print('svos', svos) 61 | 62 | # 方法3-测试结果 63 | content = '李克强总理今天来我家了,我感到非常荣幸' 64 | svos = [ 65 | ('李克强总理', '来', '我家') 66 | ('李克强', '感到', '荣幸') 67 | ] 68 | 69 | content = ''' 以色列国防军20日对加沙地带实施轰炸,造成3名巴勒斯坦武装人员死亡。此外,巴勒斯坦人与以色列士兵当天在加沙地带与以交界地区发生冲突,一名巴勒斯坦人被打死。当天的冲突还造成210名巴勒斯坦人受伤。 70 | 当天,数千名巴勒斯坦人在加沙地带边境地区继续“回归大游行”抗议活动。部分示威者燃烧轮胎,并向以军投掷石块、燃烧瓶等,驻守边境的以军士兵向示威人群发射催泪瓦斯并开枪射击。''' 71 | svos = [ 72 | ('数千名巴勒斯坦人在加沙地带边境地区', '继续回归游行', '抗议活动') 73 | ('部分示威者', '燃烧', '轮胎') 74 | ('边境', '以军', '士兵向示威人群发射催泪瓦斯开枪射击') 75 | ('士兵向示威人群', '发射', '催泪瓦斯开枪射击') 76 | ] 77 | 78 | 79 | 80 | # 总结 81 | 82 | 本项目公开了基于ltp句法分析和语义角色标注、基于百度DDParser以及基于词法模式的事件三元组抽取方法,并给了实验示例。可以得到以下结论: 83 | 1、LTP在DDParser之外,还提供了语义角色标注的功能,这个可以用于事件三元组抽取的有效补充。 84 | 2、LTP速度比DDParser要快。 85 | 3、基于词法模式的事件三元组抽取速度最快,但效果取决于分词、词性标注性能。 86 | 4、基于词法模式,可以得到语义更长的三元组元素信息。 87 | 88 | 89 | If any question about the project or me ,see https://liuhuanyong.github.io/ 90 | 如有自然语言处理、知识图谱、事理图谱、社会计算、语言资源建设等问题或合作,可联系我: 91 | 1、我的github项目介绍:https://liuhuanyong.github.io 92 | 2、我的csdn博客:https://blog.csdn.net/lhy2014 93 | 3、about me:刘焕勇,中国科学院软件研究所,lhy_in_blcu@126.com 94 | -------------------------------------------------------------------------------- /__pycache__/sentence_parser.cpython-36.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liuhuanyong/EventTriplesExtraction/000f94ca3e732dad4200e0f547f2336d6be8ce55/__pycache__/sentence_parser.cpython-36.pyc -------------------------------------------------------------------------------- /baidu_svo_extract.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: sentence_parser.py 4 | # Author: lhy 5 | # Date: 18-3-10 6 | 7 | import os, re 8 | from ddparser import DDParser 9 | 10 | class SVOParser: 11 | def __init__(self): 12 | self.parser = DDParser(use_pos=True) 13 | print('loaded model') 14 | 15 | '''文章分句处理, 切分长句,冒号,分号,感叹号等做切分标识''' 16 | 17 | def split_sents(self, content): 18 | return [sentence for sentence in re.split(r'[??!!。;;::\n\r]', content) if sentence] 19 | 20 | '''句法分析---为句子中的每个词语维护一个保存句法依存儿子节点的字典''' 21 | def build_parse_child_dict(self, words, postags, rel_id, relation): 22 | child_dict_list = [] 23 | format_parse_list = [] 24 | for index in range(len(words)): 25 | child_dict = dict() 26 | for arc_index in range(len(rel_id)): 27 | if rel_id[arc_index] == index+1: #arcs的索引从1开始 28 | if rel_id[arc_index] in child_dict: 29 | child_dict[relation[arc_index]].append(arc_index) 30 | else: 31 | child_dict[relation[arc_index]] = [] 32 | child_dict[relation[arc_index]].append(arc_index) 33 | child_dict_list.append(child_dict) 34 | heads = ['Root' if id == 0 else words[id - 1] for id in rel_id] # 匹配依存父节点词语 35 | for i in range(len(words)): 36 | # ['ATT', '李克强', 0, 'nh', '总理', 1, 'n'] 37 | a = [relation[i], words[i], i, postags[i], heads[i], rel_id[i]-1, postags[rel_id[i]-1]] 38 | format_parse_list.append(a) 39 | 40 | return child_dict_list, format_parse_list 41 | 42 | '''parser主函数''' 43 | def parser_main(self, sentence): 44 | res = self.parser.parse(sentence, )[0] 45 | words = res["word"] 46 | postags = res["postag"] 47 | rel_id = res["head"] 48 | relation = res["deprel"] 49 | 50 | child_dict_list, format_parse_list = self.build_parse_child_dict(words, postags, rel_id, relation) 51 | return words, postags, child_dict_list, format_parse_list 52 | 53 | """将所有的ATT进行合并""" 54 | def merge_ATT(self, words, postags, format_parse_list): 55 | words_ = words 56 | retain_nodes = set() 57 | ATTs = [] 58 | ATT = [] 59 | format_parse_list_ = [] 60 | for parse in format_parse_list: 61 | dep = parse[0] 62 | if dep in ['ATT', 'ADV']: 63 | ATT += [parse[2], parse[5]] 64 | else: 65 | if ATT: 66 | body = ''.join([words[i] for i in sorted(set(ATT))]) 67 | ATTs.append(body) 68 | retain_nodes.add(sorted(set(ATT))[-1]) 69 | words_[sorted(set(ATT))[-1]] = body 70 | else: 71 | retain_nodes.add(parse[2]) 72 | ATT = [] 73 | for indx, parse in enumerate(format_parse_list): 74 | if indx in retain_nodes: 75 | parse_ = [parse[0], words_[indx], indx, postags[indx], words_[parse[5]], parse[5], postags[parse[5]]] 76 | format_parse_list_.append(parse_) 77 | return words_, postags, format_parse_list_, retain_nodes 78 | 79 | """基于该结果,提取三元组""" 80 | def extract(self, words, postags, child_dict_list, arcs, retain_nodes): 81 | svos = [] 82 | for index in range(len(postags)): 83 | if index not in retain_nodes: 84 | continue 85 | tmp = 1 86 | # 如果语义角色标记为空,则使用依存句法进行抽取 87 | if postags[index]: 88 | # 抽取以谓词为中心的事实三元组 89 | child_dict = child_dict_list[index] 90 | # 主谓宾 91 | if 'SBV' in child_dict and 'VOB' in child_dict: 92 | # e1s = self.expand_e(words, postags, child_dict_list, child_dict['SBV'][0]) 93 | # e2s = self.expand_e(words, postags, child_dict_list, child_dict['VOB'][0]) 94 | r = words[index] 95 | e1 = words[child_dict['SBV'][0]] 96 | e2 = words[child_dict['VOB'][0]] 97 | if e1.replace(' ', '') and e2.replace(' ', ''): 98 | svos.append([e1, r, e2]) 99 | 100 | # 含有介宾关系的主谓动补关系 101 | if 'SBV' in child_dict and 'CMP' in child_dict: 102 | e1 = words[child_dict['SBV'][0]] 103 | cmp_index = child_dict['CMP'][0] 104 | r = words[index] + words[cmp_index] 105 | if 'POB' in child_dict_list[cmp_index]: 106 | e2 = words[child_dict_list[cmp_index]['POB'][0]] 107 | if e1.replace(' ', '') and e2.replace(' ', ''): 108 | svos.append([e1, r, e2]) 109 | 110 | return svos 111 | 112 | '''三元组抽取主函数''' 113 | 114 | def ruler2(self, words, postags, child_dict_list, arcs): 115 | svos = [] 116 | for index in range(len(postags)): 117 | tmp = 1 118 | # 先借助语义角色标注的结果,进行三元组抽取 119 | if tmp == 1: 120 | # 如果语义角色标记为空,则使用依存句法进行抽取 121 | # if postags[index] == 'v': 122 | if postags[index]: 123 | # 抽取以谓词为中心的事实三元组 124 | child_dict = child_dict_list[index] 125 | # 主谓宾 126 | if 'SBV' in child_dict and 'VOB' in child_dict: 127 | r = words[index] 128 | e1 = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0]) 129 | e2 = self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0]) 130 | if e1.replace(' ', '') and e2.replace(' ', ''): 131 | svos.append([e1, r, e2]) 132 | 133 | # 定语后置,动宾关系 134 | relation = arcs[index][0] 135 | head = arcs[index][2] 136 | if relation == 'ATT': 137 | if 'VOB' in child_dict: 138 | e1 = self.complete_e(words, postags, child_dict_list, head - 1) 139 | r = words[index] 140 | e2 = self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0]) 141 | temp_string = r + e2 142 | if temp_string == e1[:len(temp_string)]: 143 | e1 = e1[len(temp_string):] 144 | if temp_string not in e1: 145 | if e1.replace(' ', '') and e2.replace(' ', ''): 146 | svos.append([e1, r, e2]) 147 | 148 | # 含有介宾关系的主谓动补关系 149 | if 'SBV' in child_dict and 'CMP' in child_dict: 150 | e1 = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0]) 151 | cmp_index = child_dict['CMP'][0] 152 | r = words[index] + words[cmp_index] 153 | if 'POB' in child_dict_list[cmp_index]: 154 | e2 = self.complete_e(words, postags, child_dict_list, child_dict_list[cmp_index]['POB'][0]) 155 | if e1.replace(' ', '') and e2.replace(' ', ''): 156 | svos.append([e1, r, e2]) 157 | return svos 158 | 159 | '''对找出的主语或者宾语进行扩展''' 160 | 161 | def complete_e(self, words, postags, child_dict_list, word_index): 162 | child_dict = child_dict_list[word_index] 163 | prefix = '' 164 | if 'ATT' in child_dict: 165 | for i in range(len(child_dict['ATT'])): 166 | prefix += self.complete_e(words, postags, child_dict_list, child_dict['ATT'][i]) 167 | postfix = '' 168 | if postags[word_index] == 'v': 169 | if 'VOB' in child_dict: 170 | postfix += self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0]) 171 | if 'SBV' in child_dict: 172 | prefix = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0]) + prefix 173 | 174 | return prefix + words[word_index] + postfix 175 | 176 | '''程序主控函数''' 177 | 178 | def triples_main(self, content): 179 | sentences = self.split_sents(content) 180 | svos = [] 181 | for sentence in sentences: 182 | print(sentence) 183 | words, postags, child_dict_list, arcs = self.parser_main(sentence) 184 | svo = self.ruler2(words, postags, child_dict_list, arcs) 185 | svos += svo 186 | 187 | return svos 188 | 189 | '''测试''' 190 | def test(): 191 | content1 = """环境很好,位置独立性很强,比较安静很切合店名,半闲居,偷得半日闲。点了比较经典的菜品,味道果然不错!烤乳鸽,超级赞赞赞,脆皮焦香,肉质细嫩,超好吃。艇仔粥料很足,香葱自己添加,很贴心。金钱肚味道不错,不过没有在广州吃的烂,牙口不好的慎点。凤爪很火候很好,推荐。最惊艳的是长寿菜,菜料十足,很新鲜,清淡又不乏味道,而且没有添加调料的味道,搭配的非常不错!""" 192 | content2 = """近日,一条男子高铁吃泡面被女乘客怒怼的视频引发热议。女子情绪激动,言辞激烈,大声斥责该乘客,称高铁上有规定不能吃泡面,质问其“有公德心吗”“没素质”。视频曝光后,该女子回应称,因自己的孩子对泡面过敏,曾跟这名男子沟通过,但对方执意不听,她才发泄不满,并称男子拍视频上传已侵犯了她的隐私权和名誉权,将采取法律手段。12306客服人员表示,高铁、动车上一般不卖泡面,但没有规定高铁、动车上不能吃泡面。 193 | 高铁属于密封性较强的空间,每名乘客都有维护高铁内秩序,不破坏该空间内空气质量的义务。这也是乘客作为公民应当具备的基本品质。但是,在高铁没有明确禁止食用泡面等食物的背景下,以影响自己或孩子为由阻挠他人食用某种食品并厉声斥责,恐怕也超出了权利边界。当人们在公共场所活动时,不宜过分干涉他人权利,这样才能构建和谐美好的公共秩序。 194 | 一般来说,个人的权利便是他人的义务,任何人不得随意侵犯他人权利,这是每个公民得以正常工作、生活的基本条件。如果权利可以被肆意侵犯而得不到救济,社会将无法运转,人们也没有幸福可言。如西谚所说,“你的权利止于我的鼻尖”,“你可以唱歌,但不能在午夜破坏我的美梦”。无论何种权利,其能够得以行使的前提是不影响他人正常生活,不违反公共利益和公序良俗。超越了这个边界,权利便不再为权利,也就不再受到保护。 195 | 在“男子高铁吃泡面被怒怼”事件中,初一看,吃泡面男子可能侵犯公共场所秩序,被怒怼乃咎由自取,其实不尽然。虽然高铁属于封闭空间,但与禁止食用刺激性食品的地铁不同,高铁运营方虽然不建议食用泡面等刺激性食品,但并未作出禁止性规定。由此可见,即使食用泡面、榴莲、麻辣烫等食物可能产生刺激性味道,让他人不适,但是否食用该食品,依然取决于个人喜好,他人无权随意干涉乃至横加斥责。这也是此事件披露后,很多网友并未一边倒地批评食用泡面的男子,反而认为女乘客不该高声喧哗。 196 | 现代社会,公民的义务一般分为法律义务和道德义务。如果某个行为被确定为法律义务,行为人必须遵守,一旦违反,无论是受害人抑或旁观群众,均有权制止、投诉、举报。违法者既会受到应有惩戒,也会受到道德谴责,积极制止者则属于应受鼓励的见义勇为。如果有人违反道德义务,则应受到道德和舆论谴责,并有可能被追究法律责任。如在公共场所随地吐痰、乱扔垃圾、脱掉鞋子、随意插队等。此时,如果行为人对他人的劝阻置之不理甚至行凶报复,无疑要受到严厉惩戒。 197 | 当然,随着社会的发展,某些道德义务可能上升为法律义务。如之前,很多人对公共场所吸烟不以为然,烟民可以旁若无人地吞云吐雾。现在,要是还有人不识时务地在公共场所吸烟,必然将成为众矢之的。 198 | 再回到“高铁吃泡面”事件,要是随着人们观念的更新,在高铁上不得吃泡面等可能产生刺激性气味的食物逐渐成为共识,或者上升到道德义务或法律义务。斥责、制止他人吃泡面将理直气壮,否则很难摆脱“矫情”,“将自我权利凌驾于他人权利之上”的嫌疑。 199 | 在相关部门并未禁止在高铁上吃泡面的背景下,吃不吃泡面系个人权利或者个人私德,是不违反公共利益的个人正常生活的一部分。如果认为他人吃泡面让自己不适,最好是请求他人配合并加以感谢,而非站在道德制高点强制干预。只有每个人行使权利时不逾越边界,与他人沟通时好好说话,不过分自我地将幸福和舒适凌驾于他人之上,人与人之间才更趋于平等,公共生活才更趋向美好有序。""" 200 | content3 = '''(原标题:央视独家采访:陕西榆林产妇坠楼事件在场人员还原事情经过) 201 | 央视新闻客户端11月24日消息,2017年8月31日晚,在陕西省榆林市第一医院绥德院区,产妇马茸茸在待产时,从医院五楼坠亡。事发后,医院方面表示,由于家属多次拒绝剖宫产,最终导致产妇难忍疼痛跳楼。但是产妇家属却声称,曾向医生多次提出剖宫产被拒绝。 202 | 事情经过究竟如何,曾引起舆论纷纷,而随着时间的推移,更多的反思也留给了我们,只有解决了这起事件中暴露出的一些问题,比如患者的医疗选择权,人们对剖宫产和顺产的认识问题等,这样的悲剧才不会再次发生。央视记者找到了等待产妇的家属,主治医生,病区主任,以及当时的两位助产师,一位实习医生,希望通过他们的讲述,更准确地还原事情经过。 203 | 产妇待产时坠亡,事件有何疑点。公安机关经过调查,排除他杀可能,初步认定马茸茸为跳楼自杀身亡。马茸茸为何会在医院待产期间跳楼身亡,这让所有人的目光都聚焦到了榆林第一医院,这家在当地人心目中数一数二的大医院。 204 | 就这起事件来说,如何保障患者和家属的知情权,如何让患者和医生能够多一份实质化的沟通?这就需要与之相关的法律法规更加的细化、人性化并且充满温度。用这种温度来消除孕妇对未知的恐惧,来保障医患双方的权益,迎接新生儿平安健康地来到这个世界。''' 205 | content4 = '李克强总理今天来我家了,我感到非常荣幸' 206 | content5 = ''' 以色列国防军20日对加沙地带实施轰炸,造成3名巴勒斯坦武装人员死亡。此外,巴勒斯坦人与以色列士兵当天在加沙地带与以交界地区发生冲突,一名巴勒斯坦人被打死。当天的冲突还造成210名巴勒斯坦人受伤。 207 | 当天,数千名巴勒斯坦人在加沙地带边境地区继续“回归大游行”抗议活动。部分示威者燃烧轮胎,并向以军投掷石块、燃烧瓶等,驻守边境的以军士兵向示威人群发射催泪瓦斯并开枪射击。''' 208 | extractor = SVOParser() 209 | svos = extractor.triples_main(content2) 210 | print('svos', svos) 211 | for svo in svos: 212 | print(svo) 213 | 214 | 215 | 216 | if __name__ == '__main__': 217 | print("loading model...") 218 | test() 219 | 220 | -------------------------------------------------------------------------------- /pattern_event_triples.py: -------------------------------------------------------------------------------- 1 | # coding=utf-8 2 | import re, os 3 | import jieba.posseg as pseg 4 | 5 | class ExtractEvent: 6 | def __init__(self): 7 | self.map_dict = self.load_mapdict() 8 | self.minlen = 2 9 | self.maxlen = 30 10 | self.keywords_num = 20 11 | self.limit_score = 10 12 | self.IP = "(([NERMQ]*P*[ABDP]*)*([ABDV]{1,})*([NERMQ]*)*([VDAB]$)?([NERMQ]*)*([VDAB]$)?)*" 13 | self.IP = "([NER]*([PMBQADP]*[NER]*)*([VPDA]{1,}[NEBRVMQDA]*)*)" 14 | self.MQ = '[DP]*M{1,}[Q]*([VN]$)?' 15 | self.VNP = 'V*N{1,}' 16 | self.NP = '[NER]{1,}' 17 | self.REN = 'R{2,}' 18 | self.VP = 'P?(V|A$|D$){1,}' 19 | self.PP = 'P?[NERMQ]{1,}' 20 | self.SPO_n = "n{1,}" 21 | self.SPO_v = "v{1,}" 22 | self.stop_tags = {'u', 'wp', 'o', 'y', 'w', 'f', 'u', 'c', 'uj', 'nd', 't', 'x'} 23 | self.combine_words = {"首先", "然后", "之前", "之后", "其次", "接着"} 24 | 25 | """构建映射字典""" 26 | def load_mapdict(self): 27 | tag_dict = { 28 | 'B': 'b'.split(), # 时间词 29 | 'A': 'a d'.split(), # 时间词 30 | 'D': "d".split(), # 限定词 31 | 'N': "n j s zg en l r".split(), #名词 32 | "E": "nt nz ns an ng".split(), #实体词 33 | "R": "nr".split(), #人物 34 | 'G': "g".split(), #语素 35 | 'V': "vd v va i vg vn g".split(), #动词 36 | 'P': "p f".split(), #介词 37 | "M": "m t".split(), #数词 38 | "Q": "q".split(), #量词 39 | "v": "V".split(), #动词短语 40 | "n": "N".split(), #名词介宾短语 41 | } 42 | map_dict = {} 43 | for flag, tags in tag_dict.items(): 44 | for tag in tags: 45 | map_dict[tag] = flag 46 | return map_dict 47 | 48 | """根据定义的标签,对词性进行标签化""" 49 | def transfer_tags(self, postags): 50 | tags = [self.map_dict.get(tag[:2], 'W') for tag in postags] 51 | return ''.join(tags) 52 | 53 | """抽取出指定长度的ngram""" 54 | def extract_ngram(self, pos_seq, regex): 55 | ss = self.transfer_tags(pos_seq) 56 | def gen(): 57 | for s in range(len(ss)): 58 | for n in range(self.minlen, 1 + min(self.maxlen, len(ss) - s)): 59 | e = s + n 60 | substr = ss[s:e] 61 | if re.match(regex + "$", substr): 62 | yield (s, e) 63 | return list(gen()) 64 | 65 | '''抽取ngram''' 66 | def extract_sentgram(self, pos_seq, regex): 67 | ss = self.transfer_tags(pos_seq) 68 | def gen(): 69 | for m in re.finditer(regex, ss): 70 | yield (m.start(), m.end()) 71 | return list(gen()) 72 | 73 | """指示代词替换,消解处理""" 74 | def cite_resolution(self, words, postags, persons): 75 | if not persons and 'r' not in set(postags): 76 | return words, postags 77 | elif persons and 'r' in set(postags): 78 | cite_index = postags.index('r') 79 | if words[cite_index] in {"其", "他", "她", "我"}: 80 | words[cite_index] = persons[-1] 81 | postags[cite_index] = 'nr' 82 | elif 'r' in set(postags): 83 | cite_index = postags.index('r') 84 | if words[cite_index] in {"为何", "何", "如何"}: 85 | postags[cite_index] = 'w' 86 | return words, postags 87 | 88 | """抽取量词性短语""" 89 | def extract_mqs(self, wds, postags): 90 | phrase_tokspans = self.extract_sentgram(postags, self.MQ) 91 | if not phrase_tokspans: 92 | return [] 93 | phrases = [''.join(wds[i[0]:i[1]])for i in phrase_tokspans] 94 | return phrases 95 | 96 | '''抽取动词性短语''' 97 | def get_ips(self, wds, postags): 98 | ips = [] 99 | phrase_tokspans = self.extract_sentgram(postags, self.IP) 100 | if not phrase_tokspans: 101 | return [] 102 | phrases = [''.join(wds[i[0]:i[1]])for i in phrase_tokspans] 103 | phrase_postags = [''.join(postags[i[0]:i[1]]) for i in phrase_tokspans] 104 | for phrase, phrase_postag_ in zip(phrases, phrase_postags): 105 | if not phrase: 106 | continue 107 | phrase_postags = ''.join(phrase_postag_).replace('m', '').replace('q','').replace('a', '').replace('t', '') 108 | if phrase_postags.startswith('n') or phrase_postags.startswith('j'): 109 | has_subj = 1 110 | else: 111 | has_subj = 0 112 | ips.append((has_subj, phrase)) 113 | return ips 114 | 115 | """分短句处理""" 116 | def split_short_sents(self, text): 117 | return [i for i in re.split(r'[,,]', text) if len(i)>2] 118 | """分段落""" 119 | def split_paras(self, text): 120 | return [i for i in re.split(r'[\n\r]', text) if len(i) > 4] 121 | 122 | """分长句处理""" 123 | def split_long_sents(self, text): 124 | return [i for i in re.split(r'[;。:; :??!!【】▲丨|]', text) if len(i) > 4] 125 | 126 | """移出噪声数据""" 127 | def remove_punc(self, text): 128 | text = text.replace('\u3000', '').replace("'", '').replace('“', '').replace('”', '').replace('▲','').replace('” ', "”") 129 | tmps = re.findall('[\(|(][^\((\))]*[\)|)]', text) 130 | for tmp in tmps: 131 | text = text.replace(tmp, '') 132 | return text 133 | 134 | """保持专有名词""" 135 | def zhuanming(self, text): 136 | books = re.findall('[<《][^《》]*[》>]', text) 137 | return books 138 | 139 | """对人物类词语进行修正""" 140 | def modify_nr(self, wds, postags): 141 | phrase_tokspans = self.extract_sentgram(postags, self.REN) 142 | wds_seq = ' '.join(wds) 143 | pos_seq = ' '.join(postags) 144 | if not phrase_tokspans: 145 | return wds, postags 146 | else: 147 | wd_phrases = [' '.join(wds[i[0]:i[1]]) for i in phrase_tokspans] 148 | postag_phrases = [' '.join(postags[i[0]:i[1]]) for i in phrase_tokspans] 149 | for wd_phrase in wd_phrases: 150 | tmp = wd_phrase.replace(' ', '') 151 | wds_seq = wds_seq.replace(wd_phrase, tmp) 152 | for postag_phrase in postag_phrases: 153 | pos_seq = pos_seq.replace(postag_phrase, 'nr') 154 | words = [i for i in wds_seq.split(' ') if i] 155 | postags = [i for i in pos_seq.split(' ') if i] 156 | return words, postags 157 | 158 | """对人物类词语进行修正""" 159 | def modify_duplicate(self, wds, postags, regex, tag): 160 | phrase_tokspans = self.extract_sentgram(postags, regex) 161 | wds_seq = ' '.join(wds) 162 | pos_seq = ' '.join(postags) 163 | if not phrase_tokspans: 164 | return wds, postags 165 | else: 166 | wd_phrases = [' '.join(wds[i[0]:i[1]]) for i in phrase_tokspans] 167 | postag_phrases = [' '.join(postags[i[0]:i[1]]) for i in phrase_tokspans] 168 | for wd_phrase in wd_phrases: 169 | tmp = wd_phrase.replace(' ', '') 170 | wds_seq = wds_seq.replace(wd_phrase, tmp) 171 | for postag_phrase in postag_phrases: 172 | pos_seq = pos_seq.replace(postag_phrase, tag) 173 | words = [i for i in wds_seq.split(' ') if i] 174 | postags = [i for i in pos_seq.split(' ') if i] 175 | return words, postags 176 | 177 | '''对句子进行分词处理''' 178 | def cut_wds(self, sent): 179 | wds = list(pseg.cut(sent)) 180 | postags = [w.flag for w in wds] 181 | words = [w.word for w in wds] 182 | return self.modify_nr(words, postags) 183 | 184 | """移除噪声词语""" 185 | def clean_wds(self, words, postags): 186 | wds = [] 187 | poss =[] 188 | for wd, postag in zip(words, postags): 189 | if postag[0].lower() in self.stop_tags: 190 | continue 191 | wds.append(wd) 192 | poss.append(postag[:2]) 193 | return wds, poss 194 | 195 | """检测是否成立, 肯定需要包括名词""" 196 | def check_flag(self, postags): 197 | if not {"v", 'a', 'i'}.intersection(postags): 198 | return 0 199 | return 1 200 | 201 | """识别出人名实体""" 202 | def detect_person(self, words, postags): 203 | persons = [] 204 | for wd, postag in zip(words, postags): 205 | if postag == 'nr': 206 | persons.append(wd) 207 | return persons 208 | 209 | """识别出名词性短语""" 210 | def get_nps(self, wds, postags): 211 | phrase_tokspans = self.extract_sentgram(postags, self.NP) 212 | if not phrase_tokspans: 213 | return [],[] 214 | phrases_np = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans] 215 | return phrase_tokspans, phrases_np 216 | 217 | """识别出介宾短语""" 218 | def get_pps(self, wds, postags): 219 | phrase_tokspans = self.extract_sentgram(postags, self.PP) 220 | if not phrase_tokspans: 221 | return [],[] 222 | phrases_pp = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans] 223 | return phrase_tokspans, phrases_pp 224 | 225 | """识别出动词短语""" 226 | def get_vps(self, wds, postags): 227 | phrase_tokspans = self.extract_sentgram(postags, self.VP) 228 | if not phrase_tokspans: 229 | return [],[] 230 | phrases_vp = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans] 231 | return phrase_tokspans, phrases_vp 232 | 233 | """抽取名动词性短语""" 234 | def get_vnps(self, s): 235 | wds, postags = self.cut_wds(s) 236 | if not postags: 237 | return [], [] 238 | if not (postags[-1].endswith("n") or postags[-1].endswith("l") or postags[-1].endswith("i")): 239 | return [], [] 240 | phrase_tokspans = self.extract_sentgram(postags, self.VNP) 241 | if not phrase_tokspans: 242 | return [], [] 243 | phrases_vnp = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans] 244 | phrase_tokspans2 = self.extract_sentgram(postags, self.NP) 245 | if not phrase_tokspans2: 246 | return [], [] 247 | phrases_np = [''.join(wds[i[0]:i[1]]) for i in phrase_tokspans2] 248 | return phrases_vnp, phrases_np 249 | 250 | """提取短语""" 251 | def phrase_ip(self, content): 252 | spos = [] 253 | events = [] 254 | content = self.remove_punc(content) 255 | paras = self.split_paras(content) 256 | for para in paras: 257 | long_sents = self.split_long_sents(para) 258 | for long_sent in long_sents: 259 | persons = [] 260 | short_sents = self.split_short_sents(long_sent) 261 | for sent in short_sents: 262 | words, postags = self.cut_wds(sent) 263 | person = self.detect_person(words, postags) 264 | words, postags = self.cite_resolution(words, postags, persons) 265 | words, postags = self.clean_wds(words, postags) 266 | #print(words,postags) 267 | ips = self.get_ips(words, postags) 268 | persons += person 269 | for ip in ips: 270 | events.append(ip[1]) 271 | wds_tmp = [] 272 | postags_tmp = [] 273 | words, postags = self.cut_wds(ip[1]) 274 | verb_tokspans, verbs = self.get_vps(words, postags) 275 | pp_tokspans, pps = self.get_pps(words, postags) 276 | tmp_dict = {str(verb[0]) + str(verb[1]): ['V', verbs[idx]] for idx, verb in enumerate(verb_tokspans)} 277 | pp_dict = {str(pp[0]) + str(pp[1]): ['N', pps[idx]] for idx, pp in enumerate(pp_tokspans)} 278 | tmp_dict.update(pp_dict) 279 | sort_keys = sorted([int(i) for i in tmp_dict.keys()]) 280 | for i in sort_keys: 281 | if i < 10: 282 | i = '0' + str(i) 283 | wds_tmp.append(tmp_dict[str(i)][-1]) 284 | postags_tmp.append(tmp_dict[str(i)][0]) 285 | wds_tmp, postags_tmp = self.modify_duplicate(wds_tmp, postags_tmp, self.SPO_v, 'V') 286 | wds_tmp, postags_tmp = self.modify_duplicate(wds_tmp, postags_tmp, self.SPO_n, 'N') 287 | if len(postags_tmp) < 2: 288 | continue 289 | seg_index = [] 290 | i = 0 291 | for wd, postag in zip(wds_tmp, postags_tmp): 292 | if postag == 'V': 293 | seg_index.append(i) 294 | i += 1 295 | spo = [] 296 | for indx, seg_indx in enumerate(seg_index): 297 | if indx == 0: 298 | pre_indx = 0 299 | else: 300 | pre_indx = seg_index[indx-1] 301 | if pre_indx < 0: 302 | pre_indx = 0 303 | if seg_indx == 0: 304 | spo.append(('', wds_tmp[seg_indx], ''.join(wds_tmp[seg_indx+1:]))) 305 | elif seg_indx > 0 and indx < 1: 306 | spo.append((''.join(wds_tmp[:seg_indx]), wds_tmp[seg_indx], ''.join(wds_tmp[seg_indx + 1:]))) 307 | else: 308 | spo.append((''.join(wds_tmp[pre_indx+1:seg_indx]), wds_tmp[seg_indx], ''.join(wds_tmp[seg_indx + 1:]))) 309 | spos += spo 310 | 311 | return events, spos 312 | 313 | if __name__ == '__main__': 314 | import time 315 | handler = ExtractEvent() 316 | start = time.time() 317 | content1 = """环境很好,位置独立性很强,比较安静很切合店名,半闲居,偷得半日闲。点了比较经典的菜品,味道果然不错!烤乳鸽,超级赞赞赞,脆皮焦香,肉质细嫩,超好吃。艇仔粥料很足,香葱自己添加,很贴心。金钱肚味道不错,不过没有在广州吃的烂,牙口不好的慎点。凤爪很火候很好,推荐。最惊艳的是长寿菜,菜料十足,很新鲜,清淡又不乏味道,而且没有添加调料的味道,搭配的非常不错!""" 318 | content2 = """近日,一条男子高铁吃泡面被女乘客怒怼的视频引发热议。女子情绪激动,言辞激烈,大声斥责该乘客,称高铁上有规定不能吃泡面,质问其“有公德心吗”“没素质”。视频曝光后,该女子回应称,因自己的孩子对泡面过敏,曾跟这名男子沟通过,但对方执意不听,她才发泄不满,并称男子拍视频上传已侵犯了她的隐私权和名誉权,将采取法律手段。12306客服人员表示,高铁、动车上一般不卖泡面,但没有规定高铁、动车上不能吃泡面。 319 | 高铁属于密封性较强的空间,每名乘客都有维护高铁内秩序,不破坏该空间内空气质量的义务。这也是乘客作为公民应当具备的基本品质。但是,在高铁没有明确禁止食用泡面等食物的背景下,以影响自己或孩子为由阻挠他人食用某种食品并厉声斥责,恐怕也超出了权利边界。当人们在公共场所活动时,不宜过分干涉他人权利,这样才能构建和谐美好的公共秩序。 320 | 一般来说,个人的权利便是他人的义务,任何人不得随意侵犯他人权利,这是每个公民得以正常工作、生活的基本条件。如果权利可以被肆意侵犯而得不到救济,社会将无法运转,人们也没有幸福可言。如西谚所说,“你的权利止于我的鼻尖”,“你可以唱歌,但不能在午夜破坏我的美梦”。无论何种权利,其能够得以行使的前提是不影响他人正常生活,不违反公共利益和公序良俗。超越了这个边界,权利便不再为权利,也就不再受到保护。 321 | 在“男子高铁吃泡面被怒怼”事件中,初一看,吃泡面男子可能侵犯公共场所秩序,被怒怼乃咎由自取,其实不尽然。虽然高铁属于封闭空间,但与禁止食用刺激性食品的地铁不同,高铁运营方虽然不建议食用泡面等刺激性食品,但并未作出禁止性规定。由此可见,即使食用泡面、榴莲、麻辣烫等食物可能产生刺激性味道,让他人不适,但是否食用该食品,依然取决于个人喜好,他人无权随意干涉乃至横加斥责。这也是此事件披露后,很多网友并未一边倒地批评食用泡面的男子,反而认为女乘客不该高声喧哗。 322 | 现代社会,公民的义务一般分为法律义务和道德义务。如果某个行为被确定为法律义务,行为人必须遵守,一旦违反,无论是受害人抑或旁观群众,均有权制止、投诉、举报。违法者既会受到应有惩戒,也会受到道德谴责,积极制止者则属于应受鼓励的见义勇为。如果有人违反道德义务,则应受到道德和舆论谴责,并有可能被追究法律责任。如在公共场所随地吐痰、乱扔垃圾、脱掉鞋子、随意插队等。此时,如果行为人对他人的劝阻置之不理甚至行凶报复,无疑要受到严厉惩戒。 323 | 当然,随着社会的发展,某些道德义务可能上升为法律义务。如之前,很多人对公共场所吸烟不以为然,烟民可以旁若无人地吞云吐雾。现在,要是还有人不识时务地在公共场所吸烟,必然将成为众矢之的。 324 | 再回到“高铁吃泡面”事件,要是随着人们观念的更新,在高铁上不得吃泡面等可能产生刺激性气味的食物逐渐成为共识,或者上升到道德义务或法律义务。斥责、制止他人吃泡面将理直气壮,否则很难摆脱“矫情”,“将自我权利凌驾于他人权利之上”的嫌疑。 325 | 在相关部门并未禁止在高铁上吃泡面的背景下,吃不吃泡面系个人权利或者个人私德,是不违反公共利益的个人正常生活的一部分。如果认为他人吃泡面让自己不适,最好是请求他人配合并加以感谢,而非站在道德制高点强制干预。只有每个人行使权利时不逾越边界,与他人沟通时好好说话,不过分自我地将幸福和舒适凌驾于他人之上,人与人之间才更趋于平等,公共生活才更趋向美好有序。""" 326 | content3 = '''(原标题:央视独家采访:陕西榆林产妇坠楼事件在场人员还原事情经过) 327 | 央视新闻客户端11月24日消息,2017年8月31日晚,在陕西省榆林市第一医院绥德院区,产妇马茸茸在待产时,从医院五楼坠亡。事发后,医院方面表示,由于家属多次拒绝剖宫产,最终导致产妇难忍疼痛跳楼。但是产妇家属却声称,曾向医生多次提出剖宫产被拒绝。 328 | 事情经过究竟如何,曾引起舆论纷纷,而随着时间的推移,更多的反思也留给了我们,只有解决了这起事件中暴露出的一些问题,比如患者的医疗选择权,人们对剖宫产和顺产的认识问题等,这样的悲剧才不会再次发生。央视记者找到了等待产妇的家属,主治医生,病区主任,以及当时的两位助产师,一位实习医生,希望通过他们的讲述,更准确地还原事情经过。 329 | 产妇待产时坠亡,事件有何疑点。公安机关经过调查,排除他杀可能,初步认定马茸茸为跳楼自杀身亡。马茸茸为何会在医院待产期间跳楼身亡,这让所有人的目光都聚焦到了榆林第一医院,这家在当地人心目中数一数二的大医院。 330 | 就这起事件来说,如何保障患者和家属的知情权,如何让患者和医生能够多一份实质化的沟通?这就需要与之相关的法律法规更加的细化、人性化并且充满温度。用这种温度来消除孕妇对未知的恐惧,来保障医患双方的权益,迎接新生儿平安健康地来到这个世界。''' 331 | content4 = '2020年9月25日,恒生电子2020年财富资管行业峰会即将在上海召开。当日下午15:00,白雪博士作《基于产业链图谱的智能投研实践》报告:近年来各行各业的数字化进程日益加速,而在投资、投研领域,投资对象数字化,投研投资过程数字化也被重视起来,越来越多的金融机构都开始依据金融知识图谱着手打造有自身特色的数据库。本次大会分为上午的外滩大会-恒生全球财富资管高峰论坛和下午的财富资管行业峰会两个半场。' 332 | content5 = ''' 以色列国防军20日对加沙地带实施轰炸,造成3名巴勒斯坦武装人员死亡。此外,巴勒斯坦人与以色列士兵当天在加沙地带与以交界地区发生冲突,一名巴勒斯坦人被打死。当天的冲突还造成210名巴勒斯坦人受伤。 333 | 当天,数千名巴勒斯坦人在加沙地带边境地区继续“回归大游行”抗议活动。部分示威者燃烧轮胎,并向以军投掷石块、燃烧瓶等,驻守边境的以军士兵向示威人群发射催泪瓦斯并开枪射击。''' 334 | content6 = """互联网为每个人提供了展现自我的舞台,因为一句话、一个表情、一个动作而走红的不在少数,但像丁真这样引发如此大规模的网络热潮,甚至收获了外交部发言人打call的,却也罕见。有人说丁真的走红,源自于其超高的颜值和帅气的外表,也有人说丁真身上的淳朴和纯真格外打动人。事实上,这些只是让他具备了网红的潜质。真正让他成为“顶级流量”的,是他对家乡、对生活的热爱,让人们感受到逆境中成长的自强不息力量,是镜头中的雪山、白云、藏族服饰等元素,展现了一个丰富多彩而又立体的中国,唤醒了人们对诗和远方的向往,是在他走红之后依然保持的纯真本色,让人们看到一颗赤子之心。 335 | 最近,生活在四川甘孜州理塘县的藏族小伙丁真,因为一条不到10秒的视频意外走红网络,不仅被当地聘为旅游大使,更引发了各地文旅机构助推热潮。通过丁真这个窗口让更多人了解到四川乃至全国的景点,网友纷纷表示“这才是网红最好的打开方式”。今晚,我们就从这个藏族小伙聊起。 336 |  11月30日警方通报了情况:涉事男生马某是山东外事职业大学大学大一在校生,18岁,女生李某是四川大学锦江学院大三在校生,21岁,两人系情侣关系,当天马某从山东乘坐飞机来到李某学校,然后通过衣物掩饰混入了李某所在宿舍楼,在宿舍内两人见面后因为感情纠纷,马某拿起了宿舍内的一把12厘米长剪刀,将李某捅致重伤后跳楼身亡。 337 | """ 338 | events, spos = handler.phrase_ip(content1) 339 | spos = [i for i in spos if i[0] and i[2]] 340 | for spo in spos: 341 | print(spo) 342 | 343 | # while 1: 344 | # content = input("enter an sent to parser").strip() 345 | # events, spos = handler.phrase_ip(content) 346 | # for spo in spos: 347 | # print(spo) 348 | # print('#####'*5) 349 | # for event in events: 350 | # print(event) -------------------------------------------------------------------------------- /sentence_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: sentence_parser.py 4 | # Author: lhy 5 | # Date: 18-3-10 6 | 7 | import os 8 | from pyltp import Segmentor, Postagger, Parser, NamedEntityRecognizer, SementicRoleLabeller 9 | class LtpParser: 10 | def __init__(self): 11 | LTP_DIR = "./ltp_data" 12 | self.segmentor = Segmentor() 13 | self.segmentor.load(os.path.join(LTP_DIR, "cws.model")) 14 | 15 | self.postagger = Postagger() 16 | self.postagger.load(os.path.join(LTP_DIR, "pos.model")) 17 | 18 | self.parser = Parser() 19 | self.parser.load(os.path.join(LTP_DIR, "parser.model")) 20 | 21 | self.recognizer = NamedEntityRecognizer() 22 | self.recognizer.load(os.path.join(LTP_DIR, "ner.model")) 23 | 24 | self.labeller = SementicRoleLabeller() 25 | self.labeller.load(os.path.join(LTP_DIR, 'pisrl.model')) 26 | 27 | '''语义角色标注''' 28 | def format_labelrole(self, words, postags): 29 | arcs = self.parser.parse(words, postags) 30 | roles = self.labeller.label(words, postags, arcs) 31 | roles_dict = {} 32 | for role in roles: 33 | roles_dict[role.index] = {arg.name:[arg.name,arg.range.start, arg.range.end] for arg in role.arguments} 34 | return roles_dict 35 | 36 | '''句法分析---为句子中的每个词语维护一个保存句法依存儿子节点的字典''' 37 | def build_parse_child_dict(self, words, postags, arcs): 38 | child_dict_list = [] 39 | format_parse_list = [] 40 | for index in range(len(words)): 41 | child_dict = dict() 42 | for arc_index in range(len(arcs)): 43 | if arcs[arc_index].head == index+1: #arcs的索引从1开始 44 | if arcs[arc_index].relation in child_dict: 45 | child_dict[arcs[arc_index].relation].append(arc_index) 46 | else: 47 | child_dict[arcs[arc_index].relation] = [] 48 | child_dict[arcs[arc_index].relation].append(arc_index) 49 | child_dict_list.append(child_dict) 50 | rely_id = [arc.head for arc in arcs] # 提取依存父节点id 51 | relation = [arc.relation for arc in arcs] # 提取依存关系 52 | heads = ['Root' if id == 0 else words[id - 1] for id in rely_id] # 匹配依存父节点词语 53 | for i in range(len(words)): 54 | # ['ATT', '李克强', 0, 'nh', '总理', 1, 'n'] 55 | a = [relation[i], words[i], i, postags[i], heads[i], rely_id[i]-1, postags[rely_id[i]-1]] 56 | format_parse_list.append(a) 57 | 58 | return child_dict_list, format_parse_list 59 | 60 | '''parser主函数''' 61 | def parser_main(self, sentence): 62 | words = list(self.segmentor.segment(sentence)) 63 | postags = list(self.postagger.postag(words)) 64 | arcs = self.parser.parse(words, postags) 65 | child_dict_list, format_parse_list = self.build_parse_child_dict(words, postags, arcs) 66 | roles_dict = self.format_labelrole(words, postags) 67 | return words, postags, child_dict_list, roles_dict, format_parse_list 68 | 69 | 70 | if __name__ == '__main__': 71 | parse = LtpParser() 72 | sentence = '李克强总理今天来我家了,我感到非常荣幸' 73 | words, postags, child_dict_list, roles_dict, format_parse_list = parse.parser_main(sentence) 74 | print(words, len(words)) 75 | print(postags, len(postags)) 76 | print(child_dict_list, len(child_dict_list)) 77 | print(roles_dict) 78 | print(format_parse_list, len(format_parse_list)) -------------------------------------------------------------------------------- /triple_extraction.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # coding: utf-8 3 | # File: triple_extraction.py 4 | # Author: lhy 5 | # Date: 18-3-12 6 | from sentence_parser import * 7 | import re 8 | 9 | class TripleExtractor: 10 | def __init__(self): 11 | self.parser = LtpParser() 12 | 13 | '''文章分句处理, 切分长句,冒号,分号,感叹号等做切分标识''' 14 | def split_sents(self, content): 15 | return [sentence for sentence in re.split(r'[??!!。;;::\n\r]', content) if sentence] 16 | 17 | '''利用语义角色标注,直接获取主谓宾三元组,基于A0,A1,A2''' 18 | def ruler1(self, words, postags, roles_dict, role_index): 19 | v = words[role_index] 20 | role_info = roles_dict[role_index] 21 | if 'A0' in role_info.keys() and 'A1' in role_info.keys(): 22 | s = ''.join([words[word_index] for word_index in range(role_info['A0'][1], role_info['A0'][2]+1) if 23 | postags[word_index][0] not in ['w', 'u', 'x'] and words[word_index]]) 24 | o = ''.join([words[word_index] for word_index in range(role_info['A1'][1], role_info['A1'][2]+1) if 25 | postags[word_index][0] not in ['w', 'u', 'x'] and words[word_index]]) 26 | if s and o: 27 | return '1', [s, v, o] 28 | # elif 'A0' in role_info: 29 | # s = ''.join([words[word_index] for word_index in range(role_info['A0'][1], role_info['A0'][2] + 1) if 30 | # postags[word_index][0] not in ['w', 'u', 'x']]) 31 | # if s: 32 | # return '2', [s, v] 33 | # elif 'A1' in role_info: 34 | # o = ''.join([words[word_index] for word_index in range(role_info['A1'][1], role_info['A1'][2]+1) if 35 | # postags[word_index][0] not in ['w', 'u', 'x']]) 36 | # return '3', [v, o] 37 | return '4', [] 38 | 39 | '''三元组抽取主函数''' 40 | def ruler2(self, words, postags, child_dict_list, arcs, roles_dict): 41 | svos = [] 42 | for index in range(len(postags)): 43 | tmp = 1 44 | # 先借助语义角色标注的结果,进行三元组抽取 45 | if index in roles_dict: 46 | flag, triple = self.ruler1(words, postags, roles_dict, index) 47 | if flag == '1': 48 | svos.append(triple) 49 | tmp = 0 50 | if tmp == 1: 51 | # 如果语义角色标记为空,则使用依存句法进行抽取 52 | # if postags[index] == 'v': 53 | if postags[index]: 54 | # 抽取以谓词为中心的事实三元组 55 | child_dict = child_dict_list[index] 56 | # 主谓宾 57 | if 'SBV' in child_dict and 'VOB' in child_dict: 58 | r = words[index] 59 | e1 = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0]) 60 | e2 = self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0]) 61 | svos.append([e1, r, e2]) 62 | 63 | # 定语后置,动宾关系 64 | relation = arcs[index][0] 65 | head = arcs[index][2] 66 | if relation == 'ATT': 67 | if 'VOB' in child_dict: 68 | e1 = self.complete_e(words, postags, child_dict_list, head - 1) 69 | r = words[index] 70 | e2 = self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0]) 71 | temp_string = r + e2 72 | if temp_string == e1[:len(temp_string)]: 73 | e1 = e1[len(temp_string):] 74 | if temp_string not in e1: 75 | svos.append([e1, r, e2]) 76 | # 含有介宾关系的主谓动补关系 77 | if 'SBV' in child_dict and 'CMP' in child_dict: 78 | e1 = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0]) 79 | cmp_index = child_dict['CMP'][0] 80 | r = words[index] + words[cmp_index] 81 | if 'POB' in child_dict_list[cmp_index]: 82 | e2 = self.complete_e(words, postags, child_dict_list, child_dict_list[cmp_index]['POB'][0]) 83 | svos.append([e1, r, e2]) 84 | return svos 85 | 86 | '''对找出的主语或者宾语进行扩展''' 87 | def complete_e(self, words, postags, child_dict_list, word_index): 88 | child_dict = child_dict_list[word_index] 89 | prefix = '' 90 | if 'ATT' in child_dict: 91 | for i in range(len(child_dict['ATT'])): 92 | prefix += self.complete_e(words, postags, child_dict_list, child_dict['ATT'][i]) 93 | postfix = '' 94 | if postags[word_index] == 'v': 95 | if 'VOB' in child_dict: 96 | postfix += self.complete_e(words, postags, child_dict_list, child_dict['VOB'][0]) 97 | if 'SBV' in child_dict: 98 | prefix = self.complete_e(words, postags, child_dict_list, child_dict['SBV'][0]) + prefix 99 | 100 | return prefix + words[word_index] + postfix 101 | 102 | '''程序主控函数''' 103 | def triples_main(self, content): 104 | sentences = self.split_sents(content) 105 | svos = [] 106 | for sentence in sentences: 107 | words, postags, child_dict_list, roles_dict, arcs = self.parser.parser_main(sentence) 108 | svo = self.ruler2(words, postags, child_dict_list, arcs, roles_dict) 109 | svos += svo 110 | 111 | return svos 112 | 113 | 114 | '''测试''' 115 | def test(): 116 | content1 = """环境很好,位置独立性很强,比较安静很切合店名,半闲居,偷得半日闲。点了比较经典的菜品,味道果然不错!烤乳鸽,超级赞赞赞,脆皮焦香,肉质细嫩,超好吃。艇仔粥料很足,香葱自己添加,很贴心。金钱肚味道不错,不过没有在广州吃的烂,牙口不好的慎点。凤爪很火候很好,推荐。最惊艳的是长寿菜,菜料十足,很新鲜,清淡又不乏味道,而且没有添加调料的味道,搭配的非常不错!""" 117 | content2 = """近日,一条男子高铁吃泡面被女乘客怒怼的视频引发热议。女子情绪激动,言辞激烈,大声斥责该乘客,称高铁上有规定不能吃泡面,质问其“有公德心吗”“没素质”。视频曝光后,该女子回应称,因自己的孩子对泡面过敏,曾跟这名男子沟通过,但对方执意不听,她才发泄不满,并称男子拍视频上传已侵犯了她的隐私权和名誉权,将采取法律手段。12306客服人员表示,高铁、动车上一般不卖泡面,但没有规定高铁、动车上不能吃泡面。 118 | 高铁属于密封性较强的空间,每名乘客都有维护高铁内秩序,不破坏该空间内空气质量的义务。这也是乘客作为公民应当具备的基本品质。但是,在高铁没有明确禁止食用泡面等食物的背景下,以影响自己或孩子为由阻挠他人食用某种食品并厉声斥责,恐怕也超出了权利边界。当人们在公共场所活动时,不宜过分干涉他人权利,这样才能构建和谐美好的公共秩序。 119 | 一般来说,个人的权利便是他人的义务,任何人不得随意侵犯他人权利,这是每个公民得以正常工作、生活的基本条件。如果权利可以被肆意侵犯而得不到救济,社会将无法运转,人们也没有幸福可言。如西谚所说,“你的权利止于我的鼻尖”,“你可以唱歌,但不能在午夜破坏我的美梦”。无论何种权利,其能够得以行使的前提是不影响他人正常生活,不违反公共利益和公序良俗。超越了这个边界,权利便不再为权利,也就不再受到保护。 120 | 在“男子高铁吃泡面被怒怼”事件中,初一看,吃泡面男子可能侵犯公共场所秩序,被怒怼乃咎由自取,其实不尽然。虽然高铁属于封闭空间,但与禁止食用刺激性食品的地铁不同,高铁运营方虽然不建议食用泡面等刺激性食品,但并未作出禁止性规定。由此可见,即使食用泡面、榴莲、麻辣烫等食物可能产生刺激性味道,让他人不适,但是否食用该食品,依然取决于个人喜好,他人无权随意干涉乃至横加斥责。这也是此事件披露后,很多网友并未一边倒地批评食用泡面的男子,反而认为女乘客不该高声喧哗。 121 | 现代社会,公民的义务一般分为法律义务和道德义务。如果某个行为被确定为法律义务,行为人必须遵守,一旦违反,无论是受害人抑或旁观群众,均有权制止、投诉、举报。违法者既会受到应有惩戒,也会受到道德谴责,积极制止者则属于应受鼓励的见义勇为。如果有人违反道德义务,则应受到道德和舆论谴责,并有可能被追究法律责任。如在公共场所随地吐痰、乱扔垃圾、脱掉鞋子、随意插队等。此时,如果行为人对他人的劝阻置之不理甚至行凶报复,无疑要受到严厉惩戒。 122 | 当然,随着社会的发展,某些道德义务可能上升为法律义务。如之前,很多人对公共场所吸烟不以为然,烟民可以旁若无人地吞云吐雾。现在,要是还有人不识时务地在公共场所吸烟,必然将成为众矢之的。 123 | 再回到“高铁吃泡面”事件,要是随着人们观念的更新,在高铁上不得吃泡面等可能产生刺激性气味的食物逐渐成为共识,或者上升到道德义务或法律义务。斥责、制止他人吃泡面将理直气壮,否则很难摆脱“矫情”,“将自我权利凌驾于他人权利之上”的嫌疑。 124 | 在相关部门并未禁止在高铁上吃泡面的背景下,吃不吃泡面系个人权利或者个人私德,是不违反公共利益的个人正常生活的一部分。如果认为他人吃泡面让自己不适,最好是请求他人配合并加以感谢,而非站在道德制高点强制干预。只有每个人行使权利时不逾越边界,与他人沟通时好好说话,不过分自我地将幸福和舒适凌驾于他人之上,人与人之间才更趋于平等,公共生活才更趋向美好有序。""" 125 | content3 = '''(原标题:央视独家采访:陕西榆林产妇坠楼事件在场人员还原事情经过) 126 | 央视新闻客户端11月24日消息,2017年8月31日晚,在陕西省榆林市第一医院绥德院区,产妇马茸茸在待产时,从医院五楼坠亡。事发后,医院方面表示,由于家属多次拒绝剖宫产,最终导致产妇难忍疼痛跳楼。但是产妇家属却声称,曾向医生多次提出剖宫产被拒绝。 127 | 事情经过究竟如何,曾引起舆论纷纷,而随着时间的推移,更多的反思也留给了我们,只有解决了这起事件中暴露出的一些问题,比如患者的医疗选择权,人们对剖宫产和顺产的认识问题等,这样的悲剧才不会再次发生。央视记者找到了等待产妇的家属,主治医生,病区主任,以及当时的两位助产师,一位实习医生,希望通过他们的讲述,更准确地还原事情经过。 128 | 产妇待产时坠亡,事件有何疑点。公安机关经过调查,排除他杀可能,初步认定马茸茸为跳楼自杀身亡。马茸茸为何会在医院待产期间跳楼身亡,这让所有人的目光都聚焦到了榆林第一医院,这家在当地人心目中数一数二的大医院。 129 | 就这起事件来说,如何保障患者和家属的知情权,如何让患者和医生能够多一份实质化的沟通?这就需要与之相关的法律法规更加的细化、人性化并且充满温度。用这种温度来消除孕妇对未知的恐惧,来保障医患双方的权益,迎接新生儿平安健康地来到这个世界。''' 130 | content4 = '李克强总理今天来我家了,我感到非常荣幸' 131 | content5 = ''' 以色列国防军20日对加沙地带实施轰炸,造成3名巴勒斯坦武装人员死亡。此外,巴勒斯坦人与以色列士兵当天在加沙地带与以交界地区发生冲突,一名巴勒斯坦人被打死。当天的冲突还造成210名巴勒斯坦人受伤。 132 | 当天,数千名巴勒斯坦人在加沙地带边境地区继续“回归大游行”抗议活动。部分示威者燃烧轮胎,并向以军投掷石块、燃烧瓶等,驻守边境的以军士兵向示威人群发射催泪瓦斯并开枪射击。''' 133 | extractor = TripleExtractor() 134 | svos = extractor.triples_main(content2) 135 | print('svos', svos) 136 | 137 | test() 138 | --------------------------------------------------------------------------------