2014年新加坡国立大学(颜水成)提出的
在GoogLeNet之前,该设计后来为GoogLeNet(Inception)和 ResNet 等网络模型所借鉴
NiN——Network In Network 网络中的网络
前面的的LeNet、 AlexNet和VGG在设计上的共同之处是:先以由卷积层构成的模块充分抽取空间特征,再以由全连接层构成的模块来输出分类结果。其中, AlexNet和VGG对LeNet的改进主要在于如何对这两个模块加宽(增加通道数)和加深。
而 NiN 提出了另外⼀一个思路,即串联多个由卷积层和“全连接”层构成的小网络来构建一个深层网络。采用了少量的参数就松松击败了Alexnet网络,Alexnet网络参数大小是230M,采用这篇paper的算法才29M,减小了将近10倍。NiN提出的网络结构,是对传统CNN网络的一种改进
一个mlpconv 就是一个NiN block
我们知道,卷积层的输入和输出通常是四维数组(b,c,w,h), 而全连接层的输入和输出则通常是二维数组(样本,特征)。如果想在全连接层后再接上卷积层,则需要将全连接层的输出变换为四维。1*1的卷积层。它可以看成全连接层,其中空间维度(高和宽)上的每个元素相当于样本,通道相当于特征。因此, NiN使用1*1卷积层来替代全连接层,从而使空间信息能够自然传递到后面的层中去.
这里的用1*1卷积代替全连接层只是在一个点的不同channel之间的全连接。并不是把整个feature打平了的那种全连接,那种看这里 https://blog.csdn.net/hxxjxw/article/details/123450949
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
注意一个NiN block是1个卷积后加两个1*1的卷积,但是每个1*1卷积后面都跟着一个ReLu, 所以可以引入更多的非线性。
全局平均池化
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中数据量足够大的话可以不做交叉验证的