BiLSTM模型中CRF层的运行原理-1

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了BiLSTM模型中CRF层的运行原理-1相关的知识,希望对你有一定的参考价值。

参考技术A 本文框架如下:

介绍——在命名实体识别任务中,BiLSTM模型中CRF层的通用思想

详细的实例——通过实例来一步步展示CRF的工作原理

实现——CRF层的一步步实现过程

谁可以读本文——本文适用与NLP初级入门者或者AI其他相关领域

需要有的基础知识:你只需要知道什么是命名实体识别,如果你不懂神经网络,条件随机场(CRF)或者其它相关知识,不必担心,本文将向你展示CRF层是如何工作的。本文将尽可能的讲的通俗易懂。

1.介绍

        基于神经网络的方法,在命名实体识别任务中非常流行和普遍。在文献【1】中,作者提出了Bi-LSTM模型用于实体识别任务中,在模型中用到了字嵌入和词嵌入。本文将向你展示CRF层是如何工作的。

        如果你不知道Bi-LSTM和CRF是什么,你只需要记住他们分别是命名实体识别模型中的两个层。

1.1开始之前

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

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

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

1.2BiLSTM-CRF模型

        以下将给出模型的结构:

        第一,句子x中的每一个单元都代表着由字嵌入或词嵌入构成的向量。其中,字嵌入是随机初始化的,词嵌入是通过数据训练得到的。所有的嵌入在训练过程中都会调整到最优。

        第二,这些字或词嵌入为BiLSTM-CRF模型的输入,输出的是句子x中每个单元的标签。

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

        如上图所示,BiLSTM层的输出为每一个标签的预测分值,例如,对于单元w0,BiLSTM层输出的是1.5 (B-Person), 0.9 (I-Person), 0.1 (B-Organization), 0.08 (I-Organization) and 0.05 (O). 这些分值将作为CRF的输入。

1.3 如果没有CRF层会怎样

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

        由于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.4CRF层能从训练数据中获得约束性的规则

        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”。

有了这些约束,标签序列预测中非法序列出现的概率将会大大降低。

参考文献: 【1】

基于BiLSTM+CRF的信息抽取模型

BiLSTM+CRF
import torch
import torch.nn as nn
from modelgraph.BILSTM import BiLSTM
from itertools import zip_longest
class BiLSTM_CRF(nn.Module):

def __init__(self, vocab_size, emb_size, hidden_size, out_size):
    super(BiLSTM_CRF, self).__init__()
    self.bilstm = BiLSTM(vocab_size, emb_size, hidden_size, out_size)
    self.transition = nn.Parameter(torch.ones(out_size, out_size) * 1 / out_size)
def forward(self, sents_tensor, lengths):
    emission = self.bilstm(sents_tensor, lengths)
    batch_size, max_len, out_size = emission.size()
    crf_scores = emission.unsqueeze(2).expand(-1, -1, out_size, -1) + self.transition.unsqueeze(0)
    return crf_scores
def test(self, test_sents_tensor, lengths, tag2id):
    start_id = tag2id[\'<start>\']
    end_id = tag2id[\'<end>\']
    pad = tag2id[\'<pad>\']
    tagset_size = len(tag2id)
    crf_scores =self.forward(test_sents_tensor, lengths)
    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    B , L , T, _ =crf_scores.size()
    viterbi = torch.zeros(B, L, T).to(device)
    backpointer = (torch.zeros(B, L, T).long() * end_id).to(device)
    lengths = torch.LongTensor(lengths).to(device)
    for step in range(L):
        batch_size_t =(lengths > step).sum().item()
        if step == 0:
            viterbi[:batch_size_t, step, :] = crf_scores[: batch_size_t, step, start_id, :]
            backpointer[:batch_size_t, step, :] = start_id
        else:
            max_scores, prev_tags = torch.max(viterbi[:batch_size_t, step-1, :].unsqueeze(2) + crf_scores[:batch_size_t, step, :, :], dim=1)
            viterbi[:batch_size_t, step, :] = max_scores
            backpointer[:batch_size_t, step, :] = prev_tags
    backpointer = backpointer.view(B, -1)
    tagids = []
    tags_t = None
    for step in range(L-1, 0, -1):
        batch_size_t = (lengths > step).sum().item()
        if step == L-1:
            index = torch.ones(batch_size_t).long() * (step * tagset_size)
            index = index.to(device)
            index += end_id
        else:
            prev_batch_size_t = len(tags_t)
            new_in_batch = torch.LongTensor([end_id] * (batch_size_t - prev_batch_size_t)).to(device)
            offset = torch.cat([tags_t, new_in_batch], dim=0)
            index = torch.ones(batch_size_t).long() * (step *tagset_size)
            index = index.to(device)
            index += offset.long()
        try:
            tags_t = backpointer[:batch_size_t].gather(dim=1, index=index.unsqueeze(1).long())
        except RuntimeError:
            import pdb
            pdb.set_trace()
        tags_t = tags_t.squeeze(1)
        tagids.append(tags_t.tolist())
    tagids = list(zip_longest(*reversed(tagids), fillvalue=pad))
    tagids = torch.Tensor(tagids).long()
    return tagids

def cal_lstm_crf_loss(crf_scores, targets, tag2id):

pad_id = tag2id.get(\'<pad>\')
start_id = tag2id.get(\'<start>\')
end_id = tag2id.get(\'<end>\')
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
batch_size, [货币代码](https://www.gendan5.com/currencycode.html)max_len = targets.size()
target_size = len(tag2id)
mask = (targets != pad_id)
lengths = mask.sum(dim=1)
targets = indexed(targets, target_size, start_id)
targets = targets.masked_select(mask)
flatten_scores = crf_scores.masked_select(
    mask.view(batch_size, max_len, 1, 1).expand_as(crf_scores)
).view(-1, target_size*target_size).contiguous()
golden_scores = flatten_scores.gather(
    dim=1, index=targets.unsqueeze(1)).sum()
scores_upto_t = torch.zeros(batch_size, target_size).to(device)
for t in range(max_len):
    batch_size_t = (lengths > t).sum().item()
    if t == 0:
        scores_upto_t[:batch_size_t] = crf_scores[:batch_size_t,
                                       t, start_id, :]
    else:
        scores_upto_t[:batch_size_t] = torch.logsumexp(
            crf_scores[:batch_size_t, t, :, :] +
            scores_upto_t[:batch_size_t].unsqueeze(2),
            dim=1
        )
all_path_scores = scores_upto_t[:, end_id].sum()
loss = (all_path_scores - golden_scores) / batch_size
return loss

def indexed(targets, tagset_size, start_id):

batch_size, max_len = targets.size()
for col in range(max_len-1, 0, -1):
    targets[:, col] += (targets[:, col-1] * tagset_size)
targets[:, 0] += (start_id * tagset_size)
return targets

以上是关于BiLSTM模型中CRF层的运行原理-1的主要内容,如果未能解决你的问题,请参考以下文章

序列标注(BiLSTM-CRF/Lattice LSTM)

Bert加bilstm和crf做ner的意义

基于BiLSTM-CNN-CRF的中文分词(一)

基于BiLSTM+CRF的信息抽取模型

NLP作业三:用BiLSTM+CRF实现中文命名实体识别(TensorFlow入门)代码+报告

文本数据挖掘中文命名实体识别:HMM模型+BiLSTM_CRF模型(Pytorch)调研与实验分析