中文关系抽取总结

前言:

关系分类抽取模型项目的任务是给定句子和句子中的两个实体,判断这两个实体之间的关系,项目文件中带有质量较高的数据集。项目的思路大致是将关系抽取转化成对两个实体的关系进行分类。

大致可以分为三块:model,也就是要定义我们模型各层的结构;dataset,把要训练的数据集改写成model需要的格式;train,定义训练、验证和测试的参数和过程。dataset是由model的输入层和输出层决定的,所以只要我们自己明确了model的输入和输出,dataset通过一些Python基础很容易就实现了。

一、requirements

torch
transformers==4.0.1
tqdm
sklearn

二、BERT模型

BERT主要分为两个部分。一个是训练语言模型(language model)的预训练(run_pretraining.py)部分。另一个是训练具体任务(task)的fine-tune部分,预训练部分巨大的运算资源。

1、数据预处理

data_utils.py部分

1.1数据切分部分

定义MyTokenizer类,实现对中文关系的切分,def __init__(self, pretrained_model_path=None, mask_entity=False):
进行初始化,加载相应的参数

class MyTokenizer(object):
    def __init__(self, pretrained_model_path=None, mask_entity=False):
        self.pretrained_model_path = pretrained_model_path or 'bert-base-chinese'
        self.bert_tokenizer = BertTokenizer.from_pretrained(self.pretrained_model_path)
        self.mask_entity = mask_entity

    def tokenize(self, item):

1.2数据读取

定义read_data函数实现从文件中读取数据

def read_data(input_file, tokenizer=None, max_len=128):
    tokens_list = []
    e1_mask_list = []
    e2_mask_list = []
    tags = []
    with open(input_file, 'r', encoding='utf-8') as f_in:
        for line in tqdm(f_in):
            line = line.strip()
            item = json.loads(line)
            if tokenizer is None:
                tokenizer = MyTokenizer()
            tokens, pos_e1, pos_e2 = tokenizer.tokenize(item)
            if pos_e1[0] < max_len - 1 and pos_e1[1] < max_len and \
                    pos_e2[0] < max_len - 1 and pos_e2[1] < max_len:
                tokens_list.append(tokens)
                e1_mask = convert_pos_to_mask(pos_e1, max_len)
                e2_mask = convert_pos_to_mask(pos_e2, max_len)
                e1_mask_list.append(e1_mask)
                e2_mask_list.append(e2_mask)
                tag = item['relation']
                tags.append(tag)
    return tokens_list, e1_mask_list, e2_mask_list, tags

1.3数据预处理过程

列表长度实现

 def __len__(self):
        return len(self.tags)

索引index实现 def __getitem__(self, idx):

class SentenceREDataset(Dataset):
   def __init__(self, data_file_path, tagset_path, pretrained_model_path=None, max_len=128):
   
    def __getitem__(self, idx):
        if torch.is_tensor(idx):
            idx = idx.tolist()
        sample_tokens = self.tokens_list[idx]
        sample_e1_mask = self.e1_mask_list[idx]
        sample_e2_mask = self.e2_mask_list[idx]
        sample_tag = self.tags[idx]
        encoded = self.tokenizer.bert_tokenizer.encode_plus(sample_tokens, max_length=self.max_len, pad_to_max_length=True)
        sample_token_ids = encoded['input_ids']
        sample_token_type_ids = encoded['token_type_ids']
        sample_attention_mask = encoded['attention_mask']
        sample_tag_id = self.tag2idx[sample_tag]

        sample = {
            'token_ids': torch.tensor(sample_token_ids),
            'token_type_ids': torch.tensor(sample_token_type_ids),
            'attention_mask': torch.tensor(sample_attention_mask),
            'e1_mask': torch.tensor(sample_e1_mask),
            'e2_mask': torch.tensor(sample_e2_mask),
            'tag_id': torch.tensor(sample_tag_id)
        }
        return sample

2、模型部分

导入BertTokenizerBertModel模型from transformers import BertTokenizer, BertModel

2.1、输入输出

    首先明确model的输入输出,前面已经介绍了本项目的任务是对给定的句子和句子中的两个实体判断关系,所以输入就是句子、实体1和实体2,输出就是关系编号。然后看forward函数的输入输出,forward函数的参数包括以下5个:

    1、token_ids:这个比较常见,标记着句子中每个字在词表中的位置。

    2、token_type_ids:区分两个句子的编码,但是在本次项目中只输入一个句子。

    3、attention_mask:标记哪些位置要进行self-attention操作,其他位置都是pad。

    4、e1_mask:标记第一个实体的位置。

    5、e2_mask:标记第二个实体的位置。

    输出的是模型对两个实体的关系预测结果。

2.2、中间层

模型的中间层实际上体现在forward函数中,上面的初始化函数对各层的定义顺序没有要求,所以看forward函数才能真正掌握模型的数据流动过程。

1、forward函数中首先是一个bert层,先将对整个句子进行编码,得到句中每个字的字向量sequence_output和整句的句向量pooled_output。这些向量都是transformer最后一层的输出。
sequence_output, pooled_output = self.bert_model(input_ids=token_ids, token_type_ids=token_type_ids, attention_mask=attention_mask, return_dict=False)
2、通过前面的e1_mask和e2_mask来找到两个实体每个字的字向量,然后每个实体的所有字向量相加后求均值。这部分工作体现在entity_average函数中,该函数的作用就是求一个实体中所有字的字向量均值,过程比较巧妙,所以对这个函数每句进行一些解释。函数的输入一个是句子的句向量hidden_output,维度为(batch_size,seq_l,embedding_dim),还有一个是实体的位置e_mask,这是一个(batch_size,seq_len)的向量,实体位置为1,其余位置为0。

①、第一步对e_mask向量进行升维,从(batch_size,seq_len)变为(batch_size,1,seq_len),这步的作用是对齐e_mask和hidden_output的维度,使得后面二者可以相乘。

e_mask_unsqueeze = e_mask.unsqueeze(1)

②、第二步求出每个实体的字数,使用的sum函数可以统计向量中值为1的元素数量,然后再进行升维。

length_tensor = (e_mask != 0).sum(dim=1).unsqueeze(1)

③、第三步要计算实体所有字向量的平均值,用e_mask和hidden_output相乘,其中e_mask为0的位置,与非实体位置的字向量相乘,e_mask为1的位置与实体位置的每个字向量相乘,这样就实现了实体每个字的字向量相加。这里使用了bmm批量矩阵乘法函数,二者的维度分别为(batch_size,1,seq_l)和(batch_size,seq_len,embedding_dim),在理解bmm函数时可以先不考虑batch_size维度,因为三维矩阵可以看做是多个二维矩阵的结合,bmm函数每次分别取两个二维矩阵相乘,然后将所有结果组合成一个三维矩阵。也就是说:
[batch_size,1,max_len] × [batch_size,max_len,dim] 可以看做是

batch_size ×([1,max_len] × [max_len,dim])= batch_size × [1,dim] = [batch_size,1,dim]

然后再将中间那个维度去掉,得到最后结果(batch_size,embedd_dim)。

sum_vector = torch.bmm(e_mask_unsqueeze.float(), hidden_output).squeeze(1)

④、最后将相加后的向量除以实体长度,这样就实现了实体字向量的求均值。在这里就可以看出第二步对字数升维的作用,是为了对齐维度。

avg_vector = sum_vector.float() / length_tensor.float()
3、求出的两个实体字向量均值经过激活函数后,与[CLS]位置的向量连接起来,然后经过一个归一化层和Dropout层,最后通过一个全连接层求分类。
concat_h = torch.cat([pooled_output, e1_h, e2_h], dim=1)
concat_h = self.norm(concat_h)
logits = self.hidden2tag(self.drop(concat_h))
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值