命名实体识别学习-用lstm+crf处理conll03数据集

本文介绍了使用LSTM和CRF处理CONLL03数据集进行命名实体识别的过程,包括数据结构设计、mini-batch处理、pad和mask技术的运用,以及如何将for循环优化为矩阵运算。文章详细讨论了LSTM读入、转移矩阵计算、前向得分和负对数似然损失计算中的mask机制,并分享了整个项目的源代码链接和初步实验结果。
摘要由CSDN通过智能技术生成

title: 命名实体识别学习-用lstm+crf处理conll03数据集
date: 2020-07-18 16:32:31
tags:


命名实体识别学习-用lstm+crf处理conll03数据集


一直想写的一篇文章,虽然好像也不是很忙,但是一直拖着没做。就是讲下面两篇文章介绍的数据集和算法做一个整合

命名实体识别学习-数据集介绍-conll03

命名实体识别学习-从基础算法开始(02)lstm+crf序列标注

一 整合时要解决的问题

  1. 要为数据和模型读入设计合理的数据结构:即vocab-vecterizer-dataset这个pipeline,几乎所有的nlp任务都要走这个pipeline的模式(看别人的源码发现,真正实现这些数据结构时代码五花八门,不过数据结构本来就是ADT的物理实现,只要把核心功能实现就好了。)

  2. 原有的算法是一句一句读入的,我实现的时候要用mini-batch, mini-batch已经被证明了其在深度学习应用的作用和功能。不过应用mini-batch要考虑输入句子长短不一的问题,使用pad和mask的技术,尽量避免模型看到pad的元素。本模型主要有三处用到,第一是lstm读入时,然后是crf的算句子得分,和loss计算的时候。在这三处要用mask的方法避免模型读到pad的元素。

  3. 原代码,即pytorch官网上放的教程为了使代码便于理解,使用了很多for循环,不利于cuda对代码的加速,尽量将能够变为矩阵运算的for循环变为矩阵的计算。

要解决的三个问题:数据结构,pad和mask,for循环改为矩阵计算。还有一个使代码可以在GPU上运行。(不过我自己最近没法找到卡,所以代码都是凭感觉debug的,不过这次代码已经在学弟卡上跑过了)

二 mask和pad

变长序列的处理是深度学习框架的一大难题,各个框架都有比较成熟的解决问题,

lstm读入

其中pytorch为RNN的读入专门做了处理。所以对于lstm读入时处理就很简单,只需简单调用torch.nn.utils.rnn.pack_padded_sequence()和torch.nn.utils.rnn.pad_packed_sequence即可:

def _get_lstm_features(self, embedded_vec, seq_len):
        """
        :param embedded_vec: [max_seq_len, b_s, e_d]
        :param seq_len: [b_s]
        :return:
        """
        # 初始化 h0 和 c0,可以缺省 shape:
        # ([num_layers * num_directions, batch, hidden_size],[num_layers * num_directions, batch, hidden_size])
        # self.hidden = self.init_hidden(1, seq_len.size(0))
        pack_seq = pack_padded_sequence(embedded_vec, seq_len)
        # 不初始化状态,默认初始状态都为0
        # lstm_out, self.hidden = self.lstm(pack_seq, self.hidden)
        lstm_out, self.hidden = self.lstm(pack_seq)
        lstm_out, _ = pad_packed_sequence(lstm_out, batch_first=True) #[b_s, seq_len, h_d]
        lstm_feats = self.hidden2tag(lstm_out)  #[b_s, seq_len, tag_size]
        lstm_feats = self.dropout(lstm_feats)
        return lstm_feats

注意:使用这两个自带函数有个问题,并不能恢复百分百恢复原来的输入,他恢复后的句长是输入最长句子的长度,也就是说如果你输入时最长句子也有一定长度的pad元素,那样是没办法恢复的。

涉及转移分矩阵的计算

第二处mask是在转移分的计算,因为self.transitions给pad元素留了位置,代码如下:

self.transition = nn.Parameter(
            torch.randn(self.tagset_size, self.tagset_size))

这其实不符合我们尽量避免模型看到pad元素的原则(我尝试不在transition里给pad留位置,但是由于 变长序列总会有pad元素,如果没有pad元素的位置,索引就会报错。)这里我使用折中处理,在涉及到转移分矩阵的运算并直接关联结果的都mask掉,(其实存在于矩阵里无所谓,只要最后计算不影响结果即可。

涉及到转移分的计算,主要是loss的计算,在官网文档里:

    def neg_log_likelihood(self, sentence, tags):
        feats = self._get_lstm_features(sentence)
        forward_score = self._forward_alg(feats)
        gold_score = self._score_sentence(feats, tags)
        return forward_score - gold_score

其中,gold_score的计算和forward_score的计算都需要mask机制。

首先是得到mask:

mask = (token_vec != self.token_vocab.lookup_token(self.pad)).to(self.device)  # [b_s, max_seq_len]

这个token_vec就是句子向量,mask是一个布尔值向量,其中不等于pad的位置为true,等于pad的位置为false。

    def _score_sentence(self, feats, tags):
        # Gives the score of a provided tag sequence
        score = torch.zeros(1)
        tags = torch.cat([torch.tensor([ self.tag_to_ix[START_TAG]], dtype=torch.long), tags ])
        for i, feat in enumerate(feats):
            score = score + \
                self.transitions[tags[i + 1], tags[i]] + feat[tags[i + 1]]
        score = score + self.transitions[self.tag_to_ix[STOP_TAG], tags[-1]]
        return score

这个gold_score的代码相对简单:逻辑就是把真实tag对应的转移分和发射分相加,(其实这里的for循环可以去掉换成矩阵运算)因为feats中每个句子(即每个时间步)都参与一次计算,且可能有pad元素,对mask的处理:

total_score = (score * mask.type(torch.float)).sum(dim=1)

forward_score出的mask的处理,官网关于foward_score的计算比较长,就不放了,简述下逻辑,forward_score的计算本质上就是前向算法,前向算法就是DP。(在前面的博客里介绍的比较详细)在每个时间步里求前向变量,而我们用了mini-batch那么一个时间步计算的就不是一个token了。而是一批token,而由于一个mini-batch里句子是不等长的,可能一个句子还没结束,其他句子就已经运算的pad元素了,所以要用mask机制避免pad元素参与到运算中。

        for feat_index in range(1, feats.size(1)):
            n_unfinish = mask[:, feat_index].
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值