Pytorch:nn.Dropout 与 F.dropout 的区别

67

执行dropout的方法有两种:

  • torch.nn.Dropout
  • torch.nn.functional.Dropout

我想问:

  • 它们之间有区别吗?
  • 在什么情况下应该使用其中一种而不是另一种?

当我切换它们时,我没有看到任何性能差异。

3个回答

100

其他回答已经展示了技术上的区别。然而,主要区别在于nn.Dropout本身是一个torch模块,具有一些便利性:

以下是一个简短的例子,以说明一些差异:

import torch
import torch.nn as nn

class Model1(nn.Module):
    # Model 1 using functional dropout
    def __init__(self, p=0.0):
        super().__init__()
        self.p = p

    def forward(self, inputs):
        return nn.functional.dropout(inputs, p=self.p, training=True)

class Model2(nn.Module):
    # Model 2 using dropout module
    def __init__(self, p=0.0):
        super().__init__()
        self.drop_layer = nn.Dropout(p=p)

    def forward(self, inputs):
        return self.drop_layer(inputs)
model1 = Model1(p=0.5) # functional dropout 
model2 = Model2(p=0.5) # dropout module

# creating inputs
inputs = torch.rand(10)
# forwarding inputs in train mode
print('Normal (train) model:')
print('Model 1', model1(inputs))
print('Model 2', model2(inputs))
print()

# switching to eval mode
model1.eval()
model2.eval()

# forwarding inputs in evaluation mode
print('Evaluation mode:')
print('Model 1', model1(inputs))
print('Model 2', model2(inputs))
# show model summary
print('Print summary:')
print(model1)
print(model2)

输出:

Normal (train) model:
Model 1 tensor([ 1.5040,  0.0000,  0.0000,  0.8563,  0.0000,  0.0000,  1.5951,
         0.0000,  0.0000,  0.0946])
Model 2 tensor([ 0.0000,  0.3713,  1.9303,  0.0000,  0.0000,  0.3574,  0.0000,
         1.1273,  1.5818,  0.0946])

Evaluation mode:
Model 1 tensor([ 0.0000,  0.3713,  0.0000,  0.0000,  0.0000,  0.0000,  0.0000,
         0.0000,  0.0000,  0.0000])
Model 2 tensor([ 0.7520,  0.1857,  0.9651,  0.4281,  0.7883,  0.1787,  0.7975,
         0.5636,  0.7909,  0.0473])
Print summary:
Model1()
Model2(
  (drop_layer): Dropout(p=0.5)
)

那我应该使用哪一个?

在应用dropout方面,两者是完全等效的。虽然使用上的差异不是很大,但有一些理由支持使用nn.Dropout而非nn.functional.dropout

Dropout设计为仅在训练期间应用,因此当对模型进行预测或评估时,您希望关闭dropout。

dropout模块nn.Dropout方便处理此问题,并在模型进入评估模式时关闭dropout,而功能性dropout则不关心评估/预测模式。

尽管您可以将功能性dropout设置为training=False以将其关闭,但与nn.Dropout相比,它仍然不是如此方便的解决方案。

此外,丢失率存储在模块中,因此无需将其保存在额外变量中。在较大的网络中,您可能希望创建具有不同丢失率的不同dropout层 - 在这里,nn.Dropout可以提高可读性,并且在多次使用这些层时也会更加方便。

最后,分配给您的模型的所有模块都在您的模型中注册。因此,您的模型类会跟踪它们,这就是为什么您可以通过调用eval()来关闭dropout模块。使用功能性丢失时,您的模型不知道它,因此它不会出现在任何摘要中。


5
感谢你提到“SO which should I use?” - 那是我所缺失的部分!通常我只是使用 F.dropout(x,training=self.training) 来处理训练/评估时的差异。因此,总结一下:这是个人偏好的问题吗? - CutePoison
@Jakob 是的,完全正确!- nn.Dropout 的目的只是为了提供一个稍微更高级别的 API,以便在层样式中使用功能性 dropout。但是,如果您按照您描述的方式使用它,行为上并没有真正的区别。 - MBT
当我有多个层需要应用dropout时,我是为每个层实例化一个nn.Dropout对象还是可以安全地重复使用它? 一般来说:我如何知道哪些层可以被重复使用,哪些不能? - Simon Hessner
除了输入和输出层之外,我通常在每个层中都有一个标准的。 - CutePoison
另一方面,当辍学率不固定时,我发现功能API比更改“层”的辍学率更方便。 - dedObed

12
如果您查看nn.DropoutFunctional.Dropout的源代码,就会发现Functional是一个接口,而nn模块则根据这个接口实现功能。
请查看nn类中的实现代码:
from .. import functional as F
class Dropout(_DropoutNd):
    def forward(self, input):
        return F.dropout(input, self.p, self.training, self.inplace)

class Dropout2d(_DropoutNd):
    def forward(self, input):
        return F.dropout2d(input, self.p, self.training, self.inplace)

等等。

Functional类的实现:

def dropout(input, p=0.5, training=False, inplace=False):
    return _functions.dropout.Dropout.apply(input, p, training, inplace)

def dropout2d(input, p=0.5, training=False, inplace=False):
    return _functions.dropout.FeatureDropout.apply(input, p, training, inplace)

请看下面的示例以理解:

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = nn.Conv2d(10, 20, kernel_size=5)
        self.conv2_drop = nn.Dropout2d()
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    def forward(self, x):
        x = F.relu(F.max_pool2d(self.conv1(x), 2))
        x = F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))
        x = x.view(-1, 320)
        x = F.relu(self.fc1(x))
        x = F.dropout(x, training=self.training)
        x = self.fc2(x)
        return F.log_softmax(x)

forward()函数中有F.dropout,而__init__()函数中则有nn.Dropout。这是解释:

在PyTorch中,你可以把你的模型定义为torch.nn.Module的子类。

init函数中,你需要初始化你想使用的层。不同于Keras,PyTorch更加底层,所以你需要指定网络的大小以确保一切匹配。

在forward方法中,你需要指定层之间的连接关系。这意味着你会重复使用已经初始化好的层,用于每次向前传递数据的计算。

torch.nn.functional包含了一些有用的函数,比如激活函数和卷积操作。然而,这些并不是完整的层,如果你想要定义任何类型的层,你应该使用torch.nn.Module。

你可以使用torch.nn.functional中的卷积操作来定义一个自定义层,但不是标准的卷积层。


但是什么时候应该使用什么?这有区别吗? - CutePoison
1
但是Dropout本身没有任何参数/权重。那么为什么要将它们添加为一层呢?我有点难以理解F.dropout(x)何时优于nn.Dropout(或反之亦然)。对我来说,它们完全相同。 例如:除了一个是函数,另一个是模块之外,F.droput(x)F.relu(F.max_pool2d(self.conv2_drop(self.conv2(x)), 2))有什么区别吗?你不能用F.relu(F.max_pool2d(F.dropout(self.conv2(x)), 2))替换后者吗? - CutePoison
编辑上面的内容:为什么你要在初始函数中添加它们/以那种方式使用它们? - CutePoison
您也可以在此链接中查看此帖子:https://discuss.pytorch.org/t/dropout-functional-api-advantages-disadvantages/181/3 - M. Doosti Lakhani
@CutePoison 你说得对,Dropout 没有任何参数/权重。然而,当 PyTorch 模型处于评估模式时,它不会应用 Dropout;Dropout 只应在训练中应用以获得最佳效果。如果你使用 F.dropout(x),那么你必须记得指定你是在训练还是评估模式下,而如果你使用 nn.Dropout(x),它会自动为你完成。 - Corey Levinson
这是正确的,这就是为什么我总是使用参数“train=self.train”,它处理训练/评估。 - CutePoison

2

请检查torch.nn.functional的实现:

 if p < 0. or p > 1.:
        raise ValueError("dropout probability has to be between 0 and 1, "
                         "but got {}".format(p))
    return (_VF.dropout_(input, p, training)
            if inplace
            else _VF.dropout(input, p, training))

检查:torch.nn.dropout的实现:

def forward(self, input):
        return F.dropout(input, self.p, self.training, self.inplace)

所以:它们的内部运作方式相同。接口不同。至于_VF,我猜那是一些C/C++代码。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接