GoogLeNet网络

创新点:

  • 引入了 Inception 结构(融合不同尺度的特征信息)
  • 使用1x1的卷积核进行降维以及映射处理 (虽然VGG网络中也有,但该论文介绍的更详细)
  • 添加两个辅助分类器帮助训练
  • 丢弃全连接层,使用平均池化层(大大减少模型参数,除去两个辅助分类器,网络大小只有vgg的1/20)

把多个卷积或池化操作,放在一起组装成一个网络模块,设计神经网络时以模块为单位去组装整个网络结构。模块如下图所示

主要贡献:
  • 使用1x1的卷积来进行升降维;
  • 在多个尺寸上同时进行卷积再聚合
1x1卷积有什么用?
  • 在相同尺寸的感受野中叠加更多的卷积,能提取到更丰富的特征(观点来源于NiN)(三个1x1卷积都起到了该作用)
    • NIN结构中无论是第一个3x3卷积还是新增的1x1卷积,后面都紧跟着激活函数(比如relu)。
    • 将两个卷积串联,就能组合出更多的非线性特征
  • 进行降维,降低了计算复杂度(图中中间3x3卷积和5x5卷积前的1x1卷积都起到了这个作用)
    • 当某个卷积层输入的特征数较多,对这个输入进行卷积运算将产生巨大的计算量;如果对输入先进行降维,减少特征数后再做卷积计算量就会显著减少。
    • 只要最后输出的特征数不变(256组),中间的降维类似于压缩的效果,并不影响最终训练的结果

对输入做了4个分支,分别用不同尺寸的filter进行卷积或池化,最后再在特征维度上拼接到一起。这种全新的结构有什么好处呢?
  • 在直观感觉上在多个尺度上同时进行卷积,能提取到不同尺度的特征。特征更为丰富也意味着最后分类判断时更加准确。
  • 利用稀疏矩阵分解成密集矩阵计算的原理来加快收敛速度
  • Hebbin赫布原理

 代码实现

import torch
from torch import nn, optim
import torchvision
import sys
from time import time
import torch.nn.functional as F

device = torch.device('cuda:1' if torch.cuda.is_available() else 'cpu')
class Inception(nn.Module):
    def __init__(self, in_c, c1, c2, c3, c4): # c1-c4为每条路线里的层的输出通道数
        super(Inception, self).__init__()
        # 线路1,单1*1卷积层
        self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)
        # 线路2,1*1卷积层后接3*3卷积层
        self.p2_1 = nn.Conv2d(in_c, c2[0], kernel_size=1)
        self.p2_2 = nn.Conv2d(c2[0], c2[1], kernel_size=3, padding=1)
        # 线路3,1*1卷积层后接5*5卷积层
        self.p3_1 = nn.Conv2d(in_c, c3[0], kernel_size=1)
        self.p3_2 = nn.Conv2d(c3[0], c3[1], kernel_size=5, padding=2)
        # 线路4,3*3最大池化层后接1*1卷积层
        self.p4_1 = nn.MaxPool2d(kernel_size=3, stride=1, padding=1)
        self.p4_2 = nn.Conv2d(in_c, c4, kernel_size=1)
    def forward(self, x):
        p1 = F.relu(self.p1_1(x))
        p2 = F.relu(self.p2_2(self.p2_1(x)))
        p3 = F.relu(self.p3_2(self.p3_1(x)))
        p4 = F.relu(self.p4_2(self.p4_1(x)))
        # 在通道维上连结输出
        return torch.cat((p1, p2, p3, p4), dim=1)

class GlobalAvgPool2d(nn.Module):
    # 全局平均池化层可通过将池化窗口形状设置成输入的高和宽实现
    def __init__(self):
        super(GlobalAvgPool2d, self).__init__()
    def forward(self, x):
        return F.avg_pool2d(x, kernel_size=x.size()[2:])

'''对 x 的形状转换 '''
class FlattenLayer(nn.Module):
    def __init__(self):
        super(FlattenLayer, self).__init__()
    def forward(self, x):
        return x.view(x.shape[0], -1)

class GoogLeNet(nn.Module):
    def __init__(self, num_classes=1000):
        super(GoogLeNet, self).__init__()
        '''
        Conv2d:输出特征图尺寸为(224-7+3*2)/2+1=112.5(向下取整)=112,输出特征图维度为112x112x64
        MaxPool2d:输出特征图尺寸为((112 -3)/2)+1=55.5(向上取整)=56,输出特征图维度为56x56x64
        '''
        self.b1 = nn.Sequential(nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3),
                                # nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
                                nn.ReLU(),
                                nn.MaxPool2d(kernel_size=3, stride=2, padding=1))
        
        '''
        Conv2d:先用64个1x1的卷积核(3x3卷积核之前的降维)将输入的特征图(56x56x64)变为56x56x64
        Conv2d:输出特征图尺寸为(56-3+1*2)/1+1=56,输出特征图维度为56x56x192
        MaxPool2d:输出为((56 - 3)/2)+1=27.5(向上取整)=28,输出特征图维度为28x28x192
        '''
        self.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))
        '''
        3a
        使用64个1x1的卷积核,运算后特征图输出为28x28x64,然后RuLU操作
        96个1x1的卷积核(3x3卷积核之前的降维)运算后特征图输出为28x28x96,进行ReLU计算,再进行128个3x3的卷积,输出28x28x128
        16个1x1的卷积核(5x5卷积核之前的降维)将特征图变成28x28x16,进行ReLU计算,再进行32个5x5的卷积,输出28x28x32。
        pool层,使用3x3的核,输出28x28x192,然后进行32个1x1的卷积,输出28x28x32
        
        3b
        使用128个1x1的卷积核,运算后特征图输出为28x28x128,然后RuLU操作
        128个1x1的卷积核(3x3卷积核之前的降维)运算后特征图输出为28x28x128,进行ReLU计算,再进行192个3x3的卷积,输出28x28x192
        32个1x1的卷积核(5x5卷积核之前的降维)将特征图变成28x28x32,进行ReLU计算,再进行96个5x5的卷积,输出28x28x96。
        pool层,使用3x3的核,输出28x28x256,然后进行64个1x1的卷积,输出28x28x64
        将四个结果进行连接,对这四部分输出结果的第三维并联,即128+192+96+64=480,最终输出输出为28x28x480
        '''
        
        self.b3 = nn.Sequential(Inception(192, 64, (96, 128), (16, 32), 32), #3a
                                Inception(256, 128, (128, 192), (32, 96), 64), #3b
                                nn.MaxPool2d(kernel_size=3, stride=2, padding=1))

        self.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))

        self.b5 = nn.Sequential(Inception(832, 256, (160, 320), (32, 128), 128),
                                Inception(832, 384, (192, 384), (48, 128), 128),
                                GlobalAvgPool2d())
        
        self.output=nn.Sequential(FlattenLayer(),
                                  nn.Dropout(p=0.4),
                                  nn.Linear(1024, 10)) #原为1000
        
    def forward(self, x):
        x=self.b1(x)
        x=self.b2(x)
        x=self.b3(x)
        x=self.b4(x)
        x=self.b5(x)
        output=self.output(x)
        return output
'''测试输出'''
net = GoogLeNet()
# X = torch.rand(1, 3, 224, 224)
X = torch.rand(1, 1, 96, 96)
for blk in net.children(): 
    X = blk(X)
    print('output shape: ', X.shape)

'''读取数据集'''
def load_data_fashion_mnist(batch_size, resize=None):
    if sys.platform.startswith('win'):
        num_workers = 0  # 0表示不用额外的进程来加速读取数据
    else:
        num_workers = 4
        
    '''定义数据预处理的转换函数列表'''    
    trans = []
    if resize: #判断是否需要进行图像尺寸调整(resize)
        trans.append(torchvision.transforms.Resize(size=resize)) 
        #将torchvision.transforms.Resize转换函数添加到转换函数列表trans中,并指定目标尺寸为resize
    trans.append(torchvision.transforms.ToTensor())
    # 将torchvision.transforms.ToTensor转换函数添加到转换函数列表trans中。这个函数用于将图像数据转换为张量,并且按照通道顺序排列(RGB)
    transform = torchvision.transforms.Compose(trans) 
    #通过torchvision.transforms.Compose函数将转换函数列表trans组合成一个转换操作
    
    mnist_train = torchvision.datasets.FashionMNIST(root='data/FashionMNIST',
                                                    train=True,
                                                    download=True,
                                                    transform=transform)
    mnist_test = torchvision.datasets.FashionMNIST(root='data/FashionMNIST',
                                                   train=False,
                                                   download=True,
                                                   transform=transform)
    train_iter = torch.utils.data.DataLoader(mnist_train,
                                             batch_size=batch_size,
                                             shuffle=True,
                                             num_workers=num_workers)
    test_iter = torch.utils.data.DataLoader(mnist_train,
                                             batch_size=batch_size,
                                             shuffle=False,
                                             num_workers=num_workers) 
    return train_iter, test_iter

'''设置网络'''
net = GoogLeNet()

'''用于GPU的准确率评估函数'''
def evaluate_accuracy(data_iter, net, device=None): 
    if device is None and isinstance(net, torch.nn.Module):
        device = list(net.parameters())[0].device #如果没指定device就使用net的device
    acc_sum, n = 0.0, 0
    for X, y in data_iter:
        if isinstance(net, torch.nn.Module):
            net.eval() # 评估模式, 这会关闭dropout
            acc_sum += (net(X.to(device)).argmax(dim=1) == y.to(device)).float().sum().cpu().item()
            net.train() # 改回训练模式
        else:
            if ('is_training' in net.__code__.co_varnames):
                acc_sum += (net(X, is_training=False).argmax(dim=1) == y).float().sum().item()
            else:
                acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
        n += y.shape[0]
    return acc_sum / n

'''确保计算使用的数据和模型同在内存或显存上'''
def train_ch5(net, train_iter, test_iter,  batch_size, optimizer , device, num_epochs):
    # 将模型加载到指定运算器中
    net = net.to(device)
    print("training on ", device)
    loss = torch.nn.CrossEntropyLoss()
    for epoch in range(num_epochs):
    # 在每一个迭代周期中,会使用训练集中所有样本一次(假设样本数能够被批量大小整除)
        train_l_sum, train_acc_sum, n ,batch_count= 0.0, 0.0, 0,0
        # 分别表示训练损失总和、训练准确度总和、样本数
        start = time()
        for X, y in train_iter:
            X = X.to(device)
            y = y.to(device)
            '''前向传播'''
            y_hat = net(X) 
            #由于变量 l 并不是一个标量,所以我们可以调用 .sum() 将其求和得到一个标量
            '''计算损失'''
            l = loss(y_hat, y).sum()
            '''梯度清零'''
            if optimizer is not None:
                optimizer.zero_grad()
            elif params is not None and params[0].grad is not None:
                for param in params:
                    param.grad.data.zero_()
            '''反向传播'''
            l.backward() # 运行 l.backward() 得到该变量有关模型参数的梯度
            
            if optimizer is None:
                d2l.sgd(params, lr, batch_size)
            else: 
                '''更新参数'''
                optimizer.step()
            '''计算精度'''  
            train_l_sum += l.cpu().item()  
            train_acc_sum += (y_hat.argmax(dim=1) == y).sum().cpu().item()
            n += y.shape[0]
            batch_count += 1
        test_acc = evaluate_accuracy(test_iter, net)
        print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f,\
        time %.1f sec' %(epoch+1, train_l_sum/batch_count, 
                         train_acc_sum/n, test_acc,
                         time()-start))
'''使用高和宽均为96像素的图像来训练GoogLeNet模型'''
batch_size = 256
train_iter, test_iter = load_data_fashion_mnist(batch_size, resize=96)
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(net.parameters(), lr=lr)
train_ch5(net, train_iter, test_iter, batch_size, optimizer,device, num_epochs)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值