测试版本:
torch1.7.1 + CPU

python 3.6

nn是在autograd上构建的神经网络模块。

torch.mm的核心数据结构是Module

一、构建神经网络的层

1、全连接层

下面示范如何构建一个全连接层(仿射层)

import torch as t
from torch import nn
class Linear(nn.Module):
    def __init__(self,in_features,out_features):
        super(Linear,self).__init__()
        self.w = nn.Parameter(t.randn(in_features,out_features))
        self.b = nn.Parameter(t.randn(out_features))
    def forward(self,x):
        x = x.mm(self.w)
        return x + self.b.expand_as(x)
layer = Linear(4,3)
input = t.randn(2,4)
output = layer(input)
output
tensor([[ 1.9043,  0.9315,  0.9800],
        [ 3.7605,  0.7794, -2.3700]], grad_fn=<AddBackward0>)

8行便定义了仿射层

使用的注意点如下

  • 1、继承torch.nn.Module
  • 2、在init中定义所有需要学习的参数,并且封装成Parameter。Mudele便能够检测到参数并进行学习
  • 3、定义完forward函数,backward自动通过autograd实现。
  • 4、尽量使用layer(input)而非layer.forward(input)

2、多层感知机

利用前面实现的全连接层,下面我们实现多层感知机:

class Perceptron(nn.Module):
    def __init__(self,in_features,hidden_features,out_features):
        nn.Module.__init__(self)
        self.layer1 = Linear(in_features,hidden_features)# 利用到了前面构建的仿射层
        self.layer2 = Linear(hidden_features,out_features)
    def forward(self,x):
        x = self.layer1(x)
        x = t.sigmoid(x)
        return self.layer2(s)
perceptron = Perceptron(3,4,1)
for name, param in perceptron.named_parameters():
    print(name, param.size())
layer1.w torch.Size([3, 4])
layer1.b torch.Size([4])
layer2.w torch.Size([4, 1])
layer2.b torch.Size([1])

注意点:

  • Module可以有子Module,子Module的可学习参数,也会成为Module的可学习参数(比如前面代码里嵌套了Linear)
  • 前向传播时我们有意识地将变量都命名为x,为的是回收无用的中间变量。

二、常用的神经网络层

1、图像相关层

图像相关层主要是卷积层和池化层

from PIL import Image
from torchvision.transforms import ToTensor, ToPILImage
children = Image.open('img/children.png')
children

png

to_tensor = ToTensor()
to_PIL = ToPILImage()
input = to_tensor(children).unsqueeze(0)

#锐化卷积核
kernel = t.ones(3,3)/-9.
kernel[1][1] = 1
conv = nn.Conv2d(1,1,(3,3),1,bias=False)
conv.weight.data = kernel.view(1,1,3,3)
out = conv(input)
to_PIL(out.data.squeeze(0))

png

池化层可以看作没有可学习参数的卷积层。

pool = nn.AvgPool2d(2,2)
out = pool(input)
to_PIL(out.data.squeeze(0))

png

下面介绍几个其他的常用层

Linear:全连接层

BatchNorm:批规范化层

Dropout:dropout层,防止过拟合

input = t.randn(2,3)# batch_size=2 维度为3
linear = nn.Linear(3,4) # 输出为3维,输出为4维
h = linear(input)
h
tensor([[-0.5548, -0.3660,  0.1190, -0.0853],
        [-1.2067, -0.1710,  0.2143, -0.1718]], grad_fn=<AddmmBackward>)
bn = nn.BatchNorm1d(4)#标准差为4
bn.weight.data = t.ones(4)*4
bn.bias.data = t.zeros(4) # 规范化层也有权重和偏置诶。。
bn_out = bn(h)
print(bn_out)
print(bn_out.mean(0))
print(bn_out.var(0,unbiased=False))
tensor([[ 3.9998, -3.9979, -3.9912,  3.9893],
        [-3.9998,  3.9979,  3.9912, -3.9893]],
       grad_fn=<NativeBatchNormBackward>)
tensor([0.0000e+00, 3.5763e-07, 0.0000e+00, 0.0000e+00],
       grad_fn=<MeanBackward1>)
tensor([15.9985, 15.9832, 15.9298, 15.9148], grad_fn=<VarBackward1>)
drop = nn.Dropout(0.5)#每个元素有0.5的概率被舍弃
o = drop(bn_out)
o
tensor([[ 0.0000, -0.0000, -7.9824,  0.0000],
        [-7.9996,  0.0000,  7.9824, -0.0000]], grad_fn=<MulBackward0>)

2、激活函数

以ReLU函数为例。

relu = nn.ReLU(inplace=True)
input = t.randn(2,3)
print(input)
output = relu(input)
print(output)
tensor([[ 0.7331, -0.9779,  1.1113],
        [ 1.0889,  1.5384,  1.1430]])
tensor([[0.7331, 0.0000, 1.1113],
        [1.0889, 1.5384, 1.1430]])

注意inplace=True,input与output共享内存。

下面是对其进行验证

output[0][0] = 0 # 
print(input)
tensor([[0.0000, 0.0000, 1.1113],
        [1.0889, 1.5384, 1.1430]])

ReLU不需要记录中间变量就可以反向传播,所以可以进行inplace操作,但是只有极少数的层支持inplace操作。

3、Module和Sequential

Sqential是一个特殊的Module,它包含几个特殊的Module,前向传播时会一个接一个的传下去
下面介绍3种写法

net1 = nn.Sequential()
net1.add_module('conv',nn.Conv2d(3,3,3))
net1.add_module('batchnorm',nn.BatchNorm2d(3))
net1.add_module('activation_layer',nn.ReLU())

net2 = nn.Sequential(nn.BatchNorm2d(3,3,3),nn.BatchNorm2d(3),nn.ReLU())
from collections import OrderedDict
net3 = nn.Sequential(OrderedDict([('conv',nn.Conv2d(3,3,3)),('batchnorm',nn.BatchNorm2d(3)),('activation_layer',nn.ReLU())]))
print('net1:',net1)
print('net2:',net2)
print('net3:',net3)
net1: Sequential(
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (activation_layer): ReLU()
)
net2: Sequential(
  (0): BatchNorm2d(3, eps=3, momentum=3, affine=True, track_running_stats=True)
  (1): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (2): ReLU()
)
net3: Sequential(
  (conv): Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1))
  (batchnorm): BatchNorm2d(3, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (activation_layer): ReLU()
)

可以根据名字或者序号访问子Module

net1.conv,net2[0]
(Conv2d(3, 3, kernel_size=(3, 3), stride=(1, 1)),
 BatchNorm2d(3, eps=3, momentum=3, affine=True, track_running_stats=True))
input = t.randn(1,3,4,4)
print('net1:',net1(input))
net1: tensor([[[[0.8649, 0.0000],
          [0.8694, 0.0000]],

         [[0.4893, 0.0000],
          [0.3040, 0.8981]],

         [[0.7874, 0.0622],
          [0.0000, 0.8030]]]], grad_fn=<ReluBackward0>)

ModuleList和python中的list很相似。

modellist = nn.ModuleList([nn.Linear(3,4),nn.ReLU(),nn.Linear(4,2)])
input = t.randn(1,3)
for model in modellist:
    input = model(input)

与list的区别是:ModuleList是Module的子类。其中的所有参数都能被Module识别。

而List则不可以

Pytorch还有一个ParameterList,它可以包含多个parameter。与ModuleList类似。

当创建的网络需要用到List、Dict时,应该考虑是否要使用ModuleList以及ParameterList