前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >如何和用keras和tensorflow构建企业级NER

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

作者头像
AI研习社
发布2018-12-29 12:08:53
1.1K0
发布2018-12-29 12:08:53
举报
文章被收录于专栏: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的数据集。

代码语言:javascript
复制
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个不同的命名实体(标签)。

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

代码语言:javascript
复制
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

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

代码语言:javascript
复制
getter = SentenceGetter(data)
sent = getter.get_next()
print(sent)
[('Thousands', 'O'), ('of', 'O'), ('demonstrators', 'O'), ('have', 'O'), ('marched',
代码语言:javascript
复制
sentences = getter.sentences
print(len(sentences))
47959
largest_sen = max(len(sen) for sen in sentences)
print('biggest sentence has {} words'.format(largest_sen))
代码语言:javascript
复制
biggest sentence has 104 words

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

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

代码语言:javascript
复制
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]
代码语言:javascript
复制
['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']

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

代码语言:javascript
复制
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]
代码语言:javascript
复制
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以开始构建网络。

代码语言:javascript
复制
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
代码语言:javascript
复制
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嵌入:

代码语言:javascript
复制
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"]

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

代码语言:javascript
复制
from keras.models import Model, Input
from keras.layers.merge import add
from keras.layers import LSTM, Embedding, Dense, TimeDistributed, Dropout, Bidirectional, Lambda
代码语言:javascript
复制
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)
代码语言:javascript
复制
model = Model(input_text, out)
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])

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

代码语言:javascript
复制
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
代码语言:javascript
复制
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份数:

代码语言:javascript
复制
from seqeval.metrics import precision_score, recall_score, f1_score, classification_report
代码语言:javascript
复制
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))
代码语言:javascript
复制
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,是一个突出的成绩。它胜过本节开头提到的其它三种深度学习方法的结果,也很容易被业界所采用。

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

代码语言:javascript
复制
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]))
代码语言:javascript
复制
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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-12-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 AI研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • NER方法:
相关产品与服务
GPU 云服务器
GPU 云服务器(Cloud GPU Service,GPU)是提供 GPU 算力的弹性计算服务,具有超强的并行计算能力,作为 IaaS 层的尖兵利器,服务于生成式AI,自动驾驶,深度学习训练、科学计算、图形图像处理、视频编解码等场景。腾讯云随时提供触手可得的算力,有效缓解您的计算压力,提升业务效率与竞争力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档