当前位置:   article > 正文

pytorch实现BiLSTM+CRF用于NER(命名实体识别)_pytorch命名实体识别代码

pytorch命名实体识别代码

pytorch实现BiLSTM+CRF用于NER(命名实体识别)

在写这篇博客之前,我看了网上关于pytorch,BiLstm+CRF的实现,都是一个版本(对pytorch教程的翻译),

翻译得一点质量都没有,还有一些竟然说做得是词性标注,B,I,O是词性标注的tag吗?真是误人子弟。所以

自己打算写一篇关于pytorch上实现命名实体识别的翻译,加入自己的理解。前面是一些牢骚话

BiLSTM

我上篇博客介绍了pytorch实现LSTM 链接,这里是BiLSTM,网络结构图如下

单向的LSTM,当前序列元素只能看见前面的元素,而无法看见后面的元素,双向LSTM克服了这个缺点,既能

看见前面的元素,也能看见后面的元素。学术一点的就是,单向LSTM无法编码从后往前的信息

注意一点双向LSTM的输出 O O O是[ O l e f t O_{left} Oleft, O r i g h t O_{right} Oright],即size为(2, 隐藏单元数)

CRF

CRF是判别模型, 判别公式如下 y y y是标记序列, x x x是单词序列,即已知单词序列,求最有可能的标记序列
P ( y ∣ x ) = exp ⁡ ( Score ( x , y ) ) ∑ y ′ exp ⁡ ( Score ( x , y ′ ) ) P(y|x) = \frac{\exp{(\text{Score}(x, y)})}{\sum_{y'} \exp{(\text{Score}(x, y')})} P(yx)=yexp(Score(x,y))exp(Score(x,y))
S c o r e ( x , y ) Score(x,y) Score(x,y)即单词序列 x x x产生标记序列 y y y的得分,得分越高,说明其产生的概率越大。

在pytorch教程中链接,其用于实体识别定义的 S c o r e ( x , y ) Score(x,y) Score(x,y)包含两个特征函数,一个是转移特征函数

一个是状态特征函数
S c o r e ( x , y ) = ∑ i log ⁡ ψ EMIT ( y i → x i ) + log ⁡ ψ TRANS ( y i − 1 → y i ) {Score}(x,y) = \sum_i \log \psi_\text{EMIT}(y_i \rightarrow x_i) + \log \psi_\text{TRANS}(y_{i-1} \rightarrow y_i) Score(x,y)=ilogψEMIT(yixi)+logψTRANS(yi1yi)
代码中用到了前向算法维特比算法,在代码中我会具体解释

log_sum_exp函数就是计算 l o g ∑ i = 1 n e x i log\sum^n_{i=1}{e^{x_{i}}} logi=1nexi,前向算法需要用到这个函数

def log_sum_exp(vec):
    max_score = vec[0, 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)))
  • 1
  • 2
  • 3
  • 4
  • 5
'
运行

前向算法,求出 α \alpha α,即 Z ( x ) Z(x) Z(x), 也就是 ∑ y ′ exp ⁡ ( Score ( x , y ′ ) ) {\sum_{y'} \exp{(\text{Score}(x, y')})} yexp(Score(x,y)),如果不懂可以看一下李航的书关于CRF的前向算法

但是不同于李航书的是,代码中 α \alpha α都取了对数,一个是为了运算方便,二个为了后面的最大似然估计。

这个代码里面没有进行优化,作者也指出来了,其实对feats的迭代完全没有必要用两次循环,其实矩阵相乘

就够了,作者是为了方便我们理解,所以细化了步骤

def _forward_alg(self, feats):

    init_alphas = torch.full((1, self.tagset_size), -10000.)
    
	#初始时,start位置为0,其他位置为-10000
    init_alphas[0][self.tag_to_ix[START_TAG]] = 0.
	
    #赋给变量方便后面反向传播
    forward_var = init_alphas

    for feat in feats:
        alphas_t = [] 
        for next_tag in range(self.tagset_size):
            #状态特征函数的得分
            emit_score = feat[next_tag].view(1, -1).expand(1, self.tagset_size)
            
			#状态转移函数的得分
            trans_score = self.transitions[next_tag].view(1, -1)
            
			#从上一个单词的每个状态转移到next_tag状态的得分
            #所以next_tag_var是一个大小为tag_size的数组
            next_tag_var = forward_var + trans_score + emit_score
            
            #对next_tag_var进行log_sum_exp操作
            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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
'
运行

维特比算法中规中矩,可以参考李航书上条件随机场的预测算法

def _viterbi_decode(self, feats):
    backpointers = []
	#初始化
    init_vvars = torch.full((1, self.tagset_size), -10000.)
    init_vvars[0][self.tag_to_ix[START_TAG]] = 0

    forward_var = init_vvars
    for feat in feats:
        #保持路径节点,用于重构最优路径
        bptrs_t = []  
        #保持路径变量概率
        viterbivars_t = []

        for next_tag in range(self.tagset_size):

            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))

        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_tag pop出来,最终的结果不需要
    start = best_path.pop()
    assert start == self.tag_to_ix[START_TAG]
    best_path.reverse()
    return path_score, best_path
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
'
运行

其实我最想讲的是这个函数

def neg_log_likelihood(self, sentence, tags):
    feats = self._get_lstm_features(sentence)
    forward_score = self._forward_alg(feats)
    gold_score = self._score_sentence(feats, tags)
    return forward_score - gold_score
  • 1
  • 2
  • 3
  • 4
  • 5
'
运行

我们知道forward_score是 l o g Z ( x ) logZ(x) logZ(x),即 l o g ∑ y ′ exp ⁡ ( Score ( x , y ′ ) ) log{\sum_{y'} \exp{(\text{Score}(x, y')})} logyexp(Score(x,y))

gold_score是 l o g exp ⁡ ( Score ( x , y ′ ) log\exp{(\text{Score}(x, y')} logexp(Score(x,y)

我们的目标是极大化
P ( y ∣ x ) = exp ⁡ ( Score ( x , y ) ) ∑ y ′ exp ⁡ ( Score ( x , y ′ ) ) P(y|x) = \frac{\exp{(\text{Score}(x, y)})}{\sum_{y'} \exp{(\text{Score}(x, y')})} P(yx)=yexp(Score(x,y))exp(Score(x,y))
两边取对数即
l o g P ( y ∣ x ) = l o g   exp ⁡ ( Score ( x , y ) ) − l o g ∑ y ′ exp ⁡ ( Score ( x , y ′ ) ) l o g P ( y ∣ x ) = g o l d _ s c o r e − f o r w a r d _ s c o r e logP(y|x) = log\ {\exp{(\text{Score}(x, y)})}-log{\sum_{y'} \exp{(\text{Score}(x, y')})} \\logP(y|x)=gold\_score-forward\_score logP(yx)=log exp(Score(x,y))logyexp(Score(x,y))logP(yx)=gold_scoreforward_score
所以我们需要极大化 g o l d _ s c o r e − f o r w a r d _ s c o r e gold\_score - forward\_score gold_scoreforward_score,也就是极小化 f o r w a r d _ s c o r e − g o l d _ s c o r e forward\_score -gold\_score forward_scoregold_score

也就是为什么 f o r w a r d _ s c o r e − g o l d _ s c o r e forward\_score - gold\_score forward_scoregold_score可以作为loss的根本原因

完整代码

pytorch高级教程BiLSTM+CRF实现命名实体识别

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/872502
推荐阅读
相关标签
  

闽ICP备14008679号