GoogLeNet是一个到目前为止仍然被广泛使用的网络。
GoogLeNet被认为是第一个卷积层超过一百层的网络。
GoogLeNet,其实就是Google的Net,本身就是Google的一批人做的,这里是玩了一个梗,将L大写,算是对LeNet的致敬。
Inception块:
GoogLeNet最重要的概念。
Inception块:我什么都用,不同通道可能用不同的玩意。
Inception不改变高宽,只改变通道数。
原理:将输入数据分成多个通道,每个通道做定制的信息提取,比如1通道用卷积,2通道用另一种卷积或者卷积组合,3通道加个最大池化,4通道加个平均池化等 等,最后将各个通道的输出大小整理成一致的,合并后输出,这个合并操作叫做Concatenation。
这种构造的直观理解:你各种各样的卷积池化都有了,所以就不必考虑用谁好谁坏了,全都交给显卡去决定吧!
构造:
第一个Inception块:可以看到,输入通道数是182,传入四条路上的时候基本都降低了通道数,只是降低的尺度不同,这也是根据每条路上的不同的结构来控制的,比如5x5卷积相对来说更宽更复杂,那么通道数就可以少一些,通过这种方式能够控制模型的复杂度。
图示中,1x1中的卷积层基本都是为了控制维度,而蓝色块更多的是用来抽取信息。
在分配通道的时候,可以赋予“权重”。比如,这个网络将192通道变成了256通道,而在四条路中,3x3的卷积层效果一般会很好而且计算量不是很大,那么就可以重视这条路,所以他的输出就是128(等于另外三个通道的和)。
Inception和单层的3x3与5x5的卷积相比:参数个数减少了,计算复杂度减少了。(1x1的卷积层立大功)
GoogLeNet架构:
具体这个结构如何设计出来的,天知道,炼丹炉调参调出来的,有钱人才可能复现的模型。
因此,在这里咱就不具体研究一段中每一个网络了,因为本来就没有可解释性。
Inception的各种版本:
V1:上面介绍的,已经不太用了;
V2:使用了batch normalization
V3:替换5x5为多个3x3卷积层;
替换5x5为1x7和7x1卷积层;
替换3x3为1x3和3x1卷积层;
更深。
V4:使用残差网络。
主要:V3:速度较慢,占内存较大,但是精确度很好的一个模型。
V3v4仍然是现在常用的模型。
但是其内部太复杂,这是他不太受欢迎的最主要原因。
结果
可以看到,准确率大大提高,而且仅仅训练了28min,可以说非常快了。在很快的速度上训练出了一个更好的模型,这就是GoogLeNet迷人的地方,也正因为如此,这个网络到目前仍然被广泛使用。当然,相对GoogLeNet V3来说,最原始的这个已经效果不咋地了,现在更广泛使用的是GoogLeNet V3。
Q&A
1、d2l这个名字什么意思?哈哈哈哈我疑惑很久了,原来是沐神随便选的,原话是dive into deep learning,into其实就是to,可以叫d2dl,但是名字有点长,沐神就选了个双关,d2l的2除了有to的意思,还有两个d的意思。
2、通道数用2的n次方,在GPU上算起来比较快,而且人算也比较快。
3、一般我们处理数据都是用经典网络处理,除非数据很特殊,或者在神经网络、深度学习领域成为了集大成者,否则不要轻易改动经典网络,因为,这毕竟是人家炼丹不知道多少个日夜跑废了多少块显卡跑出来的成果。
4、可以认为通道数越多,我们学到的模式就会越来越多,但是不能多到硬件学不动,一般1024维度,再高一些2048,再高一般都没有了。维度过高,当然会出现很严重的过拟合问题。
5、模型有的时候不见得是最重要的,各种调参的trick有时更加重要。
代码
import torch from torch import nn from torch.nn import functional as F from d2l import torch as d2l class Inception(nn.Module): # c1--c4是每条路径的输出通道数 def __init__(self, in_channels, c1, c2, c3, c4, **kwargs): super(Inception, self).__init__(**kwargs) # 线路1,单1x1卷积层 self.p1_1 = nn.Conv2d(in_channels, c1, kernel_size=1) # 线路2,1x1卷积层后接3x3卷积层 self.p2_1 = nn.Conv2d(in_channels, c2[0], kernel_size=1) self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1) # 线路3,1x1卷积层后接5x5卷积层 self.p3_1 = nn.Conv2d(in_channels, c3[0], kernel_size=1) self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2) # 线路4,3x3最大汇聚层后接1x1卷积层 self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1) self.p4_2 = nn.Conv2d(in_channels, c4, kernel_size=1) def forward(self, x): p1 = F.relu(self.p1_1(x)) p2 = F.relu(self.p2_2(F.relu(self.p2_1(x)))) p3 = F.relu(self.p3_2(F.relu(self.p3_1(x)))) p4 = F.relu(self.p4_2(self.p4_1(x))) # 在通道维度上连结输出 return torch.cat((p1, p2, p3, p4), dim=1) # 四个维度,0 1 2 3 批量数、通道数、宽高,在这里就是在通道数这个维度上进行concat b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1), nn.ReLU(), nn.Conv2d(64, 192, kernel_size=3, padding=1), nn.ReLU(), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), Inception(256, 128, (128, 192), (32, 96), 64), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b4 = nn.Sequential(Inception(480, 192, (96, 208), (16, 48), 64), Inception(512, 160, (112, 224), (24, 64), 64), Inception(512, 128, (128, 256), (24, 64), 64), Inception(512, 112, (144, 288), (32, 64), 64), Inception(528, 256, (160, 320), (32, 128), 128), nn.MaxPool2d(kernel_size=3, stride=2, padding=1)) b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128), Inception(832, 384, (192, 384), (48, 128), 128), nn.AdaptiveAvgPool2d((1,1)), nn.Flatten()) net = nn.Sequential(b1, b2, b3, b4, b5, nn.Linear(1024, 10)) X = torch.rand(size=(1, 1, 96, 96)) for layer in net: X = layer(X) print(layer.__class__.__name__,'output shape:\t', X.shape) lr, num_epochs, batch_size = 0.1, 10, 128 train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size, resize=96) d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())