=====================
序列标注
=====================

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

.. note::

    本教程推荐使用 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两个基类衔接完成的，您可以通过 :doc:`使用Loader和Pipe处理数据 </tutorials/tutorial_4_load_dataset>`
了解如何使用fastNLP提供的数据加载函数。下面我们以微博命名实体任务来演示一下在fastNLP进行序列标注任务。

.. code-block:: python

    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的相关说明可以参见 :doc:`使用Embedding模块将文本转成向量 </tutorials/tutorial_3_embedding>` 。
在这里我们使用通过word2vec预训练的中文汉字embedding。

.. code-block:: python

    from fastNLP.embeddings import StaticEmbedding

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

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

.. code-block:: python

    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，以及优化用到的优化函数。

.. code-block:: python

    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 的值来选择显卡。

.. code-block:: python

    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.

进行测试
--------------------------------

训练结束之后过，可以通过 :class:`~fastNLP.Tester` 测试其在测试集上的性能

.. code-block:: python

    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进行任务，您只需要把 :class:`fastNLP.embeddings.StaticEmbedding` 切换为 :class:`fastNLP.embeddings.BertEmbedding` （可修改 device 选择显卡）。

.. code-block:: python

    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。


----------------------------------
代码下载
----------------------------------

.. raw:: html

    <a href="../_static/notebooks/%E5%BA%8F%E5%88%97%E6%A0%87%E6%B3%A8.ipynb" download="序列标注.ipynb">点击下载 IPython Notebook 文件</a><hr>
