Torcheck —— 测试你的 PyTorch 模型

引言

你是否有过这样的经历:长时间训练 PyTorch 模型,结果发现在模型的 forward 方法中输入了一行错误?你是否曾经遇到过这样的情况:你从模型中获得了一些合理的输出,但是不确定这是否表明你构建的模型是正确的,或者这只是因为深度学习是如此强大,即使是错误的模型架构也会产生下降的结果。

就我个人而言,测试深度学习模型有时会让我抓狂。最突出的痛点是:

  • 它的黑盒特性使它很难测试。即使不是不可能,也需要很多专业知识来理解中间结果

  • 长的训练时间大大减少了迭代次数

  • 没有专门的工具。通常,您希望在一个小样本数据集上测试模型,这需要重复编写样板代码,以便进行设置优化、计算损失和反向传播

为了减少这种开销,我之前做了一些研究。这里有一篇详细文章:https://thenerdstation.medium.com/how-to-unit-test-machine-learning-code-57cf6fd81765,其核心思想是,我们永远不能百分之百确定我们的模型是正确的,但至少它应该能够通过一些合理性检验。换句话说,这些合理性检验是必要的,但可能还不够。

为了节省您的时间,以下是他提出的所有合理性检验的摘要:

  • 如果一个模型参数在训练过程中没有被故意冻结,那么它应该在训练过程中不断变化。这可以是 PyTorch 线性层的张量

  • 模型参数在训练过程中不应该改变,如果它被冻结。这可能是一个你不想更新的预训练好的层

  • 根据您的模型属性,模型输出的范围应该服从某些条件。例如,如果它是一个分类模型,它的输出不应该都在范围(0,1)内。否则,很有可能您在计算损失之前错误地将 softmax 最大激活函数应用于输出

  • (这实际上不是来自那篇文章,而是一个常见的问题)在大多数情况下,模型参数不应该包含 NaN 或 Inf(infinite number),这同样适用于模型输出

除了提出这些检查,他还构建了一个 Python 包来实现这些检查。这是一个很好的包,但仍然有未解决的痛点。这个包是几年前创建的,现在已经不再维护了。

因此,受这种合理性检验思想的启发,目标是创建一个易于使用的 Python 包,创建 torcheck!其主要创新包括:

  • 不再需要额外的测试代码。只需添加几行代码指定训练前的检查,torcheck 将在训练发生时执行检查,并在检查失败时提出信息性错误消息

  • 可以在不同的级别检查模型。你可以指定子模块、线性层甚至权重张量的检查,而不是检验整个模型!这样就可以对复杂体系结构的检查进行更多的自定义

接下来,我们会给你一个关于 torcheck 的快速教程。

假设我们已经编写了一个 ConvNet 模型来对 MNIST 数据集进行分类:

# model
class CNN(nn.Module):
    
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 1, kernel_size=1, stride=1)
        self.conv2 = nn.Conv2d(1, 8, kernel_size=3, stride=1, padding=1)
        self.conv3 = nn.Conv2d(8, 16, kernel_size=3, stride=1, padding=1)
        self.relu = nn.ReLU()
        self.maxpool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(16 * 7 * 7, 128)
        self.fc2 = nn.Linear(128, 10)
        
    def forward(self, x):
        output = self.relu(self.conv1(x))
        output = self.relu(self.conv2(x))
        output = self.maxpool(output)
        output = self.relu(self.conv3(output))
        output = self.maxpool(output)
        output = output.view(output.size()[0], -1)
        output = self.relu(self.fc1(output))
        output = self.fc2(output)
        return output




# training routine
model = CNN()
optimizer = optim.Adam(model.parameters(), lr=0.001)
for epoch in range(num_epochs):
    for x, y in dataloader:
        y_pred = model(x)
        loss = F.cross_entropy(y_pred, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

在模型代码中实际上有一个细微的错误。你们中的一些人可能已经注意到了:在第16行中,我们不小心把 x 放在了右边,而应该是 output。

现在让我们看看 torcheck 如何帮助我们检验这个隐藏的错误!

步骤0:安装

在我们开始之前,首先用一行代码安装软件包。

$ pip install torcheck

步骤1: 添加 torcheck 代码

接下来我们将添加代码。Torcheck 代码总是驻留在循环训练之前,在模型和优化器实例化之后,如下所示:

# model and optimizer instantiation
model = CNN()
optimizer = optim.Adam(model.parameters(), lr=0.001)


###########################
# torcheck code goes here #
###########################


# training routine
for epoch in range(num_epochs):
    for x, y in dataloader:
        y_pred = model(x)
        loss = F.cross_entropy(y_pred, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

步骤1.1:注册优化器

首先,用 torcheck 注册你的优化器:

torcheck.register(optimizer)

步骤1.2:增加合理性检验

接下来,添加要在四个类别中执行的所有检验。

1. 参数改变/不改变

对于我们的例子,我们希望在训练过程中更改所有的模型参数:

# check all the model parameters will change
# module_name is optional, but it makes error messages more informative when checks fail
torcheck.add_module_changing_check(model, module_name="my_model")

附注

为了演示 torcheck 的全部功能,让我们假设稍后你已经冻结了卷积层,只想微调线性层。在这种情况下将会像这样:

# check the first convolutional layer's parameters won't change
torcheck.add_module_unchanging_check(model.conv1, module_name="conv_layer_1")
# check the second convolutional layer's parameters won't change
torcheck.add_module_unchanging_check(model.conv2, module_name="conv_layer_2")
# check the third convolutional layer's parameters won't change
torcheck.add_module_unchanging_check(model.conv3, module_name="conv_layer_3")
# check the first linear layer's parameters will change
torcheck.add_module_changing_check(model.fc1, module_name="linear_layer_1")
# check the second linear layer's parameters will change
torcheck.add_module_changing_check(model.fc2, module_name="linear_layer_2")

2. 输出范围检查

因为我们的模型是一个分类模型,所以我们想要添加前面提到的检验:模型输出不应该都在范围(0,1)内。

# check model outputs are not all within (0, 1)
# aka softmax hasn't been applied before loss calculation
torcheck.add_module_output_range_check(
    model,
    output_range=(0, 1),
    negate_range=True,
)

negate_range = True 参数带有“ not all”的含义。如果您只是想检查模型输出都在某个范围内,只需删除该参数。

尽管 torcheck 不适用于我们的示例,但是它还允许你检查子模块的中间输出。

3. NaN 检查

我们当然希望确保模型参数在训练期间不会变成 NaN,并且模型输出不包含 NaN。添加 NaN 检查很简单:

# check whether model parameters become NaN or outputs contain NaN
torcheck.add_module_nan_check(model)

4. Inf 检查

类似地,添加 Inf 检查:

# check whether model parameters become infinite or outputs contain infinite value
torcheck.add_module_inf_check(model)

在添加了所有感兴趣的检验之后,最终的训练代码如下:

# model and optimizer instantiation
model = CNN()
optimizer = optim.Adam(model.parameters(), lr=0.001)


# torcheck code
torcheck.register(optimizer)
torcheck.add_module_changing_check(model, module_name="my_model")
torcheck.add_module_output_range_check(model, output_range=(0, 1), negate_range=True)
torcheck.add_module_nan_check(model)
torcheck.add_module_inf_check(model)


# training routine
for epoch in range(num_epochs):
    for x, y in dataloader:
        y_pred = model(x)
        loss = F.cross_entropy(y_pred, y)
        loss.backward()
        optimizer.step()
        optimizer.zero_grad()

步骤2:训练和修复

现在让我们像往常一样进行训练,看看会发生什么:

$ python run.py
Traceback (most recent call last):
  (stack trace information here)
RuntimeError: The following errors are detected while training:
Module my_model's conv1.weight should change.
Module my_model's conv1.bias should change.

砰!我们立即得到一个错误消息,说我们的模型的 conv1.weight 和 conv1.bias 不会改变。一定是 model.conv1出了什么问题。

正如预期的那样,我们转向模型代码,注意错误,修复它,并重新运行训练。

(可选)步骤3:关闭检验

耶! 我们的模型通过了所有的检验。最后,我们可以对其进行关闭:

torcheck.disable()

当你想要在验证集上运行模型,或者只想从你的模型训练中消除检查耗时,这是非常有用的。

如果你还想继续使用,只需要:

torcheck.enable()

·  END  ·

HAPPY LIFE

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值