re匹配截至到第一个中文_NLP入门系列:中文复合事件抽取(附python源码)

文/IT可达鸭

图/IT可达鸭、网络

前言

什么是复合事件?复合事件包括条件事件、因果事件、顺承事件、反转事件。

272d375b3860f580586a898ad8fc2553.png

很多NLP实际项目都需要用到复合事件的抽取,例如:知识图谱中的事件抽取,形成事理图谱;智能聊天对话中的事件抽取,用于识别用户意图。复合事件的抽取作为一个NLP的基础模块,还是有很多东西可以研究的。

本文主要介绍,一种基于规则的中文事件抽取方法,源码来自网友的贡献。本文的源码在此基础上进行优化,将数据与代码分开,方便后期扩充事件抽取的规则,同时简化代码,更易于理解。

最好的学习方法就是阅读优秀的源码,最好的成果检验,就修改源码,把代码融入自己的想法。下面就跟着可达鸭一起学习如何开发这个项目吧。

技术难点

json文件的读取和存储,以及一些乱码情况的处理。

1a842e627f08c97b9882e0fc3af76d92.png

读取json文件

保存文件的时候,使用dump函数,“ensure_ascii”参数设置为False,这样保存的文件就不会出现中文乱码。

82822b8df3f2f5917128b34cb48fc07e.png

保存json文件

如何去提取复合事件呢?这里使用正则表达式,也是最常用的基于规则的方法。

729bcb1705688d70703957841dd009cf.png

由于事件的句式有上百个连接关键词词,而连接词有上千种组合。我们必须设计出能匹配所有组合的正则表达式。这里设计一个生成动态正则表达式的函数,由手工整理出固定的连接词(详细见数据文件data.json),再有程序自动排列组合出各种形式。

将上述“不但”、“而且”设计为变量,用python中的占位符代替。从文件中读取句式连接词,迭代赋值“pre”和“pos”,就能生成所有的正则表达式。

baa4f686c2abae34c6629d59fcbe08e2.png

文章代码来源于开源的源码,这边做了优化和改进。我们将数据与代码分开,并引入设计模式中的策略模式。策略模式主要解决:在有多种算法相似的情况下,使用“if...else...”所带来的难以维护的问题。避免过多使用多重条件的判断,扩展性良好。

如果大家的代码出现大量的“if....else....”, 可以考虑是否可以使用策略模式。

项目总体框架图

74dd6624a3ef2b284985233da822e1aa.png

有了项目总体框架图,就能很好的把握整个项目的开发进度。

详细代码

导入相应的python包,其中re是一个正则表达式模块,用于模式匹配。

c153a1e83f69f02bbfd22f91e2d4bc7a.png

所有的事件连接词,都写入data.json,其中格式如下图。写入json文件的好处就是,方便后续的扩展。增加事件连接词,增加事件类型都可以很方便实现,不用去修改源码。

c2d5f7e945e16d38caeeb7683876307a.png

将复合事件抽取封装成一个类,这是类初始化函数基本写法。

39061fc92d84d248874a63d5f6ed9bd4.png

数据加载模块,从data.json读取数据,并整理数据的格式。

706a8e8b3d146bc755e6e716ebe95d92.png

根据数据加载模块中获取的数据,生成所有的正则表达式。

8a2059918ac4571d759bc1d2a7c62881.png

基于正则表达式,对每个输入的句子,进行匹配与事件抽取。

c790358e5ac29e0e5c70068be69c4fe3.png

这里是对输入的文本进行切分,然后对每个句子进行事件抽取。

230eeee171df9ba88d81aa9f4e38ff4e.png

建立main函数,对写好的类进行测试。

1a7fb4ba313971548e79dd1b748d93b0.png

最终输出结果,大家可以根据自己想要的输出格式进行修改。

c237f623ce446517680e14cea78907b7.png

结语

本项目中用到了正则表达式文件读取设计模式--策略模式,这些都是一些基本的编程知识。需要在日常编程中不断积累,不能一蹴而就。

学习python,不需要一杯奶茶钱,只需要你点个关注。如果觉得小编的文章对你有帮助,记得点个赞,顺便帮我分享出去。如果想获取源码,可以关注后,私信:python事件抽取,我把源码发给你。最后,感谢大家的阅读,祝大家生活愉快。

本文由 IT可达鸭 原创,欢迎关注,带你一起长知识!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的基于BERT+BiLSTM+CRF模型的中文事件抽取代码示例,使用了pytorch和transformers库: ```python import torch from transformers import BertTokenizer, BertModel import torch.nn as nn class BertBiLSTMCRF(nn.Module): def __init__(self, bert_model, num_tags, hidden_size=768, dropout_prob=0.1): super(BertBiLSTMCRF, self).__init__() self.bert = bert_model self.bilstm = nn.LSTM(hidden_size, hidden_size // 2, num_layers=1, bidirectional=True, batch_first=True) self.dropout = nn.Dropout(dropout_prob) self.fc = nn.Linear(hidden_size, num_tags) self.crf = CRF(num_tags, batch_first=True) def forward(self, input_ids, attention_mask): bert_outputs = self.bert(input_ids=input_ids, attention_mask=attention_mask) sequence_output = bert_outputs[0] sequence_output = self.dropout(sequence_output) lstm_output, _ = self.bilstm(sequence_output) lstm_output = self.dropout(lstm_output) emissions = self.fc(lstm_output) mask = attention_mask.bool() crf_output = self.crf.decode(emissions, mask) return crf_output class CRF(nn.Module): def __init__(self, num_tags, batch_first=False): super(CRF, self).__init__() self.num_tags = num_tags self.transitions = nn.Parameter(torch.randn(num_tags, num_tags)) self.start_transitions = nn.Parameter(torch.randn(num_tags)) self.end_transitions = nn.Parameter(torch.randn(num_tags)) self.batch_first = batch_first def forward(self, emissions, tags, mask): if self.batch_first: emissions = emissions.transpose(0, 1) tags = tags.transpose(0, 1) mask = mask.transpose(0, 1) sequence_length = emissions.shape[0] batch_size = emissions.shape[1] score = self.start_transitions.view(1, -1) + emissions[0] for i in range(1, sequence_length): broadcast_score = score.unsqueeze(2) broadcast_emissions = emissions[i].unsqueeze(1) next_score = broadcast_score + self.transitions + broadcast_emissions next_score = torch.logsumexp(next_score, dim=1) mask_idx = mask[i].unsqueeze(1).expand(batch_size, self.num_tags) score = torch.where(mask_idx, next_score, score) score = score + self.end_transitions.view(1, -1) score = torch.logsumexp(score, dim=1) gold_score = self._score_sentence(emissions, tags, mask) return (score - gold_score) / batch_size def decode(self, emissions, mask): if self.batch_first: emissions = emissions.transpose(0, 1) mask = mask.transpose(0, 1) sequence_length = emissions.shape[0] batch_size = emissions.shape[1] score = self.start_transitions.view(1, -1) + emissions[0] history = [] for i in range(1, sequence_length): broadcast_score = score.unsqueeze(2) broadcast_emissions = emissions[i].unsqueeze(1) next_score = broadcast_score + self.transitions + broadcast_emissions next_score, indices = torch.max(next_score, dim=1) history.append(indices) mask_idx = mask[i].unsqueeze(1).expand(batch_size, self.num_tags) score = torch.where(mask_idx, next_score, score) score = score + self.end_transitions.view(1, -1) _, best_tag = torch.max(score, dim=1) best_path = [best_tag] for h in reversed(history): best_tag = torch.gather(h, 1, best_tag.unsqueeze(1)).squeeze() best_path.insert(0, best_tag) best_path = torch.stack(best_path).transpose(0, 1) return best_path def _score_sentence(self, emissions, tags, mask): if self.batch_first: emissions = emissions.transpose(0, 1) tags = tags.transpose(0, 1) mask = mask.transpose(0, 1) sequence_length = emissions.shape[0] batch_size = emissions.shape[1] score = self.start_transitions[tags[0]] for i in range(1, sequence_length): current_tags = tags[i] previous_tags = tags[i - 1] transition_score = self.transitions[previous_tags, current_tags] emission_score = emissions[i, torch.arange(batch_size), current_tags] mask_idx = mask[i] score = score + transition_score * mask_idx + emission_score * mask_idx last_tag_indexes = mask.sum(dim=0) - 1 last_tags = tags[last_tag_indexes, torch.arange(batch_size)] last_transition_score = self.end_transitions[last_tags] last_mask_idx = mask.sum(dim=0).float() last_score = last_transition_score + last_mask_idx return score + last_score tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') model = BertModel.from_pretrained('bert-base-chinese') num_tags = 4 # 事件类型数 class EventExtractor: def __init__(self, model_path='event_extractor.pt'): self.model = BertBiLSTMCRF(model, num_tags) self.model.load_state_dict(torch.load(model_path, map_location=torch.device('cpu'))) self.model.eval() def extract(self, text): input_ids = tokenizer.encode(text, add_special_tokens=True) attention_mask = [1] * len(input_ids) with torch.no_grad(): pred_tags = self.model(torch.tensor(input_ids).unsqueeze(0), torch.tensor(attention_mask).unsqueeze(0)) pred_tags = pred_tags[0] tags = [idx2tag[i] for i in pred_tags] entities = [] for i, tag in enumerate(tags): if tag.startswith('B-'): entity = {'start': i, 'type': tag[2:]} j = i + 1 while j < len(tags) and tags[j] == 'I-' + tag[2:]: j += 1 entity['end'] = j - 1 entity['word'] = tokenizer.decode(input_ids[entity['start']:entity['end']+1]).replace(' ', '') entities.append(entity) return entities tag2idx = {'O': 0, 'B-LOC': 1, 'B-PER': 2, 'B-ORG': 3, 'I-LOC': 4, 'I-PER': 5, 'I-ORG': 6} idx2tag = {idx: tag for tag, idx in tag2idx.items()} extractor = EventExtractor() ``` 在上述代码中,我们使用了BERT作为输入特征提取器,然后将其输出送入一个双向LSTM网络中进行序列标注,最后使用CRF层进行整个序列的解码,得到最终的事件抽取结果。具体实现细节可以参考代码中的注释。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值