【NLP实战系列】Tensorflow命名实体识别实战

实战是学习一门技术最好的方式,也是深入了解一门技术唯一的方式。因此,NLP专栏计划推出一个实战专栏,让有兴趣的同学在看文章之余也可以自己动手试一试。

本篇介绍自然语言处理中一种非常重要的任务:命名实体识别。因为最常见的是Bilstm+CRF模型进行实体识别,本文介绍介绍另外一种有效的模型,Dilated-CNN+CRF模型,但是两种模型的代码都会给出。

作者&编辑 | 小Dream哥

1 命名实体识别任务介绍 

笔者在这篇文章中,曾经系统的介绍过命名实体识别任务的相关概念语料标注方式,不了解的同学可以先阅读这篇文章:

【NLP-NER】什么是命名实体识别?

关于Bilstm和Dilated-CNN两个模型理论方面的内容,笔者在这篇文章中做了详细的介绍,不了解的同学可以先阅读这篇文章:

【NLP-NER】命名实体识别中最常用的两种深度学习模型

话不多说,既然是实战篇,我们就赶紧开始吧。

2 数据预处理

1) 查看数据格式

先了解一下数据格式,方便后面进行处理。如下图所示,语料为标准的BIO标注方式,每个字和标记之间用空格隔开,语料之间用一个空行隔开

2)读取训练数据

def load_sentences(path, lower, zeros):
   """
   加载训练,测试,验证数据的函数
   """
   sentences = []
   sentence = []
   num = 0
   for line in codecs.open(path, 'r', 'utf8'):
       num+=1
       line = zero_digits(line.rstrip()) if zeros else line.rstrip()
       if not line:
           if len(sentence) > 0:
               if 'DOCSTART' not in sentence[0][0]:
                   sentences.append(sentence)
               sentence = []
       else:
           if line[0] == " ":
               line = "$" + line[1:]
               word = line.split()
           else:
               word= line.split( )
           assert len(word) == 2
           sentence.append(word)
   if len(sentence) > 0:
       if 'DOCSTART' not in sentence[0][0]:
           sentences.append(sentence)
   return sentences

上面这个函数是用来读取训练,测试,验证数据的,该函数返回一个列表,如下图所示:

3) 标记格式转化

因为原始语料用的是BIO标注方法,但是BIOES标注形式包含了实体内更多的位置和边界信息,最好将其转化为BIOES的形式,方法如下:

def update_tag_scheme(sentences, tag_scheme):
   """
将IOB格式转化为BIOES格式。
   """
   for i, s in enumerate(sentences):
       tags = [w[-1] for w in s]
       # 保证语料上BIO格式
       if not iob2(tags):
           s_str = '\n'.join(' '.join(w) for w in s)
##产生异常
       if tag_scheme == 'iob':
           # 转化为BIOES
           for word, new_tag in zip(s, tags):
               word[-1] = new_tag
       elif tag_scheme == 'iobes':
           new_tags = iob_iobes(tags)
           for word, new_tag in zip(s, new_tags):
               word[-1] = new_tag
       else:
           raise Exception('Unknown tagging scheme!')

看看效果如何:

4)构造字典

经过上述步骤,虽然我们将数据读到了一个列表里组织了起来,但是一个一个的汉字及其标签,后面的LSTM或者CNN网络是没有办法处理的,需要进行词嵌入。在此之前需要构造2个字典,汉字字典和标签字典,然后将上面的汉字和标签转化成字典里的序号。看看代码是怎么做的:

def char_mapping(sentences, lower):
      #生成字典和mapping
   chars = [[x[0].lower() if lower else x[0] for x in s] for s in sentences]
   dico = create_dico(chars)
   dico["<PAD>"] = 10000001
   dico['<UNK>'] = 10000000
   char_to_id, id_to_char = create_mapping(dico)
   #print("Found %i unique words (%i in total)" % (
   #    len(dico), sum(len(x) for x in chars)
   #))
   return dico, char_to_id, id_to_char

def create_dico(item_list):
  #根据传入的列表,生成一个字典
   assert type(item_list) is list
   dico = {}
   for items in item_list:
       for item in items:
           if item not in dico:
               dico[item] = 1
           else:
               dico[item] += 1
   return dico

def create_mapping(dico):
   #生成连个字典,id->word word->id
   sorted_items = sorted(dico.items(), key=lambda x: (-x[1], x[0]))
   id_to_item = {i: v[0] for i, v in enumerate(sorted_items)}
   item_to_id = {v: k for k, v in id_to_item.items()}
   return item_to_id, id_to_item

上述过程并不难理解,大家好好看看,我们看看生成的字典:

5) 根据字典组织好训练数据

训练时,应该是输入汉字序列,然后预测出一个BIO序列,所以我们的输入数据还需要整理,将汉字和标记分开,得到汉字序列及其标记序列,并且要将汉字及其标记转换成他们在字典里的序号,转化结果如下:

至此,我们将训练数据结构化的组织了起来,放在一个大列表里,并且列表里的数据通过生成的字典转化成了id。

3 模型实现

1)word embedding

我们将语料中的中文换成了他们在字典里的ID,了解词向量的同学都知道我们还需要对词进行词嵌入,后面的深度学习模型才能进行处理。不了解的同学可以找词向量的文章看一下。

下面看看如何实现:

def embedding_layer(self, char_inputs, seg_inputs, config, name=None):
   """
:param char_inputs: one-hot encoding of sentence
   :param seg_inputs: segmentation feature
   :param config: wither use segmentation feature
   :return: [1, num_steps, embedding size],

   """

embedding = []
   with tf.variable_scope("char_embedding" if not name else name), tf.device('/cpu:0'):

# 生成一个表,用于后面查表
       self.char_lookup = tf.get_variable(
               name="char_embedding",
               shape=[self.num_chars, self.char_dim],
               initializer=self.initializer)

# 查表,将词向量化 

embedding.append(tf.nn.embedding_lookup(self.char_lookup, char_inputs))

# 分词的维度,可先不关注
       if config["seg_dim"]:
           with tf.variable_scope("seg_embedding"), tf.device('/cpu:0'):
               self.seg_lookup = tf.get_variable(
                   name="seg_embedding",
                   shape=[self.num_segs, self.seg_dim],
                   initializer=self.initializer)

embedding.append(tf.nn.embedding_lookup(self.seg_lookup, seg_inputs))
       embed = tf.concat(embedding, axis=-1)
   return embed

也许有同学会问,这里的词向量表是随机初始化的。如果我之前训练好了一份词向量,该如何使用呢?可以在定义好模型之后,char_lookup.assign函数为该词向量表赋值,参考如下:

通过embedding_layer,就将形状为[bacth_size, seq_length]的输入数据转化为形状为[bacth_size, seq_length,embeding_dim]的矩阵了。词向量化几乎是所有自然语言处理任务所必须要经过的步骤,读者务必弄明白。

2)构建dilated CNN模型

首先,做一些数据的维度变化,并做一次CNN特征提取

def IDCNN_layer(self, model_inputs,
               name=None):
    #param idcnn_inputs: [batch_size, num_steps, emb_size]
    #return: [batch_size, num_steps, cnn_output_width]


   model_inputs = tf.expand_dims(model_inputs, 1)
   reuse = False
   if self.dropout == 1.0:
       reuse = True
   with tf.variable_scope("idcnn" if not name else name):
       shape=[1, self.filter_width, self.embedding_dim,
                  self.num_filter]
       filter_weights = tf.get_variable(
           "idcnn_filter",
           shape=[1, self.filter_width, self.embedding_dim,
                  self.num_filter],
           initializer=self.initializer)

#输入的尺寸 = [batch, in_height, in_width, in_channels]
        #卷积核尺寸 [filter_height, filter_width, in_channels, out_channels]

layerInput = tf.nn.conv2d(model_inputs,
                                 filter_weights,
                                 strides=[1, 1, 1, 1],
                                 padding="SAME",                                                         name="init_layer",

use_cudnn_on_gpu=True)
       finalOutFromLayers = []
       totalWidthForLastDim = 0

#重复4次增强特征提取能力
       for j in range(self.repeat_times):

#每次有3次卷积操作,前两次卷积膨胀系数为

              1,后一次膨胀系数为2
           for i in range(len(self.layers)):
               dilation = self.layers[i]['dilation']
               isLast = True if i == (len(self.layers) - 1) else False
               with tf.variable_scope("atrous-conv-layer-

%d" % i,
                                      reuse=True
                                      if (reuse or j > 0) else False):
#卷积核

w = tf.get_variable(
                       "filterW",
                       shape=[1, self.filter_width,

self.num_filter,
                                    self.num_filter],

initializer=tf.contrib.layers.

xavier_initializer())
                   b = tf.get_variable("filterB", shape=[self.num_filter])
                   conv = tf.nn.atrous_conv2d(layerInput,w,
                                              rate=dilation,
                                              padding="SAME")
                   conv = tf.nn.bias_add(conv, b)
                   conv = tf.nn.relu(conv)
                   if isLast:
                       finalOutFromLayers.append(conv)
                       totalWidthForLastDim += self.num_filter
                   layerInput = conv
       finalOut = tf.concat(axis=3, values=finalOutFromLayers)
       keepProb = 1.0 if reuse else 0.5
       finalOut = tf.nn.dropout(finalOut, keepProb)

finalOut = tf.squeeze(finalOut, [1])
       finalOut = tf.reshape(finalOut, [-1, totalWidthForLastDim])
       self.cnn_output_width = totalWidthForLastDim
       return finalOut

重复做卷积操作进行特征提取,膨胀卷积可以扩大卷积核的感受野,提高卷积操作对长序列特征的提取能力。同时加深深度,也可以进一步提高特征提取能力。如下,repeat times为4,dilation数组为[1,1,2]。

3)project层

用一个中间层,将CNN的输出转化为shape为[batch_size, num_steps, num_tags]的输出,后面可以接softmax作为输出,也可以接crf计算loss

4)CRF层计算loss

采用CRF计算损失几乎是序列标注问题的标准做法,关于CRF的相关问题,笔者在机器学习模型介绍条件随机场CRF一文中做了详细介绍,不了解的读者可以出门看看。

4 开始训练

在main文件开头的参数配置中,选择train为True:

flags.DEFINE_boolean("train",       True,      "Whether train the model")

执行python main.py即可以开始进行训练,学习率,batch_size,dropout等超参数,读者可以自行调整。

训练开始后,会有如下的打印信息,读者可以观察loss下降的趋势。

训练结束后,会在ckpt文件夹生成模型文件。

5 模型测试

在main文件开头的参数配置中,选择train为False:

flags.DEFINE_boolean("train",       False,      "Whether train the model")

我们看看结果是怎么样的:

输入“明天我想去深圳市”模型将“明天”作为TIME提取出来了,将“深圳市”作为LOC提取出来了。

至此,介绍了如何利用构建DI-CNN模型进行命名实体识别,代码在我们有三AI的github可以下载:https://github.com/longpeng2008/yousan.ai/tree/master/natural_language_processing

找到ner文件夹,执行python3 main.py就可以训练或者测试了。

总结

NER是一个非常基础,但是非常重要的任务,在具体的操作中,相信大家能够更为细致的体会NER任务的真正作用和意涵。

我们也会在知识星球还介绍了基于BERT来做NER的方法,感兴趣扫描下面的二维码了解。

(0)

相关推荐

  • 解析Transformer模型

    ❝ GiantPandaCV导语:这篇文章为大家介绍了一下Transformer模型,Transformer模型原本是NLP中的一个Idea,后来也被引入到计算机视觉中,例如前面介绍过的DETR就是将 ...

  • 深度学习尝鲜-DeepFM模型原理

    一.背景 精排模型是推荐系统中效果产出最重要的模块,深度排序模型是推荐领域应用最广迭代最快的技术领域.近年来各大公司纷纷抛弃了原有的传统机器学习模型转向深度排序模型的研究和应用,提出了非常多结合工业应 ...

  • 搞懂 Vision Transformer 原理和代码,看这篇技术综述就够了(八)

    作者丨科技猛兽 审稿丨邓富城 编辑丨极市平台 极市导读 本文为详细解读Vision Transformer的第八篇,本文主要介绍了两个用以加深Transformer模型的工作:DeepViT.CaiT ...

  • 广告ctr预估场景下的dnn调优实战

    特征 DNN需要组合特征 LR模型的时候,我们需要构造许多组合特征,比如UserID与ItemID的组合,许多做DNN的都宣称简化了特征工程,由隐层学习特征交叉,但是隐层进行特征组合的方式并没有明确的 ...

  • CTR学习笔记&代码实现2-深度ctr模型 MLP->Wide&Deep

    背景 这一篇我们从基础的深度ctr模型谈起.我很喜欢Wide&Deep的框架感觉之后很多改进都可以纳入这个框架中.Wide负责样本中出现的频繁项挖掘,Deep负责样本中未出现的特征泛化.而后续 ...

  • 【NLP基础】NLP关键字提取技术之LDA算法原理与实践

    人们是如何从大量文本资料中便捷得浏览和获取信息?答案你肯定会说通过关键字.仔细想想,我们人类是怎么提取关键词?我们从小就接触语言,语法,当听到或者看到一句话时,我们大脑自动会对这句话按规则分词(小学是 ...

  • 【NLP实战】tensorflow词向量训练实战

    实战是学习一门技术最好的方式,也是深入了解一门技术唯一的方式.因此,NLP专栏计划推出一个实战专栏,让有兴趣的同学在看文章之余也可以自己动手试一试. 本篇介绍自然语言处理中最基础的词向量的训练. 作者 ...

  • 【每周NLP论文推荐】 NLP中命名实体识别从机器学习到深度学习的代表性研究

    NER是自然语言处理中相对比较基础的任务,但却是非常重要的任务.在NLP中,大部分的任务都需要NER的能力,例如,聊天机器人中,需要NER来提取实体完成对用户输入的理解:在信息提取任务中,需要提取相应 ...

  • 搜索中的命名实体识别

    最近在做的工作主要是在命名实体识别上,那么在搜索场景,命名实体识别是一个什么样的存在,又是怎么实施落地的,今天来给大家具体讲讲.(额,又是一篇搜索和NLP交叉的文章,由于更偏向NLP的通式通法,所以我 ...

  • 【NLP-NER】如何使用BERT来做命名实体识别

    命名实体识别(Named Entity Recognition,NER)是NLP中一项非常基础的任务.NER是信息提取.问答系统.句法分析.机器翻译等众多NLP任务的重要基础工具. 上一期我们详细介绍 ...

  • 【NLP-NER】命名实体识别中最常用的两种深度学习模型

    命名实体识别(Named Entity Recognition,NER)是NLP中一项非常基础的任务.NER是信息提取.问答系统.句法分析.机器翻译等众多NLP任务的重要基础工具. 上一期我们介绍了N ...

  • 【NLP-NER】什么是命名实体识别?

    命名实体识别(Named Entity Recognition,NER)是NLP中一项非常基础的任务.NER是信息提取.问答系统.句法分析.机器翻译等众多NLP任务的重要基础工具. 命名实体识别的准确 ...

  • 【学术论文】一种面向微博文本的命名实体识别方法

    命名实体识别(Named Entity Recognition)是指识别文本中的各种实体,如人名.地名.机构名或其他特有标识[1],是自然语言处理(Natural Language Processin ...

  • 【NLP实战系列】朴素贝叶斯文本分类实战

    实战是学习一门技术最好的方式,也是深入了解一门技术唯一的方式.因此,NLP专栏计划推出一个实战专栏,让有兴趣的同学在看文章之余也可以自己动手试一试. 本篇介绍自然语言处理中一种比较简单,但是有效的文本 ...

  • 项目实战系列-增强智能下拉列表【2】

    上次我们介绍了使用COUNTA来实现智能下拉列表,已经非常好用了,但是我在项目却遇到了一些不老实的用户,他跳着配置--我 问题出现了,那么我们是否有更好的办法去兼容了,答案肯定是有的,就是我们今天的主 ...