一. BILSTM + CRF介绍

https://www.jianshu.com/p/97cb3b6db573

1.介绍

基于神经网络的方法,在命名实体识别任务中非常流行和普遍。 如果你不知道Bi-LSTM和CRF是什么,你只需要记住他们分别是命名实体识别模型中的两个层。

1.1开始之前

我们假设我们的数据集中有两类实体——人名和地名,与之相对应在我们的训练数据集中,有五类标签:

B-Person, I- Person,B-Organization,I-Organization

假设句子x由五个字符w1,w2,w3,w4,w5组成,其中【w1,w2】为人名类实体,【w3】为地名类实体,其他字符标签为“O”。

1.2BiLSTM-CRF模型

以下将给出模型的结构:
第一,句子x中的每一个单元都代表着由字嵌入或词嵌入构成的向量。其中,字嵌入是随机初始化的,词嵌入是通过数据训练得到的。所有的嵌入在训练过程中都会调整到最优。
第二,这些字或词嵌入为BiLSTM-CRF模型的输入,输出的是句子x中每个单元的标签。

Bi-LSTM结构图

尽管一般不需要详细了解BiLSTM层的原理,但是为了更容易知道CRF层的运行原理,我们需要知道BiLSTM的输出层。

图2.Bi-LSTM标签预测原理图

如上图所示,BiLSTM层的输出为每一个标签的预测分值,例如,对于单元w0,BiLSTM层输出的是

1.5 (B-Person), 0.9 (I-Person), 0.1 (B-Organization), 0.08 (I-Organization) 0.05 (O).

这些分值将作为CRF的输入。

1.3 如果没有CRF层会怎样

你也许已经发现了,即使没有CRF层,我们也可以训练一个BiLSTM命名实体识别模型,如图所示:

图3.去除CRF的BiLSTM命名实体识别模型

由于BiLSTM的输出为单元的每一个标签分值,我们可以挑选分值最高的一个作为该单元的标签。例如,对于单元w0,“B-Person”有最高分值—— 1.5,因此我们可以挑选“B-Person”作为w0的预测标签。同理,我们可以得到w1——“I-Person”,w2—— “O” ,w3——“B-Organization”,w4——“O”。
虽然我们可以得到句子x中每个单元的正确标签,但是我们不能保证标签每次都是预测正确的。例如,图4.中的例子,标签序列是“I-Organization I-Person” and “B-Organization I-Person”,很显然这是错误的。

在这里插入图片描述

1.4 CRF层能从训练数据中获得约束性的规则

CRF层可以为最后预测的标签添加一些约束来保证预测的标签是合法的。在训练数据训练过程中,这些约束可以通过CRF层自动学习到。
这些约束可以是:
I:句子中第一个词总是以标签“B-“ 或 “O”开始,而不是“I-”
II:标签“B-label1 I-label2 I-label3 I-…”,label1, label2, label3应该属于同一类实体。例如,“B-Person I-Person” 是合法的序列, 但是“B-Person I-Organization” 是非法标签序列.
III:标签序列“O I-label” is 非法的.实体标签的首个标签应该是 “B-“ ,而非 “I-“, 换句话说,有效的标签序列应该是“O B-label”。
有了这些约束,标签序列预测中非法序列出现的概率将会大大降低。

二. 标签的score和损失函数的定义

https://zhuanlan.zhihu.com/p/27338210

Bi-LSTM layer的输出维度是tag size,这就相当于是每个词 wi 映射到tag的发射概率值,设Bi-LSTM的输出矩阵为P,其中Pi,j代表词wi映射到tagj的非归一化概率。对于CRF来说,我们假定存在一个转移矩阵A,则Ai,j代表tagi转移到tagj的转移概率。
对于输入序列 X 对应的输出tag序列 y,定义分数为

在这里插入图片描述

利用Softmax函数,我们为每一个正确的tag序列y定义一个概率值(YX代表所有的tag序列,包括不可能出现的)

在这里插入图片描述

因而在训练中,我们只需要最大化似然概率p(y|X)即可,这里我们利用对数似然

在这里插入图片描述

所以我们将损失函数定义为-log(p(y|X)),就可以利用梯度下降法来进行网络的学习了。
loss function:

在这里插入图片描述

在对损失函数进行计算的时候,S(X,y)的计算很简单,而

在这里插入图片描述

(下面记作logsumexp)的计算稍微复杂一些,因为需要计算每一条可能路径的分数。这里用一种简便的方法,对于到词wi+1的路径,可以先把到词wi的logsumexp计算出来,因为

在这里插入图片描述

因此先计算每一步的路径分数和直接计算全局分数相同,但这样可以大大减少计算的时间。

三. 对于损失函数的详细解释

这篇文章对于理解十分有用

https://blog.csdn.net/cuihuijun1hao/article/details/79405740

举例说 【我 爱 中国人民】对应标签【N V N】那这个标签就是一个完整的路径,也就对应一个Score值。
接下来我想讲的是这个公式:

在这里插入图片描述

这个公式成立是很显然的,动笔算一算就知道了,代码里其实就是用了这个公式的原理。

def _forward_alg(self, feats): # Do the forward algorithm to compute the partition function init_alphas = torch.full((1, self.tagset_size), -10000.) # START_TAG has all of the score. init_alphas[0][self.tag_to_ix[START_TAG]] = 0. # Wrap in a variable so that we will get automatic backprop forward_var = init_alphas # Iterate through the sentence for feat in feats: alphas_t = [] # The forward tensors at this timestep for next_tag in range(self.tagset_size): # broadcast the emission score: it is the same regardless of # the previous tag emit_score = feat[next_tag].view( 1, -1).expand(1, self.tagset_size) # the ith entry of trans_score is the score of transitioning to # next_tag from i trans_score = self.transitions[next_tag].view(1, -1) # The ith entry of next_tag_var is the value for the # edge (i -> next_tag) before we do log-sum-exp next_tag_var = forward_var + trans_score + emit_score # The forward variable for this tag is log-sum-exp of all the # scores. alphas_t.append(log_sum_exp(next_tag_var).view(1)) forward_var = torch.cat(alphas_t).view(1, -1) terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]] alpha = log_sum_exp(terminal_var) return alpha

我们看到有这么一段代码 :

next_tag_var = forward_var + trans_score + emit_score

我们主要就是来讲讲他。
首先这个算法的思想是:假设我们要做一个词性标注的任务,对句子【我 爱 中华人民】,我们要对这个句子做

在这里插入图片描述

意思就是 对这个句子所有可能的标注,都算出来他们的Score,然后按照指数次幂加起来,再取对数。一般来说取所有可能的标注情况比较复杂,我们这里举例是长度为三,但是实际过程中,可能比这个要大得多,所以我们需要有一个简单高效得算法。也就是我们程序中得用得算法, 他是这么算得:
先算出【我, 爱】可能标注得所有情况,取 log_sum_exp 然后加上 转换到【中国人民】得特征值 再加上【中国人民】对应得某个标签得特征值。其等价于【我,爱,中国人民】所有可能特征值指数次幂相加,然后取对数.
接下来我们来验证一下是不是这样

首先我们假设词性一共只有两种 名词N 和 动词 V
那么【我,爱】得词性组合一共有四种 N + N,N + V, V + N, V + V
那么【爱】标注为N时得log_sum_exp 为

在这里插入图片描述

【爱】 标注为 V时的 log_sum_exp为

在这里插入图片描述

我们的forward列表里就是存在着这两个值,即:

在这里插入图片描述

假设【中华人民】得词性为N,我们按照代码来写一下公式,在forward列表对应位置相加就是这样

在这里插入图片描述

在这里插入图片描述

四. 代码块详细说明:

https://blog.csdn.net/Jason__Liang/article/details/81772632

先说明两个重要的矩阵:

feats: 发射矩阵(emit score)是sentence 在embedding后,再经过LSTM后得到的矩阵(也就是LSTM的输出), 维度为11 * 5 (11为sentence 的length,5是标签数)。这个矩阵表示经过LSTM后sentence的每个word对应的每个labels的得分)。 表示发射概率。

self.transitions:转移矩阵,维度为55,transitions[i][j]表示label j转移到label i的概率。transtion[i]维度为15,表示每个label转移到label i的概率。 表示概率矩阵

1. def log_sum_exp(vec)

# compute log sum exp in numerically stable way for the forward algorithm
def log_sum_exp(vec): #vec是1*5, type是Variable max_score = vec[0, argmax(vec)] # max_score维度是1, max_score.view(1,-1)维度是1*1, # max_score.view(1, -1).expand(1, vec.size()[1])的维度是1*5 max_score_broadcast = max_score.view(1, -1).expand(1, vec.size()[1]) # 里面先做减法,减去最大值可以避免e的指数次,计算机上溢 return max_score + \ torch.log(torch.sum(torch.exp(vec - max_score_broadcast)))

你可能会疑问return 的结果为什么先减去max_score.其实这是一个小技巧,因为一上来就做指数运算可能会引起计算结果溢出,先减去score,经过log_sum_exp后,再把max_score给加上。
其实就等同于:

return torch.log(torch.sum(torch.exp(vec)))

2. def neg_log_likelihood(self, sentence, tags)

如果你完整地把代码读完,你会发现neg_log_likelihood()这个函数是loss function.

loss = model.neg_log_likelihood(sentence_in, targets)

我们来分析一下neg_log_likelihood()函数代码:

    def neg_log_likelihood(self, sentence, tags): # feats: 11*5 经过了LSTM+Linear矩阵后的输出,之后作为CRF的输入。 feats = self._get_lstm_features(sentence) forward_score = self._forward_alg(feats) gold_score = self._score_sentence(feats, tags) return forward_score - gold_score

你在这里可能会有疑问:问什么forward_score - gold_score可以作为loss呢。
这里,我们回顾一下我们在上文中说明的loss function函数公式:

在这里插入图片描述

你就会发现forward_score和gold_score分别表示上述等式右边的两项。

3. def _forward_alg(self, feats):

我们通过上一个函数的分析得知这个函数就是用来求forward_score的,也就是loss function等式右边的第一项:

在这里插入图片描述

 # 预测序列的得分# 只是根据随机的transitions,前向传播算出的一个score#用到了动态规划的思想,但因为用的是随机的转移矩阵,算出的值很大score>20def _forward_alg(self, feats): # do the forward algorithm to compute the partition function init_alphas = torch.full((1, self.tagset_size), -10000.) # 1*5 而且全是-10000 # START_TAG has all of the score # 因为start tag是4,所以tensor([[-10000., -10000., -10000., 0., -10000.]]), # 将start的值为零,表示开始进行网络的传播, init_alphas[0][self.tag_to_ix[START_TAG]] = 0 # warp in a variable so that we will get automatic backprop forward_var = init_alphas # 初始状态的forward_var,随着step t变化 # iterate through the sentence # 会迭代feats的行数次 for feat in feats: #feat的维度是5 依次把每一行取出来~ alphas_t = [] # the forward tensors at this timestep for next_tag in range(self.tagset_size): #next tag 就是简单 i,从0到len # broadcast the emission(发射) score: # it is the same regardless of the previous tag # 维度是1*5 LSTM生成的矩阵是emit score emit_score = feat[next_tag].view( 1, -1).expand(1, self.tagset_size) # the i_th entry of trans_score is the score of transitioning # to next_tag from i trans_score = self.transitions[next_tag].view(1, -1) # 维度是1*5 # The ith entry of next_tag_var is the value for the # edge (i -> next_tag) before we do log-sum-exp # 第一次迭代时理解: # trans_score所有其他标签到B标签的概率 # 由lstm运行进入隐层再到输出层得到标签B的概率,emit_score维度是1*5,5个值是相同的 next_tag_var = forward_var + trans_score + emit_score # The forward variable for this tag is log-sum-exp of all the scores alphas_t.append(log_sum_exp(next_tag_var).view(1)) # 此时的alphas t 是一个长度为5,例如<class 'list'>: # [tensor(0.8259), tensor(2.1739), tensor(1.3526), tensor(-9999.7168), tensor(-0.7102)] forward_var = torch.cat(alphas_t).view(1, -1) #到第(t-1)step时5个标签的各自分数 # 最后只将最后一个单词的forward var与转移 stop tag的概率相加 # tensor([[ 21.1036, 18.8673, 20.7906, -9982.2734, -9980.3135]]) terminal_var = forward_var + self.transitions[self.tag_to_ix[STOP_TAG]] alpha = log_sum_exp(terminal_var) # alpha是一个0维的tensor return alpha

4. def _score_sentence(self, feats, tags)

由2的函数分析,我们知道这个函数就是求gold_score,即loss function的第二项

# 根据真实的标签算出的一个score,# 这与上面的def _forward_alg(self, feats)共同之处在于:# 两者都是用的随机转移矩阵算的score# 不同地方在于,上面那个函数算了一个最大可能路径,但实际上可能不是真实的各个标签转移的值 # 例如:真实标签是N V V,但是因为transitions是随机的,所以上面的函数得到其实是N N N这样, # 两者之间的score就有了差距。而后来的反向传播,就能够更新transitions,使得转移矩阵逼近 #真实的“转移矩阵” # 得到gold_seq tag的score 即根据真实的label 来计算一个score, # 但是因为转移矩阵是随机生成的,故算出来的score不是最理想的值 def _score_sentence(self, feats, tags): #feats 11*5 tag 11 维 # gives the score of a provied tag sequence score = torch.zeros(1) # 将START_TAG的标签3拼接到tag序列最前面,这样tag就是12个了 tags = torch.cat([torch.tensor([self.tag_to_ix[START_TAG]], dtype=torch.long), tags]) for i, feat in enumerate(feats): # self.transitions[tags[i + 1], tags[i]] 实际得到的是从标签i到标签i+1的转移概率 # feat[tags[i+1]], feat是step i 的输出结果,有5个值, # 对应B, I, E, START_TAG, END_TAG, 取对应标签的值 # transition【j,i】 就是从i ->j 的转移概率值 score = score + \ self.transitions[tags[i+1], tags[i]] + feat[tags[i + 1]] score = score + self.transitions[self.tag_to_ix[STOP_TAG], tags[-1]] return score

5. def _viterbi_decode(self, feats):

# 维特比解码, 实际上就是在预测的时候使用了, 输出得分与路径值# 预测序列的得分def _viterbi_decode(self, feats): backpointers = [] # initialize the viterbi variables in long space init_vvars = torch.full((1, self.tagset_size), -10000.) init_vvars[0][self.tag_to_ix[START_TAG]] = 0 # forward_var at step i holds the viterbi variables for step i-1 forward_var = init_vvars for feat in feats: bptrs_t = [] # holds the backpointers for this step viterbivars_t = [] # holds the viterbi variables for this step for next_tag in range(self.tagset_size): # next-tag_var[i] holds the viterbi variable for tag i # at the previous step, plus the score of transitioning # from tag i to next_tag. # we don't include the emission scores here because the max # does not depend on them(we add them in below) # 其他标签(B,I,E,Start,End)到标签next_tag的概率 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)) # now add in the emssion scores, and assign forward_var to the set # of viterbi variables we just computed # 从step0到step(i-1)时5个序列中每个序列的最大score forward_var = (torch.cat(viterbivars_t) + feat).view(1, -1) backpointers.append(bptrs_t) # bptrs_t有5个元素 # transition to STOP_TAG # 其他标签到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] # follow the back pointers to decode the best path 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) # pop off the start tag # we don't want to return that ti the caller start = best_path.pop() assert start == self.tag_to_ix[START_TAG] # Sanity check best_path.reverse() # 把从后向前的路径正过来 return path_score, best_path

如果对于该函数还没有太理解,可以参考这篇博客:

https://blog.csdn.net/appleml/article/details/78579675

总结

以上就是我结合了几篇比较不错的博客后的总结,欢迎大家提问

作者:17shouuu
链接:https://www.jianshu.com/p/566c6faace64
来源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

pytorch BiLSTM+CRF代码详解 重点相关推荐

  1. Pytorch Bi-LSTM + CRF 代码详解

    久闻LSTM + CRF的效果强大,最近在看Pytorch官网文档的时候,看到了这段代码,前前后后查了很多资料,终于把代码弄懂了.我希望在后来人看这段代码的时候,直接就看我的博客就能完全弄懂这段代码. ...

  2. dann的alpha torch_pytorch BiLSTM+CRF代码详解

    一. BILSTM + CRF介绍 1.介绍 基于神经网络的方法,在命名实体识别任务中非常流行和普遍. 如果你不知道Bi-LSTM和CRF是什么,你只需要记住他们分别是命名实体识别模型中的两个层. 1 ...

  3. 【Gans入门】Pytorch实现Gans代码详解【70+代码】

    简述 由于科技论文老师要求阅读Gans论文并在网上找到类似的代码来学习. 文章目录 简述 代码来源 代码含义概览 代码分段解释 导入包: 设置参数: 给出标准数据: 构建模型: 构建优化器 迭代细节 ...

  4. pytorch中resnet_ResNet代码详解

    代码学习第一天! fighting! import torch.nn as nn import math import torch.utils.model_zoo as model_zoo# 这个文件 ...

  5. pytorch lstm crf 代码理解

    好久没有写博客了,这一次就将最近看的pytorch 教程中的lstm+crf的一些心得与困惑记录下来. 原文 PyTorch Tutorials 参考了很多其他大神的博客,https://blog.c ...

  6. PyTorch 迁移学习 (Transfer Learning) 代码详解

    PyTorch 迁移学习 代码详解 概述 为什么使用迁移学习 更好的结果 节省时间 加载模型 ResNet152 冻层实现 模型初始化 获取需更新参数 训练模型 获取数据 完整代码 概述 迁移学习 ( ...

  7. BilSTM 实体识别_NLP-入门实体命名识别(NER)+Bilstm-CRF模型原理Pytorch代码详解——最全攻略

    最近在系统地接触学习NER,但是发现这方面的小帖子还比较零散.所以我把学习的记录放出来给大家作参考,其中汇聚了很多其他博主的知识,在本文中也放出了他们的原链.希望能够以这篇文章为载体,帮助其他跟我一样 ...

  8. BilSTM 实体识别_NLP入门实体命名识别(NER)+BilstmCRF模型原理Pytorch代码详解——最全攻略...

    来自 | 知乎   作者 | seven链接 | https://zhuanlan.zhihu.com/p/79552594编辑 | 机器学习算法与自然语言处理公众号本文仅作学术分享,如有侵权,请联系 ...

  9. Pytorch | yolov3原理及代码详解(一)

    YOLO相关原理 : https://blog.csdn.net/leviopku/article/details/82660381 https://www.jianshu.com/p/d13ae10 ...

最新文章

  1. python编程有什么用处-python中的生成器是什么?生成器有什么用处?
  2. final cut pro的vlog模版的需要region of interests和导出视频注意事项
  3. 八.linux系统文件属性知识
  4. pycharm 安装 tensorflow
  5. ansible-plabybook 常用的有用的命令
  6. 《深入浅出DPDK》读书笔记(九):流分类与多队列、流过滤、虚拟化流分类方式、流分类技术的使用
  7. 箭头函数和普通函数有什么区别?
  8. 【实例解析】某集团BI决策系统建设方案分享
  9. sql server 比较大小
  10. 【Luogu1182】数列分段Section II(二分)
  11. 编程之美 4.6桶中取黑白球
  12. Matlab R2014a安装教程
  13. 仿美团酒店预订的html模板,jQuery手机端仿美团酒店预订日期选择插件
  14. 单片机原理及接口技术(C51编程)(第2版)-习题答案
  15. 中英文1:2等宽字体
  16. Android15_ListView分页
  17. 双系统安装 Ubuntu 18.04 以及删除双系统中的 Ubuntu 的方法
  18. python程序弹出输入框_Python弹出输入框并获取输入值的实例
  19. 面向对象的设计原则(SOLID)
  20. java数据类型最大值_Java 基本数据类型最大值极限和最小值极限

热门文章

  1. nginx配置http、https访问,nginx指定ssl证书,阿里云腾讯云华为云设置nginx https安全访问
  2. ECS(Linux)连接RDS,使用命令行方式连接实例
  3. 伍六七带你学算法 入门篇-最长回文串
  4. Pytorch 多 GPU 并行处理机制
  5. Harmony生命周期
  6. BEP 7:CUDA外部内存管理插件(上)
  7. 如何在CPU上优化GEMM(下)
  8. 将HLSL射线追踪到Vulkan
  9. 视频系列:RTX实时射线追踪(下)
  10. Found option without preceding group in config file E:\mysql\mysql-5.7.23-winx64\my.ini at line 1!