在上一节中整理了数据模块的知识点,在本节中主要围绕如何用pytorch构建一个模型来展开,最后用pytorch实现Alexnet网络结构的搭建。
下面基于上面的框架来探索每一个模块的实现细节。
一、模型的创建
上面是LetNet的网络图,由边和节点组成,节点表示输入的数据大小,而边就是数据之间的运算。从上面的LetNet的网络中可以看出,网络接受一个输入,然后经过运算得到一个输出,在网络结构的内部,又分为多个子网络层进行拼接组成,这些子网络层之间的拼接 配合,最终得到我们想要的输出。
所以通过上面的分析,可以得到构建模型的两大要素:
● 构建子模块(比如网络结构中的卷积层、池化层、激活层、全连接层);
● 拼接子模块(将子模块按照一定的顺序拼接起来,最终得到想到的网络结构)。
以人民币的二分类任务来构建LetNet网络结构:
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)
def forward(self, x):
# layer1: conv2d -> relu -> max_pool
x = F.relu(self.conv1(x))
x = F.max_pool2d(x, 2)
# layer2: conv2d -> relu -> max_pool
x = F.relu(self.conv2(x))
x = F.max_pool2d(x, 2)
# layer3: fc1 -> relu
x = x.view(x.size(0), -1)
x = F.relu(self.fc1(x))
# layer4: fc2 -> relu
x = F.relu(self.fc2(x))
# layer5:fc3
x = self.fc3(x)
return x
def 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_()
从上面的代码里可看出,LeNet类继承了nn.Module,并且在__init__方法中实现了各个子模块的构建,所以构建模型的第一个要素—— 构建子模块在这里体现。
那子模块的拼接是怎么实现的呢?——LetNet类里面的forward方法,下面来调试代码,看如何调用forward方法来实现子模块的拼接操作。
主程序模型训练部分,在outputs = net(inputs)处打上断点,因为这里是模型开始训练的部分,此处也是模型开始正向传播的过程,debug如下:
程序进入module.py文件中的__call_impl函数,这是因为类LetNet继承了nn.Module。在这里会实现调用forward方法,把各个子模块拼接起来。
在构造模型中继承了 nn.Module 类,所有的网络层都是继承于这个类的。所以下面来详细了解一下nn.Module类。
二、nn.Module 类
1、nn.Module的简介
torch.nn: 这是 Pytorch 的神经网络模块,这里的 Module 就是它的子模块之一,另外还有几个与 Module 并列的子模块。其中nn.Module的自定义为:
class Module(object):
def __init__(self):
torch._C._log_api_usage_once("python.nn_module")
self.training = True
self._parameters = OrderedDict()
self._buffers = OrderedDict()
self._non_persistent_buffers_set = set()
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()
def register_parameter(self, name: str, param: Optional[Parameter]) -> None:
def add_module(self, name: str, module: Optional['Module']) -> None:
def get_parameter(self, target: str) -> "Parameter":
def apply(self: T, fn: Callable[['Module'], None]) -> T:
def cuda(self: T, device: Optional[Union[int, device]] = None) -> T:
def cpu(self: T) -> T:
def state_dict(self, destination=None, prefix='', keep_vars=False):
def load_state_dict(self, state_dict: 'OrderedDict[str, Tensor]',
strict: bool = True):
def named_parameters(self, prefix: str = '',
recurse: bool = True) -> Iterator[Tuple[str, Parameter]]:
def children(self) -> Iterator['Module']:
def named_children(self) -> Iterator[Tuple[str, 'Module']]:
def modules(self) -> Iterator['Module']:
def named_modules(self, memo: Optional[Set['Module']] = None,
prefix: str = '', remove_duplicate: bool = True):
def train(self: T, mode: bool = True) -> T:
def eval(self: T) -> T:
...
"""
还有一些其他的函数
"""
在nn.Module自定义中的属性:
● parameters:存储管理 nn.Parameter 类;
● modules:存储管理nn.Modules类;
● buffers:存储管理缓冲属性,如BN层中的running_mean;
● ***_hook:存储管理钩子函数
在构造网络结构是需要继承 nn.Module 类,并重新构造 __init__和forward两个方法,但需要注意的有:
● 一般将网络中具有可学习参数的层(如卷积层、全连接层)放在构造函数 __init__中,当然也可以把不具有参数的层也放在里面;
● 一般把不具有可学习参数的层(如ReLU、dropout、BatchNormanation层)可放在构造函数中,也可不放在构造函数中,如果不放在构造函数__init__里面,则在forward方法里面可以使用nn.functional来代替;
● forward方法是必须要重写的,它是实现模型的功能,实现各个层之间的连接关系的核心。
例如:
1、将所有的层都放在构造函数 __init__中,然后在forward中将这些层按顺序连接起来。
import torch.nn as nn
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, 1, 1)
self.relu1 = nn.ReLU()
self.max_pooling1 = nn.MaxPool2d(2, 1)
self.conv2 = nn.Conv2d(32, 32, 3, 1, 1)
self.relu2 = nn.ReLU()
self.max_pooling2 = nn.MaxPool2d(2, 1)
self.dense1 = nn.Linear(32*3*3, 128)
self.dense2 = nn.Linear(128, 10)
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
model = MyNet()
print(model)
输出结果:
2、将没有训练参数的层放到forward中,所以这些层没有出现在model中,但是在运行关系是在forward中通过torch.nn.function实现,如下:
import torch.nn.functional as F
class MyNet(nn.Module):
def __init__(self):
super(MyNet, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, 1, 1)
self.conv2 = nn.Conv2d(3, 32, 3, 1, 1)
self.dense1 = nn.Linear(32 * 3 * 3, 128)
self.dense2 = nn.Linear(128, 10)
def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = F.max_pool2d(x)
x = self.conv2(x)
x = F