GoogleNet重点介绍和源码

1 GoogleNet背景

GoogLeNet在2014年由Google团队提出(与VGG网络同年,注意GoogLeNet中的L大写是为了致敬LeNet),斩获当年ImageNet竞赛中Classification Task (分类任务) 第一名。
创新点:

  1. 引入Inception模块,获取不同尺度的图像特征
  2. 使用1*1的卷积层进行数据降维和整个多通道间的信息和特征
  3. 添加两个辅助分类器帮助训练
  4. 丢弃最后的全连接层使用平均池化层,减少参数
    结构图:
    在这里插入图片描述

2 Inception模块

使用并联的结构,获取图片中不同尺寸的特征,并且在最后进行通道整合,大大减少了参数量并且提升了特征感知力,同时还可以扩宽和扩深网络

2.1 InceptionV1

在这里插入图片描述

要求四个通道最后的输出长宽需要相同,这样才不用裁剪,这里给出Conv2d和MaxPool2d尺寸计算公式:尺寸计算

2.2 InceptionV2

在这里插入图片描述

这里和V1比较,就是2,3,4通道都添加了1*1的卷积层,这是为了减少参数。

假设Previous layer输入的是64✖192✖56✖56 (batch_size,channel,width,height)
从左往右1,2,3,4通道最后需要输出的通道数是64,512,256,128, 同时1*1的卷积输出通道为64
则V1的最后参数是 sum1 = 64✖192✖1✖1 + 512✖192✖3✖3 + 256✖192✖5✖5 + 128✖192✖1✖1 = 2150400
则V2的最后参数是 sum2 = 64✖192✖1✖1 + (64✖192✖1✖1 + 512✖64✖3✖3) + (64✖192✖1✖1 + 256✖64✖5✖5) + 128✖192✖1✖1 = 765952
比值:sum1/sum2 = 0.3561
附上CNN参数计算公式:CNN参数个数 = 卷积核尺寸×卷积核深度 × 卷积核组数 = 卷积核尺寸 × 输入特征矩阵深度 × 输出特征矩阵深度

从这个假设中可以看出,是有很明显的改善的.

3 辅助分类器(Auxiliary Classifier)

就是结构图中的右边部分,在训练模型时,将两个辅助分类器的损失乘以权重(论文中是0.3)加到网络的整体损失上,再进行反向传播。
辅助分类器的作用:

  1. 可以把他看做inception网络中的一个小细节,它确保了即便是隐藏单元和中间层也参与了特征计算,他们也能预测图片的类别,他在inception网络中起到一种调整的效果,并且能防止网络发生过拟合。

  2. 给定深度相对较大的网络,有效传播梯度反向通过所有层的能力是一个问题。通过将辅助分类器添加到这些中间层,可以期望较低阶段分类器的判别力。在训练期间,它们的损失以折扣权重(辅助分类器损失的权重是0.3)加到网络的整个损失上。
    使用参数:
    The exact structure of the extra network on the side, including the auxiliary classifier, is as follows:

  3. An average pooling layer with 5×5 filter size and stride 3, resulting in an 4×4×512 output for the (4a), and 4×4×528 for the (4d) stage.

  4. A 1×1 convolution with 128 filters for dimension reduction and rectified linear activation.

  5. A fully connected layer with 1024 units and rectified linear activation.

  6. A dropout layer with 70% ratio of dropped outputs.

  7. A linear layer with softmax loss as the classifier (predicting the same 1000 classes as the main classifier, but removed at inference time).
    *具体代码见最后! *
    后来从其他论文中得到,这个辅助分类器实际用途不大,就没实现,有需要的可以自己尝试一下!

4 模型参数

在这里插入图片描述
属性说明:
#1x1 :对应着分支1上1x1的卷积核个数
#3x3reduce :对应着分支2上1x1的卷积核个数
#3x3 :对应着分支2上3x3的卷积核个数
#5x5reduce :对应着分支3上1x1的卷积核个数
#5x5 :对应着分支3上5x5的卷积核个数
poolproj: 对应着分支4上1x1的卷积核个数。

5 源码分享

5.1 使用方法1

使用Kaggle平台,其免费提供GPU算计,同时数据集也提供,以下是使用情况: Kaggle搜索这个数据集,然后新建一个文本即可使用。
在这里插入图片描述

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

# dataLoader
import os
from torch.utils.data import DataLoader
from torchvision import datasets, transforms, models
from torchvision.utils import make_grid

from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report

# pred_list = os.listdir("../input/intel-image-classification/seg_pred/seg_pred")

# 前期准备工作 一下仅供参考
train_transforms = transforms.Compose([
    transforms.RandomRotation(10),
    transforms.RandomHorizontalFlip(),
    transforms.Resize(227),
    transforms.CenterCrop(227),
    transforms.ToTensor(),
    transforms.Normalize([0.485,0.456,0.406],
                         [0.229,0.224,0.225])
])

test_transforms = transforms.Compose([
    transforms.Resize([227,227]),
    transforms.ToTensor(),
    transforms.Normalize([0.485,0.456,0.406],
                         [0.229,0.224,0.225])
])

# 第一步 分别传入训练集和测试集
train_dataset = datasets.ImageFolder(root="./seg_train/seg_train", transform=train_transforms)
test_dataset = datasets.ImageFolder(root="./seg_test/seg_test" , transform = test_transforms)

# 第二步 不需要索引了, 直接使用数据加载器进行加载 
train_loader = DataLoader(train_dataset, batch_size=16, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=16, shuffle=True)

# Parameters
NUM_CLASSES = 6
EPOCH = 150
LR = 0.001

# inception block
class Inception(nn.Module):
    # c1, c2, c3, c4 为四条线路中的通道数量
    def __init__(self, in_c, c1, c2, c3, c4):
        super(Inception, self).__init__()
        # path1 with 1*1
        self.p1_1 = nn.Conv2d(in_c, c1, kernel_size=1)
        
        # path2 with 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)
        
        # path3 with 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)
        
        # path4 with 3*3Pool + 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(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)  # (batch, channel, w,h) 在通道上链接

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:])
    
class FlattenLayer(nn.Module):  # 将其直接利用reshape转化为线性数据
    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=NUM_CLASSES):
        super(GoogleNet, self).__init__()
        self.b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),
                                nn.ReLU(inplace=True),
                                # nn.BatchNorm2d(64),
                                nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )
        
        self.b2 = nn.Sequential(nn.Conv2d(64, 64, kernel_size=1),
                                nn.Conv2d(64, 192, kernel_size=3, padding=1),
                                nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        )
        
        self.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)
        )
        

        
        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, NUM_CLASSES)
        )
        
    def forward(self, x):
        x = self.b1(x)
        x = self.b2(x)
        x = self.b3(x)
        x = self.b4(x)
        x = self.b5(x)
        x = self.output(x)
        return x

# Initializtion

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

net = GoogleNet(NUM_CLASSES).to(device)

criterion = nn.CrossEntropyLoss().to(device)

optimizer = torch.optim.Adam(net.parameters(), lr=LR)

lr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=20, gamma=0.5)

# X = torch.rand(1, 3, 227, 227).to(device)

# for blk in net.children():
    # X  = blk(X)
    # print( "output shape:", X.shape)

def count_parameters(model):
    params = [p.numel() for p in model.parameters() if p.requires_grad]
#     for item in params:
#         print(f'{item:>8}')
    print(f'________\n{sum(params):>8}')
count_parameters(net)

import time
start_time = time.time()
train_losses = []
test_losses = []
train_acc = []
test_acc = []
for i in range(EPOCH):
    start_time = time.time()
    total_train_loss = 0
    total_train_acc = 0
    for idx, (x_train, y_train) in enumerate(train_loader):
        x_train, y_train = x_train.to(device), y_train.to(device)
        y_pred = net(x_train)
        
        loss = criterion(y_pred, y_train)
        total_train_loss += loss.item()
        acc = (y_pred.argmax(1) == y_train).sum()
        total_train_acc += acc

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
    lr_scheduler.step()
        
    train_losses.append(total_train_loss)
    train_acc.append(total_train_acc/len(train_dataset))
    
    total_test_loss = total_test_acc = 0
    with torch.no_grad():
        for idx, (x_test, y_test) in enumerate(test_loader):
            x_test, y_test = x_test.to(device), y_test.to(device)
            
            y_pred = net(x_test)
            loss = criterion(y_pred, y_test)
            total_test_loss += loss.item()
            
            acc = (y_pred.argmax(1) == y_test).sum()
            total_test_acc += acc
            
        test_losses.append(total_test_loss)
        test_acc.append(total_test_acc/len(test_dataset))
    end_time = time.time()
    print(f"{i+1}/{EPOCH}, time:{end_time-start_time} \t train_loss:{total_train_loss}\t train_acc:{total_train_acc/len(train_dataset)}\t test_loss:{total_test_loss} \t test_acc:{total_test_acc/len(test_dataset)}") 
    

5.2 使用方法2

Kaggle数据集下载
点击Download下载即可,然后复制以上代码,调整好输入路径,就可以跑起来了。

训练结果:
在这里插入图片描述

更新一下代码,之前代码中的lr_scheduler放错地方了,导致出现不收敛的问题,代码已更正,可放心食用。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Philo`

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值