从零开始学Python自然语言处理(28)—— Bi-LSTM+CRF完成命名实体识别任务

浏览: 2778

前文传送门:

从零开始学自然语言处理(27)—— 开辟新纪元的Transformer

俗话说的好,NER该怎么做?双向LSTM+CRF啊。这LSTM听说比一般的RNN牛逼,那这双向的LSTM岂不是更牛逼了?(BERT心想:渣渣)

不管是实际业务需要,还是参加一些NLP比赛,亦或是参加面试,都有可能用到NER,那双向LSTM+CRF简直就是NER界的标配。今天我们就手把手教大家如何用Keras优雅的搭建Bi-LSTM+CRF模型。

我们假设大家已经有了可供训练的数据(文末提供数据)。其数据格式应该为这样:

image.png

这里我们以字为单元进行标注。“O”是other的缩写,表示非实体词。“B”表示“开头”,即实体词的开头字符,“I”表示“中间”,实体词的中间字符。我们的数据样例里面是使用的是“BIO”标签,没有“E”,“E”表示的是实体词的结尾。但是在绝大多数情况下,没有“E”问题也不大,毕竟很难找出混淆实体的例子。

关于命名实体识别(NER)任务和Bi-LSTM还有CRF,我们在之前的文章中写过:

从零开始学自然语言处理(六)—— 命名实体识别

从零开始学自然语言处理(22)—— 效果震撼的Bi-LSTM

从零开始学自然语言处理(23)—— 巧妙的条件随机场(CRF)(上)

从零开始学自然语言处理(24)—— 巧妙的条件随机场(CRF)(下)

我们引入我们需要的python包:

    from keras.models import Model
    from keras.layers import Embedding, Bidirectional, LSTM, Dense, Dropout, Input
    from keras_contrib.layers import CRF
    from keras.preprocessing.sequence import pad_sequences
    from keras.utils import np_utils

    keras_contrib.layers.CRF是某个大神封装在keras的CRF包,keras可以直接调用。

    构建模型。

      class bi_lstm_crf():
      def __init__(self,
      vocab_size,
      n_class,
      embedding_dim=128,
      rnn_units=128,
      drop_rate=0.3,
      ):
      self.vocab_size = vocab_size #词汇量
      self.n_class = n_class #NER label种类数
      self.embedding_dim = embedding_dim #embedding层维度
      self.rnn_units = rnn_units #lstm单元维度
      self.drop_rate = drop_rate

      def creat_model(self):
      inputs = Input(shape=(None,))
      embedding = Embedding(input_dim=self.vocab_size, output_dim=self.embedding_dim)(inputs)
      x = Bidirectional(LSTM(units=self.rnn_units, return_sequences=True))(embedding) #双向lstm对输入进行编码
      x = Dropout(self.drop_rate)(x)
      x = Dense(self.n_class)(x)
      crf = CRF(self.n_class, sparse_target=False)
      x = crf(x)
      model = Model(inputs=inputs, outputs=x)
      model.compile('adam',
      loss=crf.loss_function,
      metrics=[crf.accuracy])
      return model

      是不是很简单,模型的代码量其实只有不到10行。首先将文本进行id化,然后输入到embedding层取出对应的词向量,然后经过双向LSTM编码,再经过一层Dense(其实可有可无)的线性变换,最后经过CRF层,即可。

      数据处理的代码稍微复杂一点。我们第一步要做的就是构建词典,即给语料中的每一个词一个唯一的id。同时,我们也需要对label(即那些B、I、O)进行id化处理。具体代码如下:

        from collections import Counter
        def get_dict(min_num = 5):
        paths = [
        'data/example.train',
        'data/example.dev',
        'data/example.test'
        ]

        label_dict = []
        word_dict = []
        for path in paths:
        with open(path) as f:
        for line in f:
        p = line.split()
        if len(p) == 1:
        word_dict.append(p[0])
        elif len(p) == 2:
        word_dict.append(p[0])
        label_dict.append(p[1])

        label_dict = Counter(label_dict)
        word_dict = dict(Counter(word_dict))
        label2id = {key: index for index, key in enumerate(label_dict)}
        id2label = {v: k for k, v in label2id.items()}
        word_dict = {v:k for k,v in dict(word_dict).items() if word_dict.get(k)>min_num }
        word2id = {key: index+2 for index, key in enumerate(word_dict)}
        word2id['PAD'] = 0
        word2id['UNK'] = 1
        id2word = {v: k for k, v in word2id.items()}
        return word2id, id2word, label2id, id2label

        #获取字符-di映射字典,和label-id映射字典
        word2id, id2word, label2id, id2label = get_dict()

        再写一个生成训练数据的函数:

          def get_data(path, train=True):
          X, Y = [], []
          x, y = [], []
          with open(path) as f:
          for line in f:
          if line == '\n':
          X.append(x)
          Y.append(y)
          x = []
          y = []
          continue
          p = line.split()
          x.append(word2id.get(p[0], 1))
          y.append(label2id.get(p[1]))
          if len(x)>0:
          X.append(x)
          Y.append(y)

          return X, Y

          训练数据X,Y以及验证数据val_x,val_y,对其进行补零和截断操作。同时,将label进行to_categorical操作,转化为one-hot编码。

            X, Y = get_data('data/example.train')
            val_x, val_y = get_data('data/example.dev')
            X, Y = pad_sequences(X, maxlen=200), pad_sequences(Y, maxlen=200)
            val_x, val_y = pad_sequences(val_x, maxlen=200), pad_sequences(val_y, maxlen=200)
            Y =np_utils.to_categorical(Y)
            val_y =np_utils.to_categorical(val_y)

            训练模型:

              lstm_crf = bi_lstm_crf(vocab_size=len(word2id), n_class=len(label2id))
              model = lstm_crf.creat_model()
              model.fit(X, Y, batch_size=64, epochs=2,
              validation_data=[val_x, val_y])

              简单训练几轮就可以达到不错的效果。

              欢迎关注公众号“数据科学杂谈”,回复“CRF”获取本文全部代码和数据,本公众号原创技术文章第一时间推送。

              扫码下图关注我们不会让你失望!

              image.png

              推荐 0
              本文由 ID王大伟 创作,采用 知识共享署名-相同方式共享 3.0 中国大陆许可协议 进行许可。
              转载、引用前需联系作者,并署名作者且注明文章出处。
              本站文章版权归原作者及原出处所有 。内容为作者个人观点, 并不代表本站赞同其观点和对其真实性负责。本站是一个个人学习交流的平台,并不用于任何商业目的,如果有任何问题,请及时联系我们,我们将根据著作权人的要求,立即更正或者删除有关内容。本站拥有对此声明的最终解释权。

              0 个评论

              要回复文章请先登录注册