目录
1、nn.Module——搭建属于自己的神经网络
前言:
-
前面介绍了自定义一个模型的5种方式,大家应该更喜欢最后一种吧,可以快速搭建复杂的神经网络、能GPU运算、还能自动求导和更新参数,简直perfect!这样的好事怎么能不深入了解一下呢?
-
pytorch中对于一般的序列模型,直接使用torch.nn.Sequential类及可以实现,这点类似于keras,但是更多的时候面对复杂的模型,比如:多输入多输出、多分支模型、跨层连接模型、带有自定义层的模型等,就需要自己来定义一个模型了。
-
事实上,在pytorch里面自定义层也是通过继承自nn.Module类来实现的,pytorch里面一般是没有层的概念,层也是当成一个模型来处理的,这里和keras是不一样的。当然也可以直接通过继承torch.autograd.Function类来自定义一个层,但是这很不推荐,不提倡,至于为什么后面会介绍。记住一句话,keras更加注重的是层Layer、pytorch更加注重的是模型Module。
-
本文将详细说明如何让使用Mudule类来自定义一个模型,以及详细解读一下系统预设的一些神经网络层,看看源代码
注意:
- 我们当然也可以直接通过继承torch.autograd.Function类来自定义一个层,但是这很不推荐,不提倡,至于为什么后面会介绍。
本文仅仅先讨论使用Module来实现自定义模块,后面会接着讨论自定义层、自定义激活函数、自定义梯度下降算法等
**本文重点为nn.Module的使用
-
希望能自己独立动手搭建属于自己的独特的神经网络
-
可以以后私人定制化设计属于自己的激活函数层、神经网络变换层–包括线性变换和非线性变换,还有梯度更新、优化算法等)**
-
总结:pytorch里面一切自定义操作基本上都是继承nn.Module类来实现的
1.1、torch.nn.Module类概述
1.1.1、torch.nn.Module类的简介
想要学习一个类,最好的方式就先看看类的方法,来看一下torch.nn.Module中的Module类中封装了哪些方法?
Module类封装的方法简介
class Module(object):
#******************************************************************************************
# 最重要__init__初始化方法,便于一些参数的传递
def __init__(self):
# 最重要的forward方法,便于进行前向传播
def forward(self, *input):
#******************************************************************************************
# add_module方法,可以添加层
def add_module(self, name, module):
# 设置处理数据、计算使用的设备
def cuda(self, device=None):
def cpu(self):
#******************************************************************************************
# __call__,非常重要的魔法方法,其中该方法中调用了forwrd方法,因此继承的时候,子类都要覆写forwrd方法
# 为了实现将不同的神经网络层进行连接
def __call__(self, *input, **kwargs):
#******************************************************************************************
def parameters(self, recurse=True):
def named_parameters(self, prefix='', recurse=True):
def children(self):
def named_children(self):
def modules(self):
def named_modules(self, memo=None, prefix=''):
def train(self, mode=True):
def eval(self):
def zero_grad(self):
def __repr__(self):
def __dir__(self):
'''
以上只是列出了最重要最常用的的一些方法,有一部分没有完全列出来
'''
设计神经网络的核心:构造函数__init__和forward方法
下面重点说一下,在设计神经网络模型的时候,最重要的两个方法:构造函数__init__和forward方法
我们在定义自已的网络的时候,需要继承nn.Module类,并重新实现构造函数__init__和forward这两个方法
。但有一些注意技巧:
(1)一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数__init__()中,当然也可以吧不具有参数的层也放在里面;
(2)
- 一般把
不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中
,也可不放在构造函数中; - 如果不具有可学习参数的层不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替
(3)forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
下面通过简单的例子来说明一下
import torch
# 神经网络模型都继承自torch.nn.Module类
class MyNet(torch.nn.Module):
"""
1. 构造函数的作用
构造函数主要用来在创建对象时完成对对象属性的一些初始化等操作, 当创建
对象时, 对象会自动调用它的构造函数。一般来说, 构造函数有以下三个方面
的作用:
- 给创建的对象建立一个标识符;
- 为对象数据成员开辟内存空间;
- 完成对象数据成员的初始化
"""
# 重写构造函数,构造函数也被称为构造器,当创建对象的时候第一个被自动调用的函数
# 一旦创建对象,则会自动调用该构造函数,完成对象的属性的初始化设置
def __init__(self):
# 调用父类的构造函数
###################################第一步############################################
#####################调用父类的构造函数,以继承父类的一些属性#############################
super(MyNet, self).__init__()
###################################第二步############################################
#####################增加一些属性,比如Conv2d卷积层、ReLU激活函数层、MaxPool2d池化层#######
# 通过self.属性名的方式,给创建的初始化实例对象增加一些属性
# 具体卷积层的涉及,留到以后再谈
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu1=torch.nn.ReLU()
self.max_pooling1=torch.nn.MaxPool2d(2,1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.relu2=torch.nn.ReLU()
self.max_pooling2=torch.nn.MaxPool2d(2,1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
###################################第三步############################################
##############覆写forward方法,实现前面定义的各个层的顺序连接,也就是完成来了前向传播##########
def forward(self, x):
x = self.conv1(x)
x = self.relu1(x)
x = self.max_pooling1(x)
x = self.conv2(x)
x = self.relu2(x)
x = self.max_pooling2(x)
x = self.dense1(x)
x = self.dense2(x)
# 前向传播结束后,返回最终得到的计算值
return x
###################################第四步:创建一个对象###################################
##############该对象会自动调用构造函数__init__(),就是说该对象已经被添加了一些层的属性###########
model = MyNet()
print(model)
'''运行结果为:
MyNet(
(conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu1): ReLU()
(max_pooling1): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
(relu2): ReLU()
(max_pooling2): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
(dense1): Linear(in_features=288, out_features=128, bias=True)
(dense2): Linear(in_features=128, out_features=10, bias=True)
)
'''
- 注意:上面的是将所有的层都放在了构造函数__init__里面,但是只是定义了一系列的层,各个层之间到底是什么连接关系并没有,而是在forward里面实现所有层的连接关系,当然这里依然是顺序连接的。下面再来看一下一个例子
import torch
import torch.nn.functional as F
class MyNet(torch.nn.Module):
def __init__(self):
############重点1:此处没有把Conv2d卷积层、ReLU激活函数层放入构造函数中,因为不含需要训练的参数#########
super(MyNet, self).__init__() # 第一步,调用父类的构造函数
self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
self.dense2 = torch.nn.Linear(128, 10)
########################重点2:由于构造函数中没有放一些不含训练参数的层###########################
###########因此在forward方法中可以使用torch.nn.functional来设置不含参数的层######################
def forward(self, x):
x = self.conv1(