序列标注

这一部分的内容主要展示如何使用fastNLP实现序列标注(Sequence labeling)任务。您可以使用fastNLP的各个组件快捷,方便地完成序列标注任务,达到出色的效果。 在阅读这篇教程前,希望您已经熟悉了fastNLP的基础使用,尤其是数据的载入以及模型的构建。通过这个小任务,能让您进一步熟悉fastNLP的使用。

注解

本教程推荐使用 GPU 进行实验

命名实体识别(name entity recognition, NER)

命名实体识别任务是从文本中抽取出具有特殊意义或者指代性非常强的实体,通常包括人名、地名、机构名和时间等。 如下面的例子中

我来自复旦大学。

其中“复旦大学”就是一个机构名,命名实体识别就是要从中识别出“复旦大学”这四个字是一个整体,且属于机构名这个类别。这个问题在实际做的时候会被 转换为序列标注问题

针对"我来自复旦大学"这句话,我们的预测目标将是[O, O, O, B-ORG, I-ORG, I-ORG, I-ORG],其中O表示out,即不是一个实体,B-ORG是ORG( organization的缩写)这个类别的开头(Begin),I-ORG是ORG类别的中间(Inside)。

在本tutorial中我们将通过fastNLP尝试写出一个能够执行以上任务的模型。

载入数据

fastNLP的数据载入主要是由Loader与Pipe两个基类衔接完成的,您可以通过 使用Loader和Pipe处理数据 了解如何使用fastNLP提供的数据加载函数。下面我们以微博命名实体任务来演示一下在fastNLP进行序列标注任务。

from fastNLP.io import WeiboNERPipe
data_bundle = WeiboNERPipe().process_from_file()
print(data_bundle.get_dataset('train')[:2])

打印的数据如下

+-------------------------------------------------+------------------------------------------+------------------------------------------+---------+
|                    raw_chars                    |                  target                  |                  chars                   | seq_len |
+-------------------------------------------------+------------------------------------------+------------------------------------------+---------+
| ['一', '节', '课', '的', '时', '间', '真', '... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, ... | [8, 211, 775, 3, 49, 245, 89, 26, 101... |    16   |
| ['回', '复', '支', '持', ',', '赞', '成', '... | [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ... | [116, 480, 127, 109, 2, 446, 134, 2, ... |    59   |
+-------------------------------------------------+------------------------------------------+------------------------------------------+---------+

模型构建

首先选择需要使用的Embedding类型。关于Embedding的相关说明可以参见 使用Embedding模块将文本转成向量 。 在这里我们使用通过word2vec预训练的中文汉字embedding。

from fastNLP.embeddings import StaticEmbedding

embed = StaticEmbedding(vocab=data_bundle.get_vocab('chars'), model_dir_or_name='cn-char-fastnlp-100d')

选择好Embedding之后,我们可以使用fastNLP中自带的 fastNLP.models.BiLSTMCRF 作为模型。

from fastNLP.models import BiLSTMCRF

data_bundle.rename_field('chars', 'words')  # 这是由于BiLSTMCRF模型的forward函数接受的words,而不是chars,所以需要把这一列重新命名
model = BiLSTMCRF(embed=embed, num_classes=len(data_bundle.get_vocab('target')), num_layers=1, hidden_size=200, dropout=0.5,
              target_vocab=data_bundle.get_vocab('target'))

进行训练

下面我们选择用来评估模型的metric,以及优化用到的优化函数。

from fastNLP import SpanFPreRecMetric
from torch.optim import Adam
from fastNLP import LossInForward

metric = SpanFPreRecMetric(tag_vocab=data_bundle.get_vocab('target'))
optimizer = Adam(model.parameters(), lr=1e-2)
loss = LossInForward()

使用Trainer进行训练, 您可以通过修改 device 的值来选择显卡。

from fastNLP import Trainer
import torch

device= 0 if torch.cuda.is_available() else 'cpu'
trainer = Trainer(data_bundle.get_dataset('train'), model, loss=loss, optimizer=optimizer,
                    dev_data=data_bundle.get_dataset('dev'), metrics=metric, device=device)
trainer.train()

训练过程输出为:

input fields after batch(if batch size is 2):
    target: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 26])
    seq_len: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2])
    words: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 26])
target fields after batch(if batch size is 2):
    target: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2, 26])
    seq_len: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2])

training epochs started 2019-09-25-10-43-09
Evaluate data in 0.62 seconds!
Evaluation on dev at Epoch 1/10. Step:43/430:
SpanFPreRecMetric: f=0.070352, pre=0.100962, rec=0.053985

...

Evaluate data in 0.61 seconds!
Evaluation on dev at Epoch 10/10. Step:430/430:
SpanFPreRecMetric: f=0.51223, pre=0.581699, rec=0.457584


In Epoch:7/Step:301, got best dev performance:
SpanFPreRecMetric: f=0.515528, pre=0.65098, rec=0.426735
Reloaded the best model.

进行测试

训练结束之后过,可以通过 Tester 测试其在测试集上的性能

from fastNLP import Tester

tester = Tester(data_bundle.get_dataset('test'), model, metrics=metric)
tester.test()

输出为:

[tester]
SpanFPreRecMetric: f=0.482399, pre=0.530086, rec=0.442584

使用更强的Bert做序列标注

在fastNLP使用Bert进行任务,您只需要把 fastNLP.embeddings.StaticEmbedding 切换为 fastNLP.embeddings.BertEmbedding (可修改 device 选择显卡)。

from fastNLP.io import WeiboNERPipe
from fastNLP.models import BiLSTMCRF

data_bundle = WeiboNERPipe().process_from_file()
data_bundle.rename_field('chars', 'words')

from fastNLP.embeddings import BertEmbedding
embed = BertEmbedding(vocab=data_bundle.get_vocab('words'), model_dir_or_name='cn')
model = BiLSTMCRF(embed=embed, num_classes=len(data_bundle.get_vocab('target')), num_layers=1, hidden_size=200, dropout=0.5,
              target_vocab=data_bundle.get_vocab('target'))

from fastNLP import SpanFPreRecMetric
from torch.optim import Adam
from fastNLP import LossInForward
metric = SpanFPreRecMetric(tag_vocab=data_bundle.get_vocab('target'))
optimizer = Adam(model.parameters(), lr=2e-5)
loss = LossInForward()

from fastNLP import Trainer
import torch
device= 0 if torch.cuda.is_available() else 'cpu'
trainer = Trainer(data_bundle.get_dataset('train'), model, loss=loss, optimizer=optimizer, batch_size=12,
                    dev_data=data_bundle.get_dataset('dev'), metrics=metric, device=device)
trainer.train()

from fastNLP import Tester
tester = Tester(data_bundle.get_dataset('test'), model, metrics=metric)
tester.test()

输出为:

training epochs started 2019-09-25-07-15-43
Evaluate data in 2.02 seconds!
Evaluation on dev at Epoch 1/10. Step:113/1130:
SpanFPreRecMetric: f=0.0, pre=0.0, rec=0.0

...

Evaluate data in 2.17 seconds!
Evaluation on dev at Epoch 10/10. Step:1130/1130:
SpanFPreRecMetric: f=0.647332, pre=0.589852, rec=0.717224

In Epoch:6/Step:678, got best dev performance:
SpanFPreRecMetric: f=0.669963, pre=0.645238, rec=0.696658
Reloaded the best model.

Evaluate data in 1.82 seconds!
[tester]
SpanFPreRecMetric: f=0.641774, pre=0.626424, rec=0.657895

可以看出通过使用Bert,效果有明显的提升,从48.2提升到了64.1。