自定义模型 Evaluator
===============================
模型评估器(Evaluator)对于评估新探索的模型的性能是必要的。 一个模型评估器通常包括训练、验证和测试一个单一的模型。 我们为用户提供了两种方法来编写新的模型评估器,下面将分别演示。
使用 FunctionalEvaluator
------------------------
定制一个新的评估器的最简单的方法是使用功能性的 API,当训练代码已经可用时,这就非常容易。 用户只需要编写一个 fit 函数来包装所有内容。 此函数接收一个位置参数(``model_cls``)和可能的关键字参数。 关键字参数(除 ``model_cls`` 外)作为 FunctionEvaluator 的初始化参数被输入。 通过这种方式,用户可以控制一切,但向框架公开的信息较少,因此进行优化的机会也较少。 示例如下。
.. code-block:: python
from nni.retiarii.evaluator import FunctionalEvaluator
from nni.retiarii.experiment.pytorch import RetiariiExperiment
def fit(model_cls, dataloader):
model = model_cls()
train(model, dataloader)
acc = test(model, dataloader)
nni.report_final_result(acc)
evaluator = FunctionalEvaluator(fit, dataloader=DataLoader(foo, bar))
experiment = RetiariiExperiment(base_model, evaluator, mutators, strategy)
.. note:: 由于我们目前的实施限制,``fit`` 函数应该放在另一个 python 文件中,而不是放在主文件中。 这个限制将在未来的版本中得到修复。
使用 PyTorch-Lightning
----------------------
NNI 建议以 PyTorch-Lightning 风格编写训练代码,即编写一个 LightningModule,定义训练所需的所有元素(例如 loss function、optimizer),并定义一个 Trainer,使用 dataloader 来执行训练(可选)。 在此之前,请阅读 `PyTorch-lightning 文档 ` 了解 PyTorch-lightning 的基本概念和组件。 在此之前,请阅读 `PyTorch-lightning 文档 `__ 了解 PyTorch-lightning 的基本概念和组件。
在实践中,在 NNI 中编写一个新的训练模块应继承 ``nni.retiarii.trainer.pytorch.lightning.LightningModule``,它将在 ``__init__`` 之后调用 ``set_model`` 函数,以将候选模型(由策略生成的)保存为 ``self.model``。 编写其余过程(如 ``training_step``)应与其他 lightning 模块相同。 Evaluators 还应该通过两个 API 调用与策略进行通讯(对于中间指标而言为 ``nni.report_intermediate_result``,对于最终指标而言为 ``nni.report_final_result``),分别被添加在 ``on_validation_epoch_end`` 和 ``teardown`` 中。
示例如下。
.. code-block:: python
from nni.retiarii.evaluator.pytorch.lightning import LightningModule # please import this one
@basic_unit
class AutoEncoder(LightningModule):
def __init__(self):
super().__init__()
self.decoder = nn.Sequential(
nn.Linear(3, 64),
nn.ReLU(),
nn.Linear(64, 28*28)
)
def forward(self, x):
embedding = self.model(x) # let's search for encoder
return embedding
def training_step(self, batch, batch_idx):
# training_step 定义了训练循环
# 它独立于 forward 函数
x, y = batch
x = x.view(x.size(0), -1)
z = self.model(x) # 模型是一个被搜索的模型
x_hat = self.decoder(z)
loss = F.mse_loss(x_hat, x)
# 默认日志记录到 TensorBoard
self.log('train_loss', loss)
return loss
def validation_step(self, batch, batch_idx):
x, y = batch
x = x.view(x.size(0), -1)
z = self.model(x)
x_hat = self.decoder(z)
loss = F.mse_loss(x_hat, x)
self.log('val_loss', loss)
def configure_optimizers(self):
optimizer = torch.optim.Adam(self.parameters(), lr=1e-3)
return optimizer
def on_validation_epoch_end(self):
nni.report_intermediate_result(self.trainer.callback_metrics['val_loss'].item())
def teardown(self, stage):
if stage == 'fit':
nni.report_final_result(self.trainer.callback_metrics['val_loss'].item())
然后,用户需要将所有东西(包括 LightningModule、trainer 和 dataloaders)包装成一个 ``Lightning`` 对象,并将这个对象传递给 Retiarii Experiment。
.. code-block:: python
import nni.retiarii.evaluator.pytorch.lightning as pl
from nni.retiarii.experiment.pytorch import RetiariiExperiment
lightning = pl.Lightning(AutoEncoder(),
pl.Trainer(max_epochs=10),
train_dataloader=pl.DataLoader(train_dataset, batch_size=100),
val_dataloaders=pl.DataLoader(test_dataset, batch_size=100))
experiment = RetiariiExperiment(base_model, lightning, mutators, strategy)