GoogLeNet

GoogLeNet和vgg分别是2014的ImageNet挑战赛的冠亚军.GoogLeNet则做了更加大胆的网络结构尝试,虽然深度只有22层,但大小却比AlexNet和VGG小很多,GoogleNet参数为500万个,AlexNet参数个数是GoogleNet的12倍,VGGNet参数又是AlexNet的3倍,因此在内存或计算资源有限时,GoogleNet是比较好的选择;从模型结果来看,GoogLeNet的性能却更加优越。

之前转过一篇文章,详细描述了GoogLeNet的演化,有兴趣的可以去看看:https://www.cnblogs.com/sdu20112013/p/11308388.html

基本结构Inception

GoogleNet的基础结构叫Inception.如下所示:



这个结构的好处主要是:

  • 增加了网络宽度(增加了每一层的神卷积核的数量),提高了模型学习的能力.
  • 使用了不同大小的卷积核,增加了对不同模式的特征的提取能力.也增强了模型对不同尺度的适应性.

    Inception中3x3和5x5之前的1x1主要用于降低channel维度数量,减少计算量.

这个结构中的每一个通路的卷积核的数量是超参数,可调的.

那么,我们定义inception结构

class Inception(nn.Module):
def __init__(self,in_c,c1,c2,c3,c4):
super(Inception, self).__init__()
self.branch1 = nn.Conv2d(in_c,c1,kernel_size=1) self.branch2_1 = nn.Conv2d(in_c,c2[0],kernel_size=1)
self.branch2_2 = nn.Conv2d(c2[0],c2[1],kernel_size=3,padding=1) self.branch3_1 = nn.Conv2d(in_c,c3[0],kernel_size=1)
self.branch3_2 = nn.Conv2d(c3[0],c3[1],kernel_size=5,padding=2) self.branch4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
self.branch4_2 = nn.Conv2d(in_c,c4,kernel_size=1) def forward(self,x):
o1 = self.branch1(x)
o1 = F.relu(o1)
print("o1:",o1.shape) o2 = self.branch2_1(x)
o2 = F.relu(o2)
o2 = self.branch2_2(o2)
o2 = F.relu(o2)
print("o2:",o2.shape) o3 = self.branch3_1(x)
o3 = F.relu(o3)
o3 = self.branch3_2(o3)
o3 = F.relu(o3)
print("o3:",o3.shape) o4 = self.branch4_1(x)
o4 = self.branch4_2(o4)
o4 = F.relu(o4)
print("o4:",o4.shape) concat = torch.cat((o1,o2,o3,o4),dim=1)
print("concat:",concat.shape) return concat

如前所示,inception分为4个分支.每个分支的卷积核的数量是可调的参数.

GoogLeNet完整结构

我们根据论文里的结构来实现GoogleNet.



上图里的红圈处代表的即3x3或5x5卷积之前的用于降维的1x1卷积.

第一层是普通卷积,64组卷积核,卷积核大小7x7,stride=2.池化层窗口大小为3x3,stride=2.

第二层是先做1x1卷积,再做3x3卷积.

可写出以下代码:

X = torch.randn((1,1,224,224))
conv1 = nn.Conv2d(1,64,kernel_size=7,stride=2,padding=3)
max_pool1 = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
o=conv1(X)
print(o.shape) #[1,64,112,112]
o=max_pool1(o)
print(o.shape) #[1,64,56,56] conv2_1 = nn.Conv2d(64,64,kernel_size=1)
conv2_2 = nn.Conv2d(64,192,kernel_size=3,stride=1,padding=1)
max_pool2 = nn.MaxPool2d(kernel_size=3,stride=2,padding=1)
o=conv2_1(o)
print(o.shape) #[1,64,56,56]
o=conv2_2(o)
print(o.shape) #[1,192,56,56]
o=max_pool2(o)
print(o.shape) #[1,192,28,28]

接下来是第一个inception结构.

inception_3a = Inception(192,64,(96,128),(16,32),32)
o=inception_3a(o)
print(o.shape)

输出

o1: torch.Size([1, 64, 28, 28])
o2: torch.Size([1, 128, 28, 28])
o3: torch.Size([1, 32, 28, 28])
o4: torch.Size([1, 32, 28, 28])
concat: torch.Size([1, 256, 28, 28])
torch.Size([1, 256, 28, 28])

依次类推,最终我们可以给出模型定义:

class GoogLeNet(nn.Module):
def __init__(self):
super(GoogLeNet,self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1,64,kernel_size=7,stride=2,padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1),
) self.conv2 = 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),
) self.inception_3a = Inception(192,64,(96,128),(16,32),32)
self.inception_3b = Inception(256,128,(128,192),(32,96),64)
self.max_pool3 = nn.MaxPool2d(kernel_size=3,stride=2,padding=1) self.inception_4a = Inception(480,192,(96,208),(16,48),64)
self.inception_4b = Inception(512,160,(112,224),(24,64),64)
self.inception_4c = Inception(512,128,(128,256),(24,64),64)
self.inception_4d = Inception(512,112,(144,288),(32,64),64)
self.inception_4e = Inception(528,256,(160,320),(32,128),128)
self.max_pool4 = nn.MaxPool2d(kernel_size=3,stride=2,padding=1) self.inception_5a = Inception(832,256,(160,320),(32,128),128)
self.inception_5b = Inception(832,384,(192,384),(48,128),128) self.avg_pool = nn.AvgPool2d(kernel_size=7)
self.dropout = nn.Dropout(p=0.4) self.fc = nn.Linear(1024,10,bias=True) def forward(self,x):
feature = self.conv1(x)
feature = self.conv2(feature) feature = self.inception_3a(feature)
feature = self.inception_3b(feature)
feature = self.max_pool3(feature) feature = self.inception_4a(feature)
feature = self.inception_4b(feature)
feature = self.inception_4c(feature)
feature = self.inception_4d(feature)
feature = self.inception_4e(feature)
feature = self.max_pool4(feature) feature = self.inception_5a(feature)
feature = self.inception_5b(feature) feature = self.avg_pool(feature)
feature = self.dropout(feature) out = self.fc(feature.view(x.shape[0],-1)) return out

测试一下输出

X=torch.randn((1,1,224,224))
net = GoogLeNet()
# for name,module in net.named_children():
# X=module(X)
# print(name,X.shape) out = net(X)
print(out.shape)

输出

torch.Size([1, 10])

上面的代码只是看起来复杂,其实对着前面图里描述的GoogleNet结构实现起来并不难.比如先写出

class GoogLeNet(nn.Module):
def __init__(self):
super(GoogLeNet,self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1,64,kernel_size=7,stride=2,padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1),
)

然后用

X=torch.randn((1,1,224,224))
net = GoogLeNet()
for name,module in net.named_children():
X=module(X)
print(name,X.shape)

测试一下输出,如果不对,就调整代码,看看是kernel_size,padding还是哪里写错了.如果正确就继续扩展代码为

class GoogLeNet(nn.Module):
def __init__(self):
super(GoogLeNet,self).__init__()
self.conv1 = nn.Sequential(
nn.Conv2d(1,64,kernel_size=7,stride=2,padding=3),
nn.ReLU(),
nn.MaxPool2d(kernel_size=3,stride=2,padding=1),
) self.conv2 = 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),
)

再次测试输出的shape,如此,一层层layer添加下去,最终就可以完成整个模型的定义.

加载数据

batch_size,num_workers=16,4
train_iter,test_iter = learntorch_utils.load_data(batch_size,num_workers,resize=224)

定义模型

net = GoogLeNet().cuda()
print(net)

定义损失函数

loss = nn.CrossEntropyLoss()

定义优化器

opt = torch.optim.Adam(net.parameters(),lr=0.001)

定义评估函数

def test():
start = time.time()
acc_sum = 0
batch = 0
for X,y in test_iter:
X,y = X.cuda(),y.cuda()
y_hat = net(X)
acc_sum += (y_hat.argmax(dim=1) == y).float().sum().item()
batch += 1
#print('acc_sum %d,batch %d' % (acc_sum,batch)) acc = 1.0*acc_sum/(batch*batch_size)
end = time.time()
print('acc %3f,test for test dataset:time %d' % (acc,end - start)) return acc

训练

num_epochs = 3
save_to_disk = False
def train():
for epoch in range(num_epochs):
train_l_sum,batch,acc_sum = 0,0,0
start = time.time()
for X,y in train_iter:
# start_batch_begin = time.time()
X,y = X.cuda(),y.cuda()
y_hat = net(X)
acc_sum += (y_hat.argmax(dim=1) == y).float().sum().item() l = loss(y_hat,y)
opt.zero_grad()
l.backward() opt.step()
train_l_sum += l.item() batch += 1 mean_loss = train_l_sum/(batch*batch_size) #计算平均到每张图片的loss
start_batch_end = time.time()
time_batch = start_batch_end - start train_acc = acc_sum/(batch*batch_size)
if batch % 100 == 0:
print('epoch %d,batch %d,train_loss %.3f,train_acc:%.3f,time %.3f' %
(epoch,batch,mean_loss,train_acc,time_batch)) if save_to_disk and batch % 1000 == 0:
model_state = net.state_dict()
model_name = 'nin_epoch_%d_batch_%d_acc_%.2f.pt' % (epoch,batch,train_acc)
torch.save(model_state,model_name) print('***************************************')
mean_loss = train_l_sum/(batch*batch_size) #计算平均到每张图片的loss
train_acc = acc_sum/(batch*batch_size) #计算训练准确率
test_acc = test() #计算测试准确率
end = time.time()
time_per_epoch = end - start
print('epoch %d,train_loss %f,train_acc %f,test_acc %f,time %f' %
(epoch + 1,mean_loss,train_acc,test_acc,time_per_epoch)) train()

实验发现googlenet收敛比较慢.可能和全连接层用全局平均池化取代有关.因为用全局平均池化的话,相当于在全局平均池化之前,提取到的特征就是有高级语义的了,每一个feature map就代表了一个类别,所以前面负责特征提取的卷积部分就需要提取出更高级的特征.所以收敛会变慢.

完整代码

从头学pytorch(十八):GoogLeNet的更多相关文章

  1. 从头学pytorch(十二):模型保存和加载

    模型读取和存储 总结下来,就是几个函数 torch.load()/torch.save() 通过python的pickle完成序列化与反序列化.完成内存<-->磁盘转换. Module.s ...

  2. 从头学pytorch(十五):AlexNet

    AlexNet AlexNet是2012年提出的一个模型,并且赢得了ImageNet图像识别挑战赛的冠军.首次证明了由计算机自动学习到的特征可以超越手工设计的特征,对计算机视觉的研究有着极其重要的意义 ...

  3. 从头学pytorch(十九):批量归一化batch normalization

    批量归一化 论文地址:https://arxiv.org/abs/1502.03167 批量归一化基本上是现在模型的标配了. 说实在的,到今天我也没搞明白batch normalize能够使得模型训练 ...

  4. 从头学pytorch(十四):lenet

    卷积神经网络 在之前的文章里,对28 X 28的图像,我们是通过把它展开为长度为784的一维向量,然后送进全连接层,训练出一个分类模型.这样做主要有两个问题 图像在同一列邻近的像素在这个向量中可能相距 ...

  5. 从头学pytorch(十六):VGG NET

    VGG AlexNet在Lenet的基础上增加了几个卷积层,改变了卷积核大小,每一层输出通道数目等,并且取得了很好的效果.但是并没有提出一个简单有效的思路. VGG做到了这一点,提出了可以通过重复使⽤ ...

  6. 从头学pytorch(一):数据操作

    跟着Dive-into-DL-PyTorch.pdf从头开始学pytorch,夯实基础. Tensor创建 创建未初始化的tensor import torch x = torch.empty(5,3 ...

  7. 从头学pytorch(二十):残差网络resnet

    残差网络ResNet resnet是何凯明大神在2015年提出的.并且获得了当年的ImageNet比赛的冠军. 残差网络具有里程碑的意义,为以后的网络设计提出了一个新的思路. googlenet的思路 ...

  8. 从头学pytorch(二十二):全连接网络dense net

    DenseNet 论文传送门,这篇论文是CVPR 2017的最佳论文. resnet一文里说了,resnet是具有里程碑意义的.densenet就是受resnet的启发提出的模型. resnet中是把 ...

  9. 从头学pytorch(六):权重衰减

    深度学习中常常会存在过拟合现象,比如当训练数据过少时,训练得到的模型很可能在训练集上表现非常好,但是在测试集上表现不好. 应对过拟合,可以通过数据增强,增大训练集数量.我们这里先不介绍数据增强,先从模 ...

随机推荐

  1. hdu 1026 Ignatius and the Princess I(bfs)

    Ignatius and the Princess I Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (J ...

  2. PyTorch中view的用法

    相当于numpy中resize()的功能,但是用法可能不太一样. 我的理解是: 把原先tensor中的数据按照行优先的顺序排成一个一维的数据(这里应该是因为要求地址是连续存储的),然后按照参数组合成其 ...

  3. 洛谷P3178 [HAOI2015]树上操作 题解 树链剖分+线段树

    题目链接:https://www.luogu.org/problem/P3178 这道题目是一道树链剖分的模板题. 但是在解决这道问题的同事刷新了我的两个认识: 第一个认识是:树链剖分不光可以处理链, ...

  4. 通俗理解tf.name_scope()、tf.variable_scope()

    前言:最近做一个实验,遇到TensorFlow变量作用域问题,对tf.name_scope().tf.variable_scope()等进行了较为深刻的比较,记录相关笔记:tf.name_scope( ...

  5. java 综合示例代码

    package javaenhance.src.cn.itcast.day3; import java.lang.reflect.Constructor; import java.lang.refle ...

  6. VS2017 OpenCV3.4.2 通过Release的版本 源码编译成 x86

    官方release的OpenCV3..2版本只有64bit,由于项目需要,现在把它重新编译成x86的库. 下载源码: github官方仓库 https://github.com/opencv/open ...

  7. dotnet core 使用 CoreRT 将程序编译为 Native 程序

    现在微软有一个开源项目 CoreRT 能通过将托管的 .NET Core 编译为单个无依赖的 Native 程序 这个项目现在还没发布,但是能尝试使用,可以带来很多的性能提升 使用 CoreRT 发布 ...

  8. 关于CPython中set集合的无序研究

    set集合本身是无序的,但是无意间发现set集合中都是数字时set貌似有序了. 无论声明这个set时数字如何摆放,输出结果总是以一种固定的顺序!同样我将dict字典的key值设为int类型,这时候字典 ...

  9. C# 转换类型和字符串

    有时候我们需要互转类型和字符串,把字符串转类型.把类型转字符串. 如果是基础类型,可以使用 x.Parse 这个方法,很多基础类型都支持. 那么我们可以使用 TypeDescriptor string ...

  10. chrome查看当前网页cookie内容

    按下F12,打开开发者工具 然后点开Application,在点击cookie左侧的小三角,选择网页,就可以看见对应的cookies了