1、网络模型创建步骤

模型模块中分为两个部分,模型创建和权值初始化;

模型创建又分为两部分,构建网络层和拼接网络层;网络层有卷积层,池化层,激活函数等;构建网络层后,需要进行网络层的拼接,拼接成LeNet,AlexNet和ResNet等。

创建好模型后,需要对模型进行权值初始化,pytorch提供了丰富的初始化方法,Xavier,Kaiming,均匀分布,正态分布等。

以上一切都会基于nn.Module进行,nn.Module是整个模块的根基,下面会详细介绍nn.Module。

1.1 模型创建步骤


以LeNet模型进行讲解,LeNet由很多网络层构成,由两个卷积层,两个池化层和三个全连接层组成。

在创建LeNet的时候会首先构建子模块,构建子模块之后按照一定的顺序进行连接,然后包装起来就可以得到LeNet。

LeNet是由很多子模块构成的,而这些子模块是基于nn.Module构成的。下面形象表示一下创建LeNet的过程(计算图)是怎么的。

上图可以看做一个计算图,计算图有两个主要的概念,一个是节点一个是边,节点就是张量数据,边就是运算,在图中就是箭头。

构建模型有两要素,第一是构建子模块,比如LeNet是由很多网络层构成的,所以首先得构建子模块中的网络层。构建好网络层后,第二是拼接子模块,按照一定拓扑结构拼接子模块就可以得到模型。

下面以代码阐述模型构建的步骤,首先是初始化部分,构建子模块是在__init__()中进行的:

import torch.nn as nn
import torch.nn.functional as Fclass LeNet(nn.Module):def __init__(self, classes):super(LeNet, self).__init__()    # 继承父类nn.Module的初始化self.conv1 = nn.Conv2d(3, 6, 5)    # 卷积层,卷积核为5*5,输入通道为3,输出通道为6self.conv2 = nn.Conv2d(6, 16, 5)    # 卷积层self.fc1 = nn.Linear(16*5*5, 120)    # 全连接层self.fc2 = nn.Linear(120, 84)  # 全连接层self.fc3 = nn.Linear(84, classes)    # 全连接层def forward(self, x):out = F.relu(self.conv1(x))out = F.max_pool2d(out, 2)out = F.relu(self.conv2(out))out = F.max_pool2d(out, 2)out = out.view(out.size(0), -1)out = F.relu(self.fc1(out))out = F.relu(self.fc2(out))out = self.fc3(out)return outdef initialize_weights(self):for m in self.modules():if isinstance(m, nn.Conv2d):nn.init.xavier_normal_(m.weight.data)if m.bias is not None:m.bias.data.zero_()elif isinstance(m, nn.BatchNorm2d):m.weight.data.fill_(1)m.bias.data.zero_()elif isinstance(m, nn.Linear):nn.init.normal_(m.weight.data, 0, 0.1)m.bias.data.zero_()

构建完子模块之后,进行模型的拼接是在另一部分代码中进行的:

net = LeNet(classes=2)
outputs = net(inputs)

当将输入传递给LeNet类对象net的时候,进入net(inputs)代码内部看具体情况,点击step into会进入module.py文件当中的__call__()函数,因为LeNet是继承module类的,module类中有__call__()函数表示一个实例是可以被调用的,我们进入__call__()当中的forward()函数进行查看(代码中第11行):

    def __call__(self, *input, **kwargs):for hook in self._forward_pre_hooks.values():result = hook(self, input)if result is not None:if not isinstance(result, tuple):result = (result,)input = resultif torch._C._get_tracing_state():result = self._slow_forward(*input, **kwargs)else:result = self.forward(*input, **kwargs)for hook in self._forward_hooks.values():hook_result = hook(self, input, result)if hook_result is not None:result = hook_resultif len(self._backward_hooks) > 0:var = resultwhile not isinstance(var, torch.Tensor):if isinstance(var, dict):var = next((v for v in var.values() if isinstance(v, torch.Tensor)))else:var = var[0]grad_fn = var.grad_fnif grad_fn is not None:for hook in self._backward_hooks.values():wrapper = functools.partial(hook, self)functools.update_wrapper(wrapper, hook)grad_fn.register_hook(wrapper)return result

对代码中的forward()函数点击step into,然后就进入了上面第一段代码中LeNet类的forward(self, x)函数当中:

def forward(self, x):out = F.relu(self.conv1(x))  # import torch.nn.functional as Fout = F.max_pool2d(out, 2)out = F.relu(self.conv2(out))out = F.max_pool2d(out, 2)out = out.view(out.size(0), -1)out = F.relu(self.fc1(out))out = F.relu(self.fc2(out))out = self.fc3(out)return out

这段代码具体实现了前向传播,实现了每一层的计算,最终得到分类结果out,然后out会返回forward函数中的result = self.forward(*input, **kwargs),这样就得到了outputs = net(inputs)。

综上,构建模型的子模块是在__init__()函数中实现的,拼接模块是在forward()函数中实现的,这就是模型构建的两个要素,构建子模块(init())和拼接子模块(forward())。

2、nn.Module

在构建模型模块的过程中,有一个非常重要的概念是nn.Module,所有的网络层都是继承于这个类的,现在了解一下nn.Module这个类。

介绍一下与nn.Module相关的几个模块,第一个是torch.nn,这是pytorch的一个神经网络模块,在torch.nn中有很多子模块,这里介绍四个:

  • nn.Parameter:张量子类,表示可学习参数,如weight,bias;
  • nn.Module:所有网络层基类,管理网络属性;
  • nn.functional:函数具体实现,如卷积,池化,激活函数等;
  • nn.init:参数初始化方法

这里主要介绍nn.Module,nn.module中有八个重要的属性用于管理整个模型。这里主要关注其中的parameters和modules两个属性。

  • parameters:管理存储属于nn.Parameter类的属性,例如权值或者偏置参数;
  • modules:用来存储管理nn.Module类,例如在LeNet中会构建子模块,modules就会存储创建的卷积层等;
  • buffers:存储管理缓冲的属性,如训练过程中BN的均值,或者是方差都会存储在buffers
  • ***_hooks:存储管理钩子函数(5个,暂时不去了解)

现在主要了解nn.Module的创建以及对属性的管理机制。以上面介绍的LeNet模型了解nn.Module的创建。

class LeNet(nn.Module):def __init__(self, classes):super(LeNet, self).__init__()self.conv1 = nn.Conv2d(3, 6, 5)self.conv2 = nn.Conv2d(6, 16, 5)self.fc1 = nn.Linear(16*5*5, 120)self.fc2 = nn.Linear(120, 84)self.fc3 = nn.Linear(84, classes)

代码中,LeNet是继承nn.Module类的(class LeNet(nn.Module))。super(LeNet, self).init()的作用是实现父类的函数调用功能,LeNet的父类是nn.Module,所以调用nn.Module的__init__()函数,进入super(LeNet, self).init()查看init()函数实现了什么操作。

def __init__(self):self._construct()# initialize self.training separately from the rest of the internal# state, as it is managed differently by nn.Module and ScriptModuleself.training = True

module.py代码中的__init__()函数只有两行,第一行是self._construct(),进入construct()函数。

    def _construct(self):"""Initializes internal Module state, shared by both nn.Module and ScriptModule."""torch._C._log_api_usage_once("python.nn_module")self._backend = thnn_backendself._parameters = OrderedDict()self._buffers = OrderedDict()self._backward_hooks = OrderedDict()self._forward_hooks = OrderedDict()self._forward_pre_hooks = OrderedDict()self._state_dict_hooks = OrderedDict()self._load_state_dict_pre_hooks = OrderedDict()self._modules = OrderedDict()

在construct()函数中实现了上面介绍的八个有序字典的初始化,这里主要关注其中的self._parameters和self._modules。

接着返回nn.Module()的__init__()函数中,代码中的self.training = True表示模型的训练状态,这样就构建好了一个module的基本属性。

从图中可以看到,LeNet就有了八个有序字典,可以看到字典都是空的。接着代码就开始构建子模块,第一个网络层是Conv2d的卷积层(self.conv1 = nn.Conv2d(3, 6, 5)),现在进入这个卷积层看看,进入Conv2d这个类中。

class Conv2d(_ConvNd):def __init__(self, in_channels, out_channels, kernel_size, stride=1,padding=0, dilation=1, groups=1,bias=True, padding_mode='zeros'):kernel_size = _pair(kernel_size)stride = _pair(stride)padding = _pair(padding)dilation = _pair(dilation)super(Conv2d, self).__init__(in_channels, out_channels, kernel_size, stride, padding, dilation,False, _pair(0), groups, bias, padding_mode)

Conv2d是继承于ConvNd这个类的,看一下init()函数实现什么操作。我们进入代码中的super(Conv2d, self).init(in_channels, out_channels, kernel_size, stride, padding, dilation,False, _pair(0), groups, bias, padding_mode)看看具体实现了什么操作。

class _ConvNd(Module):__constants__ = ['stride', 'padding', 'dilation', 'groups', 'bias','padding_mode', 'output_padding', 'in_channels','out_channels', 'kernel_size']def __init__(self, in_channels, out_channels, kernel_size, stride,padding, dilation, transposed, output_padding,groups, bias, padding_mode):super(_ConvNd, self).__init__()if in_channels % groups != 0:raise ValueError('in_channels must be divisible by groups')if out_channels % groups != 0:raise ValueError('out_channels must be divisible by groups')self.in_channels = in_channelsself.out_channels = out_channelsself.kernel_size = kernel_sizeself.stride = strideself.padding = paddingself.dilation = dilationself.transposed = transposedself.output_padding = output_paddingself.groups = groupsself.padding_mode = padding_modeif transposed:self.weight = Parameter(torch.Tensor(in_channels, out_channels // groups, *kernel_size))else:self.weight = Parameter(torch.Tensor(out_channels, in_channels // groups, *kernel_size))if bias:self.bias = Parameter(torch.Tensor(out_channels))else:self.register_parameter('bias', None)self.reset_parameters()

进入ConvNd模块中,可以看到,ConvNd是继承于Module,代码中还是用了super(_ConvNd, self).init()调用module类的init()函数,这里就是为了构建八个有序字典,这样一个网络层就构建完毕,返回LeNet()函数中。因为nn.Conv2d是一个module,所以会存储在module字典中。如下图所示:

这样就实现了一个子网络层的构建。下面构建第二个网络层来观察module是如何将子module存储到modules字典中的,观察一下这个机制。

观察self.conv2 = nn.Conv2d(6, 16, 5),具体的构建过程和第一个卷积层是一样的,当代码构建完成返回到代码self.conv2 = nn.Conv2d(6, 16, 5)时,这时候其实是还没有对self.conv2进行赋值的。这里只是实现了nn.Conv2d(6, 16, 5)的实例化,下一步才是赋值到属性self.conv2中,这里并不能直接赋值给self.conv2。因为在module中有一个机制,会拦截所有的类属性赋值操作,在赋值之前会跳转到module.py中的__setattr__()函数中,如下代码所示。

    def __setattr__(self, name, value):def remove_from(*dicts):for d in dicts:if name in d:del d[name]params = self.__dict__.get('_parameters')if isinstance(value, Parameter):if params is None:raise AttributeError("cannot assign parameters before Module.__init__() call")remove_from(self.__dict__, self._buffers, self._modules)self.register_parameter(name, value)elif params is not None and name in params:if value is not None:raise TypeError("cannot assign '{}' as parameter '{}' ""(torch.nn.Parameter or None expected)".format(torch.typename(value), name))self.register_parameter(name, value)else:modules = self.__dict__.get('_modules')if isinstance(value, Module):if modules is None:raise AttributeError("cannot assign module before Module.__init__() call")remove_from(self.__dict__, self._parameters, self._buffers)modules[name] = valueelif modules is not None and name in modules:if value is not None:raise TypeError("cannot assign '{}' as child module '{}' ""(torch.nn.Module or None expected)".format(torch.typename(value), name))modules[name] = valueelse:buffers = self.__dict__.get('_buffers')if buffers is not None and name in buffers:if value is not None and not isinstance(value, torch.Tensor):raise TypeError("cannot assign '{}' as buffer '{}' ""(torch.Tensor or None expected)".format(torch.typename(value), name))buffers[name] = valueelse:object.__setattr__(self, name, value)

这个函数的功能是会拦截所有类属性的赋值,观察上面的代码,代码会对参数name和value的数据类型进行判断(if isinstance(value, Parameter):)。如果value是parameter的话,就会存储到self.register_parameter(name, value)中。因为我们要赋值的Conv2d是一个类,不是parameter类型。所以代码会往下继续运行,进入if isinstance(value, Module):判断,判断数据是不是一个module,因为要赋值的数据是一个module,所以会把数据存储到modules[name] = value当中,这样就完成了对LeNet当中module字典的更新。

其余的网络层的效果也一样,这样就完成了对LeNet的构建。

nn.Module的属性构建会在module类中进行属性赋值的时候会被setattr()函数拦截,在这个函数当中会判断即将要赋值的数据类型是否是nn.parameters类,如果是的话就会存储到parameters字典中;如果是module类就会存储到modul字典中。

3、nn.Module总结

  • 一个module可以包含多个子module;例如LeNet包含很多个子module,例如卷积层,池化层等。
  • 一个module相当于一个运算,必须实现forward()函数;
  • 每个module都有8个字典管理它的属性;

pytorch ——模型创建与nn.Module相关推荐

  1. PyTorch系列入门到精通——模型创建与nn.Module

    PyTorch系列入门到精通--模型创建与nn.Module  

  2. pytorch模型容器Containers nn.ModuleDict、nn.moduleList、nn.Sequential

    模型容器Containers之nn.ModuleDict.nn.moduleList.nn.Sequential nn.Sequential()对象 建立nn.Sequential()对象,必须小心确 ...

  3. PyTorch学习笔记2:nn.Module、优化器、模型的保存和加载、TensorBoard

    文章目录 一.nn.Module 1.1 nn.Module的调用 1.2 线性回归的实现 二.损失函数 三.优化器 3.1.1 SGD优化器 3.1.2 Adagrad优化器 3.2 分层学习率 3 ...

  4. Pytorch:模型的保存与加载 torch.save()、torch.load()、torch.nn.Module.load_state_dict()

    Pytorch 保存和加载模型后缀:.pt 和.pth 1 torch.save() [source] 保存一个序列化(serialized)的目标到磁盘.函数使用了Python的pickle程序用于 ...

  5. pytorch教程之nn.Module类详解——使用Module类来自定义网络层

    前言:前面介绍了如何自定义一个模型--通过继承nn.Module类来实现,在__init__构造函数中申明各个层的定义,在forward中实现层之间的连接关系,实际上就是前向传播的过程. 事实上,在p ...

  6. PyTorch 入坑七:模块与nn.Module学习

    PyTorch 入坑七 模型创建概述 PyTorch中的模块 torch模块 torch.Tensor模块 torch.sparse模块 torch.cuda模块 torch.nn模块 torch.n ...

  7. 在C++平台上部署PyTorch模型流程+踩坑实录

    点击上方"小白学视觉",选择加"星标"或"置顶" 重磅干货,第一时间送达 导读 本文主要讲解如何将pytorch的模型部署到c++平台上的模 ...

  8. PyTorch模型保存与加载

    torch.save:保存序列化的对象到磁盘,使用了Python的pickle进行序列化,模型.张量.所有对象的字典. torch.load:使用了pickle的unpacking将pickled的对 ...

  9. pytorch模型的保存和加载、checkpoint

    pytorch模型的保存和加载.checkpoint 其实之前笔者写代码的时候用到模型的保存和加载,需要用的时候就去度娘搜一下大致代码,现在有时间就来整理下整个pytorch模型的保存和加载,开始学习 ...

最新文章

  1. 在ubuntu 16.04.5 LTS上使用Python 2.7生成带logo的二维码实战
  2. 忘了root口令解决方法
  3. ADF:在任务流终结器中支持bean作用域
  4. 使用AvalonDock制作WPF多标签浏览器(一)
  5. Netty设计模式应用
  6. Big Event in HDU
  7. C#通用验证类:邮箱,电话,手机,数字,英文,日期,身份证,邮编,网址,IP
  8. python免费课程400节-Python2 教程
  9. 【转】Struts2 验证问题:验证失败一次后就连续验证失败
  10. 计算机组成原理试题库10,计算机组成原理试题10
  11. 周公解梦|做梦的解释|鬼压床|为什么会做梦
  12. 如何混淆.NET 程序集与 ConfuserEx
  13. 【转载】正则表达式好文【30分钟教程】
  14. 口袋妖怪lets go服务器维护中,口袋妖怪lets go攻略 口袋妖怪lets go新手攻略(中)...
  15. 属于C语言类型标识符的是,【单选题】以上不属于简单比较法的缺点的是( )
  16. 源码自动生成流程图软件介绍
  17. 使用RxJava的retryWhen操作符实现token过期自动刷新
  18. 23web app实现上下左右滑动
  19. 【C 语言小游戏】手打贪吃蛇1,闭关在家37天“吃透”这份345页PDF
  20. JavaScript 数组新增 4 个非破坏性方法!

热门文章

  1. 读书笔记:《一生的计划》
  2. SQL Server 日志传送
  3. ASP.NET 2.0 中动态添加 GridView 模板列
  4. NET Core 3.0 项目中使用 AutoFac
  5. 12-Factor与云原生
  6. docker 安装 jdk,配置环境变量
  7. ramda.js api 速查
  8. js iframe 出现跨越问题
  9. portainer使用阿里云docker镜像加速器
  10. 442个超实用电脑快捷键大全!总有你会用上的