torch.nn.functional.conv2d和torch.nn.Conv2d区别

2021/09/17 Pytorch 学习记录 共 5264 字,约 16 分钟

torch.nn.functional.conv2d和torch.nn.Conv2d区别

[TOC]

nn.Conv2d和functional.conv2d的区别

1、nn.Conv2d

这个是构建卷积神经网络用的类。Class类,需要继承自nn.Module nn.Module 类,用来构造卷积层。不是图像处理中的卷积。

torch.nn.Conv2d(in_channels,out_channels,kernel_size,stride=1,padding=0,dilation=1,groups=1,bias=True,padding_mode=’zeros’)

in_channels—–输入通道数

out_channels——-输出通道数

kernel_size——–卷积核大小

stride————-步长

padding———是否对输入数据填充0

2、nn.function.conv2d

这是图像处理中卷积的意思。一般来说需要一个卷积核,和输入数据分别做卷积操作,像是一个函数。

torch.nn.functional.conv2d(input,weight,bias=None,stride=1,padding=0,dilation=1,groups=1)

重点需要注意的是groups和input以及weight的关系:

input——-输入tensor大小(minibatch,in_channels,iH, iW)

weight——权重大小,就是卷积核(out_channels,$\frac{in_channels}{groups}$ , kH, kW)

注意上述的的out_channels,需要时groups的倍数。groups是将input在in_channels的维度上分成sub-input的数量。这样的好处是加快卷积的速度。比如说input shape=(8, 32, 100, 100),那么如果group设置为8,那么就是将input分成8个sub-stack array。每个array的维度是(8, 4, 100, 100)。

那么我们设置卷积核weight的时候,out_channels应该是groups的倍数。如果你嫌弃这个设计比较麻烦,groups可以设置为1.

注意:权重参数中,第一个卷积核的输出通道数,第二个是输入通道数

3、使用情况

在使用Dropout时,我们可以多使用 nn.xxx。通常情况下我们希望训练时开启 Dropout,在验证或测试时关闭 Dropout。使用 nn.Dropout时,如果调用 model.eval() ,模型的 Dropout 层都会关闭;但如果使用 nn.functional.dropout,在调用 model.eval() 时,不会关闭 Dropout。因此在在使用Dropout时定义的网络时,可以多使用 nn.xxx。

另外一个值得注意的地方是torch.nn.ConvNd 是没有办法随便定义的,因为它里面的权重都是需要学习的参数。 torch.nn.functional.conv2d() 是可以不需要一定通过学习得到。

延申:PyTorch 中,nn 与 nn.functional 有什么区别?

1. 类和函数的区别

一个包装好的类,一个是可以直接调用的函数。我们可以去翻这两个模块的具体实现代码,我下面以卷积Conv1d为例。

首先是torch.nn下的Conv1d:

class Conv1d(_ConvNd):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1,
                 padding=0, dilation=1, groups=1, bias=True):
        kernel_size = _single(kernel_size)
        stride = _single(stride)
        padding = _single(padding)
        dilation = _single(dilation)
        super(Conv1d, self).__init__(
            in_channels, out_channels, kernel_size, stride, padding, dilation,
            False, _single(0), groups, bias)

    def forward(self, input):
        return F.conv1d(input, self.weight, self.bias, self.stride,
                        self.padding, self.dilation, self.groups)

这是torch.nn.functional下的conv1d:

def conv1d(input, weight, bias=None, stride=1, padding=0, dilation=1,
           groups=1):
    if input is not None and input.dim() != 3:
        raise ValueError("Expected 3D tensor as input, got {}D tensor instead.".format(input.dim()))

    f = ConvNd(_single(stride), _single(padding), _single(dilation), False,
               _single(0), groups, torch.backends.cudnn.benchmark,
               torch.backends.cudnn.deterministic, torch.backends.cudnn.enabled)
    return f(input, weight, bias)

可以看到,torch.nn下的Conv1d类调用了F.conv1d。但是两者都是调用了用C++写的THNN库中的ConvNd。两者是互相调用的。

为什么pytorch要用两个功能相近的模块。如果只有functional下的函数,那么训练或者使用的时候,就需要手动维护weight,bias,stride之类的中间量的值;如果只有nn下面的类,那么就没有了灵活性,做一些简单的计算,比如卷积,都需要创造一个类。

比如:

import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(512, 256)
        self.fc2 = nn.Linear(256, 256)
        self.fc3 = nn.Linear(256, 2)

    def forward(self, x):
        x = F.relu(self.fc1(x))
        x = F.relu(F.dropout(self.fc2(x), 0.5))
        x = F.dropout(self.fc3(x), 0.5)
        return x

上面的代码,只有nn.Linear()三个部分是需要维持状态的,所以构造模型的时候,用了nn三个类;但是在计算relu,dropout之类的不需要保存状态,就可以直接使用,所以用的是functional的函数。

2. 下面详细的列出两者的不同

1. 两者的调用方式不一样

nn.xxx需要先传入参数并实例化,然后用函数调用的方式,调用实例化对象并传入输入的数据

inputs = torch.rand(64, 3, 244, 244)
conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1) # 传入参数,实例化
out = conv(inputs) # 调用实例化对象,输入数据

nn.functional.xxx 同时传入输入数据和其他的参数

weight = torch.rand(64,3,3,3)
bias = torch.rand(64) 
out = nn.functional.conv2d(inputs, weight, bias, padding=1)

2. nn.Xxx继承于nn.Module, 能够很好的与nn.Sequential结合使用, 而nn.functional.xxx无法与nn.Sequential结合使用。

fm_layer = nn.Sequential(
            nn.Conv2d(3, 64, kernel_size=3, padding=1),
            nn.BatchNorm2d(num_features=64),
            nn.ReLU(),
            nn.MaxPool2d(kernel_size=2),
            nn.Dropout(0.2)
  )

3. nn.Xxx不需要你自己定义和管理weight;而nn.functional.xxx需要你自己定义weight,每次调用的时候都需要手动传入weight, 不利于代码复用。

这在定义模型上面,有巨大的差别。以定义同一个网络模型为例

使用nn.Xxx定义一个CNN 。

class CNN(nn.Module):
    
    
    def __init__(self):
        super(CNN, self).__init__()
        
        self.cnn1 = nn.Conv2d(in_channels=1,  out_channels=16, kernel_size=5,padding=0)
        self.relu1 = nn.ReLU()
        self.maxpool1 = nn.MaxPool2d(kernel_size=2)
        
        self.cnn2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=5,  padding=0)
        self.relu2 = nn.ReLU()
        self.maxpool2 = nn.MaxPool2d(kernel_size=2)
        
        self.linear1 = nn.Linear(4 * 4 * 32, 10)
        
    def forward(self, x):
        x = x.view(x.size(0), -1)
        out = self.maxpool1(self.relu1(self.cnn1(x)))
        out = self.maxpool2(self.relu2(self.cnn2(out)))
        out = self.linear1(out.view(x.size(0), -1))
        return out

使用nn.function.xxx定义一个与上面相同的CNN。

class CNN(nn.Module):
    
    
    def __init__(self):
        super(CNN, self).__init__()
        
        self.cnn1_weight = nn.Parameter(torch.rand(16, 1, 5, 5)) # 自己定义weight
        self.bias1_weight = nn.Parameter(torch.rand(16))
        
        self.cnn2_weight = nn.Parameter(torch.rand(32, 16, 5, 5))
        self.bias2_weight = nn.Parameter(torch.rand(32))
        
        self.linear1_weight = nn.Parameter(torch.rand(4 * 4 * 32, 10))
        self.bias3_weight = nn.Parameter(torch.rand(10))
        
    def forward(self, x):
        x = x.view(x.size(0), -1)
        out = F.conv2d(x, self.cnn1_weight, self.bias1_weight) # 需要手动传入weight
        out = F.relu(out)
        out = F.max_pool2d(out)
        
        out = F.conv2d(x, self.cnn2_weight, self.bias2_weight)
        out = F.relu(out)
        out = F.max_pool2d(out)
        
        out = F.linear(x, self.linear1_weight, self.bias3_weight)
        return out

上面两种定义方式得到CNN功能都是相同的,至于喜欢哪一种方式,是个人口味问题,但PyTorch官方推荐:

· 具有学习参数的(例如,conv2d, linear, batch_norm)采用nn.Xxx方式,没有学习参数的(例如,maxpool, loss func, activation func)等根据个人选择使用nn.functional.xxx或者nn.Xxx方式。(这样定义模型,不如全部使用nn.xxx,等要用到某些函数实现一些功能的时候在用functional的)

· 但关于dropout,个人强烈推荐使用nn.Xxx方式,因为一般情况下只有训练阶段才进行dropout,在eval阶段都不会进行dropout。使用nn.Xxx方式定义dropout,在调用model.eval()之后,model中所有的dropout layer都关闭,但以nn.function.dropout方式定义dropout,在调用model.eval()之后并不能关闭dropout。

文档信息

Search

    Table of Contents