乐趣区

关于人工智能:PyTorch模型容器与AlexNet构建

文章和代码曾经归档至【Github 仓库:https://github.com/timerring/dive-into-AI】或者公众号【AIShareLab】回复 pytorch 教程 也可获取。

模型容器与 AlexNet 构建

除了上述的模块之外,还有一个重要的概念是模型容器 (Containers),罕用的容器有 3 个,这些容器都是继承自nn.Module

  • nn.Sequetial:依照程序包装多个网络层
  • nn.ModuleList:像 python 的 list 一样包装多个网络层,能够迭代
  • nn.ModuleDict:像 python 的 dict 一样包装多个网络层,通过 (key, value) 的形式为每个网络层指定名称。

nn.Sequetial

深度学习中,特征提取和分类器这两步被交融到了一个神经网络中。在卷积神经网络中,后面的卷积层以及池化层能够认为是特征提取局部,而前面的全连贯层能够认为是分类器局部。比方 LeNet 就能够分为 特征提取 分类器 两局部,这 2 局部都能够别离应用 nn.Seuqtial 来包装。

代码如下:

class LeNetSequetial(nn.Module):
    def __init__(self, classes):
        super(LeNet2, self).__init__()
        self.features = nn.Sequential(nn.Conv2d(3, 6, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
            nn.Conv2d(6, 16, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, 2)
        )
        self.classifier = nn.Sequential(nn.Linear(16*5*5, 120),
            nn.ReLU(),
            nn.Linear(120, 84),
            nn.ReLU(),
            nn.Linear(84, classes)
        )

    def forward(self, x):
        x = self.features(x)
        x = x.view(x.size()[0], -1)
        x = self.classifier(x)
        return x

在初始化时,nn.Sequetial会调用 __init__() 办法,将每一个子 module 增加到 本身的 _modules 属性中。这里能够看到,咱们传入的参数能够是一个 list,或者一个 OrderDict。如果是一个 OrderDict,那么则应用 OrderDict 里的 key,否则应用数字作为 key。

    def __init__(self, *args):
        super(Sequential, self).__init__()
        if len(args) == 1 and isinstance(args[0], OrderedDict):
            for key, module in args[0].items():
                self.add_module(key, module)
        else:
            for idx, module in enumerate(args):
                self.add_module(str(idx), module)

网络初始化实现后有两个子 modulefeaturesclassifier

features 中的子 module 如下,每个网络层以序号作为 key:

在进行前向流传时,会进入 LeNet 的 forward() 函数,首先调用第一个 Sequetial 容器:self.features,因为 self.features 也是一个 module,因而会调用 __call__() 函数,外面调用

result = self.forward(*input, **kwargs),进入 nn.Seuqetialforward()函数,在这里顺次调用所有的 module。上一个 module 的输入是下一个 module 的输出。

  def forward(self, input):
        for module in self:
            input = module(input)
        return input

在下面能够看到在 nn.Sequetial 中,外面的每个子网络层 module 是应用序号来索引的,即应用数字来作为 key。

一旦网络层增多,难以查找特定的网络层,这种状况能够应用 OrderDict (有序字典)。能够与下面的代码比照一下

class LeNetSequentialOrderDict(nn.Module):
    def __init__(self, classes):
        super(LeNetSequentialOrderDict, self).__init__()

        self.features = nn.Sequential(OrderedDict({'conv1': nn.Conv2d(3, 6, 5),
            'relu1': nn.ReLU(inplace=True),
            'pool1': nn.MaxPool2d(kernel_size=2, stride=2),

            'conv2': nn.Conv2d(6, 16, 5),
            'relu2': nn.ReLU(inplace=True),
            'pool2': nn.MaxPool2d(kernel_size=2, stride=2),
        }))

        self.classifier = nn.Sequential(OrderedDict({'fc1': nn.Linear(16*5*5, 120),
            'relu3': nn.ReLU(),

            'fc2': nn.Linear(120, 84),
            'relu4': nn.ReLU(inplace=True),

            'fc3': nn.Linear(84, classes),
        }))
        ...
        ...
        ...

总结

nn.Sequetialnn.Module 的容器,用于按程序包装一组网络层,有以下两个个性。

  • 程序性:各网络层之间严格依照程序构建,咱们在构建网络时,肯定要留神前后网络层之间输出和输入数据之间的形态是否匹配
  • 自带 forward() 函数:在 nn.Sequetialforward()函数里通过 for 循环顺次读取每个网络层,执行前向流传运算。这使得咱们咱们构建的模型更加简洁

nn.ModuleList

nn.ModuleListnn.Module 的容器,用于包装一组网络层,以迭代的形式调用网络层,次要有以下 3 个办法:

  • append():在 ModuleList 前面增加网络层
  • extend():拼接两个 ModuleList
  • insert():在 ModuleList 的指定地位中插入网络层

上面的代码通过列表生成式来循环迭代创立 20 个全连贯层,十分不便,只是在 forward()函数中须要手动调用每个网络层。

class ModuleList(nn.Module):
    def __init__(self):
        super(ModuleList, self).__init__()
        self.linears = nn.ModuleList([nn.Linear(10, 10) for i in range(20)])

    def forward(self, x):
        for i, linear in enumerate(self.linears):
            x = linear(x)
        return x


net = ModuleList()

print(net)

fake_data = torch.ones((10, 10))

output = net(fake_data)

print(output)

nn.ModuleDict

nn.ModuleDictnn.Module 的容器,用于包装一组网络层,以索引的形式调用网络层,次要有以下 5 个办法:

  • clear():清空 ModuleDict
  • items():返回可迭代的键值对 (key, value)
  • keys():返回字典的所有 key
  • values():返回字典的所有 value
  • pop():返回一对键值,并从字典中删除

上面的模型创立了两个 ModuleDictself.choicesself.activations,在前向流传时通过传入对应的 key 来执行对应的网络层。

class ModuleDict(nn.Module):
    def __init__(self):
        super(ModuleDict, self).__init__()
        self.choices = nn.ModuleDict({'conv': nn.Conv2d(10, 10, 3),
            'pool': nn.MaxPool2d(3)
        })

        self.activations = nn.ModuleDict({'relu': nn.ReLU(),
            'prelu': nn.PReLU()})

    def forward(self, x, choice, act):
        x = self.choices[choice](x)
        x = self.activations[act](x)
        return x


net = ModuleDict()

fake_img = torch.randn((4, 10, 32, 32))

output = net(fake_img, 'conv', 'relu')
# output = net(fake_img, 'conv', 'prelu')
print(output)

容器总结

  • nn.Sequetial:程序性,各网络层之间严格依照程序执行,罕用于 block 构建,在前向流传时的代码调用变得简洁
  • nn.ModuleList:迭代行,罕用于大量反复网络构建,通过 for 循环实现反复构建
  • nn.ModuleDict:索引性,罕用于可抉择的网络层

AlexNet 实现

AlexNet 特点如下:

  • 采纳 ReLU 替换饱和激活函数,加重梯度隐没
  • 采纳 LRN (Local Response Normalization) 对数据进行部分归一化,加重梯度隐没
  • 采纳 Dropout 进步网络的鲁棒性,减少泛化能力
  • 应用 Data Augmentation,包含 TenCrop 和一些色调批改

AlexNet 的网络结构能够分为两局部:features 和 classifier。

能够在计算机视觉库 torchvision.models 中找到 AlexNet 的代码,通过看可知应用了 nn.Sequential 来封装网络层。

class AlexNet(nn.Module):

    def __init__(self, num_classes=1000):
        super(AlexNet, self).__init__()
        self.features = nn.Sequential(nn.Conv2d(3, 64, kernel_size=11, stride=4, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(64, 192, kernel_size=5, padding=2),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Conv2d(192, 384, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(384, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.Conv2d(256, 256, kernel_size=3, padding=1),
            nn.ReLU(inplace=True),
            nn.MaxPool2d(kernel_size=3, stride=2),
        )
        self.avgpool = nn.AdaptiveAvgPool2d((6, 6))
        self.classifier = nn.Sequential(nn.Dropout(),
            nn.Linear(256 * 6 * 6, 4096),
            nn.ReLU(inplace=True),
            nn.Dropout(),
            nn.Linear(4096, 4096),
            nn.ReLU(inplace=True),
            nn.Linear(4096, num_classes),
        )

    def forward(self, x):
        x = self.features(x)
        x = self.avgpool(x)
        x = torch.flatten(x, 1)
        x = self.classifier(x)
        return x
退出移动版