Pytorch之经典神经网络CNN(六) —— NiN (Fashion-MNIST)

2014年新加坡国立大学提出的

在GoogLeNet之前,该设计后来为GoogLeNet(Inception)和 ResNet 等网络模型所借鉴

NiN——Network In Network 网络中的网络

前面的的LeNet、 AlexNet和VGG在设计上的共同之处是:先以由卷积层构成的模块充分抽取空间特征,再以由全连接层构成的模块来输出分类结果。其中, AlexNet和VGG对LeNet的改进主要在于如何对这两个模块加宽(增加通道数)和加深。

而 NiN 提出了了另外⼀一个思路,即串联多个由卷积层和“全连接”层构成的小网络来构建一个深层网络。采用了少量的参数就松松击败了Alexnet网络,Alexnet网络参数大小是230M,采用这篇paper的算法才29M,减小了将近10倍。NiN提出的网络结构,是对传统CNN网络的一种改进


我们知道,卷积层的输入和输出通常是四维数组(b,c,w,h), 而全连接层的输入和输出则通常是二维数组(样本,特征)。

如果想在全连接层后再接上卷积层,则需要将全连接层的输出变换为四维。1*1的卷积层。它可以看成全连接层,其中空间维度(高和宽)上的每个元素相当于样本,通道相当于特征。因此, NiN使用1*1卷积层来替代全连接层,从而使空间信息能够自然传递到后面的层中去.

NIN网络也是首次提出1×1卷积核的paper,后续对1*1卷积核的应用都是自它而始

NiN块是NiN中的基础块。它由一个卷积层加两个充当全连接层的卷积层串联⽽而成。其中第一个卷积层的超参数可以自行设置,⽽而第二和第三个卷积层的超参数一般是固定的

NiN的贡献总结主要就2点:

  • mlpconv的提出(我们用1x1卷积实现),整合多个feature map上的特征.进一步增强非线性.
  • 全局平均池化替代全连接层


MLP卷积层(mlpconv层)

NiN paper最大的创新点就是提出了mlpconv层,即用MLP代替GLM广义线性模型

一般卷积操作可以看成特征的提取操作,而一般卷积一层只相当于一个线性操作,所以其只能提取出线性特征。所以作者就想能否在卷积层后也加入一个MLP使得每层卷积操作能够提取非线性特征。而为了减少参数量,又用1*1的卷积层模拟了MLP

 

全局平均池化

Global Average Pooling主要为了解决全连接层参数过多的问题,早期对于分类问题,最后一个卷积层的 Feature Map 通常与全连接层连接,最后通过 softmax 逻辑回归分类。全连接层带来的问题就是参数空间过大,容易过拟合。早期 Alex 采用了Dropout 的方法,来减轻过拟合,提高网络的泛化能力,但依旧无法解决参数过多问题。

global average pooling的概念非常简单,分类任务有多少个类别,就控制最终产生多少个feature map

我们之前学的卷积神经网络后跟的普通池化都是局部池化。比方说一个10x10的输入,用2x2的窗口去做池化,然后这个窗口不断地滑动,从而对不同的2x2区域可以做求平均(平均池化),取最大值(最大值池化)等.这个就可以理解为'局部'的池化,2x2是10x10的一部分嘛.相应地,所谓全局池化,自然就是用一个和输入大小一样的窗口做池化,即对全部的输入做池化操作.

例如,本例中经过4个NiN block后的x size是[128, 10, 5, 5],那么选取的avg pooling的kernel_size就是5

 

1×1卷积核的作用

       那么1×1卷积核有什么作用呢,如果当前层和下一层都只有一个通道那么1×1卷积核确实没什么作用,但是如果它们分别为m层和n层的话,1×1卷积核可以起到一个跨通道聚合的作用所以进一步可以起到降维(或者升维)的作用,起到减少参数的目的。 
      比如当前层为 x×x×m即图像大小为x×x,特征层数为m,然后如果将其通过1×1的卷积核,特征层数为n,那么只要n<m。这样就能起到降维的目的,减少之后步骤的运算量。如果使用1x1的卷积核,这个操作实现的就是多个feature map的线性组合,可以实现feature map在通道个数上的变化。 而因为卷积操作本身就可以做到各个通道的重新聚合的作用,所以1×1的卷积核也能达到这个效果。

        卷积核的每一个layer都是要在input的所有通道卷积求和,然后作为结果的一层layer

 

Fashion-MNIST数据集

       Fashion-MNIST 是一个替代原始的MNIST手写数字数据集的另一个图像数据集。 号称计算机视觉领域的Hello, World。它是由Zalando(一家德国的时尚科技公司)旗下的研究部门提供。其涵盖了来自10种类别的共7万个不同商品的正面图片(口红、包包、鞋子、裤子、衬衫、T恤、衬衣、靴子)。

       Fashion-MNIST的大小、格式和训练集/测试集划分与原始的MNIST完全一致。60000/10000的训练测试数据划分,28x28的单通道灰度图片。你可以直接用它来测试你的机器学习和深度学习算法性能,且不需要改动任何的代码

NiN实现代码

新增验证集的使用

import torch
from torch import nn, optim
import torchvision
from datetime import datetime

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

#NiN块
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(),
        #1*1卷积层
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU(),
        #1*1卷积层
        nn.Conv2d(out_channels, out_channels, kernel_size=1),
        nn.ReLU()
    )
    return blk

net = nn.Sequential(
    #输入x是[128, 1, 224, 224]
    #第一个卷积块
    nin_block(1, 96, kernel_size=11, stride=4, padding=0),
    #x是[128, 96, 54, 54]
    nn.MaxPool2d(kernel_size=3, stride=2),
    #x是[128, 96, 26, 26]

    #第二个卷积块
    nin_block(96, 256, kernel_size=5, stride=1, padding=2),
    #x是[128, 256, 26, 26]
    nn.MaxPool2d(kernel_size=3, stride=2),
    #x是[128, 256, 12, 12]

    #第三个卷积块
    nin_block(256, 384, kernel_size=3, stride=1, padding=1),
    #x是[128,384,12,12]
    nn.MaxPool2d(kernel_size=3, stride=2),
    nn.Dropout(0.5),
    #x是[128, 384, 5, 5]

    #第四个卷积块
    # 标签类别数是10
    nin_block(384, 10, kernel_size=3, stride=1, padding=1),
    #x是[128, 10, 5, 5]

    #全局平均池化层
    #全局平均池化层可通过将窗口形状设置成输入的高和宽实现
    nn.AvgPool2d(kernel_size=5),
    #x是[128, 10, 1, 1]
    # 将四维的输出转成二维的输出,其形状为(批量大小, 10)
    nn.Flatten(start_dim=1, end_dim=3)
    #x是[128, 10]
)



def get_acc(output, label):
    total = output.shape[0]
    #output是概率,每行概率最高的就是预测值
    _, pred_label = output.max(1)
    num_correct = (pred_label == label).sum().item()
    return num_correct / total


batch_size = 128


transform = torchvision.transforms.Compose([
    torchvision.transforms.Resize(size=224),
    torchvision.transforms.ToTensor()
])

train_set = torchvision.datasets.FashionMNIST(
    root='dataset/',
    train=True,
    download=True,
    transform=transform
)

#hand-out留出法划分
train_set, val_set = torch.utils.data.random_split(train_set, [50000,10000])

test_set = torchvision.datasets.FashionMNIST(
    root='dataset/',
    train=False,
    download=True,
    transform=transform
)


train_loader = torch.utils.data.DataLoader(
    dataset=train_set,
    batch_size=batch_size,
    shuffle=True
)
val_loader = torch.utils.data.DataLoader(
    dataset=val_set,
    batch_size=batch_size,
    shuffle=True
)
test_loader = torch.utils.data.DataLoader(
    dataset=test_set,
    batch_size=batch_size,
    shuffle=False
)

lr = 2e-3
optimizer = optim.Adam(net.parameters(), lr=lr)
critetion = nn.CrossEntropyLoss()
net = net.to(device)
prev_time = datetime.now()
valid_data = val_loader

for epoch in range(3):
    train_loss = 0
    train_acc = 0
    net.train()

    for inputs,labels in train_loader:
        inputs = inputs.to(device)
        labels = labels.to(device)

        #forward
        outputs = net(inputs)
        loss = critetion(outputs,labels)
        #backward
        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        train_loss += loss.item()
        train_acc += get_acc(outputs, labels)
        #最后还要求平均的

    #显示时间
    cur_time = datetime.now()
    h,remainder = divmod((cur_time - prev_time).seconds, 3600)
    m,s = divmod(remainder,60)
    # time_str = 'Time %02d:%02d:%02d'%(h,m,s)
    time_str = 'Time %02d:%02d:%02d(from %02d/%02d/%02d %02d:%02d:%02d to %02d/%02d/%02d %02d:%02d:%02d)' % (h, m, s,prev_time.year,prev_time.month,prev_time.day,prev_time.hour,prev_time.minute,prev_time.second,cur_time.year,cur_time.month,cur_time.day,cur_time.hour,cur_time.minute,cur_time.second)

    #validation
    with torch.no_grad():
        net.eval()
        valid_loss = 0
        valid_acc = 0
        for inputs,labels in valid_data:
            inputs = inputs.to(device)
            labels = labels.to(device)

            outputs = net(inputs)
            loss = critetion(outputs,labels)
            valid_loss += loss.item()
            valid_acc += get_acc(outputs,labels)

    print("Epoch %d. Train Loss: %f, Train Acc: %f, Valid Loss: %f, Valid Acc: %f,"
          %(epoch, train_loss/len(train_loader), train_acc / len(train_loader), valid_loss / len(valid_data),
            valid_acc / len(valid_data))
          + time_str)

    torch.save(net.state_dict(),'params.pkl')


#测试
with torch.no_grad():
    net.eval()
    correct = 0
    total = 0
    for (images, labels) in test_loader:
        images, labels = images.to(device), labels.to(device)
        output = net(images)
        _, predicted = torch.max(output.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

    print("The accuracy of total {} images: {}%".format(total, 100 * correct / total))

在神经网络中一般少见到k-fold validation,因为在机器学习中用k-fold validation是为了充分利用数据集,而在比如CNN中数据量足够大的话可以不做交叉验证的

猜你喜欢

转载自blog.csdn.net/hxxjxw/article/details/113780954