1、引言
通过【信息抽取(一)信息抽取任务初识】一文,我们对于信息抽取任务有了初步的认识,即识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等。通常包括两部分:(1)实体边界识别;(2) 确定实体类别(人名、地名、机构名或其他)。
2、基于规则和词典的方法
(1)基于规则的方法
-
推理的准确性非常好 -
可解释性好,其是具有逻辑性的
-
其需要专家制定大量的推理规则 -
对于不知道规则的泛化性能力比较差
-
身份证号码信息的抽取
regex = "d{17}[d|x|X]"
text = "身份证号码信息是410403198805135531"
id_number = re.findall(regex, text)
print(id_number)
#抽取结果:['410403198805135531']
-
Ip地址信息的抽取
regex = "(?:(?:[0,1]?d?d|2[0-4]d|25[0-5]).){3}(?:[0,1]?d?d|2[0-4]d|25[0-5])"
text = "ip地址信息是127.0.0.1"
ip = re.findall(regex, text)
print(ip)
#抽取结果:['127.0.0.1'']
-
电话号码信息的抽取
regex = "1[34578]d{9}"
text = "电话号码信息是18768875303"
phone_number = re.findall(regex, text)
print(phone_n umber)
#抽取结果:['18768875303']
3、基于机器学习的方法
机器学习模型应用于实体抽取最重要的思想是获取实体的特征信息,本文将以经典的隐马尔代夫模型HMM模型以及随机条件场CRF进行介绍。
-
HMM模型描述的就是由隐状态序列(实体标记) 生成可观测状态(可读文本)的过程
-
观测状态就是:衣服穿多少;隐状态则是:今天的天气温度 -
迁移到我们nlp领域就是,观测状态输入:一串可观测文本,输出则是:文本对应的状态序列。
两个假设:
假设1:当前状态Yt仅仅依赖于前一个状态Yt-1,连续多个状态构成隐马尔可夫链y
假设2:任意时刻的观测X只依赖于该时刻的状态Yt,与其他时刻的状态或观测独立无关
-
模型:(π, A, B) -
观测序列:O = (o1, o2, … ,oT) -
状态序列:I =(i1, i2, … , iT)
-
首先不考虑最大概率问题,先计算出联合概率p(x, y) -
在所有这些成对的x和y的概率里面找到最大的那个概率,也就意味着找到了给定观测序列x时最可能出现的隐状态序列y,这样问题就解决了
(1)先求p(y)
(2)再求p(x|y)
(3)CRF模型原理图:
-
我们能够看到CRF和HMM模型十分相似,但有不同的是,每一个状态,都可以由每一个状态,都可以和整个观测序列X决定,即y3与X都相关。 -
每一个状态,仅与它相邻的状态相关,即y2仅与y1和y3相关
计算公式:
转移特征函数:
转态特征函数:
-
viterbi算法是每次记录到当前时刻,每个观察标签的最优序列,同样以上图例子举例,假设我们在t3时刻已经保存了从0到t3时刻最佳路径,那么在t4时刻,只需要计算从t3到t4的最优就好了。 -
每次只需要保存到当前位置最优路径,之后循环向后走。到结束时,从最后一个时刻的最优值回溯到开始位置,回溯完成后,这个从开始到结束的路径就是最优的。
(4)CRF和HMM对比
二者的对比如下表所示,我们能够看出CRF有着更高的处理效能。
|
|
|
|
|
|
|
|
|
|
|
|
4、基于深度学习的方法
(1)TEXTCNN模型
由上图可知,整个网络分为4层:分别是Embedding层、卷积层、池化层、全连接分类层;
-
输入一句话,句长为7, embedding size为5,每个词被表示成一个5维的向量
2、卷积层
-
卷积层拥有3种高度(2、3、4)的卷积核,表示捕获的上下文长度,类似于n-gram;
-
卷积核的宽度都和词向量的大小(embedding size)一致;
-
每种高度的卷积核,我们设置了2个卷积核,于是最终将拥有6个卷积核,每个卷积核提取不同的特征;
-
每个卷积核在文本序列上滑窗,产生一个特征图(feature map)
-
是一个1-max pooling,每个不等长的特征图被综合成一个数字,最后我们将6个数字拼接起来,形成一个6维的向量
-
我们使用一个6维的向量, 进行多分类, 使用softmax返回每个标签对应的概率;
-
低效的池化层:由于序列标注里每个位置都对应一个标签, 针对特征图的池化会降低输出表示的精度,但不加池化层会使感受野变小,学不到全局的特征。如果我们单纯的去掉池化层、扩大卷积核的话,这样纯粹的扩大卷积核势必导致计算量的增大,此时最好的办法就是就是使用它所提出的膨胀卷积。
-
传统的LSTM应用于中文任务时, 如下图所示,它的输入要么基于字切分,要么基于词切分。但是基于字的用不了词汇信息,基于词的可能存在分词误差,而分词误差对下游的NER影响是致命的。
-
Lattice LSTM将词汇信息引入到基于字的LSTM中,如下图所示,将词汇单元增加到词尾位置,以“长江大桥”为例,“长江”,“长江大桥”,“大桥”就消除了实体“江大桥”的可能性。
-
它对每个词构建词汇单元(Cell),将词汇Cell接入Char级别LSTM,从词首位置开始,到词尾位置终止。
-
下图是词汇单元的结构,b、e分别表示词开始和词终止的位置;上标c、w分别表示字级别和词级别
实现流程:
(5)BERT–BiLSTM-CRF模型
-
需要注意的是,由于LSTM和BERT层承担的作用基本一致,都起到特征抽取的作用,在实际应用场景中,也可以去除LSTM层,直接将CRF接到BERT层后即可。
总结回顾:
本次分享中,从规则、机器学习、深度学习等角度对实体抽取的经典通用模型进行介绍,接下来我们的视角将关注实体抽取的嵌套、不连续等复杂问题的抽取。
def _viterbi_decode(self, feats):
backpointers = []
# 在对数空间初始化维特比变量
init_vvars = torch.full((1, self.tagset_size), -10000.)
0 =
# 第i步的 forward_var 存放第i-1步的维特比变量
forward_var = init_vvars
for feat in feats:
bptrs_t = [] # 存放这一步的后指针
viterbivars_t = [] # 存放这一步的维特比变量
for next_tag in range(self.tagset_size):
# next_tag_var[i] 存放先前一步标签i的维特比变量, 加上了从标签i到next_tag的转移
# (这里暂时没有将发射score添加进来,因为最大值并不依赖它们。我们下面会加它。)
next_tag_var = forward_var + self.transitions[next_tag]
best_tag_id = argmax(next_tag_var)
bptrs_t.append(best_tag_id)
viterbivars_t.append(next_tag_var[0][best_tag_id].view(1))
# 现在将所有发射score相加,更新forward_var
forward_var = (torch.cat(viterbivars_t) + feat).view(1, -1)
backpointers.append(bptrs_t)
# 转移到STOP_TAG
terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]]
best_tag_id = argmax(terminal_var)
path_score = terminal_var[0][best_tag_id]
# 使用后指针解码最优路径
best_path = [best_tag_id]
for bptrs_t in reversed(backpointers):
best_tag_id = bptrs_t[best_tag_id]
best_path.append(best_tag_id)
# 弹出开始标签,无需返回它
start = best_path.pop()
assert start == self.tag_to_ix[START_TAG]
best_path.reverse()
return path_score, best_path
class BiLSTM_CRF(nn.Module):
def __init__(self, vocab_size, tag_to_ix, embedding_dim, hidden_dim):
"""
:param vocab_size: 词库大小
:param tag_to_ix: tag id映射
:param embedding_dim: embedding维度
:param hidden_dim: 隐含层维度
"""
super(BiLSTM_CRF, self).__init__()
self.embedding_dim = embedding_dim
self.hidden_dim = hidden_dim
self.vocab_size = vocab_size
self.tag_to_ix = tag_to_ix
self.tagset_size = len(tag_to_ix) # 获得tag size
# 定义embedding层,
# 设置
# 词库大小(输入序列的最大id不能超过它)
# embedding维度(一个词被表示为向量的维度)
self.word_embeds = nn.Embedding(vocab_size, embedding_dim)
# 定义lstm层,
# 设置
# 输入dim为embedding_dim
# 隐含层单元为 1/2 hidden_dim(双向拼接之后为hidden_dim,送入下一层)
# 一层lstm
# 设置双向
self.lstm = nn.LSTM(embedding_dim, hidden_dim // 2, num_layers=1, bidirectional=True)
# 将bilstm的输出通过全连接层映射到标签空间
self.hidden2tag = nn.Linear(hidden_dim, self.tagset_size)
# CRF层的核心参数,转移矩阵
# transitions[i,j]表示从j转移到i的score
self.transitions = nn.Parameter(
torch.randn(self.tagset_size, self.tagset_size)
)
# 使不能出现 转移到START_TAG
self.transitions.data[tag_to_ix[START_TAG], :] = -10000
# 使不能出现 从STOP_TAG转移
self.transitions.data[:, tag_to_ix[STOP_TAG]] = -10000
class BertBiLSTMCRFModel(nn.Module):
def __init__(self, bert_base_model_dir, label_size, drop_out_rate=0.5):
super(BertCRFModel, self).__init__()
self.label_size = label_size
if 'albert' in bert_base_model_dir.lower():
# 注意albert base使用bert tokenizer,参考https://huggingface.co/voidful/albert_chinese_base
self.bert_tokenizer = BertTokenizer.from_pretrained(bert_base_model_dir)
self.bert_model = AlbertModel.from_pretrained(bert_base_model_dir)
elif 'electra' in bert_base_model_dir.lower():
self.bert_tokenizer = ElectraTokenizer.from_pretrained(bert_base_model_dir)
self.bert_model = ElectraModel.from_pretrained(bert_base_model_dir)
else:
self.bert_tokenizer = BertTokenizer.from_pretrained(bert_base_model_dir)
self.bert_model = BertModel.from_pretrained(bert_base_model_dir)
self.lstm=nn.LSTM(self.bert_model.config.hidden_size,384,num_layers=1, bidirectional=True,batch_first=True) ##双向lstm
self.dropout = nn.Dropout(drop_out_rate)
#self.linear = nn.Linear(self.bert_model.config.hidden_size, label_size)
self.linear = nn.Linear(768, label_size)##bilstm层
self.crf = CRF(label_size) # # 定义CRF层
关于二范数智能:二范数AI教育是一家新锐的AI+科创公司,团队主要来自阿里巴巴,成员毕业于华科、武大、东南大学等知名高校。我们在自然语言处理、计算机视觉、推荐系统等领域有深厚的技术积累,同时也具备多年的教育经验。AI培训,我们是最专业的!
欢迎大家通过下面联系方式联系我们:
原文始发于微信公众号(二范数智能):信息抽取(二)实体识别之经典方法介绍