一、背景知识

1.1 什么是分词?

  NLP的基础任务分为三个部分,词法分析、句法分析和语义分析,其中词法分析中有一种方法叫Tokenization,对汉字以字为单位进行处理叫做分词。

  Example :  我  去  北  京

       S       S       B       E

  注:S代表一个单独词,B代表一个词的开始,E表示一个词的结束(北京是一个词)。

1.2 什么是词性标注?

  句法分析中有一种方法叫词性标注(pos tagging),词性标注的目标是使用类似PN、VB等的标签对句子(一连串的词或短语)进行打签。

  Example :           I   can  open  this   can  .

  Pos tagging  -> PN  MD   VV   PN   NN  PU

  注:PN代词 MD情态动词 VV 动词 NN名词 PU标点符号

1.3 什么是分词-词性标注?

  分词-词性标注就是将分词和词性标注两个任务同时进行,在一个模型里完成,可以减少错误传播。

  Example :   我    去    北    京

       S-PN      S-VV        B-NN        E-NN

  注:如果想理解更多关于nlp基础任务的知识,可参看我整理的张岳老师暑期班的第一天的笔记。

1.4 什么是CRF?

  条件随机场(conditional random field)是一种用来标记和切分序列化数据的统计模型。在NLP领域可以用来做序列标注任务。

  注:更多关于条件随机场的理论知识,可以参考以下内容:

  条件随机场综述  

  如何轻松愉快地理解条件随机场(CRF)  

  条件随机场介绍(译)Introduction to Conditional Random Fields

  CRF条件随机场简介

二、CRF序列标注

2.1 模型结构图

最底下的词向量层,上两层是Bi-LSTM层,最上面一层是CRF层。数据流程是从下层向上层计算。

2.2 CRF部分

2.2.1 理论

Point 1: 在CRF中,每个特征函数以下列信息作为输入,输出是一个实数值。

(1)一个句子s

(2)词在句子中的位置i

(3)当前词的标签

(4)前一个词的标签

注:通过限制特征只依赖于当前与之前词的标签,而不是句子中的任意标签,实际上是建立了一种特殊的线性CRF,而不是广义上的CRF。

Point 2: CRF的训练参数

(1)Input: x = {我,去,北京}

(2)Answer: ygold = {PN,  VV,  NN}

(3)y'是CRF标注的所有可能值,有3*3*3=27个;

(4)T矩阵存储转移分数,T[yiyi-1]是上个标签是的情况下,下个标签是yi的分数;

(5)hi是向量序列,通过神经网络Bi-LSTM得到,hi[yi]是被标成的发射分数;

(6)score(x,y)是模型对x被标注成y所打出的分数,是一个实数值;

       

  Example : 我  去  北京

     PN     VV     NN

   

(7)P(ygold|x)是模型x对标注出ygold的概率;

      

Point 3: CRF的训练目标:训练模型使得变大

Step 1: 对P(ygold|x)进行转化,取对数

       

      

Step 2: 最终目标函数,使用梯度下降法

       

      

Step 3: 编程实现

       

     def _forward_alg(self, feats):
# do the forward algorithm to compute the partition function
init_alphas = torch.Tensor(1, self.labelSize).fill_(0)
# Wrap in a variable so that we will get automatic backprop
forward_var = autograd.Variable(init_alphas) # Iterate through the sentence
for idx in range(len(feats)):
feat = feats[idx]
alphas_t = [] # The forward variables at this timestep
for next_tag in range(self.labelSize):
# broadcast the emission score: it is the same regardless of the previous tag
if idx == 0:
alphas_t.append(feat[next_tag].view(1, -1))
else:
emit_score = feat[next_tag].view(1, -1).expand(1, self.labelSize)
# the ith entry of trans_score is the score of transitioning to next_tag from i
trans_score = self.T[next_tag]
# 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(self.log_sum_exp(next_tag_var))
forward_var = torch.cat(alphas_t).view(1, -1)
alpha_score = self.log_sum_exp(forward_var)
return alpha_score
   # Compute log sum exp in a numerically stable way for the forward algorithm
def log_sum_exp(self, vec):
max_score = vec[0, self.argmax(vec)]
max_score_broadcast = max_score.view(1, -1).expand(1, vec.size()[1])
return max_score + torch.log(torch.sum(torch.exp(vec - max_score_broadcast)))

     

     def neg_log_likelihood(self, feats, tags):
forward_score = self._forward_alg(feats) # calculate denominator
gold_score = self._score_sentence(feats, tags)
return forward_score - gold_score # calculate loss

train()中的训练部分:

         for iter in range(self.hyperParams.maxIter):
print('###Iteration' + str(iter) + "###")
random.shuffle(indexes)
for idx in range(len(trainExamples)):
# Step 1. Remember that Pytorch accumulates gradients. We need to clear them out before each instance
self.model.zero_grad()
# Step 2. Get our inputs ready for the network, that is, turn them into Variables of word indices.
self.model.LSTMHidden = self.model.init_hidden()
exam = trainExamples[indexes[idx]]
# Step 3. Run our forward pass. Compute the loss, gradients, and update the parameters by calling optimizer.step()
lstm_feats = self.model(exam.feat)
loss = self.model.crf.neg_log_likelihood(lstm_feats, exam.labelIndexs)
loss.backward()
optimizer.step()
if (idx + 1) % self.hyperParams.verboseIter == 0:
print('current: ', idx + 1, ", cost:", loss.data[0])

Point 4: 使用模型预测序列

使用维特比解码算法,解决篱笆图中的最短路径问题

 

step 1:  初始节点没有转移值

                 if idx == 0:
viterbi_var.append(feat[next_tag].view(1, -1))

step 2: 节点值由三部分组成,最后求取最大值,得到lastbestlabel的下标

             for next_tag in range(self.labelSize):
if idx == 0:
viterbi_var.append(feat[next_tag].view(1, -1))
else:
emit_score = feat[next_tag].view(1, -1).expand(1, self.labelSize)
trans_score = self.T[next_tag]
next_tag_var = forward_var + trans_score + emit_score
best_label_id = self.argmax(next_tag_var)
bptrs_t.append(best_label_id)
viterbi_var.append(next_tag_var[0][best_label_id])

step 3: 计算出所有节点,比较最后一个词的值,求取最大值之后,向前推出最佳序列。

维特比解码算法实现预测序列

     def _viterbi_decode(self, feats):
init_score = torch.Tensor(1, self.labelSize).fill_(0)
# forward_var at step i holds the viterbi variables for step i-1
forward_var = autograd.Variable(init_score)
back = []
for idx in range(len(feats)):
feat = feats[idx]
bptrs_t = [] # holds the backpointers for this step
viterbi_var = [] # holds the viterbi variables for this step
for next_tag in range(self.labelSize):
# 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)
if idx == 0:
viterbi_var.append(feat[next_tag].view(1, -1))
else:
emit_score = feat[next_tag].view(1, -1).expand(1, self.labelSize)
trans_score = self.T[next_tag]
next_tag_var = forward_var + trans_score + emit_score
best_label_id = self.argmax(next_tag_var)
bptrs_t.append(best_label_id)
viterbi_var.append(next_tag_var[0][best_label_id])
# Now add in the emission scores, and assign forward_var to the set of viterbi variables we just computed
forward_var = (torch.cat(viterbi_var)).view(1, -1)
if idx > 0:
back.append(bptrs_t)
best_label_id = self.argmax(forward_var)
# Follow the back pointers to decode the best path.
best_path = [best_label_id]
path_score = forward_var[0][best_label_id]
for bptrs_t in reversed(back):
best_label_id = bptrs_t[best_label_id]
best_path.append(best_label_id)
best_path.reverse()
return path_score, best_path

train()函数中的预测部分

        # Check predictions after training
eval_dev = Eval()
for idx in range(len(devExamples)):
predictLabels = self.predict(devExamples[idx])
devInsts[idx].evalPRF(predictLabels, eval_dev)
print('Dev: ', end="")
eval_dev.getFscore() eval_test = Eval()
for idx in range(len(testExamples)):
predictLabels = self.predict(testExamples[idx])
testInsts[idx].evalPRF(predictLabels, eval_test)
print('Test: ', end="")
eval_test.getFscore()
     def predict(self, exam):
tag_hiddens = self.model(exam.feat)
_, best_path = self.model.crf._viterbi_decode(tag_hiddens)
predictLabels = []
for idx in range(len(best_path)):
predictLabels.append(self.hyperParams.labelAlpha.from_id(best_path[idx]))
return predictLabels

Point 5 : 使用F1分数测量精度,最佳值为1,最差为0

        

     def getFscore(self):
if self.predict_num == 0:
self.precision = 0
else:
self.precision = self.correct_num / self.predict_num if self.gold_num == 0:
self.recall = 0
else:
self.recall = self.correct_num / self.gold_num if self.precision + self.recall == 0:
self.fscore = 0
else:
self.fscore = 2 * (self.precision * self.recall) / (self.precision + self.recall)
print("precision: ", self.precision, ", recall: ", self.recall, ", fscore: ", self.fscore)

注:全部代码和注释链接

扩展:可将数据中第二列和第一列一起放入Bi-LSTM中提取特征,这次只用到数据的第一列和第三列。

第四期coding_group笔记_用CRF实现分词-词性标注的更多相关文章

  1. 老男孩Python全栈第2期+课件笔记【高清完整92天整套视频教程】

    点击了解更多Python课程>>> 老男孩Python全栈第2期+课件笔记[高清完整92天整套视频教程] 课程目录 ├─day01-python 全栈开发-基础篇 │ 01 pyth ...

  2. 《Linux内核设计与实现》课本第四章自学笔记——20135203齐岳

    <Linux内核设计与实现>课本第四章自学笔记 进程调度 By20135203齐岳 4.1 多任务 多任务操作系统就是能同时并发的交互执行多个进程的操作系统.多任务操作系统使多个进程处于堵 ...

  3. 读经典——《CLR via C#》(Jeffrey Richter著) 笔记_发布者策略控制

    在 读经典——<CLR via C#>(Jeffrey Richter著) 笔记_高级管理控制(配置)中,是由程序集的发布者将程序集的一个新版本发送给管理员,后者安装程序集,并手动编辑应用 ...

  4. 老男孩python3.5全栈开发第9期+课件笔记(1-15部全 共125天完整无加密)

    点击了解更多Python课程>>> 老男孩python3.5全栈开发第9期+课件笔记(1-15部全 共125天完整无加密)大小:236G 此课程为老男孩全栈开发最新完结课程,适合零基 ...

  5. React笔记_(3)_react语法2

    React笔记_(3)_react语法2 state和refs props就是在render渲染时,向组件内传递的变量,这个传递是单向的,只能继承下来读取. 如何进行双向传递呢? state (状态机 ...

  6. [置顶] CSDN博客第四期移动开发最佳博主评选

    CSDN博客第三期最佳移动开发博主评选圆满结束,恭喜所有上榜用户,为继续展示移动开发方向优秀博主,发掘潜力新星,为移动开发方向的博客用户提供平台,CSDN博客第四期移动开发最佳博主评选开始.同时,获奖 ...

  7. 计算机爱好者协会技术贴markdown第四期

    首先先让爱酱用CSDN自带的数学公式方法来闪瞎大家的钛合金狗眼: 有没有感觉到Markdown的强大!!!!! ## KaTeX数学公式 您可以使用渲染LaTeX数学表达式 [KaTeX](https ...

  8. 《Linux内核设计与实现》第四章学习笔记

    <Linux内核设计与实现>第四章学习笔记           ——进程调度 姓名:王玮怡  学号:20135116 一.多任务 1.多任务操作系统的含义 多任务操作系统就是能同时并发地交 ...

  9. Linux内核分析第四章 读书笔记

    Linux内核分析第四章 读书笔记 第一部分--进程调度 进程调度:操作系统规定下的进程选取模式 面临问题:多任务选择问题 多任务操作系统就是能同时并发地交互执行多个进程的操作系统,在单处理器机器上这 ...

随机推荐

  1. LeetCode(201) Bitwise AND of Numbers Range

    题目 Given a range [m, n] where 0 <= m <= n <= 2147483647, return the bitwise AND of all numb ...

  2. LeetCode(128) Longest Consecutive Sequence

    题目 Given an unsorted array of integers, find the length of the longest consecutive elements sequence ...

  3. python format 用法详解

    format 用法详解 不需要理会数据类型的问题,在%方法中%s只能替代字符串类型 单个参数可以多次输出,参数顺序可以不相同 填充方式十分灵活,对齐方式十分强大 官方推荐用的方式,%方式将会在后面的版 ...

  4. webdriver高级应用- 使用Chrome浏览器自动将文件下载到指定路径

    #encoding=utf-8 from selenium import webdriver import unittest, time class TestDemo(unittest.TestCas ...

  5. ICCID

     ICCID:Integrate circuit card identity 集成电路卡识别码(固化在手机SIM卡中) ICCID为IC卡的唯一识别号码,共有20位数字+英文组成,其编码格式为:XXX ...

  6. 九度oj 题目1411:转圈

    题目描述: 在一个有向图有n个顶点(编号从1到n),给一个起点s,问从起点出发,至少经过一条边,回到起点的最短距离. 输入: 输入包括多组,每组输入第一行包括三个整数n,m,s(1<=n< ...

  7. 集合篇 —— Collection(1):JDK 中的重复实现问题

    1.      问题的提出 在 Java 的集合体系当中,无论是 List(列表)还是 Set(集),在设计的时候都存在一个很奇怪的现象:这两种集合的接口,Java 都为其设计了抽象类 Abstrac ...

  8. Json操作(汇总)

    利用:com.fasterxml.jackson 原文地址:https://blog.csdn.net/joyous/article/details/9448461 说明:Map转化为Json:创建J ...

  9. jquery 选择器加变量

    var $role_id = btn.parent().prev().prev().attr('id') var $department_id = btn.parent().prev().prev() ...

  10. 区间求mex的几种方法

    Tags : 总结 莫队 线段树 区间取mex的几种方法 题目大意 无修改,求区间 \(mex\) 做法1 莫队+二分+树状数组 树状数组维护维护桶,每次扫完二分答案,用树状数组判断 \(O(n\sq ...