diff --git a/fastNLP/core/callbacks/callback_manager.py b/fastNLP/core/callbacks/callback_manager.py index eabc489b..f34c5dd3 100644 --- a/fastNLP/core/callbacks/callback_manager.py +++ b/fastNLP/core/callbacks/callback_manager.py @@ -10,8 +10,8 @@ from .callback_event import Event from .callback import Callback from fastNLP.core.log import logger from .progress_callback import ProgressCallback, choose_progress_callback -from fastNLP.envs import rank_zero_call -from fastNLP.core.utils.utils import _get_fun_msg +from ..utils.exceptions import EarlyStopException +from ..utils.utils import _get_fun_msg def _transfer(func): @@ -25,6 +25,8 @@ def _transfer(func): for callback_fn in manager.callback_fns[func.__name__]: try: callback_fn(*arg, **kwargs) + except EarlyStopException as e: + raise e except BaseException as e: logger.error(f"The following callback_fn raise exception:{_get_fun_msg(callback_fn)}.") raise e diff --git a/fastNLP/core/callbacks/checkpoint_callback.py b/fastNLP/core/callbacks/checkpoint_callback.py index a51406af..c4ed8d47 100644 --- a/fastNLP/core/callbacks/checkpoint_callback.py +++ b/fastNLP/core/callbacks/checkpoint_callback.py @@ -19,7 +19,7 @@ class CheckpointCallback(Callback): only_state_dict: bool = True, model_save_fn: Optional[Callable] = None, save_object: str = 'model', save_evaluate_results=True, **kwargs): """ - 保存模型 checkpoint 的 callback ,其保存的文件目录以及文件名命名规则如下:: + 保存 checkpoint 的 callback ,其保存的文件目录以及文件名命名规则如下:: - folder/ - YYYY-mm-dd-HH_MM_SS_fffff/ # 自动根据当前脚本的启动时间创建的 @@ -29,8 +29,9 @@ class CheckpointCallback(Callback): - {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 下不进行模型保存。 + 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 值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 最长公共字符串算法 找到最匹配 的那个作为 monitor 。如果为 None,将尝试使用 Trainer 设置的 monitor 。也可以传入一个函数,接受参数为 evaluation 的结 @@ -46,22 +47,14 @@ class CheckpointCallback(Callback): :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 。 + :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 folder is None: - logger.warning( - "Parameter `folder` is None, and we will use the current work directory to find and load your model.") - folder = Path.cwd() - folder = Path(folder) - if not folder.exists(): - raise NotADirectoryError(f"Path '{folder.absolute()}' is not existed!") - elif folder.is_file(): - raise ValueError("Parameter `folder` should be a directory instead of a file.") - if every_n_epochs is not None: if not isinstance(every_n_epochs, int) or every_n_epochs < 1: raise ValueError("Parameter `every_n_epochs` should be an int and greater than or equal to 1.") @@ -74,12 +67,6 @@ class CheckpointCallback(Callback): else: every_n_batches = sys.maxsize # 使得没有数字可以整除 - if topk is not None: - if not isinstance(topk, int): - raise ValueError("Parameter `topk` should be an int.") - else: - topk = 0 - if on_exceptions is not None: if not isinstance(on_exceptions, Sequence): on_exceptions = [on_exceptions] diff --git a/fastNLP/core/callbacks/load_best_model_callback.py b/fastNLP/core/callbacks/load_best_model_callback.py index 362716ef..55ef40ad 100644 --- a/fastNLP/core/callbacks/load_best_model_callback.py +++ b/fastNLP/core/callbacks/load_best_model_callback.py @@ -19,7 +19,8 @@ class LoadBestModelCallback(HasMonitorCallback): model_load_fn:Optional[Callable] = None, delete_after_train:bool = True): """ - 保存最佳的 monitor 值最佳的模型,并在训练结束的时候重新加载模型。仅在训练正常结束的时候才能加载最好的模型。 + 保存最佳的 monitor 值最佳的模型,并在训练结束的时候重新加载模型,默认会在加载之后删除权重文件。仅在训练正常结束的时候才能加载 + 最好的模型。 :param str monitor: 监控的 metric 值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 最长公共字符串算法 找到最匹配 的那个作为 monitor 。如果为 None,将尝试使用 Trainer 设置的 monitor 。也可以传入一个函数,接受参数为 evaluation 的结 diff --git a/fastNLP/core/callbacks/topk_saver.py b/fastNLP/core/callbacks/topk_saver.py index aba2ff63..c629e9de 100644 --- a/fastNLP/core/callbacks/topk_saver.py +++ b/fastNLP/core/callbacks/topk_saver.py @@ -33,9 +33,8 @@ class Saver: :param kwargs: 更多需要传递给 Trainer.save() 或者 Trainer.save_model() 接口的参数。 """ if folder is None: - logger.rank_zero_warning( - "Parameter `folder` is None, and we will use the current work directory to find and load your model.") - folder = Path.cwd() + folder = Path.cwd().absolute() + logger.info(f"Parameter `folder` is None, and we will use {folder} to save and load your model.") folder = Path(folder) if not folder.exists(): folder.mkdir(parents=True, exist_ok=True) diff --git a/fastNLP/core/collators/padders/get_padder.py b/fastNLP/core/collators/padders/get_padder.py index db48011b..e76391aa 100644 --- a/fastNLP/core/collators/padders/get_padder.py +++ b/fastNLP/core/collators/padders/get_padder.py @@ -121,7 +121,7 @@ def get_padder(batch_field:Sequence[Any], pad_val, dtype, backend, field_name)-> # 这里 ele_dtype 传入为 None 的原因是防止出现 paddle tensor 转换为 torch tensor return TorchTensorPadder(pad_val=pad_val, ele_dtype=None, dtype=dtype) elif backend == 'paddle': - return PaddleTensorPadder(pad_val=pad_val, ele_dtype=None, dtype=dtype) + return PaddleTensorPadder(pad_val=pad_val, ele_dtype=ele_dtype, dtype=dtype) elif backend == 'jittor': return JittorTensorPadder(pad_val=pad_val, ele_dtype=ele_dtype, dtype=dtype) else: diff --git a/fastNLP/core/collators/padders/paddle_padder.py b/fastNLP/core/collators/padders/paddle_padder.py index c4dbdadc..826d21c7 100644 --- a/fastNLP/core/collators/padders/paddle_padder.py +++ b/fastNLP/core/collators/padders/paddle_padder.py @@ -141,7 +141,10 @@ class PaddleTensorPadder(Padder): shapes = [field.shape for field in batch_field] max_shape = [len(batch_field)] + [max(*_) for _ in zip(*shapes)] - array = np.full(max_shape, fill_value=pad_val) + if isinstance(batch_field[0], paddle.Tensor): + array = paddle.full(max_shape, fill_value=pad_val, dtype=dtype) + else: + array = np.full(max_shape, fill_value=pad_val, dtype=batch_field[0].dtype) for i, field in enumerate(batch_field): slices = (i, ) + tuple(slice(0, s) for s in shapes[i]) array[slices] = field diff --git a/fastNLP/core/collators/padders/torch_padder.py b/fastNLP/core/collators/padders/torch_padder.py index b67aeff8..aaa0d4e9 100644 --- a/fastNLP/core/collators/padders/torch_padder.py +++ b/fastNLP/core/collators/padders/torch_padder.py @@ -118,8 +118,8 @@ class TorchTensorPadder(Padder): batch_field = [torch.tensor(field.tolist(), dtype=dtype) for field in batch_field] else: device = batch_field[0].device - if dtype is None: - dtype = batch_field[0].dtype + if dtype is None: + dtype = batch_field[0].dtype except AttributeError: raise RuntimeError(f"If the field is not a torch.Tensor (it is {type(batch_field[0])}), " f"it must have tolist() method.") diff --git a/fastNLP/core/controllers/evaluator.py b/fastNLP/core/controllers/evaluator.py index abd70644..9ad35ea8 100644 --- a/fastNLP/core/controllers/evaluator.py +++ b/fastNLP/core/controllers/evaluator.py @@ -56,6 +56,8 @@ class Evaluator: * 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 状态做任何设置。无论 @@ -234,8 +236,7 @@ class Evaluator: """ 调用所有 metric 的 reset() 方法,清除累积的状态。 - Returns: - + :return: """ self.metrics_wrapper.reset() @@ -357,6 +358,11 @@ class _MetricsWrapper: metric.update(res) def reset(self): + """ + 将 Metric 中的状态重新设置。 + + :return: + """ for metric in self._metrics: if _is_allennlp_metric(metric): metric.get_metric(reset=True) diff --git a/fastNLP/core/controllers/trainer.py b/fastNLP/core/controllers/trainer.py index 9de400ab..2116674f 100644 --- a/fastNLP/core/controllers/trainer.py +++ b/fastNLP/core/controllers/trainer.py @@ -1,4 +1,10 @@ -from typing import Union, Optional, List, Callable, Dict, Sequence, BinaryIO, IO +""" +``Trainer`` 是 fastNLP 用于训练模型的专门的训练器,其支持多种不同的驱动模式 ``Driver``,不仅包括最为经常使用的 DDP,而且还支持 jittor 等国产 +的训练框架;新版的 fastNLP 新加入了方便的 callback 函数修饰器,并且支持定制用户自己特定的训练循环过程;通过使用该训练器,用户只需要自己实现 +模型部分,而将训练层面的逻辑完全地交给 fastNLP; +""" + +from typing import Union, Optional, List, Callable, Dict, BinaryIO from functools import partial from collections import defaultdict import copy @@ -7,7 +13,6 @@ from dataclasses import is_dataclass import os from pathlib import Path import io -import inspect __all__ = [ 'Trainer', @@ -62,12 +67,20 @@ class Trainer(TrainerEventTrigger): **kwargs ): r""" - `Trainer` 是 fastNLP 用于训练模型的专门的训练器,其支持多种不同的驱动模式,不仅包括最为经常使用的 DDP,而且还支持 jittor 等国产 - 的训练框架;新版的 fastNLP 新加入了方便的 callback 函数修饰器,并且支持定制用户自己特定的训练循环过程;通过使用该训练器,用户只需 - 要自己实现模型部分,而将训练层面的逻辑完全地交给 fastNLP; + :param model: 训练所需要的模型,例如 ``torch.nn.Module``; + + .. note:: + + 当使用 pytorch 时,注意参数 ``model`` 在大多数情况下为 ``nn.Module``。但是您仍能够通过使用一些特定的组合来使用情况,如下所示: - :param model: 训练所需要的模型,目前支持 pytorch; - :param driver: 训练模型所使用的具体的驱动模式,应当为以下选择中的一个:["torch",],之后我们会加入 jittor、paddle 等 + 1. 当希望使用 ``DataParallel`` 时,您应当使用 ``TorchSingleDriver``,意味着您在初始化 ``Trainer`` 时参数 ``device`` 不应当为 + 一个 ``List``; + + 2. 当您选择自己初始化 ``init_process_group`` 时(这种情况要求您传入的 ``model`` 参数一定为 ``DistributedDataParallel``), + 您应当使用 ``TorchDDPDriver``,意味着您需要通过 ``python -m torch.distributed.launch`` 的方式来启动训练,此时参数 ``device`` + 应当设置为 None(此时我们会忽略该参数),具体见下面对于参数 ``device`` 的更详细的解释。 + + :param driver: 训练模型所使用的具体的驱动模式,应当为以下选择中的一个:["torch"],之后我们会加入 jittor、paddle 等 国产框架的训练模式;其中 "torch" 表示使用 ``TorchSingleDriver`` 或者 ``TorchDDPDriver``,具体使用哪一种取决于参数 ``device`` 的设置; :param train_dataloader: 训练数据集,注意其必须是单独的一个数据集,不能是 List 或者 Dict; @@ -80,79 +93,248 @@ class Trainer(TrainerEventTrigger): device 的可选输入如下所示: * *str*: 例如 'cpu', 'cuda', 'cuda:0', 'cuda:1' 等; - * *torch.device*: 将模型装载到 ``torch.device`` 上; + * *torch.device*: 例如 'torch.device("cuda:0")'; * *int*: 将使用 ``device_id`` 为该值的 ``gpu`` 进行训练;如果值为 -1,那么默认使用全部的显卡,此时使用的 driver 实例是 `TorchDDPDriver`; * *list(int)*: 如果多于 1 个device,应当通过该种方式进行设定;注意此时我们一定会使用 ``TorchDDPDriver``,不管您传入的列表的长度是 1 还是其它值; - * *None*: 为None则不对模型进行任何处理; + * *None*: 仅当用户自己通过训练框架提供的并行训练启动脚本开启 ddp 进程时为 None; + + .. note:: + + 如果希望使用 ``TorchDDPDriver``,在初始化 ``Trainer`` 时您应当使用:: + + Trainer(driver="torch", device=[0, 1]) + + 注意如果这时 ``device=[0]``,我们仍旧会使用 ``TorchDDPDriver``。 + + 如果希望使用 ``TorchSingleDriver``,则在初始化 ``Trainer`` 时您应当使用:: + + Trainer(driver="torch", device=0) - .. node:: + .. warning:: - 如果希望使用 ``TorchDDPDriver`` + 注意参数 ``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 n_epochs: 训练总共的 epoch 的数量,默认为 20; :param evaluate_dataloaders: 验证数据集,其可以是单独的一个数据集,也可以是多个数据集;当为多个数据集时,注意其必须是 Dict;默认 - 为 None; - :param batch_step_fn: 定制每次 train batch 执行的函数。该函数应接受两个参数为 `trainer` 和`batch`,不需要要返回值;可以 - 参考 fastNLP.core.controllers.loops.train_batch_loop.TrainBatchLoop中的batch_step_fn函数。 - :param evaluate_batch_step_fn: 定制每次 evaluate batch 执行的函数。该函数应接受的两个参数为 `evaluator` 和 `batch`, - 不需要有返回值;可以参考 fastNLP.core.controllers.loops.evaluate_batch_loop.EvaluateBatchLoop中的batch_step_fn函数。 - :param train_fn: 用来控制 `Trainer` 在训练的前向传播过程中是调用模型的哪一个函数,例如是 `train_step` 还是 `forward`; - 默认为 None,如果该值是 None,那么我们会默认使用 `train_step` 当做前向传播的函数,如果在模型中没有找到该方法, - 则使用模型默认的前向传播函数。 - :param evaluate_fn: 用来控制 `Trainer` 中内置的 `Evaluator` 的模式,应当为 None 或者一个字符串;其使用方式和 train_fn 类似; - 注意该参数我们会直接传给 Trainer 中内置的 Evaluator(如果不为 None);如果该值为 None ,将首先尝试寻找模型中是否有 - evaluate_step 这个函数,如果没有则使用 forward 函数。 - :param callbacks: 训练当中触发的 callback 类,该参数应当为一个列表,其中的每一个元素都应当继承 `Callback` 类; - :param metrics: 应当为一个字典,其中 key 表示 monitor,例如 {"acc1": AccMetric(), "acc2": AccMetric()}; - :param evaluate_every: 可以为负数、正数或者函数;为负数时表示每隔几个 epoch evaluate 一次;为正数则表示每隔几个 batch evaluate 一次; - 为函数时表示用户自己传入的用于控制 Trainer 中的 evaluate 的频率的函数,该函数的应该接受当前 trainer 对象作为参数,并 - 返回一个 bool 值,返回为 True 说明需要进行 evaluate ;将在每个 batch 结束后调用该函数判断是否需要 evaluate 。 - :param input_mapping: 应当为一个字典或者一个函数,表示在当前 step 拿到一个 batch 的训练数据后,应当做怎样的映射处理;如果其是 - 一个字典,并且 batch 也是一个 `Dict`,那么我们会把 batch 中同样在 input_mapping 中的 key 修改为 input_mapping 的对应 key 的 - value;如果 batch 是一个 `dataclass`,那么我们会先将该 dataclass 转换为一个 Dict,然后再进行上述转换;如果 batch 此时是其它 - 类型,那么我们将会直接报错;如果 input_mapping 是一个函数,那么对于取出的 batch,我们将不会做任何处理,而是直接将其传入该函数里; - 注意该参数会被传进 `Evaluator` 中;因此你可以通过该参数来实现将训练数据 batch 移到对应机器上的工作(例如当参数 `device` 为 None 时); - 如果 train 和 evaluate 需要使用不同的 input_mapping, 请使用 train_input_mapping 与 evaluate_input_mapping 设置。 - :param output_mapping: 应当为一个字典或者函数。作用和 input_mapping 类似,区别在于其用于转换输出;如果 output_mapping 是一个 - 函数,那么我们将会直接将模型的输出传给该函数;如果其是一个 `Dict`,那么我们需要 batch 必须是 `Dict` 或者 `dataclass` 类型, - 如果 batch 是一个 `Dict`,那么我们会把 batch 中同样在 output_mapping 中的 key 修改为 output_mapping 的对应 key 的 value; - 如果 batch 是一个 `dataclass`,那么我们会先将该 dataclass 转换为一个 Dict,然后再进行上述转换; - 如果 train 和 evaluate 需要使用不同的 output_mapping, 请使用 train_output_mapping 与 evaluate_output_mapping 设置。 - :param model_wo_auto_param_call: 是否关闭在训练时调用我们的 auto_param_call 来自动匹配 batch 和 forward 函数的参数的行为; - 如果该值为 False,并且当 batch 为字典时,我们会根据 forward 所需要的参数从 batch 中提取对应的对象,传入到 forward 函数中;如果该值 - 为 True,那么我们会将 batch 直接透传给模型。注意该参数应用于 `train_step`, `evaluate_step` 和 `test_step`; - :param accumulation_steps: 梯度累积的步数,表示每隔几个 batch 优化器迭代一次;默认为 1; - :param fp16: 是否开启混合精度训练;默认为 False; - :param monitor: 当存在 evaluate_dataloaders 时,默认的 monitor metric 的名字。传入的 callback 如果有 monitor 参数且没有 - 在 callback 初始化设定的,将采取这个值。如果在 evaluation 结果中没有找到完全一致的名称,将使用 最长公共字符串算法 找到最匹配 - 的那个作为 monitor 。也可以传入一个函数,接受参数为 evaluation 的结果(字典类型),返回一个 float 值作为 monitor 的结果。 - 如果 evaluate_dataloaders 与 metrics 没有提供,该参数无意义。 - :param larger_better: monitor 的值是否是越大越好。 - :param marker: 用于标记一个 Trainer 实例,从而在用户调用 `Trainer.on` 函数时,标记该 callback 函数属于哪一个具体的 'trainer' 实例;默认为 None; - :param kwargs: 一些其它的可能需要的参数,见下方的说明 + 为 None; + :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`; + :param train_fn: 用来控制 ``Trainer`` 在训练的前向传播过程中是调用模型的哪一个函数,例如是 ``train_step`` 还是 ``forward``; + 默认为 ``None``,如果该值是 ``None``,那么我们会默认使用 ``train_step`` 当做前向传播的函数,如果在模型的定义类中没有找到该方法, + 则使用模型默认的前向传播函数,例如对于 pytorch 来说就是 ``forward``。 + + .. note:: + 在 fastNLP 中,对于训练时使用的前向传播函数的查找逻辑如下所示: + + 1. 如果 ``train_fn`` 为 None,那么在 model 的类 Model 中寻找方法 ``Model.train_step``;如果没有找到,那么默认使用 ``Model.forward``; + 2. 如果 ``train_fn`` 为一个字符串,例如 'my_step_fn',那么我们首先会在 model 的类 Model 中寻找方法 ``Model.my_step_fn``, + 如果没有找到,那么会直接报错; + + :param evaluate_fn: 用来控制 ``Trainer`` 中内置的 ``Evaluator`` 在验证的前向传播过程中是调用模型的哪一个函数,应当为 ``None`` + 或者一个字符串;其使用方式和 train_fn 类似;具体可见 :class:`fastNLP.core.controllers.Evaluator`; + :param callbacks: 训练当中触发的 callback 类,该参数应当为一个列表,其中的每一个元素都应当继承 ``Callback`` 类;具体可见 + :class:`fastNLP.core.callbacks.Callback`; + :param metrics: 用于传给 ``Trainer`` 内部的 ``Evaluator`` 实例来进行训练过程中的验证。其应当为一个字典,其中 key 表示 monitor, + 例如 {"acc1": AccMetric(), "acc2": AccMetric()}; + + 目前我们支持的 ``metric`` 的种类有以下几种: + + 1. fastNLP 自己的 ``metric``:详见 :class:`fastNLP.core.metrics.Metric`; + 2. torchmetrics; + 3. allennlp.training.metrics; + 4. paddle.metric; + + :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 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,我们将不会做任何处理,而是直接将其传入该函数里; + + 注意该参数会被传进 ``Evaluator`` 中;因此你可以通过该参数来实现将训练数据 batch 移到对应机器上的工作(例如当参数 ``device`` 为 ``None`` 时); + 如果 ``Trainer`` 和 ``Evaluator`` 需要使用不同的 ``input_mapping``, 请使用 ``train_input_mapping`` 与 ``evaluate_input_mapping`` 分别进行设置。 + + :param output_mapping: 应当为一个字典或者函数。作用和 ``input_mapping`` 类似,区别在于其用于转换输出: + + 1. 如果 ``output_mapping`` 是一个 ``Dict``,那么我们需要模型的输出必须是 ``Dict`` 或者 ``dataclass`` 类型: + + 1. 如果此时模型的输出是一个 ``Dict``,那么我们会把输出中同样在 ``output_mapping`` 中的 key 修改为 ``output_mapping`` 的对应 key 的 value; + 2. 如果此时模型的输出是一个 ``dataclass``,那么我们会先将其转换为一个 Dict,然后再进行上述转换; + 2. 如果 ``output_mapping`` 是一个函数,那么我们将会直接将模型的输出传给该函数; + + 如果 ``Trainer`` 和 ``Evaluator`` 需要使用不同的 ``output_mapping``, 请使用 ``train_output_mapping`` 与 ``evaluate_output_mapping`` 分别进行设置; + + .. note:: + + ``input_mapping`` 和 ``output_mapping`` 与 fastNLP 的一个特殊的概念 **'参数绑定'** 高度相关,它们的存在也是为了 fastNLP + 中的参数匹配能够正确地运行; + + .. todo:: + 之后链接上 参数匹配 的文档; + + .. warning:: + + 如果 ``Trainer`` 的参数 ``output_mapping`` 不为 ``None``,请保证其返回的一定是一个字典,并且其中含有关键字 **'loss'**; + + :param model_wo_auto_param_call: 是否关闭在训练时调用我们的 ``auto_param_call`` 函数来自动匹配 batch 和前向函数的参数的行为; + + 1. 如果该值为 ``False``,并且当 batch 为字典时,我们会根据**前向函数**所需要的参数从 batch 中提取对应的对象,然后传入到**前向函数**中; + 2. 如果该值为 ``True``,那么我们会将 batch 直接透传给模型; + + .. todo:: + 之后链接上 参数匹配 的文档; + + 函数 ``auto_param_call`` 详见 :func:`fastNLP.core.utils.auto_param_call`; + + :param accumulation_steps: 梯度累积的步数,表示每隔几个 batch 才让优化器迭代一次,默认为 1; + :param fp16: 是否开启混合精度训练,默认为 False; + :param monitor: 对于一些特殊的 ``Callback``,例如 :class:`fastNLP.core.callbacks.CheckpointCallback`,它们需要参数 ``monitor`` + 来从 ``Evaluator`` 的验证结果中获取当前评测的值,从而来判断是否执行一些特殊的操作。例如,对于 ``CheckpointCallback`` 而言,如果我们 + 想要每隔一个 epoch 让 ``Evaluator`` 进行一次验证,然后保存训练以来的最好的结果;那么我们需要这样设置: + + .. code-block:: + + trainer = Trainer( + ..., + metrics={'acc': accMetric()}, + callbacks=[CheckpointCallback( + ..., + monitor='acc', + topk=1 + )] + ) + + 这意味着对于 ``CheckpointCallback`` 来说,*'acc'* 就是一个监测的指标,用于在 ``Evaluator`` 验证后取出其需要监测的那个指标的值。 + + ``Trainer`` 中的参数 ``monitor`` 的作用在于为没有设置 ``monitor`` 参数但是需要该参数的 *callback* 实例设置该值。关于 ``monitor`` + 参数更详细的说明,请见 :class:`fastNLP.core.callbacks.CheckpointCallback`; + + 注意该参数仅当 ``Trainer`` 内置的 ``Evaluator`` 不为 None 时且有需要该参数但是没有设置该参数的 *callback* 实例才有效; + + :param larger_better: 对于需要参数 ``monitor`` 的 *callback* 来说,``monitor`` 的值是否是越大越好;类似于 ``monitor``,其作用 + 在于为没有设置 ``larger_better`` 参数但是需要该参数的 *callback* 实例设置该值; + + 注意该参数仅当 ``Trainer`` 内置的 ``Evaluator`` 不为 None 时且有需要该参数但是没有设置该参数的 *callback* 实例才有效; + + :param marker: 用于标记一个 ``Trainer`` 实例,从而在用户调用 ``Trainer.on`` 函数时,标记该函数属于哪一个具体的 ``Trainer`` 实例;默认为 None; + + .. note:: + + marker 的使用场景主要在于如果一个脚本中含有多个 ``Trainer`` 实例,并且含有多个使用 ``Trainer.on`` 修饰的函数时,不同的函数属于 + 不同的 ``Trainer`` 实例; + + 此时,通过将修饰器 ``Trainer.on`` 的参数 ``marker`` 和 ``Trainer`` 的参数 ``marker`` 置为相同,就可以使得该函数只会在这一 + ``Trainer`` 实例中被调用;例如, + + .. code-block:: + + @Trainer.on(Event.on_train_begin(), marker='trainer1') + def fn(trainer): + ... + + trainer = Trainer( + ..., + marker='trainer1' + ) + + 另一点需要说明的是,如果一个被 ``Trainer.on`` 修饰的函数,其修饰时没有指明 ``marker``,那么会将该函数传给代码位于其之后的 + 第一个 ``Trainer`` 实例,即使该 ``Trainer`` 实例的 marker 不为 None;这一点详见 :meth:`~fastNLP.core.controllers.Trainer.on` + :kwargs: * *torch_kwargs* -- 用于在指定 ``driver`` 为 'torch' 时设定具体 driver 实例的一些参数: * ddp_kwargs -- 用于在使用 ``TorchDDPDriver`` 时指定 ``DistributedDataParallel`` 初始化时的参数;例如传入 {'find_unused_parameters': True} 来解决有参数不参与前向运算导致的报错等; * set_grad_to_none -- 是否在训练过程中在每一次 optimizer 更新后将 grad 置为 None; * torch_non_blocking -- 表示用于 pytorch 的 tensor 的 to 方法的参数 non_blocking; - * *data_device* -- 表示如果用户的模型 device (在 Driver 中对应为参数 model_device)为 None 时,我们会将数据迁移到 data_device 上; - 注意如果 model_device 为 None,那么 data_device 不会起作用; - * *use_dist_sampler* -- 表示是否使用分布式的 sampler 。在多卡时,分布式 sampler 将自动决定每张卡上读取的 sample ,使得一个epoch - 内所有卡的 sample 加起来为一整个数据集的 sample。默认会根据 driver 是否为分布式进行设置。 - * *evaluate_use_dist_sampler* -- 表示在 Evaluator 中在使用 分布式 的时候是否将 dataloader 的 sampler 替换为分布式的 sampler;默认为 True; + * *data_device* -- 一个具体的 driver 实例中,有 ``model_device`` 和 ``data_device``,前者表示模型所在的设备,后者表示 + 当 ``model_device`` 为 None 时应当将数据迁移到哪个设备; + + .. note:: + + 注意您在绝大部分情况下不会用到该参数! + + 1. 当 driver 实例的 ``model_device`` 不为 None 时,该参数无效; + 2. 对于 pytorch,仅当用户自己通过 ``python -m torch.distributed.launch`` 并且自己初始化 ``init_process_group`` 时, + driver 实例的 ``model_device`` 才会为 None; + + * *use_dist_sampler* -- 表示是否使用分布式的 ``sampler``。在多卡时,分布式 ``sampler`` 将自动决定每张卡上读取的 sample ,使得一个 epoch + 内所有卡的 sample 加起来为一整个数据集的 sample。默认会根据 driver 是否为分布式进行设置。 + * *evaluate_use_dist_sampler* -- 表示在 ``Evaluator`` 中在使用分布式的时候是否将 dataloader 的 ``sampler`` 替换为分布式的 ``sampler``;默认为 ``True``; * *output_from_new_proc* -- 应当为一个字符串,表示在多进程的 driver 中其它进程的输出流应当被做如何处理;其值应当为以下之一: ["all", "ignore", "only_error"];当该参数的值不是以上值时,该值应当表示一个文件夹的名字,我们会将其他 rank 的输出流重定向到 log 文件中,然后将 log 文件保存在通过该参数值设定的文件夹中;默认为 "only_error"; + + 注意该参数仅当使用分布式的 ``driver`` 时才有效,例如 ``TorchDDPDriver``; * *progress_bar* -- 以哪种方式显示 progress ,目前支持[None, 'raw', 'rich', 'auto'] 或者 RichCallback, RawTextCallback对象, - 默认为 auto , auto 表示如果检测到当前 terminal 为交互型则使用 RichCallback,否则使用 RawTextCallback对象。如果 - 需要定制 progress bar 的参数,例如打印频率等,可以传入 RichCallback, RawTextCallback 对象。 - * *train_input_mapping* -- 与 input_mapping 一致,但是只用于 train 中。与 input_mapping 互斥。 - * *train_output_mapping* -- 与 output_mapping 一致,但是只用于 train 中。与 output_mapping 互斥。 - * *evaluate_input_mapping* -- 与 input_mapping 一致,但是只用于 evaluate 中。与 input_mapping 互斥。 - * *evaluate_output_mapping* -- 与 output_mapping 一致,但是只用于 evaluate 中。与 output_mapping 互斥。 + 默认为 auto , auto 表示如果检测到当前 terminal 为交互型则使用 RichCallback,否则使用 RawTextCallback对象。如果 + 需要定制 progress bar 的参数,例如打印频率等,可以传入 RichCallback, RawTextCallback 对象。 + * *train_input_mapping* -- 与 input_mapping 一致,但是只用于 ``Trainer`` 中。与 input_mapping 互斥。 + * *train_output_mapping* -- 与 output_mapping 一致,但是只用于 ``Trainer`` 中。与 output_mapping 互斥。 + * *evaluate_input_mapping* -- 与 input_mapping 一致,但是只用于 ``Evaluator`` 中。与 input_mapping 互斥。 + * *evaluate_output_mapping* -- 与 output_mapping 一致,但是只用于 ``Evaluator`` 中。与 output_mapping 互斥。 + + .. note:: + ``Trainer`` 是通过在内部直接初始化一个 ``Evaluator`` 来进行验证; + ``Trainer`` 内部的 ``Evaluator`` 默认是 None,如果您需要在训练过程中进行验证,你需要保证这几个参数得到正确的传入: + + 必须的参数:1. ``metrics``;2. ``evaluate_dataloaders``; + + 可选的其它参数:1. ``evaluate_batch_step_fn;2. ``evaluate_fn``;3. ``evaluate_every``;4. ``input_mapping``; + 5. ``output_mapping``; 6. ``model_wo_auto_param_call``;7. ``fp16``;8. ``monitor``;9. ``larger_better``; + + .. warning:: + 如果 ``Trainer`` 中内置的 ``Evaluator`` 实例不为 ``None``,那么需要注意 ``Trainer`` 中的一些参数是与 ``Evaluator`` 一致的,它们分别为: + + 1. ``Evaluator`` 在初始化时的 ``driver`` 参数是 ``Trainer`` 中已经实例化过的 driver;这一点使得一些参数对于 ``Trainer`` 内部的 + ``Evaluator`` 没有用处,例如 ``device``,``torch_kwargs``,``data_device`` 和 ``output_from_new_proc`` 等; + 2. ``input_mapping``,``output_mapping``,``model_wo_auto_param_call`` 和 ``fp16`` 是 ``Trainer`` 和其内部默认的 + ``Evaluator`` 是一致的; + + 当然,对于 ``input_mapping`` 和 ``output_mapping``,您可以通过添加 ``kwargs`` 中的参数 ``evaluate_input_mapping`` 和 + ``evaluate_output_mapping`` 来单独为 ``Evaluator`` 进行更细致的订制。 + + 另一方面,注意一些专门独属于 ``Evaluator`` 的参数仅当 ``Evaluator`` 不为 None 时才会生效。 """ self.model = model @@ -174,7 +356,7 @@ class Trainer(TrainerEventTrigger): evaluate_input_mapping = kwargs.get('evaluate_input_mapping', None) evaluate_output_mapping = kwargs.get('evaluate_output_mapping', None) - train_input_mapping, train_output_mapping, evaluate_input_mapping, evaluate_output_mapping = \ + train_input_mapping, train_output_mapping, evaluate_input_mapping, evaluate_output_mapping = \ _get_input_output_mapping(input_mapping, output_mapping, train_input_mapping, train_output_mapping, evaluate_input_mapping, evaluate_output_mapping) @@ -273,7 +455,7 @@ class Trainer(TrainerEventTrigger): if not (isinstance(progress_bar, str) or progress_bar is None): # 应该是ProgressCallback,获取其名称。 progress_bar = progress_bar.name self.evaluator = Evaluator(model=model, dataloaders=evaluate_dataloaders, metrics=metrics, - driver=self.driver, device=device, evaluate_batch_step_fn=evaluate_batch_step_fn, + driver=self.driver, evaluate_batch_step_fn=evaluate_batch_step_fn, evaluate_fn=evaluate_fn, input_mapping=evaluate_input_mapping, output_mapping=evaluate_output_mapping, fp16=fp16, verbose=0, use_dist_sampler=kwargs.get("evaluate_use_dist_sampler", None), @@ -302,7 +484,7 @@ class Trainer(TrainerEventTrigger): def run(self, num_train_batch_per_epoch: int = -1, num_eval_batch_per_dl: int = -1, num_eval_sanity_batch: int = 2, resume_from: str = None, resume_training: bool = True, catch_KeyboardInterrupt=None): - """ + r""" 注意如果是断点重训的第一次训练,即还没有保存任何用于断点重训的文件,那么其应当置 resume_from 为 None,并且使用 ModelCheckpoint 去保存断点重训的文件; :param num_train_batch_per_epoch: 每个 epoch 运行多少个 batch 即停止,-1 为根据 dataloader 有多少个 batch 决定。 @@ -491,6 +673,36 @@ class Trainer(TrainerEventTrigger): # do something # 以上函数会在 Trainer 每个新的 batch 开始的时候执行,但是是两个 batch 才执行一次。 + .. note:: + + + 例如: + + .. code-block:: + + @Trainer.on(Event.on_train_begin()) + def fn1(trainer): + ... + + @Trainer.on(Event.on_train_epoch_begin()) + def fn2(trainer): + ... + + trainer1 = Trainer( + ..., + marker='trainer1' + ) + + @Trainer.on(Event.on_fetch_data_begin()) + def fn3(trainer): + ... + + trainer2 = Trainer( + ..., + marker='trainer2' + ) + + 注意如果你使用该函数修饰器来为你的训练添加 callback,请务必保证你加入 callback 函数的代码在实例化 `Trainer` 之前; :param event: 特定的 callback 时机,用户需要为该 callback 函数指定其属于哪一个 callback 时机。每个时机运行的函数应该包含 @@ -646,7 +858,7 @@ class Trainer(TrainerEventTrigger): self.driver.save_model(folder, only_state_dict, **kwargs) self.driver.barrier() - def load_model(self, folder: Union[str, Path, BinaryIO, io.BytesIO], only_state_dict: bool = False, + def load_model(self, folder: Union[str, Path, BinaryIO, io.BytesIO], only_state_dict: bool = True, model_load_fn: Optional[Callable] = None, **kwargs): """ 加载模型 diff --git a/fastNLP/core/dataloaders/prepare_dataloader.py b/fastNLP/core/dataloaders/prepare_dataloader.py index 193ec384..8a7e3d1e 100644 --- a/fastNLP/core/dataloaders/prepare_dataloader.py +++ b/fastNLP/core/dataloaders/prepare_dataloader.py @@ -10,7 +10,7 @@ from ..samplers import RandomBatchSampler, RandomSampler from .torch_dataloader import prepare_torch_dataloader from .paddle_dataloader import prepare_paddle_dataloader from .jittor_dataloader import prepare_jittor_dataloader -from ...envs import FASTNLP_BACKEND, SUPPORT_BACKENDS, _module_available +from ...envs import FASTNLP_BACKEND, SUPPORT_BACKENDS from ..log import logger diff --git a/fastNLP/core/dataset/dataset.py b/fastNLP/core/dataset/dataset.py index 7cba5a8c..6b908c6a 100644 --- a/fastNLP/core/dataset/dataset.py +++ b/fastNLP/core/dataset/dataset.py @@ -452,8 +452,8 @@ class DataSet: apply_out = self._apply_process(num_proc, func, progress_desc=progress_desc, show_progress_bar=show_progress_bar, _apply_field=field_name) # 只检测第一个数据是否为dict类型,若是则默认所有返回值为dict;否则报错。 - if not isinstance(apply_out[0], dict): - raise Exception("The result of func is not a dict") + if not isinstance(apply_out[0], Mapping): + raise Exception(f"The result of func is not a Mapping, but a {type(apply_out[0])}") for key, value in apply_out[0].items(): results[key] = [value] diff --git a/fastNLP/core/drivers/torch_driver/initialize_torch_driver.py b/fastNLP/core/drivers/torch_driver/initialize_torch_driver.py index 1ca83c09..025744bb 100644 --- a/fastNLP/core/drivers/torch_driver/initialize_torch_driver.py +++ b/fastNLP/core/drivers/torch_driver/initialize_torch_driver.py @@ -55,8 +55,8 @@ def initialize_torch_driver(driver: str, device: Optional[Union[str, "torch.devi elif each < 0: raise ValueError("When parameter `device` is 'Sequence' type, the value in it should be bigger than 0.") elif each >= _could_use_device_num: - raise ValueError("When parameter `device` is 'Sequence' type, the value in it should not be bigger than" - " the available gpu number.") + raise ValueError(f"When parameter `device` is 'Sequence' type, the value in it should not be bigger than" + f" the available gpu number:{_could_use_device_num}.") device = [torch.device(f"cuda:{w}") for w in device] elif device is not None and not isinstance(device, torch.device): raise ValueError("Parameter `device` is wrong type, please check our documentation for the right use.") diff --git a/fastNLP/core/drivers/torch_driver/torch_driver.py b/fastNLP/core/drivers/torch_driver/torch_driver.py index c74c79ed..5aee15e9 100644 --- a/fastNLP/core/drivers/torch_driver/torch_driver.py +++ b/fastNLP/core/drivers/torch_driver/torch_driver.py @@ -167,6 +167,12 @@ class TorchDriver(Driver): """ model = self.unwrap_model() res = torch.load(filepath, map_location='cpu') + if isinstance(res, dict) and only_state_dict is False: + logger.rank_zero_warning(f"It seems like that {filepath} only contains state, you may need to use " + f"`only_state_dict=True`") + elif not isinstance(res, dict) and only_state_dict is True: + logger.rank_zero_warning(f"It seems like that {filepath} is not state, you may need to use " + f"`only_state_dict=False`") if only_state_dict: model.load_state_dict(res) else: diff --git a/tests/core/collators/test_collator.py b/tests/core/collators/test_collator.py index ae219793..09ec4af8 100644 --- a/tests/core/collators/test_collator.py +++ b/tests/core/collators/test_collator.py @@ -334,9 +334,9 @@ def test_torch_dl(): dl = TorchDataLoader(ds, batch_size=2) batch = next(iter(dl)) assert 'x' in batch and 'y' in batch and 'z' in batch and 'i' in batch and 'j' in batch - assert isinstance(batch['z'], torch.Tensor) + assert isinstance(batch['z'], torch.FloatTensor) assert isinstance(batch['j'], list) - assert isinstance(batch['i']['j'], torch.Tensor) + assert isinstance(batch['i']['j'], torch.LongTensor) dl.set_ignore('x') batch = next(iter(dl))