文章目录
一、torch.nn.xxx 和 torch.nn.functional.xxx
在写 PyTorch 代码时,我们会发现在 torch.nn.xxx 和 torch.nn.functional.xxx 中有一些功能重复的操作,比如卷积、激活、池化。 这些操作有什么不同?各有什么用处?
首先可以观察源码:
eg:torch.nn.Conv2d
eg:torch.nn.functional
从中,我们可以发现,nn.Conv2d 是一个类,而 nn.functional.conv2d是一个函数。
换言之:
- nn.Module 实现的 layer 是由 class Layer(nn.Module) 定义的特殊类
- nn.functional 中的函数更像是纯函数,由 def function(input) 定义
此外:
两者的调用方式不同:调用 nn.xxx 时要先在里面传入超参数,然后再将数据以函数调用的方式传入 nn.xxx
# torch.nn
inputs = torch.randn(64, 3, 244, 244)
self.conv = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, padding=1)
outputs = self.conv(inputs)
# torch.nn.functional 需要同时传入数据和 weight,bias等参数
inputs = torch.randn(64, 3, 244, 244)
weight = torch.randn(64, 3, 3, 3)
bias = torch.randn(64)
outputs = nn.functinoal.conv2d(inputs, weight, bias, padding=1)
nn.xxx 能够放在 nn.Sequential里,而 nn.functional.xxx 就不行
nn.functional.xxx 需要自己定义 weight,每次调用时都需要手动传入 weight,而 nn.xxx 则不用
import torch
import torch.nn as nn
import torch.nn.functional as F
# torch.nn 定义的CNN
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv_1 = nn.Conv2d(1, 16, krenel_size=5, padding=0)
self.relu_1 = nn.ReLU(inplace=True)
self.maxpool_1 = nn.MaxPool2d(kernel_size=2)
self.conv_2 = nn.Conv2d(16, 32, krenel_size=5, padding=0)
self.relu_2 = nn.ReLU(inplace=True)
self.maxpool_2 = nn.MaxPool2d(kernel_size=2)
self.linear = nn.Linear(4*4*32, 10)
def forward(self, x):
x = x.view(x.size(0), -1)
out = self.maxpool_1(self.relu_1(self.conv_1(x)))
out = self.maxpool_2(self.relu_2(self.conv_2(out)))
out = self.linear(out.view(x.size(0), -1))
return out
# torch.nn.functional 定义一个相同的CNN
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv_1_weight = nn.Parameter(torch.randn(16, 1, 5, 5))
self.bias_1_weight = nn.Parameter(torch.randn(16))
self.conv_2_weight = nn.Parameter(torch.randn(32, 16, 5, 5))
self.bias_2_weight = nn.Parameter(torch.randn(32))
self.linear_weight = nn.Parameter(torch.randn(4 * 4 * 32, 10))
self.bias_weight = nn.Parameter(torch.randn(10))
def forward(self, x):
x = x.view(x.size(0), -1)
out = F.conv2d(x, self.conv_1_weight, self.bias_1_weight)
out = F.conv2d(out, self.conv_2_weight, self.bias_2_weight)
out = F.linear(out.view(x.size(0), -1), self.linear_weight, self.bias_weight)
在使用Dropout
时,推荐使用 nn.xxx
。因为一般只有训练时才使用 Dropout
,在验证或测试时不需要使用 Dropout
。使用 nn.Dropout
时,如果调用 model.eval()
,模型的 Dropout
层都会关闭;但如果使用 nn.functional.dropout
,在调用model.eval()
时,不会关闭 Dropout
。
当我们想要自定义卷积核时,是不能使用torch.nn.ConvNd
的,因为它里面的权重都是需要学习的参数,没有办法自行定义。但是,我们可以使用torch.nn.functional.conv2d
。
二、nn.Sequential
torch.nn.Sequential是一个Sequential容器,模块将按照构造函数中传递的顺序添加到模块中。另外,也可以传入一个有序模块。 为了更容易理解,官方给出了一些案例:
# Sequential使用实例
model = nn.Sequential(
nn.Conv2d(1,20,5),
nn.ReLU(),
nn.Conv2d(20,64,5),
nn.ReLU()
)
# Sequential with OrderedDict使用实例
model = nn.Sequential(OrderedDict([
('conv1', nn.Conv2d(1,20,5)),
('relu1', nn.ReLU()),
('conv2', nn.Conv2d(20,64,5)),
('relu2', nn.ReLU())
]))
三、nn.Module
下面我们再用 Module 定义这个模型,下面是使用 Module 的模板
class 网络名字(nn.Module):
def __init__(self, 一些定义的参数):
super(网络名字, self).__init__()
self.layer1 = nn.Linear(num_input, num_hidden)
self.layer2 = nn.Sequential(...)
...
定义需要用的网络层
def forward(self, x): # 定义前向传播
x1 = self.layer1(x)
x2 = self.layer2(x)
x = x1 + x2
...
return x
注意的是,Module 里面也可以使用 Sequential,同时 Module 非常灵活,具体体现在 forward 中,如何复杂的操作都能直观的在 forward 里面执行
四、Module 和Sequential对比
为了方便比较,我们先用普通方法搭建一个神经网络。
class Net(torch.nn.Module):
def __init__(self, n_feature, n_hidden, n_output):
super(Net, self).__init__()
self.hidden = torch.nn.Linear(n_feature, n_hidden)
self.predict = torch.nn.Linear(n_hidden, n_output)
def forward(self, x):
x = F.relu(self.hidden(x))
x = self.predict(x)
return x
net1 = Net(1, 10, 1)
net2 = torch.nn.Sequential(
torch.nn.Linear(1, 10),
torch.nn.ReLU(),
torch.nn.Linear(10, 1)
)
打印这两个net
print(net1)
"""
Net (
(hidden): Linear (1 -> 10)
(predict): Linear (10 -> 1)
)
"""
print(net2)
"""
Sequential (
(0): Linear (1 -> 10)
(1): ReLU ()
(2): Linear (10 -> 1)
)
"""
我们可以发现,打印torch.nn.Sequential会自动加入激励函数,在 net1 中, 激励函数实际上是在 forward() 功能中被调用的,没有在init中定义,所以在打印网络结构时不会有激励函数的信息.
解析源码,在torch.nn.Sequential中:
def forward(self, input):
for module in self:
input = module(input)
return input
五、Module和Sequential总结
- 使用torch.nn.Module,我们可以根据自己的需求改变传播过程,如RNN等
- 如果需要快速构建或者不需要过多的过程,直接使用torch.nn.Sequential即可。