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


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

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

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

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

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

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

    注意一点双向LSTM的输出O OO是[Oleft O_{left}O
    left

    ,Oright O_{right}O
    right

    ],即size为(2, 隐藏单元数)

    CRF
    CRF是判别模型, 判别公式如下y yy是标记序列,x xx是单词序列,即已知单词序列,求最有可能的标记序列
    P(y∣x)=exp(Score(x,y))∑y′exp(Score(x,y′)) P(y|x) = frac{exp{( ext{Score}(x, y)})}{sum_{y'} exp{( ext{Score}(x, y')})}
    P(y∣x)=

    y



    exp(Score(x,y

    ))
    exp(Score(x,y))

    Score(x,y) Score(x,y)Score(x,y)即单词序列x xx产生标记序列y yy的得分,得分越高,说明其产生的概率越大。

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

    一个是状态特征函数
    Score(x,y)=∑ilogψEMIT(yi→xi)+logψTRANS(yi−1→yi) {Score}(x,y) = sum_i log psi_ ext{EMIT}(y_i ightarrow x_i) + log psi_ ext{TRANS}(y_{i-1} ightarrow y_i)
    Score(x,y)=
    i


    logψ
    EMIT

    (y
    i

    →x
    i

    )+logψ
    TRANS

    (y
    i−1

    →y
    i

    )

    代码中用到了前向算法和维特比算法,在代码中我会具体解释

    log_sum_exp函数就是计算log∑ni=1exi logsum^n_{i=1}{e^{x_{i}}}log∑
    i=1
    n

    e
    x
    i


    ,前向算法需要用到这个函数

    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{( ext{Score}(x, y')})}∑
    y



    exp(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是logZ(x) logZ(x)logZ(x),即log∑y′exp(Score(x,y′)) log{sum_{y'} exp{( ext{Score}(x, y')})}log∑
    y



    exp(Score(x,y

    )),

    gold_score是logexp(Score(x,y′) logexp{( ext{Score}(x, y')}logexp(Score(x,y

    )

    我们的目标是极大化
    P(y∣x)=exp(Score(x,y))∑y′exp(Score(x,y′)) P(y|x) = frac{exp{( ext{Score}(x, y)})}{sum_{y'} exp{( ext{Score}(x, y')})}
    P(y∣x)=

    y



    exp(Score(x,y

    ))
    exp(Score(x,y))

    两边取对数即
    logP(y∣x)=log exp(Score(x,y))−log∑y′exp(Score(x,y′))logP(y∣x)=gold_score−forward_score logP(y|x) = log {exp{( ext{Score}(x, y)})}-log{sum_{y'} exp{( ext{Score}(x, y')})} \logP(y|x)=gold\_score-forward\_score
    logP(y∣x)=log exp(Score(x,y))−log
    y




    exp(Score(x,y

    ))
    logP(y∣x)=gold_score−forward_score

    所以我们需要极大化gold_score−forward_score gold\_score - forward\_scoregold_score−forward_score,也就是极小化forward_score−gold_score forward\_score -gold\_scoreforward_score−gold_score

    也就是为什么forward_score−gold_score forward\_score - gold\_scoreforward_score−gold_score可以作为loss的根本原因
    ---------------------
    作者:zycxnanwang
    来源:CSDN
    原文:https://blog.csdn.net/zycxnanwang/article/details/90385259
    版权声明:本文为博主原创文章,转载请附上博文链接!

  • 相关阅读:
    浏览器基本的工作原理
    ES6异步操作之Promise
    vux中x-input在安卓手机输入框的删除按钮(@on-click-clear-icon)点击没反应
    浏览器工作原理
    form表单的两种提交方式,submit和button的用法
    HTML DOM submit() 方法
    JavaScript test() 方法
    eval() 函数
    正则表达式
    onblur 事件
  • 原文地址:https://www.cnblogs.com/jfdwd/p/11184895.html
Copyright © 2020-2023  润新知