参考来源:
CSDN:pytorch 教程之 nn.Module 类详解——使用 Module 类来自定义模型
CSDN:pytorch 教程之 nn.Module 类详解——使用 Module 类来自定义网络层
知乎:【Pytorch】nn.module() 类解析及冻结特定层参数的方法

一、使用 Module 类来自定义模型

前言pytorch 中对于一般的序列模型,直接使用 torch.nn.Sequential 类及可以实现,这点类似于keras,但是更多的时候面对复杂的模型,比如:多输入多输出、多分支模型、跨层连接模型、带有自定义层的模型等,就需要自己来定义一个模型了。本文将详细说明如何让使用 **Mudule** 类来自定义一个模型。

1. torch.nn.Module 类概述

个人理解,pytorch 不像 tensorflow 那么底层,也不像 keras 那么高层,这里先比较 keraspytorch 的一些小区别。

  1. keras 更常见的操作是通过继承 Layer 类来实现自定义层,不推荐去继承 Model 类定义模型,详细原因可以参见官方文档
  2. pytorch 中其实一般没有特别明显的 LayerModule 的区别,不管是自定义层、自定义块、自定义模型,都是通过继承 **Module** 类完成的,这一点很重要。其实 Sequential 类也是继承自 Module 类的。

注意:我们当然也可以直接通过继承 torch.autograd.Function 类来自定义一个层,但是这很不推荐,不提倡,至于为什么后面会介绍。
总结pytorch 里面一切自定义操作基本上都是继承 nn.Module 类来实现的
这里仅仅先讨论使用 Module 来实现自定义模块,自定义层先不做讨论。

2. torch.nn.Module 类的简介

先来简单看一它的定义:

  1. class Module(object):
  2. def __init__(self):
  3. def forward(self, *input):
  4. def add_module(self, name, module):
  5. def cuda(self, device=None):
  6. def cpu(self):
  7. def __call__(self, *input, **kwargs):
  8. def parameters(self, recurse=True):
  9. def named_parameters(self, prefix='', recurse=True):
  10. def children(self):
  11. def named_children(self):
  12. def modules(self):
  13. def named_modules(self, memo=None, prefix=''):
  14. def train(self, mode=True):
  15. def eval(self):
  16. def zero_grad(self):
  17. def __repr__(self):
  18. def __dir__(self):
  19. '''
  20. 有一部分没有完全列出来
  21. '''

我们在定义自已的网络的时候,需要继承 nn.Module 类,并重新实现构造函数 **__init__** 和前向传播 **forward** 这两个方法。但有一些注意技巧:

  1. 一般把网络中具有可学习参数的层(如全连接层、卷积层等)放在构造函数 **__init__()** 中,当然我也可以把不具有参数的层也放在里面。
  2. 一般把不具有可学习参数的层(如 **ReLU****dropout****BatchNormanation** 层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数 __init__ 里面,则在 forward 方法里面可以使用 nn.functional 来代替。
  3. **forward** 方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。

下面先看一个简单的例子。

  1. import torch
  2. class MyNet(torch.nn.Module):
  3. def __init__(self):
  4. super(MyNet, self).__init__() # 第一句话,调用父类的构造函数
  5. self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
  6. self.relu1=torch.nn.ReLU()
  7. self.max_pooling1=torch.nn.MaxPool2d(2,1)
  8. self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
  9. self.relu2=torch.nn.ReLU()
  10. self.max_pooling2=torch.nn.MaxPool2d(2,1)
  11. self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
  12. self.dense2 = torch.nn.Linear(128, 10)
  13. def forward(self, x):
  14. x = self.conv1(x)
  15. x = self.relu1(x)
  16. x = self.max_pooling1(x)
  17. x = self.conv2(x)
  18. x = self.relu2(x)
  19. x = self.max_pooling2(x)
  20. x = self.dense1(x)
  21. x = self.dense2(x)
  22. return x
  23. model = MyNet()
  24. print(model)
  25. '''
  26. 运行结果为:
  27. MyNet(
  28. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  29. (relu1): ReLU()
  30. (max_pooling1): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
  31. (conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  32. (relu2): ReLU()
  33. (max_pooling2): MaxPool2d(kernel_size=2, stride=1, padding=0, dilation=1, ceil_mode=False)
  34. (dense1): Linear(in_features=288, out_features=128, bias=True)
  35. (dense2): Linear(in_features=128, out_features=10, bias=True)
  36. )
  37. '''

注意:上面的是将所有的层都放在了构造函数 __init__ 里面,但是只是定义了一系列的层,各个层之间到底是什么连接关系并没有,而是在 forward 里面实现所有层的连接关系,当然这里依然是顺序连接的。下面再来看一下一个例子:

  1. import torch
  2. import torch.nn.functional as F
  3. class MyNet(torch.nn.Module):
  4. def __init__(self):
  5. super(MyNet, self).__init__() # 第一句话,调用父类的构造函数
  6. self.conv1 = torch.nn.Conv2d(3, 32, 3, 1, 1)
  7. self.conv2 = torch.nn.Conv2d(3, 32, 3, 1, 1)
  8. self.dense1 = torch.nn.Linear(32 * 3 * 3, 128)
  9. self.dense2 = torch.nn.Linear(128, 10)
  10. def forward(self, x):
  11. x = self.conv1(x)
  12. x = F.relu(x)
  13. x = F.max_pool2d(x)
  14. x = self.conv2(x)
  15. x = F.relu(x)
  16. x = F.max_pool2d(x)
  17. x = self.dense1(x)
  18. x = self.dense2(x)
  19. return x
  20. model = MyNet()
  21. print(model)
  22. '''
  23. 运行结果为:
  24. MyNet(
  25. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  26. (conv2): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  27. (dense1): Linear(in_features=288, out_features=128, bias=True)
  28. (dense2): Linear(in_features=128, out_features=10, bias=True)
  29. )
  30. '''

注意:此时,将没有训练参数的层没有放在构造函数里面了,所以这些层就不会出现在 model 里面,但是运行关系是在 forward 里面通过 nn.functional 中的方法实现的。

总结所有放在构造函数 **__init__** 里面的层的都是这个模型的“固有属性”。

3. torch.nn.Module 类的的多种实现

上面是为了一个简单的演示,但是Module类是非常灵活的,可以有很多灵活的实现方式,下面将一一介绍。

3.1 通过 Sequential 来包装层

即将几个层包装在一起作为一个大的层(块),前面的一篇文章详细介绍了 Sequential 类的使用,包括常见的三种方式,以及每一种方式的优缺点,参见语雀:torch.nn.Sequential 类

所以这里对层的包装当然也可以通过这三种方式了。

方法一

语雀:最简单的序贯模型

  1. import torch.nn as nn
  2. from collections import OrderedDict
  3. class MyNet(nn.Module):
  4. def __init__(self):
  5. super(MyNet, self).__init__()
  6. self.conv_block = nn.Sequential(
  7. nn.Conv2d(3, 32, 3, 1, 1),
  8. nn.ReLU(),
  9. nn.MaxPool2d(2))
  10. self.dense_block = nn.Sequential(
  11. nn.Linear(32 * 3 * 3, 128),
  12. nn.ReLU(),
  13. nn.Linear(128, 10)
  14. )
  15. # 在这里实现层之间的连接关系,其实就是所谓的前向传播
  16. def forward(self, x):
  17. conv_out = self.conv_block(x)
  18. res = conv_out.view(conv_out.size(0), -1)
  19. out = self.dense_block(res)
  20. return out
  21. model = MyNet()
  22. print(model)
  23. '''
  24. 运行结果为:
  25. MyNet(
  26. (conv_block): Sequential(
  27. (0): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  28. (1): ReLU()
  29. (2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  30. )
  31. (dense_block): Sequential(
  32. (0): Linear(in_features=288, out_features=128, bias=True)
  33. (1): ReLU()
  34. (2): Linear(in_features=128, out_features=10, bias=True)
  35. )
  36. )
  37. '''

同前面的文章,这里在每一个包装块里面,各个层是没有名称的,默认按照 0、1、2、3、4 来排名。

方法二

语雀:给每一个层添加名称

  1. import torch.nn as nn
  2. from collections import OrderedDict
  3. class MyNet(nn.Module):
  4. def __init__(self):
  5. super(MyNet, self).__init__()
  6. self.conv_block = nn.Sequential(
  7. OrderedDict(
  8. [
  9. ("conv1", nn.Conv2d(3, 32, 3, 1, 1)),
  10. ("relu1", nn.ReLU()),
  11. ("pool", nn.MaxPool2d(2))
  12. ]
  13. ))
  14. self.dense_block = nn.Sequential(
  15. OrderedDict([
  16. ("dense1", nn.Linear(32 * 3 * 3, 128)),
  17. ("relu2", nn.ReLU()),
  18. ("dense2", nn.Linear(128, 10))
  19. ])
  20. )
  21. def forward(self, x):
  22. conv_out = self.conv_block(x)
  23. res = conv_out.view(conv_out.size(0), -1)
  24. out = self.dense_block(res)
  25. return out
  26. model = MyNet()
  27. print(model)
  28. '''
  29. 运行结果为:
  30. MyNet(
  31. (conv_block): Sequential(
  32. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  33. (relu1): ReLU()
  34. (pool): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  35. )
  36. (dense_block): Sequential(
  37. (dense1): Linear(in_features=288, out_features=128, bias=True)
  38. (relu2): ReLU()
  39. (dense2): Linear(in_features=128, out_features=10, bias=True)
  40. )
  41. )
  42. '''

方法三

[model.add_module(self, name, module)](https://www.yuque.com/yuque-qsztn/va7nxh/znemg4#y1Jzx)

  1. import torch.nn as nn
  2. from collections import OrderedDict
  3. class MyNet(nn.Module):
  4. def __init__(self):
  5. super(MyNet, self).__init__()
  6. self.conv_block=torch.nn.Sequential()
  7. self.conv_block.add_module("conv1",torch.nn.Conv2d(3, 32, 3, 1, 1))
  8. self.conv_block.add_module("relu1",torch.nn.ReLU())
  9. self.conv_block.add_module("pool1",torch.nn.MaxPool2d(2))
  10. self.dense_block = torch.nn.Sequential()
  11. self.dense_block.add_module("dense1",torch.nn.Linear(32 * 3 * 3, 128))
  12. self.dense_block.add_module("relu2",torch.nn.ReLU())
  13. self.dense_block.add_module("dense2",torch.nn.Linear(128, 10))
  14. def forward(self, x):
  15. conv_out = self.conv_block(x)
  16. res = conv_out.view(conv_out.size(0), -1)
  17. out = self.dense_block(res)
  18. return out
  19. model = MyNet()
  20. print(model)
  21. '''
  22. 运行结果为:
  23. MyNet(
  24. (conv_block): Sequential(
  25. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  26. (relu1): ReLU()
  27. (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  28. )
  29. (dense_block): Sequential(
  30. (dense1): Linear(in_features=288, out_features=128, bias=True)
  31. (relu2): ReLU()
  32. (dense2): Linear(in_features=128, out_features=10, bias=True)
  33. )
  34. )
  35. '''

上面的方式二和方式三,在每一个包装块里面,每个层都是有名称的。

3.2 Module 类的几个常见方法使用

特别注意Sequential 类虽然继承自 Module 类,二者有相似部分,但是也有很多不同的部分,集中体现在:
**Sequenrial** 类实现了整数索引,故而可以使用 **model[index]** 这样的方式获取一个曾,但是 **Module** 类并没有实现整数索引,不能够通过整数索引来获得层,那该怎么办呢?它提供了几个主要的方法,如下:

  1. def children(self):
  2. def named_children(self):
  3. def modules(self):
  4. def named_modules(self, memo=None, prefix=''):
  5. '''
  6. 注意:这几个方法返回的都是一个Iterator迭代器,故而通过for循环访问,当然也可以通过next
  7. '''

1. model.children()model.named_children() 方法

  1. model.children()model.named_children() 方法返回的是迭代器 **iterator**
  2. **model.children()**:每一次迭代返回的每一个元素实际上是 **Sequential** 类型,而 Sequential 类型又可以使用下标 index 索引来获取每一个 Sequenrial 里面的具体层,比如 conv 层、dense 层等;
  3. **model.named_children()**:每一次迭代返回的每一个元素实际上是 一个元组类型,元组的第一个元素是名称,第二个元素就是对应的层或者是 Sequential

2. model.children() 方法

  1. import torch
  2. import torch.nn as nn
  3. from collections import OrderedDict
  4. class MyNet(nn.Module):
  5. def __init__(self):
  6. super(MyNet, self).__init__()
  7. self.conv_block = torch.nn.Sequential()
  8. self.conv_block.add_module("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1))
  9. self.conv_block.add_module("relu1", torch.nn.ReLU())
  10. self.conv_block.add_module("pool1", torch.nn.MaxPool2d(2))
  11. self.dense_block = torch.nn.Sequential()
  12. self.dense_block.add_module("dense1", torch.nn.Linear(32 * 3 * 3, 128))
  13. self.dense_block.add_module("relu2", torch.nn.ReLU())
  14. self.dense_block.add_module("dense2", torch.nn.Linear(128, 10))
  15. def forward(self, x):
  16. conv_out = self.conv_block(x)
  17. res = conv_out.view(conv_out.size(0), -1)
  18. out = self.dense_block(res)
  19. return out
  20. model = MyNet()
  21. for i in model.children():
  22. print(i)
  23. print(type(i)) # 查看每一次迭代的元素到底是什么类型,实际上是 Sequential 类型,所以有可以使用下标 index 索引来获取每一个 Sequenrial 里面的具体层
  24. '''
  25. 运行结果为:
  26. Sequential(
  27. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  28. (relu1): ReLU()
  29. (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  30. )
  31. <class 'torch.nn.modules.container.Sequential'>
  32. Sequential(
  33. (dense1): Linear(in_features=288, out_features=128, bias=True)
  34. (relu2): ReLU()
  35. (dense2): Linear(in_features=128, out_features=10, bias=True)
  36. )
  37. <class 'torch.nn.modules.container.Sequential'>
  38. '''

3. model.named_children() 方法

  1. import torch
  2. import torch.nn as nn
  3. from collections import OrderedDict
  4. class MyNet(nn.Module):
  5. def __init__(self):
  6. super(MyNet, self).__init__()
  7. self.conv_block = torch.nn.Sequential()
  8. self.conv_block.add_module("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1))
  9. self.conv_block.add_module("relu1", torch.nn.ReLU())
  10. self.conv_block.add_module("pool1", torch.nn.MaxPool2d(2))
  11. self.dense_block = torch.nn.Sequential()
  12. self.dense_block.add_module("dense1", torch.nn.Linear(32 * 3 * 3, 128))
  13. self.dense_block.add_module("relu2", torch.nn.ReLU())
  14. self.dense_block.add_module("dense2", torch.nn.Linear(128, 10))
  15. def forward(self, x):
  16. conv_out = self.conv_block(x)
  17. res = conv_out.view(conv_out.size(0), -1)
  18. out = self.dense_block(res)
  19. return out
  20. model = MyNet()
  21. for i in model.named_children():
  22. print(i)
  23. print(type(i)) # 查看每一次迭代的元素到底是什么类型,实际上是 返回一个 tuple,tuple 的第一个元素是名称
  24. '''
  25. 运行结果为:
  26. ('conv_block', Sequential(
  27. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  28. (relu1): ReLU()
  29. (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  30. ))
  31. <class 'tuple'>
  32. ('dense_block', Sequential(
  33. (dense1): Linear(in_features=288, out_features=128, bias=True)
  34. (relu2): ReLU()
  35. (dense2): Linear(in_features=128, out_features=10, bias=True)
  36. ))
  37. <class 'tuple'>
  38. '''

4. model.modules()model.named_modules()

  1. model.modules()model.named_modules() 方法返回的是迭代器 iterator
  2. modelmodules() 方法和 named_modules() 方法都会将整个模型的所有构成(包括包装层、单独的层、自定义层等)由浅入深依次遍历出来,只不过 modules() 返回的每一个元素是直接返回的层对象本身,而 named_modules() 返回的每一个元素是一个元组,第一个元素是名称,第二个元素才是层对象本身。
  3. 如何理解 childrenmodules 之间的这种差异性。注意 pytorch 里面不管是模型、层、激活函数、损失函数都可以当成是 Module 的拓展,所以 modulesnamed_modules 会层层迭代,由浅入深,将每一个自定义块 block 、然后 block 里面的每一个层都当成是 module 来迭代。而 children 就比较直观,就表示的是所谓的“孩子”,所以没有层层迭代深入。

5. model.modules() 方法

  1. import torch
  2. import torch.nn as nn
  3. from collections import OrderedDict
  4. class MyNet(nn.Module):
  5. def __init__(self):
  6. super(MyNet, self).__init__()
  7. self.conv_block = torch.nn.Sequential()
  8. self.conv_block.add_module("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1))
  9. self.conv_block.add_module("relu1", torch.nn.ReLU())
  10. self.conv_block.add_module("pool1", torch.nn.MaxPool2d(2))
  11. self.dense_block = torch.nn.Sequential()
  12. self.dense_block.add_module("dense1", torch.nn.Linear(32 * 3 * 3, 128))
  13. self.dense_block.add_module("relu2", torch.nn.ReLU())
  14. self.dense_block.add_module("dense2", torch.nn.Linear(128, 10))
  15. def forward(self, x):
  16. conv_out = self.conv_block(x)
  17. res = conv_out.view(conv_out.size(0), -1)
  18. out = self.dense_block(res)
  19. return out
  20. model = MyNet()
  21. for i in model.modules():
  22. print(i)
  23. print("==================================================")
  24. '''
  25. 运行结果为:
  26. MyNet(
  27. (conv_block): Sequential(
  28. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  29. (relu1): ReLU()
  30. (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  31. )
  32. (dense_block): Sequential(
  33. (dense1): Linear(in_features=288, out_features=128, bias=True)
  34. (relu2): ReLU()
  35. (dense2): Linear(in_features=128, out_features=10, bias=True)
  36. )
  37. )
  38. ==================================================
  39. Sequential(
  40. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  41. (relu1): ReLU()
  42. (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  43. )
  44. ==================================================
  45. Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  46. ==================================================
  47. ReLU()
  48. ==================================================
  49. MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  50. ==================================================
  51. Sequential(
  52. (dense1): Linear(in_features=288, out_features=128, bias=True)
  53. (relu2): ReLU()
  54. (dense2): Linear(in_features=128, out_features=10, bias=True)
  55. )
  56. ==================================================
  57. Linear(in_features=288, out_features=128, bias=True)
  58. ==================================================
  59. ReLU()
  60. ==================================================
  61. Linear(in_features=128, out_features=10, bias=True)
  62. ==================================================
  63. '''

6. model.named_modules() 方法

  1. import torch
  2. import torch.nn as nn
  3. from collections import OrderedDict
  4. class MyNet(nn.Module):
  5. def __init__(self):
  6. super(MyNet, self).__init__()
  7. self.conv_block = torch.nn.Sequential()
  8. self.conv_block.add_module("conv1", torch.nn.Conv2d(3, 32, 3, 1, 1))
  9. self.conv_block.add_module("relu1", torch.nn.ReLU())
  10. self.conv_block.add_module("pool1", torch.nn.MaxPool2d(2))
  11. self.dense_block = torch.nn.Sequential()
  12. self.dense_block.add_module("dense1", torch.nn.Linear(32 * 3 * 3, 128))
  13. self.dense_block.add_module("relu2", torch.nn.ReLU())
  14. self.dense_block.add_module("dense2", torch.nn.Linear(128, 10))
  15. def forward(self, x):
  16. conv_out = self.conv_block(x)
  17. res = conv_out.view(conv_out.size(0), -1)
  18. out = self.dense_block(res)
  19. return out
  20. model = MyNet()
  21. for i in model.named_modules():
  22. print(i)
  23. print("==================================================")
  24. '''
  25. 运行结果是:
  26. ('', MyNet(
  27. (conv_block): Sequential(
  28. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  29. (relu1): ReLU()
  30. (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  31. )
  32. (dense_block): Sequential(
  33. (dense1): Linear(in_features=288, out_features=128, bias=True)
  34. (relu2): ReLU()
  35. (dense2): Linear(in_features=128, out_features=10, bias=True)
  36. )
  37. ))
  38. ==================================================
  39. ('conv_block', Sequential(
  40. (conv1): Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1))
  41. (relu1): ReLU()
  42. (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  43. ))
  44. ==================================================
  45. ('conv_block.conv1', Conv2d(3, 32, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1)))
  46. ==================================================
  47. ('conv_block.relu1', ReLU())
  48. ==================================================
  49. ('conv_block.pool1', MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False))
  50. ==================================================
  51. ('dense_block', Sequential(
  52. (dense1): Linear(in_features=288, out_features=128, bias=True)
  53. (relu2): ReLU()
  54. (dense2): Linear(in_features=128, out_features=10, bias=True)
  55. ))
  56. ==================================================
  57. ('dense_block.dense1', Linear(in_features=288, out_features=128, bias=True))
  58. ==================================================
  59. ('dense_block.relu2', ReLU())
  60. ==================================================
  61. ('dense_block.dense2', Linear(in_features=128, out_features=10, bias=True))
  62. ==================================================
  63. '''

注意:上面这四个方法是以层包装为例来说明的,如果没有层的包装,我们依然可以使用这四个方法,其实结果也是类似的这样去推,这里就不再列出来了。

二、使用 Module 类来自定义网络层

前言:前面介绍了如何自定义一个模型——通过继承 nn.Module 类来实现,在 __init__ 构造函数中申明各个层的定义,在 forward 中实现层之间的连接关系,实际上就是前向传播的过程。
事实上,在 pytorch 里面自定义层也是通过继承自 nn.Module 类来实现的,我前面说过,**pytorch** 里面一般是没有层的概念,层也是当成一个模型来处理的,这里和 keras 是不一样的。前面介绍过,我们当然也可以直接通过继承 torch.autograd.Function 类来自定义一个层,但是这很不推荐,不提倡,至于为什么后面会介绍。记住一句话,keras 更加注重的是层 Layerpytorch 更加注重的是模型 Module
所以本文就专门来介绍如何通过 **nn.Module** 类来实现自定义层

1. 从系统预定义的层说起

1.1 Linear 层的代码

  1. import math
  2. import torch
  3. from torch.nn.parameter import Parameter
  4. from .. import functional as F
  5. from .. import init
  6. from .module import Module
  7. from ..._jit_internal import weak_module, weak_script_method
  8. class Linear(Module):
  9. __constants__ = ['bias']
  10. def __init__(self, in_features, out_features, bias=True):
  11. super(Linear, self).__init__()
  12. self.in_features = in_features
  13. self.out_features = out_features
  14. self.weight = Parameter(torch.Tensor(out_features, in_features))
  15. if bias:
  16. self.bias = Parameter(torch.Tensor(out_features))
  17. else:
  18. self.register_parameter('bias', None)
  19. self.reset_parameters()
  20. def reset_parameters(self):
  21. init.kaiming_uniform_(self.weight, a=math.sqrt(5))
  22. if self.bias is not None:
  23. fan_in, _ = init._calculate_fan_in_and_fan_out(self.weight)
  24. bound = 1 / math.sqrt(fan_in)
  25. init.uniform_(self.bias, -bound, bound)
  26. @weak_script_method
  27. def forward(self, input):
  28. return F.linear(input, self.weight, self.bias)
  29. def extra_repr(self):
  30. return 'in_features={}, out_features={}, bias={}'.format(
  31. self.in_features, self.out_features, self.bias is not None
  32. )

1.2 Conv2d 类的实现

  1. class Conv2d(_ConvNd):
  2. def __init__(self, in_channels, out_channels, kernel_size, stride=1,
  3. padding=0, dilation=1, groups=1,
  4. bias=True, padding_mode='zeros'):
  5. kernel_size = _pair(kernel_size)
  6. stride = _pair(stride)
  7. padding = _pair(padding)
  8. dilation = _pair(dilation)
  9. super(Conv2d, self).__init__(
  10. in_channels, out_channels, kernel_size, stride, padding, dilation,
  11. False, _pair(0), groups, bias, padding_mode)
  12. @weak_script_method
  13. def forward(self, input):
  14. if self.padding_mode == 'circular':
  15. expanded_padding = ((self.padding[1] + 1) // 2, self.padding[1] // 2,
  16. (self.padding[0] + 1) // 2, self.padding[0] // 2)
  17. return F.conv2d(F.pad(input, expanded_padding, mode='circular'),
  18. self.weight, self.bias, self.stride,
  19. _pair(0), self.dilation, self.groups)
  20. return F.conv2d(input, self.weight, self.bias, self.stride,
  21. self.padding, self.dilation, self.groups)

1.3 初步总结

我在前面的文章里面说过,torch 里面实现神经网络有两种方式

  1. 高层 **API** 方法:使用 torch.nn.**** 来实现;
  2. 低层 **API** 方法:使用低层函数方法,torch.nn.functional.**** 来实现;

其中,我们推荐使用高层 **API** 的方法,原因如下:
高层 API 是使用类的形式来包装的,既然是类就可以存储参数,比如全连接层的权值矩阵、偏置矩阵等都可以作为类的属性存储着,但是低层 API 仅仅是实现函数的运算功能,没办法保存这些信息,会丢失参数信息,但是高层 API 是依赖于低层 API 的计算函数的,比如上面的两个层:

  • **Linear** 高级层——>低层 **F.linear()** 函数
  • **Conv2d** 高级层——>低层 **F.conv2d()** 函数

1.4 自定义层的步骤

要实现一个自定义层大致分以下几个主要的步骤:

  1. 自定义一个类,继承自 **Module** 类,并且一定要实现两个基本的函数,第一是构造函数 __init__ ,第二个是层的逻辑运算函数,即所谓的前向计算函数 forward 函数。
  2. 在构造函数 init_ 中实现层的参数定义。比如 Linear 层的权重和偏置,Conv2d 层的 in_channelsout_channelskernel_sizestride=1padding=0dilation=1groups=1bias=Truepadding_mode='zeros' 这一系列参数;
  3. 在前向传播 **forward** 函数里面实现前向运算。这一般都是通过 torch.nn.functional.*** 函数来实现,当然很多时候我们也需要自定义自己的运算方式。如果该层含有权重,那么权重必须是 nn.Parameter 类型,关于TensorVariable(0.3版本之前)与 Parameter 的区别请参阅相关的文档。简单说就是 Parameter 默认需要求导,其他两个类型则不会。另外一般情况下,可能的话,为自己定义的新层提供默认的参数初始化,以防使用过程中忘记初始化操作。
  4. 补充:一般情况下,我们定义的参数是可以求导的,但是自定义操作如不可导,需要实现 backward 函数。

总结:这里其实和定义一个自定义模型是一样的,核心都是实现最基本的构造函数 __init__ 和前向运算函数 forward 函数,可以参考上面的 语雀:一、使用 Module 类来自定义模型

2. 自定义层的简单例子

比如我要实现一个简单的层,这个层的功能是 y=w*sqrt(x2+bias),即输入 X 的平方再加上一个偏执项,再开根号,然后再乘以权值矩阵 w,那要怎么做呢,按照上面的定义过程,我们先定义一个这样的层(即一个类),代码如下:

2.1 定义一个自定义层 MyLayer

  1. # 定义一个 my_layer.py
  2. import torch
  3. class MyLayer(torch.nn.Module):
  4. '''
  5. 因为这个层实现的功能是:y=weights*sqrt(x**2+bias),所以有两个参数:
  6. 权值矩阵weights
  7. 偏置矩阵bias
  8. 输入 x 的维度是(in_features,)
  9. 输出 y 的维度是(out_features,) 故而
  10. bias 的维度是(in_fearures,),注意这里为什么是in_features,而不是out_features,注意体会这里和Linear层的区别所在
  11. weights 的维度是(in_features, out_features)注意这里为什么是(in_features, out_features),而不是(out_features, in_features),注意体会这里和Linear层的区别所在
  12. '''
  13. def __init__(self, in_features, out_features, bias=True):
  14. super(MyLayer, self).__init__() # 和自定义模型一样,第一句话就是调用父类的构造函数
  15. self.in_features = in_features
  16. self.out_features = out_features
  17. self.weight = torch.nn.Parameter(torch.Tensor(in_features, out_features)) # 由于weights是可以训练的,所以使用Parameter来定义
  18. if bias:
  19. self.bias = torch.nn.Parameter(torch.Tensor(in_features)) # 由于bias是可以训练的,所以使用Parameter来定义
  20. else:
  21. self.register_parameter('bias', None)
  22. def forward(self, input):
  23. input_=torch.pow(input,2)+self.bias
  24. y=torch.matmul(input_,self.weight)
  25. return y

2.2 自定义模型并且训练

  1. import torch
  2. from my_layer import MyLayer # 自定义层
  3. N, D_in, D_out = 10, 5, 3 # 一共10组样本,输入特征为5,输出特征为3
  4. # 先定义一个模型
  5. class MyNet(torch.nn.Module):
  6. def __init__(self):
  7. super(MyNet, self).__init__() # 第一句话,调用父类的构造函数
  8. self.mylayer1 = MyLayer(D_in,D_out)
  9. def forward(self, x):
  10. x = self.mylayer1(x)
  11. return x
  12. model = MyNet()
  13. print(model)
  14. '''
  15. 运行结果为:
  16. MyNet(
  17. (mylayer1): MyLayer() # 这就是自己定义的一个层
  18. )
  19. '''

下面开始训练

  1. # 创建输入、输出数据
  2. x = torch.randn(N, D_in) #(10,5)
  3. y = torch.randn(N, D_out) #(10,3)
  4. #定义损失函数
  5. loss_fn = torch.nn.MSELoss(reduction='sum')
  6. learning_rate = 1e-4
  7. #构造一个optimizer对象
  8. optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
  9. for t in range(10): #
  10. # 第一步:数据的前向传播,计算预测值p_pred
  11. y_pred = model(x)
  12. # 第二步:计算计算预测值p_pred与真实值的误差
  13. loss = loss_fn(y_pred, y)
  14. print(f"第 {t} 个epoch, 损失是 {loss.item()}")
  15. # 在反向传播之前,将模型的梯度归零,这
  16. optimizer.zero_grad()
  17. # 第三步:反向传播误差
  18. loss.backward()
  19. # 直接通过梯度一步到位,更新完整个网络的训练参数
  20. optimizer.step()

程序的运行结果为:

  1. 0 epoch, 损失是 29.241456985473633
  2. 1 epoch, 损失是 29.223047256469727
  3. 2 epoch, 损失是 29.20465850830078
  4. 3 epoch, 损失是 29.186279296875
  5. 4 epoch, 损失是 29.167924880981445
  6. 5 epoch, 损失是 29.14959716796875
  7. 6 epoch, 损失是 29.131284713745117
  8. 7 epoch, 损失是 29.112987518310547
  9. 8 epoch, 损失是 29.094717025756836
  10. 9 epoch, 损失是 29.076465606689453

总结:

本文的实践说明了如何使用 **Module** 父类来拓展实现自定义模型、自定义层,我们发现二者有异曲同工之处,这也是 pytorch 如此受欢迎的原因之一了。后面还会继续讲解通过 Function 来自定义一个层。需要注意的是:
**Function****Module** 都可以对 **pytorch** 进行自定义拓展,使其满足网络的需求,但这两者还是有十分重要的不同,具体的不同后面再说。