NiN-串联多个由卷积层和“全连接”层构成的小网络来构建一个深层网络

经典的CNN网络:

由多个堆叠的卷积层和空间池化层组成,通过线性的卷积核+非线性激活函数(ReLU、sigmoid、tanh等)生成特征图

NIN网络:

NIN的结构和传统的神经网络中多层的结构有些类似,后者的多层是跨越了不同尺寸的感受野(通过层与层中间加pool层),从而在更高尺度上提取出特征;NIN结构是在同一个尺度上的多层(中间没有pool层),从而在相同的感受野范围能提取更强的非线性。

NiN块:

  • 是NiN中的基础块。
  • 由一个卷积层加两个充当全连接层的卷积层(1x1卷积层)串联而成,
  • 第一个卷积层的超参数可以自行设置,而第二和第三个卷积层的超参数一般是固定的
  • 1x1卷积的作用(池化层压缩宽高,对通道数不起作用)
    • 增加非线性函数
    • 更改通道数
  • 为什么要用到1x1卷积?

  • 保持特征图的尺寸
  • 把各通道的输入特征图进行线性加权,起到综合各个通道特征图信息的作用

多几层这样的1X1卷积的话,最终提取的特征会更加抽象,如果不用1X1卷积而是用更大的卷积核,那和连续用GLM有什么区别?就是因为用了1X1卷积,才增强了常规卷积的抽象表达能力

        MLPconv其实就是在常规卷积后面加了N层1X1卷积,

        1x1卷积作为NIN函数逼近器基本单元,除了增强了网络局部模块的抽象表达能力外,

        在现在看来, 还可以实现跨通道特征融合和通道升维降维。

创新点:

MLPconv:

        先前CNN中简单的线性卷积层被替换为了多层感知机(MLP多层全连接层和非线性函数的组合)

这样做优点:

  • 提供了网络层间映射的一种新可能;
  • 增加了网络卷积层的非线性能力。
全局平均池化:

        去除了容易造成过拟合的全连接输出层,而是将其替换成输出通道数等于标签类别数的NiN块和全局平均池化层

这样做优点:

  • 对数据在整个feature上作正则化,防止了过拟合
  • 用再关注输入图像的尺寸,因为不管是怎样的输入都是一样的平均方法,传统的全连接层要根据尺寸来选择参数数目,不具有通用性
  • 不再需要全连接层,减少了整个结构参数的数目(一般全连接层是整个结构中参数最多的层),过拟合的可能性降低

全局平均池化层:

对每一个feature上的所有点做平均,有n个feature就输出n个平均值作为最后的softmax的输入

实质:窗口形状等于输入空间维形状的平均池化层。

  • NiN的这个设计的好处是可以显著减小模型参数尺寸,从而缓解过拟合。
  • 但该设计有时会造成获得有效模型的训练时间的增加

代码实现

import torch
from torch import nn, optim
import torchvision
import sys
from time import time
import torch.nn.functional as F

定义模型

def nin_block(in_channels, out_channels, kernel_size, stride, padding):
    blk = nn.Sequential(
        #普通卷积层
        nn.Conv2d(in_channels, out_channels, kernel_size, stride,padding),
        nn.ReLU(),
        #2个1x1卷积层,相当于2个全连接层,将特征先抽象出来
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU()
    )
    return blk

'''全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现'''
class GlobalAvgPool2d(nn.Module):
    def __init__(self):
        super(GlobalAvgPool2d, self).__init__()
    def forward(self, x):
        return F.avg_pool2d(x, kernel_size=x.size()[2:])
        # x.size()[2:]表示从第2维开始到最后一维的尺寸大小,返回输入张量的高度和宽度的尺寸信息
        # F.avg_pool2d函数是PyTorch中的平均池化操作

'''对 x 的形状转换 '''
class FlattenLayer(nn.Module):
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x):
        return x.view(x.shape[0], -1)

搭建NIN网络

class NinNet(nn.Module):
    def __init__(self):
        super(NinNet, self).__init__()
        self.mlpconv = nn.Sequential(
            nin_block(1, 96, kernel_size=11, stride=4, padding=0),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nin_block(96, 256, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nin_block(256, 384, kernel_size=3, stride=1, padding=1),
            nn.MaxPool2d(kernel_size=3, stride=2),
            nn.Dropout(0.5),
            nin_block(384, 10, kernel_size=3, stride=1, padding=1), ## 标签类别数是10
            GlobalAvgPool2d(),
            FlattenLayer() ## 将四维的输出转成二维的输出,其形状为(批量大小, 
        )
    def forward(self, img):
        output = self.mlpconv(img)
        return output

训练模型

读取数据集
'''读取数据集'''
def load_data_fashion_mnist(batch_size, resize=None):
    if sys.platform.startswith('win'):
        num_workers = 0  # 0表示不用额外的进程来加速读取数据
    else:
        num_workers = 4
        
    '''定义数据预处理的转换函数列表'''    
    trans = []
    if resize: #判断是否需要进行图像尺寸调整(resize)
        trans.append(torchvision.transforms.Resize(size=resize)) 
        #将torchvision.transforms.Resize转换函数添加到转换函数列表trans中,并指定目标尺寸为resize
    trans.append(torchvision.transforms.ToTensor())
    # 将torchvision.transforms.ToTensor转换函数添加到转换函数列表trans中。这个函数用于将图像数据转换为张量,并且按照通道顺序排列(RGB)
    transform = torchvision.transforms.Compose(trans) 
    #通过torchvision.transforms.Compose函数将转换函数列表trans组合成一个转换操作
    
    mnist_train = torchvision.datasets.FashionMNIST(root='data/FashionMNIST',
                                                    train=True,
                                                    download=True,
                                                    transform=transform)
    mnist_test = torchvision.datasets.FashionMNIST(root='data/FashionMNIST',
                                                   train=False,
                                                   download=True,
                                                   transform=transform)
    train_iter = torch.utils.data.DataLoader(mnist_train,
                                             batch_size=batch_size,
                                             shuffle=True,
                                             num_workers=num_workers)
    test_iter = torch.utils.data.DataLoader(mnist_train,
                                             batch_size=batch_size,
                                             shuffle=False,
                                             num_workers=num_workers) 
    return train_iter, test_iter
设置网络模型
'''设置GPU运行'''
device = torch.device('cuda:1' if torch.cuda.is_available() else 'cpu')
'''选择网络'''
net = NinNet()
print(net)

 定义训练函数
'''用于GPU的准确率评估函数'''
def evaluate_accuracy(data_iter, net, device=None): 
    if device is None and isinstance(net, torch.nn.Module):
        device = list(net.parameters())[0].device #如果没指定device就使用net的device
    acc_sum, n = 0.0, 0
    for X, y in data_iter:
        if isinstance(net, torch.nn.Module):
            net.eval() # 评估模式, 这会关闭dropout
            acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
            net.train() # 改回训练模式
        else:
            if ('is_training' in net.__code__.co_varnames):
                acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
            else:
                acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
        n += y.shape[0]
    return acc_sum / n
'''确保计算使用的数据和模型同在内存或显存上'''
def train_ch5(net, train_iter, test_iter,  batch_size, optimizer , device, num_epochs):
    # 将模型加载到指定运算器中
    net = net.to(device)
    print("training on ", device)
    loss = torch.nn.CrossEntropyLoss()
    for epoch in range(num_epochs):
    # 在每一个迭代周期中,会使用训练集中所有样本一次(假设样本数能够被批量大小整除)
        train_l_sum, train_acc_sum, n ,batch_count= 0.0, 0.0, 0,0
        # 分别表示训练损失总和、训练准确度总和、样本数
        start = time()
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            '''前向传播'''
            y_hat = net(X) 
            #由于变量 l 并不是一个标量,所以我们可以调用 .sum() 将其求和得到一个标量
            '''计算损失'''
            l = loss(y_hat, y).sum()
            '''梯度清零'''
            if optimizer is not None:
                optimizer.zero_grad()
            elif params is not None and params[0].grad is not None:
                for param in params:
                    param.grad.data.zero_()
            '''反向传播'''
            l.backward() # 运行 l.backward() 得到该变量有关模型参数的梯度
            
            if optimizer is None:
                d2l.sgd(params, lr, batch_size)
            else: 
                '''更新参数'''
                optimizer.step()
            '''计算精度'''  
            train_l_sum += l.cpu().item()  
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f,\
        time %.1f sec' %(epoch+1, train_l_sum/batch_count, 
                         train_acc_sum/n, test_acc,
                         time()-start))

训练
'''训练模型'''
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=224)
lr, num_epochs = 0.002, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer,device, num_epochs)

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值