From f83e9aeef8c42cf60574e88b86c1483f0880baa5 Mon Sep 17 00:00:00 2001 From: x54-729 <17307130121@fudan.edu.cn> Date: Wed, 11 May 2022 09:14:34 +0000 Subject: [PATCH 1/4] =?UTF-8?q?transformers=20=E6=94=B9=E4=B8=BA=E5=B0=86?= =?UTF-8?q?=20HfFolder=20=E6=95=B4=E4=B8=AA=E8=BF=81=E7=A7=BB=E8=BF=87?= =?UTF-8?q?=E6=9D=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastNLP/transformers/torch/file_utils.py | 63 +++++++++++++++---- .../torch/models/auto/tokenization_auto.py | 2 +- 2 files changed, 52 insertions(+), 13 deletions(-) diff --git a/fastNLP/transformers/torch/file_utils.py b/fastNLP/transformers/torch/file_utils.py index 60f95fdd..4c7ee7a4 100644 --- a/fastNLP/transformers/torch/file_utils.py +++ b/fastNLP/transformers/torch/file_utils.py @@ -82,6 +82,52 @@ def filelock(path): except: pass +class HfFolder: + """ + hugging_face.HfFolder + version = 0.5.1 + """ + path_token = os.path.expanduser("~/.huggingface/token") + + @classmethod + def save_token(cls, token): + """ + Save token, creating folder as needed. + + Args: + token (`str`): + The token to save to the [`HfFolder`] + """ + os.makedirs(os.path.dirname(cls.path_token), exist_ok=True) + with open(cls.path_token, "w+") as f: + f.write(token) + + @classmethod + def get_token(cls): + """ + Retrieves the token + + Returns: + `str` or `None`: The token, `None` if it doesn't exist. + + """ + try: + with open(cls.path_token, "r") as f: + return f.read() + except FileNotFoundError: + pass + + @classmethod + def delete_token(cls): + """ + Deletes the token from storage. Does not fail if token does not exist. + """ + try: + os.remove(cls.path_token) + except FileNotFoundError: + pass + + def is_offline_mode(): return _is_offline_mode @@ -629,11 +675,10 @@ def get_from_cache( if isinstance(use_auth_token, str): headers["authorization"] = f"Bearer {use_auth_token}" elif use_auth_token: - raise RuntimeError("`use_auth_token=True` is not supported in FastNLP now") - # token = HfFolder.get_token() - # if token is None: - # raise EnvironmentError("You specified use_auth_token=True, but a huggingface token was not found.") - # headers["authorization"] = f"Bearer {token}" + token = HfFolder.get_token() + if token is None: + raise EnvironmentError("You specified use_auth_token=True, but a huggingface token was not found.") + headers["authorization"] = f"Bearer {token}" url_to_download = url etag = None @@ -791,13 +836,7 @@ def get_list_of_files( if isinstance(use_auth_token, str): token = use_auth_token elif use_auth_token is True: - # token = HfFolder.get_token() - path_token = os.path.expanduser("~/.huggingface/token") - try: - with open(path_token, "r") as f: - token = f.read() - except FileNotFoundError: - token = None + token = HfFolder.get_token() else: token = None # model_info = HfApi(endpoint=HUGGINGFACE_CO_RESOLVE_ENDPOINT).model_info( diff --git a/fastNLP/transformers/torch/models/auto/tokenization_auto.py b/fastNLP/transformers/torch/models/auto/tokenization_auto.py index e275579f..f1618d6a 100644 --- a/fastNLP/transformers/torch/models/auto/tokenization_auto.py +++ b/fastNLP/transformers/torch/models/auto/tokenization_auto.py @@ -15,7 +15,7 @@ """ Auto Tokenizer class. """ from collections import OrderedDict -from typing import TYPE_CHECKING, Dict, Optional, Tuple, Union +from typing import TYPE_CHECKING, Optional, Tuple from ...file_utils import ( is_sentencepiece_available, From 84d862c13176395fb6f799bf707abeb66d1524f1 Mon Sep 17 00:00:00 2001 From: x54-729 <17307130121@fudan.edu.cn> Date: Wed, 11 May 2022 10:48:37 +0000 Subject: [PATCH 2/4] =?UTF-8?q?=E5=AE=8C=E5=96=84paddle=5Fdriver=E7=9A=84?= =?UTF-8?q?=E9=83=A8=E5=88=86=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../drivers/jittor_driver/jittor_driver.py | 14 +-- fastNLP/core/drivers/jittor_driver/mpi.py | 4 + fastNLP/core/drivers/jittor_driver/utils.py | 2 +- .../core/drivers/paddle_driver/dist_utils.py | 2 + .../drivers/paddle_driver/paddle_driver.py | 117 +++++++++++------- .../drivers/paddle_driver/single_device.py | 34 +++-- fastNLP/core/drivers/paddle_driver/utils.py | 26 ++-- 7 files changed, 121 insertions(+), 78 deletions(-) diff --git a/fastNLP/core/drivers/jittor_driver/jittor_driver.py b/fastNLP/core/drivers/jittor_driver/jittor_driver.py index 7efff348..25fc4af6 100644 --- a/fastNLP/core/drivers/jittor_driver/jittor_driver.py +++ b/fastNLP/core/drivers/jittor_driver/jittor_driver.py @@ -21,6 +21,9 @@ if _NEED_IMPORT_JITTOR: 'sum': jt.sum } +__all__ = [ + "JittorDriver", +] class JittorDriver(Driver): r""" @@ -90,9 +93,6 @@ class JittorDriver(Driver): "'test_step'.") def save_model(self, filepath: str, only_state_dict: bool = False, model_save_fn: Optional[Callable]=None): - """ - 保存模型 - """ if model_save_fn is not None: outputs = model_save_fn(filepath) if outputs is not None: @@ -105,12 +105,6 @@ class JittorDriver(Driver): jt.save(states, filepath) def load_model(self, filepath: str): - """ - 加载模型的加载函数; - - :param file_path: 保存文件的文件位置(需要包括文件名); - :return: 加载后的state_dict - """ if not os.path.exists(filepath): raise FileNotFoundError("Checkpoint at {} not found.".format(filepath)) return jt.load(filepath) @@ -156,7 +150,7 @@ class JittorDriver(Driver): def move_data_to_device(self, batch: 'jt.Var'): """ - jittor暂时没有提供数据迁移的函数,因此这个函数只是简单地返回batch + **jittor** 暂时没有提供数据迁移的函数,因此这个函数只是简单地返回 **batch** """ return batch diff --git a/fastNLP/core/drivers/jittor_driver/mpi.py b/fastNLP/core/drivers/jittor_driver/mpi.py index bfa49e68..4ade3fd1 100644 --- a/fastNLP/core/drivers/jittor_driver/mpi.py +++ b/fastNLP/core/drivers/jittor_driver/mpi.py @@ -20,6 +20,10 @@ class JittorMPIDriver(JittorDriver): 这是一个正在开发中的功能,敬请期待。 + .. todo: + + 实现断点重训中替换 dataloader 的 set_dist_repro_dataloader 函数 + """ def __init__( self, diff --git a/fastNLP/core/drivers/jittor_driver/utils.py b/fastNLP/core/drivers/jittor_driver/utils.py index 43be9ac3..c6d44cfc 100644 --- a/fastNLP/core/drivers/jittor_driver/utils.py +++ b/fastNLP/core/drivers/jittor_driver/utils.py @@ -9,7 +9,7 @@ __all__ = [] class DummyGradScaler: """ - 用于仿造的GradScaler对象,防止重复写大量的if判断 + 用于仿造的 **GradScaler** 对象,防止重复写大量的if判断 """ def __init__(self, *args, **kwargs): pass diff --git a/fastNLP/core/drivers/paddle_driver/dist_utils.py b/fastNLP/core/drivers/paddle_driver/dist_utils.py index ffa142d3..4dea268d 100644 --- a/fastNLP/core/drivers/paddle_driver/dist_utils.py +++ b/fastNLP/core/drivers/paddle_driver/dist_utils.py @@ -21,6 +21,8 @@ if _NEED_IMPORT_PADDLE: _parse_load_result, ) +__all__ = [] + def _validate_output_list_for_rank(my_rank, dst, gather_list): if dst == my_rank: if not gather_list: diff --git a/fastNLP/core/drivers/paddle_driver/paddle_driver.py b/fastNLP/core/drivers/paddle_driver/paddle_driver.py index 74c7b7a8..a228a90d 100644 --- a/fastNLP/core/drivers/paddle_driver/paddle_driver.py +++ b/fastNLP/core/drivers/paddle_driver/paddle_driver.py @@ -1,14 +1,12 @@ import os import random -from typing import Union, Optional, Dict +from typing import Union, Optional, Dict, Any from pathlib import Path from functools import partial from dataclasses import dataclass import numpy as np -from fastNLP.envs.env import USER_CUDA_VISIBLE_DEVICES - from .utils import _build_fp16_env, optimizer_state_to_device, DummyGradScaler from fastNLP.envs.imports import _NEED_IMPORT_PADDLE from fastNLP.core.drivers.driver import Driver @@ -50,9 +48,26 @@ if _NEED_IMPORT_PADDLE: class PaddleDriver(Driver): r""" - Paddle框架的Driver,包括实现单卡训练的 `PaddleSingleDriver` 和分布式训练的 `PaddleFleetDriver`。 + 实现了 **PaddlePaddle** 框架训练功能的基本 Driver,实现了单卡和多卡情景下均需要实现的功能,以和 **fastNLP** 的 + :class:`~fastNLP.core.Trainer` 兼容;通过这个 Driver,可以在 **fastNLP** 中实现从 **Pytorch** 框架到 + **PaddlePaddle** 深度学习框架的切换。 + + 这个类被以下子类继承: + + 1. :class:`~fastNLP.core.drivers.PaddleSingleDriver`:实现了使用单卡和 ``cpu`` 训练的具体功能; + 2. :class:`~fastNLP.core.drivers.PaddleFleetDriver`:实现了使用 ``fleet`` 分布式训练 API 进行分布式训练的具体功能; + + :param model: 训练时使用的 **PaddlePaddle** 模型; + :param fp16: 是否开启混合精度训练; + :kwargs: + * wo_auto_param_call (``bool``) -- 是否关闭在训练时调用我们的 ``auto_param_call`` 函数来自动匹配 batch 和前向函数的参数的行为; + + .. note:: + + 关于该参数的详细说明,请参见 :class:`~fastNLP.core.controllers.Trainer` 中的描述;函数 ``auto_param_call`` 详见 :func:`fastNLP.core.utils.auto_param_call`。 + """ - def __init__(self, model, fp16: Optional[bool] = False, **kwargs): + def __init__(self, model: "paddle.nn.Layer", fp16: Optional[bool] = False, **kwargs): if not isinstance(model, paddle.nn.Layer): raise ValueError(f"Parameter `model` can not be `{type(model)}` in `PaddleDriver`, it should be exactly " f"`paddle.nn.Layer` type.") @@ -69,10 +84,10 @@ class PaddleDriver(Driver): def zero_grad(self, set_to_none: bool = False): r""" - 实现深度学习中的梯度的置零操作,应当直接通过优化器 optimizers 来将梯度置零; - 注意梯度累积不需要在这里实现,trainer 已经在内部实现了梯度累积; + 实现深度学习中的梯度的置零操作,应当直接通过优化器 ``optimizers`` 来将梯度置零; + 注意梯度累积不需要在这里实现,:class:`~fastNLP.core.Trainer` 已经在内部实现了梯度累积; - :param set_to_none: 用来判断是否需要将梯度直接置为 None;Paddle中这个参数无效。 + :param set_to_none: 用来判断是否需要将梯度直接置为 ``None``;在 **PaddlePaddle** 中这个参数无效。 """ for optimizer in self.optimizers: optimizer.clear_grad() @@ -87,14 +102,6 @@ class PaddleDriver(Driver): @staticmethod def check_dataloader_legality(dataloader, dataloader_name, is_train: bool = False): - r""" - 该函数会在 trainer 或者 evaluator 设置 dataloader 后检测 dataloader 的合法性。 - 要求传入的 dataloader 必须为 `paddle.io.DataLoader` 或包含该类型的字典。 - - :param dataloader: 需要检测的输入的 `dataloader`; - :param dataloader_name: - :param is_train: - """ if is_train: if not isinstance(dataloader, DataLoader): raise ValueError(f"Parameter `{dataloader_name}` should be 'paddle.io.DataLoader' type, not {type(dataloader)}.") @@ -164,16 +171,15 @@ class PaddleDriver(Driver): @rank_zero_call def save_model(self, filepath: str, only_state_dict: bool = True, **kwargs): r""" - 保存模型的函数;注意函数 `save` 是用来进行断点重训的函数; + 将模型保存到 ``filepath`` 中。 :param filepath: 保存文件的文件位置(需要包括文件名); - :param only_state_dict: 是否只保存模型的 `state_dict`;如果为 False,则会调用 `paddle.jit.save` 函数 - 保存整个模型的参数,此时需要传入 `input_spec` 参数,否则在 load 时会报错。 - :param kwargs: - input_spec: 描述存储模型 forward 方法的输入,当 `only_state_dict` 为 False时必须传入,否则加载时会报错。 - 可以通过 InputSpec 或者示例 Tensor 进行描述。详细的可以参考 paddle 关于`paddle.jit.save` - 的文档: - https://www.paddlepaddle.org.cn/documentation/docs/zh/api/paddle/jit/save_cn.html#save + :param only_state_dict: 是否只保存模型的 ``state_dict``;如果为 ``False``,则会调用 ``paddle.jit.save`` 函数 + 保存整个模型的参数,此时需要传入 ``input_spec`` 参数; + :kwargs: + * input_spec -- 描述存储模型 ``forward`` 方法的输入; + 当 ``only_state_dict`` 为 ``False`` 时必须传入,否则加载时会报错。您可以通过 ``InputSpec`` 或者示例 ``Tensor`` + 进行描述。详细的使用方法可以参考 **PaddlePaddle** `关于 paddle.jit.save 函数的文档 `_; """ model = self.unwrap_model() if isinstance(filepath, Path): @@ -189,14 +195,6 @@ class PaddleDriver(Driver): paddle.jit.save(model, filepath, input_spec) def load_model(self, filepath: str, only_state_dict: bool = True, **kwargs): - r""" - 加载模型的函数;将 filepath 中的模型加载并赋值给当前 model 。 - - :param filepath: 需要被加载的对象的文件位置(需要包括文件名); - :param load_state_dict: 保存的文件是否只是模型的权重,还是完整的模型。即便是保存的完整的模型,此处也只能使用尝试加载filepath - 模型中的权重到自身模型,而不会直接替代当前 Driver 中的模型。 - :return: 返回加载指定文件后的结果; - """ model = self.unwrap_model() if isinstance(filepath, Path): filepath = str(filepath) @@ -210,6 +208,28 @@ class PaddleDriver(Driver): @rank_zero_call def save(self, folder: Path, states: Dict, dataloader, only_state_dict: bool = True, should_save_model: bool = True, **kwargs): + r""" + 断点重训的保存函数,该函数会负责保存模型和 optimizers, fp16 的 state_dict;以及模型的保存(若 should_save_model 为 True) + + :param folder: 保存断点重训的状态的文件夹;save 函数应该在下面新增两(一)个文件 的 FASTNLP_CHECKPOINT_FILENAME 文件与 + FASTNLP_MODEL_FILENAME (如果 should_save_model 为 True )。把 model 相关的内容放入到 FASTNLP_MODEL_FILENAME 文件 + 中,将传入的 states 以及自身产生其它状态一并保存在 FASTNLP_CHECKPOINT_FILENAME 里面。 + :param states: 由 trainer 传入的一个字典,其中已经包含了为了实现断点重训所需要保存的其它对象的状态,Driver 应该只需要保存 + 该对象即可, Driver 应该不需要理解该对象,同时在 driver.load() 的时候,需要将 states 返回回去,load() 返回的值与这里的 + 传入的值保持一致。 + :param dataloader: 正在使用的 dataloader,需要保存里面的状态使得之后可以从当前迭代的位置恢复。 + :param only_state_dict: 是否只保存模型的参数,当 should_save_model 为 False ,该参数无效。 + :param should_save_model: 是否应该保存模型,如果为False,Driver 将不负责 model 的保存。 + :kwargs: + * input_spec -- 描述存储模型 ``forward`` 方法的输入; + 当 ``only_state_dict`` 为 ``False`` 时必须传入,否则加载时会报错。您可以通过 ``InputSpec`` 或者示例 ``Tensor`` + 进行描述。详细的使用方法可以参考 **PaddlePaddle** `关于 paddle.jit.save 函数的文档 `_; + + .. todo: + + 等 Driver 的文档写完 + + """ # 传入的 dataloader 参数是 trainer 的 dataloader 属性,因为 driver 的所有 dataloader 我们是不会去改变它的,而是通过改变 # trainer.dataloader 来改变 dataloader 的状态,从而适配训练或者评测环境; @@ -352,37 +372,41 @@ class PaddleDriver(Driver): r""" 返回一个不计算梯度的环境用来对模型进行评测; - :return: context 上下文对象 `paddle.no_grad`; + :return: 上下文对象 ``paddle.no_grad``; """ return paddle.no_grad @staticmethod def move_model_to_device(model: "paddle.nn.Layer", device: Union[str, int, "paddle.CUDAPlace", "paddle.CPUPlace"]): r""" - 用来将模型转移到指定的 device 上; - 在 Paddle 中使用可能会引起因与设置的设备不一致而产生的问题,请注意。 + 用来将模型 ``model`` 转移到指定的设备上; + + .. note:: + + 在 **Paddle** 中使用可能会引起因与设置的设备不一致而产生的问题,请注意。 + + :param model: 需要进行转移的模型; + :param device: 目标设备; """ if device is not None: model.to(device) - def move_data_to_device(self, batch: "paddle.Tensor"): + def move_data_to_device(self, batch: Any) -> Any: r""" - 将数据迁移到指定的机器上;batch 可能是 list 也可能 dict ,或其嵌套结构。 - 在 Paddle 中使用可能会引起因与设置的设备不一致而产生的问题,请注意。 + 将数据集合 ``batch`` 迁移到指定的机器上。 + + .. note:: + + 在 **Paddle** 中使用可能会引起因与设置的设备不一致而产生的问题,请注意。 - :return: 将移动到指定机器上的 batch 对象返回; + :param batch: 包含 :class:`paddle.Tensor` 的数据集合,可以是 **List**、**Dict** 等嵌套类型; + :return: 移动到指定机器后的 `batch``; """ device = _convert_data_device(self.data_device) return paddle_move_data_to_device(batch, device) @staticmethod def worker_init_function(worker_id: int, rank: Optional[int] = None) -> None: # pragma: no cover - """The worker_init_fn that Lightning automatically adds to your dataloader if you previously set set the seed - with ``seed_everything(seed, workers=True)``. - - See also the PyTorch documentation on - `randomness in DataLoaders `_. - """ # implementation notes: https://github.com/pytorch/pytorch/issues/5059#issuecomment-817392562 global_rank = rank if rank is not None else int(os.environ.get(FASTNLP_GLOBAL_RANK, 0)) # TODO gpu @@ -409,9 +433,6 @@ class PaddleDriver(Driver): @staticmethod def get_dataloader_args(dataloader: "DataLoader"): - """ - 获取 dataloader 的 shuffle 和 drop_last 属性; - """ @dataclass class Res: diff --git a/fastNLP/core/drivers/paddle_driver/single_device.py b/fastNLP/core/drivers/paddle_driver/single_device.py index c0957dbf..a9e92fd3 100644 --- a/fastNLP/core/drivers/paddle_driver/single_device.py +++ b/fastNLP/core/drivers/paddle_driver/single_device.py @@ -33,9 +33,20 @@ __all__ = [ class PaddleSingleDriver(PaddleDriver): """ - 支持 paddle cpu 或单卡 gpu 训练的 driver + 实现了 **PaddlePaddle** 框架下在单卡或 ``cpu`` 环境下训练功能的 **Driver**。 + + :param model: 训练时使用的 **PaddlePaddle** 模型; + :param device: 训练使用的设备; + :param fp16: 是否开启混合精度训练; + :kwargs: + * wo_auto_param_call (``bool``) -- 是否关闭在训练时调用我们的 ``auto_param_call`` 函数来自动匹配 batch 和前向函数的参数的行为; + + .. note:: + + 关于该参数的详细说明,请参见 :class:`~fastNLP.core.controllers.Trainer` 中的描述;函数 ``auto_param_call`` 详见 :func:`fastNLP.core.utils.auto_param_call`。 + """ - def __init__(self, model, device: Union[str, int], fp16: Optional[bool] = False, **kwargs): + def __init__(self, model: "paddle.nn.Layer", device: Union[str, int], fp16: Optional[bool] = False, **kwargs): if isinstance(model, DataParallel): raise ValueError("`paddle.DataParallel` is not supported in `PaddleSingleDriver`") @@ -62,7 +73,7 @@ class PaddleSingleDriver(PaddleDriver): def setup(self): r""" - 该函数用来初始化训练环境,用于设置当前训练的设备,并将模型迁移到对应设备上。 + 初始化训练环境;设置当前训练的设备,并将模型迁移到对应设备上。 """ device = _convert_data_device(self.data_device) @@ -127,17 +138,20 @@ class PaddleSingleDriver(PaddleDriver): return dataloader def unwrap_model(self): - if isinstance(self.model, paddle.DataParallel): - return self.model._layers - else: - return self.model + """ + 返回训练使用的模型。 + """ + return self.model @property - def data_device(self): + def data_device(self) -> str: """ - 返回数据所在的设备。由于单卡模式不支持 data_device,因此返回的是 model_device + :return: 数据和模型所在的设备; """ return self.model_device - def is_distributed(self): + def is_distributed(self) -> bool: + """ + 判断是否为分布式的 **Driver** ,在 ``PaddleSingleDriver`` 中,返回 ``False`` + """ return False diff --git a/fastNLP/core/drivers/paddle_driver/utils.py b/fastNLP/core/drivers/paddle_driver/utils.py index 6362193e..1a324c97 100644 --- a/fastNLP/core/drivers/paddle_driver/utils.py +++ b/fastNLP/core/drivers/paddle_driver/utils.py @@ -31,7 +31,15 @@ __all__ = [ def _select_seed_randomly(min_seed_value: int = 0, max_seed_value: int = 255) -> int: return random.randint(min_seed_value, max_seed_value) -def paddle_seed_everything(seed: Optional[int] = None, workers: bool = False) -> int: +def paddle_seed_everything(seed: Optional[int], workers: bool = False) -> int: + r""" + 为 **paddle**、**numpy**、**python.random** 伪随机数生成器设置种子。 + + :param seed: 全局随机状态的整数值种子。如果为 ``None``,将从环境变量 ``FASTNLP_GLOBAL_SEED`` 中读取种子或随机选择; + :param workers: 如果为 ``True`` ,则会设置环境变量 ``FASTNLP_SEED_WORKERS`` 。该环境变量会在 :class:`~fastNLP.core.Trainer` + 中配置 ``dataloader`` 时用于设置 ``worker_init_fn`` 。如果用户已经为 ``dataloader`` 提供了 ``worker_init_fn`` ,则设置 + 此参数将没有影响; + """ max_seed_value = np.iinfo(np.uint32).max min_seed_value = np.iinfo(np.uint32).min @@ -70,7 +78,7 @@ def paddle_seed_everything(seed: Optional[int] = None, workers: bool = False) -> def reset_seed() -> None: """ - fleet 会开启多个进程,因此当用户在脚本中指定 seed_everything 时,在开启多个脚本后,会在每个脚本内重新 + ``fleet`` 会开启多个进程,因此当用户在脚本中指定 ``seed_everything`` 时,在开启多个脚本后,会在每个脚本内重新 进行随机数的设置; """ seed = os.environ.get(FASTNLP_GLOBAL_SEED, None) @@ -80,8 +88,8 @@ def reset_seed() -> None: class _FleetWrappingModel(Layer): """ - 参考 _DDPWrappingModel , paddle 的分布式训练也需要用 paddle.nn.DataParallel 进行包装,采用和 - pytorch 相似的处理方式 + 参考 :class:`fastNLP.core.drivers.torch_driver.utils._DDPWrappingModel` , **PaddlePaddle** 的分布式训练也需要用 :class:`paddle.nn.DataParallel` 进行包装,采用和 + **pytorch** 相似的处理方式 """ def __init__(self, model: 'nn.Layer'): super(_FleetWrappingModel, self).__init__() @@ -100,7 +108,7 @@ class _FleetWrappingModel(Layer): class DummyGradScaler: """ - 用于仿造的GradScaler对象,防止重复写大量的if判断 + 用于仿造的 **GradScaler** 对象,防止重复写大量的if判断 """ def __init__(self, *args, **kwargs): pass @@ -144,7 +152,7 @@ def _build_fp16_env(dummy=False): def find_free_ports(num): """ - 在空闲的端口中找到 num 个端口 + 在空闲的端口中找到 ``num`` 个端口 """ def __free_port(): with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: @@ -174,8 +182,8 @@ def find_free_ports(num): def replace_batch_sampler(dataloader: "DataLoader", batch_sampler: "BatchSampler"): """ - 利用 `batch_sampler` 重新构建一个 DataLoader,起到替换 `batch_sampler` 又不影响原 `dataloader` 的作用。 - 考虑了用户自己定制了 DataLoader 的情形。 + 利用 ``batch_sampler`` 重新构建一个 ``DataLoader``,起到替换 ``batch_sampler`` 又不影响原 ``dataloader`` 的作用。 + 考虑了用户自己定制了 ``DataLoader`` 的情形。 """ # 拿到非下划线开头的实例属性; instance_attrs = {k: v for k, v in vars(dataloader).items() if not k.startswith('_')} @@ -246,7 +254,7 @@ def replace_batch_sampler(dataloader: "DataLoader", batch_sampler: "BatchSampler def replace_sampler(dataloader, new_sampler): """ - 使用 `new_sampler` 重新构建一个 BatchSampler,并替换到 `dataloader` 中 + 使用 ``new_sampler`` 重新构建一个 ``BatchSampler``,并替换到 ``dataloader`` 中 """ new_batch_sampler = deepcopy(dataloader.batch_sampler) new_batch_sampler.sampler = new_sampler From 6cbb5ceec41e17424f08b137dee9c8996455dfde Mon Sep 17 00:00:00 2001 From: x54-729 <17307130121@fudan.edu.cn> Date: Wed, 11 May 2022 15:43:45 +0000 Subject: [PATCH 3/4] =?UTF-8?q?PaddleFleetDriver=E7=9A=84=E6=96=87?= =?UTF-8?q?=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastNLP/core/controllers/trainer.py | 2 +- fastNLP/core/drivers/paddle_driver/fleet.py | 281 ++++++++---------- .../drivers/paddle_driver/paddle_driver.py | 15 +- .../drivers/paddle_driver/single_device.py | 2 +- fastNLP/core/utils/jittor_utils.py | 4 +- fastNLP/core/utils/paddle_utils.py | 8 +- fastNLP/core/utils/rich_progress.py | 4 +- fastNLP/core/utils/utils.py | 16 +- fastNLP/modules/mix_modules/utils.py | 6 +- 9 files changed, 156 insertions(+), 182 deletions(-) diff --git a/fastNLP/core/controllers/trainer.py b/fastNLP/core/controllers/trainer.py index d64a39fe..dada3e68 100644 --- a/fastNLP/core/controllers/trainer.py +++ b/fastNLP/core/controllers/trainer.py @@ -264,7 +264,7 @@ class Trainer(TrainerEventTrigger): * fleet_kwargs -- 用于在使用 ``PaddleFleetDriver`` 时指定 ``DataParallel`` 和 ``fleet`` 初始化时的参数,包括: - * is_collective -- 是否使用 paddle 集群式的分布式训练方法,目前仅支持为 True 的情况; + * is_collective -- 是否使用 paddle 集群式的分布式训练方法,目前仅支持为 ``True`` 的情况; * role_maker -- 初始化 ``fleet`` 分布式训练 API 时使用的 ``RoleMaker`` * 其它用于初始化 ``DataParallel`` 的参数; * *data_device* -- 一个具体的 driver 实例中,有 ``model_device`` 和 ``data_device``,前者表示模型所在的设备,后者表示 diff --git a/fastNLP/core/drivers/paddle_driver/fleet.py b/fastNLP/core/drivers/paddle_driver/fleet.py index 03dc3375..36fb74fd 100644 --- a/fastNLP/core/drivers/paddle_driver/fleet.py +++ b/fastNLP/core/drivers/paddle_driver/fleet.py @@ -1,3 +1,69 @@ +r""" +用于实现 **PaddlePaddle** 框架下使用 ``fleet`` 分布式训练 API 进行集群式(*collective*)多卡训练的 Driver。 + +.. note:: + + 在 **PaddlePaddle** 框架中,使用分布式训练的方式可以参见 **PaddlePaddle** 的 + `官方文档 `_ 。 + 简言之,分布式训练的过程可以概括为:导入 ``fleet`` 包 -> 使用 :func:`fleet.init` 初始化分布式环境 -> 初始化模型,转换为并行模型开始训练。 + +**fastNLP** 支持三种启动分布式训练的方式(假设执行训练的文件名为 ``train.py``): + + A. 用户自己不进行分布式的任何操作,直接使用我们的 :class:`~fastNLP.core.Trainer` 进行训练,此时将参数 ``device`` + 设置为一个列表,然后使用 ``python train.py`` 的方式开始训练; + B. 用户自己不进行分布式的任何操作,但是使用 ``python -m paddle.distributed.launch train.py`` 开始训练; + C. 用户自己在外面初始化分布式环境,并且通过 ``python -m paddle.distributed.launch train.py`` 开始训练; + +.. note:: + + 在后两种启动方式中,您需要通过参数 ``--gpus`` 来指定训练使用的设备,在 ``trainer`` 中设置的参数是无效的。 + +不过在使用该 Driver 之前,我们需要向您说明 **fastNLP** 实现 ``PaddleFleetDriver`` 的思路,以便于您理解代码编写过程中可能出现的问题。 + +在 **fastNLP** 中,为了尽可能减少单卡向分布式训练转换过程中的代码变动,我们需要在 ``PaddleFleetDriver`` 中进行 **分布式环境初始化** +和 **将模型转换为并行模式** 等操作,同时实现多卡训练的方法是从主进程(``rank=0``)中创建其它的所有子进程(``rank=1,2,...``)。 +在这个过程中,我们发现由于 **PaddlePaddle** 框架的特性,会出现下面的问题: + + 1. **fastNLP** 中,初始化模型一定会在初始化 ``Driver`` 之前,因此调用 :func:`fleet.init` 的时机会在初始化模型之后; + 此时子进程中模型将无法正常地初始化,提示无法找到设备 ``gpu:0``; + 2. 在训练的过程中,会出现训练一个 ``batch`` 后程序卡住或程序会占用所有可见显卡的情况; + +考虑到这些问题,我们为 **PaddlePaddle** 的分布式训练制定了这样的约束:在导入 **fastNLP** 之前,必须设置环境变量 ``FASTNLP_BACKEND`` +为 ``paddle``。执行方法有两种:: + + >>> import os + >>> os.environ["FASTNLP_BACKEND"] = "paddle" # 设置环境变量 + >>> import fastNLP # 设置之后才可以导入 fastNLP + +或是在执行脚本(假设文件名为 ``train.py`` )时设置:: + + FASTNLP_BACKEND=paddle python train.py + FASTNLP_BACKEND=paddle python -m paddle.distributed.lauch train.py + +设置 ``FASTNLP_BACKEND=paddle`` 后,**fastNLP** 会在 ``import paddle`` 之前通过 ``CUDA_VISIBLE_DEVICES`` 将设备限制在所有可见设备的第 +**0** 张卡上,以此绕开通信和同步上的种种限制。我们会将用户希望可见的设备(如用户自己设置了 ``CUDA_VISIBLE_DEVICES`` 的情况)保存在另一个环境变量 +``USER_CUDA_VISIBLE_DEVICES`` 中来确保 **fastNLP** 能够知道用户的设置。假设用户希望在 ``[0,2,3]`` 三张显卡上进行分布式训练,那么在三个训练进程中, +``CUDA_VISIBLE_DEVICES`` 就分别为 0、2 和 3 。 + +.. note:: + + 我们会事先将设备限制在所有可见设备的第 **0** 张卡上,因此多卡训练的参数 ``device`` 一定要以 **0** 开始,否则会无法正常地启动。 + 如果您希望调整使用的第一张显卡,请使用 ``CUDA_VISIBLE_DEVICES`` 进行限制。 + +.. note:: + + 根据 **PaddlePaddle** 的说明,设置 ``CUDA_VISIBLE_DEVICES`` 之后启动分布式训练时,情况A与情况BC设置设备的方式会有所不同。 + 情况A应设置为实际设备相对可见设备的索引,而情况BC应设置为实际的设备号: + + 1. 情况A中, ``CUDA_VISIBLE_DEVICES=3,4,5,6`` 且参数 ``device=[0,2,3]`` 代表使用 **3号、5号和6号** 显卡; + 2. 情况BC中,``CUDA_VISIBLE_DEVICES=3,4,5,6`` 且参数 ``--gpu=3,5,6`` 代表使用 **3号、5号和6号** 显卡; + +.. note:: + + 多机的启动强制要求用户在每一台机器上使用 ``python -m paddle.distributed.launch`` 启动;因此我们不会在 ``PaddleFleetDriver`` + 中保存任何当前有多少台机器的信息; + +""" import os from typing import List, Union, Optional, Dict, Tuple, Callable @@ -53,6 +119,33 @@ __all__ = [ ] class PaddleFleetDriver(PaddleDriver): + """ + :param model: 训练使用的模型; + + * 如果不想自己初始化分布式环境,类型应为 :class:`paddle.nn.Layer`; + * 如果已经在外面初始化了分布式环境,类型应为 :class:`paddle.DataParallel`; + + :param parallel_device: 多卡训练时使用的设备,必须是一个列表。 + 当使用 ``python -m paddle.distributed.launch`` 启动时,该参数无效; + :param is_pull_by_paddle_run: 标记当前进程是否为通过 ``python -m paddle.distributed.launch`` 启动的。 + 这个参数仅在 :class:`~fastNLP.core.Trainer` 中初始化 driver 时使用 + :param fp16: 是否开启混合精度训练; + :kwargs: + * *paddle_kwargs* -- 用于在指定 ``driver`` 为 'paddle' 时设定具体 driver 实例的一些参数: + + * fleet_kwargs -- 用于在使用 ``PaddleFleetDriver`` 时指定 ``DataParallel`` 和 ``fleet`` 初始化时的参数,包括: + + * is_collective -- 是否使用 paddle 集群式的分布式训练方法,目前仅支持为 ``True`` 的情况; + * role_maker -- 初始化 ``fleet`` 分布式训练 API 时使用的 ``RoleMaker`` + * 其它用于初始化 ``DataParallel`` 的参数; + + * wo_auto_param_call (``bool``) -- 是否关闭在训练时调用我们的 ``auto_param_call`` 函数来自动匹配 batch 和前向函数的参数的行为; + + .. note:: + + 关于该参数的详细说明,请参见 :class:`~fastNLP.core.controllers.Trainer` 中的描述;函数 ``auto_param_call`` 详见 :func:`fastNLP.core.utils.auto_param_call`。 + + """ def __init__( self, model, @@ -61,143 +154,20 @@ class PaddleFleetDriver(PaddleDriver): fp16: bool = False, **kwargs ): - r""" - 通过使用 PaddlePaddle 的 Fleet 框架启动多卡进程的 Driver。 - 需要注意的一点是,由于 PaddlePaddle 框架的特性,如果直接使用在 rank0 拉起其它进程的方法的话,如果不加以任何限制,PaddlePaddle会出现 - 第一次前向传播后卡住或占用所有显卡的现象;为了解决这一问题,我们在引入 FastNLP 时,会使用 `CUDA_VISIBLE_DEVICES` 将设备限制在卡0上, - 而用户如果使用了这一环境变量,我们会将其储存在 `USER_CUDA_VISIBLE_DEVICES` 中,并且通过一定的手段实现了转换(详细的设置请参见: - `fastNLP/envs/set_backend.py`)。在拉起其它进程的时候,我们会如法炮制,将环境限制在对应的设备上。 - - `PaddleFleetDriver` 目前支持的三种启动方式: - 1. 用户自己不进行分布式的任何操作,直接使用我们的 Trainer,这时是由我们自己使用 `FleetLauncher` 拉起多个进程, - 然后 `PaddleFleetDriver` 自己通过调用 `fleet.init` 来初始化 ddp 的通信组;(情况 A) - 2. 用户同样不在 Trainer 之外初始化分布式训练,但是用户自己使用 python -m paddle.distributed.launch 拉起来创建多个进程,这时我们仍旧 - 会通过调用 `fleet.init` 来初始化 ddp 的通信组;(情况 B) - 3. 用户自己在外面初始化分布式,并且通过 python -m paddle.distributed.launch 拉起,这时无论是多个进程的拉起和通信组的建立 - 都由用户自己操作,我们只会在 driver.setup 的时候对 `PaddleFleetDriver` 设置一些必要的属性值;(情况 C) - - 注意多机的启动强制要求用户在每一台机器上使用 python -m paddle.distributed.launch 启动;因此我们不会在 `PaddleFleetDriver` 中保存 - 任何当前有多少台机器的信息; - - Part 1:三种启动方式的具体分析: - (1)对于用户运行的脚本中,如果 `driver.setup` 只会被调用一次(意味着用户的启动脚本中只初始化了一个 trainer/evaluator)时, - `PaddleFleetDriver` 在初始化以及 `setup` 函数中会做的事情分别如下所示: - -> 情况 A:这种情况下用户传入的 model 在一定是普通的 model(没有经 `DataParallel` 包裹的model), - 因为 `Parallel` 的使用一定要求 fleet.init 已经被调用用来建立当前的 ddp 通信组;但是这意味着如果 - 用户需要使用 2 张以上的显卡,那么其必然需要使用 paddle.distributed.launch 来启动,意味着就不是情况 A 了; - 这时我们首先会调用 `FleetLauncher.launch` 函数来拉起多个进程,其中进程的数量等于用户传入给 trainer 的使用的 gpu - 的数量(例如 `Trainer` 中的参数是 device=[0, 1, 6, 7],那么我们就会使用第 0、1、6、7 张 gpu 来拉起 4 个进程); - 接着我们会调用 `fleet.init` 来初始化各个进程之间的通信组; - 这里需要注意拉起的新的进程会从前到后完整地运行一遍用户的启动脚本(例如 main.py),因此也都会运行这两个函数,但是需要注意只有进程 0 - 才会去真正地运行 `FleetLauncher.launch`;进程 0 运行到 `fleet.init`,paddle 会阻塞进程 0 继续 - 向前运行,直到其它进程也运行到这里; - 最后我们会设置这个进程对应的 device,然后将模型迁移到对应的机器上,再使用 `DataParallel` 将模型包裹; - 至此,paddle 分布式的环境配置过程全部完成; - - -> 情况 B:注意这种情况我们直接限定了用户是通过 paddle.distributed.launch 拉起,并且没有自己建立分布式的通信组。这时在 - `PaddleFleetDriver` 的初始化和 setup 函数的调用过程中,与情况 A 首要的不同就在于用户在 trainer 中输入的参数 device 不再有效, - 这时每个进程所使用的 gpu 是我们直接通过 `CUDA_VISIBLE_DEVICE` 来配置的;因此,如果用户想要实现使用特定 gpu - 设备的目的,可以通过自己设置环境变量实现(例如 os.environ["CUDA_VISIBLE_DEVICE"] 来实现,我们会通过一定的手段将其保存起来); - 剩下的操作和情况 A 类似; - - -> 情况 C:注意这种情况我们限定了用户是通过 paddle.distributed.launch 拉起,并且 ddp 的通信组也是由自己建立。这时基本上所有的 - 与操作相关的操作都应当由用户自己完成,包括迁移模型到对应 gpu 上以及将模型用 `DataParallel` 包裹等。 - (2)如果 `driver.setup` 函数在脚本中会被调用两次及以上(意味着用户的启动脚本初始化了两个及以上的 trainer/evaluator)时: - 注意这种情况下我们是会保证前后两个 trainer/evaluator 使用的 `PaddleFleetDriver` 以及其初始化方式的一致性,换句话说,如果 trainer1 - 检测到的启动方式是 '情况 A',那么我们会保证 trainer2 检测到的启动方式同样是 '情况A'(即使这需要一些额外的处理);因此这里我们主要讨论 - 我们是通过怎样的操作来保证 trainer2/3/... 检测到的启动方式是和 trainer1 一致的;简单来说,我们是通过使用环境变量来标记每一种不同的 - 启动方式来实现这一点的: - 我们会使用 `FASTNLP_DISTRIBUTED_CHECK` 来标记 '情况 A',使用 `fastnlp_torch_launch_not_ddp` 来标记 '情况 B',意味着我们在 - 使用 '情况 A' 来启动 `PaddleFleetDriver` 时,我们会将 `FASTNLP_DISTRIBUTED_CHECK` 这一字符串注入到环境变量中,而 '情况 B' 时则 - 会将 `fastnlp_torch_launch_not_ddp` 这一字符串注入到环境变量中。因此在 trainer2 的 `PaddleFleetDriver` 的初始化和 setup 过程中, - 如果检测到这些特殊的环境变量,我们就会将启动方式变更为其对应的启动方式,即使其它的参数特征属于另外的启动方式。 - - Part 2:对应的代码细节: - 1. 如何判断当前的各进程之间的通信组已经被建立(fleet 已经被初始化); - parallel_helper._is_parallel_ctx_initialized(); - 2. 如何判断不同的进程是否是由 `python -m paddle.distributed.launch` 拉起还是由我们的 `FleetLauncher.launch()` - 函数拉起; - 我们会在用户脚本 `import fastNLP` 的时候检测当前的环境变量中是否有 'PADDLE_RANK_IN_NODE'、'PADDLE_TRAINER_ID' - 以及没有 `FASTNLP_DISTRIBUTED_CHECK`, - 如果满足条件,则我们会向环境变量中注入特殊的值 'FASTNLP_BACKEND_LAUNCH' 来标记用户是否使用了 `python -m paddle.distributed.launch` - 来拉起多个进程; - 3. 整体的处理判断流程: - ___________________________________ - |进入 PaddleFleetDriver 的 __init__ 函数| - ——————————————————————————————————— - ↓ - ___________________________________________________ - | 判断不同的进程是否是由 paddle.distributed.launch 拉起 | - |(或者我们自己的 FleetLauncher 函数拉起) | --------------> - ———————————————————————————————————————————————————  | - ↓ 是由 paddle.distributed.launch 拉起 | 我们自己的 FleetLauncher 函数拉起多个进程 -  _____________________________            |  - ←←←←← | 检测用户是否自己初始化了 fleet |              | - ↓ —————————————————————————————                  ↓ - ↓ ↓ 是 ________ - ↓ ______ | 情况 A | - ↓ 否 |情况 C| ————————— - ↓ ——————— - ↓ - ↓ ______ - ↓ -----------> |情况 B| -   ——————— - 4. 为了完成全部的建立分布式所需要的操作,三种情况都需要做的事情,以及每件事情的职责归属: - - 情况 A | 情况 B | 情况 C - ________________________________________________________________________________________________________ - 配置 fleet 所 | FleetLauncher.launch | paddle.distributed.launch| paddle.distributed.launch - 需要的环境变量 | | | - ———————————————————————————————————————————————————————————————————————————————————————————————————————— - 开启多个进程 | FleetLauncher.launch | paddle.distributed.launch| paddle.distributed.launch - ———————————————————————————————————————————————————————————————————————————————————————————————————————— - 调用 fleet.init函数 | PaddleFleetDriver.setup | PaddleFleetDriver.setup | 用户自己调用 - ———————————————————————————————————————————————————————————————————————————————————————————————————————— - 设置 PaddleFleetDriver | | | - 的 world_size 和 | PaddleFleetDriver.setup | PaddleFleetDriver.setup | PaddleFleetDriver.setup - global_rank 属性 | | | - ———————————————————————————————————————————————————————————————————————————————————————————————————————— - - Part 3:其它的处理细节: - 1. 环境变量; - fastNLP 的 `PaddleFleetDriver` 运行时所需要的环境变量分为两种,一种是 paddle fleet 运行所需要的环境变量;另一种是 fastNLP 自己 - 的环境变量。前者的配置情况如上表所示;而后者中的大多数环境变量则是在用户 import fastNLP 时就设置好了; - 2. parallel_device, model_device 和 data_device 的关系; - parallel_device 为 `PaddleFleetDriver` 的参数,model_device 和 data_device 都为 driver 的属性; - 其中 data_device 仅当情况 C 时由用户自己指定;如果其不为 None,那么在模型 forward 的时候,我们就会将数据迁移到 data_device 上; - model_device 永远都为单独的一个 torch.device; - - 情况 A | 情况 B | 情况 C - ________________________________________________________________________________________________________ - parallel_device | 由用户传入trainer的参数 | | - | device 决定,必须是一个list, | 为 CUDA_VISIBLE_DEVICES | 为 CUDA_VISIBLE_DEVICES - | 其中每一个对象都是 int | | - ———————————————————————————————————————————————————————————————————————————————————————————————————————— - model_device | parallel_device[local_rank] | parallel_device | None - ———————————————————————————————————————————————————————————————————————————————————————————————————————— - data_device | model_device | model_device | 由用户传入 trainer 的参数 - | | | data_device 决定 - ———————————————————————————————————————————————————————————————————————————————————————————————————————— - - 3. _DDPWrappingModel 的作用; - 因为我们即需要调用模型的 `train_step`、`evaluate_step`、`test_step` 方法,又需要通过 `DataParallel` 的forward 函数来帮助 - 我们同步各个设备上的梯度,因此我们需要先将模型单独包裹一层,然后在 forward 的时候,其先经过 `DataParallel` 的 forward 方法, - 然后再经过 `_DDPWrappingModel` 的 forward 方法,我们会在该 forward 函数中进行判断,确定调用的是模型自己的 forward 函数,还是 - `train_step`、`evaluate_step`、`test_step` 方法。 - - 4. 当某一个进程出现 exception 后,`PaddleFleetDriver` 的处理; - - 不管是什么情况,`PaddleFleetDriver` 在 `setup` 函数的最后,都会将所有进程的 pid 主动记录下来,这样当一个进程出现 exception 后, - driver 的 on_exception 函数就会被 trainer 调用,其会调用 os.kill 指令将其它进程 kill 掉; - """ if USER_CUDA_VISIBLE_DEVICES not in os.environ: raise RuntimeError("To run paddle distributed training, please set `FASTNLP_BACKEND` to 'paddle' before using FastNLP.") super(PaddleFleetDriver, self).__init__(model, fp16=fp16, **kwargs) # 如果不是通过 launch 启动,要求用户必须传入 parallel_device - if not is_pull_by_paddle_run and parallel_device is None: - raise ValueError("Parameter `parallel_device` can not be None when using `PaddleFleetDriver`. This error is caused " - "when your value of parameter `device` is `None` in your `Trainer` instance.") + if not is_pull_by_paddle_run: + if parallel_device is None: + raise ValueError("Parameter `parallel_device` can not be None when using `PaddleFleetDriver`. This error is caused " + "when your value of parameter `device` is `None` in your `Trainer` instance.") + if not isinstance(parallel_device, List): + raise ValueError("Parameter `parallel_device`'s type must be List when using `PaddleFleetDriver`, " + f"not {type(parallel_device)}.") + if get_paddle_device_id(parallel_device[0]) != 0: + raise ValueError("The first device of `parallel_device` must be 'gpu:0' in fastNLP.") # 如果用户自己初始化了 paddle 的分布式训练那么一定是通过 launch 拉起的 # 这个参数会在 initialize_paddle_drvier 中设置。 @@ -254,10 +224,10 @@ class PaddleFleetDriver(PaddleDriver): def setup(self): """ - 根据不同的情况进行不同的设置。 - 1、如果是通过 paddle.distributed.launch 方法启动时,则根据已经设置好的环境获取 - 分布式的属性。 - 2、否则,调用 FleetLauncher 类启动子进程 + 初始化分布式训练的环境。 + + 1. 如果是通过 ``paddle.distributed.launch`` 方法启动的,则根据已经设置好的环境获取分布式的属性。 + 2. 否则启动子进程。 """ if self._has_setup: return @@ -267,7 +237,7 @@ class PaddleFleetDriver(PaddleDriver): if self.outside_fleet: # 已经初始化了多机环境 - self.set_from_fleet_environment() + self._set_from_fleet_environment() else: # 用户没有初始化多机环境 # TODO 绕一下 @@ -287,7 +257,7 @@ class PaddleFleetDriver(PaddleDriver): # parallel_device 是 list, if not parallel_helper._is_parallel_ctx_initialized(): # 拉起子进程并设置相应的属性 - self.init_fleet_and_set() + self._init_fleet_and_set() # 用户在这个 trainer 前面又初始化了一个 trainer,并且使用的是 PaddleFleetDriver; else: # 已经设置过一次,保证参数必须是一样的 @@ -321,7 +291,7 @@ class PaddleFleetDriver(PaddleDriver): self._pids = self._pids[node_rank*local_world_size: (node_rank+1)*local_world_size] self._pids = self.tensor_to_numeric(self._pids) - def init_fleet_and_set(self): + def _init_fleet_and_set(self): """ 使用 FleetLauncher 拉起子进程 """ @@ -340,7 +310,7 @@ class PaddleFleetDriver(PaddleDriver): assert self.world_size is not None assert self.world_size == len(self.parallel_device) - def set_from_fleet_environment(self): + def _set_from_fleet_environment(self): """ 当用户使用了 `python -m paddle.distributed.launch xxx.py` 启动时,我们需要 根据 paddle 设置的环境变量来获得各种属性 @@ -349,19 +319,11 @@ class PaddleFleetDriver(PaddleDriver): self.global_rank = paddledist.get_rank() def barrier(self): - r""" - 用于在多进程工作时同步各进程的工作进度,运行快的进程运行到这里会等待运行慢的进程,只有所有进程都运行到此函数时,所有的进程才会继续运行; - 仅在多分布式训练场景中有使用。 - - 注意,该函数的行为会受到 FASTNLP_NO_SYNC 的影响。仅当 FASTNLP_NO_SYNC 在 os.environ 中不存在,或小于 1 时才真的执行 barrier 。 - """ if int(os.environ.get(FASTNLP_NO_SYNC, 0)) < 1: # 当 FASTNLP_NO_SYNC 小于 1 时实际执行 paddledist.barrier() def configure_fleet(self): - """ - 将模型用 DataParallel 和自定义的类型包裹起来 - """ + # 将模型用 DataParallel 和自定义的类型包裹起来 if not self._has_fleetwrapped and not isinstance(self.model, DataParallel): self.model = DataParallel( _FleetWrappingModel(self.model), @@ -395,10 +357,17 @@ class PaddleFleetDriver(PaddleDriver): @property def model_device(self): + """ + :return: 模型所在的设备; + """ return self._model_device @property def data_device(self): + """ + :return: 数据所在的设备;由于 **PaddlePaddle** 可以通过环境变量获取当前进程的设备,因此该属性 + 和 ``model_device`` 表现相同; + """ return self.model_device def model_call(self, batch, fn: Callable, signature_fn: Optional[Callable]) -> Dict: @@ -522,23 +491,29 @@ class PaddleFleetDriver(PaddleDriver): else: raise ValueError("Parameter `dist_sampler` can only be one of three values: ('dist', 'unrepeatdist', None).") - def is_global_zero(self): + def is_global_zero(self) -> bool: return self.global_rank == 0 def get_model_no_sync_context(self): return self.model.no_sync - def unwrap_model(self): + def unwrap_model(self) -> "paddle.nn.Layer": + """ + 获得 driver 最原始的模型。该函数可以取出被 :class:`paddle.DataParallel` 包裹起来的模型。 + """ _layers = self.model._layers if isinstance(_layers, _FleetWrappingModel): return _layers.model else: return _layers - def get_local_rank(self) ->int: + def get_local_rank(self) -> int: return self.local_rank - def is_distributed(self): + def is_distributed(self) -> bool: + """ + 判断是否为分布式的 **Driver** ,在 ``PaddleFleetDriver`` 中,返回 ``True``。 + """ return True @staticmethod diff --git a/fastNLP/core/drivers/paddle_driver/paddle_driver.py b/fastNLP/core/drivers/paddle_driver/paddle_driver.py index a228a90d..c10c98a1 100644 --- a/fastNLP/core/drivers/paddle_driver/paddle_driver.py +++ b/fastNLP/core/drivers/paddle_driver/paddle_driver.py @@ -55,7 +55,7 @@ class PaddleDriver(Driver): 这个类被以下子类继承: 1. :class:`~fastNLP.core.drivers.PaddleSingleDriver`:实现了使用单卡和 ``cpu`` 训练的具体功能; - 2. :class:`~fastNLP.core.drivers.PaddleFleetDriver`:实现了使用 ``fleet`` 分布式训练 API 进行分布式训练的具体功能; + 2. :class:`~fastNLP.core.drivers.PaddleFleetDriver`:实现了使用 ``fleet`` 分布式训练 API 进行集群式分布式训练的具体功能; :param model: 训练时使用的 **PaddlePaddle** 模型; :param fp16: 是否开启混合精度训练; @@ -174,8 +174,8 @@ class PaddleDriver(Driver): 将模型保存到 ``filepath`` 中。 :param filepath: 保存文件的文件位置(需要包括文件名); - :param only_state_dict: 是否只保存模型的 ``state_dict``;如果为 ``False``,则会调用 ``paddle.jit.save`` 函数 - 保存整个模型的参数,此时需要传入 ``input_spec`` 参数; + :param only_state_dict: 是否只保存模型的 ``state_dict``;如果为 ``False``,则会调用 ``paddle.jit.save`` + 函数保存整个模型的参数,此时需要传入 ``input_spec`` 参数; :kwargs: * input_spec -- 描述存储模型 ``forward`` 方法的输入; 当 ``only_state_dict`` 为 ``False`` 时必须传入,否则加载时会报错。您可以通过 ``InputSpec`` 或者示例 ``Tensor`` @@ -212,11 +212,10 @@ class PaddleDriver(Driver): 断点重训的保存函数,该函数会负责保存模型和 optimizers, fp16 的 state_dict;以及模型的保存(若 should_save_model 为 True) :param folder: 保存断点重训的状态的文件夹;save 函数应该在下面新增两(一)个文件 的 FASTNLP_CHECKPOINT_FILENAME 文件与 - FASTNLP_MODEL_FILENAME (如果 should_save_model 为 True )。把 model 相关的内容放入到 FASTNLP_MODEL_FILENAME 文件 - 中,将传入的 states 以及自身产生其它状态一并保存在 FASTNLP_CHECKPOINT_FILENAME 里面。 - :param states: 由 trainer 传入的一个字典,其中已经包含了为了实现断点重训所需要保存的其它对象的状态,Driver 应该只需要保存 - 该对象即可, Driver 应该不需要理解该对象,同时在 driver.load() 的时候,需要将 states 返回回去,load() 返回的值与这里的 - 传入的值保持一致。 + FASTNLP_MODEL_FILENAME (如果 should_save_model 为 True )。把 model 相关的内容放入到 FASTNLP_MODEL_FILENAME 文件中, + 将传入的 states 以及自身产生其它状态一并保存在 FASTNLP_CHECKPOINT_FILENAME 里面。 + :param states: 由 trainer 传入的一个字典,其中已经包含了为了实现断点重训所需要保存的其它对象的状态,Driver 应该只需要保存该对象即可, + Driver 应该不需要理解该对象,同时在 driver.load() 的时候,需要将 states 返回回去,load() 返回的值与这里的传入的值保持一致。 :param dataloader: 正在使用的 dataloader,需要保存里面的状态使得之后可以从当前迭代的位置恢复。 :param only_state_dict: 是否只保存模型的参数,当 should_save_model 为 False ,该参数无效。 :param should_save_model: 是否应该保存模型,如果为False,Driver 将不负责 model 的保存。 diff --git a/fastNLP/core/drivers/paddle_driver/single_device.py b/fastNLP/core/drivers/paddle_driver/single_device.py index a9e92fd3..10779fd6 100644 --- a/fastNLP/core/drivers/paddle_driver/single_device.py +++ b/fastNLP/core/drivers/paddle_driver/single_device.py @@ -152,6 +152,6 @@ class PaddleSingleDriver(PaddleDriver): def is_distributed(self) -> bool: """ - 判断是否为分布式的 **Driver** ,在 ``PaddleSingleDriver`` 中,返回 ``False`` + 判断是否为分布式的 **Driver** ,在 ``PaddleSingleDriver`` 中,返回 ``False``。 """ return False diff --git a/fastNLP/core/utils/jittor_utils.py b/fastNLP/core/utils/jittor_utils.py index 08b3b7a8..f29b1f46 100644 --- a/fastNLP/core/utils/jittor_utils.py +++ b/fastNLP/core/utils/jittor_utils.py @@ -32,8 +32,8 @@ def is_jittor_dataset(dataset) -> bool: def jittor_collate_wraps(func, auto_collator: Callable): """ - 对 ``jittor`` 的 ``collate_fn`` 进行 ``wrap`` 封装,。如果数据集为 ``mapping`` 类型,那么采用 ``auto_collator`` ,否则 - 还是采用 ``jittor`` 的 ``collate_batch``。 + 对 ``jittor`` 的 ``collate_fn`` 进行 ``wrap`` 封装,。如果数据集为 ``mapping`` 类型,那么采用 ``auto_collator`` , + 否则还是采用 ``jittor`` 的 ``collate_batch``。 :param func: :param auto_collator: diff --git a/fastNLP/core/utils/paddle_utils.py b/fastNLP/core/utils/paddle_utils.py index d3764d4e..2d7b65cc 100644 --- a/fastNLP/core/utils/paddle_utils.py +++ b/fastNLP/core/utils/paddle_utils.py @@ -61,8 +61,8 @@ def _convert_data_device(device: Union[str, int]) -> str: def paddle_to(data: "paddle.Tensor", device: Union[str, int]) -> "paddle.Tensor": """ - 将 ``data`` 迁移到指定的 ``device`` 上。``paddle.Tensor`` 没有类似 ``torch.Tensor`` 的 ``to`` 函数,该函数 - 只是集成了 :func:`paddle.Tensor.cpu` 和 :func:`paddle.Tensor.cuda` 两个函数。 + 将 ``data`` 迁移到指定的 ``device`` 上。``paddle.Tensor`` 没有类似 ``torch.Tensor`` 的 ``to`` 函数, + 该函数只是集成了 :func:`paddle.Tensor.cpu` 和 :func:`paddle.Tensor.cuda` 两个函数。 :param data: 要迁移的张量; :param device: 目标设备,可以是 ``str`` 或 ``int`` 类型; @@ -130,8 +130,8 @@ def paddle_move_data_to_device(batch: Any, device: Optional[Union[str, int]]) -> 将 **paddle** 的数据集合传输到给定设备。只有 :class:`paddle.Tensor` 对象会被传输到设备中,其余保持不变。 :param batch: 需要进行迁移的数据集合; - :param device: 目标设备。可以是显卡设备的编号,或是``cpu``, ``gpu`` 或 ``gpu:x`` 格式的字符串;当这个参数 - 为 `None`` 时,不会执行任何操作。 + :param device: 目标设备。可以是显卡设备的编号,或是``cpu``, ``gpu`` 或 ``gpu:x`` 格式的字符串; + 当这个参数为 `None`` 时,不会执行任何操作。 :return: 迁移到新设备上的数据集合; """ if device is None: diff --git a/fastNLP/core/utils/rich_progress.py b/fastNLP/core/utils/rich_progress.py index 53d4e281..fc056526 100644 --- a/fastNLP/core/utils/rich_progress.py +++ b/fastNLP/core/utils/rich_progress.py @@ -1,6 +1,6 @@ """ -该文件用于为 **fastNLP** 提供一个统一的 ``progress bar`` 管理,通过共用一个``Task`` 对象, :class:`~fastNLP.core.Trainer` 中 -的 ``progress bar`` 和 :class:`~fastNLP.core.Evaluator` 中的 ``progress bar`` 才能不冲突 +该文件用于为 **fastNLP** 提供一个统一的 ``progress bar`` 管理,通过共用一个``Task`` 对象, :class:`~fastNLP.core.Trainer` +中的 ``progress bar`` 和 :class:`~fastNLP.core.Evaluator` 中的 ``progress bar`` 才能不冲突 """ import sys from typing import Any, Union, Optional diff --git a/fastNLP/core/utils/utils.py b/fastNLP/core/utils/utils.py index 4d8bbb5e..5e02ef6d 100644 --- a/fastNLP/core/utils/utils.py +++ b/fastNLP/core/utils/utils.py @@ -60,7 +60,7 @@ def auto_param_call(fn: Callable, *args, signature_fn: Optional[Callable] = None ``value`` 的参数。 1. 该函数用来提供给用户根据字符串匹配从而实现自动调用; - 2. 注意 ``mapping`` 默认为 ``None``,如果你希望指定输入和运行函数的参数的对应方式,那么你应当让 ``mapping`` 为一个字典传入进来; + 2. 注意 ``mapping`` 默认为 ``None``,如果您希望指定输入和运行函数的参数的对应方式,那么您应当让 ``mapping`` 为一个字典传入进来; 如果 ``mapping`` 不为 ``None``,那么我们一定会先使用 ``mapping`` 将输入的字典的 ``keys`` 修改过来,因此请务必亲自检查 ``mapping`` 的正确性; 3. 如果输入的函数的参数有默认值,那么如果之后的输入中没有该参数对应的值,我们就会使用该参数对应的默认值,否则也会使用之后的输入的值; 4. 如果输入的函数是一个 ``partial`` 函数,情况同第三点,即和默认参数的情况相同; @@ -82,8 +82,8 @@ def auto_param_call(fn: Callable, *args, signature_fn: Optional[Callable] = None :param fn: 用来进行实际计算的函数,其参数可以包含有默认值; :param args: 一系列的位置参数,应当为一系列的字典,我们需要从这些输入中提取 ``fn`` 计算所需要的实际参数; - :param signature_fn: 函数,用来替换 ``fn`` 的函数签名,如果该参数不为 ``None``,那么我们首先会从该函数中提取函数签名,然后通过该函数签名提取 - 参数值后,再传给 ``fn`` 进行实际的运算; + :param signature_fn: 函数,用来替换 ``fn`` 的函数签名,如果该参数不为 ``None``,那么我们首先会从该函数中提取函数签名, + 然后通过该函数签名提取参数值后,再传给 ``fn`` 进行实际的运算; :param mapping: 一个字典,用来更改其前面的字典的键值; :return: 返回 ``fn`` 运行的结果; @@ -195,8 +195,8 @@ def _get_fun_msg(fn, with_fp=True)->str: def _check_valid_parameters_number(fn, expected_params:List[str], fn_name=None): """ - 检查一个函数是否需要 expected_params 参数(检测数量是否匹配)。除掉 self (如果是method),给定默认值的参数等。如果匹配不上,就会 - 进行报错。 + 检查一个函数是否需要 expected_params 参数(检测数量是否匹配)。除掉 self (如果是method),给定默认值的参数等。 + 如果匹配不上,就会进行报错。 :param fn: 需要检测的函数,可以是 method 或者 function 。 :param expected_params: 期待应该支持的参数。 @@ -345,8 +345,8 @@ def apply_to_collection( :param dtype: 数据的类型,函数 ``function`` 只会被应用于 ``data`` 中类型为 ``dtype`` 的数据; :param function: 对数据进行处理的函数; :param args: ``function`` 所需要的其它参数; - :param wrong_dtype: ``function`` 一定不会生效的数据类型。如果数据既是 ``wrong_dtype`` 类型又是 ``dtype`` 类型 - 那么也不会生效; + :param wrong_dtype: ``function`` 一定不会生效的数据类型。 + 如果数据既是 ``wrong_dtype`` 类型又是 ``dtype`` 类型那么也不会生效; :param include_none: 是否包含执行结果为 ``None`` 的数据,默认为 ``True``; :param kwargs: ``function`` 所需要的其它参数; :return: 经过 ``function`` 处理后的数据集合; @@ -587,7 +587,7 @@ def seq_len_to_mask(seq_len, max_len: Optional[int]): :param seq_len: 大小为 ``(B,)`` 的长度序列; :param int max_len: 将长度补齐或截断到 ``max_len``。默认情况(为 ``None``)使用的是 ``seq_len`` 中最长的长度; 但在 :class:`torch.nn.DataParallel` 等分布式的场景下可能不同卡的 ``seq_len`` 会有区别,所以需要传入 - 一个 ``max_len`` 使得 ``mask`` 的补齐或截断到该长度。 + ``max_len`` 使得 ``mask`` 的补齐或截断到该长度。 :return: 大小为 ``(B, max_len)`` 的 ``mask``, 元素类型为 ``bool`` 或 ``uint8`` """ if isinstance(seq_len, np.ndarray): diff --git a/fastNLP/modules/mix_modules/utils.py b/fastNLP/modules/mix_modules/utils.py index b19a5d53..142644f9 100644 --- a/fastNLP/modules/mix_modules/utils.py +++ b/fastNLP/modules/mix_modules/utils.py @@ -202,12 +202,12 @@ def jittor2torch(batch: Any, device: str = None, no_gradient: bool = None) -> An .. note:: - 注意,由于 **pytorch** 和 **jittor** 之间的差异,从 :class:`jittor.Var` 转换 - 至 :class:`torch.Tensor` 的过程中无法保留原张量的梯度。 + 注意,由于 **pytorch** 和 **jittor** 之间的差异,从 :class:`jittor.Var` 转换至 + :class:`torch.Tensor` 的过程中无法保留原张量的梯度。 :param batch: 包含 :class:`jittor.Var` 类型的数据集合; :param device: 是否将转换后的张量迁移到特定设备上。为 ``None``时,和输入保持一致; - :param no_gradient: 是否保留原张量的梯度,在这个函数中该参数无效。 + :param no_gradient: 是否保留原张量的梯度,在这个函数中该参数无效; :return: 转换后的数据; """ From c562982f1d98bc4af32930143a48b022b3cb0102 Mon Sep 17 00:00:00 2001 From: yh_cc Date: Thu, 12 May 2022 01:02:10 +0800 Subject: [PATCH 4/4] =?UTF-8?q?=E8=A1=A5=E5=85=85=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastNLP/core/callbacks/__init__.py | 1 - fastNLP/core/callbacks/callback_event.py | 20 +-- fastNLP/core/callbacks/callback_manager.py | 4 - fastNLP/core/callbacks/checkpoint_callback.py | 90 +++++----- fastNLP/core/callbacks/early_stop_callback.py | 31 ++-- .../core/callbacks/has_monitor_callback.py | 93 +++++----- .../callbacks/load_best_model_callback.py | 48 ++--- .../core/callbacks/lr_scheduler_callback.py | 14 +- .../core/callbacks/more_evaluate_callback.py | 103 ++++++----- fastNLP/core/callbacks/progress_callback.py | 36 ++-- fastNLP/core/callbacks/topk_saver.py | 96 +++++----- .../torch_grad_clip_callback.py | 25 +-- .../torch_lr_sched_callback.py | 22 +-- fastNLP/core/collators/collator.py | 31 ++-- fastNLP/core/collators/packer_unpacker.py | 17 +- .../core/collators/padders/jittor_padder.py | 6 +- .../core/collators/padders/numpy_padder.py | 60 ++++--- fastNLP/core/collators/padders/padder.py | 20 ++- .../core/collators/padders/paddle_padder.py | 48 ++--- fastNLP/core/collators/padders/raw_padder.py | 48 ++--- .../core/collators/padders/torch_padder.py | 55 +++--- fastNLP/core/controllers/evaluator.py | 170 ++++++++++++------ fastNLP/core/controllers/trainer.py | 8 +- fastNLP/core/dataset/instance.py | 16 +- fastNLP/core/metrics/metric.py | 21 ++- .../core/metrics/span_f1_pre_rec_metric.py | 34 ++-- fastNLP/core/samplers/conversion_utils.py | 2 +- .../samplers/reproducible_batch_sampler.py | 78 ++++---- fastNLP/core/samplers/reproducible_sampler.py | 60 +++---- fastNLP/core/samplers/unrepeated_sampler.py | 52 +++--- fastNLP/core/utils/rich_progress.py | 5 - fastNLP/envs/distributed.py | 12 +- 32 files changed, 732 insertions(+), 594 deletions(-) diff --git a/fastNLP/core/callbacks/__init__.py b/fastNLP/core/callbacks/__init__.py index efd9280f..caf96af7 100644 --- a/fastNLP/core/callbacks/__init__.py +++ b/fastNLP/core/callbacks/__init__.py @@ -2,7 +2,6 @@ __all__ = [ 'Callback', 'Event', 'Filter', - 'CallbackManager', 'CheckpointCallback', 'choose_progress_callback', 'ProgressCallback', diff --git a/fastNLP/core/callbacks/callback_event.py b/fastNLP/core/callbacks/callback_event.py index 9bdbfd95..e7657a25 100644 --- a/fastNLP/core/callbacks/callback_event.py +++ b/fastNLP/core/callbacks/callback_event.py @@ -30,20 +30,20 @@ def check_legality(fn): class Event: + """ + 与 Trainer.on 函数配合使用,达到控制 callback 函数运行时机的目的。 + + :param value: Trainer 的 callback 时机。 + :param int every: 触发了多少次,才真正运行一次。 + :param bool once: 是否只在第一次运行后就不再执行了。 + :param Callable filter_fn: 输入参数的应该为 (filter, trainer),其中 filter 对象中包含了 filter.num_called 和 + filter.num_executed 两个变量分别获取当前被调用了多少次,真正执行了多少次。trainer 对象即为当前正在运行的 Trainer 。 + """ every: Optional[int] once: Optional[int] def __init__(self, value: str, every: Optional[int] = None, once: Optional[int] = None, filter_fn: Optional[Callable] = None): - """ - 请勿直接使用本对象,而是通过调用 Event.on_after_trainer_initialized() 等方式调用。 - - :param value: Trainer 的 callback 时机。 - :param int every: 触发了多少次,才真正运行一次。 - :param bool once: 是否只在第一次运行后就不再执行了。 - :param Callable filter_fn: 输入参数的应该为 (filter, trainer),其中 filter 对象中包含了 filter.num_called 和 - filter.num_executed 两个变量分别获取当前被调用了多少次,真正执行了多少次。trainer 对象即为当前正在运行的 Trainer 。 - """ self.every = every self.once = once self.filter_fn = filter_fn @@ -456,7 +456,7 @@ class Event: class Filter: def __init__(self, every: Optional[int] = None, once: Optional[bool] = None, filter_fn: Optional[Callable] = None): r""" - 通过该 `Filter` 作为函数修饰器来控制一个函数的实际的运行频率; + 通过该 `Filter` 作为函数修饰器来控制一个函数的实际的运行频率。 :param every: 表示一个函数隔多少次运行一次; :param once: 表示一个函数只运行一次; diff --git a/fastNLP/core/callbacks/callback_manager.py b/fastNLP/core/callbacks/callback_manager.py index 765a0346..60c9b17d 100644 --- a/fastNLP/core/callbacks/callback_manager.py +++ b/fastNLP/core/callbacks/callback_manager.py @@ -2,10 +2,6 @@ import inspect from typing import List, Optional, Dict, Sequence from collections import defaultdict -__all__ = [ - 'CallbackManager' -] - from .callback_event import Event from .callback import Callback from fastNLP.core.log import logger diff --git a/fastNLP/core/callbacks/checkpoint_callback.py b/fastNLP/core/callbacks/checkpoint_callback.py index 625aea09..04ce1fc9 100644 --- a/fastNLP/core/callbacks/checkpoint_callback.py +++ b/fastNLP/core/callbacks/checkpoint_callback.py @@ -13,55 +13,55 @@ from ..utils.exceptions import EarlyStopException class CheckpointCallback(Callback): + """ + 保存 checkpoint 的 callback ,其保存的文件目录以及文件名命名规则如下:: + + - folder/ + - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 + - {save_object}-epoch_{epoch_idx}/ # 满足 every_n_epochs 条件保存的模型 + - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}/ # 满足 every_n_batches 保存的模型 + - {save_object}-last/ # 最后一个 epoch 的保存 + - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-exception_{exception_type}/ # exception时保存。 + - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-{monitor}_{monitor_value}/ # 满足topk条件存储文件名 + + model_save_fn 为 None ,则以上每个 folder 中,将生成 fastnlp_model.pkl.tar 文件。若 model_save_fn 不为 None, + 则 fastNLP 将 folder 绝对路径传递给该函数,fastNLP 在该 folder 下不进行模型保存。默认情况下,本 checkpoint 只保存了 model + 的状态;如还需保存 Trainer 的状态以断点重训的话,请使用 ``save_object='trainer'`` 。 + + :param monitor: 监控的 metric 值。 + + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param folder: 保存的文件夹,fastNLP 将在该文件下以时间戳创建子文件夹,并在里面保存。因此不同次运行可以将被保存到不同的 + 时间戳文件夹中。如果为 None ,默认使用当前文件夹。 + :param every_n_epochs: 多少个 epoch 保存一次。 + :param every_n_batches: 多少个 batch 保存一次。 + :param last: 如果为 True ,将在每次 epoch 运行结束都保存一次,会覆盖之前的保存。 + :param topk: 保存 monitor 结果 topK 个。 + :param on_exceptions: 在出异常信息时,是否保存。传入需要捕获的异常的类。默认将捕获 EarlyStopException 。 + :param larger_better: monitor 的值是否时越大越好。 + :param only_state_dict: 保存模型时是否只保存 state_dict 。当 model_save_fn 不为 None 时,该参数无效。 + :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 + 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 + :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 ``trainer+model`` 还是 只是 ``model`` 。如果 + 保存 ``trainer`` 对象的话,将会保存 :class:~fastNLP.Trainer 的相关状态,可以通过 :meth:`Trainer.load` 加载该断 + 点继续训练。如果保存的是 ``Model`` 对象,则可以通过 :meth:`Trainer.load_model` 加载该模型权重。 + :param save_evaluate_results: 是否保存 evaluate 的结果。如果为 True ,在保存 topk 模型的 folder 中还将额外保存一个 + fastnlp_evaluate_results.json 文件,记录当前的 results。仅在设置了 topk 的场景下有用,默认为 True 。 + :param kwargs: + """ def __init__(self, folder: Optional[Union[str, Path]] = None, every_n_epochs: Optional[int] = None, every_n_batches: Optional[int] = None, last: bool = False, topk: int = 0, on_exceptions: Optional[Union[BaseException, Sequence[BaseException]]] = [EarlyStopException], monitor: Optional[Union[str, Callable]] = None, larger_better: bool = True, only_state_dict: bool = True, model_save_fn: Optional[Callable] = None, save_object: str = 'model', save_evaluate_results=True, **kwargs): - """ - 保存 checkpoint 的 callback ,其保存的文件目录以及文件名命名规则如下:: - - - folder/ - - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 - - {save_object}-epoch_{epoch_idx}/ # 满足 every_n_epochs 条件保存的模型 - - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}/ # 满足 every_n_batches 保存的模型 - - {save_object}-last/ # 最后一个 epoch 的保存 - - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-exception_{exception_type}/ # exception时保存。 - - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-{monitor}_{monitor_value}/ # 满足topk条件存储文件名 - - model_save_fn 为 None ,则以上每个 folder 中,将生成 fastnlp_model.pkl.tar 文件。若 model_save_fn 不为 None, - 则 fastNLP 将 folder 绝对路径传递给该函数,fastNLP 在该 folder 下不进行模型保存。默认情况下,本 checkpoint 只保存了 model - 的状态;如还需保存 Trainer 的状态以断点重训的话,请使用 ``save_object='trainer'`` 。 - - :param monitor: 监控的 metric 值。 - - * 为 ``None`` - 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 - * 为 ``str`` - 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 - 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 - * 为 ``Callable`` - 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 - 的 ``monitor`` 值请返回 ``None`` 。 - :param folder: 保存的文件夹,fastNLP 将在该文件下以时间戳创建子文件夹,并在里面保存。因此不同次运行可以将被保存到不同的 - 时间戳文件夹中。如果为 None ,默认使用当前文件夹。 - :param every_n_epochs: 多少个 epoch 保存一次。 - :param every_n_batches: 多少个 batch 保存一次。 - :param last: 如果为 True ,将在每次 epoch 运行结束都保存一次,会覆盖之前的保存。 - :param topk: 保存 monitor 结果 topK 个。 - :param on_exceptions: 在出异常信息时,是否保存。传入需要捕获的异常的类。默认将捕获 EarlyStopException 。 - :param larger_better: monitor 的值是否时越大越好。 - :param only_state_dict: 保存模型时是否只保存 state_dict 。当 model_save_fn 不为 None 时,该参数无效。 - :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 - 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 - :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 ``trainer+model`` 还是 只是 ``model`` 。如果 - 保存 ``trainer`` 对象的话,将会保存 :class:~fastNLP.Trainer 的相关状态,可以通过 :meth:`Trainer.load` 加载该断 - 点继续训练。如果保存的是 ``Model`` 对象,则可以通过 :meth:`Trainer.load_model` 加载该模型权重。 - :param save_evaluate_results: 是否保存 evaluate 的结果。如果为 True ,在保存 topk 模型的 folder 中还将额外保存一个 - fastnlp_evaluate_results.json 文件,记录当前的 results。仅在设置了 topk 的场景下有用,默认为 True 。 - :param kwargs: - """ super().__init__() if every_n_epochs is not None: if not isinstance(every_n_epochs, int) or every_n_epochs < 1: @@ -133,10 +133,6 @@ class CheckpointCallback(Callback): self.topk_saver.save(trainer, folder_name=folder_name) def on_save_checkpoint(self, trainer) -> Dict: - """ - 保存状态,以便之后可以继续使用 - """ - states = {} states['topk_saver'] = self.topk_saver.state_dict() return states diff --git a/fastNLP/core/callbacks/early_stop_callback.py b/fastNLP/core/callbacks/early_stop_callback.py index c706bf12..db9b6493 100644 --- a/fastNLP/core/callbacks/early_stop_callback.py +++ b/fastNLP/core/callbacks/early_stop_callback.py @@ -9,22 +9,23 @@ from fastNLP.core.utils.exceptions import EarlyStopException class EarlyStopCallback(HasMonitorCallback): - def __init__(self, monitor:Union[str, Callable]=None, larger_better:bool=True, patience:int=10): - """ + """ + 用于 early stop 的 callback 。当监控的结果连续多少次没有变好边 raise 一个 EarlyStopException 。 - :param monitor: 监控的 metric 值。 + :param monitor: 监控的 metric 值。 - * 为 ``None`` - 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 - * 为 ``str`` - 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 - 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 - * 为 ``Callable`` - 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 - 的 ``monitor`` 值请返回 ``None`` 。 - :param larger_better: monitor 的值是否是越大越好。 - :param patience: 多少次 evaluate 不没有提升就停止。 - """ + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param larger_better: monitor 的值是否是越大越好。 + :param patience: 多少次 evaluate 不没有提升就停止。 + """ + def __init__(self, monitor:Union[str, Callable]=None, larger_better:bool=True, patience:int=10): super(EarlyStopCallback, self).__init__(monitor=monitor, larger_better=larger_better, must_have_monitor=True) self.wait = 0 self.patience = patience @@ -42,7 +43,7 @@ class EarlyStopCallback(HasMonitorCallback): # 当是 step evaluate 的时候,下一步执行的就是这个, 所以在这里检查。 if self.wait >= self.patience: raise EarlyStopException(f"After {self.wait} validations, no improvement for " - f"metric `{self._real_monitor}`") + f"metric `{self._real_monitor}`(best value: {self.monitor_value})") def on_train_epoch_begin(self, trainer): # 当是 epoch evaluate 的时候,下一步执行的就是这个, 所以在这里检查。 diff --git a/fastNLP/core/callbacks/has_monitor_callback.py b/fastNLP/core/callbacks/has_monitor_callback.py index e5406d78..ac09e266 100644 --- a/fastNLP/core/callbacks/has_monitor_callback.py +++ b/fastNLP/core/callbacks/has_monitor_callback.py @@ -16,11 +16,6 @@ from fastNLP.core.utils.utils import _check_valid_parameters_number class CanItemDataType(ABC): - """ - 检测可以进行传输的对象。 - - """ - @classmethod def __subclasshook__(cls, subclass: Any) -> Union[bool, Any]: if cls is CanItemDataType: @@ -30,15 +25,22 @@ class CanItemDataType(ABC): class ResultsMonitor: + """ + 可用于监控某个数值,并通过 is_better_results() 等接口实现检测结果是否变得更好了。 + + :param monitor: 监控的 metric 值。 + + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param larger_better: monitor 是否时越大越好 + """ def __init__(self, monitor:Union[Callback, str], larger_better:bool=True): - """ - 可用于监控某个数值,并通过 is_better_results() 等接口实现检测结果是否变得更好了。 - - :param monitor: 监控的 metric 值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 最长公共字符串算法 找到最匹配 - 的那个作为 monitor 。如果为 None,将尝试使用 Trainer 设置的 monitor 。也可以传入一个函数,接受参数为 evaluation 的结 - 果(字典类型),返回一个 float 值作为 monitor 的结果,如果当前结果中没有相关的 monitor 值请返回 None 。 - :param larger_better: monitor 是否时越大越好 - """ self.set_monitor(monitor, larger_better) def set_monitor(self, monitor, larger_better): @@ -66,9 +68,9 @@ class ResultsMonitor: def get_monitor_value(self, results:Dict)->Union[float, None]: """ - 获取 monitor 的值,如果 monitor 没有直接找到,会尝试使用匹配的方式寻找,并把匹配到的设置到 self._real_monitor 属性上。 + 获取 monitor 的值,如果 monitor 没有直接找到,会尝试使用 最长公共字符串算法 匹配的方式寻找。 - :param results: + :param results: 评测结果。 :return: 如果为 None ,表明此次没有找到合适的monitor """ if len(results) == 0 or self.monitor is None: @@ -113,7 +115,7 @@ class ResultsMonitor: """ 检测给定的 results 是否比上一次更好,如果本次 results 中没有找到相关的monitor 返回 False。 - :param results: on_valid_ends() 接口中传入的 evaluation 结果。 + :param results: evaluation 结果。 :param keep_if_better: 当返回为 True 时,是否保存到 self.monitor_value 中。 :return: """ @@ -166,24 +168,24 @@ class ResultsMonitor: class HasMonitorCallback(ResultsMonitor, Callback): + """ + 该 callback 不直接进行使用,作为其它相关 callback 的父类使用,如果 callback 有使用 monitor 可以继承该函数里面实现了 + (1)判断monitor合法性;(2)在需要时, 根据trainer的monitor设置自己的monitor名称。 + + :param monitor: 监控的 metric 值。 + + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param larger_better: monitor 是否时越大越好 + :param must_have_monitor: 这个 callback 是否必须有 monitor 设置。如果设置为 True ,且没检测到设置 monitor 会报错。 + """ def __init__(self, monitor, larger_better, must_have_monitor=False): - """ - 该 callback 不直接进行使用,作为其它相关 callback 的父类使用,如果 callback 有使用 monitor 可以继承该函数里面实现了 - (1)判断monitor合法性;(2)在需要时, 根据trainer的monitor设置自己的monitor名称。 - - :param monitor: 监控的 metric 值。 - - * 为 ``None`` - 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 - * 为 ``str`` - 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 - 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 - * 为 ``Callable`` - 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 - 的 ``monitor`` 值请返回 ``None`` 。 - :param larger_better: monitor 是否时越大越好 - :param must_have_monitor: 这个 callback 是否必须有 monitor 设置。如果设置为 True ,且没检测到设置 monitor 会报错。 - """ super().__init__(monitor, larger_better) self.must_have_monitor = must_have_monitor @@ -212,16 +214,23 @@ class HasMonitorCallback(ResultsMonitor, Callback): class ExecuteOnceBetterMonitor(HasMonitorCallback): + """ + 当监控的 monitor 结果更好的时候,调用 execute_fn 函数。 + + :param monitor: 监控的 metric 值。 + + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param larger_better: monitor 是否时越大越好 + :param execute_fn: 一个可执行的函数,不接受任何参数,不反回值。在 monitor 取得更好结果的时候会调用。 + """ def __init__(self, monitor, larger_better, execute_fn): - """ - 当监控的 monitor 结果更好的时候,调用 execute_fn 函数。 - - :param monitor: 监控的 metric 值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 最长公共字符串算法 找到最匹配 - 的那个作为 monitor 。如果为 None,将尝试使用 Trainer 设置的 monitor 。也可以传入一个函数,接受参数为 evaluation 的结 - 果(字典类型),返回一个 float 值作为 monitor 的结果,如果当前结果中没有相关的 monitor 值请返回 None 。 - :param larger_better: monitor 是否时越大越好 - :param execute_fn: 一个可执行的函数,不接受任何参数,不反回值。在 monitor 取得更好结果的时候会调用。 - """ super().__init__(monitor, larger_better, must_have_monitor=True) _check_valid_parameters_number(execute_fn, expected_params=[], fn_name='execute_fn') self.execute_fn = execute_fn diff --git a/fastNLP/core/callbacks/load_best_model_callback.py b/fastNLP/core/callbacks/load_best_model_callback.py index 5083f5c3..94e6ed90 100644 --- a/fastNLP/core/callbacks/load_best_model_callback.py +++ b/fastNLP/core/callbacks/load_best_model_callback.py @@ -14,34 +14,34 @@ from fastNLP.envs import all_rank_call_context class LoadBestModelCallback(HasMonitorCallback): + """ + 保存最佳的 monitor 值最佳的模型,并在训练结束的时候重新加载模型,默认会在加载之后删除权重文件。仅在训练正常结束的时候才能加载 + 最好的模型。 + + :param monitor: 监控的 metric 值。 + + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param larger_better: 该 metric 值是否是越大越好。 + :param save_folder: 保存的文件夹,如果为空,则保存在内存中。不为空,则保存一份权重到文件中,当为多机训练,且本值不为空时,请确保 + 不同的机器均可访问当该路径。当 model_save_fn 不为 None 时该值一定不能为空。 + :param only_state_dict: 是否只保存模型的参数。当 model_save_fn 不为空时,该值无效。 + :param model_save_fn: 保存 model 的函数,与 model_load_fn 必须同时不为空。本函数的输入为一个已经创建好的文件夹,没有输出, + 请在函数内完成对模型的保存。 + :param model_load_fn: 加载 model 的函数,与 model_save_fn 必须同时不为空。本函数的输入为一个已经创建好的文件夹,没有输出, + 请在函数内完成对模型的加载。 + :param delete_after_train: 在训练结束后是否删掉模型。 + """ def __init__(self, monitor:Union[str, Callable]=None, larger_better:bool = True, only_state_dict:bool = True, save_folder:Optional[str] = None, model_save_fn:Optional[Callable] = None, model_load_fn:Optional[Callable] = None, delete_after_train:bool = True): - """ - 保存最佳的 monitor 值最佳的模型,并在训练结束的时候重新加载模型,默认会在加载之后删除权重文件。仅在训练正常结束的时候才能加载 - 最好的模型。 - - :param monitor: 监控的 metric 值。 - - * 为 ``None`` - 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 - * 为 ``str`` - 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 - 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 - * 为 ``Callable`` - 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 - 的 ``monitor`` 值请返回 ``None`` 。 - :param larger_better: 该 metric 值是否是越大越好。 - :param save_folder: 保存的文件夹,如果为空,则保存在内存中。不为空,则保存一份权重到文件中,当为多机训练,且本值不为空时,请确保 - 不同的机器均可访问当该路径。当 model_save_fn 不为 None 时该值一定不能为空。 - :param only_state_dict: 是否只保存模型的参数。当 model_save_fn 不为空时,该值无效。 - :param model_save_fn: 保存 model 的函数,与 model_load_fn 必须同时不为空。本函数的输入为一个已经创建好的文件夹,没有输出, - 请在函数内完成对模型的保存。 - :param model_load_fn: 加载 model 的函数,与 model_save_fn 必须同时不为空。本函数的输入为一个已经创建好的文件夹,没有输出, - 请在函数内完成对模型的加载。 - :param delete_after_train: 在训练结束后是否删掉模型。 - """ super().__init__(monitor=monitor, larger_better=larger_better, must_have_monitor=True) if model_load_fn is not None: assert callable(model_load_fn), "`model_load_fn` must be a callable object." diff --git a/fastNLP/core/callbacks/lr_scheduler_callback.py b/fastNLP/core/callbacks/lr_scheduler_callback.py index ba496b5e..37d089bd 100644 --- a/fastNLP/core/callbacks/lr_scheduler_callback.py +++ b/fastNLP/core/callbacks/lr_scheduler_callback.py @@ -6,14 +6,14 @@ __all__ = [ class LRSchedCallback(Callback): - def __init__(self, scheduler, step_on:str='batch'): - """ - 根据 step_on 参数在合适的时机调用 scheduler 的 step 函数。 + """ + 根据 step_on 参数在合适的时机调用 scheduler 的 step 函数。 - :param scheduler: 实现了 step() 函数的对象 - :param step_on: 可选 ['batch', 'epoch'] 表示在何时调用 scheduler 的 step 函数。如果为 batch 的话在每次更新参数 - 之前调用;如果为 epoch 则是在一个 epoch 运行结束后调用。 - """ + :param scheduler: 实现了 step() 函数的对象 + :param step_on: 可选 ['batch', 'epoch'] 表示在何时调用 scheduler 的 step 函数。如果为 batch 的话在每次更新参数 + 之前调用;如果为 epoch 则是在一个 epoch 运行结束后调用。 + """ + def __init__(self, scheduler, step_on:str='batch'): assert hasattr(scheduler, 'step') and callable(scheduler.step), "The scheduler object should have a " \ "step function." self.scheduler = scheduler diff --git a/fastNLP/core/callbacks/more_evaluate_callback.py b/fastNLP/core/callbacks/more_evaluate_callback.py index 896f8865..2b2aa16e 100644 --- a/fastNLP/core/callbacks/more_evaluate_callback.py +++ b/fastNLP/core/callbacks/more_evaluate_callback.py @@ -11,6 +11,67 @@ from .topk_saver import TopkSaver class MoreEvaluateCallback(HasMonitorCallback): + """ + 当评测时需要调用不同的 evaluate_fn (例如在大部分生成任务中,一般使用训练 loss 作为训练过程中的 evaluate ;但同时在训练到 + 一定 epoch 数量之后,会让 model 生成的完整的数据评测 bleu 等。此刻就可能需要两种不同的 evaluate_fn ),只使用 Trainer + 无法满足需求,可以通过调用本 callback 进行。如果需要根据本 callback 中的评测结果进行模型保存,请传入 topk 以及 + topk_monitor 等相关参数。可以通过 evaluate_every 或 watch_monitor 控制触发进行 evaluate 的条件。 + + 如果设置了 evaluate 结果更好就保存的话,将按如下文件结构进行保存:: + + - folder/ + - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 + - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-{topk_monitor}_{monitor_value}/ # 满足topk条件存储文件名 + + :param dataloaders: 需要评估的数据 + :param metrics: 使用的 metrics 。 + :param evaluate_every: 用来控制 ``Trainer`` 内部的 ``Evaluator`` 验证的频率,其可以为负数、正数或者函数: + + 1. 为负数时表示每隔几个 ``epoch`` evaluate 一次; + 2. 为正数则表示每隔几个 ``batch`` evaluate 一次; + 3. 为函数时表示用户自己传入的用于控制 evaluate 的频率的函数,该函数的应该接受当前 trainer 对象作为参数,并 + 返回一个 bool 值,返回为 True 说明需要进行 evaluate ;将在每个 ``batch`` 结束后调用该函数判断是否需要 evaluate; + + .. note:: + + 如果参数 ``evaluate_every`` 为函数,其应当类似: + + >>> def my_evaluate_every(trainer) -> bool: + ... if (trainer.global_forward_batches+1) % 1000 == 0: + ... return True + ... else: + ... return False + + 该函数表示当每经过 1000 个 batch,``Trainer`` 中内置的 ``Evaluator`` 就会验证一次; + + 另一个需要注意的事情在于该函数会在每一次 batch 的结尾进行调用,当该函数返回 ``True`` 时,``Evaluator`` 才会进行验证; + :param watch_monitor: 这个值用来表示监控的 Trainer 中的 evaluate 结果的,当该值不为 None ,evaluate_every 失效。本参数的 + 意义是,当检测到 Trainer 中 evaluate results 的 {watch_monitor} 的结果更好时,则进行一次 evaluate 。该参数有两种 + 取值: (1) str 类型,监控的 metric 值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 最长公共字符串算法 找到最 + 匹配的那个作为 monitor ; (2) 也可以传入一个函数,接受参数为 evaluation 的结果(字典类型),返回一个 float 值作为 monitor + 的结果,如果当前结果中没有相关的monitor 值请返回 None 。 + :param watch_monitor_larger_better: watch_monitor 是否越大越好。 + :param evaluate_fn: 用来控制 `Evaluator` 在评测的前向传播过程中是调用哪一个函数,例如是 `model.evaluate_step` 还是 + `model.forward`;(1) 如果该值是 None,那么我们会默认使用 `evaluate_step` 当做前向传播的函数,如果在模型中没有 + 找到该方法,则使用 `model.forward` 函数;(2) 如果为 str 类型,则尝试从 model 中寻找该方法,找不到则报错。 + :param num_eval_sanity_batch: 在初始化 Evaluator 后运行多少个 sanity check 的 batch ,检测一下。 + :param topk: 如果需要根据当前 callback 中的 evaluate 结果保存模型或 Trainer ,可以通过设置 tokp 实现。(1)为 -1 表示每次 + evaluate 后都保存;(2)为 0 (默认),表示不保存;(3)为整数,表示保存性能最 topk 个。 + :param topk_monitor: 如果需要根据当前 callback 中的 evaluate 结果保存。这个参数是指在当前 callback 中的 evaluate 结果寻找 + :param topk_larger_better: topk_monitor 的值是否时越大越好。 + :param folder: 保存的文件夹,fastNLP 将在该文件下以时间戳创建子文件夹,并在里面保存。因此不同次运行可以将被保存到不同的 + 时间戳文件夹中。如果为 None ,默认使用当前文件夹。 + :param only_state_dict: 保存模型时是否只保存 state_dict 。当 model_save_fn 不为 None 时,该参数无效。 + :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 ``trainer+model`` 还是 只是 ``model`` 。如果 + 保存 ``trainer`` 对象的话,将会保存 :class:~fastNLP.Trainer 的相关状态,可以通过 :meth:`Trainer.load` 加载该断 + 点继续训练。如果保存的是 ``Model`` 对象,则可以通过 :meth:`Trainer.load_model` 加载该模型权重。 + :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 + 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 + :param save_evaluate_results: 是否保存 evaluate 的结果。如果为 True ,在保存 topk 模型的 folder 中还将额外保存一个 + ``fastnlp_evaluate_results.json`` 文件,记录当前的 results。仅在设置了 topk 的场景下有用,默认为 True 。 + :param save_kwargs: dict。更多的保存相关的参数。 + :param kwargs: 其它与 Evaluator 相关的初始化参数,如果不传入,将从 Trainer 中获取。 + """ def __init__(self, dataloaders, metrics:Dict, evaluate_every:Optional[Union[int, Callable]]=-1, watch_monitor:Union[str, Callable]=None, watch_monitor_larger_better:bool=True, evaluate_fn=None, num_eval_sanity_batch=2, @@ -18,48 +79,6 @@ class MoreEvaluateCallback(HasMonitorCallback): folder=None, only_state_dict=True, save_object='model', model_save_fn=None, save_evaluate_results=True, save_kwargs=None, **kwargs): - """ - 当评测时需要调用不同的 evaluate_fn (例如在大部分生成任务中,一般使用训练 loss 作为训练过程中的 evaluate ;但同时在训练到 - 一定 epoch 数量之后,会让 model 生成的完整的数据评测 bleu 等。此刻就可能需要两种不同的 evaluate_fn ),只使用 Trainer - 无法满足需求,可以通过调用本 callback 进行。如果需要根据本 callback 中的评测结果进行模型保存,请传入 topk 以及 - topk_monitor 等相关参数。可以通过 evaluate_every 或 watch_monitor 控制触发进行 evaluate 的条件。 - - 如果设置了 evaluate 结果更好就保存的话,将按如下文件结构进行保存:: - - - folder/ - - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 - - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-{topk_monitor}_{monitor_value}/ # 满足topk条件存储文件名 - - :param dataloaders: 需要评估的数据 - :param metrics: 使用的 metrics 。 - :param evaluate_every: 可以为负数、正数和函数;(1) 为负整数时表示每隔几个 epoch evaluate 一次;(2) 为正整数则表示每隔几个 batch - evaluate 一次;(3) 为函数时表示用户自己传入的用于控制 evaluate 的频率的函数,该函数的应该接受 trainer 对象作为参数,并返回 - 一个 bool 值,返回为 True 说明需要进行 evaluate ;将在每个 batch 结束后调用该函数判断是否需要 evaluate 。 - :param watch_monitor: 这个值用来表示监控的 Trainer 中的 evaluate 结果的,当该值不为 None ,evaluate_every 失效。本参数的 - 意义是,当检测到 Trainer 中 evaluate results 的 {watch_monitor} 的结果更好时,则进行一次 evaluate 。该参数有两种 - 取值: (1) str 类型,监控的 metric 值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 最长公共字符串算法 找到最 - 匹配的那个作为 monitor ; (2) 也可以传入一个函数,接受参数为 evaluation 的结果(字典类型),返回一个 float 值作为 monitor - 的结果,如果当前结果中没有相关的monitor 值请返回 None 。 - :param watch_monitor_larger_better: watch_monitor 是否越大越好。 - :param evaluate_fn: 用来控制 `Evaluator` 在评测的前向传播过程中是调用哪一个函数,例如是 `model.evaluate_step` 还是 - `model.forward`;(1) 如果该值是 None,那么我们会默认使用 `evaluate_step` 当做前向传播的函数,如果在模型中没有 - 找到该方法,则使用 `model.forward` 函数;(2) 如果为 str 类型,则尝试从 model 中寻找该方法,找不到则报错。 - :param num_eval_sanity_batch: 在初始化 Evaluator 后运行多少个 sanity check 的 batch ,检测一下。 - :param topk: 如果需要根据当前 callback 中的 evaluate 结果保存模型或 Trainer ,可以通过设置 tokp 实现。(1)为 -1 表示每次 - evaluate 后都保存;(2)为 0 (默认),表示不保存;(3)为整数,表示保存性能最 topk 个。 - :param topk_monitor: 如果需要根据当前 callback 中的 evaluate 结果保存。这个参数是指在当前 callback 中的 evaluate 结果寻找 - :param topk_larger_better: topk_monitor 的值是否时越大越好。 - :param folder: 保存的文件夹,fastNLP 将在该文件下以时间戳创建子文件夹,并在里面保存。因此不同次运行可以将被保存到不同的 - 时间戳文件夹中。如果为 None ,默认使用当前文件夹。 - :param only_state_dict: 保存模型时是否只保存 state_dict 。当 model_save_fn 不为 None 时,该参数无效。 - :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 trainer+model 还是 只是model 。 - :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 - 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 - :param save_evaluate_results: 是否保存 evaluate 的结果。如果为 True ,在保存 topk 模型的 folder 中还将额外保存一个 - fastnlp_evaluate_results.json 文件,记录当前的 results。仅在设置了 topk 的场景下有用,默认为 True 。 - :param save_kwargs: dict。更多的保存相关的参数。 - :param kwargs: 其它与 Evaluator 相关的初始化参数,如果不传入,将从 Trainer 中获取。请特别留意 evaluate_fn 的设置。 - """ super(MoreEvaluateCallback, self).__init__(watch_monitor, watch_monitor_larger_better, must_have_monitor=False) diff --git a/fastNLP/core/callbacks/progress_callback.py b/fastNLP/core/callbacks/progress_callback.py index 24eda36e..2618431f 100644 --- a/fastNLP/core/callbacks/progress_callback.py +++ b/fastNLP/core/callbacks/progress_callback.py @@ -39,25 +39,27 @@ def choose_progress_callback(progress_bar: Union[str, ProgressCallback]) -> Prog class RichCallback(ProgressCallback): + """ + 在训练过程中打印 rich progress bar 的 callback 。在 Trainer 中,默认就会使用这个 callback 来显示进度。如果需要定制这个 Callback 的 + 参数,请通过实例化本 Callback 并传入到 Trainer 中实现。 + + :param print_every: 多少个 batch 更新一次显示。 + :param loss_round_ndigit: 显示的 loss 保留多少位有效数字 + :param monitor: 监控的 metric 值。当检测到这个key的结果更好时,会打印出不同的颜色进行提示。 + + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param larger_better: 是否是 monitor 的结果越大越好。 + :param format_json: 是否格式化 json 再打印 + """ def __init__(self, print_every:int = 1, loss_round_ndigit:int = 6, monitor:str=None, larger_better:bool=True, format_json=True): - """ - - :param print_every: 多少个 batch 更新一次显示。 - :param loss_round_ndigit: 显示的 loss 保留多少位有效数字 - :param monitor: 监控的 metric 值。当检测到这个key的结果更好时,会打印出不同的颜色进行提示。 - - * 为 ``None`` - 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 - * 为 ``str`` - 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 - 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 - * 为 ``Callable`` - 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 - 的 ``monitor`` 值请返回 ``None`` 。 - :param larger_better: 是否是 monitor 的结果越大越好。 - :param format_json: 是否格式化 json 再打印 - """ super().__init__(monitor=monitor, larger_better=larger_better, must_have_monitor=False) self.print_every = print_every self.progress_bar = f_rich_progress diff --git a/fastNLP/core/callbacks/topk_saver.py b/fastNLP/core/callbacks/topk_saver.py index c629e9de..9b02e1b2 100644 --- a/fastNLP/core/callbacks/topk_saver.py +++ b/fastNLP/core/callbacks/topk_saver.py @@ -16,22 +16,24 @@ from .has_monitor_callback import ResultsMonitor class Saver: + """ + 执行保存的对象。保存的文件组织结构为:: + + - folder # 当前初始化的参数 + - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 + - folder_name # 由 save() 调用时传入。 + + :param folder: 保存在哪个文件夹下,默认为当前 folder 下。 + :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 ``trainer+model`` 还是 只是 ``model`` 。如果 + 保存 ``trainer`` 对象的话,将会保存 :class:~fastNLP.Trainer 的相关状态,可以通过 :meth:`Trainer.load` 加载该断 + 点继续训练。如果保存的是 ``Model`` 对象,则可以通过 :meth:`Trainer.load_model` 加载该模型权重。 + :param only_state_dict: 保存时是否仅保存权重,在 model_save_fn 不为 None 时无意义。 + :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 + 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 + :param kwargs: 更多需要传递给 Trainer.save() 或者 Trainer.save_model() 接口的参数。 + """ def __init__(self, folder:str=None, save_object:str='model', only_state_dict:bool=True, model_save_fn:Callable=None, **kwargs): - """ - 执行保存的对象。保存的文件组织结构为:: - - - folder # 当前初始化的参数 - - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 - - folder_name # 由 save() 调用时传入。 - - :param folder: 保存在哪个文件夹下,默认为当前 folder 下。 - :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 trainer+model 还是 只是model 。 - :param only_state_dict: 保存时是否仅保存权重,在 model_save_fn 不为 None 时无意义。 - :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 - 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 - :param kwargs: 更多需要传递给 Trainer.save() 或者 Trainer.save_model() 接口的参数。 - """ if folder is None: folder = Path.cwd().absolute() logger.info(f"Parameter `folder` is None, and we will use {folder} to save and load your model.") @@ -79,8 +81,8 @@ class Saver: """ 以 json 格式保存 results 到 path 中 - :param results: - :param path: + :param results: 一般是评测后的结果。 + :param path: 保存的文件名 :return: """ with open(path, 'w', encoding='utf8') as f: @@ -117,12 +119,12 @@ class Saver: class TopkQueue: - def __init__(self, topk): - """ - 用于维护处于 topk 的 key, value 对。 + """ + 用于维护处于 topk 的 key, value 对。 - :param int topk: 整数,-1 表示所有数据都是 topk 的; 如果是 0, 表示没有任何数据是满足 topk 的。 - """ + :param int topk: 整数,-1 表示所有数据都是 topk 的; 如果是 0, 表示没有任何数据是满足 topk 的。 + """ + def __init__(self, topk): assert isinstance(topk, int) self.topk = topk self.topk_dict = {} # 其中 key 为保存的内容, value 是对应的性能。 @@ -170,31 +172,39 @@ class TopkQueue: class TopkSaver(ResultsMonitor, Saver): + """ + 用来识别 topk 模型并保存,也可以仅当一个保存 Saver 使用。保存路径为:: + + - folder/ + - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 + - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-{topk_monitor}_{monitor_value}/ # 满足topk条件存储文件名 + + :param topk: 保存 topk 多少的模型,-1 为保存所有模型;0 为都不保存;大于 0 的数为保存 topk 个。 + :param monitor: 监控的 metric 值。 + + * 为 ``None`` + 将尝试使用 :class:`~fastNLP.Trainer` 中设置 `monitor` 值(如果有设置)。 + * 为 ``str`` + 尝试直接使用该名称从 ``evaluation`` 结果中寻找,如果在 ``evaluation`` 结果中没有找到完全一致的名称,将 + 使用 最长公共字符串算法 从 ``evaluation`` 结果中找到最匹配的那个作为 ``monitor`` 。 + * 为 ``Callable`` + 接受参数为 ``evaluation`` 的结果(字典类型),返回一个 ``float`` 值作为 ``monitor`` 的结果,如果当前结果中没有相关 + 的 ``monitor`` 值请返回 ``None`` 。 + :param larger_better: 该 monitor 是否越大越好。 + :param folder: 保存在哪个文件夹下,默认为当前 folder 下。 + :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 ``trainer+model`` 还是 只是 ``model`` 。如果 + 保存 ``trainer`` 对象的话,将会保存 :class:~fastNLP.Trainer 的相关状态,可以通过 :meth:`Trainer.load` 加载该断 + 点继续训练。如果保存的是 ``Model`` 对象,则可以通过 :meth:`Trainer.load_model` 加载该模型权重。 + :param only_state_dict: 保存时是否仅保存权重,在 model_save_fn 不为 None 时无意义。 + :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 + 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 + :param save_evaluate_results: 是否保存 evaluate 的结果。如果为 True ,在保存 topk 模型的 folder 中还将额外保存一个 + ``fastnlp_evaluate_results.json`` 文件,记录当前的 metric results 。仅在设置了 topk 的场景下有用,默认为 True 。 + :param kwargs: 更多需要传递给 Trainer.save() 或者 Trainer.save_model() 接口的参数。 + """ def __init__(self, topk:int=0, monitor:str=None, larger_better:bool=True, folder:str=None, save_object:str='model', only_state_dict:bool=True, model_save_fn:Callable=None, save_evaluate_results:bool=True, **kwargs): - """ - 用来识别 topk 模型并保存,也可以仅当一个保存 Saver 使用。保存路径为:: - - - folder/ - - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 - - {save_object}-epoch_{epoch_idx}-batch_{global_batch_idx}-{topk_monitor}_{monitor_value}/ # 满足topk条件存储文件名 - - :param topk: 保存 topk 多少的模型,-1 为保存所有模型;0 为都不保存;大于 0 的数为保存 topk 个。 - :param monitor: 监控哪个指标判断是否是 topk 的。监控的 metric 值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 - 最长公共字符串算法 找到最匹配的那个作为 monitor 。如果为 None,将尝试使用 Trainer 设置的 monitor 。也可以传入一个函数, - 接受参数为 evaluation 的结果(字典类型),返回一个 float 值作为 monitor 的结果,如果当前结果中没有相关的 monitor 值请 - 返回 None 。 - :param larger_better: 该 monitor 是否越大越好。 - :param folder: 保存在哪个文件夹下,默认为当前 folder 下。 - :param save_object: 可选 ['trainer', 'model'],表示在保存时的保存对象为 trainer+model 还是 只是model 。 - :param only_state_dict: 保存时是否仅保存权重,在 model_save_fn 不为 None 时无意义。 - :param model_save_fn: 个性化的保存函数,当触发保存操作时,就调用这个函数,这个函数应当接受一个文件夹作为参数,不返回任何东西。 - 如果传入了 model_save_fn 函数,fastNLP 将不再进行模型相关的保存。在多卡场景下,我们只在 rank 0 上会运行该函数。 - :param save_evaluate_results: 是否保存 evaluate 的结果。如果为 True ,在保存 topk 模型的 folder 中还将额外保存一个 - fastnlp_evaluate_results.json 文件,记录当前的 results。仅在设置了 topk 的场景下有用,默认为 True 。 - :param kwargs: 更多需要传递给 Trainer.save() 或者 Trainer.save_model() 接口的参数。 - """ ResultsMonitor.__init__(self, monitor, larger_better) Saver.__init__(self, folder, save_object, only_state_dict, model_save_fn, **kwargs) diff --git a/fastNLP/core/callbacks/torch_callbacks/torch_grad_clip_callback.py b/fastNLP/core/callbacks/torch_callbacks/torch_grad_clip_callback.py index cc0e1e98..40a03b89 100644 --- a/fastNLP/core/callbacks/torch_callbacks/torch_grad_clip_callback.py +++ b/fastNLP/core/callbacks/torch_callbacks/torch_grad_clip_callback.py @@ -1,25 +1,26 @@ __all__ = [ 'TorchGradClipCallback' ] +from typing import Union, List from ..callback import Callback class TorchGradClipCallback(Callback): - def __init__(self, clip_value=1, clip_type='norm', parameters=None): - r""" - 在每次 optimizer update 之前将 parameter 进行 clip + r""" + 在每次 optimizer update 之前将 parameter 进行 clip 。 - :param float clip_value: 将gradient 限制到[-clip_value, clip_value]。clip_value应该为正数 - :param str clip_type: 支持'norm', 'value'两种: + :param clip_value: 将gradient 限制到[-clip_value, clip_value]。clip_value应该为正数 + :param clip_type: 支持'norm', 'value'两种: - 1. 'norm', 将gradient的norm rescale到[-clip_value, clip_value] - 2. 'value', 将gradient限制在[-clip_value, clip_value], - 小于-clip_value的gradient被赋值为-clip_value; - 大于clip_value的gradient被赋值为clip_value. + 1. 'norm', 将gradient的norm rescale到[-clip_value, clip_value] + 2. 'value', 将gradient限制在[-clip_value, clip_value], + 小于-clip_value的gradient被赋值为-clip_value;大于clip_value的gradient被赋值为clip_value. - :param None,torch.Tensor,List[torch.Tensor] parameters: 一般通过model.parameters()获得。 - 如果为None则默认对 Trainer 的 optimizers 中所有参数进行梯度裁剪。 - """ + :param None,torch.Tensor,List[torch.Tensor] parameters: 一般通过model.parameters()获得。 + 如果为None则默认对 Trainer 的 optimizers 中所有参数进行梯度裁剪。 + """ + def __init__(self, clip_value:int=1, clip_type:str='norm', + parameters:Union["torch.Tensor", List["torch.Tensor"]]=None): super().__init__() from torch import nn diff --git a/fastNLP/core/callbacks/torch_callbacks/torch_lr_sched_callback.py b/fastNLP/core/callbacks/torch_callbacks/torch_lr_sched_callback.py index 3d428d47..006a39c0 100644 --- a/fastNLP/core/callbacks/torch_callbacks/torch_lr_sched_callback.py +++ b/fastNLP/core/callbacks/torch_callbacks/torch_lr_sched_callback.py @@ -2,21 +2,23 @@ __all__ = [ 'TorchWarmupCallback' ] import math +from typing import Union from ..callback import Callback class TorchWarmupCallback(Callback): - def __init__(self, warmup=0.1, schedule='constant'): - r""" - 调整 learning rate 的 callback 。仅在实际发生参数更新的情况下 - - :param int,float warmup: 如果warmup为int,则在该step之前,learning rate根据schedule的策略变化; 如果warmup为float, - 如0.1, 则前10%的step是按照schedule策略调整learning rate。 - :param str schedule: 以哪种方式调整。 - linear: 前warmup的step上升到指定的learning rate(从Trainer中的optimizer处获取的), 后warmup的step下降到0; - constant前warmup的step上升到指定learning rate,后面的step保持learning rate. - """ + r""" + 调整 learning rate 的 callback 。 + + :param warmup: 如果warmup为int,则在该step之前,learning rate根据schedule的策略变化; 如果warmup为float, + 如0.1, 则前10%的step是按照schedule策略调整learning rate。 + :param schedule: 以哪种方式调整。 + + 1. linear: 前warmup的step上升到指定的learning rate(从Trainer中的optimizer处获取的), 后warmup的step下降到0; + 2. constant前warmup的step上升到指定learning rate,后面的step保持learning rate. + """ + def __init__(self, warmup:Union[int, float]=0.1, schedule:str='constant'): super().__init__() self.warmup = max(warmup, 0.) diff --git a/fastNLP/core/collators/collator.py b/fastNLP/core/collators/collator.py index aef9de4c..2978de89 100644 --- a/fastNLP/core/collators/collator.py +++ b/fastNLP/core/collators/collator.py @@ -82,16 +82,26 @@ def _get_backend() -> str: class Collator: - def __init__(self, backend='auto'): - """ - 用于 pad 数据的对象。会自动将所有能够 pad (由 fastNLP 根据数据判定能否 pad )的数据都进行 pad 操作,默认 pad 的值为 0。 - 可使用 set_pad() 函数调整。如果有些 field 不想输出,可以使用 set_ignore() 函数进行设置。Collator 在第一次进行 pad 的 - 时候自动根据设置以及数据情况,为每个 field 获取一个 padder ,在之后的每次调用中,都将使用对应的 Padder 给对应的 field 。 + """ + 用于 pad 数据的对象。会自动将所有能够 pad (由 fastNLP 根据数据判定能否 pad )的数据都进行 pad 操作,默认 pad 的值为 0。 + 哦安定一个 field 是否可以 pad 的方式为:(1)当前这个 field 是否所有对象都是一样的数据类型;(因此,如果某 field 的数据有些是float + 有些是 int 将知道该 field 被判定为不可 pad 类型。)(2)当前这个 field 是否每个 sample 都具有一样的深度;(因此,例如有个 field 的 + 数据转为 batch 类型后为 [1, [1,2]], 会被判定为不可 pad ,因为第一个 sample 与 第二个 sample 深度不同)(3)当前这个 field 的类 + 型是否是可以 pad (例如 str 类型的数据)。可以通过设置 logger.setLevel('debug') 来打印是判定不可 pad 的原因。 - :param backend: 对于可以 pad 的 field,使用哪种 tensor,支持 ['torch','jittor','paddle','numpy','raw', auto, None]。 - 若为 'auto' ,则在进行 pad 的时候会根据调用的环境决定其 backend 。该参数对不能进行 pad 的数据没用影响,不能 pad - 的数据返回一定是 list 。 - """ + todo 补充 code example 。 + + 如果需要将某个本可以 pad 的 field 设置为不可 pad ,则可以通过 :meth:`~fastNLP.Collator.set_pad` 的 pad_val 设置为 None 实现。 + 如果需要某些 field 不要包含在 pad 之后的结果中,可以使用 :meth:`~fastNLP.Collator.set_ignore` 进行设置。 + + Collator 在第一次进行 pad 的时候自动根据设置以及数据情况,为每个 field 获取一个 padder ,在之后的每次调用中,都将使用对应 + 的 Padder 给对应的 field 。 + + :param backend: 对于可以 pad 的 field,使用哪种 tensor,支持 ['torch','jittor','paddle','numpy','raw', auto, None]。 + 若为 'auto' ,则在进行 pad 的时候会根据调用的环境决定其 backend 。该参数对不能进行 pad 的数据没用影响,不能 pad + 的数据返回一定是 list 。 + """ + def __init__(self, backend='auto'): self.unpack_batch_func = None self.pack_batch_func = None self.ignore_fields = set() @@ -264,9 +274,8 @@ class Collator: def set_ignore(self, *field_names) -> "Collator": """ 如果有的内容不希望输出,可以在此处进行设置,被设置的 field 将在 batch 的输出中被忽略。 - Example:: - collator.set_ignore('field1', 'field2') + >>> collator = Collator().set_ignore('field1', 'field2') :param field_names: 需要忽略的 field 的名称。如果 Dataset 的 __getitem__ 方法返回的是 dict 类型的,则可以直接使用对应的 field 的 key 来表示,如果是 nested 的 dict,可以使用元组来表示,例如 {'a': {'b': 1}} 中的使用 ('a', 'b'); 如果 diff --git a/fastNLP/core/collators/packer_unpacker.py b/fastNLP/core/collators/packer_unpacker.py index 033cfca5..2b78ea0a 100644 --- a/fastNLP/core/collators/packer_unpacker.py +++ b/fastNLP/core/collators/packer_unpacker.py @@ -9,9 +9,9 @@ class MappingPackerUnpacker: """ 将 Sequence[Mapping] 转为 Dict 。例如 [{'a': [1, 2], 'b': 1}, {'a': [3], 'b': 2}] -> {'a': [[1, 2], [3]], 'b': [1, 2]} - :param batch: - :param ignore_fields: - :param input_fields: + :param batch: 需要 unpack 的 batch 数据。 + :param ignore_fields: 需要忽略的 field 。 + :param input_fields: 需要设置为 input 的 field 。 :return: """ dict_batch = defaultdict(list) @@ -29,13 +29,13 @@ class MappingPackerUnpacker: class NestedMappingPackerUnpacker: @staticmethod - def unpack_batch(batch:Sequence[Mapping], ignore_fields:set, input_fields:Dict): + def unpack_batch(batch:Sequence[Mapping], ignore_fields:set, input_fields:Dict)->Dict: """ 将 nested 的 dict 中的内容展开到一个 flat dict 中 - :param batch: + :param batch: 需要 unpack 的 batch 数据。 :param ignore_fields: 需要忽略的 field 。 - :param input_fields: 不需要继续往下衍射的 + :param input_fields: 需要设置为 input 的 field 。 :return: """ dict_batch = defaultdict(list) @@ -72,8 +72,9 @@ class SequencePackerUnpacker: """ 将 Sequence[Sequence] 转为 Mapping 。例如 [[[1, 2], 2], [[3], 2]] -> {'_0': [[1, 2], [3]], '_1': [1, 2]} - :param batch: - :param ignore_fields: 需要忽略的field + :param batch: 需要 unpack 的 batch 数据。 + :param ignore_fields: 需要忽略的 field 。 + :param input_fields: 需要设置为 input 的 field 。 :return: """ dict_batch = defaultdict(list) diff --git a/fastNLP/core/collators/padders/jittor_padder.py b/fastNLP/core/collators/padders/jittor_padder.py index 6c30d835..5fcc469b 100644 --- a/fastNLP/core/collators/padders/jittor_padder.py +++ b/fastNLP/core/collators/padders/jittor_padder.py @@ -90,7 +90,7 @@ class JittorNumberPadder(Padder): super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): return jittor.Var(np.array(batch_field, dtype=dtype)) @@ -107,7 +107,7 @@ class JittorSequencePadder(Padder): super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): tensor = get_padded_jittor_tensor(batch_field, dtype=dtype, pad_val=pad_val) return tensor @@ -125,7 +125,7 @@ class JittorTensorPadder(Padder): super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): try: if not isinstance(batch_field[0], jittor.Var): batch_field = [jittor.Var(np.array(field.tolist(), dtype=dtype)) for field in batch_field] diff --git a/fastNLP/core/collators/padders/numpy_padder.py b/fastNLP/core/collators/padders/numpy_padder.py index 1113c91a..5f2d70a9 100644 --- a/fastNLP/core/collators/padders/numpy_padder.py +++ b/fastNLP/core/collators/padders/numpy_padder.py @@ -30,53 +30,65 @@ def _get_dtype(ele_dtype, dtype, class_name): class NumpyNumberPadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 可以将形如 [1, 2, 3] 这类的数据转为 np.array([1, 2, 3]) + """ + 可以将形如 [1, 2, 3] 这类的数据转为 np.array([1, 2, 3]) 。可以通过: + + >>> NumpyNumberPadder.pad([1, 2, 3]) + + 使用。 - :param pad_val: 该值无意义 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 - :param dtype: 输出的数据的 dtype 是什么 - """ + :param pad_val: 该值无意义 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 + :param dtype: 输出的数据的 dtype 是什么 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): dtype = _get_dtype(ele_dtype, dtype, self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): return np.array(batch_field, dtype=dtype) class NumpySequencePadder(Padder): + """ + 将类似于 [[1], [1, 2]] 的内容 pad 为 np.array([[1, 0], [1, 2]]) 可以 pad 多重嵌套的数据。 + 可以通过以下的方式直接使用: + + >>> NumpySequencePadder.pad([[1], [1, 2]], pad_val=-100, dtype=float) + [[ 1. -100.] + [ 1. 2.]] + + :param pad_val: pad 的值是多少。 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 + :param dtype: 输出的数据的 dtype 是什么 + """ def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 将类似于 [[1], [1, 2]] 的内容 pad 为 np.array([[1, 0], [1, 2]]) 可以 pad 多重嵌套的数据。 - - :param pad_val: pad 的值是多少。 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 - :param dtype: 输出的数据的 dtype 是什么 - """ dtype = _get_dtype(ele_dtype, dtype, self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): return get_padded_numpy_array(batch_field, dtype=dtype, pad_val=pad_val) class NumpyTensorPadder(Padder): + """ + pad 类似于 [np.array([3, 4]), np.array([1])] 的 field 。若内部元素不为 np.ndarray ,则必须含有 tolist() 方法。 + + >>> NumpyTensorPadder.pad([np.array([3, 4]), np.array([1])], pad_val=-100) + [[ 3. 4.] + [ 1. -100.]] + :param pad_val: pad 的值是多少。 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 + :param dtype: 输出的数据的 dtype 是什么 + """ def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - pad 类似于 [np.array([3, 4], np.array([1])] 的 field 。若内部元素不为 np.ndarray ,则必须含有 tolist() 方法。 - - :param pad_val: pad 的值是多少。 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 - :param dtype: 输出的数据的 dtype 是什么 - """ dtype = _get_dtype(ele_dtype, dtype, self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): try: if not isinstance(batch_field[0], np.ndarray): batch_field = [np.array(field.tolist(), dtype=dtype) for field in batch_field] diff --git a/fastNLP/core/collators/padders/padder.py b/fastNLP/core/collators/padders/padder.py index 065ce39f..6a75b634 100644 --- a/fastNLP/core/collators/padders/padder.py +++ b/fastNLP/core/collators/padders/padder.py @@ -1,5 +1,9 @@ class Padder: + """ + 所有 Padder 对象父类,所有的 Padder 对象都会实现 pad(batch_field, pad_val=0, dtype=None) 的静态函数。 + + """ def __init__(self, pad_val, dtype): self.pad_val = pad_val self.dtype = dtype @@ -8,19 +12,19 @@ class Padder: return self.pad(batch_field=batch_field, pad_val=self.pad_val, dtype=self.dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): raise NotImplementedError() class NullPadder(Padder): - def __init__(self, ele_dtype=None, pad_val=None, dtype=None): - """ - 不进行任何 检查 与 pad 的空 padder 。 + """ + 不进行任何 检查 与 pad 的空 padder 。 - :param ele_dtype: - :param pad_val: - :param dtype: - """ + :param ele_dtype: + :param pad_val: + :param dtype: + """ + def __init__(self, ele_dtype=None, pad_val=None, dtype=None): super().__init__(pad_val=pad_val, dtype=dtype) def __call__(self, batch_field): diff --git a/fastNLP/core/collators/padders/paddle_padder.py b/fastNLP/core/collators/padders/paddle_padder.py index 826d21c7..a891eb9f 100644 --- a/fastNLP/core/collators/padders/paddle_padder.py +++ b/fastNLP/core/collators/padders/paddle_padder.py @@ -80,55 +80,55 @@ def _get_dtype(ele_dtype, dtype, class_name): class PaddleNumberPadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 可以将形如 [1, 2, 3] 这类的数据转为 paddle.Tensor([1, 2, 3]) + """ + 可以将形如 [1, 2, 3] 这类的数据转为 paddle.Tensor([1, 2, 3]) - :param pad_val: 该值无意义 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 paddle.tensor 类型。 - :param dtype: 输出的数据的 dtype 是什么。如 int, float, 'int32' 等 - """ + :param pad_val: 该值无意义 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 paddle.tensor 类型。 + :param dtype: 输出的数据的 dtype 是什么。如 int, float, 'int32' 等 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): # 仅当 ele_dtype 是 python number/ numpy number 或者 tensor dtype = _get_dtype(ele_dtype, dtype, class_name=self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): return paddle.to_tensor(batch_field, dtype=dtype) class PaddleSequencePadder(Padder): - def __init__(self, ele_dtype=None, pad_val=0, dtype=None): - """ - 将类似于 [[1], [1, 2]] 的内容 pad 为 paddle.Tensor([[1, 0], [1, 2]]) 可以 pad 多重嵌套的数据。 + """ + 将类似于 [[1], [1, 2]] 的内容 pad 为 paddle.Tensor([[1, 0], [1, 2]]) 可以 pad 多重嵌套的数据。 - :param pad_val: pad 的值。 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 paddle.tensor 类型。 - :param dtype: 输出的数据的 dtype 是什么。如 int, float, 'int32' 等 - """ + :param pad_val: pad 的值。 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 paddle.tensor 类型。 + :param dtype: 输出的数据的 dtype 是什么。如 int, float, 'int32' 等 + """ + def __init__(self, ele_dtype=None, pad_val=0, dtype=None): dtype = _get_dtype(ele_dtype, dtype, class_name=self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): tensor = get_padded_paddle_tensor(batch_field, dtype=dtype, pad_val=pad_val) return tensor class PaddleTensorPadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 目前支持 [paddle.tensor([3, 2], paddle.tensor([2, 1])] 类似的,若内部元素不为 paddle.tensor ,则必须含有 tolist() 方法。 + """ + 目前支持 [paddle.tensor([3, 2], paddle.tensor([2, 1])] 类似的,若内部元素不为 paddle.tensor ,则必须含有 tolist() 方法。 - :param pad_val: pad 的值。 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 paddle.tensor 类型。 - :param dtype: 输出的数据的 dtype 是什么。如 int, float, 'int32' 等 - """ + :param pad_val: pad 的值。 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 paddle.tensor 类型。 + :param dtype: 输出的数据的 dtype 是什么。如 int, float, 'int32' 等 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): dtype = _get_dtype(ele_dtype, dtype, class_name=self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): try: if not isinstance(batch_field[0], paddle.Tensor): batch_field = [np.array(field.tolist()) for field in batch_field] diff --git a/fastNLP/core/collators/padders/raw_padder.py b/fastNLP/core/collators/padders/raw_padder.py index bc3bbaee..91d97333 100644 --- a/fastNLP/core/collators/padders/raw_padder.py +++ b/fastNLP/core/collators/padders/raw_padder.py @@ -26,14 +26,14 @@ def _get_dtype(ele_dtype, dtype, class_name): class RawNumberPadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 可以将形如 [1, 2, 3] 这类的数据转为 [1, 2, 3] 。实际上该 padder 无意义。 + """ + 可以将形如 [1, 2, 3] 这类的数据转为 [1, 2, 3] 。实际上该 padder 无意义。 - :param pad_val: 该值无意义 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 - :param dtype: 输出的数据的 dtype 是什么 - """ + :param pad_val: 该值无意义 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 + :param dtype: 输出的数据的 dtype 是什么 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): dtype = _get_dtype(ele_dtype, dtype, self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @@ -41,24 +41,24 @@ class RawNumberPadder(Padder): return batch_field @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): raise NotImplementedError() class RawSequencePadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 将类似于 [[1], [1, 2]] 的内容 pad 为 [[1, 0], [1, 2]] 。可以 pad 多重嵌套的数据。 + """ + 将类似于 [[1], [1, 2]] 的内容 pad 为 [[1, 0], [1, 2]] 。可以 pad 多重嵌套的数据。 - :param pad_val: pad 的值 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 - :param dtype: 输出的数据的 dtype 是什么 - """ + :param pad_val: pad 的值 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 + :param dtype: 输出的数据的 dtype 是什么 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): dtype = _get_dtype(ele_dtype, dtype, self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): """ :param batch_field: @@ -70,19 +70,19 @@ class RawSequencePadder(Padder): class RawTensorPadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 将类似于 [[1], [1, 2]] 的内容 pad 为 [[1, 0], [1, 2]] 。可以 pad 多重嵌套的数据。 + """ + 将类似于 [[1], [1, 2]] 的内容 pad 为 [[1, 0], [1, 2]] 。可以 pad 多重嵌套的数据。 - :param pad_val: pad 的值 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 - :param dtype: 输出的数据的 dtype 是什么 - """ + :param pad_val: pad 的值 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 np.array 类型。 + :param dtype: 输出的数据的 dtype 是什么 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): dtype = _get_dtype(ele_dtype, dtype, self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): """ :param batch_field: diff --git a/fastNLP/core/collators/padders/torch_padder.py b/fastNLP/core/collators/padders/torch_padder.py index aaa0d4e9..9ef2a12d 100644 --- a/fastNLP/core/collators/padders/torch_padder.py +++ b/fastNLP/core/collators/padders/torch_padder.py @@ -64,54 +64,61 @@ def _get_dtype(ele_dtype, dtype, class_name): class TorchNumberPadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 可以将形如 [1, 2, 3] 这类的数据转为 torch.Tensor([1, 2, 3]) + """ + 可以将形如 [1, 2, 3] 这类的数据转为 torch.Tensor([1, 2, 3]) - :param pad_val: 该值无意义 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 torch.tensor 类型。 - :param dtype: 输出的数据的 dtype 是什么。如 torch.long, torch.float32, int, float 等 - """ + :param pad_val: 该值无意义 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 torch.tensor 类型。 + :param dtype: 输出的数据的 dtype 是什么。如 torch.long, torch.float32, int, float 等 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): dtype = _get_dtype(ele_dtype, dtype, class_name=self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): return torch.tensor(batch_field, dtype=dtype) class TorchSequencePadder(Padder): - def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 将类似于 [[1], [1, 2]] 的内容 pad 为 torch.Tensor([[1, 0], [1, 2]]) 可以 pad 多重嵌套的数据。 + """ + 将类似于 [[1], [1, 2]] 的内容 pad 为 torch.Tensor([[1, 0], [1, 2]]) 可以 pad 多重嵌套的数据。 - :param pad_val: 需要 pad 的值。 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 torch.tensor 类型。 - :param dtype: 输出的数据的 dtype 是什么。如 torch.long, torch.float32, int, float 等 - """ + :param pad_val: 需要 pad 的值。 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 torch.tensor 类型。 + :param dtype: 输出的数据的 dtype 是什么。如 torch.long, torch.float32, int, float 等 + """ + def __init__(self, pad_val=0, ele_dtype=None, dtype=None): dtype = _get_dtype(ele_dtype, dtype, class_name=self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): tensor = get_padded_torch_tensor(batch_field, dtype=dtype, pad_val=pad_val) return tensor class TorchTensorPadder(Padder): + """ + 目前支持 [torch.tensor([3, 2], torch.tensor([1])] 类似的。若内部元素不为 torch.tensor ,则必须含有 tolist() 方法。 + + >>> TorchTensorPadder.pad([np.array([3, 4]), np.array([1])], pad_val=-100) + [[ 3. 4.] + [ 1. -100.]] + >>> TorchTensorPadder.pad([torch.LongTensor([3, 4]), torch.LongTensor([1])], pad_val=-100) + tensor([[ 3, 4], + [ 1, -100]]) + + :param pad_val: 需要 pad 的值。 + :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 torch.tensor 类型。 + :param dtype: 输出的数据的 dtype 是什么。如 torch.long, torch.float32, int, float 等 + """ def __init__(self, pad_val=0, ele_dtype=None, dtype=None): - """ - 目前支持 [torch.tensor([3, 2], torch.tensor([1])] 类似的。若内部元素不为 torch.tensor ,则必须含有 tolist() 方法。 - - :param pad_val: 需要 pad 的值。 - :param ele_dtype: 用于检测当前 field 的元素类型是否可以转换为 torch.tensor 类型。 - :param dtype: 输出的数据的 dtype 是什么。如 torch.long, torch.float32, int, float 等 - """ dtype = _get_dtype(ele_dtype, dtype, class_name=self.__class__.__name__) super().__init__(pad_val=pad_val, dtype=dtype) @staticmethod - def pad(batch_field, pad_val, dtype): + def pad(batch_field, pad_val=0, dtype=None): device = None try: if not isinstance(batch_field[0], torch.Tensor): diff --git a/fastNLP/core/controllers/evaluator.py b/fastNLP/core/controllers/evaluator.py index 8ac35ad2..7ef15392 100644 --- a/fastNLP/core/controllers/evaluator.py +++ b/fastNLP/core/controllers/evaluator.py @@ -20,6 +20,114 @@ from fastNLP.core.log import logger class Evaluator: + """ + 用于对数据进行评测。 + + :param model: 待测试的模型,如果传入的 driver 为 Driver 实例,该参数将被忽略。 + :param dataloaders: 待评测的数据集。如果为多个,请使用 dict 传入。 + :param metrics: 使用的 metric 。必须为 dict 类型,其中 key 为 metric 的名称,value 为一个 Metric 对象。支持 fastNLP 的 + metric ,torchmetrics,allennlpmetrics 等。 + :param driver: 训练模型所使用的具体的驱动模式,应当为以下选择中的一个:``["torch", "jittor", "paddle"]`` + 其中 "torch" 表示使用 ``TorchSingleDriver`` 或者 ``TorchDDPDriver``,具体使用哪一种取决于参数 ``device`` + 的设置。 + :param device: 该参数用来指定具体训练时使用的机器;注意当该参数仅当您通过 `torch.distributed.launch/run` 启动时可以为 None, + 此时 fastNLP 不会对模型和数据进行设备之间的移动处理,但是你可以通过参数 `input_mapping` 和 `output_mapping` 来实现设备之间 + 数据迁移的工作(通过这两个参数传入两个处理数据的函数);同时你也可以通过在 kwargs 添加参数 "data_device" 来让我们帮助您将数据 + 迁移到指定的机器上(注意这种情况理应只出现在用户在 Trainer 实例化前自己构造 DDP 的场景); + + device 的可选输入如下所示: + + * *str*: 例如 'cpu', 'cuda', 'cuda:0', 'cuda:1' 等; + * *torch.device*: 例如 'torch.device("cuda:0")'; + * *int*: 将使用 ``device_id`` 为该值的 ``gpu`` 进行训练;如果值为 -1,那么默认使用全部的显卡,此时使用的 driver 实例是 `TorchDDPDriver`; + * *list(int)*: 如果多于 1 个device,应当通过该种方式进行设定;注意此时我们一定会使用 ``TorchDDPDriver``,不管您传入的列表的长度是 1 还是其它值; + * *None*: 仅当用户自己通过训练框架提供的并行训练启动脚本开启 ddp 进程时为 None; + + .. note:: + + 如果希望使用 ``TorchDDPDriver``,在初始化 ``Trainer`` 时您应当使用:: + + Trainer(driver="torch", device=[0, 1]) + + 注意如果这时 ``device=[0]``,我们仍旧会使用 ``TorchDDPDriver``。 + + 如果希望使用 ``TorchSingleDriver``,则在初始化 ``Trainer`` 时您应当使用:: + + Trainer(driver="torch", device=0) + + .. warning:: + + 注意参数 ``device`` 仅当您通过 pytorch 或者其它训练框架自身的并行训练启动脚本启动 ddp 训练时才允许为 ``None``! + + 例如,当您使用:: + + python -m torch.distributed.launch --nproc_per_node 2 train.py + + 来使用 ``TorchDDPDriver`` 时,此时参数 ``device`` 不再有效(不管您是否自己初始化 ``init_process_group``),我们将直接 + 通过 ``torch.device(f"cuda:{local_rank}")`` 来获取当前进程所使用的的具体的 gpu 设备。因此此时您需要使用 ``os.environ["CUDA_VISIBLE_DEVICES"]`` + 来指定要使用的具体的 gpu 设备。 + + 另一点需要注意的是,当您没有选择自己初始化 ``init_process_group`` 时,我们仍旧会帮助您把模型和数据迁移到当前进程所使用的 + 具体的 gpu 设备上。但是如果您选择自己在 ``Trainer`` 初始化前(意味着在 ``driver`` 的 ``setup`` 前)初始化 ``init_process_group``, + 那么对于模型的迁移应当完全由您自己来完成。此时对于数据的迁移,如果您在 ``Trainer`` 初始化时指定了参数 ``data_device``,那么 + 我们会将数据迁移到 ``data_device`` 上;如果其为 None,那么将数据迁移到正确的设备上应当由您自己来完成。 + + 对于使用 ``TorchDDPDriver`` 的更多细节,请见 :class:`fastNLP.core.drivers.torch_driver.TorchDDPDriver`。 + + :param evaluate_batch_step_fn: 定制每次 evaluate batch 执行的函数。该函数应接受的两个参数为 `evaluator` 和 `batch`, + 不需要有返回值;可以参考 :meth:`~fastNLP.core.controllers.loops.EvaluateBatchLoop.batch_step_fn` + 函数。 + :param evaluate_fn: 用来控制 `Evaluator` 在评测的前向传播过程中是调用哪一个函数,例如是 `model.evaluate_step` 还是 + `model.forward`;(1) 如果该值是 None,那么我们会默认使用 `evaluate_step` 函数,如果在模型中没有 + 找到该方法,则使用 `model.forward` 函数;(2) 如果为 str 类型,则尝试从 model 中寻找该方法,找不到则报错。 + :param input_mapping: 应当为一个字典或者一个函数,表示在当前 step 拿到一个 batch 的数据后,应当做怎样的映射处理: + + 1. 如果 ``input_mapping`` 是一个字典: + + 1. 如果此时 batch 也是一个 ``Dict``,那么我们会把 batch 中同样在 ``input_mapping`` 中的 key 修改为 ``input_mapping`` 的对应 ``key`` 的 ``value``; + 2. 如果此时 batch 是一个 ``dataclass``,那么我们会先将其转换为一个 ``Dict``,然后再进行上述转换; + 3. 如果此时 batch 此时是其它类型,那么我们将会直接报错; + 2. 如果 ``input_mapping`` 是一个函数,那么对于取出的 batch,我们将不会做任何处理,而是直接将其传入该函数里,并将其返回值 + 送入模型中; + + :param output_mapping: 应当为一个字典或者一个函数,表示在当前 step 拿到一个 model 的返回值后,应当做怎样的映射处理: + + 1. 如果 ``output_mapping`` 是一个字典: + + 1. 如果此时 batch 也是一个 ``Dict``,那么我们会把输出中同样在 ``output_mapping`` 中的 key 修改为 ``output_mapping`` 的对应 ``key`` 的 ``value``; + 例如输出结果为 {'a': 1},而 output_mapping={'a':'b'} ,那么结果就是 {'b': 1} + 2. 如果此时 batch 是一个 ``dataclass``,那么我们会先将其转换为一个 ``Dict``,然后再进行上述转换; + 3. 如果此时 batch 此时是其它类型,那么我们将会直接报错; + 2. 如果 ``output_mapping`` 是一个函数,我们将会把结果传入该函数里,并将其返回值送入到 metric 中。 + + :param model_wo_auto_param_call: 是否关闭在训练时调用我们的 auto_param_call 来自动匹配 batch 和 evaluate_fn 函数的参数的行为; + 如果该值为 True,并且当 batch 为字典时,我们会根据 evaluate_fn 所需要的参数从 batch 中提取对应的对象,传入到 evaluate_fn 函数中;如果该值 + 为 False,那么我们会将 batch 直接透传给 evaluate_fn 函数。 + :param fp16: 是否使用 fp16 。 + :param verbose: 是否打印 evaluate 的结果。 + :kwargs: + * *torch_kwargs* -- 用于在指定 ``driver`` 为 'torch' 时设定具体 driver 实例的一些参数: + * ddp_kwargs -- 用于在使用 ``TorchDDPDriver`` 时指定 ``DistributedDataParallel`` 初始化时的参数;例如传入 + {'find_unused_parameters': True} 来解决有参数不参与前向运算导致的报错等; + * torch_non_blocking -- 表示用于 pytorch 的 tensor 的 to 方法的参数 non_blocking; + * *data_device* -- 表示如果用户的模型 device (在 Driver 中对应为参数 model_device)为 None 时,我们会将数据迁移到 data_device 上; + 注意如果 model_device 为 None,那么 data_device 不会起作用; + * *model_use_eval_mode* (``bool``) -- + 是否在 evaluate 的时候将 model 的状态设置成 eval 状态。在 eval 状态下,model 的 + dropout 与 batch normalization 将会关闭。默认为True。如果为 False,fastNLP 不会对 model 的 evaluate 状态做任何设置。无论 + 该值是什么,fastNLP 都会在 evaluate 接受后将 model 的状态设置为 train 。 + * *use_dist_sampler* -- + 是否使用分布式evaluate的方式。仅当 driver 为分布式类型时,该参数才有效。默认为根据 driver 是否支持 + 分布式进行设置。如果为True,将使得每个进程上的 dataloader 自动使用不同数据,所有进程的数据并集是整个数据集。 + * *output_from_new_proc* -- + 应当为一个字符串,表示在多进程的 driver 中其它进程的输出流应当被做如何处理;其值应当为以下之一: + ["all", "ignore", "only_error"];当该参数的值不是以上值时,该值应当表示一个文件夹的名字,我们会将其他 rank 的输出流重定向到 + log 文件中,然后将 log 文件保存在通过该参数值设定的文件夹中;默认为 "only_error"; + * *progress_bar* -- + evaluate 的时候显示的 progress bar 。目前支持三种 [None, 'raw', 'rich', 'auto'], auto 表示如果检测 + 到当前terminal为交互型则使用 rich,否则使用 raw。 + """ + driver: Driver _evaluate_batch_loop: Loop @@ -29,51 +137,6 @@ class Evaluator: input_mapping: Optional[Union[Callable, Dict]] = None, output_mapping: Optional[Union[Callable, Dict]] = None, model_wo_auto_param_call: bool = False, fp16: bool = False, verbose: int = 1, **kwargs): - """ - 用于对数据进行评测。 - - :param model: 待测试的模型,如果传入的 driver 为 Driver 实例,该参数将被忽略。 - :param dataloaders: 待评测的数据集。如果为多个,请使用 dict 传入。 - :param metrics: 使用的 metric 。必须为 dict 类型,其中 key 为 metric 的名称,value 为一个 Metric 对象。支持 fastNLP 的 - metric ,torchmetrics,allennlpmetrics 等。 - :param driver: 使用 driver 。 - :param device: 使用的设备。 - :param evaluate_batch_step_fn: 定制每次 evaluate batch 执行的函数。该函数应接受的两个参数为 `evaluator` 和 `batch`, - 不需要有返回值;可以参考 fastNLP.core.controllers.loops.evaluate_batch_loop.EvaluateBatchLoop中的batch_step_fn函数。 - :param evaluate_fn: 用来控制 `Evaluator` 在评测的前向传播过程中是调用哪一个函数,例如是 `model.evaluate_step` 还是 - `model.forward`;(1) 如果该值是 None,那么我们会默认使用 `evaluate_step` 当做前向传播的函数,如果在模型中没有 - 找到该方法,则使用 `model.forward` 函数;(2) 如果为 str 类型,则尝试从 model 中寻找该方法,找不到则报错。 - :param input_mapping: 对 dataloader 中输出的内容将通过 input_mapping 处理之后再输入到 model 以及 metric 中。如果针对 - model 和 metric 需要不同的 mapping,请考虑使用 evaluate_batch_step_fn 参数定制。 - :param output_mapping: 对 model 输出的内容,将通过 output_mapping 处理之后再输入到 metric 中。 - :param model_wo_auto_param_call: 是否关闭在训练时调用我们的 auto_param_call 来自动匹配 batch 和 forward 函数的参数的行为; - 如果该值为 True,并且当 batch 为字典时,我们会根据 forward 所需要的参数从 batch 中提取对应的对象,传入到 forward 函数中;如果该值 - 为 False,那么我们会将 batch 直接透传给 forward 函数。注意上述逻辑同样应用于 `train_step`, `evaluate_step` 和 `test_step`; - :param fp16: 是否使用 fp16 。 - :param verbose: 是否打印 evaluate 的结果。 - :kwargs: - * *torch_kwargs* -- 用于在指定 ``driver`` 为 'torch' 时设定具体 driver 实例的一些参数: - * ddp_kwargs -- 用于在使用 ``TorchDDPDriver`` 时指定 ``DistributedDataParallel`` 初始化时的参数;例如传入 - {'find_unused_parameters': True} 来解决有参数不参与前向运算导致的报错等; - * torch_non_blocking -- 表示用于 pytorch 的 tensor 的 to 方法的参数 non_blocking; - * *data_device* -- 表示如果用户的模型 device (在 Driver 中对应为参数 model_device)为 None 时,我们会将数据迁移到 data_device 上; - 注意如果 model_device 为 None,那么 data_device 不会起作用; - * *model_use_eval_mode* (``bool``) -- - 是否在 evaluate 的时候将 model 的状态设置成 eval 状态。在 eval 状态下,model 的 - dropout 与 batch normalization 将会关闭。默认为True。如果为 False,fastNLP 不会对 model 的 evaluate 状态做任何设置。无论 - 该值是什么,fastNLP 都会在 evaluate 接受后将 model 的状态设置为 train 。 - * *use_dist_sampler* -- - 是否使用分布式evaluate的方式。仅当 driver 为分布式类型时,该参数才有效。默认为根据 driver 是否支持 - 分布式进行设置。如果为True,将使得每个进程上的 dataloader 自动使用不同数据,所有进程的数据并集是整个数据集。 - * *output_from_new_proc* -- - 应当为一个字符串,表示在多进程的 driver 中其它进程的输出流应当被做如何处理;其值应当为以下之一: - ["all", "ignore", "only_error"];当该参数的值不是以上值时,该值应当表示一个文件夹的名字,我们会将其他 rank 的输出流重定向到 - log 文件中,然后将 log 文件保存在通过该参数值设定的文件夹中;默认为 "only_error"; - * *progress_bar* -- - evaluate 的时候显示的 progress bar 。目前支持三种 [None, 'raw', 'rich', 'auto'], auto 表示如果检测 - 到当前terminal为交互型则使用 rich,否则使用 raw。 - """ - self.model = model self.metrics = metrics self.driver = choose_driver(model, driver, device, fp16=fp16, model_wo_auto_param_call=model_wo_auto_param_call, @@ -129,16 +192,13 @@ class Evaluator: def run(self, num_eval_batch_per_dl: int = -1, **kwargs) -> Dict: """ - 返回一个字典类型的数据,其中key为metric的名字,value为对应metric的结果。 - 如果存在多个metric,一个dataloader的情况,key的命名规则是 - metric_indicator_name#metric_name - 如果存在多个数据集,一个metric的情况,key的命名规则是 - metric_indicator_name#metric_name#dataloader_name (其中 # 是默认的 separator ,可以通过 Evaluator 初始化参数修改)。 - 如果存在多个metric,多个dataloader的情况,key的命名规则是 - metric_indicator_name#metric_name#dataloader_name - 其中 metric_indicator_name 可能不存在。 - - :param num_eval_batch_per_dl: 每个 dataloader 测试多少个 batch 的数据,-1 为测试所有数据。 + 返回一个字典类型的数据,其中 key 为 metric 的名字,value 为对应结果。返回的字典中,key 的命名规则如下 + ``metric_indicator_name#metric_name#dataloader_name`` ,其中 ``metric_indicator_name`` 是由使用的 metric 返回的结果 + 决定的,仅当 metric 的结果返回是 dict 类型是才有该值;``metric_name`` 则由 ``Evaluator`` 初始化时传入的 ``metrics`` 参数 + 决定;``dataloader_name``仅在传入的 ``dataloaders`` 为 dict 时会有。此外其中的 ``#`` 符号通过 ``Evaluator`` 初始化 + 参数 ``separator`` 进行设置。 + + :param num_eval_batch_per_dl: 每个 dataloader 测试前多少个 batch 的数据,-1 为测试所有数据。 :return: """ assert isinstance(num_eval_batch_per_dl, int), "num_eval_batch_per_dl must be of int type." diff --git a/fastNLP/core/controllers/trainer.py b/fastNLP/core/controllers/trainer.py index d0642e77..1b650a7d 100644 --- a/fastNLP/core/controllers/trainer.py +++ b/fastNLP/core/controllers/trainer.py @@ -80,9 +80,9 @@ class Trainer(TrainerEventTrigger): 您应当使用 ``TorchDDPDriver``,意味着您需要通过 ``python -m torch.distributed.launch`` 的方式来启动训练,此时参数 ``device`` 应当设置为 None(此时我们会忽略该参数),具体见下面对于参数 ``device`` 的更详细的解释。 - :param driver: 训练模型所使用的具体的驱动模式,应当为以下选择中的一个:["torch"],之后我们会加入 jittor、paddle 等 - 国产框架的训练模式;其中 "torch" 表示使用 ``TorchSingleDriver`` 或者 ``TorchDDPDriver``,具体使用哪一种取决于参数 ``device`` - 的设置; + :param driver: 训练模型所使用的具体的驱动模式,应当为以下选择中的一个:``["torch", "jittor", "paddle"]`` + 其中 "torch" 表示使用 ``TorchSingleDriver`` 或者 ``TorchDDPDriver``,具体使用哪一种取决于参数 ``device`` + 的设置。 :param train_dataloader: 训练数据集,注意其必须是单独的一个数据集,不能是 List 或者 Dict; :param optimizers: 训练所需要的优化器;可以是单独的一个优化器实例,也可以是多个优化器组成的 List; :param device: 该参数用来指定具体训练时使用的机器;注意当该参数仅当您通过 `torch.distributed.launch/run` 启动时可以为 None, @@ -135,7 +135,7 @@ class Trainer(TrainerEventTrigger): :param batch_step_fn: 定制每次训练时前向运行一个 batch 的数据所执行的函数。该函数应接受两个参数为 ``trainer`` 和 ``batch``, 不需要要返回值;更详细的使用位置和说明请见 :meth:`fastNLP.core.controllers.TrainBatchLoop.batch_step_fn`; :param evaluate_batch_step_fn: 定制每次验证时前向运行一个 batch 的数据所执行的函数。该函数应接受的两个参数为 ``evaluator`` 和 ``batch``, - 不需要有返回值;可以参考 :meth:`fastNLP.core.controllers.EvaluateBatchLoop.batch_step_fn`; + 不需要有返回值;可以参考 :meth:`fastNLP.core.controllers.TrainBatchLoop.batch_step_fn`; :param train_fn: 用来控制 ``Trainer`` 在训练的前向传播过程中是调用模型的哪一个函数,例如是 ``train_step`` 还是 ``forward``; 默认为 ``None``,如果该值是 ``None``,那么我们会默认使用 ``train_step`` 当做前向传播的函数,如果在模型的定义类中没有找到该方法, 则使用模型默认的前向传播函数,例如对于 pytorch 来说就是 ``forward``。 diff --git a/fastNLP/core/dataset/instance.py b/fastNLP/core/dataset/instance.py index f8f02621..88d4604b 100644 --- a/fastNLP/core/dataset/instance.py +++ b/fastNLP/core/dataset/instance.py @@ -1,6 +1,6 @@ r""" instance 模块实现了Instance 类在fastNLP中对应sample。一个sample可以认为是一个Instance类型的对象。 -便于理解的例子可以参考文档 :mod:`fastNLP.core.dataset` 中的表格 +便于理解的例子可以参考文档 :mod:`fastNLP.core.dataset` 。 """ @@ -14,10 +14,10 @@ from fastNLP.core.utils.utils import pretty_table_printer class Instance(Mapping): r""" - Instance是fastNLP中对应一个sample的类。每个sample在fastNLP中是一个Instance对象。 - Instance一般与 :class:`~fastNLP.DataSet` 一起使用, Instance的初始化如下面的Example所示:: + Instance 是 fastNLP 中对应一个 sample 的类。每个 sample 在 fastNLP 中是一个 Instance 对象。 + Instance 一般与 :class:`~fastNLP.DataSet` 一起使用, Instance 的初始化如下面的 Example 所示:: - instance = Instance() # 请补充完整 + >>> instance = Instance(input="this is a demo sentence", label='good') # 请补充完整 """ @@ -44,17 +44,17 @@ class Instance(Mapping): def keys(self): r""" - 返回一个迭代器,内容是field_name + 返回一个迭代器,内容是 field_name - :return: 一个迭代器 + :return: 一个迭代器 """ return self.fields.keys() def values(self): r""" - 返回一个迭代器,内容是field_value + 返回一个迭代器,内容是 field_value - :return: 一个迭代器 + :return: 一个迭代器 """ return self.fields.values() diff --git a/fastNLP/core/metrics/metric.py b/fastNLP/core/metrics/metric.py index 87505be1..b340beea 100644 --- a/fastNLP/core/metrics/metric.py +++ b/fastNLP/core/metrics/metric.py @@ -14,14 +14,16 @@ from fastNLP.core.metrics.element import Element class Metric: + """ + fastNLP 中 Metric 的基类,自定义 Metric 时,请继承该对象。使用该对象,将有助于减少在分布式状态下的 Metric 计算。 + + :param backend: 目前支持四种类型的 backend, ``[torch, paddle, jittor, auto]``。其中 ``auto`` 表示根据实际调用 + Metric.update() 函数时传入的参数决定具体的 ``backend`` ,大部分情况下直接使用 ``auto`` 即可。 + :param aggregate_when_get_metric: 在计算 metric 的时候是否自动将各个进程上的相同的 element 的数字聚合后再得到metric, + 当 backend 不支持分布式时,该参数无意义。如果为 None ,将在 :class:`fastNLP.Evaluator` 中根据 sampler 是否使用分布式 + 进行自动设置。 + """ def __init__(self, backend: Union[str, Backend, None] = 'auto', aggregate_when_get_metric: bool = None): - """ - - :param str backend: 目前支持四种类型的backend, [torch, paddle, jittor, auto]。其中 auto 表示根据实际调用 Metric.update() - 函数时传入的参数决定具体的 backend ,大部分情况下直接使用 auto 即可。 - :param bool aggregate_when_get_metric: 在计算 metric 的时候是否自动将各个进程上的相同的 element 的数字聚合后再得到metric, - 当 backend 不支持分布式时,该参数无意义。如果为 None ,将在 Evaluator 中根据 sampler 是否使用分布式进行自动设置。 - """ self.backend = AutoBackend(backend) self._updated = False self.get_metric = self._sync_get_metric(self.get_metric) @@ -39,7 +41,10 @@ class Metric: """ 注册一个 element 对象,注册之后便可以通过在 Metric 中直接通过 self.{name} 进行调用,可以认为该对象即为对应 backend 的 tensor 直接进行加减乘除计算即可。 - 注意:如果想使得该 metric 可自动扩展到多卡的情况,请一定申明 aggregate_method 。 + + ..warning:: + + 如果想使得该 metric 可自动扩展到多卡的情况,请一定申明 aggregate_method 。 :param name: 当前 element 的名字,注册后,在 Metric 中可以通过 self.{name} 访问该变量。 :param value: 初始化的值。在调用 Metric.reset() 方法时也将自动设置为该值 diff --git a/fastNLP/core/metrics/span_f1_pre_rec_metric.py b/fastNLP/core/metrics/span_f1_pre_rec_metric.py index 12d86a31..4879e724 100644 --- a/fastNLP/core/metrics/span_f1_pre_rec_metric.py +++ b/fastNLP/core/metrics/span_f1_pre_rec_metric.py @@ -200,27 +200,27 @@ def _bio_tag_to_spans(tags, ignore_labels=None): class SpanFPreRecMetric(Metric): + r""" + :param tag_vocab: 标签的 :class:`~fastNLP.Vocabulary` 。支持的标签为"B"(没有label);或"B-xxx"(xxx为某种label,比如POS中的NN), + 在解码时,会将相同xxx的认为是同一个label,比如['B-NN', 'E-NN']会被合并为一个'NN'. + :param pred: 用该key在evaluate()时从传入dict中取出prediction数据。 为None,则使用 `pred` 取数据 + :param target: 用该key在evaluate()时从传入dict中取出target数据。 为None,则使用 `target` 取数据 + :param seq_len: 用该key在evaluate()时从传入dict中取出sequence length数据。为None,则使用 `seq_len` 取数据。 + :param encoding_type: 目前支持bio, bmes, bmeso, bioes。默认为None,通过tag_vocab自动判断. + :param ignore_labels: str 组成的list. 这个list中的class不会被用于计算。例如在POS tagging时传入['NN'],则不会计算'NN'个label + :param only_gross: 是否只计算总的f1, precision, recall的值;如果为False,不仅返回总的f1, pre, rec, 还会返回每个label的f1, pre, rec + :param f_type: `micro` 或 `macro` . `micro` :通过先计算总体的TP,FN和FP的数量,再计算f, precision, recall; `macro` : 分布计算每个类别的f, precision, recall,然后做平均(各类别f的权重相同) + :param 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,则召回率权重高于精确率。 + :param backend: 目前支持四种类型的 backend, ``[torch, paddle, jittor, auto]``。其中 ``auto`` 表示根据实际调用 + Metric.update() 函数时传入的参数决定具体的 ``backend`` ,大部分情况下直接使用 ``auto`` 即可。 + :param aggregate_when_get_metric: 在计算 metric 的时候是否自动将各个进程上的相同的 element 的数字聚合后再得到metric, + 当 backend 不支持分布式时,该参数无意义。如果为 None ,将在 :class:`fastNLP.Evaluator` 中根据 sampler 是否使用分布式 + 进行自动设置。 + """ def __init__(self, tag_vocab: Vocabulary, encoding_type: str = None, ignore_labels: List[str] = None, only_gross: bool = True, f_type='micro', beta=1, backend: Union[str, Backend, None] = 'auto', aggregate_when_get_metric: bool = None) -> None: - r""" - - :param tag_vocab: 标签的 :class:`~fastNLP.Vocabulary` 。支持的标签为"B"(没有label);或"B-xxx"(xxx为某种label,比如POS中的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 encoding_type: 目前支持bio, bmes, bmeso, bioes。默认为None,通过tag_vocab自动判断. - :param list ignore_labels: str 组成的list. 这个list中的class不会被用于计算。例如在POS tagging时传入['NN'],则不会计算'NN'个label - :param bool only_gross: 是否只计算总的f1, precision, recall的值;如果为False,不仅返回总的f1, pre, rec, 还会返回每个label的f1, pre, rec - :param str f_type: `micro` 或 `macro` . `micro` :通过先计算总体的TP,FN和FP的数量,再计算f, precision, recall; `macro` : 分布计算每个类别的f, precision, recall,然后做平均(各类别f的权重相同) - :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,则召回率权重高于精确率。 - :param str backend: 目前支持四种类型的backend, ['auto', 'torch', 'paddle', 'jittor']。其中 auto 表示根据实际调用 Metric.update() - 函数时传入的参数决定具体的 backend ,一般情况下直接使用 'auto' 即可。 - :param bool aggregate_when_get_metric: 在计算 metric 的时候是否自动将各个进程上的相同的 element 的数字聚合后再得到metric, - 当 backend 不支持分布式时,该参数无意义。如果为 None ,将在 Evaluator 中根据 sampler 是否使用分布式进行自动设置。 - """ super(SpanFPreRecMetric, self).__init__(backend=backend, aggregate_when_get_metric=aggregate_when_get_metric) if f_type not in ('micro', 'macro'): raise ValueError("f_type only supports `micro` or `macro`', got {}.".format(f_type)) diff --git a/fastNLP/core/samplers/conversion_utils.py b/fastNLP/core/samplers/conversion_utils.py index c13e4884..85846bde 100644 --- a/fastNLP/core/samplers/conversion_utils.py +++ b/fastNLP/core/samplers/conversion_utils.py @@ -10,7 +10,7 @@ def conversion_between_reproducible_and_unrepeated_sampler(sampler): 将 sampler 替换成其对应的 reproducible 版本或 unrepeated 版本。如果输入是 UnrepeatedSampler 但是没找到对应的 ReproducibleSampler, - :param sampler: + :param sampler: 需要转换的 sampler 。 :return: """ assert isinstance(sampler, UnrepeatedSampler) or isinstance(sampler, ReproducibleSampler), \ diff --git a/fastNLP/core/samplers/reproducible_batch_sampler.py b/fastNLP/core/samplers/reproducible_batch_sampler.py index 143a5438..520ad9ba 100644 --- a/fastNLP/core/samplers/reproducible_batch_sampler.py +++ b/fastNLP/core/samplers/reproducible_batch_sampler.py @@ -55,16 +55,16 @@ class ReproducibleBatchSampler: class ReproduceBatchSampler(ReproducibleBatchSampler): + """ + 可以使得 batch_sampler 对象状态恢复的 wrapper 。 + + :param batch_sampler: 可迭代出 数字 或 数字列表 的可迭代对象。ReproduceBatchSampler 将首先遍历一边该对象,然后将迭代 + 出来的序号暂存起来,使用时按照 batch_size 的 batch 大小吐出序号列表。 + :param batch_size: 每个 batch 的大小是多少。 + :param drop_last: 如果最后一个 batch 无法构成 batch_size 那么多个 sample ,是否丢掉。 + :param kwargs: fastNLP 内部使用。 + """ def __init__(self, batch_sampler, batch_size: int, drop_last: bool, **kwargs): - """ - 可以使得 batch_sampler 对象状态恢复的 wrapper 。 - - :param batch_sampler: 可迭代出 数字 或 数字列表 的可迭代对象。ReproduceBatchSampler 将首先遍历一边该对象,然后将迭代 - 出来的序号暂存起来,使用时按照 batch_size 的 batch 大小吐出序号列表。 - :param batch_size: 每个 batch 的大小是多少。 - :param drop_last: 如果最后一个 batch 无法构成 batch_size 那么多个 sample ,是否丢掉。 - :param kwargs: fastNLP 内部使用。 - """ super().__init__() self.batch_sampler = batch_sampler @@ -158,18 +158,18 @@ class ReproduceBatchSampler(ReproducibleBatchSampler): class RandomBatchSampler(ReproducibleBatchSampler): + """ + 随机分 batch 的 batch_sampler 。 + + :param dataset: 实现了 __len__ 方法的数据容器。 + :param batch_size: 每个 batch 的大小 + :param shuffle: 如果为 True,将不进行 shuffle,实际上数据会以从长到短的方式输出。 + :param drop_last: 如果最后一个 batch 的 sample 数量无法凑齐 batch_size 这么多,是否需要丢掉。 + :param seed: 设置的随机数种子 + :param kwargs: fastNLP 保留使用 + """ def __init__(self, dataset, batch_size:int = 32, shuffle: bool = True, drop_last: bool = False, seed: int = 0, **kwargs): - """ - 随机分 batch 的 batch_sampler 。 - - :param dataset: 实现了 __len__ 方法的数据容器。 - :param batch_size: 每个 batch 的大小 - :param shuffle: 如果为 True,将不进行 shuffle,实际上数据会以从长到短的方式输出。 - :param drop_last: 如果最后一个 batch 的 sample 数量无法凑齐 batch_size 这么多,是否需要丢掉。 - :param seed: 设置的随机数种子 - :param kwargs: fastNLP 保留使用 - """ super().__init__() self.dataset = dataset @@ -363,28 +363,28 @@ class RandomBatchSampler(ReproducibleBatchSampler): class BucketedBatchSampler(ReproducibleBatchSampler): + """ + 首先按照 ``sample`` 的长度排序,然后按照 batch_size*num_batch_per_bucket 为一个桶的大小,``sample`` 只会在这个桶内进行组 + 合,这样每个 ``batch`` 中的 ``padding`` 数量会比较少 (因为桶内的数据的长度都接近)。 + + :param dataset: 实现了 __len__ 方法的数据容器。 + :param length: 每条数据的长度。 + + * 为 ``List[int]`` 时 + 应当与 dataset 有一样的长度,表示 dataset 中每个元素的数量; + * 为 ``str`` 时 + 仅当传入的 ``dataset`` 是 :class:`fastNLP.DataSet` 时,允许传入 `str` ,该 `str` 将被认为是 ``dataset`` 中的 + ``field`` 。若 field 中的元素为 ``int``,则认为该值是 sample 的长度;若不为 ``int`` ,则尝试使用 ``len`` 方法 + 获取该 ``field`` 中每个元素的长度。 + :param batch_size: 每个 batch 的大小 + :param num_batch_per_bucket: 多少个 ``batch`` 组成一个桶,数据只会在一个桶内进行 ``shuffle`` 。 + :param shuffle: 如果为 True,将不进行 ``shuffle``,实际上数据会以从长到短的方式输出。 + :param drop_last: 如果最后一个 `batch` 的 ``sample`` 数量无法凑齐 ``batch_size`` 这么多,是否需要丢掉。 + :param seed: 设置的随机数种子 + :param kwargs: fastNLP 保留使用 + """ def __init__(self, dataset, length: Union[List[int], str], batch_size:int = 32, num_batch_per_bucket:int = 10, shuffle: bool = True, drop_last: bool = False, seed: int = 0, **kwargs): - """ - 首先按照 ``sample`` 的长度排序,然后按照 batch_size*num_batch_per_bucket 为一个桶的大小,``sample`` 只会在这个桶内进行组 - 合,这样每个 ``batch`` 中的 ``padding`` 数量会比较少 (因为桶内的数据的长度都接近)。 - - :param dataset: 实现了 __len__ 方法的数据容器。 - :param length: 每条数据的长度。 - - * 为 ``List[int]`` 时 - 应当与 dataset 有一样的长度,表示 dataset 中每个元素的数量; - * 为 ``str`` 时 - 仅当传入的 ``dataset`` 是 :class:`fastNLP.DataSet` 时,允许传入 `str` ,该 `str` 将被认为是 ``dataset`` 中的 - ``field`` 。若 field 中的元素为 ``int``,则认为该值是 sample 的长度;若不为 ``int`` ,则尝试使用 ``len`` 方法 - 获取该 ``field`` 中每个元素的长度。 - :param batch_size: 每个 batch 的大小 - :param num_batch_per_bucket: 多少个 ``batch`` 组成一个桶,数据只会在一个桶内进行 ``shuffle`` 。 - :param shuffle: 如果为 True,将不进行 ``shuffle``,实际上数据会以从长到短的方式输出。 - :param drop_last: 如果最后一个 `batch` 的 ``sample`` 数量无法凑齐 ``batch_size`` 这么多,是否需要丢掉。 - :param seed: 设置的随机数种子 - :param kwargs: fastNLP 保留使用 - """ super().__init__() if isinstance(dataset, DataSet) and isinstance(length, str): length = dataset.get_field(length).content diff --git a/fastNLP/core/samplers/reproducible_sampler.py b/fastNLP/core/samplers/reproducible_sampler.py index 5972ce70..599f465f 100644 --- a/fastNLP/core/samplers/reproducible_sampler.py +++ b/fastNLP/core/samplers/reproducible_sampler.py @@ -53,15 +53,15 @@ class ReproducibleSampler: class RandomSampler(ReproducibleSampler): - def __init__(self, dataset, shuffle: bool = True, seed: int = 0, **kwargs): - """ - 随机顺序的 Sampler 。 + """ + 随机顺序的 Sampler 。 - :param dataset: 实现了 __len__ 方法的数据容器 - :param shuffle: 是否在每次 iterate 的时候打乱顺序。 - :param seed: 随机数种子。 - :param kwargs: 用户不需要使用,fastNLP 内部使用 - """ + :param dataset: 实现了 __len__ 方法的数据容器 + :param shuffle: 是否在每次 iterate 的时候打乱顺序。 + :param seed: 随机数种子。 + :param kwargs: 用户不需要使用,fastNLP 内部使用 + """ + def __init__(self, dataset, shuffle: bool = True, seed: int = 0, **kwargs): super(RandomSampler, self).__init__() self.dataset = dataset self.shuffle = shuffle @@ -213,13 +213,13 @@ class RandomSampler(ReproducibleSampler): class SequentialSampler(RandomSampler): - def __init__(self, dataset, **kwargs): - """ - 按照顺序读取 ``dataset`` 。在多卡情况下,间隔读取,例如,在两卡情况下,卡 0 取 ``[0,2,4,..]``, 卡1取 ``[1,3,5...]`` 。 + """ + 按照顺序读取 ``dataset`` 。在多卡情况下,间隔读取,例如,在两卡情况下,卡 0 取 ``[0,2,4,..]``, 卡1取 ``[1,3,5...]`` 。 - :param dataset: 实现了 __len__ 方法的数据容器。 - :param kwargs: - """ + :param dataset: 实现了 __len__ 方法的数据容器。 + :param kwargs: + """ + def __init__(self, dataset, **kwargs): super().__init__(dataset=dataset, **kwargs) def __iter__(self): @@ -283,23 +283,23 @@ class SequentialSampler(RandomSampler): class SortedSampler(SequentialSampler): + """ + 将 ``dataset`` 中的数据根据 ``length`` 从长到短进行迭代。在多卡情况下,由于 ``padding`` , 最后一个 ``sample`` 可能是最长 + 的那个 ``sample`` 。 + + :param dataset: 实现了 __len__ 方法的数据容器。 + :param length: 每条数据的长度。 + + * 为 ``List[int]`` 时 + 应当与 dataset 有一样的长度,表示 dataset 中每个元素的数量; + * 为 ``str`` 时 + 仅当传入的 ``dataset`` 是 :class:`fastNLP.DataSet` 时,允许传入 `str` ,该 `str` 将被认为是 ``dataset`` 中的 + ``field`` 。若 field 中的元素为 ``int``,则认为该值是 sample 的长度;若不为 ``int`` ,则尝试使用 ``len`` 方法 + 获取该 ``field`` 中每个元素的长度。 + :param seed: 设置的随机数种子。 + :param kwargs: fastNLP 保留使用。 + """ def __init__(self, dataset, length:Union[str, List], **kwargs): - """ - 将 ``dataset`` 中的数据根据 ``length`` 从长到短进行迭代。在多卡情况下,由于 ``padding`` , 最后一个 ``sample`` 可能是最长 - 的那个 ``sample`` 。 - - :param dataset: 实现了 __len__ 方法的数据容器。 - :param length: 每条数据的长度。 - - * 为 ``List[int]`` 时 - 应当与 dataset 有一样的长度,表示 dataset 中每个元素的数量; - * 为 ``str`` 时 - 仅当传入的 ``dataset`` 是 :class:`fastNLP.DataSet` 时,允许传入 `str` ,该 `str` 将被认为是 ``dataset`` 中的 - ``field`` 。若 field 中的元素为 ``int``,则认为该值是 sample 的长度;若不为 ``int`` ,则尝试使用 ``len`` 方法 - 获取该 ``field`` 中每个元素的长度。 - :param seed: 设置的随机数种子。 - :param kwargs: fastNLP 保留使用。 - """ super().__init__(dataset=dataset, **kwargs) if isinstance(dataset, DataSet) and isinstance(length, str): length = dataset.get_field(length).content diff --git a/fastNLP/core/samplers/unrepeated_sampler.py b/fastNLP/core/samplers/unrepeated_sampler.py index 3a7b813b..c5683771 100644 --- a/fastNLP/core/samplers/unrepeated_sampler.py +++ b/fastNLP/core/samplers/unrepeated_sampler.py @@ -19,15 +19,15 @@ class UnrepeatedSampler: class UnrepeatedRandomSampler(UnrepeatedSampler): - def __init__(self, dataset, shuffle: bool = False, seed: int = 0, **kwargs): - """ - 考虑在多卡evaluate的场景下,不能重复sample。 + """ + 考虑在多卡 evaluate 的场景下,不能重复 sample。 - :param dataset: 实现了 __len__ 方法的数据容器。 - :param shuffle: 如果为 True,将不进行 shuffle,实际上数据会以从长到短的方式输出。 - :param seed: 设置的随机数种子 - :param kwargs: fastNLP 保留使用 - """ + :param dataset: 实现了 __len__ 方法的数据容器。 + :param shuffle: 如果为 True,将不进行 shuffle,实际上数据会以从长到短的方式输出。 + :param seed: 设置的随机数种子 + :param kwargs: fastNLP 保留使用 + """ + def __init__(self, dataset, shuffle: bool = False, seed: int = 0, **kwargs): self.dataset = dataset self.shuffle = shuffle self.seed = seed @@ -96,16 +96,22 @@ class UnrepeatedRandomSampler(UnrepeatedSampler): class UnrepeatedSortedSampler(UnrepeatedRandomSampler): + """ + 将 dataset 中的数据根据 length 从长到短进行迭代,并且保证在多卡场景下数据不重复。本 sampler 可能导致各个机器上的 + batch 数量不完全一致。 + + :param dataset: 实现了 __len__ 方法的数据容器。 + :param length: 每条数据的长度。 + + * 为 ``List[int]`` 时 + 应当与 dataset 有一样的长度,表示 dataset 中每个元素的数量; + * 为 ``str`` 时 + 仅当传入的 ``dataset`` 是 :class:`fastNLP.DataSet` 时,允许传入 `str` ,该 `str` 将被认为是 ``dataset`` 中的 + ``field`` 。若 field 中的元素为 ``int``,则认为该值是 sample 的长度;若不为 ``int`` ,则尝试使用 ``len`` 方法 + 获取该 ``field`` 中每个元素的长度。 + :param kwargs: fastNLP 保留使用 + """ def __init__(self, dataset, length:Union[str, List], **kwargs): - """ - 将 dataset 中的数据根据 length 从长到短进行迭代,并且保证在多卡场景下数据不重复。本 sampler 可能导致各个机器上的 - batch 数量不完全一致。 - - :param dataset: 实现了 __len__ 方法的数据容器。 - :param length: 如果为 List,应当与 dataset 有一样的长度,表示 dataset 中每个元素的数量;仅当传入的 dataset 为 fastNLP 的 - DataSet 时支持传入 str,会将该str理解为 dataset 的 field 名称,若 field 中的元素为 int,则认为该值是 sample 的长度。 - :param kwargs: fastNLP 保留使用 - """ super().__init__(dataset=dataset, shuffle=False, seed=0, **kwargs) if isinstance(dataset, DataSet) and isinstance(length, str): length = dataset.get_field(length).content @@ -125,13 +131,13 @@ class UnrepeatedSortedSampler(UnrepeatedRandomSampler): class UnrepeatedSequentialSampler(UnrepeatedRandomSampler): - def __init__(self, dataset, **kwargs): - """ - 按照顺序读取 dataset。在多卡情况下,间隔读取,例如,在两卡情况下,卡0取 [0,2,4,..], 卡1取 [1,3,5...]。 + """ + 按照顺序读取 dataset。在多卡情况下,间隔读取,例如,在两卡情况下,卡0取 [0,2,4,..], 卡1取 [1,3,5...]。 - :param dataset: 实现了 __len__ 方法的数据容器。 - :param kwargs: - """ + :param dataset: 实现了 __len__ 方法的数据容器。 + :param kwargs: + """ + def __init__(self, dataset, **kwargs): super(UnrepeatedSequentialSampler, self).__init__(dataset, shuffle=False, seed=0, **kwargs) def __iter__(self): diff --git a/fastNLP/core/utils/rich_progress.py b/fastNLP/core/utils/rich_progress.py index 53d4e281..04cb6383 100644 --- a/fastNLP/core/utils/rich_progress.py +++ b/fastNLP/core/utils/rich_progress.py @@ -44,11 +44,6 @@ class DummyFRichProgress: return True class FRichProgress(Progress, metaclass=Singleton): - """ - fastNLP 使用的 progress bar ,新增了 new_progress 函数,通过此函数即可定制 fastNLP 中所有 progress 的样式。 - - """ - def new_progess(self, *columns: Union[str, ProgressColumn], console: Optional[Console] = None, auto_refresh: bool = True, diff --git a/fastNLP/envs/distributed.py b/fastNLP/envs/distributed.py index adcfb085..706dfe00 100644 --- a/fastNLP/envs/distributed.py +++ b/fastNLP/envs/distributed.py @@ -20,13 +20,17 @@ def is_cur_env_distributed() -> bool: """ 单卡模式该函数一定返回 False; 注意进程 0 在多卡的训练模式下前后的值是不一样的,例如在开启多卡的 driver 之前,在进程 0 上的该函数返回 False;但是在开启后,在进程 0 上 - 的该函数返回的值是 True; - 多卡模式下除了进程 0 外的其它进程返回的值一定是 True; + 的该函数返回的值是 True;多卡模式下除了进程 0 外的其它进程返回的值一定是 True; """ return FASTNLP_GLOBAL_RANK in os.environ -def get_global_rank(): +def get_global_rank()->int: + """ + 获取当前进程的 global_rank 。 + + :return: + """ return int(os.environ.get(FASTNLP_GLOBAL_RANK, 0)) @@ -64,7 +68,7 @@ def rank_zero_call(fn: Callable): @contextmanager def fastnlp_no_sync_context(level=2): """ - 用于让 fastNLP 的 barrier 以及 gather/broadcast等操作等同于只有1卡的多卡程序。如果为 1 表示 fastNLP 里的barrier 操作失效; + 用于让 fastNLP 的 barrier 以及 gather/broadcast等操作等同于只有 1 卡的多卡程序。如果为 1 表示 fastNLP 里的barrier 操作失效; 如果为 2 表示 barrier 与 gather/broadcast 都失效。 :param int level: 可选 [0, 1, 2]