Browse Source

根据代码修改了文档

tags/v0.4.10
ChenXin 5 years ago
parent
commit
df0bc2aec3
13 changed files with 460 additions and 409 deletions
  1. +1
    -1
      fastNLP/__init__.py
  2. +4
    -4
      fastNLP/core/__init__.py
  3. +2
    -3
      fastNLP/core/batch.py
  4. +7
    -7
      fastNLP/core/callback.py
  5. +103
    -81
      fastNLP/core/dataset.py
  6. +3
    -2
      fastNLP/core/field.py
  7. +7
    -7
      fastNLP/core/losses.py
  8. +8
    -8
      fastNLP/core/metrics.py
  9. +6
    -1
      fastNLP/core/optimizer.py
  10. +4
    -5
      fastNLP/core/tester.py
  11. +282
    -260
      fastNLP/core/trainer.py
  12. +15
    -12
      fastNLP/core/utils.py
  13. +18
    -18
      fastNLP/core/vocabulary.py

+ 1
- 1
fastNLP/__init__.py View File

@@ -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 ,他们的文档如下:
""" """


+ 4
- 4
fastNLP/core/__init__.py View File

@@ -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` 中查看即可。如果想了解各个子模块的具体作用,您可以在下面找到每个子模块的具体文档。




+ 2
- 3
fastNLP/core/batch.py View File

@@ -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




+ 7
- 7
fastNLP/core/callback.py View File

@@ -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`


+ 103
- 81
fastNLP/core/dataset.py View File

@@ -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)


+ 3
- 2
fastNLP/core/field.py View File

@@ -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,


+ 7
- 7
fastNLP/core/losses.py View File

@@ -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'):


+ 8
- 8
fastNLP/core/metrics.py View File

@@ -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,则召回率权重高于精确率。


+ 6
- 1
fastNLP/core/optimizer.py View File

@@ -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:


+ 4
- 5
fastNLP/core/tester.py View File

@@ -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上。




+ 282
- 260
fastNLP/core/trainer.py View File

@@ -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__ = [


+ 15
- 12
fastNLP/core/utils.py View File

@@ -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

+ 18
- 18
fastNLP/core/vocabulary.py View File

@@ -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


Loading…
Cancel
Save