NiN详解

入门小菜鸟,希望像做笔记记录自己学的东西,也希望能帮助到同样入门的人,更希望大佬们帮忙纠错啦~侵权立删。

✨完整代码在我的github上,有需要的朋友可以康康✨

https://github.com/tt-s-t/Deep-Learning.git

目录

一、NiN网络的背景

二、NiN网络结构

1、NiN块

2、NiN网络架构

三、NiN的亮点

1、使用1*1卷积代替全连接层

2、使用全局平均池化代替最后的全连接层

四、NiN网络的缺点

五、NiN代码实现对FashionMNIST数据的分类


一、NiN网络的背景

AlexNet和VGG都是先由卷积层构成的模块充分抽取空间特征,再由全连接层构成的模块来输出分类结果。但是其中的全连接层的参数量过于巨大,因此NiN提出用1*1卷积代替全连接层,串联多个由卷积层和“全连接”层构成的小网络来构建⼀个深层网络。


二、NiN网络结构

1、NiN块

NiN详解_第1张图片

这里的1*1卷积(即mlpconv结构)就是为了代替全连接层。

 可代替的原因

我们知道全连接层的原理公式是:Y=XW+B,其中X\in R^{n*d}, W\in R^{d*1}

我们现在假设有一个待卷积的结果Z,shape为(N,C,H,W),1*1卷积对应的shape为(1,1,C),这时的1*1卷积相当于W。

这里的Z相当于X,每一个样本(C,H,W)就相当于是n=H*W,d=C,即相当于排成了(H*W,C)后再和W相乘。每个通道下相同位置的像素都和卷积核W(d=C)相乘,即完成了XW,默认B=0。

这么替代的好处是:

(1)灵活放缩通道数:通过控制卷积核的数量达到通道数的放缩。

(2)增加非线性。1×1卷积核的卷积过程相当于全连接层的计算过程,并且还加入了非线性函数,从而可以增加网络的非线性。

(3)计算参数少(简化模型)

(4)不改变图像空间结构

全连接层会破坏图像的空间结构(要先展平),而1*1卷积层不会破坏图像的空间结构。

(5)输入可以是任意尺寸

全连接层的输入尺寸是固定的,因为全连接层的参数个数取决于图像大小。而卷积层的输入尺寸是任意的,因为卷积核的参数个数与图像大小无关。

2、NiN网络架构

由多个NiN块按需堆起来后进行全局池化,最后再得到每个归属类所得的分数。


三、NiN的亮点

1、使用1*1卷积代替全连接层

这么做的优点如上所述

2、使用全局平均池化代替最后的全连接层

这也是为了解决全连接层参数过多的问题。

对于分类问题,在之前通常的解决方法是:在最后一个卷积层的feature map和全连接层连接,最后通过softmax进行分类。但全连接层带来的问题就是参数空间过大,容易过拟合。早期AlexNet采用了Dropout来减轻过拟合,提高网络的泛化能力,但依旧无法解决参数过多的问题。

而全局平均池化的做法是将全连接层去掉,在最后一层,将卷积层数目设为与类别数目一致,然后全局pooling, 从而直接输出属于各个类的结果分数。

优势:

(1)全局平均池化更原生地支持于卷积结构,通过加强特征映射与相应分类的对应关系,特征映射可以更容易解释为分类映射。

(2)全局平均池化一层没有需要优化的参数,减少大量的训练参数有效避免过拟合,因此对输入的空间转换具有更强的鲁棒性


四、NiN网络的缺点

全局平均池化对特征图简单地进行加权取平均操作可能会丢失一些有用信息


五、NiN代码实现对FashionMNIST数据的分类

代码也可以查看https://github.com/tt-s-t/Deep-Learning.git中的NiN文件夹

这里展示模型的搭建

import torch.nn as nn

def nin_block(in_channels, out_channels, kernel_size, stride, padding):
    block = nn.Sequential(
        nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding),
        nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU(),
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU())
    return block

class NiN(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nin_block(1, 96, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=3, stride=2,padding=1),
            nn.Dropout(0.5),
            nin_block(96, 256, kernel_size=5, stride=1, padding=2),
            nn.MaxPool2d(kernel_size=3, stride=2,padding=1),
            nn.Dropout(0.5),
            # 标签类别数是10
            nin_block(256, 10, kernel_size=3, stride=1, padding=1),
            #全局平均代替最后的全连接层
            nn.AdaptiveAvgPool2d((1,1))
            )
    
    def forward(self,input):
        x = self.net(input)
        x = x.view(x.size(0), 10)
        #print(x.shape)
        return x

调用网络进行训练与测试

import torch
import torch.nn as nn
import torchvision
import torch.optim as optim
from torch.utils.data import DataLoader
import torchvision.transforms as transforms
from nin import NiN

data_train = torchvision.datasets.FashionMNIST(
        root="FashionMNIST", train=True,transform=transforms.Compose([
        transforms.Resize((32,32)),
        transforms.ToTensor()
    ]), download=True)
data_test = torchvision.datasets.FashionMNIST(
    root="FashionMNIST", train=False, transform=transforms.Compose([
        transforms.Resize((32,32)),
        transforms.ToTensor()
    ]), download=True)
data_train_loader = DataLoader(data_train, batch_size=32, shuffle=True, num_workers=4)#数据加载器加载训练数据
data_test_loader = DataLoader(data_test, batch_size=16, num_workers=4)#数据加载器加载测试数据

model = NiN()

device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu")
model.to(device)

# config
epochs = 12#迭代次数
lr = 0.0001#学习率

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=lr)

def train():
    print('start training')
    # 训练模型
    for epoch in range(epochs):
        model.train()#训练模式
        epoch_loss = 0
        epoch_accuracy = 0
        for _, (data, label) in enumerate(data_train_loader):
            data = data.to(device)
            label = label.to(device)
            output = model(data)#输出
            loss = criterion(output, label)#计算loss

            optimizer.zero_grad()#清空过往梯度(因为每次循环都是一次完整的训练)
            loss.backward()#反向传播
            optimizer.step()#更新参数

            acc = (output.argmax(dim=1) == label).float().mean()
            epoch_accuracy += acc / len(data_train_loader)#当前训练平均准确率
            epoch_loss += loss / len(data_train_loader)#累计loss

        print(f'EPOCH:{epoch:2}, train loss:{epoch_loss:.4f}, train acc:{epoch_accuracy:.4f}')

def test():
    best_accuracy = 0
    model.eval() #加与不加都行
    total_correct = 0 #记录正确数目
    avg_loss = 0.0 #记录平均错误
    for _, (images, labels) in enumerate(data_test_loader):
        images = images.to(device)
        labels = labels.to(device)
        output = model(images)
        avg_loss += criterion(output, labels).sum() #将损失累加起来
        pred = output.detach().max(1)[1] #max(1)得到每行最大值的第一个(得到概率最大的那个),.detach()指这个tensor永远不需要计算其梯度
        total_correct += pred.eq(labels.view_as(pred)).sum() #累加与pred同类型的labels(即为正确)的数值,即记录正确分数(如果预测对了对应的位置就是1)

    avg_loss /= len(data_test) #平均误差
    if(float(total_correct) / len(data_test) > best_accuracy):
        torch.save(model.cpu().state_dict(), 'model.pth')
    best_accuracy = max(best_accuracy,float(total_correct) / len(data_test))
    print('bestaccuracy is %f' % best_accuracy)
    print('Test Avg. Loss: %f, Accuracy: %f' % (avg_loss.detach().cpu().item(), float(total_correct) / len(data_test))) #输出信息

def main(): #开始训练和测试
    train()
    test()

if __name__ == '__main__':
    main()

欢迎大家在评论区批评指正,谢谢大家~

你可能感兴趣的:(深度学习,深度学习,人工智能,神经网络,NiN)