@@ -0,0 +1,6 @@ | |||
fastNLP.models.bert | |||
=================== | |||
.. automodule:: fastNLP.models.bert | |||
:members: BertForSequenceClassification, BertForSentenceMatching, BertForMultipleChoice, BertForTokenClassification, BertForQuestionAnswering | |||
@@ -2,7 +2,7 @@ fastNLP.models | |||
============== | |||
.. automodule:: fastNLP.models | |||
:members: CNNText, SeqLabeling, AdvSeqLabel, ESIM, StarTransEnc, STSeqLabel, STNLICls, STSeqCls, BiaffineParser, GraphParser | |||
:members: CNNText, SeqLabeling, AdvSeqLabel, ESIM, StarTransEnc, STSeqLabel, STNLICls, STSeqCls, BiaffineParser, GraphParser, BertForSequenceClassification, BertForSentenceMatching, BertForMultipleChoice, BertForTokenClassification, BertForQuestionAnswering | |||
子模块 | |||
------ | |||
@@ -10,6 +10,7 @@ fastNLP.models | |||
.. toctree:: | |||
:maxdepth: 1 | |||
fastNLP.models.bert | |||
fastNLP.models.biaffine_parser | |||
fastNLP.models.cnn_text_classification | |||
fastNLP.models.sequence_labeling | |||
@@ -2,5 +2,5 @@ fastNLP.models.sequence_labeling | |||
================================ | |||
.. automodule:: fastNLP.models.sequence_labeling | |||
:members: SeqLabeling, AdvSeqLabel | |||
:members: SeqLabeling, AdvSeqLabel, BiLSTMCRF | |||
@@ -3,64 +3,52 @@ | |||
===================== | |||
这一部分的内容主要展示如何使用fastNLP 实现序列标注任务。你可以使用fastNLP的各个组件快捷,方便地完成序列标注任务,达到出色的效果。 | |||
在阅读这篇Tutorial前,希望你已经熟悉了fastNLP的基础使用,包括基本数据结构以及数据预处理,embedding的嵌入等,希望你对之前的教程有更进一步的掌握。 | |||
我们将对CoNLL-03的英文数据集进行处理,展示如何完成命名实体标注任务整个训练的过程。 | |||
在阅读这篇Tutorial前,希望你已经熟悉了fastNLP的基础使用,尤其是数据的载入以及模型的构建,通过这个小任务的能让你进一步熟悉fastNLP的使用。 | |||
我们将对基于Weibo的中文社交数据集进行处理,展示如何完成命名实体标注任务的整个过程。 | |||
载入数据 | |||
=================================== | |||
fastNLP可以方便地载入各种类型的数据。同时,针对常见的数据集,我们已经预先实现了载入方法,其中包含CoNLL-03数据集。 | |||
fastNLP的数据载入主要是由Loader与Pipe两个基类衔接完成的。通过Loader可以方便地载入各种类型的数据。同时,针对常见的数据集,我们已经预先实现了载入方法,其中包含weibo数据集。 | |||
在设计dataloader时,以DataSetLoader为基类,可以改写并应用于其他数据集的载入。 | |||
.. code-block:: python | |||
class Conll2003DataLoader(DataSetLoader): | |||
def __init__(self, task:str='ner', encoding_type:str='bioes'): | |||
assert task in ('ner', 'pos', 'chunk') | |||
index = {'ner':3, 'pos':1, 'chunk':2}[task] | |||
#ConllLoader是fastNLP内置的类 | |||
self._loader = ConllLoader(headers=['raw_words', 'target'], indexes=[0, index]) | |||
self._tag_converters = None | |||
if task in ('ner', 'chunk'): | |||
#iob和iob2bioes会对tag进行统一,标准化 | |||
self._tag_converters = [iob2] | |||
if encoding_type == 'bioes': | |||
self._tag_converters.append(iob2bioes) | |||
def load(self, path: str): | |||
dataset = self._loader.load(path) | |||
def convert_tag_schema(tags): | |||
for converter in self._tag_converters: | |||
tags = converter(tags) | |||
return tags | |||
if self._tag_converters: | |||
#使用apply实现convert_tag_schema函数,实际上也支持匿名函数 | |||
dataset.apply_field(convert_tag_schema, field_name=Const.TARGET, new_field_name=Const.TARGET) | |||
return dataset | |||
输出数据格式如: | |||
{'raw_words': ['on', 'Friday', ':'] type=list, | |||
'target': ['O', 'O', 'O'] type=list}, | |||
from fastNLP.io import WeiboNERLoader | |||
data_bundle = WeiboNERLoader().load() | |||
载入后的数据如 :: | |||
{'dev': DataSet( | |||
{{'raw_chars': ['用', '最', '大', '努', '力', '去', '做''人', '生', '。', '哈', '哈', '哈', '哈', '哈', '哈', ' | |||
'target': ['O', 'O', 'O', 'O', 'O', 'O', 'O', 'O', 'O',, 'O', 'O', 'O', 'O', 'O', 'O'] type=list})} | |||
{'test': DataSet( | |||
{{'raw_chars': ['感', '恩', '大', '回', '馈'] type=list, 'target': ['O', 'O', 'O', 'O', 'O'] type=list})} | |||
{'train': DataSet( | |||
{'raw_chars': ['国', '安', '老', '球', '迷'] type=list, 'target': ['B-ORG.NAM', 'I-ORG.NAM', 'B-PER.NOM', 'I-PER.NOM', 'I-PER.NOM'] type=list})} | |||
数据处理 | |||
---------------------------- | |||
我们进一步处理数据。将数据和词表封装在 :class:`~fastNLP.DataBundle` 类中。data是DataBundle的实例。 | |||
我们输入模型的数据包括char embedding,以及word embedding。在数据处理部分,我们尝试完成词表的构建。 | |||
使用fastNLP中的Vocabulary类来构建词表。 | |||
我们进一步处理数据。通过Pipe基类处理Loader载入的数据。 如果你还有印象,应该还能想起,实现自定义数据集的Pipe时,至少要编写process 函数或者process_from_file 函数。前者接受 :class:`~fastNLP.DataBundle` 类的数据,并返回该 :class:`~fastNLP.DataBundle` 。后者接收数据集所在文件夹为参数,读取并处理为 :class:`~fastNLP.DataBundle` 后,通过process 函数处理数据。 | |||
这里我们已经实现通过Loader载入数据,并已返回 :class:`~fastNLP.DataBundle` 类的数据。我们编写process 函数以处理Loader载入后的数据。 | |||
.. code-block:: python | |||
word_vocab = Vocabulary(min_freq=2) | |||
word_vocab.from_dataset(data.datasets['train'], field_name=Const.INPUT) | |||
word_vocab.index_dataset(*data.datasets.values(),field_name=Const.INPUT, new_field_name=Const.INPUT) | |||
from fastNLP.io import ChineseNERPipe | |||
data_bundle = ChineseNERPipe(encoding_type='bioes', bigram=True).process(data_bundle) | |||
处理后的data对象内部为: | |||
载入后的数据如下 :: | |||
dataset | |||
vocabs | |||
dataset保存了train和test中的数据,并保存为dataset类型 | |||
vocab保存了words,raw-words以及target的词表。 | |||
{'raw_chars': ['用', '最', '大', '努', '力', '去', '做', '值', '得', '的', '事', '人', '生', '。', '哈', '哈', '哈', '哈', '哈', '哈', '我', '在'] type=list, | |||
'target': [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] type=list, | |||
'chars': [97, 71, 34, 422, 104, 72, 144, 628, 66, 3, 158, 2, 9, 647, 485, 196, 2,19] type=list, | |||
'bigrams': [5948, 1950, 34840, 98, 8413, 3961, 34841, 631, 34842, 407, 462, 45, 3 1959, 1619, 3, 3, 3, 3, 3, 2663, 29, 90] type=list, | |||
'seq_len': 30 type=int} | |||
模型构建 | |||
-------------------------------- | |||
@@ -69,27 +57,23 @@ fastNLP可以方便地载入各种类型的数据。同时,针对常见的数 | |||
模型的训练 | |||
首先实例化模型,导入所需的char embedding以及word embedding。Embedding的载入可以参考教程。 | |||
也可以查看 :mod:`~fastNLP.modules.encoder.embedding` 使用所需的embedding 载入方法。 | |||
fastNLP将模型的训练过程封装在了 :class:`~fastnlp.trainer` 类中。 | |||
也可以查看 :mod:`~fastNLP.embedding` 使用所需的embedding 载入方法。 | |||
fastNLP将模型的训练过程封装在了 :class:`~fastnlp.Trainer` 类中。 | |||
根据不同的任务调整trainer中的参数即可。通常,一个trainer实例需要有:指定的训练数据集,模型,优化器,loss函数,评测指标,以及指定训练的epoch数,batch size等参数。 | |||
.. code-block:: python | |||
#实例化模型 | |||
model = CNNBiLSTMCRF(word_embed, char_embed, hidden_size=200, num_layers=1, tag_vocab=data.vocabs[Const.TARGET], encoding_type=encoding_type) | |||
#定义优化器 | |||
optimizer = Adam(model.parameters(), lr=0.005) | |||
model = CNBiLSTMCRFNER(char_embed, num_classes=len(data_bundle.vocabs['target']), bigram_embed=bigram_embed) | |||
#定义评估指标 | |||
Metrics=SpanFPreRecMetric(tag_vocab=data.vocabs[Const.TARGET], encoding_type=encoding_type) | |||
#实例化trainer | |||
trainer = Trainer(train_data=data.datasets['train'], model=model, optimizer=optimizer, dev_data=data.datasets['test'], batch_size=10, metrics=Metrics,callbacks=callbacks, n_epochs=100) | |||
#开始训练 | |||
trainer.train() | |||
Metrics=SpanFPreRecMetric(data_bundle.vocabs['target'], encoding_type='bioes') | |||
#实例化trainer并训练 | |||
Trainer(data_bundle.datasets['train'], model, batch_size=20, metrics=Metrics, num_workers=2, dev_data=data_bundle. datasets['dev']).train() | |||
训练中会保存最优的参数配置。 | |||
训练的结果如下: | |||
.. code-block:: python | |||
训练的结果如下 :: | |||
Evaluation on DataSet test: | |||
SpanFPreRecMetric: f=0.727661, pre=0.732293, rec=0.723088 | |||
@@ -21,12 +21,18 @@ __all__ = [ | |||
"STSeqCls", | |||
"BiaffineParser", | |||
"GraphParser" | |||
"GraphParser", | |||
"BertForSequenceClassification", | |||
"BertForSentenceMatching", | |||
"BertForMultipleChoice", | |||
"BertForTokenClassification", | |||
"BertForQuestionAnswering" | |||
] | |||
from .base_model import BaseModel | |||
from .bert import BertForMultipleChoice, BertForQuestionAnswering, BertForSequenceClassification, \ | |||
BertForTokenClassification | |||
BertForTokenClassification, BertForSentenceMatching | |||
from .biaffine_parser import BiaffineParser, GraphParser | |||
from .cnn_text_classification import CNNText | |||
from .sequence_labeling import SeqLabeling, AdvSeqLabel | |||
@@ -1,9 +1,35 @@ | |||
"""undocumented | |||
bert.py is modified from huggingface/pytorch-pretrained-BERT, which is licensed under the Apache License 2.0. | |||
""" | |||
fastNLP提供了BERT应用到五个下游任务的模型代码,可以直接调用。这五个任务分别为 | |||
- 文本分类任务: :class:`~fastNLP.models.BertForSequenceClassification` | |||
- Matching任务: :class:`~fastNLP.models.BertForSentenceMatching` | |||
- 多选任务: :class:`~fastNLP.models.BertForMultipleChoice` | |||
- 序列标注任务: :class:`~fastNLP.models.BertForTokenClassification` | |||
- 抽取式QA任务: :class:`~fastNLP.models.BertForQuestionAnswering` | |||
每一个模型必须要传入一个名字为 `embed` 的 :class:`fastNLP.embeddings.BertEmbedding` ,这个参数包含了 | |||
:class:`fastNLP.modules.encoder.BertModel` ,是下游模型的编码器(encoder)。 | |||
除此以外,还需要传入一个数字,这个数字在不同下游任务模型上的意义如下:: | |||
下游任务模型 参数名称 含义 | |||
BertForSequenceClassification num_labels 文本分类类别数目,默认值为2 | |||
BertForSentenceMatching num_labels Matching任务类别数目,默认值为2 | |||
BertForMultipleChoice num_choices 多选任务选项数目,默认值为2 | |||
BertForTokenClassification num_labels 序列标注标签数目,无默认值 | |||
BertForQuestionAnswering num_labels 抽取式QA列数,默认值为2(即第一列为start_span, 第二列为end_span) | |||
最后还可以传入dropout的大小,默认值为0.1。 | |||
""" | |||
__all__ = [] | |||
__all__ = [ | |||
"BertForSequenceClassification", | |||
"BertForSentenceMatching", | |||
"BertForMultipleChoice", | |||
"BertForTokenClassification", | |||
"BertForQuestionAnswering" | |||
] | |||
import warnings | |||
@@ -13,28 +39,40 @@ from torch import nn | |||
from .base_model import BaseModel | |||
from ..core.const import Const | |||
from ..core._logger import logger | |||
from ..modules.encoder import BertModel | |||
from ..modules.encoder.bert import BertConfig, CONFIG_FILE | |||
from ..embeddings.bert_embedding import BertEmbedding | |||
from ..embeddings import BertEmbedding | |||
class BertForSequenceClassification(BaseModel): | |||
"""BERT model for classification. | |||
""" | |||
def __init__(self, init_embed: BertEmbedding, num_labels: int=2): | |||
别名: :class:`fastNLP.models.BertForSequenceClassification` | |||
:class:`fastNLP.models.bert.BertForSequenceClassification` | |||
BERT model for classification. | |||
:param fastNLP.embeddings.BertEmbedding embed: 下游模型的编码器(encoder). | |||
:param int num_labels: 文本分类类别数目,默认值为2. | |||
:param float dropout: dropout的大小,默认值为0.1. | |||
""" | |||
def __init__(self, embed: BertEmbedding, num_labels: int=2, dropout=0.1): | |||
super(BertForSequenceClassification, self).__init__() | |||
self.num_labels = num_labels | |||
self.bert = init_embed | |||
self.dropout = nn.Dropout(0.1) | |||
self.bert = embed | |||
self.dropout = nn.Dropout(p=dropout) | |||
self.classifier = nn.Linear(self.bert.embedding_dim, num_labels) | |||
if not self.bert.model.include_cls_sep: | |||
warn_msg = "Bert for sequence classification excepts BertEmbedding `include_cls_sep` True, but got False." | |||
self.bert.model.include_cls_sep = True | |||
warn_msg = "Bert for sequence classification excepts BertEmbedding `include_cls_sep` True, " \ | |||
"but got False. FastNLP has changed it to True." | |||
logger.warn(warn_msg) | |||
warnings.warn(warn_msg) | |||
def forward(self, words): | |||
""" | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.Tensor [batch_size, num_labels] | |||
""" | |||
hidden = self.dropout(self.bert(words)) | |||
cls_hidden = hidden[:, 0] | |||
logits = self.classifier(cls_hidden) | |||
@@ -42,172 +80,193 @@ class BertForSequenceClassification(BaseModel): | |||
return {Const.OUTPUT: logits} | |||
def predict(self, words): | |||
""" | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.LongTensor [batch_size] | |||
""" | |||
logits = self.forward(words)[Const.OUTPUT] | |||
return {Const.OUTPUT: torch.argmax(logits, dim=-1)} | |||
class BertForSentenceMatching(BaseModel): | |||
""" | |||
别名: :class:`fastNLP.models.BertForSentenceMatching` | |||
:class:`fastNLP.models.bert.BertForSentenceMatching` | |||
BERT model for sentence matching. | |||
"""BERT model for matching. | |||
:param fastNLP.embeddings.BertEmbedding embed: 下游模型的编码器(encoder). | |||
:param int num_labels: Matching任务类别数目,默认值为2. | |||
:param float dropout: dropout的大小,默认值为0.1. | |||
""" | |||
def __init__(self, init_embed: BertEmbedding, num_labels: int=2): | |||
def __init__(self, embed: BertEmbedding, num_labels: int=2, dropout=0.1): | |||
super(BertForSentenceMatching, self).__init__() | |||
self.num_labels = num_labels | |||
self.bert = init_embed | |||
self.dropout = nn.Dropout(0.1) | |||
self.bert = embed | |||
self.dropout = nn.Dropout(p=dropout) | |||
self.classifier = nn.Linear(self.bert.embedding_dim, num_labels) | |||
if not self.bert.model.include_cls_sep: | |||
error_msg = "Bert for sentence matching excepts BertEmbedding `include_cls_sep` True, but got False." | |||
logger.error(error_msg) | |||
raise RuntimeError(error_msg) | |||
self.bert.model.include_cls_sep = True | |||
warn_msg = "Bert for sentence matching excepts BertEmbedding `include_cls_sep` True, " \ | |||
"but got False. FastNLP has changed it to True." | |||
logger.warn(warn_msg) | |||
warnings.warn(warn_msg) | |||
def forward(self, words): | |||
hidden = self.dropout(self.bert(words)) | |||
cls_hidden = hidden[:, 0] | |||
""" | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.Tensor [batch_size, num_labels] | |||
""" | |||
hidden = self.bert(words) | |||
cls_hidden = self.dropout(hidden[:, 0]) | |||
logits = self.classifier(cls_hidden) | |||
return {Const.OUTPUT: logits} | |||
def predict(self, words): | |||
""" | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.LongTensor [batch_size] | |||
""" | |||
logits = self.forward(words)[Const.OUTPUT] | |||
return {Const.OUTPUT: torch.argmax(logits, dim=-1)} | |||
class BertForMultipleChoice(BaseModel): | |||
"""BERT model for multiple choice tasks. | |||
""" | |||
def __init__(self, init_embed: BertEmbedding, num_choices=2): | |||
别名: :class:`fastNLP.models.BertForMultipleChoice` | |||
:class:`fastNLP.models.bert.BertForMultipleChoice` | |||
BERT model for multiple choice. | |||
:param fastNLP.embeddings.BertEmbedding embed: 下游模型的编码器(encoder). | |||
:param int num_choices: 多选任务选项数目,默认值为2. | |||
:param float dropout: dropout的大小,默认值为0.1. | |||
""" | |||
def __init__(self, embed: BertEmbedding, num_choices=2, dropout=0.1): | |||
super(BertForMultipleChoice, self).__init__() | |||
self.num_choices = num_choices | |||
self.bert = init_embed | |||
self.dropout = nn.Dropout(0.1) | |||
self.bert = embed | |||
self.dropout = nn.Dropout(p=dropout) | |||
self.classifier = nn.Linear(self.bert.embedding_dim, 1) | |||
self.include_cls_sep = init_embed.model.include_cls_sep | |||
if not self.bert.model.include_cls_sep: | |||
error_msg = "Bert for multiple choice excepts BertEmbedding `include_cls_sep` True, but got False." | |||
logger.error(error_msg) | |||
raise RuntimeError(error_msg) | |||
self.bert.model.include_cls_sep = True | |||
warn_msg = "Bert for multiple choice excepts BertEmbedding `include_cls_sep` True, " \ | |||
"but got False. FastNLP has changed it to True." | |||
logger.warn(warn_msg) | |||
warnings.warn(warn_msg) | |||
def forward(self, words): | |||
""" | |||
:param torch.Tensor words: [batch_size, num_choices, seq_len] | |||
:return: [batch_size, num_labels] | |||
:param torch.LongTensor words: [batch_size, num_choices, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.LongTensor [batch_size, num_choices] | |||
""" | |||
batch_size, num_choices, seq_len = words.size() | |||
input_ids = words.view(batch_size * num_choices, seq_len) | |||
hidden = self.bert(input_ids) | |||
pooled_output = hidden[:, 0] | |||
pooled_output = self.dropout(pooled_output) | |||
pooled_output = self.dropout(hidden[:, 0]) | |||
logits = self.classifier(pooled_output) | |||
reshaped_logits = logits.view(-1, self.num_choices) | |||
return {Const.OUTPUT: reshaped_logits} | |||
def predict(self, words): | |||
""" | |||
:param torch.LongTensor words: [batch_size, num_choices, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.LongTensor [batch_size] | |||
""" | |||
logits = self.forward(words)[Const.OUTPUT] | |||
return {Const.OUTPUT: torch.argmax(logits, dim=-1)} | |||
class BertForTokenClassification(BaseModel): | |||
"""BERT model for token-level classification. | |||
""" | |||
def __init__(self, init_embed: BertEmbedding, num_labels): | |||
别名: :class:`fastNLP.models.BertForTokenClassification` | |||
:class:`fastNLP.models.bert.BertForTokenClassification` | |||
BERT model for token classification. | |||
:param fastNLP.embeddings.BertEmbedding embed: 下游模型的编码器(encoder). | |||
:param int num_labels: 序列标注标签数目,无默认值. | |||
:param float dropout: dropout的大小,默认值为0.1. | |||
""" | |||
def __init__(self, embed: BertEmbedding, num_labels, dropout=0.1): | |||
super(BertForTokenClassification, self).__init__() | |||
self.num_labels = num_labels | |||
self.bert = init_embed | |||
self.dropout = nn.Dropout(0.1) | |||
self.bert = embed | |||
self.dropout = nn.Dropout(p=dropout) | |||
self.classifier = nn.Linear(self.bert.embedding_dim, num_labels) | |||
self.include_cls_sep = init_embed.model.include_cls_sep | |||
if self.include_cls_sep: | |||
warn_msg = "Bert for token classification excepts BertEmbedding `include_cls_sep` False, but got True." | |||
warnings.warn(warn_msg) | |||
if self.bert.model.include_cls_sep: | |||
self.bert.model.include_cls_sep = False | |||
warn_msg = "Bert for token classification excepts BertEmbedding `include_cls_sep` False, " \ | |||
"but got True. FastNLP has changed it to False." | |||
logger.warn(warn_msg) | |||
warnings.warn(warn_msg) | |||
def forward(self, words): | |||
""" | |||
:param torch.Tensor words: [batch_size, seq_len] | |||
:return: [batch_size, seq_len, num_labels] | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.Tensor [batch_size, seq_len, num_labels] | |||
""" | |||
sequence_output = self.bert(words) | |||
if self.include_cls_sep: | |||
sequence_output = sequence_output[:, 1: -1] # [batch_size, seq_len, embed_dim] | |||
sequence_output = self.bert(words) # [batch_size, seq_len, embed_dim] | |||
sequence_output = self.dropout(sequence_output) | |||
logits = self.classifier(sequence_output) | |||
return {Const.OUTPUT: logits} | |||
def predict(self, words): | |||
""" | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: { :attr:`fastNLP.Const.OUTPUT` : logits}: torch.LongTensor [batch_size, seq_len] | |||
""" | |||
logits = self.forward(words)[Const.OUTPUT] | |||
return {Const.OUTPUT: torch.argmax(logits, dim=-1)} | |||
class BertForQuestionAnswering(BaseModel): | |||
"""BERT model for Question Answering (span extraction). | |||
This module is composed of the BERT model with a linear layer on top of | |||
the sequence output that computes start_logits and end_logits | |||
Params: | |||
`config`: a BertConfig class instance with the configuration to build a new model. | |||
`bert_dir`: a dir which contains the bert parameters within file `pytorch_model.bin` | |||
Inputs: | |||
`input_ids`: a torch.LongTensor of shape [batch_size, sequence_length] | |||
with the word token indices in the vocabulary(see the tokens preprocessing logic in the scripts | |||
`extract_features.py`, `run_classifier.py` and `run_squad.py`) | |||
`token_type_ids`: an optional torch.LongTensor of shape [batch_size, sequence_length] with the token | |||
types indices selected in [0, 1]. Type 0 corresponds to a `sentence A` and type 1 corresponds to | |||
a `sentence B` token (see BERT paper for more details). | |||
`attention_mask`: an optional torch.LongTensor of shape [batch_size, sequence_length] with indices | |||
selected in [0, 1]. It's a mask to be used if the input sequence length is smaller than the max | |||
input sequence length in the current batch. It's the mask that we typically use for attention when | |||
a batch has varying length sentences. | |||
`start_positions`: position of the first token for the labeled span: torch.LongTensor of shape [batch_size]. | |||
Positions are clamped to the length of the sequence and position outside of the sequence are not taken | |||
into account for computing the loss. | |||
`end_positions`: position of the last token for the labeled span: torch.LongTensor of shape [batch_size]. | |||
Positions are clamped to the length of the sequence and position outside of the sequence are not taken | |||
into account for computing the loss. | |||
Outputs: | |||
if `start_positions` and `end_positions` are not `None`: | |||
Outputs the total_loss which is the sum of the CrossEntropy loss for the start and end token positions. | |||
if `start_positions` or `end_positions` is `None`: | |||
Outputs a tuple of start_logits, end_logits which are the logits respectively for the start and end | |||
position tokens of shape [batch_size, sequence_length]. | |||
Example usage: | |||
```python | |||
# Already been converted into WordPiece token ids | |||
input_ids = torch.LongTensor([[31, 51, 99], [15, 5, 0]]) | |||
input_mask = torch.LongTensor([[1, 1, 1], [1, 1, 0]]) | |||
token_type_ids = torch.LongTensor([[0, 0, 1], [0, 1, 0]]) | |||
config = BertConfig(vocab_size_or_config_json_file=32000, hidden_size=768, | |||
num_hidden_layers=12, num_attention_heads=12, intermediate_size=3072) | |||
bert_dir = 'your-bert-file-dir' | |||
model = BertForQuestionAnswering(config, bert_dir) | |||
start_logits, end_logits = model(input_ids, token_type_ids, input_mask) | |||
``` | |||
""" | |||
def __init__(self, init_embed: BertEmbedding, num_labels=2): | |||
别名: :class:`fastNLP.models.BertForQuestionAnswering` | |||
:class:`fastNLP.models.bert.BertForQuestionAnswering` | |||
BERT model for classification. | |||
:param fastNLP.embeddings.BertEmbedding embed: 下游模型的编码器(encoder). | |||
:param int num_labels: 抽取式QA列数,默认值为2(即第一列为start_span, 第二列为end_span). | |||
""" | |||
def __init__(self, embed: BertEmbedding, num_labels=2): | |||
super(BertForQuestionAnswering, self).__init__() | |||
self.bert = init_embed | |||
self.bert = embed | |||
self.num_labels = num_labels | |||
self.qa_outputs = nn.Linear(self.bert.embedding_dim, self.num_labels) | |||
if not self.bert.model.include_cls_sep: | |||
error_msg = "Bert for multiple choice excepts BertEmbedding `include_cls_sep` True, but got False." | |||
logger.error(error_msg) | |||
raise RuntimeError(error_msg) | |||
self.bert.model.include_cls_sep = True | |||
warn_msg = "Bert for question answering excepts BertEmbedding `include_cls_sep` True, " \ | |||
"but got False. FastNLP has changed it to True." | |||
logger.warn(warn_msg) | |||
warnings.warn(warn_msg) | |||
def forward(self, words): | |||
""" | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: 一个包含num_labels个logit的dict,每一个logit的形状都是[batch_size, seq_len] | |||
""" | |||
sequence_output = self.bert(words) | |||
logits = self.qa_outputs(sequence_output) # [batch_size, seq_len, num_labels] | |||
return {Const.OUTPUTS(i): logits[:, :, i] for i in range(self.num_labels)} | |||
def predict(self, words): | |||
""" | |||
:param torch.LongTensor words: [batch_size, seq_len] | |||
:return: 一个包含num_labels个logit的dict,每一个logit的形状都是[batch_size] | |||
""" | |||
logits = self.forward(words) | |||
return {Const.OUTPUTS(i): torch.argmax(logits[Const.OUTPUTS(i)], dim=-1) for i in range(self.num_labels)} |
@@ -245,7 +245,7 @@ class BiaffineParser(GraphParser): | |||
Biaffine Dependency Parser 实现. | |||
论文参考 `Deep Biaffine Attention for Neural Dependency Parsing (Dozat and Manning, 2016) <https://arxiv.org/abs/1611.01734>`_ . | |||
:param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
:param embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, | |||
此时就以传入的对象作为embedding | |||
:param pos_vocab_size: part-of-speech 词典大小 | |||
@@ -262,7 +262,7 @@ class BiaffineParser(GraphParser): | |||
""" | |||
def __init__(self, | |||
init_embed, | |||
embed, | |||
pos_vocab_size, | |||
pos_emb_dim, | |||
num_label, | |||
@@ -276,7 +276,7 @@ class BiaffineParser(GraphParser): | |||
super(BiaffineParser, self).__init__() | |||
rnn_out_size = 2 * rnn_hidden_size | |||
word_hid_dim = pos_hid_dim = rnn_hidden_size | |||
self.word_embedding = get_embeddings(init_embed) | |||
self.word_embedding = get_embeddings(embed) | |||
word_emb_dim = self.word_embedding.embedding_dim | |||
self.pos_embedding = nn.Embedding(num_embeddings=pos_vocab_size, embedding_dim=pos_emb_dim) | |||
self.word_fc = nn.Linear(word_emb_dim, word_hid_dim) | |||
@@ -23,7 +23,7 @@ class CNNText(torch.nn.Module): | |||
使用CNN进行文本分类的模型 | |||
'Yoon Kim. 2014. Convolution Neural Networks for Sentence Classification.' | |||
:param tuple(int,int),torch.FloatTensor,nn.Embedding,numpy.ndarray init_embed: Embedding的大小(传入tuple(int, int), | |||
:param tuple(int,int),torch.FloatTensor,nn.Embedding,numpy.ndarray embed: Embedding的大小(传入tuple(int, int), | |||
第一个int为vocab_zie, 第二个int为embed_dim); 如果为Tensor, Embedding, ndarray等则直接使用该值初始化Embedding | |||
:param int num_classes: 一共有多少类 | |||
:param int,tuple(int) out_channels: 输出channel的数量。如果为list,则需要与kernel_sizes的数量保持一致 | |||
@@ -31,7 +31,7 @@ class CNNText(torch.nn.Module): | |||
:param float dropout: Dropout的大小 | |||
""" | |||
def __init__(self, init_embed, | |||
def __init__(self, embed, | |||
num_classes, | |||
kernel_nums=(30, 40, 50), | |||
kernel_sizes=(1, 3, 5), | |||
@@ -39,7 +39,7 @@ class CNNText(torch.nn.Module): | |||
super(CNNText, self).__init__() | |||
# no support for pre-trained embedding currently | |||
self.embed = embedding.Embedding(init_embed) | |||
self.embed = embedding.Embedding(embed) | |||
self.conv_pool = encoder.ConvMaxpool( | |||
in_channels=self.embed.embedding_dim, | |||
out_channels=kernel_nums, | |||
@@ -24,21 +24,21 @@ class ESIM(BaseModel): | |||
ESIM model的一个PyTorch实现 | |||
论文参见: https://arxiv.org/pdf/1609.06038.pdf | |||
:param init_embedding: 初始化的Embedding | |||
:param embed: 初始化的Embedding | |||
:param int hidden_size: 隐藏层大小,默认值为Embedding的维度 | |||
:param int num_labels: 目标标签种类数量,默认值为3 | |||
:param float dropout_rate: dropout的比率,默认值为0.3 | |||
:param float dropout_embed: 对Embedding的dropout比率,默认值为0.1 | |||
""" | |||
def __init__(self, init_embedding, hidden_size=None, num_labels=3, dropout_rate=0.3, | |||
def __init__(self, embed, hidden_size=None, num_labels=3, dropout_rate=0.3, | |||
dropout_embed=0.1): | |||
super(ESIM, self).__init__() | |||
if isinstance(init_embedding, TokenEmbedding) or isinstance(init_embedding, Embedding): | |||
self.embedding = init_embedding | |||
if isinstance(embed, TokenEmbedding) or isinstance(embed, Embedding): | |||
self.embedding = embed | |||
else: | |||
self.embedding = Embedding(init_embedding) | |||
self.embedding = Embedding(embed) | |||
self.dropout_embed = EmbedDropout(p=dropout_embed) | |||
if hidden_size is None: | |||
hidden_size = self.embedding.embed_size | |||
@@ -23,7 +23,7 @@ class StarTransEnc(nn.Module): | |||
带word embedding的Star-Transformer Encoder | |||
:param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
:param embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, | |||
此时就以传入的对象作为embedding | |||
:param hidden_size: 模型中特征维度. | |||
@@ -35,7 +35,7 @@ class StarTransEnc(nn.Module): | |||
:param dropout: 模型除词嵌入外的dropout概率. | |||
""" | |||
def __init__(self, init_embed, | |||
def __init__(self, embed, | |||
hidden_size, | |||
num_layers, | |||
num_head, | |||
@@ -44,7 +44,7 @@ class StarTransEnc(nn.Module): | |||
emb_dropout, | |||
dropout): | |||
super(StarTransEnc, self).__init__() | |||
self.embedding = get_embeddings(init_embed) | |||
self.embedding = get_embeddings(embed) | |||
emb_dim = self.embedding.embedding_dim | |||
self.emb_fc = nn.Linear(emb_dim, hidden_size) | |||
# self.emb_drop = nn.Dropout(emb_dropout) | |||
@@ -108,7 +108,7 @@ class STSeqLabel(nn.Module): | |||
用于序列标注的Star-Transformer模型 | |||
:param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
:param embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, | |||
此时就以传入的对象作为embedding | |||
:param num_cls: 输出类别个数 | |||
@@ -122,7 +122,7 @@ class STSeqLabel(nn.Module): | |||
:param dropout: 模型除词嵌入外的dropout概率. Default: 0.1 | |||
""" | |||
def __init__(self, init_embed, num_cls, | |||
def __init__(self, embed, num_cls, | |||
hidden_size=300, | |||
num_layers=4, | |||
num_head=8, | |||
@@ -132,7 +132,7 @@ class STSeqLabel(nn.Module): | |||
emb_dropout=0.1, | |||
dropout=0.1, ): | |||
super(STSeqLabel, self).__init__() | |||
self.enc = StarTransEnc(init_embed=init_embed, | |||
self.enc = StarTransEnc(embed=embed, | |||
hidden_size=hidden_size, | |||
num_layers=num_layers, | |||
num_head=num_head, | |||
@@ -173,7 +173,7 @@ class STSeqCls(nn.Module): | |||
用于分类任务的Star-Transformer | |||
:param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
:param embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, | |||
此时就以传入的对象作为embedding | |||
:param num_cls: 输出类别个数 | |||
@@ -187,7 +187,7 @@ class STSeqCls(nn.Module): | |||
:param dropout: 模型除词嵌入外的dropout概率. Default: 0.1 | |||
""" | |||
def __init__(self, init_embed, num_cls, | |||
def __init__(self, embed, num_cls, | |||
hidden_size=300, | |||
num_layers=4, | |||
num_head=8, | |||
@@ -197,7 +197,7 @@ class STSeqCls(nn.Module): | |||
emb_dropout=0.1, | |||
dropout=0.1, ): | |||
super(STSeqCls, self).__init__() | |||
self.enc = StarTransEnc(init_embed=init_embed, | |||
self.enc = StarTransEnc(embed=embed, | |||
hidden_size=hidden_size, | |||
num_layers=num_layers, | |||
num_head=num_head, | |||
@@ -238,7 +238,7 @@ class STNLICls(nn.Module): | |||
用于自然语言推断(NLI)的Star-Transformer | |||
:param init_embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
:param embed: 单词词典, 可以是 tuple, 包括(num_embedings, embedding_dim), 即 | |||
embedding的大小和每个词的维度. 也可以传入 nn.Embedding 对象, | |||
此时就以传入的对象作为embedding | |||
:param num_cls: 输出类别个数 | |||
@@ -252,7 +252,7 @@ class STNLICls(nn.Module): | |||
:param dropout: 模型除词嵌入外的dropout概率. Default: 0.1 | |||
""" | |||
def __init__(self, init_embed, num_cls, | |||
def __init__(self, embed, num_cls, | |||
hidden_size=300, | |||
num_layers=4, | |||
num_head=8, | |||
@@ -262,7 +262,7 @@ class STNLICls(nn.Module): | |||
emb_dropout=0.1, | |||
dropout=0.1, ): | |||
super(STNLICls, self).__init__() | |||
self.enc = StarTransEnc(init_embed=init_embed, | |||
self.enc = StarTransEnc(embed=embed, | |||
hidden_size=hidden_size, | |||
num_layers=num_layers, | |||
num_head=num_head, | |||
@@ -23,10 +23,25 @@ class TestBert(unittest.TestCase): | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2, 2)) | |||
pred = model.predict(input_ids) | |||
pred = model(input_ids) | |||
self.assertTrue(isinstance(pred, dict)) | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2,)) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2, 2)) | |||
def test_bert_1_w(self): | |||
vocab = Vocabulary().add_word_lst("this is a test .".split()) | |||
embed = BertEmbedding(vocab, model_dir_or_name='test/data_for_tests/embedding/small_bert', | |||
include_cls_sep=False) | |||
with self.assertWarns(Warning): | |||
model = BertForSequenceClassification(embed, 2) | |||
input_ids = torch.LongTensor([[1, 2, 3], [5, 6, 0]]) | |||
pred = model.predict(input_ids) | |||
self.assertTrue(isinstance(pred, dict)) | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2,)) | |||
def test_bert_2(self): | |||
@@ -44,6 +59,23 @@ class TestBert(unittest.TestCase): | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (1, 2)) | |||
def test_bert_2_w(self): | |||
vocab = Vocabulary().add_word_lst("this is a test [SEP] .".split()) | |||
embed = BertEmbedding(vocab, model_dir_or_name='test/data_for_tests/embedding/small_bert', | |||
include_cls_sep=False) | |||
with self.assertWarns(Warning): | |||
model = BertForMultipleChoice(embed, 2) | |||
input_ids = torch.LongTensor([[[2, 6, 7], [1, 6, 5]]]) | |||
print(input_ids.size()) | |||
pred = model.predict(input_ids) | |||
self.assertTrue(isinstance(pred, dict)) | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (1,)) | |||
def test_bert_3(self): | |||
vocab = Vocabulary().add_word_lst("this is a test [SEP] .".split()) | |||
@@ -58,6 +90,22 @@ class TestBert(unittest.TestCase): | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2, 3, 7)) | |||
def test_bert_3_w(self): | |||
vocab = Vocabulary().add_word_lst("this is a test [SEP] .".split()) | |||
embed = BertEmbedding(vocab, model_dir_or_name='test/data_for_tests/embedding/small_bert', | |||
include_cls_sep=True) | |||
with self.assertWarns(Warning): | |||
model = BertForTokenClassification(embed, 7) | |||
input_ids = torch.LongTensor([[1, 2, 3], [6, 5, 0]]) | |||
pred = model.predict(input_ids) | |||
self.assertTrue(isinstance(pred, dict)) | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2, 3)) | |||
def test_bert_4(self): | |||
vocab = Vocabulary().add_word_lst("this is a test [SEP] .".split()) | |||
@@ -79,6 +127,22 @@ class TestBert(unittest.TestCase): | |||
self.assertTrue(isinstance(pred, dict)) | |||
self.assertEqual(len(pred), 7) | |||
def test_bert_4_w(self): | |||
vocab = Vocabulary().add_word_lst("this is a test [SEP] .".split()) | |||
embed = BertEmbedding(vocab, model_dir_or_name='test/data_for_tests/embedding/small_bert', | |||
include_cls_sep=False) | |||
with self.assertWarns(Warning): | |||
model = BertForQuestionAnswering(embed) | |||
input_ids = torch.LongTensor([[1, 2, 3], [6, 5, 0]]) | |||
pred = model.predict(input_ids) | |||
self.assertTrue(isinstance(pred, dict)) | |||
self.assertTrue(Const.OUTPUTS(1) in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUTS(1)].shape), (2,)) | |||
def test_bert_5(self): | |||
vocab = Vocabulary().add_word_lst("this is a test [SEP] .".split()) | |||
@@ -93,3 +157,19 @@ class TestBert(unittest.TestCase): | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2, 2)) | |||
def test_bert_5_w(self): | |||
vocab = Vocabulary().add_word_lst("this is a test [SEP] .".split()) | |||
embed = BertEmbedding(vocab, model_dir_or_name='test/data_for_tests/embedding/small_bert', | |||
include_cls_sep=False) | |||
with self.assertWarns(Warning): | |||
model = BertForSentenceMatching(embed) | |||
input_ids = torch.LongTensor([[1, 2, 3], [6, 5, 0]]) | |||
pred = model.predict(input_ids) | |||
self.assertTrue(isinstance(pred, dict)) | |||
self.assertTrue(Const.OUTPUT in pred) | |||
self.assertEqual(tuple(pred[Const.OUTPUT].shape), (2,)) | |||
@@ -27,7 +27,7 @@ def prepare_parser_data(): | |||
class TestBiaffineParser(unittest.TestCase): | |||
def test_train(self): | |||
model = BiaffineParser(init_embed=(VOCAB_SIZE, 10), | |||
model = BiaffineParser(embed=(VOCAB_SIZE, 10), | |||
pos_vocab_size=VOCAB_SIZE, pos_emb_dim=10, | |||
rnn_hidden_size=10, | |||
arc_mlp_size=10, | |||
@@ -37,7 +37,7 @@ class TestBiaffineParser(unittest.TestCase): | |||
RUNNER.run_model(model, ds, loss=ParserLoss(), metrics=ParserMetric()) | |||
def test_train2(self): | |||
model = BiaffineParser(init_embed=(VOCAB_SIZE, 10), | |||
model = BiaffineParser(embed=(VOCAB_SIZE, 10), | |||
pos_vocab_size=VOCAB_SIZE, pos_emb_dim=10, | |||
rnn_hidden_size=16, | |||
arc_mlp_size=10, | |||