命名实体识别不只有BiLSTM+CRF

最近做命名实体识别(NER)的任务比较多,也刚刚接触NER不久,做一下小小的总结。近两年中文命名实体识别在信息抽取和关系抽取上的应用受到了研究人员的广泛关注,很多比赛也以NER为主题来命题。

最开始接触NER看到的大多数方法都是以BiLSTM+CRF的框架进行,包括工业界的很多大厂都是在用这个模型来做序列标注任务,效果也比较理想。除此之外工业界的传统方法还包括Softmax、HMM、MEMM、CRF、CRF++、IDCNN+CRF等等模型,但是在大多数情况下的表现不如BiLSTM+CRF。

BiLSTM+CRF一般采用预练词向量的方式,底层可以接Word2vec、Fasttext、CNN、Glove、ELMo等任意预训练的char级别的或bichar级别的Embedding。普遍采用封锁固定向量的方式,将输入映射成向量作为输入层接入双向的LSTM,再将前向和后向的结果拼接,接全连接层输出每个字符属于每个标签的概率,再接CRF,最后用Viterbi算法解码。

BERT及XLNet的出现将预训练词向量带到了一个前所未有的新高度,BERT无需封锁向量,词向量可以随着顶层任务进行微调。自然地,BERT+BiLSTM+CRF的方式逐渐取代了传统的序列标注方法,一跃成为各大比赛榜上的常客。

可能的模型结构

但是NER任务想要取得好的效果只能基于BiLSTM+CRF这一种网络结构吗?答案肯定是否定的。看了‘达观杯’文本智能信息抽取挑战赛的前几名的解决方案,我也找到了一些新的思路,前几名的方案中包含了以下在序列标注中很少见的网络结构:

  • BiLSTM+Attention+CRF
  • BiLSTM+CNN+CRF
  • BiLSTM+CNN+Attention+CRF
  • BiLSTM+SPAN

其中第一名方案的七个融合模型都采用了BiLSTM+CNN+CRF的网络结构,所以,打算自己实验一下,看看前三种网络的效果如何。

实验配置

  • 采用roberta作为底层的词向量模型(略优于BERT base)
  • 实验基于github上的优秀项目BERT-BiLSTM-CRF-NER
  • 向量模型为chinese_roberta_wwm_ext_L-12_H-768_A-12
  • 训练语料采用人民日报命名实体识别语料(LOC:地点;ORG:机构名;PER:人名)

设定参数

  • 网络层数=6层(为了训练得快点)
  • crf_only=False
  • batch_size=8(GPU跑不了更大的batch_size了)
  • num_train_epochs=10
    其余参数使用默认值
    以下实验可能不够严谨,不具有参考价值,由于训练时间过长,epoch只选择了10轮,远远没有达到模型最好的效果,只是自己单纯测试一下。每个模型只训练了一次,没有多次训练取平均值。

开始实验

首先先放上crf_only=True的结果,即只用BERT+CRF来做NER任务

              LOC: precision:  93.62%; recall:  94.83%; FB1:  94.22  3509
              ORG: precision:  88.61%; recall:  91.27%; FB1:  89.92  2231
              PER: precision:  96.69%; recall:  96.43%; FB1:  96.56  1815

接着是crf_only=False的结果,即BERT+BiLSTM+CRF

              LOC: precision:  93.27%; recall:  95.21%; FB1:  94.23  3536
              ORG: precision:  87.97%; recall:  91.14%; FB1:  89.52  2244
              PER: precision:  96.56%; recall:  97.25%; FB1:  96.91  1833

可以看到加了BiLSTM后,效果好像有一点提升,但是很多指标好像还不如不加BiLSTM

BERT+BiLSTM+CNN+CRF

接下来将四层相同的CNN网络加入BiLSTM和CRF之间,卷积核尺寸为5,400维。没有drouout等其它层,修改lstm_crf_layer.py中的add_blstm_crf_layer函数为

    def add_blstm_crf_layer(self, crf_only):
        """
        blstm-cnn-crf网络
        :return:
        """
        if self.is_training:
            # lstm input dropout rate i set 0.9 will get best score
            self.embedded_chars = tf.nn.dropout(self.embedded_chars, self.dropout_rate)

        if crf_only:
            logits = self.project_crf_layer(self.embedded_chars)
        else:
            # blstm
            lstm_output = self.blstm_layer(self.embedded_chars)
            conv1 = tf.layers.conv1d(lstm_output, 400, 5, padding='same',
                                         name='conv1')
            conv2 = tf.layers.conv1d(conv1, 400, 5, padding='same',
                                         name='conv2')
            conv3 = tf.layers.conv1d(conv2, 400, 5, padding='same',
                                         name='conv3')
            conv4 = tf.layers.conv1d(conv3, 400, 5, padding='same',
                                         name='conv4')
            logits = self.project_bilstm_layer(conv4)
            # project
            # logits = self.project_bilstm_layer(lstm_output)
            
        # crf
        loss, trans = self.crf_layer(logits)
        # CRF decode, pred_ids 是一条最大概率的标注路径
        pred_ids, _ = crf.crf_decode(potentials=logits, transition_params=trans, sequence_length=self.lengths)
        return (loss, logits, trans, pred_ids)

实验结果:

              LOC: precision:  94.09%; recall:  95.06%; FB1:  94.57  3500
              ORG: precision:  88.92%; recall:  91.87%; FB1:  90.37  2238
              PER: precision:  96.71%; recall:  96.87%; FB1:  96.79  1823

BERT+BiLSTM+Attention+CRF

采用self-attention,其中的attention_layer, get_shape_list函数来自于bert_base.bert.modeling

    def add_blstm_crf_layer(self, crf_only):
        """
        blstm-attention-crf网络
        :return:
        """
        if self.is_training:
            # lstm input dropout rate i set 0.9 will get best score
            self.embedded_chars = tf.nn.dropout(self.embedded_chars, self.dropout_rate)

        if crf_only:
            logits = self.project_crf_layer(self.embedded_chars)
        else:
            # blstm
            lstm_output = self.blstm_layer(self.embedded_chars)
            input_shape = get_shape_list(lstm_output, expected_rank=3)
            attention_head = attention_layer(
                from_tensor=lstm_output,
                to_tensor=lstm_output,
                attention_mask=None,
                num_attention_heads=12,
                size_per_head=int(768 / 12),
                attention_probs_dropout_prob=0.1,
                initializer_range=0.02,
                do_return_2d_tensor=True,
                batch_size=input_shape[0],
                from_seq_length=input_shape[1],
                to_seq_length=input_shape[1])

            logits = self.project_bilstm_layer(attention_head)
            # project
            # logits = self.project_bilstm_layer(lstm_output)
            
        # crf
        loss, trans = self.crf_layer(logits)
        # CRF decode, pred_ids 是一条最大概率的标注路径
        pred_ids, _ = crf.crf_decode(potentials=logits, transition_params=trans, sequence_length=self.lengths)
        return (loss, logits, trans, pred_ids)

实验结果为:

              LOC: precision:  92.58%; recall:  92.55%; FB1:  92.57  3463
              ORG: precision:  85.48%; recall:  88.87%; FB1:  87.14  2252
              PER: precision:  94.16%; recall:  95.66%; FB1:  94.90  1849

BiLSTM+CNN+Attention+CRF

简单地将上面两个模型结合起来,代码就不贴了
实验结果为

              LOC: precision:  93.29%; recall:  94.66%; FB1:  93.97  3515
              ORG: precision:  88.22%; recall:  90.26%; FB1:  89.23  2216
              PER: precision:  96.26%; recall:  96.15%; FB1:  96.21  1818

总结

虽然实验结果不够严谨,不具有权威性,但总的来看BERT+BiLSTM+CNN+CRF的效果比较好,其余的几个结果差不多,起码没有比基准的BERT+CRF差。CNN的参数和一些dropout的搭配可能会让效果有所提升,但是加上CNN的原因和原理不得而知,可能有相关的论文我没看到,等有时间再看一下SPAN的原理和实现,和NER任务结合做一些实验。

你可能感兴趣的:(命名实体识别不只有BiLSTM+CRF)