点击上方,选择星标置顶,每天给你送干货

阅读大概需要39分钟

跟随小博主,每天进步一丢丢

作者:小宋是呢

本文链接:

https://blog.csdn.net/xiaosongshine/article/details/99622170

开源地址:

https://github.com/xiaosongshine/NLP_NER_RNN_Keras

个人主页:

http://www.yansongsong.cn/

近几年来,基于神经网络的深度学习方法在计算机视觉、语音识别等领域取得了巨大成功,另外在自然语言处理领域也取得了不少进展。在NLP的关键性基础任务—命名实体识别(Named Entity Recognition,NER)的研究中,深度学习也获得了不错的效果。

目录

1.概念讲解

1.1 NER 简介

1.2 深度学习方法在NER中的应用

2.编程实战

2.1 概述

2.2数据预处理

2.3 模型搭建

2.4 模型训练

2.5模型应用

3. 总结&待续

1.概念讲解 

1.1 NER 简介

NER又称作专名识别,是自然语言处理中的一项基础任务,应用范围非常广泛。命名实体一般指的是文本中具有特定意义或者指代性强的实体,通常包括人名、地名、组织机构名、日期时间、专有名词等。NER系统就是从非结构化的输入文本中抽取出上述实体,并且可以按照业务需求识别出更多类别的实体,比如产品名称、型号、价格等。因此实体这个概念可以很广,只要是业务需要的特殊文本片段都可以称为实体。

学术上NER所涉及的命名实体一般包括3大类(实体类,时间类,数字类)和7小类(人名、地名、组织机构名、时间、日期、货币、百分比)。

实际应用中,NER模型通常只要识别出人名、地名、组织机构名、日期时间即可,一些系统还会给出专有名词结果(比如缩写、会议名、产品名等)。货币、百分比等数字类实体可通过正则搞定。另外,在一些应用场景下会给出特定领域内的实体,如书名、歌曲名、期刊名等。

NER是NLP中一项基础性关键任务。从自然语言处理的流程来看,NER可以看作词法分析中未登录词识别的一种,是未登录词中数量最多、识别难度最大、对分词效果影响最大问题。同时NER也是关系抽取、事件抽取、知识图谱、机器翻译、问答系统等诸多NLP任务的基础。

NER当前并不算是一个大热的研究课题,因为学术界部分学者认为这是一个已经解决的问题。当然也有学者认为这个问题还没有得到很好地解决,原因主要有:命名实体识别只是在有限的文本类型(主要是新闻语料中)和实体类别(主要是人名、地名、组织机构名)中取得了不错的效果;与其他信息检索领域相比,实体命名评测预料较小,容易产生过拟合;命名实体识别更侧重高召回率,但在信息检索领域,高准确率更重要;通用的识别多种类型的命名实体的系统性能很差。

总结一下就是从语句中提取出关键名词

1.2 深度学习方法在NER中的应用

NER一直是NLP领域中的研究热点,从早期基于词典和规则的方法,到传统机器学习的方法,到近年来基于深度学习的方法,NER研究进展的大概趋势大致如下图所示。

图1:NER发展趋势

在基于机器学习的方法中,NER被当作序列标注问题。利用大规模语料来学习出标注模型,从而对句子的各个位置进行标注。NER 任务中的常用模型包括生成式模型HMM、判别式模型CRF等。条件随机场(ConditionalRandom Field,CRF)是NER目前的主流模型。它的目标函数不仅考虑输入的状态特征函数,而且还包含了标签转移特征函数。在训练时可以使用SGD学习模型参数。在已知模型时,给输入序列求预测输出序列即求使目标函数最大化的最优序列,是一个动态规划问题,可以使用Viterbi算法解码来得到最优标签序列。CRF的优点在于其为一个位置进行标注的过程中可以利用丰富的内部及上下文特征信息。图2:一种线性链条件随机场

近年来,随着硬件计算能力的发展以及词的分布式表示(word embedding)的提出,神经网络可以有效处理许多NLP任务。这类方法对于序列标注任务(如CWS、POS、NER)的处理方式是类似的:将token从离散one-hot表示映射到低维空间中成为稠密的embedding,随后将句子的embedding序列输入到RNN中,用神经网络自动提取特征,Softmax来预测每个token的标签。

这种方法使得模型的训练成为一个端到端的过程,而非传统的pipeline,不依赖于特征工程,是一种数据驱动的方法,但网络种类繁多、对参数设置依赖大,模型可解释性差。此外,这种方法的一个缺点是对每个token打标签的过程是独立的进行,不能直接利用上文已经预测的标签(只能靠隐含状态传递上文信息),进而导致预测出的标签序列可能是无效的,例如标签I-PER后面是不可能紧跟着B-PER的,但Softmax不会利用到这个信息。

学界提出了DL-CRF模型做序列标注。在神经网络的输出层接入CRF层(重点是利用标签转移概率)来做句子级别的标签预测,使得标注过程不再是对各个token独立分类。

1.2.1  BiLSTM-CRF(RNN base)

LongShort Term Memory网络一般叫做LSTM,是RNN的一种特殊类型,可以学习长距离依赖信息。LSTM由Hochreiter &Schmidhuber (1997)提出,并在近期被Alex Graves进行了改良和推广。在很多问题上,LSTM 都取得了相当巨大的成功,并得到了广泛的使用。LSTM 通过巧妙的设计来解决长距离依赖问题。

所有 RNN 都具有一种重复神经网络单元的链式形式。在标准的RNN中,这个重复的单元只有一个非常简单的结构,例如一个tanh层。

图3:传统RNN结构

LSTM 同样是这样的结构,但是重复的单元拥有一个不同的结构。不同于普通RNN单元,这里是有四个,以一种非常特殊的方式进行交互。

图4:LSTM结构

LSTM通过三个门结构(输入门,遗忘门,输出门),选择性地遗忘部分历史信息,加入部分当前输入信息,最终整合到当前状态并产生输出状态。

图5:LSTM各个门控结构

应用于NER中的biLSTM-CRF模型主要由Embedding层(主要有词向量,字向量以及一些额外特征),双向LSTM层,以及最后的CRF层构成。实验结果表明biLSTM-CRF已经达到或者超过了基于丰富特征的CRF模型,成为目前基于深度学习的NER方法中的最主流模型。在特征方面,该模型继承了深度学习方法的优势,无需特征工程,使用词向量以及字符向量就可以达到很好的效果,如果有高质量的词典特征,能够进一步获得提高。

图6:biLSTM-CRF结构示意图

1.2.2 IDCNN-CRF(CNN base)

对于序列标注来讲,普通CNN有一个不足,就是卷积之后,末层神经元可能只是得到了原始输入数据中一小块的信息。而对NER来讲,整个输入句子中每个字都有可能对当前位置的标注产生影响,即所谓的长距离依赖问题。为了覆盖到全部的输入信息就需要加入更多的卷积层,导致层数越来越深,参数越来越多。而为了防止过拟合又要加入更多的Dropout之类的正则化,带来更多的超参数,整个模型变得庞大且难以训练。因为CNN这样的劣势,对于大部分序列标注问题人们还是选择biLSTM之类的网络结构,尽可能利用网络的记忆力记住全句的信息来对当前字做标注。

但这又带来另外一个问题,biLSTM本质是一个序列模型,在对GPU并行计算的利用上不如CNN那么强大。如何能够像CNN那样给GPU提供一个火力全开的战场,而又像LSTM这样用简单的结构记住尽可能多的输入信息呢?

Fisher Yu and Vladlen Koltun 2015 提出了dilated CNN模型,意思是“膨胀的”CNN。其想法并不复杂:正常CNN的filter,都是作用在输入矩阵一片连续的区域上,不断sliding做卷积。dilated CNN为这个filter增加了一个dilation width,作用在输入矩阵的时候,会skip所有dilation width中间的输入数据;而filter本身的大小保持不变,这样filter获取到了更广阔的输入矩阵上的数据,看上去就像是“膨胀”了一般。

具体使用时,dilated width会随着层数的增加而指数增加。这样随着层数的增加,参数数量是线性增加的,而receptive field却是指数增加的,可以很快覆盖到全部的输入数据。图7:idcnn示意图

图7中可见感受域是以指数速率扩大的。原始感受域是位于中心点的1x1区域:

(a)图中经由原始感受域按步长为1向外扩散,得到8个1x1的区域构成新的感受域,大小为3x3;

(b)图中经过步长为2的扩散,上一步3x3的感受域扩展为为7x7;

(c)图中经步长为4的扩散,原7x7的感受域扩大为15x15的感受域。每一层的参数数量是相互独立的。感受域呈指数扩大,但参数数量呈线性增加。

对应在文本上,输入是一个一维的向量,每个元素是一个character embedding:

图8:一个最大膨胀步长为4的idcnn块

IDCNN对输入句子的每一个字生成一个logits,这里就和biLSTM模型输出logits完全一样,加入CRF层,用Viterbi算法解码出标注结果。

CNN base方法利用空洞卷积+多层的方式实现提取整句的功能,同时也能实现并行计算加速(相较于RNN,CNN与RNN速度对比区别可以参考我之间博文,CNN RNN 并行理解)。

在biLSTM或者IDCNN这样的网络模型末端接上CRF层是序列标注的一个很常见的方法。biLSTM或者IDCNN计算出的是每个词的各标签概率,而CRF层引入序列的转移概率,最终计算出loss反馈回网络。

现在就剩一个问题了:什么是CRF层?为什么要用?

1.2.3 CRF层讲解

接下来,简明介绍一下该模型。
示意图如下所示:

首先,句子xxx中的每个单词表达成一个向量,该向量包含了上述的word embedding和character embedding,其中character embedding随机初始化,word embedding通常采用预训练模型初始化。所有的embeddings 将在训练过程中进行微调。

其次,BiLSTM-CRF模型的的输入是上述的embeddings,输出是该句子xxx中每个单词的预测标签。

尽管,我们讲的是CRF层,不必了解BiLSTM层的细节,但是为了便于了解CRF层,我们必须知道BiLSTM层输出的意义。

从上图可以看出,BiLSTM层的输出是每个标签的得分,如单词w0w_0w0,BiLSTM的输出为1.5(B-Person),0.9(I-Person),0.1(B-Organization), 0.08 (I-Organization) and 0.05 (O),这些得分就是CRF层的输入。

将BiLSTM层预测的得分喂进CRF层,具有最高得分的标签序列将是模型预测的最好结果。

如果没有CRF层将如何?

根据上文,能够发现,如果没有CRF层,即我们用下图所示训练BiLSTM命名实体识别模型:

因为BiLSTM针对每个单词的输出是标签得分,对于每个单词,我们可以选择最高得分的标签作为预测结果。

例如,对于w0w_0w0,“B-Person"得分最高(1.5),因此我们可以选择“B-Person”最为其预测标签;同样的,w1w_1w1的标签为"I-Person”,w2w_2w2的为"O", w3w_3w3的标签为"B-Organization",w4w_4w4的标签为"O"。

按照上述方法,对于xxx虽然我们得到了正确的标签,但是大多数情况下是不能获得正确标签的,例如下图的例子:

显然,输出标签“I-Organization I-Person” 和 “B-Organization I-Person”是不对的。

CRF能够从训练数据中学习到约束条件

CRF层可以对最终的约束标签添加一些约束条件,从而保证预测标签的有效性。而这些约束条件是CRF层自动从训练数据中学到。
约束可能是:

  • 一句话中第一个单词的标签应该是“B-“ or “O”,而不能是"I-";

  • “B-label1 I-label2 I-label3 I-…”中,label1, label2, label3 …应该是相同的命名实体标签。如“B-Person I-Person”是有效的,而“B-Person I-Organization” 是无效的;

  • “O I-label” 是无效的。一个命名实体的第一个标签应该以 “B-“ 开头,而不能以“I-“开头,换句话说, 应该是“O B-label”这种模式;

有了这些约束条件,无效的预测标签序列将急剧减少。

CRF层就是加了约束使得输出更加符合要求,同时也增加算法成本,有些类似束搜索的功能,下面我们看一看CRF层具体如何工作的。

逐帧softmax

CRF主要用于序列标注问题,可以简单理解为是给序列中的每一帧都进行分类,既然是分类,很自然想到将这个序列用CNN或者RNN进行编码后,接一个全连接层用softmax激活,如下图所示

逐帧softmax并没有直接考虑输出的上下文关联

条件随机场

然而,当我们设计标签时,比如用s、b、m、e的4个标签来做字标注法的分词,目标输出序列本身会带有一些上下文关联,比如s后面就不能接m和e,等等。逐标签softmax并没有考虑这种输出层面的上下文关联,所以它意味着把这些关联放到了编码层面,希望模型能自己学到这些内容,但有时候会“强模型所难”。

而CRF则更直接一点,它将输出层面的关联分离了出来,这使得模型在学习上更为“从容”:

CRF在输出端显式地考虑了上下文关联

数学

当然,如果仅仅是引入输出的关联,还不仅仅是CRF的全部,CRF的真正精巧的地方,是它以路径为单位,考虑的是路径的概率。

模型概要

假如一个输入有nn帧,每一帧的标签有kk种可能性,那么理论上就有knkn中不同的输出。我们可以将它用如下的网络图进行简单的可视化。在下图中,每个点代表一个标签的可能性,点之间的连线表示标签之间的关联,而每一种标注结果,都对应着图上的一条完整的路径。

4tag分词模型中输出网络图

而在序列标注任务中,我们的正确答案是一般是唯一的。比如“今天天气不错”,如果对应的分词结果是“今天/天气/不/错”,那么目标输出序列就是bebess,除此之外别的路径都不符合要求。换言之,在序列标注任务中,我们的研究的基本单位应该是路径,我们要做的事情,是从knkn条路径选出正确的一条,那就意味着,如果将它视为一个分类问题,那么将是knkn类中选一类的分类问题!

总结一下:CRF作用可以优化输出实体之间的关联

2.编程实战

2.1 概述

  • 该实战项目参考博文

  • 该项目使用了conll2003_v2数据集,其中标注的命名实体共计九类:

['O', 'B-LOC', 'B-PER', 'B-ORG', 'I-PER', 'I-ORG', 'B-MISC', 'I-LOC', 'I-MISC']

实现了将输入识别为命名实体的模型,如下所示:

# input
['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.']
# output
['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O']

2.2数据预处理

数据下载并解压,以供训练,地址

http://files.deeppavlov.ai/deeppavlov_data/conll2003_v2.tar.gz

下载解压后可以看到三个文件:test.txt,train.txt,valid.txt

打开后可以看到,数据格式如下:我们只需要每行开头和最后一个数据,他们分别是文本信息和命名实体。

-DOCSTART- -X- -X- O
EU      NNP B-NP B-ORG
rejects VBZ B-VP O
German  JJ  B-NP B-MISC
call    NN  I-NP O
to      TO  B-VP O
boycott VB  I-VP O
British JJ  B-NP B-MISC
lamb    NN  I-NP O
.       .   O    O

数据读取与预处理

我们需要将数据进行处理,使之成为网络能接收的形式。

读取数据,并测试输出

from tqdm import tqdm
class NerDatasetReader:
def read(self, data_path):
data_parts = ['train', 'valid', 'test']
extension = '.txt'
dataset = {}
for data_part in tqdm(data_parts):
file_path = data_path + data_part + extension
dataset[data_part] = self.read_file(str(file_path))
return dataset
def read_file(self, file_path):
fileobj = open(file_path, 'r', encoding='utf-8')
samples = []
tokens = []
tags = []
for content in fileobj:
content = content.strip('\n')
if content == '-DOCSTART- -X- -X- O':
pass
elif content == '':
if len(tokens) != 0:
samples.append((tokens, tags))
tokens = []
tags = []
else:
contents = content.split(' ')
tokens.append(contents[0])
tags.append(contents[-1])
return samples
if __name__ == "__main__":
ds_rd = NerDatasetReader()
data1 = ds_rd.read("./conll2003_v2/")
for sample in data1['train'][:2]:
print(sample)
for token, tag in zip(*sample):
print('%s\t%s' % (token, tag))
print()

输出结果

(['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'], ['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O'])
EU      B-ORG
rejects O
German  B-MISC
call    O
to      O
boycott O
British B-MISC
lamb    O
.       O
(['Peter', 'Blackburn'], ['B-PER', 'I-PER'])
Peter   B-PER
Blackburn       I-PER

可以看出数据已经经过整理,每一句保存为了两个list,一个是单词list,另一个是标注list

但是这里还有两个问题:1.网络无法处理单词级别数据,我们需要准换成数值表示 2.每个句子长度不同,无法统一训练,需要归一化

对于问题1.我们可以通过转换为字典的方式来数值化。

def get_dicts(datas):w_all_dict,n_all_dict = {},{}for sample in datas:for token, tag in zip(*sample):if token not in w_all_dict.keys():w_all_dict[token] = 1else:w_all_dict[token] += 1if tag not in n_all_dict.keys():n_all_dict[tag] = 1else:n_all_dict[tag] += 1sort_w_list = sorted(w_all_dict.items(),  key=lambda d: d[1], reverse=True)sort_n_list = sorted(n_all_dict.items(),  key=lambda d: d[1], reverse=True)w_keys = [x for x,_ in sort_w_list[:15999]]w_keys.insert(0,"UNK")n_keys = [ x for x,_ in sort_n_list]w_dict = { x:i for i,x in enumerate(w_keys) }n_dict = { x:i for i,x in enumerate(n_keys) }return(w_dict,n_dict)
if __name__ == "__main__":ds_rd = NerDatasetReader()data1 = ds_rd.read("./conll2003_v2/")w_dict,n_dict = get_dicts(data1["train"])print(len(w_dict),n_dict)

测试输出结果

8000 {'O': 0, 'B-LOC': 1, 'B-PER': 2, 'B-ORG': 3, 'I-PER': 4, 'I-ORG': 5, 'B-MISC': 6, 'I-LOC': 7, 'I-MISC': 8}

我们保留前15999个常用的单词,新增了一个"UNK"代表未知单词。

下面我们就要将利用这些字典把单词给替换为数值

def w2num(datas,w_dict,n_dict):ret_datas = []for sample in datas:num_w_list,num_n_list = [],[]for token, tag in zip(*sample):if token not in w_dict.keys():token = "UNK"if tag not in n_dict:tag = "O"num_w_list.append(w_dict[token])num_n_list.append(n_dict[tag])ret_datas.append((num_w_list,num_n_list,len(num_n_list)))return(ret_datas)
if __name__ == "__main__":ds_rd = NerDatasetReader()dataset = ds_rd.read("./conll2003_v2/")w_dict,n_dict = get_dicts(dataset["train"])data_num = {}data_num["train"] = w2num(dataset["train"],w_dict,n_dict)print(data_num["train"][:4])print(dataset["train"][:4])

测试输出结果,已经实现了数值化的要求。为了方便统计句子长度,每个元祖最后一位保存为了句子长度。

[([957, 11983, 233, 762, 6, 4147, 209, 6182, 1], [3, 0, 6, 0, 0, 0, 6, 0, 0], 9), ([732, 2068], [2, 4], 2), ([1379, 134], [1, 0], 2), ([18, 226, 455, 13, 12, 66, 35, 8127, 24, 233, 4148, 6, 2476, 6, 11984, 209, 6182, 407, 3542, 2069, 499, 1789, 1920, 651, 287, 39, 8128, 6, 1921, 1], [0, 3, 5, 0, 0, 0, 0, 0, 0, 6, 0,
0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 30)]
[(['EU', 'rejects', 'German', 'call', 'to', 'boycott', 'British', 'lamb', '.'], ['B-ORG', 'O', 'B-MISC', 'O', 'O', 'O', 'B-MISC', 'O', 'O']), (['Peter', 'Blackburn'], ['B-PER', 'I-PER']), (['BRUSSELS', '1996-08-22'], ['B-LOC', 'O']), (['The', 'European', 'Commission', 'said', 'on', 'Thursday', 'it', 'disagreed', 'with', 'German', 'advice', 'to', 'consumers', 'to', 'shun', 'British', 'lamb', 'until', 'scientists', 'determine', 'whether', 'mad', 'cow', 'disease', 'can', 'be', 'transmitted', 'to', 'sheep', '.'], ['O', 'B-ORG', 'I-ORG', 'O', 'O', 'O', 'O', 'O', 'O', 'B-MISC', 'O', 'O', 'O', 'O', 'O', 'B-MISC', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O'])]

我们输出句子长度的统计,发现最大值113,最小值为1,为了方便统一训练,我们归一化长度为80

data_num["train"] = w2num(dataset["train"],w_dict,n_dict)
w_lens = [data[-1] for data in  data_num["train"]]
print(max(w_lens),min(w_lens))
#out 113 1

句子长度归一化操作,这里采用padding为0,就是当做“UNK”与“O”来用,其实也可以使用Mask方法等

def len_norm(data_num,lens=80):ret_datas = []for sample1 in list(data_num):sample = list(sample1)ls = sample[-1]#print(sample)while(ls<lens):sample[0].append(0)ls = len(sample[0])sample[1].append(0)else:sample[0] = sample[0][:lens]sample[1] = sample[1][:lens]ret_datas.append(sample[:2])return(ret_datas)
if __name__ == "__main__":ds_rd = NerDatasetReader()dataset = ds_rd.read("./conll2003_v2/")w_dict,n_dict = get_dicts(dataset["train"])data_num = {}data_num["train"] = w2num(dataset["train"],w_dict,n_dict)data_norm = {}data_norm["train"] = len_norm(data_num["train"])print(data_norm["train"][:4])

测试输出结果为

[[[957, 11983, 233, 762, 6, 4147, 209, 6182, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [3, 0, 6, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], [[732, 2068, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [2, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], [[1379, 134, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]], [[18, 226, 455, 13, 12, 66, 35, 8127, 24, 233, 4148, 6, 2476, 6, 11984, 209, 6182, 407, 3542, 2069, 499, 1789, 1920, 651, 287, 39, 8128, 6, 1921, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 3, 5, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]]]

2.3 模型搭建

模型搭建采用了BiRNN方法,具体的说是BiLSTM,为了方便讲解,采用的是RNN+Softmax方式,没有用CRF,后面有时间我会更新一个CRF的版本。网络结构如下:

模型搭建代码

from tensorflow.keras.layers import *
from tensorflow.keras.models import *
from tensorflow.keras.optimizers  import *
def build_model(num_classes=9):
model = Sequential()
model.add(Embedding(16000, 256, input_length=80))
model.add(Bidirectional(LSTM(128,return_sequences=True),merge_mode="concat"))
model.add(Bidirectional(LSTM(128,return_sequences=True),merge_mode="concat"))
model.add(Dense(128, activation='relu'))
model.add(Dense(num_classes, activation='softmax'))
return(model)

输出模型结构

Layer (type)                 Output Shape              Param #
=================================================================
embedding (Embedding)        (None, 80, 256)           4096000
_________________________________________________________________
bidirectional (Bidirectional (None, 80, 256)           394240
_________________________________________________________________
bidirectional_1 (Bidirection (None, 80, 256)           394240
_________________________________________________________________
dense (Dense)                (None, 80, 128)           32896
_________________________________________________________________
dense_1 (Dense)              (None, 80, 9)             1161
=================================================================
Total params: 4,918,537
Trainable params: 4,918,537
Non-trainable params: 0
_________________________________________________________________
None

2.4 模型训练

train_data = np.array(data_norm["train"])
train_x = train_data[:,0,:]
train_y = train_data[:,1,:]
print(train_x.shape)
model.fit(x=train_x,y=train_y,epochs=5,batch_size=200,verbose=1,validation_split=0.1)
model.save("model.h5")

训练10个epoch,MX150GPU耗时五分钟,可以发现train_loss与val_loss都在下降

12636/12636 [==============================] - 68s 5ms/sample - loss: 0.3199 - val_loss: 0.1359
Epoch 2/5
12636/12636 [==============================] - 58s 5ms/sample - loss: 0.1274 - val_loss: 0.1201
Epoch 3/5
12636/12636 [==============================] - 63s 5ms/sample - loss: 0.1099 - val_loss: 0.0957
Epoch 4/5
12636/12636 [==============================] - 58s 5ms/sample - loss: 0.0681 - val_loss: 0.0601
Epoch 5/5
12636/12636 [==============================] - 63s 5ms/sample - loss: 0.0372 - val_loss: 0.0498

2.5模型应用

最终训练10个epoch

model.load_weights("model.h5")
pre_y = model.predict(train_x[:4])
print(pre_y.shape)
pre_y = np.argmax(pre_y,axis=-1)
for i in range(0,len(train_y[0:4])):
print("label "+str(i),train_y[i])
print("pred  "+str(i),pre_y[i])

测试输出结果,可以发现,预测前四个训练集数据达到不错的效果。

label 0 [3 0 6 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]
pred 0 [3 0 6 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]
label 1 [2 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]
pred 1 [2 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]
label 2 [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]
pred 2 [1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]
label 3 [0 3 5 0 0 0 0 0 0 6 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]
pred 3 [0 3 5 0 0 0 0 0 0 6 0 0 0 0 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0]

3. 总结&待续

为了简化,本文只用了RNN+Softmax方法进行了训练集测试,可以改进地方还有很多,例如加入CRF,使用Mask方法,三个数据集都用到等,后面有时间就会进行更新。也欢迎大家一起交流,共同改进。

参考链接:

https://www.jiqizhixin.com/articles/2018-08-31-2

https://blog.csdn.net/suan2014/article/details/89419283

https://spaces.ac.cn/archives/5542

https://blog.csdn.net/chinatelecom08/article/details/82871376

http://www.taodudu.cc/news/show-1703702.html

相关文章:

  • 【干货】神经网络初始化trick:大神何凯明教你如何训练网络!
  • 【亚洲微软研究院】带你8篇论文梳理BERT相关模型进展与反思
  • 【中秋快乐】求问meta-learning和few-shot learning的关系是什么?
  • 【论文整理】NAACL2019+AAAI2019文本分类论文摘要
  • 哈工大SCIR Lab | EMNLP 2019 常识信息增强的事件表示学习
  • 论发SCI论文和生孩子的共同点:那我这篇怀的也太久了!
  • 【NLP】语义角色标注(Semantic Role Labelling)
  • 【论文】使用bilstm在中文分词上的SOTA模型
  • 【论文】图文解读经典之作Span-Graph for SRL - 一篇经典的语义角色标注paper
  • 如何高效并快速的掌握NLP与深度学习路径?来公众号寻找经验吧~
  • 【CNN】十问十答
  • 【文本分类】几个可作为Baseline的模型
  • 【NLP】doc2vec原理及实践
  • 2020年NLP算法秋招“神仙打架”,我该如何应对?
  • 【资源】PyTorch版《动手学深度学习》开源了,最美DL书遇上最赞DL框架
  • 【NLP应用之智能司法】最强之谷歌BERT模型在智能司法领域的实践浅谈
  • 【基础】模型评估指标 AUC 和 ROC,这是我看到的最透彻的讲解
  • 【NLP之情感分析】华为云NLP算法专家:全面解读文本情感分析任务
  • 【论文笔记】基于强化学习的句子摘要排序
  • 【智能医疗】48页论文详述医学AI最新进展
  • 【为书豪相亲】单身小姐姐你在哪里,我是书豪,我在等你
  • 【图神经网络】向往的GAT(图注意力模型)
  • 【比赛】智源-知乎联合发布大规模用户邀请回答数据集,同步开启10万元竞赛...
  • PyTorch1.2.0版本来啦!居然还有全套视频!让你快速熟练掌握深度学习框架!
  • 【论文笔记】基于LSTM的问答对排序
  • 【BERT中文改进版】预训练ALBERT模型:参数更少,效果更好,拿下13项NLP任务
  • 【收藏】NLP技术学习路线图,值得收藏,附下载
  • 【哈工大SCIR Lab】Attention!注意力机制可解释吗?
  • 【Linux】15 张 Vim 速查表奉上,帮你提高 N 倍效率!
  • 【北大知识图谱】知识图谱的关键技术及其智能应用

【干货】图文并茂生动详解命名实体识别NER理论与代码实战相关推荐

  1. 逐行讲解CRF实现命名实体识别(NER)

    文章标题 本文概述 NER介绍 代码详解 任务 导入库 加载数据集 构造特征字典 数据处理 模型训练 模型验证 模型参数 备注 随机搜索RandomizedSearchCV 本文概述 使用sklear ...

  2. 【命名实体识别(NER)】(1):命名实体识别综述

    什么是命名实体识别? 命名实体识别(Named Entity Recognition,简称NER),又称作"专名识别",是自然语言处理中的一项基础任务,应用范围非常广泛.命名实体一 ...

  3. 用隐马尔可夫模型(HMM)做命名实体识别——NER系列(二)

    上一篇文章里<用规则做命名实体识别--NER系列(一)>,介绍了最简单的做命名实体识别的方法–规则.这一篇,我们循序渐进,继续介绍下一个模型--隐马尔可夫模型. 隐马尔可夫模型,看上去,和 ...

  4. NLP入门(八)使用CRF++实现命名实体识别(NER)

    CRF与NER简介   CRF,英文全称为conditional random field, 中文名为条件随机场,是给定一组输入随机变量条件下另一组输出随机变量的条件概率分布模型,其特点是假设输出随机 ...

  5. 命名实体识别NER探索(1)

    命名实体识别NER探索(1) 命名实体识别(Named-entity recognition ,NER)(也称为实体识别.实体分块和实体提取)是信息提取的一个子任务,旨在将非结构化文本中提到的命名实体 ...

  6. NLP命名实体识别NER数据准备及模型训练实例

    NLP命名实体识别NER数据准备及模型训练实例 目录 NLP命名实体识别NER数据准备及模型训练实例 ​ 方案一

  7. NLP命名实体识别(NER)代码实践

    NLP命名实体识别(NER)开源实战教程  引 https://blog.csdn.net/xiaosongshine/article/details/99622170 NER学习系列之-BILSTM ...

  8. 用CRF做命名实体识别——NER系列(三)

    在上一篇文章<用隐马尔可夫模型(HMM)做命名实体识别--NER系列(二)>中,我们使用HMM模型来做命名实体识别,将问题转化为统计概率问题,进行求解.显然,它的效果是非常有限的. 在深度 ...

  9. 信息抽取实战:命名实体识别NER【ALBERT+Bi-LSTM模型 vs. ALBERT+Bi-LSTM+CRF模型】(附代码)

    实战:命名实体识别NER 目录 实战:命名实体识别NER 一.命名实体识别(NER) 二.BERT的应用 NLP基本任务 查找相似词语 提取文本中的实体 问答中的实体对齐 三.ALBERT ALBER ...

  10. 在线中文命名实体识别 ( NER ) 的工具

    在线中文命名实体识别( NER )的工具 命名实体识别(Named Entity Recognition,简称NER),又称作"专名识别",是指识别文本中具有特定意义的实体,主要包 ...

最新文章

  1. Android深度探索第四章
  2. Interview:算法岗位面试—10.17早上—上海某科技公司算法岗位(偏算法,独角兽)非技术面试之比赛项目讲解和项目意义的探讨
  3. 2012年度最受欢迎中国开源软件评选
  4. 数字图像处理——引导滤波
  5. oracle 添加服务命名空间,c# – 找不到类型或命名空间名称“OracleConnection”
  6. 用 Golang 写一个搜索引擎(0x07)--- 正排索引
  7. 京东怎么在线联系客服
  8. Flask项目之手机端租房网站的实战开发(十三)
  9. javascript-----日历控件
  10. JAVA三大框架SSH和MVC
  11. Unity中文API参考手册
  12. Android将网页转为pDf,UrlToPDF 输入网址直接将网页转存为 PDF 档(Android)
  13. 为何公众号推送会延迟发送_微信公众号客服消息群发和推送功能如何实现?
  14. Mono.Cecil ReaderParameters
  15. linux btrfs文件系统,btrfs 文件系统
  16. centos文件系统,日志以及文件误删恢复
  17. ELK学习--Kibana 5.6.5 安装
  18. 基于微信小程序的高校毕业论文管理系统#毕业设计
  19. 简单三步快速远程桌面公司内网电脑【免费内网穿透】
  20. Centos7 更改Apache默认网站目录

热门文章

  1. VS项目属性的一些配置项的总结(important)
  2. Android Drawable之getIntrinsicWidth()和getIntrinsicHeight()
  3. [Linux]Red Hat Linux 9.0环境下架设Web服务器[2]
  4. Ubuntu外观美化安装WPS
  5. spring---aop(10)---Spring AOP中AspectJ
  6. Swing 显示良好JPanel保存为图片
  7. poi导出word模板项目实例(一个文件)
  8. 简要分析武汉一起好P2P平台的核心功能
  9. 初识用.NET Remoting来开发分布式应用
  10. WinForm中的一种死锁场景