序列标注

GitHub

序列标注是指给定一个输入序列,对序列中的每个Token进行标注的过程。序列标注问题通常用于从文本中提取信息,包括分词 (Word Segmentation)、词性标注 (POS Tagging)、命名实体识别 (NER)、组块分析 (Chunking) 等。

以组块分析 (Chunking) 任务为例:

文本分块包括将文本划分为句法相关的词的一部分。例如,句子:He reckons the current account deficit will narrow to only # 1.8 billion in September . 可以被分块为:

[NP He ] [VP reckons ] [NP the current account deficit ] [VP will narrow ] [PP to ] [NP only # 1.8 billion ] [PP in ] [NP September ]. (NP:名词短语;VP:动词短语;PP:介词短语)

该任务的目标是提出机器学习方法,在训练阶段后可以尽可能好地识别测试数据的块分割。

CoNLL2000Chunking:

CoNLL2000Chunking 数据集中的分块标签基于分块任务常用的 IOB(Inside, Outside, Beginning) 标签方案。句子中的每个单词都标有一个块标签,该标签指示该单词是否是块的一部分,以及它是开头部分、中间部分还是块外部分。

提示

CoNLL2000Chunking 数据集包括一组预定义的块类型,例如名词短语 (NP)、动词短语 (VP) 和介词短语 (PP)。数据集中的块标签由块类型和IOB标签组合而成,内部词使用“I-TYPE”格式,起始词使用“B-TYPE”格式,外部词使用“O”格式。例如,块标签“B-NP”表示名词短语的开头,而块标签“I-VP”表示动词短语的内部词。

示例:

Sentence

They

refuse

to

permit

us

to

enter

.

Chunk Tag

B-NP

B-VP

B-PP

B-VP

B-NP

B-PP

B-VP

O

下面是使用CoNLL2000Chunking数据集的块标签 (chunk tag) 和Bi-LSTM+CRF模型进行组块分析 (Chunking) 任务训练的例子:

定义模型

首先继承 mindnlp.abc 中的 Seq2vecModel 定义模型的 Head,然后使用 mindnlp.modules 中的 CRF 完成 BiLSTM_CRF 模型的定义。

import math
from mindspore import nn
from mindspore.common.initializer import Uniform, HeUniform
from mindnlp.abc import Seq2vecModel
from mindnlp.modules import CRF

class Head(nn.Cell):
    """ Head for BiLSTM-CRF model """
    def __init__(self, hidden_dim, num_tags):
        super().__init__()
        weight_init = HeUniform(math.sqrt(5))
        bias_init = Uniform(1 / math.sqrt(hidden_dim * 2))
        self.hidden2tag = nn.Dense(hidden_dim, num_tags,
                                   weight_init=weight_init, bias_init=bias_init)

    def construct(self, context):
        return self.hidden2tag(context)

class BiLSTM_CRF(Seq2vecModel):
    """ BiLSTM-CRF model """
    def __init__(self, encoder, head, num_tags):
        super().__init__(encoder, head)
        self.encoder = encoder
        self.head = head
        self.crf = CRF(num_tags, batch_first=True)

    def construct(self, text, seq_length, label=None):
        output,_,_ = self.encoder(text)
        feats = self.head(output)
        res = self.crf(feats, label, seq_length)
        return res

定义超参数

以下是模型训练过程中需要的一些超参数。

embedding_dim = 16
hidden_dim = 32

数据预处理

通过调用 mindnlp.dataset 中 dataset 的接口下载并预处理数据集。

加载数据集:

from mindnlp.dataset import CoNLL2000Chunking

dataset_train,dataset_test = CoNLL2000Chunking()

初始化词表以进行预处理:

from mindspore.dataset import text

vocab = text.Vocab.from_dataset(dataset_train,columns=["words"],freq_range=None,top_k=None,
                                   special_tokens=["<pad>","<unk>"],special_first=True)

处理数据集:

from mindnlp.dataset import CoNLL2000Chunking_Process

dataset_train = CoNLL2000Chunking_Process(dataset=dataset_train, vocab=vocab,
                                          batch_size=32, max_len=80)

实例化模型

from mindnlp.modules import RNNEncoder

embedding = nn.Embedding(vocab_size=len(vocab.vocab()), embedding_size=embedding_dim,
                         padding_idx=vocab.tokens_to_ids("<pad>"))
lstm_layer = nn.LSTM(embedding_dim, hidden_dim // 2, bidirectional=True, batch_first=True)
encoder = RNNEncoder(embedding, lstm_layer)
head = Head(hidden_dim, 23)
net = BiLSTM_CRF(encoder, head, 23)

定义优化器

from mindspore import ops

optimizer = nn.SGD(net.trainable_params(), learning_rate=0.01, weight_decay=1e-4)
grad_fn = ops.value_and_grad(net, None, optimizer.parameters)

定义训练步骤

def train_step(data, seq_length, label):
    """ train step """
    loss, grads = grad_fn(data, seq_length, label)
    loss = ops.depend(loss, optimizer(grads))
    return loss

训练过程

现在我们已经完成了所有的准备工作,可以开始训练模型了。

from tqdm import tqdm

size = dataset_train.get_dataset_size()
steps = size
with tqdm(total=steps) as t:
    for batch, (data, seq_length, label) in enumerate(dataset_train.create_tuple_iterator()):
        loss = train_step(data, seq_length ,label)
        t.set_postfix(loss=loss)
        t.update(1)