From 6da627d4ceb2103046b374ca9c4d76d8e627469e Mon Sep 17 00:00:00 2001 From: lxr-tech <1838593642@qq.com> Date: Sat, 30 Apr 2022 23:29:12 +0800 Subject: [PATCH 1/2] modify-fastnlp_tutorial_0-lxr-220430 --- fastNLP/core/metrics/accuracy.py | 4 +- .../metrics/classify_f1_pre_rec_metric.py | 4 +- tutorials/fastnlp_tutorial_0.ipynb | 701 +++++++----------- .../figures/T0-fig-trainer-and-evaluator.png | Bin 0 -> 104863 bytes 4 files changed, 255 insertions(+), 454 deletions(-) create mode 100644 tutorials/figures/T0-fig-trainer-and-evaluator.png diff --git a/fastNLP/core/metrics/accuracy.py b/fastNLP/core/metrics/accuracy.py index d9ccb332..0869d8c8 100644 --- a/fastNLP/core/metrics/accuracy.py +++ b/fastNLP/core/metrics/accuracy.py @@ -28,7 +28,7 @@ class Accuracy(Metric): def get_metric(self) -> dict: r""" - get_metric 函数将根据 evaluate 函数累计的评价指标统计量来计算最终的评价结果. + get_metric 函数将根据 update 函数累计的评价指标统计量来计算最终的评价结果. :return dict evaluate_result: {"acc": float} """ @@ -37,7 +37,7 @@ class Accuracy(Metric): def update(self, pred, target, seq_len=None): r""" - evaluate函数将针对一个批次的预测结果做评价指标的累计 + update 函数将针对一个批次的预测结果做评价指标的累计 :param torch.Tensor pred: 预测的tensor, tensor的形状可以是torch.Size([B,]), torch.Size([B, n_classes]), torch.Size([B, max_len]), 或者torch.Size([B, max_len, n_classes]) diff --git a/fastNLP/core/metrics/classify_f1_pre_rec_metric.py b/fastNLP/core/metrics/classify_f1_pre_rec_metric.py index 2c71602d..8de007ce 100644 --- a/fastNLP/core/metrics/classify_f1_pre_rec_metric.py +++ b/fastNLP/core/metrics/classify_f1_pre_rec_metric.py @@ -56,7 +56,7 @@ class ClassifyFPreRecMetric(Metric): def get_metric(self) -> dict: r""" - get_metric函数将根据evaluate函数累计的评价指标统计量来计算最终的评价结果. + get_metric函数将根据update函数累计的评价指标统计量来计算最终的评价结果. :return dict evaluate_result: {"acc": float} """ @@ -117,7 +117,7 @@ class ClassifyFPreRecMetric(Metric): def update(self, pred, target, seq_len=None): r""" - evaluate函数将针对一个批次的预测结果做评价指标的累计 + update 函数将针对一个批次的预测结果做评价指标的累计 :param torch.Tensor pred: 预测的tensor, tensor的形状可以是torch.Size([B,]), torch.Size([B, n_classes]), torch.Size([B, max_len]), 或者torch.Size([B, max_len, n_classes]) diff --git a/tutorials/fastnlp_tutorial_0.ipynb b/tutorials/fastnlp_tutorial_0.ipynb index 01913ac0..28fcfddf 100644 --- a/tutorials/fastnlp_tutorial_0.ipynb +++ b/tutorials/fastnlp_tutorial_0.ipynb @@ -15,15 +15,15 @@ "\n", "    1.3   trainer 内部初始化 evaluater\n", "\n", - "  2   使用 trainer 训练模型\n", + "  2   使用 fastNLP 0.8 搭建 argmax 模型\n", "\n", - "    2.1   argmax 模型实例\n", + "    2.1   trainer_step 和 evaluator_step\n", "\n", - "    2.2   trainer 的参数匹配\n", + "    2.2   trainer 和 evaluator 的参数匹配\n", "\n", - "    2.3   trainer 的实际使用 \n", + "    2.3   一个实际案例:argmax 模型\n", "\n", - "  3   使用 evaluator 评测模型\n", + "  3   使用 fastNLP 0.8 训练 argmax 模型\n", " \n", "    3.1   trainer 外部初始化的 evaluator\n", "\n", @@ -50,21 +50,21 @@ "\n", "```python\n", "trainer = Trainer(\n", - " model=model,\n", - " train_dataloader=train_dataloader,\n", - " optimizers=optimizer,\n", + " model=model, # 模型基于 torch.nn.Module\n", + " train_dataloader=train_dataloader, # 加载模块基于 torch.utils.data.DataLoader \n", + " optimizers=optimizer, # 优化模块基于 torch.optim.*\n", "\t...\n", - "\tdriver=\"torch\",\n", - "\tdevice=0,\n", + "\tdriver=\"torch\", # 使用 pytorch 模块进行训练 \n", + "\tdevice='cuda', # 使用 GPU:0 显卡执行训练\n", "\t...\n", ")\n", "...\n", "evaluator = Evaluator(\n", - " model=model,\n", - " dataloaders=evaluate_dataloader,\n", - " metrics={'acc': Accuracy()} \n", + " model=model, # 模型基于 torch.nn.Module\n", + " dataloaders=evaluate_dataloader, # 加载模块基于 torch.utils.data.DataLoader\n", + " metrics={'acc': Accuracy()}, # 测评方法使用 fastNLP.core.metrics.Accuracy \n", " ...\n", - " driver=trainer.driver,\n", + " driver=trainer.driver, # 保持同 trainer 的 driver 一致\n", "\tdevice=None,\n", " ...\n", ")\n", @@ -88,7 +88,7 @@ "\n", "注:在同一脚本中,`Trainer`和`Evaluator`使用的`driver`应当保持一致\n", "\n", - "  一个不能违背的原则在于:**不要将多卡的`driver`前使用单卡的`driver`**(???),这样使用可能会带来很多意想不到的错误。" + "  一个不能违背的原则在于:**不要将多卡的`driver`前使用单卡的`driver`**(???),这样使用可能会带来很多意想不到的错误" ] }, { @@ -109,10 +109,10 @@ " optimizers=optimizer,\n", "\t...\n", "\tdriver=\"torch\",\n", - "\tdevice=0,\n", + "\tdevice='cuda',\n", "\t...\n", - " evaluate_dataloaders=evaluate_dataloader,\n", - " metrics={'acc': Accuracy()},\n", + " evaluate_dataloaders=evaluate_dataloader, # 传入参数 evaluator_dataloaders\n", + " metrics={'acc': Accuracy()}, # 传入参数 metrics\n", "\t...\n", ")\n", "```" @@ -123,7 +123,7 @@ "id": "0c9c7dda", "metadata": {}, "source": [ - "## 2. 使用 trainer 训练模型" + "## 2. argmax 模型的搭建实例" ] }, { @@ -131,71 +131,41 @@ "id": "524ac200", "metadata": {}, "source": [ - "### 2.1 argmax 模型实例\n", + "### 2.1 trainer_step 和 evaluator_step\n", "\n", - "本节将通过训练`argmax`模型,简单介绍如何`Trainer`模块的使用方式\n", + "在`fastNLP 0.8`中,使用`pytorch.nn.Module`搭建需要训练的模型,在搭建模型过程中,除了\n", "\n", - "  使用`pytorch`定义`argmax`模型,输入一组固定维度的向量,输出其中数值最大的数的索引\n", - "\n", - "  除了添加`pytorch`要求的`forward`方法外,还需要添加 **`train_step`** 和 **`evaluate_step`** 这两个方法" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "5314482b", - "metadata": { - "pycharm": { - "is_executing": true - } - }, - "outputs": [], - "source": [ - "import torch\n", - "import torch.nn as nn\n", - "\n", - "class ArgMaxModel(nn.Module):\n", - " def __init__(self, num_labels, feature_dimension):\n", - " super(ArgMaxModel, self).__init__()\n", - " self.num_labels = num_labels\n", - "\n", - " self.linear1 = nn.Linear(in_features=feature_dimension, out_features=10)\n", - " self.ac1 = nn.ReLU()\n", - " self.linear2 = nn.Linear(in_features=10, out_features=10)\n", - " self.ac2 = nn.ReLU()\n", - " self.output = nn.Linear(in_features=10, out_features=num_labels)\n", - " self.loss_fn = nn.CrossEntropyLoss()\n", + "  添加`pytorch`要求的`forward`方法外,还需要添加 **`train_step`** 和 **`evaluate_step`** 这两个方法\n", + "***\n", + "```python\n", + "class Model(torch.nn.Module):\n", + " def __init__(self):\n", + " super(Model, self).__init__()\n", + " self.loss_fn = torch.nn.CrossEntropyLoss()\n", + " pass\n", "\n", " def forward(self, x):\n", - " x = self.ac1(self.linear1(x))\n", - " x = self.ac2(self.linear2(x))\n", - " x = self.output(x)\n", - " return x\n", + " pass\n", "\n", " def train_step(self, x, y):\n", - " x = self(x)\n", - " return {\"loss\": self.loss_fn(x, y)}\n", + " pred = self(x)\n", + " return {\"loss\": self.loss_fn(pred, y)}\n", "\n", " def evaluate_step(self, x, y):\n", - " x = self(x)\n", - " x = torch.max(x, dim=-1)[1]\n", - " return {\"pred\": x, \"target\": y}" - ] - }, - { - "cell_type": "markdown", - "id": "ca897322", - "metadata": {}, - "source": [ + " pred = self(x)\n", + " pred = torch.max(pred, dim=-1)[1]\n", + " return {\"pred\": pred, \"target\": y}\n", + "```\n", + "***\n", "在`fastNLP 0.8`中,**函数`train_step`是`Trainer`中参数`train_fn`的默认值**\n", "\n", - "  由于,在`Trainer`训练时,**`Trainer`通过参数`_train_fn_`对应的模型方法获得当前数据批次的损失值**\n", + "  由于,在`Trainer`训练时,**`Trainer`通过参数`train_fn`对应的模型方法获得当前数据批次的损失值**\n", "\n", "  因此,在`Trainer`训练时,`Trainer`首先会寻找模型是否定义了`train_step`这一方法\n", "\n", "    如果没有找到,那么`Trainer`会默认使用模型的`forward`函数来进行训练的前向传播过程\n", "\n", - "注:在`fastNLP 0.8`中,`Trainer`要求模型通过`train_step`来返回一个字典,将损失值作为`loss`的键值\n", + "注:在`fastNLP 0.8`中,**`Trainer`要求模型通过`train_step`来返回一个字典**,**满足如`{\"loss\": loss}`的形式**\n", "\n", "  此外,这里也可以通过传入`Trainer`的参数`output_mapping`来实现高度化的定制,具体请见这一note(???)\n", "\n", @@ -205,7 +175,11 @@ "\n", "  从用户角度,模型通过`evaluate_step`方法来返回一个字典,内容与传入`Evaluator`的`metrics`一致\n", "\n", - "" + "  从模块角度,该字典的键值和`metric`中的`update`函数的签名一致,这样的机制在传参时被称为“**参数匹配**”\n", + "\n", + "***\n", + "\n", + "![fastNLP 0.8 中,Trainer 和 Evaluator 的关系图](./figures/T0-fig-trainer-and-evaluator.png)" ] }, { @@ -213,13 +187,52 @@ "id": "fb3272eb", "metadata": {}, "source": [ - "### 2.2 trainer 的参数匹配\n", + "### 2.2 trainer 和 evaluator 的参数匹配\n", + "\n", + "在`fastNLP 0.8`中,参数匹配涉及到两个方面,分别是在\n", + "\n", + "  一方面,**在模型的前向传播中**,**`dataloader`向`train_step`或`evaluate_step`函数传递`batch`**\n", + "\n", + "  另方面,**在模型的评测过程中**,**`evaluate_dataloader`向`metric`的`update`函数传递`batch`**\n", "\n", - "`fastNLP 0.8`中的参数匹配涉及到两个方面,一是在模型训练或者评测的前向传播过程中,如果从`dataloader`中出来一个`batch`的数据是一个字典,那么我们会查看模型的`train_step`和`evaluate_step`方法的参数签名,然后对于每一个参数,我们会根据其名字从 batch 这一字典中选择出对应的数据传入进去。例如在接下来的定义`Dataset`的部分,注意`ArgMaxDatset`的`__getitem__`方法,您可以通过在`Trainer`和`Evaluator`中设置参数 `model_wo_auto_param_call`来关闭这一行为。当您关闭了这一行为后,我们会将`batch`直接传给您的`train_step`、`evaluate_step`或者 `forward`函数。\n", + "对于前者,在`Trainer`和`Evaluator`中的参数`model_wo_auto_param_call`被设置为`False`时\n", "\n", - "二是在传入`Trainer`或者`Evaluator metrics`后,我们会在需要评测的时间点主动调用`metrics`来对`evaluate_dataloaders`进行评测,这一功能主要就是通过对`metrics`的`update`方法和一个`batch`的数据进行参数评测实现的。首先需要明确的是一个 metric 的计算通常分为 `update` 和 `get_metric`两步,其中`update`表示更新一个`batch`的评测数据,`get_metric` 表示根据已经得到的评测数据计算出最终的评测值,例如对于 `Accuracy`来说,其在`update`的时候会更新一个`batch`计算正确的数量 right_num 和计算错误的数量 total_num,最终在 `get_metric` 时返回评测值`right_num / total_num`。\n", + "    **`fastNLP 0.8`要求`dataloader`生成的每个`batch`**,**满足如`{\"x\": x, \"y\": y}`的形式**\n", + "\n", + "  同时,`fastNLP 0.8`会查看模型的`train_step`和`evaluate_step`方法的参数签名,并为对应参数传入对应数值\n", + "\n", + "    **字典形式的定义**,**对应在`Dataset`定义的`__getitem__`方法中**,例如下方的`ArgMaxDatset`\n", + "\n", + "  而在`Trainer`和`Evaluator`中的参数`model_wo_auto_param_call`被设置为`True`时\n", + "\n", + "    `fastNLP 0.8`会将`batch`直接传给模型的`train_step`、`evaluate_step`或`forward`函数\n", + "***\n", + "```python\n", + "class Dataset(torch.utils.data.Dataset):\n", + " def __init__(self, x, y):\n", + " self.x = x\n", + " self.y = y\n", + "\n", + " def __len__(self):\n", + " return len(self.x)\n", + "\n", + " def __getitem__(self, item):\n", + " return {\"x\": self.x[item], \"y\": self.y[item]}\n", + "```\n", + "***\n", + "对于后者,首先要明确,在`Trainer`和`Evaluator`中,`metrics`的计算分为`update`和`get_metric`两步\n", "\n", - "因为`fastNLP 0.8`的`metrics`是自动计算的(只需要传给`Trainer`或者`Evaluator`),因此其一定依赖于参数匹配。对于从`evaluate_dataloader`中生成的一个`batch`的数据,我们会查看传给 `Trainer`(最终是传给`Evaluator`)和`Evaluator`的每一个`metric`,然后查看其`update`函数的函数签名,然后根据每一个参数的名字从`batch`字典中选择出对应的数据传入进去。" + "    **`update`函数**,**针对一个`batch`的预测结果**,计算其累计的评价指标\n", + "\n", + "    **`get_metric`函数**,**统计`update`函数累计的评价指标**,来计算最终的评价结果\n", + "\n", + "  例如对于`Accuracy`来说,`update`函数会更新一个`batch`的正例数量`right_num`和负例数量`total_num`\n", + "\n", + "    而`get_metric`函数则会返回所有`batch`的评测值`right_num / total_num`\n", + "\n", + "  在此基础上,**`fastNLP 0.8`要求`evaluate_dataloader`生成的每个`batch`传递给对应的`metric`**\n", + "\n", + "    **以`{\"pred\": y_pred, \"target\": y_true}`的形式**,对应其`update`函数的函数签名" ] }, { @@ -227,9 +240,65 @@ "id": "f62b7bb1", "metadata": {}, "source": [ - "### 2.3 trainer的实际使用\n", + "### 2.3 一个实际案例:argmax 模型\n", "\n", - "接下来我们创建用于训练的 dataset,其接受三个参数:数据维度、数据量和随机数种子,生成指定数量的维度为 `feature_dimension` 向量,而每一个向量的标签就是该向量中最大值的索引。" + "下文将通过训练`argmax`模型,简单介绍如何`Trainer`模块的使用方式\n", + "\n", + "  首先,使用`pytorch.nn.Module`定义`argmax`模型,目标是输入一组固定维度的向量,输出其中数值最大的数的索引" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "5314482b", + "metadata": { + "pycharm": { + "is_executing": false + } + }, + "outputs": [], + "source": [ + "import torch\n", + "import torch.nn as nn\n", + "\n", + "class ArgMaxModel(nn.Module):\n", + " def __init__(self, num_labels, feature_dimension):\n", + " super(ArgMaxModel, self).__init__()\n", + " self.num_labels = num_labels\n", + "\n", + " self.linear1 = nn.Linear(in_features=feature_dimension, out_features=10)\n", + " self.ac1 = nn.ReLU()\n", + " self.linear2 = nn.Linear(in_features=10, out_features=10)\n", + " self.ac2 = nn.ReLU()\n", + " self.output = nn.Linear(in_features=10, out_features=num_labels)\n", + " self.loss_fn = nn.CrossEntropyLoss()\n", + "\n", + " def forward(self, x):\n", + " pred = self.ac1(self.linear1(x))\n", + " pred = self.ac2(self.linear2(pred))\n", + " pred = self.output(pred)\n", + " return pred\n", + "\n", + " def train_step(self, x, y):\n", + " pred = self(x)\n", + " return {\"loss\": self.loss_fn(pred, y)}\n", + "\n", + " def evaluate_step(self, x, y):\n", + " pred = self(x)\n", + " pred = torch.max(pred, dim=-1)[1]\n", + " return {\"pred\": pred, \"target\": y}" + ] + }, + { + "cell_type": "markdown", + "id": "71f3fa6b", + "metadata": {}, + "source": [ + "  接着,使用`torch.utils.data.Dataset`定义`ArgMaxDataset`数据集\n", + "\n", + "    数据集包含三个参数:维度`feature_dimension`、数据量`data_num`和随机种子`seed`\n", + "\n", + "    数据及初始化是,自动生成指定维度的向量,并为每个向量标注出其中最大值的索引作为预测标签" ] }, { @@ -245,7 +314,7 @@ "source": [ "from torch.utils.data import Dataset\n", "\n", - "class ArgMaxDatset(Dataset):\n", + "class ArgMaxDataset(Dataset):\n", " def __init__(self, feature_dimension, data_num=1000, seed=0):\n", " self.num_labels = feature_dimension\n", " self.feature_dimension = feature_dimension\n", @@ -269,7 +338,9 @@ "id": "2cb96332", "metadata": {}, "source": [ - "现在准备好数据和模型。" + "  然后,根据`ArgMaxModel`类初始化模型实例,保持输入维度`feature_dimension`和输出标签数量`num_labels`一致\n", + "\n", + "    再根据`ArgMaxDataset`类初始化两个数据集实例,分别用来模型测试和模型评测,数据量各1000笔" ] }, { @@ -283,16 +354,10 @@ }, "outputs": [], "source": [ - "from torch.utils.data import DataLoader\n", - "\n", - "train_dataset = ArgMaxDatset(feature_dimension=10, data_num=1000)\n", - "evaluate_dataset = ArgMaxDatset(feature_dimension=10, data_num=100)\n", - "\n", - "train_dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)\n", - "evaluate_dataloader = DataLoader(evaluate_dataset, batch_size=8)\n", + "model = ArgMaxModel(num_labels=10, feature_dimension=10)\n", "\n", - "# num_labels 设置为 10,与 feature_dimension 保持一致,因为我们是预测十个位置中哪一个的概率最大。\n", - "model = ArgMaxModel(num_labels=10, feature_dimension=10)" + "train_dataset = ArgMaxDataset(feature_dimension=10, data_num=1000)\n", + "evaluate_dataset = ArgMaxDataset(feature_dimension=10, data_num=100)" ] }, { @@ -300,12 +365,33 @@ "id": "4e7d25ee", "metadata": {}, "source": [ - "将优化器也定义好。" + "  此外,使用`torch.utils.data.DataLoader`初始化两个数据加载模块,批量大小同为8,分别用于训练和测评" ] }, { "cell_type": "code", "execution_count": 4, + "id": "363b5b09", + "metadata": {}, + "outputs": [], + "source": [ + "from torch.utils.data import DataLoader\n", + "\n", + "train_dataloader = DataLoader(train_dataset, batch_size=8, shuffle=True)\n", + "evaluate_dataloader = DataLoader(evaluate_dataset, batch_size=8)" + ] + }, + { + "cell_type": "markdown", + "id": "c8d4443f", + "metadata": {}, + "source": [ + "  最后,使用`torch.optim.SGD`初始化一个优化模块,基于随机梯度下降法" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "id": "dc28a2d9", "metadata": { "pycharm": { @@ -321,15 +407,33 @@ }, { "cell_type": "markdown", - "id": "4f1fba81", + "id": "eb8ca6cf", + "metadata": {}, + "source": [ + "## 3. 使用 fastNLP 0.8 训练 argmax 模型\n", + "\n", + "### 3.1 trainer 外部初始化的 evaluator" + ] + }, + { + "cell_type": "markdown", + "id": "55145553", "metadata": {}, "source": [ - "现在万事俱备,开始使用 Trainer 进行训练!" + "通过从`fastNLP`库中导入`Trainer`类,初始化`trainer`实例,对模型进行训练\n", + "\n", + "  需要导入预先定义好的模型`model`、对应的数据加载模块`train_dataloader`、优化模块`optimizer`\n", + "\n", + "  通过`progress_bar`设定进度条格式,默认为`\"auto\"`,此外还有`\"rich\"`、`\"raw\"`和`None`\n", + "\n", + "    但对于`\"auto\"`和`\"rich\"`格式,训练结束后进度条会不显示(???)\n", + "\n", + "  通过`n_epochs`设定优化迭代轮数,默认为20;全部`Trainer`的全部变量与函数可以通过`dir(trainer)`查询" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "b51b7a2d", "metadata": { "pycharm": { @@ -349,167 +453,20 @@ }, "metadata": {}, "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "['__annotations__',\n", - " '__class__',\n", - " '__delattr__',\n", - " '__dict__',\n", - " '__dir__',\n", - " '__doc__',\n", - " '__eq__',\n", - " '__format__',\n", - " '__ge__',\n", - " '__getattribute__',\n", - " '__gt__',\n", - " '__hash__',\n", - " '__init__',\n", - " '__init_subclass__',\n", - " '__le__',\n", - " '__lt__',\n", - " '__module__',\n", - " '__ne__',\n", - " '__new__',\n", - " '__reduce__',\n", - " '__reduce_ex__',\n", - " '__repr__',\n", - " '__setattr__',\n", - " '__sizeof__',\n", - " '__str__',\n", - " '__subclasshook__',\n", - " '__weakref__',\n", - " '_check_callback_called_legality',\n", - " '_check_train_batch_loop_legality',\n", - " '_custom_callbacks',\n", - " '_driver',\n", - " '_evaluate_dataloaders',\n", - " '_fetch_matched_fn_callbacks',\n", - " '_set_num_eval_batch_per_dl',\n", - " '_train_batch_loop',\n", - " '_train_dataloader',\n", - " '_train_step',\n", - " '_train_step_signature_fn',\n", - " 'accumulation_steps',\n", - " 'add_callback_fn',\n", - " 'backward',\n", - " 'batch_idx_in_epoch',\n", - " 'batch_step_fn',\n", - " 'callback_manager',\n", - " 'check_batch_step_fn',\n", - " 'cur_epoch_idx',\n", - " 'data_device',\n", - " 'dataloader',\n", - " 'device',\n", - " 'driver',\n", - " 'driver_name',\n", - " 'epoch_validate',\n", - " 'evaluate_batch_step_fn',\n", - " 'evaluate_dataloaders',\n", - " 'evaluate_every',\n", - " 'evaluate_fn',\n", - " 'evaluator',\n", - " 'extract_loss_from_outputs',\n", - " 'fp16',\n", - " 'get_no_sync_context',\n", - " 'global_forward_batches',\n", - " 'has_checked_train_batch_loop',\n", - " 'input_mapping',\n", - " 'kwargs',\n", - " 'larger_better',\n", - " 'load',\n", - " 'load_model',\n", - " 'marker',\n", - " 'metrics',\n", - " 'model',\n", - " 'model_device',\n", - " 'monitor',\n", - " 'move_data_to_device',\n", - " 'n_epochs',\n", - " 'num_batches_per_epoch',\n", - " 'on',\n", - " 'on_after_backward',\n", - " 'on_after_optimizers_step',\n", - " 'on_after_trainer_initialized',\n", - " 'on_after_zero_grad',\n", - " 'on_before_backward',\n", - " 'on_before_optimizers_step',\n", - " 'on_before_zero_grad',\n", - " 'on_exception',\n", - " 'on_fetch_data_begin',\n", - " 'on_fetch_data_end',\n", - " 'on_load_checkpoint',\n", - " 'on_load_model',\n", - " 'on_sanity_check_begin',\n", - " 'on_sanity_check_end',\n", - " 'on_save_checkpoint',\n", - " 'on_save_model',\n", - " 'on_train_batch_begin',\n", - " 'on_train_batch_end',\n", - " 'on_train_begin',\n", - " 'on_train_end',\n", - " 'on_train_epoch_begin',\n", - " 'on_train_epoch_end',\n", - " 'on_validate_begin',\n", - " 'on_validate_end',\n", - " 'optimizers',\n", - " 'output_mapping',\n", - " 'run',\n", - " 'save',\n", - " 'save_model',\n", - " 'set_grad_to_none',\n", - " 'state',\n", - " 'step',\n", - " 'step_validate',\n", - " 'total_batches',\n", - " 'train_batch_loop',\n", - " 'train_dataloader',\n", - " 'train_fn',\n", - " 'train_step',\n", - " 'trainer_state',\n", - " 'zero_grad']" - ] - }, - "execution_count": 5, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ "from fastNLP import Trainer\n", "\n", - "# 定义一个 Trainer\n", "trainer = Trainer(\n", " model=model,\n", - " driver=\"torch\", # 使用 pytorch 进行训练\n", - " device=0, # 使用 GPU:0\n", + " driver=\"torch\",\n", + " device='cuda',\n", " train_dataloader=train_dataloader,\n", " optimizers=optimizer,\n", - " n_epochs=10, # 训练 40 个 epoch\n", - " progress_bar=\"rich\"\n", - ")\n", - "dir(trainer)" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "f8fe9c32", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "FullArgSpec(args=['self', 'num_train_batch_per_epoch', 'num_eval_batch_per_dl', 'num_eval_sanity_batch', 'resume_from', 'resume_training', 'catch_KeyboardInterrupt'], varargs=None, varkw=None, defaults=(-1, -1, 2, None, True, None), kwonlyargs=[], kwonlydefaults=None, annotations={'num_train_batch_per_epoch': , 'num_eval_batch_per_dl': , 'num_eval_sanity_batch': , 'resume_from': , 'resume_training': })\n" - ] - } - ], - "source": [ - "import inspect \n", - "\n", - "print(inspect.getfullargspec(trainer.run))" + " n_epochs=10, # 设定迭代轮数 \n", + " progress_bar=\"auto\" # 设定进度条格式\n", + ")" ] }, { @@ -517,16 +474,20 @@ "id": "6e202d6e", "metadata": {}, "source": [ - "没有问题,那么开始真正的训练!" + "通过使用`Trainer`类的`run`函数,进行训练\n", + "\n", + "  其中,可以通过参数`num_train_batch_per_epoch`决定每个`epoch`运行多少个`batch`后停止,默认全部\n", + "\n", + "  此外,可以通过`inspect.getfullargspec(trainer.run)`查询`run`函数的全部参数列表" ] }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 7, "id": "ba047ead", "metadata": { "pycharm": { - "is_executing": false + "is_executing": true } }, "outputs": [ @@ -585,29 +546,27 @@ "trainer.run()" ] }, - { - "cell_type": "markdown", - "id": "eb8ca6cf", - "metadata": {}, - "source": [ - "## 3. 使用 evaluator 评测模型" - ] - }, { "cell_type": "markdown", "id": "c16c5fa4", "metadata": {}, "source": [ - "模型训练好了我们开始使用 Evaluator 进行评测,查看效果怎么样吧。" + "通过从`fastNLP`库中导入`Evaluator`类,初始化`evaluator`实例,对模型进行评测\n", + "\n", + "  需要导入预先定义好的模型`model`、对应的数据加载模块`evaluate_dataloader`\n", + "\n", + "  需要注意的是评测方法`metrics`,设定为形如`{'acc': fastNLP.core.metrics.Accuracy()}`的字典\n", + "\n", + "  类似地,也可以通过`progress_bar`限定进度条格式,默认为`\"auto\"`" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 8, "id": "1c6b6b36", "metadata": { "pycharm": { - "is_executing": false + "is_executing": true } }, "outputs": [], @@ -617,100 +576,32 @@ "\n", "evaluator = Evaluator(\n", " model=model,\n", - " driver=trainer.driver, # 使用 trainer 已经启动的 driver;\n", + " driver=trainer.driver, # 需要使用 trainer 已经启动的 driver\n", " device=None,\n", " dataloaders=evaluate_dataloader,\n", - " metrics={'acc': Accuracy()} # 注意这里一定得是一个字典;\n", + " metrics={'acc': Accuracy()} # 需要严格使用此种形式的字典\n", ")" ] }, { - "cell_type": "code", - "execution_count": 11, - "id": "257061df", - "metadata": { - "scrolled": true - }, - "outputs": [ - { - "data": { - "text/plain": [ - "['__annotations__',\n", - " '__class__',\n", - " '__delattr__',\n", - " '__dict__',\n", - " '__dir__',\n", - " '__doc__',\n", - " '__eq__',\n", - " '__format__',\n", - " '__ge__',\n", - " '__getattribute__',\n", - " '__gt__',\n", - " '__hash__',\n", - " '__init__',\n", - " '__init_subclass__',\n", - " '__le__',\n", - " '__lt__',\n", - " '__module__',\n", - " '__ne__',\n", - " '__new__',\n", - " '__reduce__',\n", - " '__reduce_ex__',\n", - " '__repr__',\n", - " '__setattr__',\n", - " '__sizeof__',\n", - " '__str__',\n", - " '__subclasshook__',\n", - " '__weakref__',\n", - " '_dist_sampler',\n", - " '_evaluate_batch_loop',\n", - " '_evaluate_step',\n", - " '_evaluate_step_signature_fn',\n", - " '_metric_wrapper',\n", - " '_metrics',\n", - " 'dataloaders',\n", - " 'device',\n", - " 'driver',\n", - " 'evaluate_batch_loop',\n", - " 'evaluate_batch_step_fn',\n", - " 'evaluate_fn',\n", - " 'evaluate_step',\n", - " 'finally_progress_bar',\n", - " 'get_dataloader_metric',\n", - " 'input_mapping',\n", - " 'metrics',\n", - " 'metrics_wrapper',\n", - " 'model',\n", - " 'model_use_eval_mode',\n", - " 'move_data_to_device',\n", - " 'output_mapping',\n", - " 'progress_bar',\n", - " 'remove_progress_bar',\n", - " 'reset',\n", - " 'run',\n", - " 'separator',\n", - " 'start_progress_bar',\n", - " 'update',\n", - " 'update_progress_bar',\n", - " 'verbose']" - ] - }, - "execution_count": 11, - "metadata": {}, - "output_type": "execute_result" - } - ], + "cell_type": "markdown", + "id": "8157bb9b", + "metadata": {}, "source": [ - "dir(evaluator)" + "通过使用`Evaluator`类的`run`函数,进行训练\n", + "\n", + "  其中,可以通过参数`num_eval_batch_per_dl`决定每个`evaluate_dataloader`运行多少个`batch`停止,默认全部\n", + "\n", + "  最终,输出形如`{'acc#acc': acc}`的字典,中间的进度条会在运行结束后丢弃掉(???)" ] }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 9, "id": "f7cb0165", "metadata": { "pycharm": { - "is_executing": false + "is_executing": true } }, "outputs": [ @@ -750,11 +641,11 @@ { "data": { "text/html": [ - "
{'acc#acc': 0.3}\n",
+       "
{'acc#acc': 0.43}\n",
        "
\n" ], "text/plain": [ - "\u001b[1m{\u001b[0m\u001b[32m'acc#acc'\u001b[0m: \u001b[1;36m0.3\u001b[0m\u001b[1m}\u001b[0m\n" + "\u001b[1m{\u001b[0m\u001b[32m'acc#acc'\u001b[0m: \u001b[1;36m0.43\u001b[0m\u001b[1m}\u001b[0m\n" ] }, "metadata": {}, @@ -763,10 +654,10 @@ { "data": { "text/plain": [ - "{'acc#acc': 0.3}" + "{'acc#acc': 0.43}" ] }, - "execution_count": 12, + "execution_count": 9, "metadata": {}, "output_type": "execute_result" } @@ -780,39 +671,37 @@ "id": "dd9f68fa", "metadata": {}, "source": [ - "## 4. 在 trainer 中加入 metric 来自动评测;" - ] - }, - { - "cell_type": "markdown", - "id": "ca97c9a4", - "metadata": {}, - "source": [ - "现在我们尝试在训练过程中进行评测。" + "### 3.2 trainer 内部初始化的 evaluator \n", + "\n", + "通过在初始化`trainer`实例时加入`evaluate_dataloaders`和`metrics`,可以实现在训练过程中进行评测\n", + "\n", + "  通过`progress_bar`同时设定训练和评估进度条格式,训练结束后进度条会不显示(???)\n", + "\n", + "  **通过`evaluate_every`设定评估频率**,可以为负数、正数或者函数:\n", + "\n", + "    **为负数时**,**表示每隔几个`epoch`评估一次**;**为正数时**,**则表示每隔几个`batch`评估一次**" ] }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 10, "id": "183c7d19", "metadata": { "pycharm": { - "is_executing": false + "is_executing": true } }, "outputs": [], "source": [ - "# 重新定义一个 Trainer\n", - "\n", "trainer = Trainer(\n", " model=model,\n", - " driver=trainer.driver, # 因为我们是在同一脚本中,因此这里的 driver 同样需要重用;\n", + " driver=trainer.driver, # 因为是在同个脚本中,这里的 driver 同样需要重用\n", " train_dataloader=train_dataloader,\n", " evaluate_dataloaders=evaluate_dataloader,\n", " metrics={'acc': Accuracy()},\n", " optimizers=optimizer,\n", - " n_epochs=10, # 训练 40 个 epoch;\n", - " evaluate_every=-1, # 表示每一个 epoch 的结束会进行 evaluate;\n", + " n_epochs=10, \n", + " evaluate_every=-1, # 表示每个 epoch 的结束进行评估\n", ")" ] }, @@ -821,16 +710,18 @@ "id": "714cc404", "metadata": {}, "source": [ - "再次训练。" + "通过使用`Trainer`类的`run`函数,进行训练\n", + "\n", + "  还可以通过参数`num_eval_sanity_batch`决定每次训练前运行多少个`evaluate_batch`进行评测,默认为2" ] }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 11, "id": "2e4daa2c", "metadata": { "pycharm": { - "is_executing": false + "is_executing": true } }, "outputs": [ @@ -884,96 +775,6 @@ "source": [ "trainer.run()" ] - }, - { - "cell_type": "code", - "execution_count": 15, - "id": "eabda5eb", - "metadata": {}, - "outputs": [], - "source": [ - "evaluator = Evaluator(\n", - " model=model,\n", - " driver=trainer.driver, # 使用 trainer 已经启动的 driver;\n", - " dataloaders=evaluate_dataloader,\n", - " metrics={'acc': Accuracy()} # 注意这里一定得是一个字典;\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 16, - "id": "a310d157", - "metadata": {}, - "outputs": [ - { - "data": { - "text/html": [ - "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
\n"
-      ],
-      "text/plain": []
-     },
-     "metadata": {},
-     "output_type": "display_data"
-    },
-    {
-     "data": {
-      "text/html": [
-       "
\n",
-       "
\n" - ], - "text/plain": [ - "\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/html": [ - "
{'acc#acc': 0.5}\n",
-       "
\n" - ], - "text/plain": [ - "\u001b[1m{\u001b[0m\u001b[32m'acc#acc'\u001b[0m: \u001b[1;36m0.5\u001b[0m\u001b[1m}\u001b[0m\n" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "text/plain": [ - "{'acc#acc': 0.5}" - ] - }, - "execution_count": 16, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "evaluator.run()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "f1ef78f0", - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/tutorials/figures/T0-fig-trainer-and-evaluator.png b/tutorials/figures/T0-fig-trainer-and-evaluator.png new file mode 100644 index 0000000000000000000000000000000000000000..a98ab83b48b29a07ba450f077a95fc5fcf5a8659 GIT binary patch literal 104863 zcmd43Ra{nEvfZaD^K@VC z?>;GO%{Av7V~+Tbm|u{Ll<-T0*9Z_05HCeV1mqwfo?$^iKr_KX0Z$C0WicQi2q8oT zJ}S6C?l-@1P*8T;a_?yG(!oEh{Q}8DO4MMg=Ct-P+G)gOA&QfckJ)y+nw>-$>Me9- zRVG=^5ySQGS)-hn2 z;j~x+EpzAXZcm7_pUZH08&`;J?N<>jKj6jYdwVWFICcH}`K=2Q5{?%F3dIKkh7jV< ze|=s;>03->ApHB}pO?XHtRJ3+^V*?)&qfi5kMdvVupw+&qH>FIJIePxM1=uf_Ll&E`JG@=$pX4Ld{JCPNJFv0@{ z@z|r?exb?r^kO|329uvK7iL|P#A+u2saNOs@Lgbd!Gm8S0Wh2bMgXygsPB4rvPk)C z?dE(>J2`=3Tp#}*Y6wN)A~dN)@n0G70W^*<25n&SROfGpV9}=Wct4Cw^Lzn_{J;r$ zeQ0`xu<+$KnSNpr7fyj`H7+fa#+x_vh-p8F-#Mx{52w8lDu5j4aDeDzj z;zVGk5yAr$NB<uq{h@Nr2l&K#|mQNQJ;)*c?-J{Q6k{%TuoZoj!}0( zhUB%pm=6cL2WPLUO^}_72hKE1)sh*c9m*C-@-p--B|n%_Ae5I27A><*JX(~@SG34f z#atmyOlzL!PF2%uV>q90PVbVBNe?DE&f|$eU#9L%AY8<#AYSYc{WM(Wpw?u2Ie)$c zrBtr0ax9Sh-8ipcc+UA+IJ zqt6k923X#J(})TJ&NVR&vlc9}BCnxluO;(}Y7#MtJXaM?Z7nBLRLu~iF^ZG9G*b#5@|8FDcp#LrTTk2dU7b9}&Co^jvrzO=tP;l}Zz$gKJBJf zvgCI%mFj?wCX0Zg2Zc2KkWCG9jIjpFX{6FKE0V)Kf1I=<`d38_?H+4C7a4ykI+=Fw z1k};0wq9-Ql$Km)@WCyR=ls@6-)v%^V5!3HFBQLu<@J&mu2Hi8$*Wy3FUy(NWNMzB zrqT$}k*+a5vyelbU*@HTYZAM?hi`|~QLhQt{G5jM}Ctl*9S1V*ZqbaDgu6awiWk427f@j}VXGw!)& zre=fMl}H^vhHbK6B#Doc`g=%y2ML}oD8?Xrtn9Wp$FVtCdK2?9x*YZyYf=i5ft0!9 zxmrgJk)|(%ER{z;9?6*58;s6}q;AH>u~Z3;2P36vU44`k&&$$V%%J{4p2not@9wlO zckef_H393Hw1P%EKgXEwe=>PcVqRFxEb?*@Fud#Cq<$r`J!u&zx57N0XyojNn_2&)OnmY!wO zB^DbC4Phz&nz|iHCX)EiHeaLg3g&(1Hwp%e1e%{u6K6??o7~mhYq(SC%%!WYpS_}$|^SJ!HD_$o%}QVkE0(+3KIH`n#rW! z^yFSnif5@c9(qX{sr`JPs99e4pvz^~Sl#ZLJ0csK@j`Pi%Bb1w1M1$6aGWu%>Z4YP zwH)2)MShw?I`#-ve}5dvcmc5BJ<0x*I9`v8={}5_GTNGb2 zslM1dIT)PkXkA@;24mfrUb*q&SrtC{AMH{g2ej)SFOuNXx1v!P{vP4FO+!1pq7J(1 zK&6Tb7pby~m9u>?!K}FfpQOZDE|N5%n*-+OHPJ8hZ-zla>Ob+*⪻w1 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
T|GR{-{URVMy^n-XL^->aok%c z(n8SrRhHu-Ba^}4Rv|45zq(#-t}zU(O80%HYJ;^@&FrtS%HM|f_)KtkvgfQuw!OYG zO2wXLmCyb(TN}*hHGO4pvYkPA*E*UVzNmgAW!`dP8kvRv^<*`7o%pWKR%Aq^X?&3e zMEHCz98_9a1U?R>c<~~fiy_QJi!m>$`w{$gjRgH4+}&Myd96+{648I1bp{FbuyO4> z6E?)7*GaUHJoXox2sk>;zr&vOGJYQOe( zS!U36RJ(#Gb(&OlxMhhAW}Pm^h79t8cx27bKb5ai4Dv9gHpe!`=vM_!tD9k;`JL}i z;DkY#ifp)>peJ#1Mf;XS2+Q_wTjz5O^NFFV)%*Rzw)Ij18{dE6{5d)p|8@1fsQJ4R ztq=OJEW}sjs8i18T^kPlrnP$3XMdsGZkSZfiNdvGMCi8(5`XG$r3@ZA=3<64)VjX| z*dTxUl#4Wjfu(ilR|Gcd3B7xt%n)N(J8f3Gk829;%x+!}oCD~csn71c2PRo?>4ps_ z+L8JFp&RB6X-6{?Thb+>JCf6yhT8i)hE1JwPfQj|vQq`KDR*|>zW$}n&Y!!haMV5U zS!b_>MTqe!EX(KUNP+=1*z*Ly-pwByPlYptrX%mgJ;BbFHn-+B`GE(hR)x!>s1@)K z6BP;~%Dlo#d{nOiXfrC%!AaqfqplIOPV5XfwqiN?!RIvr#U&@n-D5Jd>SZNTWS8}u z@DTOLEih#XbTSvw^~*76WNK2qyiB(XJnac%_f^LXbN@EsOkh#QP6i@?>D74%J{Do& z5+x%YcZF!&uzzk*a#eO2P;i*3Jt)Q=Mm?>%`~fZ0F`98Agbst`Qq9#dUE6WCgBttdP#T!Bx+tT!o0pmIHmtYtB*q=9>wrDYSDpnQW9mPPcm+^ae*u_5*~vDYzXvP3N`AomHb}b zGqrAFEzmsiI?+;VH8|&TN}#`?NZ_KOx4FV50y!w*&x|kizJyHM;cV`e}e>XYr4T%G8o7x7A3{<0Jyx!NB zYeS8SdCp+5BI?-VME^jMHHyJs9kO(sMHj2#%#@vf_y~yv6b-G(>AgfZjc;pLKtHH;Q3$bLi?$R2-U`Lw%-;=bu+VFu>8y z7OtDq?`%H(6T|LJG!gN+YP~ggZt#zo8AmG>RdcpK`6OJFb#d(Cn*GnQGOOwq9_-5o|#8xfW@Xj^?G zx595ucJ{J7E{3iGxZ=;xB%l7vsi7cTu0I0l+rvyDOy2JjvE+(SRu|0m<$5<68Gw$I z#ne9Xs>H?xYNb}&LsW)ZD|i`63?dTqU0nhLp&W3?DyR6efDpZzz3f*tN^TcLf#wR( z589!*=AsnZ$$Tju^}{b~!<AdA85RgNUYz_` z!Q`=2hDu`S37;>~Do+>pLJlR>kTf>@*E1R*@dl-b{gF;TgN6nGWHv?c%VY2Kw6_`+<1b85 zD>i*XTOb;S$YLCFSN-KHMt!eRD%0A`v3)FfQj-Kxn?r(9D?CT-8~0a1C|`ht+Kc6z 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 L5HAxoeEa_ZfajY( literal 0 HcmV?d00001 From b38fc1136ef4f846a54117d8d9f1deb3aebe302e Mon Sep 17 00:00:00 2001 From: yh_cc Date: Sun, 1 May 2022 00:32:11 +0800 Subject: [PATCH 2/2] =?UTF-8?q?1.=E4=BF=AE=E6=94=B9ProgressBart=E5=9C=A8Tr?= =?UTF-8?q?ainer=E4=B8=AD=E7=9A=84=E4=B8=80=E4=B8=AAbug;2=E4=BF=AE?= =?UTF-8?q?=E5=A4=8Dpytest=E7=9A=84bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- fastNLP/core/callbacks/callback_manager.py | 54 ++++++++++++++----- fastNLP/core/callbacks/progress_callback.py | 2 - fastNLP/core/controllers/trainer.py | 16 ++---- .../test_checkpoint_callback_torch.py | 11 ++-- .../test_load_best_model_callback_torch.py | 4 +- .../callbacks/test_more_evaluate_callback.py | 3 +- tests/helpers/utils.py | 9 ++-- 7 files changed, 61 insertions(+), 38 deletions(-) diff --git a/fastNLP/core/callbacks/callback_manager.py b/fastNLP/core/callbacks/callback_manager.py index 90d2e1b1..f63c6088 100644 --- a/fastNLP/core/callbacks/callback_manager.py +++ b/fastNLP/core/callbacks/callback_manager.py @@ -9,6 +9,8 @@ __all__ = [ from .callback_events import Events 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 def _transfer(func): @@ -26,6 +28,43 @@ def _transfer(func): return wrapper +def prepare_callbacks(callbacks, progress_bar): + """ + + :param callbacks: + :param progress_bar: + :return: + """ + _callbacks = [] + if callbacks is not None: + if isinstance(callbacks, Callback): + callbacks = [callbacks] + if not isinstance(callbacks, Sequence): + raise ValueError("Parameter `callbacks` should be type 'List' or 'Tuple'.") + callbacks = list(callbacks) + for _callback in callbacks: + if not isinstance(_callback, Callback): + raise TypeError(f"callbacks must be of Callback type, instead of `{type(_callback)}`") + _callbacks += callbacks + + has_no_progress = False + for _callback in _callbacks: + if isinstance(_callback, ProgressCallback): + has_no_progress = True + if not has_no_progress: + callback = choose_progress_callback(progress_bar) + if callback is not None: + _callbacks.append(callback) + elif progress_bar is not None and progress_bar != 'auto': + logger.warning(f"Since you have passed in ProgressBar callback, progress_bar will be ignored.") + + if has_no_progress and progress_bar is None: + rank_zero_call(logger.warning)("No progress bar is provided, there will have no information output " + "during training.") + + return _callbacks + + class CallbackManager: r""" 用来管理训练过程中的所有的 callback 实例; @@ -45,24 +84,13 @@ class CallbackManager: """ self._need_reproducible_sampler = False - _callbacks = [] - if callbacks is not None: - if isinstance(callbacks, Callback): - callbacks = [callbacks] - if not isinstance(callbacks, Sequence): - raise ValueError("Parameter `callbacks` should be type 'List' or 'Tuple'.") - callbacks = list(callbacks) - for _callback in callbacks: - if not isinstance(_callback, Callback): - raise TypeError(f"callbacks must be of Callback type, instead of `{type(_callback)}`") - _callbacks += callbacks self.callback_fns = defaultdict(list) # 因为理论上用户最多只能通过 'trainer.on_train_begin' 或者 'trainer.callback_manager.on_train_begin' 来调用,即其是没办法 # 直接调用具体的某一个 callback 函数,而不调用其余的同名的 callback 函数的,因此我们只需要记录具体 Event 的时机即可; self.callback_counter = defaultdict(lambda: 0) - if len(_callbacks): + if len(callbacks): # 这一对象是为了保存原始的类 callback 对象来帮助用户进行 debug,理论上在正常的使用中你并不会需要它; - self.class_callbacks = _callbacks + self.class_callbacks = callbacks else: self.class_callbacks: Optional[List[Callback]] = [] diff --git a/fastNLP/core/callbacks/progress_callback.py b/fastNLP/core/callbacks/progress_callback.py index bacdea48..335345e0 100644 --- a/fastNLP/core/callbacks/progress_callback.py +++ b/fastNLP/core/callbacks/progress_callback.py @@ -11,8 +11,6 @@ __all__ = [ from .has_monitor_callback import HasMonitorCallback from fastNLP.core.utils import f_rich_progress from fastNLP.core.log import logger -from fastNLP.core.utils.utils import is_notebook - class ProgressCallback(HasMonitorCallback): diff --git a/fastNLP/core/controllers/trainer.py b/fastNLP/core/controllers/trainer.py index 307901b1..5223c9d8 100644 --- a/fastNLP/core/controllers/trainer.py +++ b/fastNLP/core/controllers/trainer.py @@ -19,8 +19,8 @@ from .evaluator import Evaluator from fastNLP.core.controllers.utils.utils import TrainerEventTrigger, _TruncatedDataLoader from fastNLP.core.callbacks import Callback, CallbackManager, Events, EventsList from fastNLP.core.callbacks.callback import _CallbackWrapper +from fastNLP.core.callbacks.callback_manager import prepare_callbacks from fastNLP.core.callbacks.callback_events import _SingleEventState -from fastNLP.core.callbacks.progress_callback import choose_progress_callback from fastNLP.core.drivers import Driver from fastNLP.core.drivers.utils import choose_driver from fastNLP.core.utils import get_fn_arg_names, match_and_substitute_params, nullcontext @@ -133,7 +133,7 @@ class Trainer(TrainerEventTrigger): ["all", "ignore", "only_error"];当该参数的值不是以上值时,该值应当表示一个文件夹的名字,我们会将其他 rank 的输出流重定向到 log 文件中,然后将 log 文件保存在通过该参数值设定的文件夹中;默认为 "only_error"; progress_bar: 以哪种方式显示 progress ,目前支持[None, 'raw', 'rich', 'auto'] 或者 RichCallback, RawTextCallback对象, - 默认为 auto , auto 表示如果检测到当前 terminal 为交互型 则使用 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 互斥。 @@ -212,17 +212,7 @@ class Trainer(TrainerEventTrigger): self.driver.set_optimizers(optimizers=optimizers) # 根据 progress_bar 参数选择 ProgressBarCallback - progress_bar_callback = choose_progress_callback(kwargs.get('progress_bar', 'auto')) - if progress_bar_callback is not None: - if callbacks is None: - callbacks = [] - elif not isinstance(callbacks, Sequence): - callbacks = [callbacks] - - callbacks = list(callbacks) + [progress_bar_callback] - else: - rank_zero_call(logger.warning)("No progress bar is provided, there will have no information output " - "during training.") + callbacks = prepare_callbacks(callbacks, kwargs.get('progress_bar', 'auto')) # 初始化 callback manager; self.callback_manager = CallbackManager(callbacks) # 添加所有的函数式 callbacks; diff --git a/tests/core/callbacks/test_checkpoint_callback_torch.py b/tests/core/callbacks/test_checkpoint_callback_torch.py index ca2a3292..0ae9e801 100644 --- a/tests/core/callbacks/test_checkpoint_callback_torch.py +++ b/tests/core/callbacks/test_checkpoint_callback_torch.py @@ -73,7 +73,7 @@ def model_and_optimizers(request): @pytest.mark.parametrize("driver,device", [("torch", "cpu"), ("torch_ddp", [0, 1]), ("torch", 1)]) # ("torch", "cpu"), ("torch_ddp", [0, 1]), ("torch", 1) @pytest.mark.parametrize("version", [0, 1]) @pytest.mark.parametrize("only_state_dict", [True, False]) -@magic_argv_env_context +@magic_argv_env_context(timeout=100) def test_model_checkpoint_callback_1( model_and_optimizers: TrainerParameters, driver, @@ -193,7 +193,7 @@ def test_model_checkpoint_callback_1( trainer.load_model(folder, only_state_dict=only_state_dict) trainer.run() - + trainer.driver.barrier() finally: rank_zero_rm(path) @@ -203,7 +203,7 @@ def test_model_checkpoint_callback_1( @pytest.mark.parametrize("driver,device", [("torch", "cpu"), ("torch_ddp", [0, 1]), ("torch", 1)]) # ("torch", "cpu"), ("torch_ddp", [0, 1]), ("torch", 1) @pytest.mark.parametrize("only_state_dict", [True]) -@magic_argv_env_context +@magic_argv_env_context(timeout=100) def test_model_checkpoint_callback_2( model_and_optimizers: TrainerParameters, driver, @@ -283,6 +283,7 @@ def test_model_checkpoint_callback_2( trainer.load_model(folder, only_state_dict=only_state_dict) trainer.run() + trainer.driver.barrier() finally: rank_zero_rm(path) @@ -295,7 +296,7 @@ def test_model_checkpoint_callback_2( @pytest.mark.parametrize("driver,device", [("torch", "cpu"), ("torch_ddp", [0, 1]), ("torch", 0)]) # ("torch", "cpu"), ("torch_ddp", [0, 1]), ("torch", 1) @pytest.mark.parametrize("version", [0, 1]) @pytest.mark.parametrize("only_state_dict", [True, False]) -@magic_argv_env_context +@magic_argv_env_context(timeout=100) def test_trainer_checkpoint_callback_1( model_and_optimizers: TrainerParameters, driver, @@ -413,6 +414,7 @@ def test_trainer_checkpoint_callback_1( trainer.load(folder, only_state_dict=only_state_dict) trainer.run() + trainer.driver.barrier() finally: rank_zero_rm(path) @@ -661,6 +663,7 @@ def test_trainer_checkpoint_callback_2( trainer.load(folder, model_load_fn=model_load_fn) trainer.run() + trainer.driver.barrier() finally: rank_zero_rm(path) diff --git a/tests/core/callbacks/test_load_best_model_callback_torch.py b/tests/core/callbacks/test_load_best_model_callback_torch.py index 0bc63bd5..f5b67f95 100644 --- a/tests/core/callbacks/test_load_best_model_callback_torch.py +++ b/tests/core/callbacks/test_load_best_model_callback_torch.py @@ -16,7 +16,6 @@ from fastNLP.core.controllers.trainer import Trainer from fastNLP.core.metrics.accuracy import Accuracy from fastNLP.core.callbacks.load_best_model_callback import LoadBestModelCallback from fastNLP.core import Evaluator -from fastNLP.core.utils.utils import safe_rm from fastNLP.core.drivers.torch_driver import TorchSingleDriver from tests.helpers.models.torch_model import TorchNormalModel_Classification_1 from tests.helpers.datasets.torch_data import TorchArgMaxDataset @@ -112,7 +111,8 @@ def test_load_best_model_callback( results = evaluator.run() assert np.allclose(callbacks[0].monitor_value, results['acc#acc#dl1']) if save_folder: - safe_rm(save_folder) + import shutil + shutil.rmtree(save_folder, ignore_errors=True) if dist.is_initialized(): dist.destroy_process_group() diff --git a/tests/core/callbacks/test_more_evaluate_callback.py b/tests/core/callbacks/test_more_evaluate_callback.py index 16ee3e17..115f519a 100644 --- a/tests/core/callbacks/test_more_evaluate_callback.py +++ b/tests/core/callbacks/test_more_evaluate_callback.py @@ -171,7 +171,7 @@ def test_model_more_evaluate_callback_1( trainer.load_model(folder, only_state_dict=only_state_dict) trainer.run() - + trainer.driver.barrier() finally: rank_zero_rm(path) @@ -255,6 +255,7 @@ def test_trainer_checkpoint_callback_1( trainer.load(folder, only_state_dict=only_state_dict) trainer.run() + trainer.driver.barrier() finally: rank_zero_rm(path) diff --git a/tests/helpers/utils.py b/tests/helpers/utils.py index c0b51a8b..7e02ca0d 100644 --- a/tests/helpers/utils.py +++ b/tests/helpers/utils.py @@ -33,6 +33,8 @@ def recover_logger(fn): def magic_argv_env_context(fn=None, timeout=600): """ 用来在测试时包裹每一个单独的测试函数,使得 ddp 测试正确; + 会丢掉 pytest 中的 arg 参数。 + :param timeout: 表示一个测试如果经过多久还没有通过的话就主动将其 kill 掉,默认为 10 分钟,单位为秒; :return: """ @@ -46,9 +48,10 @@ def magic_argv_env_context(fn=None, timeout=600): env = deepcopy(os.environ.copy()) used_args = [] - for each_arg in sys.argv[1:]: - if "test" not in each_arg: - used_args.append(each_arg) + # for each_arg in sys.argv[1:]: + # # warning,否则 可能导致 pytest -s . 中的点混入其中,导致多卡启动的 collect tests items 不为 1 + # if each_arg.startswith('-'): + # used_args.append(each_arg) pytest_current_test = os.environ.get('PYTEST_CURRENT_TEST')