如何和用keras和tensorflow构建企业级NER

2018 年 12 月 23 日 AI研习社

本文为 AI 研习社编译的技术博客,原标题 :

Named Entity Recognition (NER) with keras and tensorflow

作者 | Nasir Safdari

翻译 | 邓普斯•杰弗

校对 | 酱番梨       整理 | 菠萝妹

原文链接:

https://towardsdatascience.com/named-entity-recognition-ner-meeting-industrys-requirement-by-applying-state-of-the-art-deep-698d2b3b4ede

注:本文的相关链接请点击文末【阅读原文】进行访问


如何和用keras和tensorflow构建企业级NER

应用最新的深度学习方法来满足工业的需求

图片来源:pexels

几年前,当我在一家初创公司做软件工程实习生的时候,我在一份发布网络应用程序的工作中看到了一个新特性。这个应用程序能够识别和解析简历中的重要信息,比如电子邮件地址、电话号码、学位信息等等。我开始与我们的团队讨论可能的方法,我们决定用python构建一个基于规则的解析器,以解析简历的不同部分。在开发解析器一段时间之后,我们意识到上述实现的答案可能不是基于规则实现的。我们开始用google搜索它是如何实现的,我们遇到了术语自然语言处理(NLP)以及与机器学习相关的更具体的命名实体识别(NER)。

图片来源:meenavyas

NER是一种用于识别和分类文本中命名实体的信息提取技术。这些实体可以是预先定义的和通用的,比如位置名称、组织、时间等,或者它们可以非常具体,比如简历中的示例。NER在业务中有各种各样的应用。我认为,当你在写一封电子邮件,你在邮件中提到一个时间或者附加一个文件,gmail会提供设置一个日历通知,或者提醒你附加文件,以防你发送电子邮件时没有附加附件。NER的其他应用包括:从法律、金融和医疗文档中提取重要的命名实体、对新闻提供者的内容进行分类、改进搜索算法等。对于本文的其余部分,我们将简要介绍解决NER问题的不同方法,然后将跳转到实现最先进的NER方法。下面是Suvro对NER的更详细的介绍。

图片来源:pexels


  NER方法:

  • 经典方法:最通用的“基于规则”。以下是Sentdex提供的一个精彩的小视频链接,该视频使用python中的NLTK包实现NER。

  • 机器学习方法:在这个类别中有两种主要的方法:A:将问题看作多类分类,其中命名实体是我们的标签,因此我们可以应用不同的分类算法。NER问题需要,识别和标记命名实体需要彻底理解句子的上下文和句子中单词标签的序列,这种方法忽略了这一点。B:这一类的另一种方法是条件随机场(CRF)模型。它是一种概率图模型,可用于对序列数据进行建模,如句子中的单词标签。有关用python实现CRF的更多细节和完整实现,请参阅Tobias的sarticle。CRF模型能够按顺序捕获当前和先前标签的特征,但是它不能理解正向标签的上下文;这个缺点加上训练CRF模型所涉及的额外特征工程,使得它不太适合于业界。


  图片来源:abajournal

  • 深度学习方法:在讨论NER的深度学习方法(最新技术)的细节之前,我们需要分析适当和清晰的度量来评估模型的性能。当在训练神经网络的不同迭代(epochs)期中,通常使用准确性作为度量指标。然而,在NER的情况下,我们可能正在处理重要的金融、医疗或法律文件,这些文件中的命名实体的精确标识决定了模型的成功。换句话说,假阳性和假阴性在NER任务中具有业务成本。因此,我们评估模型的主要指标将是F1评分,因为我们需要在精确度和召回度之间取得平衡。

构建高性能深层学习方法的另一个重要策略是理解哪种类型的神经网络最适合处理NER问题,因为文本是顺序数据格式。是的,你猜对了……长短期记忆网络(LSTM)。有关LSTM的更多细节见此链接。但并不是任何类型的LSTM都使用NER,我们需要使用双向LSTM,因为使用标准的LSTM进行预测将只考虑文本序列中的“过去”信息。对于NER,由于上下文按顺序覆盖过去和未来标签,因此我们需要同时考虑上文和下文信息。双向LSTM是两个LSTM的组合,一个是从“右到左”向前运行,一个是从“左到右”向后运行。


我们将通过参考实际的研究论文来快速地查看四种最先进结构,然后我们将继续以实现其中精准度最高的方法。


1.双向 LSTM-CRF:

更多细节和实现参考keras。


来自论文(Bidirectional LSTM-CRF Models for Sequence Tagging)


2. 双向 LSTM-CNNs:

更多细节和实现见Keras.

来自文章(Named Entity Recognition with Bidirectional LSTM-CNNs)


3. 双向 LSTM-CNNS-CRF:

来自文章(End-to-end Sequence Labeling via Bi-directional LSTM-CNNs-CRF)


4. ELMo (嵌入式语言模型):

Jay Alammar:  https://jalammar.github.io/illustrated-bert/

最近的一篇论文(Deep contextualized word representations)介绍了一种新型的深层上下文化词表示,它模拟了词语使用的复杂特征(例如,句法和语义),以及这些用法如何在语言上下文中变化(即,对多义进行建模)。新方法(ELMo)具有三个重要表示:

1.上下文:每个单词的表达取决于使用它的整个上下文。

2.深度:单词表达结合了深度预训练神经网络的所有层。

3.基于字符:ELMo表示是纯粹基于字符的,允许网络使用形态学线索来形成对在训练中看不到的词汇的鲁棒表示。

ELMo对语言有很好的理解,因为它是在一个庞大的数据集上训练的,ELMo嵌入是在10亿字的基准上训练的。这种训练被称为双向语言模型(biLM),它能够从过去中过去,并按照单词序列(如句子)预测下一个单词。让我们看看如何实现这种方法。我们将使用kaggle的数据集。

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.style.use("ggplot")
data = pd.read_csv("ner_dataset.csv", encoding="latin1")
data = data.drop(['POS'], axis =1)
data = data.fillna(method="ffill")
data.tail(12)
words = set(list(data['Word'].values))
words.add('PADword')
n_words = len(words)
n_words
35179
tags = list(set(data["Tag"].values))
n_tags = len(tags)
n_tags
17

在我们的数据集中,有47958个句子,35179个不同的单词和17个不同的命名实体(标签)。

让我们看一下数据集中语句长度的分布:

class SentenceGetter(object):
   
   def __init__(self, data):
       self.n_sent = 1
       self.data = data
       self.empty = False
       agg_func = lambda s: [(w, t) for w, t in zip(s["Word"].values.tolist(),s["Tag"].values.tolist())]
       self.grouped = self.data.groupby("Sentence #").apply(agg_func)
       self.sentences = [s for s in self.grouped]
   
   def get_next(self):
       try:
           s = self.grouped["Sentence: {}".format(self.n_sent)]
           self.n_sent += 1
           return s
       except:
           return None

这个类负责将每个具有命名实体(标记)的句子转换为元组列表[(单词,命名实体),…]

getter = SentenceGetter(data)
sent = getter.get_next()
print(sent)
[('Thousands', 'O'), ('of', 'O'), ('demonstrators', 'O'), ('have', 'O'), ('marched',


sentences = getter.sentences
print(len(sentences))
47959
largest_sen = max(len(sen) for sen in sentences)
print('biggest sentence has {} words'.format(largest_sen))


biggest sentence has 104 words

所以最长的句子有140个单词,我们可以看到几乎所有的句子都少于60个单词。

这种方法的最大好处之一是我们不需要任何特征工程;我们所需要的只是句子及其标注的单词,其余的工作由ELMo嵌入完成。为了把我们的句子输入LSTM网络,它们都需要具有相同的大小。查看分布图,我们可以将所有句子的长度设置为50,并为空白空间添加一个通用词;这个过程称为填充(50是一个好数字的另一个原因是我的笔记本电脑不能处理更长的句子)。

max_len = 50
X = [[w[0]for w in s] for s in sentences]
new_X = []
for seq in X:
   new_seq = []
   for i in range(max_len):
       try:
           new_seq.append(seq[i])
       except:
           new_seq.append("PADword")
   new_X.append(new_seq)
new_X[15]


['Israeli','officials','say','Prime','Minister','Ariel',
'Sharon', 'will','undergo','a', 'medical','procedure','Thursday',
'to','close','a','tiny','hole','in','his','heart','discovered',
'during','treatment', 'for','a', 'minor', 'stroke', 'suffered', 'last', 'month', '.', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword',
'PADword', 'PADword', 'PADword', 'PADword', 'PADword', 'PADword',
'PADword', 'PADword']

并且,对于命名实体的一些应用也是如此,但是这次我们需要将标签映射到数字:

from keras.preprocessing.sequence import pad_sequences
tags2index = {t:i for i,t in enumerate(tags)}
y
= [[tags2index[w[1]] for w in s] for s in sentences]
y = pad_sequences(maxlen=max_len, sequences=y, padding="post", value=tags2index["O"])
y[15]


array([4, 7, 7, 0, 1, 1, 1, 7, 7, 7, 7, 7, 9, 7, 7, 7, 7, 7, 7,接下来,我们将数据分割成训练和测试集,然后导入.orflowHub(用于发布、发现和消费机器学习模型的可重用部分的库)来加载ELMo嵌入特性和keras以开始构建网络。

接下来,我们将数据分割成训练和测试集,然后导入tensorflow Hub(用于发布、发现和使用机器学习模型的可重用部分的库)来加载ELMo嵌入特性和keras以开始构建网络。

from sklearn.model_selection import train_test_split
import tensorflow as tf
import tensorflow_hub as hub
from keras import backend as K
X_tr, X_te, y_tr, y_te = train_test_split(new_X, y, test_size=0.1, rando


sess = tf.Session()
K.set_session(sess)
elmo_model = hub.Module("", trainable=True)
sess.run(tf.global_variables_initializer())
sess.run(tf.tables_initializer())

第一次运行以上代码块的会花点时间,因为ELMo将近400 MB。下面,我们使用一个函数将句子转化为ELMo嵌入:

batch_size = 32
def ElmoEmbedding(x):
   return elmo_model(inputs={"tokens": tf.squeeze(tf.cast(x,    tf.string)),"sequence_len": tf.constant(batch_size*[max_len])
                    },
                     signature="tokens",
                     as_dict=True)["elmo"]

现在让我们开始构建神经网络:

from keras.models import Model, Input
from keras.layers.merge import add
from keras.layers import LSTM, Embedding, Dense, TimeDistributed, Dropout, Bidirectional, Lambda


input_text = Input(shape=(max_len,), dtype=tf.string)
embedding = Lambda(ElmoEmbedding, output_shape=(max_len, 1024))(input_text)
x = Bidirectional(LSTM(units=512, return_sequences=True,
                      recurrent_dropout=0.2, dropout=0.2))(embedding)
x_rnn = Bidirectional(LSTM(units=512, return_sequences=True,
                          recurrent_dropout=0.2, dropout=0.2))(x)
x = add([x, x_rnn])  # residual connection to the first biLSTM
out = TimeDistributed(Dense(n_tags, activation="softmax"))(x)

model = Model(input_text, out)
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])

由于我们批尺寸为32,所以给网络输入必须以全部是32的倍数的块为单位:

X_tr, X_val = X_tr[:1213*batch_size], X_tr[-135*batch_size:]
y_tr, y_val = y_tr[:1213*batch_size], y_tr[-135*batch_size:]
y_tr = y_tr.reshape(y_tr.shape[0], y_tr.shape[1], 1)
y_val = y_val.reshape(y_val.shape[0], y_val.shape[1], 1)
history = model.fit(np.array(X_tr), y_tr, validation_data=(np.array(X_val), y_val


Train on 38816 samples, validate on 4320 samples
Epoch 1/3
38816/38816 [==============================] - 834s 21ms/step - loss: 0.0625 - acc: 0.9818 - val_loss: 0.0449 - val_acc: 0.9861
Epoch 2/3
38816/38816 [==============================] - 833s 21ms/step - loss: 0.0405 - acc: 0.9869 - val_loss: 0.0417 - val_acc: 0.9868
Epoch 3/3
38816/38816 [==============================] - 831s 21ms/step - loss: 0.0336 - acc: 0.9886 - val_loss: 0.0406 - val_acc: 0.9873

最初的目标是通过参数调优来达到更高的精度,但我的笔记本电脑不能处理超过3个epoch和大于32的betch或增加测试大小。我正在Geforce GTX 1060上运行keras,花了将近45分钟来训练这3个epoch,如果您有更好的GPU,可以通过改变其中的一些参数进行尝试。

0.9873的验证准确度是一个很好结果,但我们不感兴趣以准确度为度量来评估我们的模型。让我们看看如何获得精确度、召回和F1份数:

from seqeval.metrics import precision_score, recall_score, f1_score, classification_report

X_te = X_te[:149*batch_size]
test_pred = model.predict(np.array(X_te), verbose=1)
4768/4768 [==============================] - 64s 13ms/step
idx2tag = {i: w for w, i in tags2index.items()}
def pred2label(pred):
   out
= []
   for pred_i in pred:
       out_i = []
       for p in pred_i:
           p_i = np.argmax(p)
           out_i.append(idx2tag[p_i].replace("PADword", "O"))
       out.append(out_i)
   return out
def test2label(pred):
   out
= []
   for pred_i in pred:
       out_i = []
       for p in pred_i:
           out_i.append(idx2tag[p].replace("PADword", "O"))
       out.append(out_i)
   return out
   
pred_labels = pred2label(test_pred)
test_labels = test2label(y_te[:149*32])
print(classification_report(test_labels, pred_labels))


precision   recall  f1-score   support

       org       0.69      0.66      0.68      2061
       tim       0.88      0.84      0.86      2148
       gpe       0.95      0.93      0.94      1591
       per       0.75      0.80      0.77      1677
       geo       0.85      0.89      0.87      3720
       art       0.23      0.14      0.18        49
       eve       0.33      0.33      0.33        33
       nat       0.47      0.36      0.41        22

avg / total       0.82      0.82      0.82     11301

F1分数为0.82,是一个突出的成绩。它胜过本节开头提到的其它三种深度学习方法的结果,也很容易被业界所采用。

最后,让我们看一下预测会是什么样?

i = 390
p = model.predict(np.array(X_te[i:i+batch_size]))[0]
p = np.argmax(p, axis=-1)
print("{:15} {:5}: ({})".format("Word", "Pred", "True"))
print("="*30)
for w, true, pred in zip(X_te[i], y_te[i], p):
   if w != "__PAD__":
       print("{:15}:{:5} ({})".format(w, tags[pred], tags[true]))


Word            Pred : (True)
==============================
Citing         :O     (O)
a              :O     (O)
draft          :O     (O)
report         :O     (O)
from           :O     (O)
the            :O     (O)
U.S.           :B-org (B-org)
Government     :I-org (I-org)
Accountability :I-org (O)
office         :O     (O)
,              :O     (O)
The            :B-org (B-org)
New            :I-org (I-org)
York           :I-org (I-org)
Times          :I-org (I-org)
said           :O     (O)
Saturday       :B-tim (B-tim)
the            :O     (O)
losses         :O     (O)
amount         :O     (O)
to             :O     (O)
between        :O     (O)
1,00,000       :O     (O)
and            :O     (O)
3,00,000       :O     (O)
barrels        :O     (O)
a              :O     (O)
day            :O     (O)
of             :O     (O)
Iraq           :B-geo (B-geo)
's             :O     (O)
declared       :O     (O)
oil            :O     (O)
production     :O     (O)
over           :O     (O)
the            :O     (O)
past           :B-tim (B-tim)
four           :I-tim (I-tim)
years          :O     (O)
.              :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)
PADword        :O     (O)


像往常一样,代码和jupyter笔记本也可以在我的Github上查看。

最后,非常感谢您的提问与评论。

参考文献:

  1. https://www.depends-on-the-definition.com/named-entity-recognition-with-residual-lstm-and-elmo/

  2. http://www.wildml.com/2016/08/rnns-in-tensorflow-a-practical-guide-and-undocumented-features/

  3. https://allennlp.org/elmo

  4. https://jalammar.github.io/illustrated-bert/


想要继续查看该篇文章相关链接和参考文献?

长按链接点击打开或点击底部【阅读原文】:

https://ai.yanxishe.com/page/TextTranslation/1344


AI研习社每日更新精彩内容,观看更多精彩内容:

五个很厉害的 CNN 架构

一文带你读懂计算机视觉

用Pytorch做深度学习(第一部分)

让神经网络说“我不知道”——用Pyro/PyTorch实现贝叶斯神经网络


等你来译:

对混乱的数据进行聚类 

初学者怎样使用Keras进行迁移学习 

强化学习:通往基于情感的行为系统 

如果你想学数据科学,这 7 类资源千万不能错过

点击 阅读原文 查看本文更多内容↙

登录查看更多
0

相关内容

命名实体识别(NER)(也称为实体标识,实体组块和实体提取)是信息抽取的子任务,旨在将非结构化文本中提到的命名实体定位和分类为预定义类别,例如人员姓名、地名、机构名、专有名词等。

知识荟萃

精品入门和进阶教程、论文和代码整理等

更多

查看相关VIP内容、论文、资讯等
《强化学习—使用 Open AI、TensorFlow和Keras实现》174页pdf
专知会员服务
136+阅读 · 2020年3月1日
KGCN:使用TensorFlow进行知识图谱的机器学习
专知会员服务
81+阅读 · 2020年1月13日
【赠书】TensorFlow自然语言处理
AINLP
17+阅读 · 2019年7月14日
基于TensorFlow和Keras的图像识别
Python程序员
16+阅读 · 2019年6月24日
用 TensorFlow hub 在 Keras 中做 ELMo 嵌入
AI研习社
5+阅读 · 2019年5月12日
如何用TF Serving部署TensorFlow模型
AI研习社
26+阅读 · 2019年3月27日
命名实体识别(NER)综述
AI研习社
66+阅读 · 2019年1月30日
教程 | 如何通过Scikit-Learn实现多类别文本分类?
Tensorflow 文本分类-Python深度学习
Python程序员
12+阅读 · 2017年11月22日
使用 TensorFlow 做文本情感分析
Datartisan数据工匠
15+阅读 · 2017年11月16日
Adversarial Mutual Information for Text Generation
Arxiv
13+阅读 · 2020年6月30日
Seeing What a GAN Cannot Generate
Arxiv
8+阅读 · 2019年10月24日
Arxiv
4+阅读 · 2018年4月9日
VIP会员
相关VIP内容
《强化学习—使用 Open AI、TensorFlow和Keras实现》174页pdf
专知会员服务
136+阅读 · 2020年3月1日
KGCN:使用TensorFlow进行知识图谱的机器学习
专知会员服务
81+阅读 · 2020年1月13日
相关资讯
【赠书】TensorFlow自然语言处理
AINLP
17+阅读 · 2019年7月14日
基于TensorFlow和Keras的图像识别
Python程序员
16+阅读 · 2019年6月24日
用 TensorFlow hub 在 Keras 中做 ELMo 嵌入
AI研习社
5+阅读 · 2019年5月12日
如何用TF Serving部署TensorFlow模型
AI研习社
26+阅读 · 2019年3月27日
命名实体识别(NER)综述
AI研习社
66+阅读 · 2019年1月30日
教程 | 如何通过Scikit-Learn实现多类别文本分类?
Tensorflow 文本分类-Python深度学习
Python程序员
12+阅读 · 2017年11月22日
使用 TensorFlow 做文本情感分析
Datartisan数据工匠
15+阅读 · 2017年11月16日
Top
微信扫码咨询专知VIP会员