第7篇 Fast AI深度学习课程——卷积神经网络的实现与改进

构造卷积神经网络

本节将基于CIFAR-10数据,阐述卷积神经网络的构建过程。之所以选择CIFAR-10数据,是因为其数据集很小,而且其中的图片也很小,方便开发阶段的快捷测试。

1. 数据预处理
classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
stats = (np.array([ 0.4914 ,  0.48216,  0.44653]), np.array([ 0.24703,  0.24349,  0.26159]))
def get_data(sz,bs):
     tfms = tfms_from_stats(stats, sz, aug_tfms=[RandomFlipXY()], pad=sz//8)
     return ImageClassifierData.from_paths(PATH, val_name='test', tfms=tfms, bs=bs)
bs=256

其中classesCIFAR-10数据的分类;stats是图像数据的三个通道的均值和标准差。之前我们生成数据时,往往需要构造符合某个预先训练好的模型的数据,调用的函数时tfms_from_model(),这个函数会使用预训练的模型里的参数,对数据做相应的归一化处理。而现在我们从零开始训练网络,需要实现这个处理步骤。具体而言,就是将stats参数传入tfms_from_stats()函数中。该函数的后面两个参数:aug_tfms指明了对数据所做的修饰,RandomFlipXY()为随机的水平翻转,pad为对数据的周边进行补零填充。

2. 实现全连接网络
class SimpleNet(nn.Module):
    def __init__(self, layers):
        super().__init__()
        self.layers = nn.ModuleList([
            nn.Linear(layers[i], layers[i + 1]) for i in range(len(layers) - 1)])

    def forward(self, x):
        x = x.view(x.size(0), -1)
        for l in self.layers:
            l_x = l(x)
            x = F.relu(l_x)
        return F.log_softmax(l_x, dim=-1)

SimpleNet对象中layers成员是一个nn.ModuleList实例,这是Pytorch的规则:定义一个网络层序列,需要将之放在nn.ModuleList对象中。在self.layers中,按照每层的输入输出尺寸生成nn.Linear实例。而在SimpleNet的前向传播函数forward()中,使用nn.Linear对象对输入向量x进行线性加权,然后使用ReLU做非线性操作,最后使用log_softmax()实现输出。

以上述定义模型的实例和数据构造分类器:

learn = ConvLearner.from_model_data(SimpleNet([32*32*3, 40,10]), data)

这一结构的神经网络大概需要12万个参数:
( 32 32 3 40 + 40 ) + ( 40 10 + 10 ) ,其中加数40和10是隐藏层的偏置项。
最终可以得到的分类准确率约为46%

3. 卷积网络

将全连接层替换为卷积层:

class ConvNet(nn.Module):
    def __init__(self, layers, c):
        super().__init__()
        self.layers = nn.ModuleList([
            nn.Conv2d(layers[i], layers[i + 1], kernel_size=3, stride=2)
            for i in range(len(layers) - 1)])
        self.pool = nn.AdaptiveMaxPool2d(1)
        self.out = nn.Linear(layers[-1], c)

    def forward(self, x):
        for l in self.layers: x = F.relu(l(x))
        x = self.pool(x)
        x = x.view(x.size(0), -1)
        return F.log_softmax(self.out(x), dim=-1)

其中,全连接网络中的nn.Linear层替换成了nn.Conv2d层;self.pool为输出层之前的池化层,nn.AdaptiveMaxPool2d(1)表示池化为1个数值;最终输出层会输出c个类别。

生成分类器:

learn = ConvLearner.from_model_data(ConvNet([3, 20, 40, 80], 10), data)

分类器参数个数约为3万个:
( 3 3 3 20 + 20 ) + ( 3 3 20 40 + 40 ) + ( 3 3 40 80 + 80 ) + ( 80 10 + 10 ) 。经训练后,可获得60%的准确率。

4. 块归一化(BNBatch Normalization

考虑输入数据。事实上,在其传递给输出层时,我们会做归一化,使数据变为均值为0、方差为1的分布。这是由于大部分机器学习算法都假设数据服从独立同分布,而且大部分机器学习算法都是基于标准正态分布来分析的。考虑隐含层的输入。由于层层操作,隐含层的输入已经偏离这一假设;更不利的是,隐含层的系数矩阵可能使得每层的输出越来越大或越来越小。

针对这一问题,Batch Normalization方法被提出。一个简单的版本是使每一层的输出的均值为零、方差为1。然而,这样做的效果不大。因为下一层在训练时,可能又使得其偏离所预想的数值范围。这其实是网络在反抗过分预设的条件。为了不让网络过分淘气,又不至于让网络信马由缰的瞎搞参数,我们额外增加两个参数ma,作为BN之后数据的标准差和均值。可以这样理解:如果网络想要调整参数矩阵造成的数据差异化(方差)和数据漂移(均值),就让它去调整ma,而不是去调整整个参数矩阵。从这个角度来看,这也是一种正则化(Regularization,即使得参数不过度复杂)。因此,采用BN方法的网络,可以省略随机丢弃(Drop Out)和权值衰减(weight decay)。

既然BN可以看做一种正则化方法,其和Drop Out一样,应设置为只在训练时有效,而在泛化时无效。在Pytorch中,可以通过nn.Module(注意:自定义的BN层是派生自这一对象的)对象的training参数判断是否处在训练过程中。自定义的BN层实现代码如下:

class BnLayer(nn.Module):
    def __init__(self, ni, nf, stride=2, kernel_size=3):
        super().__init__()
        self.conv = nn.Conv2d(ni, nf, kernel_size=kernel_size, 
                              stride=stride, bias=False, padding=1)
        self.a = nn.Parameter(torch.zeros(nf,1,1))
        self.m = nn.Parameter(torch.ones(nf,1,1))

    def forward(self, x):
        x = F.relu(self.conv(x))
        x_chan = x.transpose(0,1).contiguous().view(x.size(1), -1)
        if self.training:
            self.means = x_chan.mean(1)[:,None,None]
            self.stds  = x_chan.std (1)[:,None,None]
        return (x-self.means) / self.stds *self.m + self.a
5. 残差网络(ResNet

事实上,即使引入BN策略,当网络层数达到一定的数目后,网络的性能就会达到饱和,再增加网络深度反而会引起性能下降。而采用残差网络的架构,可以有效增加网络层数,且能获得较好的性能。

残差网络派生自BnLayer,只不过是将BnLayer中的前向传播函数的返回值更改为return x + super().forward(x),即我们将预测值 y 表示为 y = x + f ( x ) 。原先直接预测 y ,现在改为预测差值 f ( x ) = y x

6. Fast.AI对已有网络结构的调整

Dogs vs. Cats分类网络中,我们采用ResNet34的网络结构,调用Fast.AI的API生成了二分类网络。那么Fast.AI对已有的网络结构做了哪些调整呢?

事实上,ResNet34网络的最后一层,是利用512维特征进行1000类的划分,与Dogs vs. Cats的应用场景不符(只需两类),因此这一层将会被删除。另外,ResNet34的倒数第二层是池化层,而我们若要修改输出特征数目,也需删除这一层。

在删除上述两层后,Fast.AI增加了一个卷积层、池化层、输出层。其中卷积层将512维特征进一步压缩至2维。而在ResNet34的网络层被锁定的情形下,需要训练的就是这个卷积层。

一些有用的链接

猜你喜欢

转载自blog.csdn.net/suredied/article/details/81750362