@@ -5,7 +5,7 @@ fastNLP 由 :mod:`~fastNLP.core` 、 :mod:`~fastNLP.io` 、:mod:`~fastNLP.module | |||||
- :mod:`~fastNLP.core` 是fastNLP 的核心模块,包括 DataSet、 Trainer、 Tester 等组件。详见文档 :doc:`/fastNLP.core` | - :mod:`~fastNLP.core` 是fastNLP 的核心模块,包括 DataSet、 Trainer、 Tester 等组件。详见文档 :doc:`/fastNLP.core` | ||||
- :mod:`~fastNLP.io` 是实现输入输出的模块,包括了数据集的读取,模型的存取等功能。详见文档 :doc:`/fastNLP.io` | - :mod:`~fastNLP.io` 是实现输入输出的模块,包括了数据集的读取,模型的存取等功能。详见文档 :doc:`/fastNLP.io` | ||||
- :mod:`~fastNLP.modules` 包含了用于搭建神经网络模型的诸多组件,可以帮助用户快速搭建自己所需的网络。详见文档 :doc:`/fastNLP.modules` | - :mod:`~fastNLP.modules` 包含了用于搭建神经网络模型的诸多组件,可以帮助用户快速搭建自己所需的网络。详见文档 :doc:`/fastNLP.modules` | ||||
- :mod:`~fastNLP.models` 包含了一些使用 fastNLP 实现的完整网络模型,包括CNNText、SeqLabeling等常见模型。详见文档 :doc:`/fastNLP.models` | |||||
- :mod:`~fastNLP.models` 包含了一些使用 fastNLP 实现的完整网络模型,包括 :class:`~fastNLP.models.CNNText` 、 :class:`~fastNLP.models.SeqLabeling` 等常见模型。详见文档 :doc:`/fastNLP.models` | |||||
fastNLP 中最常用的组件可以直接从 fastNLP 包中 import ,他们的文档如下: | fastNLP 中最常用的组件可以直接从 fastNLP 包中 import ,他们的文档如下: | ||||
""" | """ | ||||
@@ -1,12 +1,12 @@ | |||||
""" | """ | ||||
core 模块里实现了 fastNLP 的核心框架,常用的功能都可以从 fastNLP 包中直接 import。当然你也同样可以从 core 模块的子模块中 import, | core 模块里实现了 fastNLP 的核心框架,常用的功能都可以从 fastNLP 包中直接 import。当然你也同样可以从 core 模块的子模块中 import, | ||||
例如 Batch 组件有两种 import 的方式:: | |||||
例如 :class:`~fastNLP.DataSetIter` 组件有两种 import 的方式:: | |||||
# 直接从 fastNLP 中 import | # 直接从 fastNLP 中 import | ||||
from fastNLP import Batch | |||||
from fastNLP import DataSetIter | |||||
# 从 core 模块的子模块 batch 中 import | |||||
from fastNLP.core.batch import Batch | |||||
# 从 core 模块的子模块 batch 中 import DataSetIter | |||||
from fastNLP.core.batch import DataSetIter | |||||
对于常用的功能,你只需要在 :doc:`fastNLP` 中查看即可。如果想了解各个子模块的具体作用,您可以在下面找到每个子模块的具体文档。 | 对于常用的功能,你只需要在 :doc:`fastNLP` 中查看即可。如果想了解各个子模块的具体作用,您可以在下面找到每个子模块的具体文档。 | ||||
@@ -1,18 +1,17 @@ | |||||
""" | """ | ||||
batch 模块实现了 fastNLP 所需的 Batch 类。 | |||||
batch 模块实现了 fastNLP 所需的 :class:`~fastNLP.core.batch.DataSetIter` 类。 | |||||
""" | """ | ||||
__all__ = [ | __all__ = [ | ||||
"BatchIter", | |||||
"DataSetIter", | "DataSetIter", | ||||
"TorchLoaderIter", | "TorchLoaderIter", | ||||
] | ] | ||||
import atexit | import atexit | ||||
from queue import Empty, Full | |||||
import numpy as np | import numpy as np | ||||
import torch | import torch | ||||
import torch.multiprocessing as mp | |||||
import torch.utils.data | import torch.utils.data | ||||
from numbers import Number | from numbers import Number | ||||
@@ -2,11 +2,11 @@ r""" | |||||
callback模块实现了 fastNLP 中的许多 callback 类,用于增强 :class:`~fastNLP.Trainer` 类。 | callback模块实现了 fastNLP 中的许多 callback 类,用于增强 :class:`~fastNLP.Trainer` 类。 | ||||
虽然Trainer本身已经集成了一些功能,但仍然不足以囊括训练过程中可能需要到的功能, | 虽然Trainer本身已经集成了一些功能,但仍然不足以囊括训练过程中可能需要到的功能, | ||||
比如负采样,learning rate decay, Early Stop等。 | |||||
为了解决这个问题fastNLP引入了callback的机制,Callback 是一种在Trainer训练过程中特定阶段会运行的函数集合。 | |||||
关于Trainer的详细文档,请参见 :doc:`trainer 模块<fastNLP.core.trainer>` | |||||
比如负采样,learning rate decay 和 early stop等。 | |||||
为了解决这个问题,fastNLP引入了callback的机制,:class:`~fastNLP.Callback` 是一种在Trainer训练过程中特定阶段会运行的函数集合。 | |||||
关于 :class:`~fastNLP.Trainer` 的详细文档,请参见 :doc:`trainer 模块<fastNLP.core.trainer>` | |||||
我们将 :meth:`~fastNLP.Train.train` 这个函数内部分为以下的阶段,在对应阶段会触发相应的调用:: | |||||
我们将 :meth:`~fastNLP.Trainer.train` 这个函数内部分为以下的阶段,在对应阶段会触发相应的调用:: | |||||
callback.on_train_begin() # 开始进行训练 | callback.on_train_begin() # 开始进行训练 | ||||
for i in range(1, n_epochs+1): | for i in range(1, n_epochs+1): | ||||
@@ -31,8 +31,8 @@ callback模块实现了 fastNLP 中的许多 callback 类,用于增强 :class: | |||||
callback.on_train_end() # 训练结束 | callback.on_train_end() # 训练结束 | ||||
callback.on_exception() # 这是一个特殊的步骤,在训练过程中遭遇exception会跳转到这里。 | callback.on_exception() # 这是一个特殊的步骤,在训练过程中遭遇exception会跳转到这里。 | ||||
如下面的例子所示,我们可以使用内置的 callback 类,或者继承 :class:`~fastNLP.core.callback.Callback` | |||||
定义自己的 callback 类:: | |||||
如下面的例子所示,我们可以使用内置的 callback 组件,或者继承 :class:`~fastNLP.core.callback.Callback` | |||||
定义自己的 callback 组件:: | |||||
from fastNLP import Callback, EarlyStopCallback, Trainer, CrossEntropyLoss, AccuracyMetric | from fastNLP import Callback, EarlyStopCallback, Trainer, CrossEntropyLoss, AccuracyMetric | ||||
from fastNLP.models import CNNText | from fastNLP.models import CNNText | ||||
@@ -448,7 +448,7 @@ class FitlogCallback(Callback): | |||||
并将验证结果写入到fitlog中。这些数据集的结果是根据dev上最好的结果报道的,即如果dev在第3个epoch取得了最佳,则 | 并将验证结果写入到fitlog中。这些数据集的结果是根据dev上最好的结果报道的,即如果dev在第3个epoch取得了最佳,则 | ||||
fitlog中记录的关于这些数据集的结果就是来自第三个epoch的结果。 | fitlog中记录的关于这些数据集的结果就是来自第三个epoch的结果。 | ||||
:param ~fastNLP.DataSet,dict(~fastNLP.DataSet) data: 传入DataSet对象,会使用多个Trainer中的metric对数据进行验证。如果需要传入多个 | |||||
:param ~fastNLP.DataSet,Dict[~fastNLP.DataSet] data: 传入DataSet对象,会使用多个Trainer中的metric对数据进行验证。如果需要传入多个 | |||||
DataSet请通过dict的方式传入,dict的key将作为对应dataset的name传递给fitlog。若tester不为None时,data需要通过 | DataSet请通过dict的方式传入,dict的key将作为对应dataset的name传递给fitlog。若tester不为None时,data需要通过 | ||||
dict的方式传入。如果仅传入DataSet, 则被命名为test | dict的方式传入。如果仅传入DataSet, 则被命名为test | ||||
:param ~fastNLP.Tester tester: Tester对象,将在on_valid_end时调用。tester中的DataSet会被称为为`test` | :param ~fastNLP.Tester tester: Tester对象,将在on_valid_end时调用。tester中的DataSet会被称为为`test` | ||||
@@ -1,7 +1,7 @@ | |||||
""" | """ | ||||
:class:`~fastNLP.core.dataset.DataSet` 是fastNLP中用于承载数据的容器。可以将DataSet看做是一个表格, | :class:`~fastNLP.core.dataset.DataSet` 是fastNLP中用于承载数据的容器。可以将DataSet看做是一个表格, | ||||
每一行是一个sample (在fastNLP中被称为 :mod:`~.instance` ), | |||||
每一列是一个feature (在fastNLP中称为 :mod:`.field` )。 | |||||
每一行是一个sample (在fastNLP中被称为 :mod:`~fastNLP.core.instance` ), | |||||
每一列是一个feature (在fastNLP中称为 :mod:`~fastNLP.core.field` )。 | |||||
.. csv-table:: Following is a demo layout of DataSet | .. csv-table:: Following is a demo layout of DataSet | ||||
:header: "sentence", "words", "seq_len" | :header: "sentence", "words", "seq_len" | ||||
@@ -13,57 +13,64 @@ | |||||
在fastNLP内部每一行是一个 :class:`~fastNLP.Instance` 对象; 每一列是一个 :class:`~fastNLP.FieldArray` 对象。 | 在fastNLP内部每一行是一个 :class:`~fastNLP.Instance` 对象; 每一列是一个 :class:`~fastNLP.FieldArray` 对象。 | ||||
1 DataSet的创建 | |||||
创建DataSet主要有以下的3种方式 | |||||
---------------------------- | |||||
1.DataSet的创建 | |||||
---------------------------- | |||||
1.1 传入dict | |||||
创建DataSet主要有以下的3种方式 | |||||
Example:: | |||||
1.1 传入dict | |||||
---------------------------- | |||||
from fastNLP import DataSet | |||||
data = {'sentence':["This is the first instance .", "Second instance .", "Third instance ."], | |||||
'words': [['this', 'is', 'the', 'first', 'instance', '.'], ['Second', 'instance', '.'], ['Third', 'instance', '.'], | |||||
'seq_len': [6, 3, 3]} | |||||
dataset = DataSet(data) | |||||
# 传入的dict的每个key的value应该为具有相同长度的list | |||||
.. code-block:: | |||||
1.2 通过构建Instance | |||||
from fastNLP import DataSet | |||||
data = {'sentence':["This is the first instance .", "Second instance .", "Third instance ."], | |||||
'words': [['this', 'is', 'the', 'first', 'instance', '.'], ['Second', 'instance', '.'], ['Third', 'instance', '.'], | |||||
'seq_len': [6, 3, 3]} | |||||
dataset = DataSet(data) | |||||
# 传入的dict的每个key的value应该为具有相同长度的list | |||||
Example:: | |||||
1.2 通过 Instance 构建 | |||||
---------------------------- | |||||
from fastNLP import DataSet | |||||
from fastNLP import Instance | |||||
dataset = DataSet() | |||||
instance = Instance(sentence="This is the first instance", | |||||
words=['this', 'is', 'the', 'first', 'instance', '.'], | |||||
seq_len=6) | |||||
dataset.append(instance) | |||||
# 可以继续append更多内容,但是append的instance应该和第一个instance拥有完全相同的field | |||||
.. code-block:: | |||||
1.3 通过list(Instance) | |||||
from fastNLP import DataSet | |||||
from fastNLP import Instance | |||||
dataset = DataSet() | |||||
instance = Instance(sentence="This is the first instance", | |||||
words=['this', 'is', 'the', 'first', 'instance', '.'], | |||||
seq_len=6) | |||||
dataset.append(instance) | |||||
# 可以继续append更多内容,但是append的instance应该和第一个instance拥有完全相同的field | |||||
Example:: | |||||
1.3 通过 List[Instance] 构建 | |||||
-------------------------------------- | |||||
from fastNLP import DataSet | |||||
from fastNLP import Instance | |||||
instances = [] | |||||
instances.append(Instance(sentence="This is the first instance", | |||||
words=['this', 'is', 'the', 'first', 'instance', '.'], | |||||
seq_len=6)) | |||||
instances.append(Instance(sentence="Second instance .", | |||||
words=['Second', 'instance', '.'], | |||||
seq_len=3)) | |||||
dataset = DataSet(instances) | |||||
.. code-block:: | |||||
2 DataSet与预处理 | |||||
常见的预处理有如下几种 | |||||
from fastNLP import DataSet | |||||
from fastNLP import Instance | |||||
instances = [] | |||||
winstances.append(Instance(sentence="This is the first instance", | |||||
ords=['this', 'is', 'the', 'first', 'instance', '.'], | |||||
seq_len=6)) | |||||
instances.append(Instance(sentence="Second instance .", | |||||
words=['Second', 'instance', '.'], | |||||
seq_len=3)) | |||||
dataset = DataSet(instances) | |||||
-------------------------------------- | |||||
2.DataSet与预处理 | |||||
-------------------------------------- | |||||
2.1 从某个文本文件读取内容 # | |||||
常见的预处理有如下几种 | |||||
.. todo:: | |||||
引用DataLoader | |||||
2.1 从某个文本文件读取内容 | |||||
-------------------------------------- | |||||
Example:: | |||||
.. code-block:: | |||||
from fastNLP import DataSet | from fastNLP import DataSet | ||||
from fastNLP import Instance | from fastNLP import Instance | ||||
@@ -78,9 +85,13 @@ | |||||
sent, label = line.strip().split('\t') | sent, label = line.strip().split('\t') | ||||
dataset.append(Instance(sentence=sent, label=label)) | dataset.append(Instance(sentence=sent, label=label)) | ||||
.. note:: | |||||
直接读取特定数据集的数据请参考 :doc:`/tutorials/tutorial_2_load_dataset` | |||||
2.2 对DataSet中的内容处理 | 2.2 对DataSet中的内容处理 | ||||
-------------------------------------- | |||||
Example:: | |||||
.. code-block:: | |||||
from fastNLP import DataSet | from fastNLP import DataSet | ||||
data = {'sentence':["This is the first instance .", "Second instance .", "Third instance ."]} | data = {'sentence':["This is the first instance .", "Second instance .", "Third instance ."]} | ||||
@@ -97,8 +108,9 @@ | |||||
dataset.apply(get_words, new_field_name='words') | dataset.apply(get_words, new_field_name='words') | ||||
2.3 删除DataSet的内容 | 2.3 删除DataSet的内容 | ||||
-------------------------------------- | |||||
Example:: | |||||
.. code-block:: | |||||
from fastNLP import DataSet | from fastNLP import DataSet | ||||
dataset = DataSet({'a': list(range(-5, 5))}) | dataset = DataSet({'a': list(range(-5, 5))}) | ||||
@@ -113,15 +125,17 @@ | |||||
2.4 遍历DataSet的内容 | 2.4 遍历DataSet的内容 | ||||
-------------------------------------- | |||||
Example:: | |||||
.. code-block:: | |||||
for instance in dataset: | for instance in dataset: | ||||
# do something | # do something | ||||
2.5 一些其它操作 | 2.5 一些其它操作 | ||||
-------------------------------------- | |||||
Example:: | |||||
.. code-block:: | |||||
# 检查是否存在名为'a'的field | # 检查是否存在名为'a'的field | ||||
dataset.has_field('a') # 或 ('a' in dataset) | dataset.has_field('a') # 或 ('a' in dataset) | ||||
@@ -129,21 +143,25 @@ | |||||
dataset.rename_field('a', 'b') | dataset.rename_field('a', 'b') | ||||
# DataSet的长度 | # DataSet的长度 | ||||
len(dataset) | len(dataset) | ||||
-------------------------------------- | |||||
3.DataSet与自然语言处理(NLP) | |||||
-------------------------------------- | |||||
3 DataSet与自然语言处理(NLP) | |||||
在目前深度学习的模型中,大都依赖于随机梯度下降法(SGD)进行模型的优化。随机梯度下降需要将数据切分成一个一个的Batch, | |||||
一个Batch进行一次前向计算(forward)与梯度后向传播(backward)。在自然语言处理的场景下,往往还需要对数据进行pad。这是 | |||||
由于句子的长度一般是不同的,但是一次Batch中的每个field都必须是一个tensor,所以需要将所有句子都补齐到相同的长度。 | |||||
在目前深度学习的模型中,大都依赖于随机梯度下降法(SGD)进行模型的优化。随机梯度下降需要将数据切分成一个个的 batch, | |||||
一个batch进行一次前向计算(forward)与梯度后向传播(backward)。在自然语言处理的场景下,往往还需要对数据进行pad。这是 | |||||
由于句子的长度一般是不同的,但是一次batch中的每个field都必须是一个tensor,所以需要将所有句子都补齐到相同的长度。 | |||||
3.1 DataSet与Batch | |||||
3.1 DataSet与DataSetIter | |||||
-------------------------------------- | |||||
我们先看fastNLP中如何将数据分成一个一个的Batch的例子, 这里我们使用随机生成的数据来模拟一个二分类文本分类任务, | |||||
我们先看fastNLP中如何将数据分成一个一个的batch的例子, 这里我们使用随机生成的数据来模拟一个二分类文本分类任务, | |||||
words和characters是输入,labels是文本类别 | words和characters是输入,labels是文本类别 | ||||
Example:: | |||||
.. code-block:: | |||||
from fastNLP import DataSet | from fastNLP import DataSet | ||||
from fastNLP import Batch | |||||
from fastNLP import DataSetIter | |||||
from fastNLP import SequentialSampler | from fastNLP import SequentialSampler | ||||
from fastNLP import EngChar2DPadder | from fastNLP import EngChar2DPadder | ||||
@@ -163,7 +181,7 @@ | |||||
d.set_target('label') | d.set_target('label') | ||||
d.set_input('words', 'chars') | d.set_input('words', 'chars') | ||||
for batch_x, batch_y in Batch(d, sampler=SequentialSampler(), batch_size=2): | |||||
for batch_x, batch_y in DataSetIter(d, sampler=SequentialSampler(), batch_size=2): | |||||
print("batch_x:", batch_x) | print("batch_x:", batch_x) | ||||
print("batch_y:", batch_y) | print("batch_y:", batch_y) | ||||
break | break | ||||
@@ -182,23 +200,26 @@ | |||||
# [ 0, 0, 0, 0, 0]]])} | # [ 0, 0, 0, 0, 0]]])} | ||||
# {'label': tensor([0, 0])} | # {'label': tensor([0, 0])} | ||||
其中 :class:`~fastNLP.Batch` 是用于从DataSet中按照batch_size为大小取出batch的迭代器, | |||||
:class:`~fastNLP.SequentialSampler` 用于指示 Batch 以怎样的 | |||||
其中 :class:`~fastNLP.DataSetIter` 是用于从DataSet中按照batch_size为大小取出batch的迭代器, | |||||
:class:`~fastNLP.SequentialSampler` 用于指示 :class:`~fastNLP.DataSetIter` 以怎样的 | |||||
顺序从DataSet中取出instance以组成一个batch, | 顺序从DataSet中取出instance以组成一个batch, | ||||
更详细的说明请参照 :class:`~fastNLP.Batch` 和 :class:`~fastNLP.SequentialSampler` 文档。 | |||||
更详细的说明请参照 :class:`~fastNLP.DataSetIter` 和 :class:`~fastNLP.SequentialSampler` 文档。 | |||||
通过DataSet.set_input('words', 'chars'), fastNLP将认为'words'和'chars'这两个field都是input,并将它们都放入迭代器 | |||||
生成的第一个dict中; DataSet.set_target('labels'), fastNLP将认为'labels'这个field是target,并将其放入到迭代器的第 | |||||
通过 ``DataSet.set_input('words', 'chars')`` , fastNLP将认为 `words` 和 `chars` 这两个field都是input,并将它们都放入迭代器 | |||||
生成的第一个dict中; ``DataSet.set_target('labels')`` , fastNLP将认为 `labels` 这个field是target,并将其放入到迭代器的第 | |||||
二个dict中。如上例中所打印结果。分为input和target的原因是由于它们在被 :class:`~fastNLP.Trainer` 所使用时会有所差异, | 二个dict中。如上例中所打印结果。分为input和target的原因是由于它们在被 :class:`~fastNLP.Trainer` 所使用时会有所差异, | ||||
详见 :class:`~fastNLP.Trainer` | 详见 :class:`~fastNLP.Trainer` | ||||
当把某个field设置为'target'或者'input'的时候(两者不是互斥的,可以同时设为input和target),fastNLP不仅仅只是将其放 | |||||
置到不同的dict中,而还会对被设置为input或target的field进行类型检查。类型检查的目的是为了看能否把该field转为 | |||||
pytorch的torch.LongTensor或torch.FloatTensor类型(也可以在Batch中设置输出numpy类型,参考 :class:`~fastNLP.Batch` ),如上例所示, | |||||
fastNLP已将words,chars和label转为了Tensor类型。如果field在每个instance都拥有相同的维度(不能超过两维),且最内层 | |||||
的元素都为相同的type(int, float, np.int*, np.float*),则fastNLP默认将对该field进行pad。也支持全为str的field作为 | |||||
target和input,这种情况下,fastNLP默认不进行pad。另外,当某个field已经被设置为了target或者input后,之后append的 | |||||
instance对应的field必须要和前面已有的内容一致,否则会报错。 | |||||
当把某个field设置为 `target` 或者 `input` 的时候(两者不是互斥的,可以同时设为两种),fastNLP不仅仅只是将其放 | |||||
置到不同的dict中,而还会对被设置为 `input` 或 `target` 的 field 进行类型检查。类型检查的目的是为了看能否把该 field 转为 | |||||
pytorch的 :class:`torch.LongTensor` 或 :class:`torch.FloatTensor` 类型 | |||||
(也可以在 :class:`~fastNLP.DataSetIter` 中设置输出numpy类型,参考 :class:`~fastNLP.DataSetIter` )。 | |||||
如上例所示,fastNLP已将 `words` ,`chars` 和 `label` 转为了 :class:`Tensor` 类型。 | |||||
如果 field 在每个 `instance` 都拥有相同的维度(不能超过两维),且最内层的元素都为相同的 type(int, float, np.int*, np.float*), | |||||
则fastNLP默认将对该 field 进行pad。也支持全为str的field作为target和input,这种情况下,fastNLP默认不进行pad。 | |||||
另外,当某个 field 已经被设置为了 target 或者 input 后,之后 `append` 的 | |||||
`instance` 对应的 field 必须要和前面已有的内容一致,否则会报错。 | |||||
可以查看field的dtype:: | 可以查看field的dtype:: | ||||
@@ -217,6 +238,7 @@ | |||||
错误:: | 错误:: | ||||
from fastNLP import DataSet | from fastNLP import DataSet | ||||
d = DataSet({'data': [1, 'a']}) | d = DataSet({'data': [1, 'a']}) | ||||
d.set_input('data') | d.set_input('data') | ||||
>> RuntimeError: Mixed data types in Field data: [<class 'str'>, <class 'int'>] | >> RuntimeError: Mixed data types in Field data: [<class 'str'>, <class 'int'>] | ||||
@@ -231,6 +253,7 @@ | |||||
当某个field被设置为忽略type之后,fastNLP将不对其进行pad。 | 当某个field被设置为忽略type之后,fastNLP将不对其进行pad。 | ||||
3.2 DataSet与pad | 3.2 DataSet与pad | ||||
-------------------------------------- | |||||
在fastNLP里,pad是与一个field绑定的。即不同的field可以使用不同的pad方式,比如在英文任务中word需要的pad和 | 在fastNLP里,pad是与一个field绑定的。即不同的field可以使用不同的pad方式,比如在英文任务中word需要的pad和 | ||||
character的pad方式往往是不同的。fastNLP是通过一个叫做 :class:`~fastNLP.Padder` 的子类来完成的。 | character的pad方式往往是不同的。fastNLP是通过一个叫做 :class:`~fastNLP.Padder` 的子类来完成的。 | ||||
@@ -240,7 +263,7 @@ | |||||
如果 :class:`~fastNLP.AutoPadder` 或 :class:`~fastNLP.EngChar2DPadder` 无法满足需求, | 如果 :class:`~fastNLP.AutoPadder` 或 :class:`~fastNLP.EngChar2DPadder` 无法满足需求, | ||||
也可以自己写一个 :class:`~fastNLP.Padder` 。 | 也可以自己写一个 :class:`~fastNLP.Padder` 。 | ||||
Example:: | |||||
.. code-block:: | |||||
from fastNLP import DataSet | from fastNLP import DataSet | ||||
from fastNLP import EngChar2DPadder | from fastNLP import EngChar2DPadder | ||||
@@ -405,7 +428,7 @@ class DataSet(object): | |||||
""" | """ | ||||
将一个instance对象append到DataSet后面。 | 将一个instance对象append到DataSet后面。 | ||||
:param instance: :class:`~fastNLP.Instance` 类型。若DataSet不为空,则instance应该拥有和DataSet完全一样的field。 | |||||
:param ~fastNLP.Instance instance: 若DataSet不为空,则instance应该拥有和DataSet完全一样的field。 | |||||
""" | """ | ||||
if len(self.field_arrays) == 0: | if len(self.field_arrays) == 0: | ||||
@@ -431,7 +454,7 @@ class DataSet(object): | |||||
将fieldarray添加到DataSet中. | 将fieldarray添加到DataSet中. | ||||
:param str field_name: 新加入的field的名称 | :param str field_name: 新加入的field的名称 | ||||
:param fieldarray: :class:`~fastNLP.FieldArray` 类型。需要加入DataSet的field的内容 | |||||
:param ~fastNLP.core.FieldArray fieldarray: 需要加入DataSet的field的内容 | |||||
:return: | :return: | ||||
""" | """ | ||||
if not isinstance(fieldarray, FieldArray): | if not isinstance(fieldarray, FieldArray): | ||||
@@ -447,8 +470,7 @@ class DataSet(object): | |||||
:param str field_name: 新增的field的名称 | :param str field_name: 新增的field的名称 | ||||
:param list fields: 需要新增的field的内容 | :param list fields: 需要新增的field的内容 | ||||
:param None, padder: :class:`~fastNLP.Padder` 类型, | |||||
如果为None,则不进行pad,默认使用 :class:`~fastNLP.AutoPadder` 自动判断是否需要做pad。 | |||||
:param None,~fastNLP.Padder padder: 如果为None,则不进行pad,默认使用 :class:`~fastNLP.AutoPadder` 自动判断是否需要做pad。 | |||||
:param bool is_input: 新加入的field是否是input | :param bool is_input: 新加入的field是否是input | ||||
:param bool is_target: 新加入的field是否是target | :param bool is_target: 新加入的field是否是target | ||||
:param bool ignore_type: 是否忽略对新加入的field的类型检查 | :param bool ignore_type: 是否忽略对新加入的field的类型检查 | ||||
@@ -510,7 +532,7 @@ class DataSet(object): | |||||
""" | """ | ||||
返回一个dict,key为field_name, value为对应的 :class:`~fastNLP.FieldArray` | 返回一个dict,key为field_name, value为对应的 :class:`~fastNLP.FieldArray` | ||||
:return: dict: 返回如上所述的字典 | |||||
:return dict: 返回如上所述的字典 | |||||
""" | """ | ||||
return self.field_arrays | return self.field_arrays | ||||
@@ -518,7 +540,7 @@ class DataSet(object): | |||||
""" | """ | ||||
返回一个list,包含所有 field 的名字 | 返回一个list,包含所有 field 的名字 | ||||
:return: list: 返回如上所述的列表 | |||||
:return list: 返回如上所述的列表 | |||||
""" | """ | ||||
return sorted(self.field_arrays.keys()) | return sorted(self.field_arrays.keys()) | ||||
@@ -612,7 +634,7 @@ class DataSet(object): | |||||
dataset.set_padder('chars', padder) # 则chars这个field会使用EngChar2DPadder进行pad操作 | dataset.set_padder('chars', padder) # 则chars这个field会使用EngChar2DPadder进行pad操作 | ||||
:param str field_name: 设置field的padding方式为padder | :param str field_name: 设置field的padding方式为padder | ||||
:param None, Padder padder: 设置为None即删除padder, 即对该field不进行pad操作。 | |||||
:param None,~fastNLP.Padder padder: 设置为None即删除padder, 即对该field不进行pad操作。 | |||||
""" | """ | ||||
if field_name not in self.field_arrays: | if field_name not in self.field_arrays: | ||||
raise KeyError("There is no field named {}.".format(field_name)) | raise KeyError("There is no field named {}.".format(field_name)) | ||||
@@ -660,7 +682,7 @@ class DataSet(object): | |||||
2. is_target: bool, 如果为True则将名为 `new_field_name` 的field设置为target | 2. is_target: bool, 如果为True则将名为 `new_field_name` 的field设置为target | ||||
3. ignore_type: bool, 如果为True则将名为 `new_field_name` 的field的ignore_type设置为true, 忽略其类型 | 3. ignore_type: bool, 如果为True则将名为 `new_field_name` 的field的ignore_type设置为true, 忽略其类型 | ||||
:return: list(Any), 里面的元素为func的返回值,所以list长度为DataSet的长度 | |||||
:return List[Any]: 里面的元素为func的返回值,所以list长度为DataSet的长度 | |||||
""" | """ | ||||
assert len(self) != 0, "Null DataSet cannot use apply_field()." | assert len(self) != 0, "Null DataSet cannot use apply_field()." | ||||
@@ -687,7 +709,7 @@ class DataSet(object): | |||||
""" | """ | ||||
将results作为加入到新的field中,field名称为new_field_name | 将results作为加入到新的field中,field名称为new_field_name | ||||
:param list(str) results: 一般是apply*()之后的结果 | |||||
:param List[str] results: 一般是apply*()之后的结果 | |||||
:param str new_field_name: 新加入的field的名称 | :param str new_field_name: 新加入的field的名称 | ||||
:param dict kwargs: 用户apply*()时传入的自定义参数 | :param dict kwargs: 用户apply*()时传入的自定义参数 | ||||
:return: | :return: | ||||
@@ -730,7 +752,7 @@ class DataSet(object): | |||||
3. ignore_type: bool, 如果为True则将 `new_field_name` 的field的ignore_type设置为true, 忽略其类型 | 3. ignore_type: bool, 如果为True则将 `new_field_name` 的field的ignore_type设置为true, 忽略其类型 | ||||
:return: list(Any), 里面的元素为func的返回值,所以list长度为DataSet的长度 | |||||
:return List[Any]: 里面的元素为func的返回值,所以list长度为DataSet的长度 | |||||
""" | """ | ||||
assert len(self) != 0, "Null DataSet cannot use apply()." | assert len(self) != 0, "Null DataSet cannot use apply()." | ||||
idx = -1 | idx = -1 | ||||
@@ -795,7 +817,7 @@ class DataSet(object): | |||||
:param float ratio: 0<ratio<1, 返回的第一个DataSet拥有 `(1-ratio)` 这么多数据,第二个DataSet拥有`ratio`这么多数据 | :param float ratio: 0<ratio<1, 返回的第一个DataSet拥有 `(1-ratio)` 这么多数据,第二个DataSet拥有`ratio`这么多数据 | ||||
:param bool shuffle: 在split前是否shuffle一下 | :param bool shuffle: 在split前是否shuffle一下 | ||||
:return: [DataSet, DataSet] | |||||
:return: [ :class:`~fastNLP.读取后的DataSet` , :class:`~fastNLP.读取后的DataSet` ] | |||||
""" | """ | ||||
assert isinstance(ratio, float) | assert isinstance(ratio, float) | ||||
assert 0 < ratio < 1 | assert 0 < ratio < 1 | ||||
@@ -819,7 +841,7 @@ class DataSet(object): | |||||
@classmethod | @classmethod | ||||
def read_csv(cls, csv_path, headers=None, sep=",", dropna=True): | def read_csv(cls, csv_path, headers=None, sep=",", dropna=True): | ||||
""" | |||||
r""" | |||||
.. warning:: | .. warning:: | ||||
此方法会在下个版本移除,请使用 :class:`fastNLP.io.CSVLoader` | 此方法会在下个版本移除,请使用 :class:`fastNLP.io.CSVLoader` | ||||
@@ -830,7 +852,7 @@ class DataSet(object): | |||||
与csv文件中每行的元素个数相同。 | 与csv文件中每行的元素个数相同。 | ||||
:param str sep: 分割符 | :param str sep: 分割符 | ||||
:param bool dropna: 是否忽略与header数量不一致行。 | :param bool dropna: 是否忽略与header数量不一致行。 | ||||
:return: 一个 :class:`~fastNLP.DataSet` 类型的对象 | |||||
:return: 读取后的 :class:`~fastNLP.读取后的DataSet`。 | |||||
""" | """ | ||||
warnings.warn('DataSet.read_csv is deprecated, use CSVLoader instead', | warnings.warn('DataSet.read_csv is deprecated, use CSVLoader instead', | ||||
category=DeprecationWarning) | category=DeprecationWarning) | ||||
@@ -870,11 +892,11 @@ class DataSet(object): | |||||
@staticmethod | @staticmethod | ||||
def load(path): | def load(path): | ||||
""" | |||||
r""" | |||||
从保存的DataSet pickle文件的路径中读取DataSet | 从保存的DataSet pickle文件的路径中读取DataSet | ||||
:param str path: 从哪里读取DataSet | :param str path: 从哪里读取DataSet | ||||
:return: 一个 :class:`~fastNLP.DataSet` 类型的对象 | |||||
:return: 读取后的 :class:`~fastNLP.读取后的DataSet`。 | |||||
""" | """ | ||||
with open(path, 'rb') as f: | with open(path, 'rb') as f: | ||||
d = pickle.load(f) | d = pickle.load(f) | ||||
@@ -448,9 +448,10 @@ class Padder: | |||||
用于对batch进行padding操作。传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前deepcopy一份。 | 用于对batch进行padding操作。传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前deepcopy一份。 | ||||
.. py:function:: __call__(self, contents, field_name, field_ele_dtype): | .. py:function:: __call__(self, contents, field_name, field_ele_dtype): | ||||
传入的是List内容。假设有以下的DataSet。 | 传入的是List内容。假设有以下的DataSet。 | ||||
:param list(Any) contents: 传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 | |||||
:param List[Any] contents: 传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 | |||||
deepcopy一份。 | deepcopy一份。 | ||||
:param str, field_name: field的名称。 | :param str, field_name: field的名称。 | ||||
:param np.int64,np.float64,np.str,None, field_ele_dtype: 该field的内层元素的类型。如果该field的ignore_type为True,该这个值为None。 | :param np.int64,np.float64,np.str,None, field_ele_dtype: 该field的内层元素的类型。如果该field的ignore_type为True,该这个值为None。 | ||||
@@ -469,7 +470,7 @@ class Padder: | |||||
""" | """ | ||||
传入的是List内容。假设有以下的DataSet。 | 传入的是List内容。假设有以下的DataSet。 | ||||
:param list(Any) contents: 传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 | |||||
:param List[Any] contents: 传入的element是inplace的,即直接修改element可能导致数据变化,建议inplace修改之前 | |||||
deepcopy一份。 | deepcopy一份。 | ||||
:param str, field_name: field的名称。 | :param str, field_name: field的名称。 | ||||
:param np.int64,np.float64,np.str,None, field_ele_dtype: 该field的内层元素的类型。如果该field的ignore_type为True, | :param np.int64,np.float64,np.str,None, field_ele_dtype: 该field的内层元素的类型。如果该field的ignore_type为True, | ||||
@@ -208,7 +208,7 @@ class CrossEntropyLoss(LossBase): | |||||
:param seq_len: 句子的长度, 长度之外的token不会计算loss。。 | :param seq_len: 句子的长度, 长度之外的token不会计算loss。。 | ||||
:param padding_idx: padding的index,在计算loss时将忽略target中标号为padding_idx的内容, 可以通过该值代替 | :param padding_idx: padding的index,在计算loss时将忽略target中标号为padding_idx的内容, 可以通过该值代替 | ||||
传入seq_len. | 传入seq_len. | ||||
:param str reduction: 支持'mean','sum'和'none'. | |||||
:param str reduction: 支持 `mean` ,`sum` 和 `none` . | |||||
Example:: | Example:: | ||||
@@ -265,9 +265,9 @@ class BCELoss(LossBase): | |||||
二分类交叉熵损失函数 | 二分类交叉熵损失函数 | ||||
:param pred: 参数映射表中`pred`的映射关系,None表示映射关系为`pred`->`pred` | |||||
:param target: 参数映射表中`target`的映射关系,None表示映射关系为`target`->`target` | |||||
:param str reduction: 支持'mean','sum'和'none'. | |||||
:param pred: 参数映射表中 `pred` 的映射关系,None表示映射关系为 `pred` -> `pred` | |||||
:param target: 参数映射表中 `target` 的映射关系,None表示映射关系为 `target` -> `target` | |||||
:param str reduction: 支持 `mean` ,`sum` 和 `none` . | |||||
""" | """ | ||||
def __init__(self, pred=None, target=None, reduction='mean'): | def __init__(self, pred=None, target=None, reduction='mean'): | ||||
@@ -286,11 +286,11 @@ class NLLLoss(LossBase): | |||||
负对数似然损失函数 | 负对数似然损失函数 | ||||
:param pred: 参数映射表中`pred`的映射关系,None表示映射关系为`pred`->`pred` | |||||
:param target: 参数映射表中`target`的映射关系,None表示映射关系为`target`->`target` | |||||
:param pred: 参数映射表中 `pred` 的映射关系,None表示映射关系为 `pred` -> `pred` | |||||
:param target: 参数映射表中 `target` 的映射关系,None表示映射关系为 `target` -> `target` | |||||
:param ignore_idx: ignore的index,在计算loss时将忽略target中标号为ignore_idx的内容, 可以通过该值代替 | :param ignore_idx: ignore的index,在计算loss时将忽略target中标号为ignore_idx的内容, 可以通过该值代替 | ||||
传入seq_len. | 传入seq_len. | ||||
:param str reduction: 支持'mean','sum'和'none'. | |||||
:param str reduction: 支持 `mean` ,`sum` 和 `none` . | |||||
""" | """ | ||||
def __init__(self, pred=None, target=None, ignore_idx=-100, reduction='mean'): | def __init__(self, pred=None, target=None, ignore_idx=-100, reduction='mean'): | ||||
@@ -27,14 +27,14 @@ from abc import abstractmethod | |||||
class MetricBase(object): | class MetricBase(object): | ||||
""" | """ | ||||
所有metrics的基类,,所有的传入到Trainer, Tester的Metric需要继承自该对象,需要覆盖写入evaluate(), get_metric()方法。 | |||||
所有metrics的基类,所有的传入到Trainer, Tester的Metric需要继承自该对象,需要覆盖写入evaluate(), get_metric()方法。 | |||||
evaluate(xxx)中传入的是一个batch的数据。 | evaluate(xxx)中传入的是一个batch的数据。 | ||||
get_metric(xxx)当所有数据处理完毕,调用该方法得到最终的metric值 | get_metric(xxx)当所有数据处理完毕,调用该方法得到最终的metric值 | ||||
以分类问题中,Accuracy计算为例 | 以分类问题中,Accuracy计算为例 | ||||
假设model的forward返回dict中包含'pred'这个key, 并且该key需要用于Accuracy:: | |||||
假设model的forward返回dict中包含 `pred` 这个key, 并且该key需要用于Accuracy:: | |||||
class Model(nn.Module): | class Model(nn.Module): | ||||
def __init__(xxx): | def __init__(xxx): | ||||
@@ -43,7 +43,7 @@ class MetricBase(object): | |||||
# do something | # do something | ||||
return {'pred': pred, 'other_keys':xxx} # pred's shape: batch_size x num_classes | return {'pred': pred, 'other_keys':xxx} # pred's shape: batch_size x num_classes | ||||
假设dataset中'label'这个field是需要预测的值,并且该field被设置为了target | |||||
假设dataset中 `label` 这个field是需要预测的值,并且该field被设置为了target | |||||
对应的AccMetric可以按如下的定义, version1, 只使用这一次:: | 对应的AccMetric可以按如下的定义, version1, 只使用这一次:: | ||||
class AccMetric(MetricBase): | class AccMetric(MetricBase): | ||||
@@ -478,7 +478,7 @@ class SpanFPreRecMetric(MetricBase): | |||||
别名::class:`fastNLP.SpanFPreRecMetric` :class:`fastNLP.core.metrics.SpanFPreRecMetric` | 别名::class:`fastNLP.SpanFPreRecMetric` :class:`fastNLP.core.metrics.SpanFPreRecMetric` | ||||
在序列标注问题中,以span的方式计算F, pre, rec. | 在序列标注问题中,以span的方式计算F, pre, rec. | ||||
比如中文Part of speech中,会以character的方式进行标注,句子'中国在亚洲'对应的POS可能为(以BMES为例) | |||||
比如中文Part of speech中,会以character的方式进行标注,句子 `中国在亚洲` 对应的POS可能为(以BMES为例) | |||||
['B-NN', 'E-NN', 'S-DET', 'B-NN', 'E-NN']。该metric就是为类似情况下的F1计算。 | ['B-NN', 'E-NN', 'S-DET', 'B-NN', 'E-NN']。该metric就是为类似情况下的F1计算。 | ||||
最后得到的metric结果为:: | 最后得到的metric结果为:: | ||||
@@ -502,15 +502,15 @@ class SpanFPreRecMetric(MetricBase): | |||||
:param tag_vocab: 标签的 :class:`~fastNLP.Vocabulary` 。支持的标签为"B"(没有label);或"B-xxx"(xxx为某种label,比如POS中的NN), | :param tag_vocab: 标签的 :class:`~fastNLP.Vocabulary` 。支持的标签为"B"(没有label);或"B-xxx"(xxx为某种label,比如POS中的NN), | ||||
在解码时,会将相同xxx的认为是同一个label,比如['B-NN', 'E-NN']会被合并为一个'NN'. | 在解码时,会将相同xxx的认为是同一个label,比如['B-NN', 'E-NN']会被合并为一个'NN'. | ||||
:param str pred: 用该key在evaluate()时从传入dict中取出prediction数据。 为None,则使用'pred'取数据 | |||||
:param str target: 用该key在evaluate()时从传入dict中取出target数据。 为None,则使用'target'取数据 | |||||
:param str seq_len: 用该key在evaluate()时从传入dict中取出sequence length数据。为None,则使用'seq_len'取数据。 | |||||
:param str pred: 用该key在evaluate()时从传入dict中取出prediction数据。 为None,则使用 `pred` 取数据 | |||||
:param str target: 用该key在evaluate()时从传入dict中取出target数据。 为None,则使用 `target` 取数据 | |||||
:param str seq_len: 用该key在evaluate()时从传入dict中取出sequence length数据。为None,则使用 `seq_len` 取数据。 | |||||
:param str encoding_type: 目前支持bio, bmes, bmeso, bioes | :param str encoding_type: 目前支持bio, bmes, bmeso, bioes | ||||
:param list ignore_labels: str 组成的list. 这个list中的class不会被用于计算。例如在POS tagging时传入['NN'],则不会计算'NN'这 | :param list ignore_labels: str 组成的list. 这个list中的class不会被用于计算。例如在POS tagging时传入['NN'],则不会计算'NN'这 | ||||
个label | 个label | ||||
:param bool only_gross: 是否只计算总的f1, precision, recall的值;如果为False,不仅返回总的f1, pre, rec, 还会返回每个 | :param bool only_gross: 是否只计算总的f1, precision, recall的值;如果为False,不仅返回总的f1, pre, rec, 还会返回每个 | ||||
label的f1, pre, rec | label的f1, pre, rec | ||||
:param str f_type: 'micro'或'macro'. 'micro':通过先计算总体的TP,FN和FP的数量,再计算f, precision, recall; 'macro': | |||||
:param str f_type: `micro` 或 `macro` . `micro` :通过先计算总体的TP,FN和FP的数量,再计算f, precision, recall; `macro` : | |||||
分布计算每个类别的f, precision, recall,然后做平均(各类别f的权重相同) | 分布计算每个类别的f, precision, recall,然后做平均(各类别f的权重相同) | ||||
:param float beta: f_beta分数, :math:`f_{beta} = \frac{(1 + {beta}^{2})*(pre*rec)}{({beta}^{2}*pre + rec)}` . | :param float beta: f_beta分数, :math:`f_{beta} = \frac{(1 + {beta}^{2})*(pre*rec)}{({beta}^{2}*pre + rec)}` . | ||||
常用为beta=0.5, 1, 2. 若为0.5则精确率的权重高于召回率;若为1,则两者平等;若为2,则召回率权重高于精确率。 | 常用为beta=0.5, 1, 2. 若为0.5则精确率的权重高于召回率;若为1,则两者平等;若为2,则召回率权重高于精确率。 | ||||
@@ -5,7 +5,8 @@ optimizer 模块定义了 fastNLP 中所需的各种优化器,一般做为 :cl | |||||
__all__ = [ | __all__ = [ | ||||
"Optimizer", | "Optimizer", | ||||
"SGD", | "SGD", | ||||
"Adam" | |||||
"Adam", | |||||
"AdamW" | |||||
] | ] | ||||
import torch | import torch | ||||
@@ -104,6 +105,10 @@ class Adam(Optimizer): | |||||
class AdamW(TorchOptimizer): | class AdamW(TorchOptimizer): | ||||
r"""对AdamW的实现,该实现应该会在pytorch更高版本中出现,https://github.com/pytorch/pytorch/pull/21250。这里提前加入 | r"""对AdamW的实现,该实现应该会在pytorch更高版本中出现,https://github.com/pytorch/pytorch/pull/21250。这里提前加入 | ||||
.. todo:: | |||||
翻译成中文 | |||||
The original Adam algorithm was proposed in `Adam: A Method for Stochastic Optimization`_. | The original Adam algorithm was proposed in `Adam: A Method for Stochastic Optimization`_. | ||||
The AdamW variant was proposed in `Decoupled Weight Decay Regularization`_. | The AdamW variant was proposed in `Decoupled Weight Decay Regularization`_. | ||||
Arguments: | Arguments: | ||||
@@ -1,7 +1,7 @@ | |||||
""" | """ | ||||
tester模块实现了 fastNLP 所需的Tester类,能在提供数据、模型以及metric的情况下进行性能测试。 | tester模块实现了 fastNLP 所需的Tester类,能在提供数据、模型以及metric的情况下进行性能测试。 | ||||
Example:: | |||||
.. code-block:: | |||||
import numpy as np | import numpy as np | ||||
import torch | import torch | ||||
@@ -60,15 +60,14 @@ class Tester(object): | |||||
Tester是在提供数据,模型以及metric的情况下进行性能测试的类。需要传入模型,数据以及metric进行验证。 | Tester是在提供数据,模型以及metric的情况下进行性能测试的类。需要传入模型,数据以及metric进行验证。 | ||||
:param data: 需要测试的数据集, :class:`~fastNLP.DataSet` 类型 | |||||
:param ~fastNLP.DataSet data: 需要测试的数据集 | |||||
:param torch.nn.module model: 使用的模型 | :param torch.nn.module model: 使用的模型 | ||||
:param metrics: :class:`~fastNLP.core.metrics.MetricBase` 或者一个列表的 :class:`~fastNLP.core.metrics.MetricBase` | |||||
:param ~fastNLP.core.metrics.MetricBase,List[~fastNLP.core.metrics.MetricBase] metrics: 测试时使用的metrics | |||||
:param int batch_size: evaluation时使用的batch_size有多大。 | :param int batch_size: evaluation时使用的batch_size有多大。 | ||||
:param str,int,torch.device,list(int) device: 将模型load到哪个设备。默认为None,即Trainer不对模型 | :param str,int,torch.device,list(int) device: 将模型load到哪个设备。默认为None,即Trainer不对模型 | ||||
的计算位置进行管理。支持以下的输入: | 的计算位置进行管理。支持以下的输入: | ||||
1. str: ['cpu', 'cuda', 'cuda:0', 'cuda:1', ...] 依次为'cpu'中, 可见的第一个GPU中, 可见的第一个GPU中, | |||||
可见的第二个GPU中; | |||||
1. str: ['cpu', 'cuda', 'cuda:0', 'cuda:1', ...] 依次为'cpu'中, 可见的第一个GPU中,可见的第一个GPU中,可见的第二个GPU中; | |||||
2. torch.device:将模型装载到torch.device上。 | 2. torch.device:将模型装载到torch.device上。 | ||||
@@ -11,288 +11,310 @@ Trainer在fastNLP中用于组织单任务的训练过程,可以避免用户在 | |||||
(5) 保存获得更好验证性能的模型。 | (5) 保存获得更好验证性能的模型。 | ||||
1 Trainer的基本使用 | |||||
下面的例子是使用神经网络来进行预测一个序列中是否有偶数个1。 | |||||
Example:: | |||||
import numpy as np | |||||
from torch import nn | |||||
import torch | |||||
import torch.nn.functional as F | |||||
from torch.optim import SGD | |||||
from fastNLP import DataSet | |||||
from fastNLP import Trainer | |||||
from fastNLP import CrossEntropyLoss | |||||
from fastNLP import AccuracyMetric | |||||
from fastNLP.modules.decoder import MLP | |||||
# 模型 | |||||
class Model(nn.Module): | |||||
def __init__(self, input_num): | |||||
super().__init__() | |||||
self.fcs = MLP([input_num, 40, 40, 2], 'relu') | |||||
def forward(self, x): | |||||
x = self.fcs(x) | |||||
return {'pred': x} | |||||
model = Model(10) | |||||
# 生成数据 | |||||
def generate_psedo_dataset(num_samples): | |||||
dataset = DataSet() | |||||
data = np.random.randint(2, size=(num_samples, 10)) | |||||
label = np.sum(data, axis=1)%2 | |||||
dataset = DataSet({'x':data.astype(float), 'label': label}) | |||||
dataset.set_input('x') | |||||
dataset.set_target('label') | |||||
return dataset | |||||
tr_dataset = generate_psedo_dataset(1000) | |||||
dev_data = generate_psedo_dataset(100) | |||||
# 训练 | |||||
trainer = Trainer(tr_dataset, model, loss=CrossEntropyLoss(target='label'), | |||||
optimizer=SGD(model.parameters(), lr=0.1),n_epochs=1000, | |||||
dev_data = dev_data, metrics=AccuracyMetric(target='label')) | |||||
trainer.train() | |||||
由上面的例子可以看出通过使用Trainer,可以使得训练部分的代码大幅减少。 | |||||
使用Trainer需要满足以下几个条件: | |||||
---------------------------- | |||||
1. Trainer的基本使用 | |||||
---------------------------- | |||||
下面的例子是使用神经网络来进行预测一个序列中是否有偶数个1。 | |||||
.. code-block:: python | |||||
import numpy as np | |||||
from torch import nn | |||||
import torch | |||||
import torch.nn.functional as F | |||||
from torch.optim import SGD | |||||
from fastNLP import DataSet | |||||
from fastNLP import Trainer | |||||
from fastNLP import CrossEntropyLoss | |||||
from fastNLP import AccuracyMetric | |||||
from fastNLP.modules.decoder import MLP | |||||
# 模型 | |||||
class Model(nn.Module): | |||||
def __init__(self, input_num): | |||||
super().__init__() | |||||
self.fcs = MLP([input_num, 40, 40, 2], 'relu') | |||||
def forward(self, x): | |||||
x = self.fcs(x) | |||||
return {'pred': x} | |||||
model = Model(10) | |||||
# 生成数据 | |||||
def generate_psedo_dataset(num_samples): | |||||
dataset = DataSet() | |||||
data = np.random.randint(2, size=(num_samples, 10)) | |||||
label = np.sum(data, axis=1)%2 | |||||
dataset = DataSet({'x':data.astype(float), 'label': label}) | |||||
dataset.set_input('x') | |||||
dataset.set_target('label') | |||||
return dataset | |||||
tr_dataset = generate_psedo_dataset(1000) | |||||
dev_data = generate_psedo_dataset(100) | |||||
# 训练 | |||||
trainer = Trainer(tr_dataset, model, loss=CrossEntropyLoss(target='label'), | |||||
optimizer=SGD(model.parameters(), lr=0.1),n_epochs=1000, | |||||
dev_data = dev_data, metrics=AccuracyMetric(target='label')) | |||||
trainer.train() | |||||
由上面的例子可以看出通过使用Trainer,可以使得训练部分的代码大幅减少。 | |||||
使用Trainer需要满足以下几个条件: | |||||
1.1 模型 | 1.1 模型 | ||||
1 模型的forward()的参数名需要与DataSet中的名字对应。实际上fastNLP在将DataSet中的数据传递给模型forward()时,是 | |||||
通过匹配名称实现的。所以上例中,如果Model的forward函数修改为forward(self, data), 则DataSet中的'x'这个field就应该 | |||||
改名为'data'。 | |||||
---------------------------- | |||||
1 模型的forward()的参数名需要与DataSet中的名字对应。实际上fastNLP在将DataSet中的数据传递给模型forward()时,是 | |||||
通过匹配名称实现的。所以上例中,如果Model的forward函数修改为forward(self, data), 则DataSet中的'x'这个field就应该 | |||||
改名为'data'。 | |||||
2 传递给forward()的参数是DataSet中被设置为input的那些field。但如果forward()中没有对应的参数,则不会将数据传递 | |||||
给forward()。例如,DataSet中'x1', 'x2'都是input,但是模型的函数为forward(self, x1), 那么'x2'不会传递给forward()。 | |||||
2 传递给forward()的参数是DataSet中被设置为input的那些field。但如果forward()中没有对应的参数,则不会将数据传递 | |||||
给forward()。例如,DataSet中'x1', 'x2'都是input,但是模型的函数为forward(self, x1), 那么'x2'不会传递给forward()。 | |||||
3 模型的forward()返回值需要为一个dict。 | |||||
3 模型的forward()返回值需要为一个dict。 | |||||
1.2 Loss | 1.2 Loss | ||||
fastNLP中的为了不限制forward函数的返回内容数量(比如一些复杂任务需要返回多个内容,如Dependency Parsing, | |||||
:mod:`Loss<fastNLP.core.losses>` 与 :mod:`Metric<fastNLP.core.metrics>` 都使用了通过名称来匹配相应内容的策略。如上面的例子中 | |||||
---------------------------- | |||||
Example:: | |||||
fastNLP中的为了不限制forward函数的返回内容数量(比如一些复杂任务需要返回多个内容,如Dependency Parsing, | |||||
:mod:`Loss<fastNLP.core.losses>` 与 :mod:`Metric<fastNLP.core.metrics>` 都使用了通过名称来匹配相应内容的策略。如上面的例子中 | |||||
trainer = Trainer(tr_dataset, model, loss=CrossEntropyLoss(target='label'), | |||||
optimizer=SGD(model.parameters(), lr=0.1),n_epochs=1000, | |||||
dev_data = dev_data, metrics=AccuracyMetric(target='label')) | |||||
.. code-block:: python | |||||
loss被设置为了 :class:`~fastNLP.CrossEntropyLoss` , 但在初始化的时候传入了target='label'这个参数, | |||||
:class:`~fastNLP.CrossEntropyLoss` 的初始化参数为(pred=None, target=None, padding_idx=-100)。 | |||||
这里的两个参数分别为计算CrossEntropy时需要使用到的模型的预测值与真实值。 | |||||
其中 `pred` 一般来自于模型forward()的返回结果,`target` 一般是来自于DataSet中被设置为target的field。 | |||||
由于每个人对真实值或者model的返回值取名并不一样,所以fastNLP的 :mod:`Loss<fastNLP.core.losses>` 提供一种类似于映射的机制来匹配对应的值, | |||||
比如这里 :class:`~fastNLP.CrossEntropyLoss` 将尝试找到名为'label'的内容来作为真实值得到loss; | |||||
而pred=None, 则 :class:`~fastNLP.CrossEntropyLoss` 使用'pred'作为名称匹配预测值, | |||||
正好forward的返回值也叫pred,所以这里不需要申明pred。 | |||||
尽管fastNLP使用了映射机制来使得loss的计算变得比较灵活,但有些情况下loss必须在模型中进行计算,比如使用了CRF的模型。 | |||||
fastNLP中提供了 :class:`~fastNLP.LossInForward` 这个loss。 | |||||
这个loss的原理是直接在forward()的返回结果中找到loss_key(默认寻找'loss')指定的那个tensor,并使用它作为loss。 | |||||
如果Trainer初始化没有提供loss则默认使用 :class:`~fastNLP.LossInForward` 。 | |||||
.. todo:: | |||||
补充一个例子 详细例子可以参照 | |||||
trainer = Trainer(tr_dataset, model, loss=CrossEntropyLoss(target='label'), | |||||
optimizer=SGD(model.parameters(), lr=0.1),n_epochs=1000, | |||||
dev_data = dev_data, metrics=AccuracyMetric(target='label')) | |||||
loss被设置为了 :class:`~fastNLP.CrossEntropyLoss` , 但在初始化的时候传入了target='label'这个参数, | |||||
:class:`~fastNLP.CrossEntropyLoss` 的初始化参数为(pred=None, target=None, padding_idx=-100)。 | |||||
这里的两个参数分别为计算CrossEntropy时需要使用到的模型的预测值与真实值。 | |||||
其中 `pred` 一般来自于模型forward()的返回结果,`target` 一般是来自于DataSet中被设置为target的field。 | |||||
由于每个人对真实值或者model的返回值取名并不一样,所以fastNLP的 :mod:`Loss<fastNLP.core.losses>` 提供一种类似于映射的机制来匹配对应的值, | |||||
比如这里 :class:`~fastNLP.CrossEntropyLoss` 将尝试找到名为'label'的内容来作为真实值得到loss; | |||||
而pred=None, 则 :class:`~fastNLP.CrossEntropyLoss` 使用'pred'作为名称匹配预测值, | |||||
正好forward的返回值也叫pred,所以这里不需要申明pred。 | |||||
尽管fastNLP使用了映射机制来使得loss的计算变得比较灵活,但有些情况下loss必须在模型中进行计算,比如使用了CRF的模型。 | |||||
fastNLP中提供了 :class:`~fastNLP.LossInForward` 这个loss。 | |||||
这个loss的原理是直接在forward()的返回结果中找到loss_key(默认寻找'loss')指定的那个tensor,并使用它作为loss。 | |||||
如果Trainer初始化没有提供loss则默认使用 :class:`~fastNLP.LossInForward` 。 | |||||
.. todo:: | |||||
补充一个例子 详细例子可以参照 | |||||
1.3 Metric | 1.3 Metric | ||||
:mod:`Metric<fastNLP.core.metrics>` 使用了与上述Loss一样的策略,即使用名称进行匹配。 | |||||
AccuracyMetric(target='label')的情况与CrossEntropyLoss 是同理的。 | |||||
在进行验证时,可能用到的计算与forward()中不太一致,没有办法直接从forward()的结果中得到预测值,这时模型可以提供一个predict()方法, | |||||
如果提供的模型具有predict方法,则在模型验证时将调用predict()方法获取预测结果, | |||||
传入到predict()的参数也是从DataSet中被设置为input的field中选择出来的; | |||||
与forward()一样,返回值需要为一个dict。 | |||||
---------------------------- | |||||
:mod:`Metric<fastNLP.core.metrics>` 使用了与上述Loss一样的策略,即使用名称进行匹配。 | |||||
AccuracyMetric(target='label')的情况与CrossEntropyLoss 是同理的。 | |||||
在进行验证时,可能用到的计算与forward()中不太一致,没有办法直接从forward()的结果中得到预测值,这时模型可以提供一个predict()方法, | |||||
如果提供的模型具有predict方法,则在模型验证时将调用predict()方法获取预测结果, | |||||
传入到predict()的参数也是从DataSet中被设置为input的field中选择出来的; | |||||
与forward()一样,返回值需要为一个dict。 | |||||
.. todo:: | |||||
补充一个例子 具体例子可以参考 | |||||
.. todo:: | |||||
补充一个例子 具体例子可以参考 | |||||
---------------------------- | |||||
2. Trainer的代码检查 | |||||
---------------------------- | |||||
2 Trainer的代码检查 | |||||
由于在fastNLP中采取了映射的机制,所以难免可能存在对应出错的情况。Trainer提供一种映射检查机制,可以通过check_code_level来进行控制 | |||||
比如下面的例子中,由于各种原因产生的报错 | |||||
由于在fastNLP中采取了映射的机制,所以难免可能存在对应出错的情况。Trainer提供一种映射检查机制,可以通过check_code_level来进行控制 | |||||
比如下面的例子中,由于各种原因产生的报错 | |||||
Example2.1 | Example2.1 | ||||
:: | |||||
import numpy as np | |||||
from torch import nn | |||||
import torch | |||||
from torch.optim import SGD | |||||
from fastNLP import Trainer | |||||
from fastNLP import DataSet | |||||
class Model(nn.Module): | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.fc = nn.Linear(1, 1) | |||||
def forward(self, x, b): | |||||
loss = torch.mean((self.fc(x)-b)**2) | |||||
return {'loss': loss} | |||||
model = Model() | |||||
dataset = DataSet({'a': np.arange(10), 'b':np.arange(10)*2}) | |||||
dataset.set_input('a', 'b') | |||||
trainer = Trainer(dataset, model, loss=None, optimizer=SGD(model.parameters(), lr=0.001)) | |||||
trainer = Trainer(dataset, model, SGD(model.parameters())) | |||||
# 会报以下的错误 | |||||
# input fields after batch(if batch size is 2): | |||||
# a: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) | |||||
# b: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) | |||||
# There is no target field. | |||||
# .... | |||||
# NameError: | |||||
# Problems occurred when calling Model.forward(self, x, b) | |||||
# missing param: ['x'] | |||||
# unused field: ['a'] | |||||
# Suggestion: You need to provide ['x'] in DataSet and set it as input. | |||||
这里就是由于在Trainer初始化的时候,fastNLP会尝试使用一个batch_size=2的batch去运行一遍forward()以及backward()。这里有两类 | |||||
信息可以为你提供参考 | |||||
1 'input fields after batch...'这部分显示的是train dataset经过Batch操作后,每个field对应的类型以及进行shape。这里 | |||||
因为train dataset没有target所以没有显示。根据这里可以看出是否正确将需要的内容设置为了input或target。 | |||||
2 NameError,NameError发生在映射出错的情况。这里报错的原因是由于尝试进行forward计算时(可以通过Model.forward(self, x, b)判断 | |||||
出当前是在调取forward),却没有获取到forward()函数中需要的'x';在报错信息中同时指出了缺'x',而'a'没有被使用,那么可能 | |||||
就是由于field的名称不对。这里将dataset中'a'这个field的名称改为'x',或者model的参数从'x'修改为'a'都可以解决问题。 | |||||
下面的例子是由于loss计算的时候找不到需要的值 | |||||
---------------------------- | |||||
.. code-block:: python | |||||
import numpy as np | |||||
from torch import nn | |||||
import torch | |||||
from torch.optim import SGD | |||||
from fastNLP import Trainer | |||||
from fastNLP import DataSet | |||||
class Model(nn.Module): | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.fc = nn.Linear(1, 1) | |||||
def forward(self, x, b): | |||||
loss = torch.mean((self.fc(x)-b)**2) | |||||
return {'loss': loss} | |||||
model = Model() | |||||
dataset = DataSet({'a': np.arange(10), 'b':np.arange(10)*2}) | |||||
dataset.set_input('a', 'b') | |||||
trainer = Trainer(dataset, model, loss=None, optimizer=SGD(model.parameters(), lr=0.001)) | |||||
trainer = Trainer(dataset, model, SGD(model.parameters())) | |||||
# 会报以下的错误 | |||||
# input fields after batch(if batch size is 2): | |||||
# a: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) | |||||
# b: (1)type:torch.Tensor (2)dtype:torch.int64, (3)shape:torch.Size([2]) | |||||
# There is no target field. | |||||
# .... | |||||
# NameError: | |||||
# Problems occurred when calling Model.forward(self, x, b) | |||||
# missing param: ['x'] | |||||
# unused field: ['a'] | |||||
# Suggestion: You need to provide ['x'] in DataSet and set it as input. | |||||
这里就是由于在Trainer初始化的时候,fastNLP会尝试使用一个batch_size=2的batch去运行一遍forward()以及backward()。这里有两类 | |||||
信息可以为你提供参考 | |||||
1 'input fields after batch...'这部分显示的是train dataset经过Batch操作后,每个field对应的类型以及进行shape。这里 | |||||
因为train dataset没有target所以没有显示。根据这里可以看出是否正确将需要的内容设置为了input或target。 | |||||
2 NameError,NameError发生在映射出错的情况。这里报错的原因是由于尝试进行forward计算时(可以通过Model.forward(self, x, b)判断 | |||||
出当前是在调取forward),却没有获取到forward()函数中需要的'x';在报错信息中同时指出了缺'x',而'a'没有被使用,那么可能 | |||||
就是由于field的名称不对。这里将dataset中'a'这个field的名称改为'x',或者model的参数从'x'修改为'a'都可以解决问题。 | |||||
下面的例子是由于loss计算的时候找不到需要的值 | |||||
Example2.2 | Example2.2 | ||||
:: | |||||
import numpy as np | |||||
from torch import nn | |||||
from torch.optim import SGD | |||||
from fastNLP import Trainer | |||||
from fastNLP import DataSet | |||||
from fastNLP import L1Loss | |||||
import torch | |||||
class Model(nn.Module): | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.fc = nn.Linear(1, 1) | |||||
def forward(self, a): | |||||
return {'pred_b': self.fc(a.unsqueeze(1)).squeeze(1), 'No use':1} | |||||
model = Model() | |||||
dataset = DataSet({'a': np.arange(10, dtype=float), 'b':np.arange(10, dtype=float)*2}) | |||||
dataset.set_input('a') | |||||
dataset.set_target('b') | |||||
trainer = Trainer(dataset, model, loss=L1Loss(target='label'), optimizer=SGD(model.parameters(), lr=0.001)) | |||||
# 报错信息如下 | |||||
# input fields after batch(if batch size is 2): | |||||
# a: (1)type:torch.Tensor (2)dtype:torch.float32, (3)shape:torch.Size([2]) | |||||
# target fields after batch(if batch size is 2): | |||||
# b: (1)type:torch.Tensor (2)dtype:torch.float32, (3)shape:torch.Size([2]) | |||||
# .... | |||||
# NameError: | |||||
# Problems occurred when calling L1Loss.get_loss(self, pred, target) | |||||
# missing param: ['pred(assign to `pred` in `L1Loss`)', 'label(assign to `target` in `L1Loss`)'] | |||||
# unused field: ['b'] | |||||
# unused param: ['pred_b', 'No use'] | |||||
# target field: ['b'] | |||||
# param from Model.forward(self, a): ['pred_b', 'No use'] | |||||
# Suggestion: (1). Check key assignment for `target` when initialize L1Loss. Or provide `label` in DataSet or output of Model.forward(self, a). | |||||
# (2). Check key assignment for `pred` when initialize L1Loss. Or provide `pred` in DataSet or output of Model.forward(self, a). | |||||
报错信息也包含两部分: | |||||
1 第一部分与上面是一样的 | |||||
2 这里报错的原因是由于计算loss的时候找不到相应的值(通过L1Loss.get_loss(self, pred, target)判断出来的); | |||||
报错的原因是因为 `pred` 和 `label` (我们在初始化L1Loss时将target指定为了label)都没有找到。 | |||||
这里'unused field'是DataSet中出现了,但却没有被设置为input或者target的field; | |||||
'unused param'是forward()中返回且没有被使用到的内容;'target field'是被设置为了target的field; | |||||
'param from Model.forward(self, a)'是forward()返回的所有key。"Suggestion"是关于当前错误处理的建议。 | |||||
但是在一些情况下,比如forward()返回值只有一个,target也只有一个,fastNLP不会进行匹配,而直接将forward()的结果作为pred, | |||||
将DataSet中的target设置为target。上面的例子在返回值中加入了一个'No use'则只是为了使得Loss去匹配结果。 | |||||
下面是带有dev dataset时如果出现错误会发生的报错, | |||||
---------------------------- | |||||
.. code-block:: python | |||||
import numpy as np | |||||
from torch import nn | |||||
from torch.optim import SGD | |||||
from fastNLP import Trainer | |||||
from fastNLP import DataSet | |||||
from fastNLP import L1Loss | |||||
import torch | |||||
class Model(nn.Module): | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.fc = nn.Linear(1, 1) | |||||
def forward(self, a): | |||||
return {'pred_b': self.fc(a.unsqueeze(1)).squeeze(1), 'No use':1} | |||||
model = Model() | |||||
dataset = DataSet({'a': np.arange(10, dtype=float), 'b':np.arange(10, dtype=float)*2}) | |||||
dataset.set_input('a') | |||||
dataset.set_target('b') | |||||
trainer = Trainer(dataset, model, loss=L1Loss(target='label'), optimizer=SGD(model.parameters(), lr=0.001)) | |||||
# 报错信息如下 | |||||
# input fields after batch(if batch size is 2): | |||||
# a: (1)type:torch.Tensor (2)dtype:torch.float32, (3)shape:torch.Size([2]) | |||||
# target fields after batch(if batch size is 2): | |||||
# b: (1)type:torch.Tensor (2)dtype:torch.float32, (3)shape:torch.Size([2]) | |||||
# .... | |||||
# NameError: | |||||
# Problems occurred when calling L1Loss.get_loss(self, pred, target) | |||||
# missing param: ['pred(assign to `pred` in `L1Loss`)', 'label(assign to `target` in `L1Loss`)'] | |||||
# unused field: ['b'] | |||||
# unused param: ['pred_b', 'No use'] | |||||
# target field: ['b'] | |||||
# param from Model.forward(self, a): ['pred_b', 'No use'] | |||||
# Suggestion: (1). Check key assignment for `target` when initialize L1Loss. Or provide `label` in DataSet or output of Model.forward(self, a). | |||||
# (2). Check key assignment for `pred` when initialize L1Loss. Or provide `pred` in DataSet or output of Model.forward(self, a). | |||||
报错信息也包含两部分: | |||||
1 第一部分与上面是一样的 | |||||
2 这里报错的原因是由于计算loss的时候找不到相应的值(通过L1Loss.get_loss(self, pred, target)判断出来的); | |||||
报错的原因是因为 `pred` 和 `label` (我们在初始化L1Loss时将target指定为了label)都没有找到。 | |||||
这里'unused field'是DataSet中出现了,但却没有被设置为input或者target的field; | |||||
'unused param'是forward()中返回且没有被使用到的内容;'target field'是被设置为了target的field; | |||||
'param from Model.forward(self, a)'是forward()返回的所有key。"Suggestion"是关于当前错误处理的建议。 | |||||
但是在一些情况下,比如forward()返回值只有一个,target也只有一个,fastNLP不会进行匹配,而直接将forward()的结果作为pred, | |||||
将DataSet中的target设置为target。上面的例子在返回值中加入了一个'No use'则只是为了使得Loss去匹配结果。 | |||||
下面是带有dev dataset时如果出现错误会发生的报错, | |||||
Example2.3 | Example2.3 | ||||
:: | |||||
---------------------------- | |||||
.. code-block:: python | |||||
import numpy as np | |||||
from torch import nn | |||||
from torch.optim import SGD | |||||
from fastNLP import Trainer | |||||
from fastNLP import DataSet | |||||
from fastNLP import AccuracyMetric | |||||
import torch | |||||
class Model(nn.Module): | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.fc = nn.Linear(1, 1) | |||||
def forward(self, a, b): | |||||
loss = torch.mean((self.fc(a.float().unsqueeze(1))-b.float())**2) | |||||
return {'loss': loss} | |||||
def predict(self, a): # 使用predict()进行验证 | |||||
return {'output':self.fc(a.float().unsqueeze(1))} #这里return的值不包含'pred'这个key | |||||
model = Model() | |||||
dataset = DataSet({'a': np.arange(10), 'b':np.arange(10)*2}) | |||||
dev_data = DataSet({'a': np.arange(10, 20), 'b':np.arange(10, 20)*2}) | |||||
dataset.set_input('a', 'b') | |||||
dev_data.set_input('a') # 这里没有设置target | |||||
trainer = Trainer(dataset, model, loss=None, optimizer=SGD(model.parameters(), lr=0.001), | |||||
dev_data=dev_data, metrics=AccuracyMetric()) | |||||
# 报错信息 | |||||
# ... | |||||
# NameError: | |||||
# Problems occurred when calling AccuracyMetric.evaluate(self, pred, target, seq_len=None) | |||||
# missing param: ['pred(assign to `pred` in `AccuracyMetric`)', 'target(assign to `target` in `AccuracyMetric`)'] | |||||
# unused param: ['output'] | |||||
# target field: [] | |||||
# param from Model.predict(self, a): ['output'] | |||||
# Suggestion: (1). Check key assignment for `pred` when initialize AccuracyMetric. Or provide `pred` in DataSet or output of Model.predict(self, a). | |||||
# (2). Check key assignment for `target` when initialize AccuracyMetric. Or provide `target` in DataSet or output of Model.predict(self, a). | |||||
报错信息和前面都是类似的,但是可以通过'AccuracyMetric.evaluate(self, pred, target, seq_len=None)'看出这里是evaluation | |||||
的时候发生了错误。这样避免了需要在完成一整个epoch的训练才能发现evaluation弄错的情况。这里的修改是通过在初始化metric的时候 | |||||
指明通过'output'获取`pred`, 即AccuracyMetric(pred='output')。 | |||||
可以通过check_code_level调节检查的强度。默认为0,即进行检查。 | |||||
---------------------------- | |||||
3. Trainer与callback | |||||
---------------------------- | |||||
虽然Trainer本身已经集成了一些功能,但仍然不足以囊括训练过程中可能需要到的功能,比如负采样,learning rate decay, Early Stop等。 | |||||
为了解决这个问题fastNLP引入了callback的机制,:class:`~fastNLP.Callback` 是一种在Trainer训练过程中特定阶段会运行的函数集合, | |||||
所有的 :class:`~fastNLP.Callback` 都具有on_*(比如on_train_start, on_backward_begin)等函数。 | |||||
如果 Callback 实现了该函数,则Trainer运行至对应阶段,会进行调用,例如:: | |||||
from fastNLP import Callback, EarlyStopCallback, Trainer, CrossEntropyLoss, AccuracyMetric | |||||
from fastNLP.models import CNNText | |||||
start_time = time.time() | |||||
import numpy as np | |||||
from torch import nn | |||||
from torch.optim import SGD | |||||
from fastNLP import Trainer | |||||
from fastNLP import DataSet | |||||
from fastNLP import AccuracyMetric | |||||
import torch | |||||
class Model(nn.Module): | |||||
def __init__(self): | |||||
super().__init__() | |||||
self.fc = nn.Linear(1, 1) | |||||
def forward(self, a, b): | |||||
loss = torch.mean((self.fc(a.float().unsqueeze(1))-b.float())**2) | |||||
return {'loss': loss} | |||||
def predict(self, a): # 使用predict()进行验证 | |||||
return {'output':self.fc(a.float().unsqueeze(1))} #这里return的值不包含'pred'这个key | |||||
model = Model() | |||||
dataset = DataSet({'a': np.arange(10), 'b':np.arange(10)*2}) | |||||
dev_data = DataSet({'a': np.arange(10, 20), 'b':np.arange(10, 20)*2}) | |||||
dataset.set_input('a', 'b') | |||||
dev_data.set_input('a') # 这里没有设置target | |||||
trainer = Trainer(dataset, model, loss=None, optimizer=SGD(model.parameters(), lr=0.001), | |||||
dev_data=dev_data, metrics=AccuracyMetric()) | |||||
# 报错信息 | |||||
# ... | |||||
# NameError: | |||||
# Problems occurred when calling AccuracyMetric.evaluate(self, pred, target, seq_len=None) | |||||
# missing param: ['pred(assign to `pred` in `AccuracyMetric`)', 'target(assign to `target` in `AccuracyMetric`)'] | |||||
# unused param: ['output'] | |||||
# target field: [] | |||||
# param from Model.predict(self, a): ['output'] | |||||
# Suggestion: (1). Check key assignment for `pred` when initialize AccuracyMetric. Or provide `pred` in DataSet or output of Model.predict(self, a). | |||||
# (2). Check key assignment for `target` when initialize AccuracyMetric. Or provide `target` in DataSet or output of Model.predict(self, a). | |||||
报错信息和前面都是类似的,但是可以通过'AccuracyMetric.evaluate(self, pred, target, seq_len=None)'看出这里是evaluation | |||||
的时候发生了错误。这样避免了需要在完成一整个epoch的训练才能发现evaluation弄错的情况。这里的修改是通过在初始化metric的时候 | |||||
指明通过'output'获取`pred`, 即AccuracyMetric(pred='output')。 | |||||
可以通过check_code_level调节检查的强度。默认为0,即进行检查。 | |||||
3 Trainer与callback | |||||
虽然Trainer本身已经集成了一些功能,但仍然不足以囊括训练过程中可能需要到的功能,比如负采样,learning rate decay, Early Stop等。 | |||||
为了解决这个问题fastNLP引入了callback的机制,:class:`~fastNLP.Callback` 是一种在Trainer训练过程中特定阶段会运行的函数集合, | |||||
所有的 :class:`~fastNLP.Callback` 都具有on_*(比如on_train_start, on_backward_begin)等函数。 | |||||
如果 Callback 实现了该函数,则Trainer运行至对应阶段,会进行调用,例如:: | |||||
class MyCallback(Callback): | |||||
def on_epoch_end(self): | |||||
print('{:d}ms\n\n'.format(round((time.time()-start_time)*1000))) | |||||
from fastNLP import Callback, EarlyStopCallback, Trainer, CrossEntropyLoss, AccuracyMetric | |||||
from fastNLP.models import CNNText | |||||
start_time = time.time() | |||||
class MyCallback(Callback): | |||||
def on_epoch_end(self): | |||||
print('{:d}ms\n\n'.format(round((time.time()-start_time)*1000))) | |||||
model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) | |||||
trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, loss=CrossEntropyLoss(), | |||||
metrics=AccuracyMetric(), callbacks=[MyCallback(),EarlyStopCallback(10)]) | |||||
trainer.train() | |||||
这里,我们通过继承 :class:`~fastNLP.Callback` 类定义了自己的 callback 的,并和内置的 :class:`~fastNLP.EarlyStopCallback` | |||||
一起传给了 :class:`~fastNLP.Trainer` ,增强了 :class:`~fastNLP.Trainer` 的功能 | |||||
model = CNNText((len(vocab),50), num_classes=5, padding=2, dropout=0.1) | |||||
trainer = Trainer(model=model, train_data=train_data, dev_data=dev_data, loss=CrossEntropyLoss(), | |||||
metrics=AccuracyMetric(), callbacks=[MyCallback(),EarlyStopCallback(10)]) | |||||
trainer.train() | |||||
fastNLP已经自带了很多callback函数供使用,可以参考 :doc:`fastNLP.core.callback` 。 | |||||
这里,我们通过继承 :class:`~fastNLP.Callback` 类定义了自己的 callback 的,并和内置的 :class:`~fastNLP.EarlyStopCallback` | |||||
一起传给了 :class:`~fastNLP.Trainer` ,增强了 :class:`~fastNLP.Trainer` 的功能 | |||||
fastNLP已经自带了很多callback函数供使用,可以参考 :doc:`fastNLP.core.callback` 。 | |||||
""" | """ | ||||
__all__ = [ | __all__ = [ | ||||
@@ -4,7 +4,6 @@ utils模块实现了 fastNLP 内部和外部所需的很多工具。其中用户 | |||||
__all__ = [ | __all__ = [ | ||||
"cache_results", | "cache_results", | ||||
"seq_len_to_mask", | "seq_len_to_mask", | ||||
"Option", | |||||
] | ] | ||||
import _pickle | import _pickle | ||||
@@ -24,26 +23,27 @@ _CheckRes = namedtuple('_CheckRes', ['missing', 'unused', 'duplicated', 'require | |||||
class Option(dict): | class Option(dict): | ||||
"""a dict can treat keys as attributes""" | """a dict can treat keys as attributes""" | ||||
def __getattr__(self, item): | def __getattr__(self, item): | ||||
try: | try: | ||||
return self.__getitem__(item) | return self.__getitem__(item) | ||||
except KeyError: | except KeyError: | ||||
raise AttributeError(item) | raise AttributeError(item) | ||||
def __setattr__(self, key, value): | def __setattr__(self, key, value): | ||||
if key.startswith('__') and key.endswith('__'): | if key.startswith('__') and key.endswith('__'): | ||||
raise AttributeError(key) | raise AttributeError(key) | ||||
self.__setitem__(key, value) | self.__setitem__(key, value) | ||||
def __delattr__(self, item): | def __delattr__(self, item): | ||||
try: | try: | ||||
self.pop(item) | self.pop(item) | ||||
except KeyError: | except KeyError: | ||||
raise AttributeError(item) | raise AttributeError(item) | ||||
def __getstate__(self): | def __getstate__(self): | ||||
return self | return self | ||||
def __setstate__(self, state): | def __setstate__(self, state): | ||||
self.update(state) | self.update(state) | ||||
@@ -163,6 +163,7 @@ def cache_results(_cache_fp, _refresh=False, _verbose=1): | |||||
return wrapper_ | return wrapper_ | ||||
def _save_model(model, model_name, save_dir, only_param=False): | def _save_model(model, model_name, save_dir, only_param=False): | ||||
""" 存储不含有显卡信息的state_dict或model | """ 存储不含有显卡信息的state_dict或model | ||||
:param model: | :param model: | ||||
@@ -673,7 +674,7 @@ def seq_len_to_mask(seq_len, max_len=None): | |||||
将一个表示sequence length的一维数组转换为二维的mask,不包含的位置为0。 | 将一个表示sequence length的一维数组转换为二维的mask,不包含的位置为0。 | ||||
转变 1-d seq_len到2-d mask. | 转变 1-d seq_len到2-d mask. | ||||
Example:: | |||||
.. code-block:: | |||||
>>> seq_len = torch.arange(2, 16) | >>> seq_len = torch.arange(2, 16) | ||||
>>> mask = seq_len_to_mask(seq_len) | >>> mask = seq_len_to_mask(seq_len) | ||||
@@ -691,7 +692,7 @@ def seq_len_to_mask(seq_len, max_len=None): | |||||
:param np.ndarray,torch.LongTensor seq_len: shape将是(B,) | :param np.ndarray,torch.LongTensor seq_len: shape将是(B,) | ||||
:param int max_len: 将长度pad到这个长度。默认(None)使用的是seq_len中最长的长度。但在nn.DataParallel的场景下可能不同卡的seq_len会有 | :param int max_len: 将长度pad到这个长度。默认(None)使用的是seq_len中最长的长度。但在nn.DataParallel的场景下可能不同卡的seq_len会有 | ||||
区别,所以需要传入一个max_len使得mask的长度是pad到该长度。 | 区别,所以需要传入一个max_len使得mask的长度是pad到该长度。 | ||||
:return: np.ndarray or torch.Tensor, shape将是(B, max_length)。 元素类似为bool或torch.uint8 | |||||
:return: np.ndarray, torch.Tensor 。shape将是(B, max_length), 元素类似为bool或torch.uint8 | |||||
""" | """ | ||||
if isinstance(seq_len, np.ndarray): | if isinstance(seq_len, np.ndarray): | ||||
assert len(np.shape(seq_len)) == 1, f"seq_len can only have one dimension, got {len(np.shape(seq_len))}." | assert len(np.shape(seq_len)) == 1, f"seq_len can only have one dimension, got {len(np.shape(seq_len))}." | ||||
@@ -737,7 +738,8 @@ class _pseudo_tqdm: | |||||
def __exit__(self, exc_type, exc_val, exc_tb): | def __exit__(self, exc_type, exc_val, exc_tb): | ||||
del self | del self | ||||
def iob2(tags:List[str])->List[str]: | |||||
def iob2(tags: List[str]) -> List[str]: | |||||
""" | """ | ||||
检查数据是否是合法的IOB数据,如果是IOB1会被自动转换为IOB2。两者的差异见 | 检查数据是否是合法的IOB数据,如果是IOB1会被自动转换为IOB2。两者的差异见 | ||||
https://datascience.stackexchange.com/questions/37824/difference-between-iob-and-iob2-format | https://datascience.stackexchange.com/questions/37824/difference-between-iob-and-iob2-format | ||||
@@ -760,7 +762,8 @@ def iob2(tags:List[str])->List[str]: | |||||
tags[i] = "B" + tag[1:] | tags[i] = "B" + tag[1:] | ||||
return tags | return tags | ||||
def iob2bioes(tags:List[str])->List[str]: | |||||
def iob2bioes(tags: List[str]) -> List[str]: | |||||
""" | """ | ||||
将iob的tag转换为bioes编码 | 将iob的tag转换为bioes编码 | ||||
:param tags: List[str]. 编码需要是大写的。 | :param tags: List[str]. 编码需要是大写的。 | ||||
@@ -773,15 +776,15 @@ def iob2bioes(tags:List[str])->List[str]: | |||||
else: | else: | ||||
split = tag.split('-')[0] | split = tag.split('-')[0] | ||||
if split == 'B': | if split == 'B': | ||||
if i+1!=len(tags) and tags[i+1].split('-')[0] == 'I': | |||||
if i + 1 != len(tags) and tags[i + 1].split('-')[0] == 'I': | |||||
new_tags.append(tag) | new_tags.append(tag) | ||||
else: | else: | ||||
new_tags.append(tag.replace('B-', 'S-')) | new_tags.append(tag.replace('B-', 'S-')) | ||||
elif split == 'I': | elif split == 'I': | ||||
if i + 1<len(tags) and tags[i+1].split('-')[0] == 'I': | |||||
if i + 1 < len(tags) and tags[i + 1].split('-')[0] == 'I': | |||||
new_tags.append(tag) | new_tags.append(tag) | ||||
else: | else: | ||||
new_tags.append(tag.replace('I-', 'E-')) | new_tags.append(tag.replace('I-', 'E-')) | ||||
else: | else: | ||||
raise TypeError("Invalid IOB format.") | raise TypeError("Invalid IOB format.") | ||||
return new_tags | |||||
return new_tags |
@@ -10,6 +10,7 @@ from .utils import Option | |||||
from functools import partial | from functools import partial | ||||
import numpy as np | import numpy as np | ||||
class VocabularyOption(Option): | class VocabularyOption(Option): | ||||
def __init__(self, | def __init__(self, | ||||
max_size=None, | max_size=None, | ||||
@@ -92,7 +93,7 @@ class Vocabulary(object): | |||||
self.rebuild = True | self.rebuild = True | ||||
# 用于承载不需要单独创建entry的词语,具体见from_dataset()方法 | # 用于承载不需要单独创建entry的词语,具体见from_dataset()方法 | ||||
self._no_create_word = Counter() | self._no_create_word = Counter() | ||||
@_check_build_status | @_check_build_status | ||||
def update(self, word_lst, no_create_entry=False): | def update(self, word_lst, no_create_entry=False): | ||||
"""依次增加序列中词在词典中的出现频率 | """依次增加序列中词在词典中的出现频率 | ||||
@@ -123,7 +124,7 @@ class Vocabulary(object): | |||||
""" | """ | ||||
self._add_no_create_entry(word, no_create_entry) | self._add_no_create_entry(word, no_create_entry) | ||||
self.word_count[word] += 1 | self.word_count[word] += 1 | ||||
def _add_no_create_entry(self, word, no_create_entry): | def _add_no_create_entry(self, word, no_create_entry): | ||||
""" | """ | ||||
在新加入word时,检查_no_create_word的设置。 | 在新加入word时,检查_no_create_word的设置。 | ||||
@@ -139,7 +140,7 @@ class Vocabulary(object): | |||||
self._no_create_word[w] += 1 | self._no_create_word[w] += 1 | ||||
elif not no_create_entry and w in self._no_create_word: | elif not no_create_entry and w in self._no_create_word: | ||||
self._no_create_word.pop(w) | self._no_create_word.pop(w) | ||||
@_check_build_status | @_check_build_status | ||||
def add_word(self, word, no_create_entry=False): | def add_word(self, word, no_create_entry=False): | ||||
""" | """ | ||||
@@ -193,10 +194,10 @@ class Vocabulary(object): | |||||
self.word2idx.update({w: i + start_idx for i, (w, _) in enumerate(words)}) | self.word2idx.update({w: i + start_idx for i, (w, _) in enumerate(words)}) | ||||
self.build_reverse_vocab() | self.build_reverse_vocab() | ||||
self.rebuild = False | self.rebuild = False | ||||
def build_reverse_vocab(self): | def build_reverse_vocab(self): | ||||
""" | """ | ||||
基于 "word to index" dict, 构建 "index to word" dict. | |||||
基于 `word to index` dict, 构建 `index to word` dict. | |||||
""" | """ | ||||
self.idx2word = {i: w for w, i in self.word2idx.items()} | self.idx2word = {i: w for w, i in self.word2idx.items()} | ||||
@@ -250,9 +251,9 @@ class Vocabulary(object): | |||||
# remember to use `field_name` | # remember to use `field_name` | ||||
vocab.index_dataset(train_data, dev_data, test_data, field_name='words') | vocab.index_dataset(train_data, dev_data, test_data, field_name='words') | ||||
:param datasets: 需要转index的 class:`~fastNLP.DataSet` , 支持一个或多个(list) | |||||
:param ~fastNLP.DataSet,List[~fastNLP.DataSet] datasets: 需要转index的一个或多个数据集 | |||||
:param str field_name: 需要转index的field, 若有多个 DataSet, 每个DataSet都必须有此 field. | :param str field_name: 需要转index的field, 若有多个 DataSet, 每个DataSet都必须有此 field. | ||||
目前仅支持 ``str`` , ``list(str)`` , ``list(list(str))`` | |||||
目前仅支持 ``str`` , ``List[str]`` , ``List[List[str]]`` | |||||
:param str new_field_name: 保存结果的field_name. 若为 ``None`` , 将覆盖原field. | :param str new_field_name: 保存结果的field_name. 若为 ``None`` , 将覆盖原field. | ||||
Default: ``None`` | Default: ``None`` | ||||
""" | """ | ||||
@@ -285,11 +286,11 @@ class Vocabulary(object): | |||||
raise e | raise e | ||||
else: | else: | ||||
raise RuntimeError("Only DataSet type is allowed.") | raise RuntimeError("Only DataSet type is allowed.") | ||||
@property | @property | ||||
def _no_create_word_length(self): | def _no_create_word_length(self): | ||||
return len(self._no_create_word) | return len(self._no_create_word) | ||||
def from_dataset(self, *datasets, field_name, no_create_entry_dataset=None): | def from_dataset(self, *datasets, field_name, no_create_entry_dataset=None): | ||||
""" | """ | ||||
使用dataset的对应field中词构建词典:: | 使用dataset的对应field中词构建词典:: | ||||
@@ -297,11 +298,11 @@ class Vocabulary(object): | |||||
# remember to use `field_name` | # remember to use `field_name` | ||||
vocab.from_dataset(train_data1, train_data2, field_name='words') | vocab.from_dataset(train_data1, train_data2, field_name='words') | ||||
:param datasets: 需要转index的 class:`~fastNLP.DataSet` , 支持一个或多个(list) | |||||
:param field_name: 可为 ``str`` 或 ``list(str)`` . | |||||
:param ~fastNLP.DataSet,List[~fastNLP.DataSet] datasets: 需要转index的一个或多个数据集 | |||||
:param str,List[str] field_name: 可为 ``str`` 或 ``List[str]`` . | |||||
构建词典所使用的 field(s), 支持一个或多个field | 构建词典所使用的 field(s), 支持一个或多个field | ||||
若有多个 DataSet, 每个DataSet都必须有这些field. | 若有多个 DataSet, 每个DataSet都必须有这些field. | ||||
目前仅支持的field结构: ``str`` , ``list(str)`` , ``list(list(str))`` | |||||
目前仅支持的field结构: ``str`` , ``List[str]`` , ``list[List[str]]`` | |||||
:param no_create_entry_dataset: 可以传入DataSet, List[DataSet]或者None(默认),该选项用在接下来的模型会使用pretrain | :param no_create_entry_dataset: 可以传入DataSet, List[DataSet]或者None(默认),该选项用在接下来的模型会使用pretrain | ||||
的embedding(包括glove, word2vec, elmo与bert)且会finetune的情况。如果仅使用来自于train的数据建立vocabulary,会导致test与dev | 的embedding(包括glove, word2vec, elmo与bert)且会finetune的情况。如果仅使用来自于train的数据建立vocabulary,会导致test与dev | ||||
中的数据无法充分利用到来自于预训练embedding的信息,所以在建立词表的时候将test与dev考虑进来会使得最终的结果更好。 | 中的数据无法充分利用到来自于预训练embedding的信息,所以在建立词表的时候将test与dev考虑进来会使得最终的结果更好。 | ||||
@@ -331,7 +332,7 @@ class Vocabulary(object): | |||||
for words in field: | for words in field: | ||||
for word in words: | for word in words: | ||||
self.add_word(word, no_create_entry=no_create_entry) | self.add_word(word, no_create_entry=no_create_entry) | ||||
for idx, dataset in enumerate(datasets): | for idx, dataset in enumerate(datasets): | ||||
if isinstance(dataset, DataSet): | if isinstance(dataset, DataSet): | ||||
try: | try: | ||||
@@ -341,7 +342,7 @@ class Vocabulary(object): | |||||
raise e | raise e | ||||
else: | else: | ||||
raise TypeError("Only DataSet type is allowed.") | raise TypeError("Only DataSet type is allowed.") | ||||
if no_create_entry_dataset is not None: | if no_create_entry_dataset is not None: | ||||
partial_construct_vocab = partial(construct_vocab, no_create_entry=True) | partial_construct_vocab = partial(construct_vocab, no_create_entry=True) | ||||
if isinstance(no_create_entry_dataset, DataSet): | if isinstance(no_create_entry_dataset, DataSet): | ||||
@@ -352,7 +353,7 @@ class Vocabulary(object): | |||||
raise TypeError("Only DataSet type is allowed.") | raise TypeError("Only DataSet type is allowed.") | ||||
dataset.apply(partial_construct_vocab) | dataset.apply(partial_construct_vocab) | ||||
return self | return self | ||||
def _is_word_no_create_entry(self, word): | def _is_word_no_create_entry(self, word): | ||||
""" | """ | ||||
判断当前的word是否是不需要创建entry的,具体参见from_dataset的说明 | 判断当前的word是否是不需要创建entry的,具体参见from_dataset的说明 | ||||
@@ -360,11 +361,10 @@ class Vocabulary(object): | |||||
:return: bool | :return: bool | ||||
""" | """ | ||||
return word in self._no_create_word | return word in self._no_create_word | ||||
def to_index(self, w): | def to_index(self, w): | ||||
""" | """ | ||||
将词转为数字. 若词不再词典中被记录, 将视为 unknown, 若 ``unknown=None`` , 将抛出 | |||||
``ValueError``:: | |||||
将词转为数字. 若词不再词典中被记录, 将视为 unknown, 若 ``unknown=None`` , 将抛出``ValueError``:: | |||||
index = vocab.to_index('abc') | index = vocab.to_index('abc') | ||||
# equals to | # equals to | ||||