代码分析 :BiLSTM-CRF 命名实体识别

指标:

BIO标注集,即B-PER、I-PER代表人名首字、人名非首字,B-LOC、I-LOC代表地名首字、地名非首字,B-ORG、I-ORG代表组织机构名首字、组织机构名非首字,O代表该字不属于命名实体的一部分

accuracy: 96.27%; precision: 61.12%; recall: 61.64%; FB1: 61.38

LOC: precision: 82.62%; recall: 77.02%; FB1: 79.73 2682
ORG: precision: 68.35%; recall: 65.21%; FB1: 66.74 1270
PER: precision: 81.45%; recall: 62.85%; FB1: 70.95 1531

训练库 222万行
训练时长 普通笔记本 20小时

代码模块说明:
python main.py –mode=train

默认参数说明:

#描述
parser = argparse.ArgumentParser(description='BiLSTM-CRF for Chinese NER task')

#训练数据路径
parser.add_argument('--train_data', type=str, default='data_path', help='train data source')

#测试数据路径
parser.add_argument('--test_data', type=str, default='data_path', help='test data source')

#每批处理数据的大小
parser.add_argument('--batch_size', type=int, default=64, help='#sample of each minibatch')

#1个epoch等于使用训练集中的全部样本训练一次
parser.add_argument('--epoch', type=int, default=60, help='#epoch of training')

# 列个数
parser.add_argument('--hidden_dim', type=int, default=300, help='#dim of hidden state')

# 优化器  Adam 梯度下降
parser.add_argument('--optimizer', type=str, default='Adam', help='Adam/Adadelta/Adagrad/RMSProp/Momentum/SGD')

parser.add_argument('--CRF', type=str2bool, default=True, help='use CRF at the top layer. if False, use Softmax')

# lr  学习率
parser.add_argument('--lr', type=float, default=0.001, help='learning rate')

# 梯度阈值:clip_gradient
parser.add_argument('--clip', type=float, default=5.0, help='gradient clipping')

# dropout是指在深度学习网络的训练过程中,对于神经网络单元,按照一定的概率将其暂时从网络中丢弃。注意是暂时,对于随机梯度下降来说,由于是随机丢弃,故而每一个mini-batch都在训练不同的网络
parser.add_argument('--dropout', type=float, default=0.5, help='dropout keep_prob')

# 更新向量
parser.add_argument('--update_embedding', type=str2bool, default=True, help='update embedding during training')

# 
parser.add_argument('--pretrain_embedding', type=str, default='random', help='use pretrained char embedding or init it randomly')

parser.add_argument('--embedding_dim', type=int, default=300, help='random init char embedding_dim')

parser.add_argument('--shuffle', type=str2bool, default=True, help='shuffle training data before each epoch')

parser.add_argument('--mode', type=str, default='demo', help='train/test/demo')

parser.add_argument('--demo_model', type=str, default='1499785642', help='model for test and demo')

 

命名实体识别(Named Entity Recognition)

命名实体识别(Named Entity Recognition, NER)是 NLP 里的一项很基础的任务,就是指从文本中识别出命名性指称项,为关系抽取等任务做铺垫。狭义上,是识别出人名、地名和组织机构名这三类命名实体(时间、货币名称等构成规律明显的实体类型可以用正则等方式识别)。当然,在特定领域中,会相应地定义领域内的各种实体类型。

汉语作为象形文字,相比于英文等拼音文字来说,针对中文的NER任务来说往往要更有挑战性,下面列举几点:

(1) 中文文本里不像英文那样有空格作为词语的界限标志,而且“词”在中文里本来就是一个很模糊的概念,中文也不具备英文中的字母大小写等形态指示

(2) 中文的用字灵活多变,有些词语在脱离上下文语境的情况下无法判断是否是命名实体,而且就算是命名实体,当其处在不同的上下文语境下也可能是不同的实体类型

(3) 命名实体存在嵌套现象,如“北京大学第三医院”这一组织机构名中还嵌套着同样可以作为组织机构名的“北京大学”,而且这种现象在组织机构名中尤其严重

(4) 中文里广泛存在简化表达现象,如“北医三院”、“国科大”,乃至简化表达构成的命名实体,如“国科大桥”。

专著 [1] 里比较详细地介绍了 NER 的各种方法(由于出版年限较早,未涵盖神经网络方法),这里笼统地摘取三类方法:

1. 基于规则的方法:利用手工编写的规则,将文本与规则进行匹配来识别出命名实体。例如,对于中文来说,“说”、“老师”等词语可作为人名的下文,“大学”、“医院”等词语可作为组织机构名的结尾,还可以利用到词性、句法信息。在构建规则的过程中往往需要大量的语言学知识,不同语言的识别规则不尽相同,而且需要谨慎处理规则之间的冲突问题;此外,构建规则的过程费时费力、可移植性不好。

2. 基于特征模板的方法

统计机器学习方法将 NER 视作序列标注任务,利用大规模语料来学习出标注模型,从而对句子的各个位置进行标注。常用的应用到 NER 任务中的模型包括生成式模型HMM、判别式模型CRF等。比较流行的方法是特征模板 + CRF的方案:特征模板通常是人工定义的一些二值特征函数,试图挖掘命名实体内部以及上下文的构成特点。对于句子中的给定位置来说,提特征的位置是一个窗口,即上下文位置。而且,不同的特征模板之间可以进行组合来形成一个新的特征模板。CRF的优点在于其为一个位置进行标注的过程中可以利用到此前已经标注的信息,利用Viterbi解码来得到最优序列。对句子中的各个位置提取特征时,满足条件的特征取值为1,不满足条件的特征取值为0;然后把特征喂给CRF,training阶段建模标签的转移,进而在inference阶段为测试句子的各个位置做标注。关于这种方法可以参阅文献 [2] 和 [3]。

3. 基于神经网络的方法

近年来,随着硬件能力的发展以及词的分布式表示(word embedding)的出现,神经网络成为可以有效处理许多NLP任务的模型。这类方法对于序列标注任务(如CWS、POS、NER)的处理方式是类似的,将token从离散one-hot表示映射到低维空间中成为稠密的embedding,随后将句子的embedding序列输入到RNN中,用神经网络自动提取特征,Softmax来预测每个token的标签。这种方法使得模型的训练成为一个端到端的整体过程,而非传统的pipeline,不依赖特征工程,是一种数据驱动的方法;但网络变种多、对参数设置依赖大,模型可解释性差。此外,这种方法的一个缺点是对每个token打标签的过程中是独立的分类,不能直接利用上文已经预测的标签(只能靠隐状态传递上文信息),进而导致预测出的标签序列可能是非法的,例如标签B-PER后面是不可能紧跟着I-LOC的,但Softmax不会利用到这个信息。

学界提出了 LSTM-CRF 模型做序列标注。文献[4][5]在LSTM层后接入CRF层来做句子级别的标签预测,使得标注过程不再是对各个token独立分类。引入CRF这个idea最早其实可以追溯到文献[6]中。文献[5]还提出在英文NER任务中先使用LSTM来为每个单词由字母构造词并拼接到词向量后再输入到LSTM中,以捕捉单词的前后缀等字母形态特征。文献[8]将这个套路用在了中文NER任务中,用偏旁部首来构造汉字。关于神经网络方法做NER,可以看博客[9] ,介绍的非常详细~

基于字的BiLSTM-CRF模型

这段讲得比较啰嗦,大概看看就好。

使用基于字的BiLSTM-CRF,主要参考的是文献[4][5]。使用Bakeoff-3评测中所采用的的BIO标注集,即B-PER、I-PER代表人名首字、人名非首字,B-LOC、I-LOC代表地名首字、地名非首字,B-ORG、I-ORG代表组织机构名首字、组织机构名非首字,O代表该字不属于命名实体的一部分。如:

这里当然也可以采用更复杂的BIOSE标注集。

以句子为单位,将一个含有 nn 个字的句子(字的序列)记作

其中 xi表示句子的第 i 个字在字典中的id,进而可以得到每个字的one-hot向量,维数是字典大小。

 

 

 

模型的第一层是 look-up 层,利用预训练或随机初始化的embedding矩阵将句子中的每个字 xixi 由one-hot向量映射为低维稠密的字向量(character embedding)xiRdxi∈Rd ,dd 是embedding的维度。在输入下一层之前,设置dropout以缓解过拟合。

 

 

 

模型的第三层是CRF层,进行句子级的序列标注。CRF层的参数是一个 (k+2)×(k+2)(k+2)×(k+2)的矩阵 AA ,AijAij 表示的是从第 ii 个标签到第 jj 个标签的转移得分,进而在为一个位置进行标注的时候可以利用此前已经标注过的标签,之所以要加2是因为要为句子首部添加一个起始状态以及为句子尾部添加一个终止状态。如果记一个长度等于句子长度的标签序列 y=(y1,y2,...,yn)y=(y1,y2,…,yn) ,那么模型对于句子 xx 的标签等于 yy 的打分为

可以看出整个序列的打分等于各个位置的打分之和,而每个位置的打分由两部分得到,一部分是由LSTM输出的 pipi 决定,另一部分则由CRF的转移矩阵 AA 决定。进而可以利用Softmax得到归一化后的概率:

 

模型训练时通过最大化对数似然函数,下式给出了对一个训练样本 (x,yx)(x,yx) 的对数似然:

 

如果这个算法要自己实现的话,需要注意的是指数的和的对数要转换成 logiexp(xi)=a+logiexp(xia)log⁡∑iexp⁡(xi)=a+log⁡∑iexp⁡(xi−a)(TensorFlow有现成的CRF,PyTorch就要自己写了),在CRF中上式的第二项使用前向后向算法来高效计算。

模型在预测过程(解码)时使用动态规划的Viterbi算法来求解最优路径:

整个模型的结构如下图所示:

1. 总的来说,经过仔细选择特征模板的CRF模型在人名上的识别效果要优于BiLSTM-CRF,但后者在地名、组织机构名上展现了更好的性能。究其原因,可能是因为:
(1) 人名用字较灵活且长度比较短,用特征模板在窗口内所提取的特征要比神经网络自动学习的特征更有效、干扰更少
(2) 地名、组织机构名的构成复杂、长度较长,使用双向LSTM能够更好地利用句子级的语义特征,而特征模板只能在窗口内进行提取,无法利用整句话的语义。

2. 对于CRF模型来说,使用 {字符,词性,词边界,实体列表} 这一组合模板的效果在CRF模型系列中表现最好(各个单一模板以及其他组合模板的结果未列出)。

3. 对于BiLSTM-CRF模型来说,这里在每一层的处理都是比较简单的,还有可以提高的空间。例如字向量embedding的初始化方式,这里只是用了最简单的随机初始化,然而由于语料规模比较小,所以不太合适。可以考虑对句子做分词,然后将字向量初始化为该字所在词的词向量(可以用在别的大型语料上的预训练值)。此外,还可以尝试文献[5][7][8]的思路,将low-level的特征经过一个RNN或CNN,进而通过“组合”的方式来得到字级别的embedding(英文是用字母构造单词,中文是用偏旁部首构造汉字),将其与随机初始化的字向量拼接在一起。

另外要提的一点是BiLSTM-CRF在这应该是过拟合了,迭代轮数(120轮)给大了,测试集指标在大约60轮之后已经开始下降。应该划个验证集做early stopping。

BiLSTM-CRF模型的代码在GitHub上,README.md里介绍了如何训练、测试。我是用笔记本的显卡训练的,batch_size 取64,Adam优化器训练120个epoch,大概用了4个多小时。如果机器条件允许,不妨试试 batch_size 直接取1,优化器用 SGD+Momentum

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *