基于pytorch的GoogleNet实现

B站UP:霹雳吧啦Wz

课程合集链接:1.1 卷积神经网络基础_哔哩哔哩_bilibili

代码参考B站UP:霹雳吧啦Wz的《深度学习-图像分类篇章》视频,代码根据个人编程习惯及本人自用数据集进行少量改动,欢迎大佬们批评指正。

1 网络结构

 

 

 

 

 

inception 有什么好处呢?Szegedy从多个角度进行了解释:

解释1:在直观感觉上在多个尺度上同时进行卷积,能提取到不同尺度的特征。特征更为丰富也意味着最后分类判断时更加准确。

解释2:利用稀疏矩阵分解成密集矩阵计算的原理来加快收敛速度。举个例子下图左侧是个稀疏矩阵(很多元素都为 0,不均匀分布在矩阵中),和一个 2x2 的矩阵进行卷积,需要对稀疏矩阵中的每一个元素进行计算;如果像下图右图那样把稀疏矩阵分解成2个子密集矩阵,再和 2x2 矩阵进行卷积,稀疏矩阵中 0 较多的区域就可以不用计算,计算量就大大降低。这个原理应用到 inception 上就是要在特征维度上进行分解!传统的卷积层的输入数据只和一种尺度(比如 3x3 )的卷积核进行卷积,输出固定维度(比如 256 个特征)的数据,所有 256 个输出特征基本上是均匀分布在 3x3 尺度范围上,这可以理解成输出了一个稀疏分布的特征集;而 inception 模块在多个尺度上提取特征(比如 1x1,3x3,5x5 ),输出的 256 个特征就不再是均匀分布,而是相关性强的特征聚集在一起(比如 1x1 的 96 个特征聚集在一起,3x3 的 96 个特征聚集在一起,5x5 的 64 个特征聚集在一起),这可以理解成多个密集分布的子特征集。这样的特征集中因为相关性较强的特征聚集在了一起,不相关的非关键特征就被弱化,同样是输出 256 个特征,Inception 方法输出的特征“冗余”的信息较少。用这样的“纯”的特征集层层传递最后作为反向计算的输入,自然收敛的速度更快。

来自 深度学习之图像分类(五)--GoogLeNet网络结构_图像分类网络结构_木卯_THU的博客-CSDN博客

助分类器(注意:在训练时使用,在测试时并不使用辅助分类器)

由于网络比较深,梯度传到前面几层的时候很可能就消失了,所以定义辅助分类器。Inception Net一共有22层,除了最后一层的输出结果,中间节点的分类效果也有可能是很好的,所以GoogLeNet将中间某一层的输出作为分类,并以一个较小的权重(0.3)加到最终的分类结果中。一共有2个这样的辅助分类节点。

辅助分类器相当于对模型做了融合,同时给网络增加了反向传播的梯度信号,在一定程度上提供了正则化的作用。

来自 <GoogLeNet 神经网络结构_辅助分类器_-牧野-的博客-CSDN博客>

 
2 代码实现 

2.1 模型搭建

import torch

# 把卷积层和激活函数打包成一个基础的卷积模块
class BasicConv2d(torch.nn.Module):
    def __init__(self, in_channels, out_channels, **kwargs): # **kwargs表示传入任意数量的参数,可能包括kernel_size, stride, padding等
        super(BasicConv2d, self).__init__()
        self.conv = torch.nn.Conv2d(in_channels, out_channels, **kwargs)
        self.active = torch.nn.ReLU(inplace=True)

    def forward(self, x):
        x = self.conv(x)
        x = self.active(x)
        return x

# 定义Inception模块
class Inception(torch.nn.Module):
    def __init__(self, in_channels, ch1x1, ch3x3reduce, ch3x3, ch5x5reduce, ch5x5, ch_pool): # 输入Inception的4条分支的参数
        super(Inception, self).__init__()
        # 第一个分支
        self.branch1 = BasicConv2d(in_channels, ch1x1, kernel_size=1)
        # 第二个分支
        self.branch2 = torch.nn.Sequential(
            BasicConv2d(in_channels, ch3x3reduce, kernel_size=1),
            BasicConv2d(ch3x3reduce, ch3x3, kernel_size=3, padding=1, stride=1),
        )
        # 第三个分支
        self.branch3 = torch.nn.Sequential(
            BasicConv2d(in_channels, ch5x5reduce, kernel_size=1),
            BasicConv2d(ch5x5reduce, ch5x5, kernel_size=5, padding=2, stride=1),
        )
        # 第四个分支
        self.branch4 = torch.nn.Sequential(
            torch.nn.MaxPool2d(kernel_size=3, padding=1, stride=1),
            BasicConv2d(in_channels, ch_pool, kernel_size=1),
        )

    def forward(self, x):
        branch1 = self.branch1(x)
        branch2 = self.branch2(x)
        branch3 = self.branch3(x)
        branch4 = self.branch4(x)
        outputs = [branch1, branch2, branch3, branch4]
        # [batch, channels, H, W] 从channels维度进行拼接
        return torch.cat(outputs, dim=1)


# 定义辅助分类器
class InceptionAux(torch.nn.Module):
    def __init__(self, in_channels, num_classes):
        super(InceptionAux, self).__init__()
        self.averagepool = torch.nn.AvgPool2d(kernel_size=5, stride=3)
        self.conv = BasicConv2d(in_channels, 128, kernel_size=1)
        self.fc1 = torch.nn.Linear(128*4*4, 1024)
        self.fc2 = torch.nn.Linear(1024, num_classes)

    def forward(self, x):
        # 辅助分类器aux1: N x 512 x 14 x 14, 辅助分类器aux2: N x 528 x 14 x 14
        x = self.averagepool(x)
        # aux1: N x 512 x 4 x 4, aux2: N x 528 x 4 x 4
        x = self.conv(x)
        # N x 128 x 4 x 4
        x = torch.flatten(x, start_dim=1)
        # N x 2048
        # 在model.train()模式下self.training=True,在model.eval()模式下self.training=False,为Ture时执行dropout,为False时不执行
        x = torch.nn.functional.dropout(x, p=0.5, training=self.training)
        x = self.fc1(x)
        x = torch.nn.functional.relu(x, inplace=True)
        x = torch.nn.functional.dropout(x, p=0.5, training=self.training)
        # x = torch.nn.ReLU(inplace=True)
        # x = torch.nn.Dropout(0.5)
        x = self.fc2(x)
        return x

class Model(torch.nn.Module):
    # 在定义模型时aux_logits=True, init_weight=False这种,如果实例化模型后传入了aux_logits,aux_logits,以传入的为准,如果没有传入这个参数,就以定义模型时设置的为准
    def __init__(self, num_classes, aux_logits=True, init_weights=False): # aux_logits=True表示使用辅助分类器
        super(Model, self).__init__()
        self.aux_logits = aux_logits

        self.conv1 = BasicConv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.maxpool1 = torch.nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True) # ceil_mode=True代表计算结果向上取整(可以理解成在原来的数据上补充了值为-NAN的边界)

        self.conv2 = BasicConv2d(64, 64, kernel_size=1)
        self.conv3 = BasicConv2d(64, 192, kernel_size=3, padding=1)
        self.maxpool2 = torch.nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True)

        self.incep3a = Inception(192, 64, 96, 128, 16, 32, 32)
        self.incep3b = Inception(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = torch.nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True)

        self.incep4a = Inception(480, 192, 96, 208, 16, 48, 64)
        self.incep4b = Inception(512, 160, 112, 224, 24, 64, 64)
        self.incep4c = Inception(512, 128, 128, 256, 24, 64, 64)
        self.incep4d = Inception(512, 112, 144, 288, 32, 64, 64)
        self.incep4e = Inception(528, 256, 160, 320, 32, 128, 128)
        self.maxpool4 = torch.nn.MaxPool2d(kernel_size=3, stride=2, ceil_mode=True)

        self.incep5a = Inception(832, 256, 160, 320, 32, 128, 128)
        self.incep5b = Inception(832, 384, 192, 384, 48, 128, 128)

        if aux_logits: # 如果传入的aux_logits为True,则定义self.aux1和self.aux2
            self.aux1 = InceptionAux(512, num_classes)
            self.aux2 = InceptionAux(528, num_classes)

        self.averagepool = torch.nn.AvgPool2d(kernel_size=7, stride=1)
        self.dropout = torch.nn.Dropout(0.4)
        self.fc = torch.nn.Linear(1024, num_classes)

        if init_weights: # 如果传入的init_weight为True,则采用_initialize_weights()的参数初始化方法
            self._initialize_weights()

    def forward(self, x):
        x = self.conv1(x)
        x = self.maxpool1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = self.maxpool2(x)
        x = self.incep3a(x)
        x = self.incep3b(x)
        x = self.maxpool3(x)
        x = self.incep4a(x)

        if self.training and self.aux_logits: # 如果是训练过程,并且使用辅助分类器
            aux1 = self.aux1(x)

        x = self.incep4b(x)
        x = self.incep4c(x)
        x = self.incep4d(x)
        if self.training and self.aux_logits: # 如果是训练过程,并且使用辅助分类器
            aux2 = self.aux2(x)

        x = self.incep4e(x)
        x = self.maxpool4(x)
        x = self.incep5a(x)
        x = self.incep5b(x)

        x = self.averagepool(x)
        x = torch.flatten(x, start_dim=1)
        x = self.dropout(x)
        x = self.fc(x)

        if self.training and self.aux_logits:  # 如果是训练过程,并且使用辅助分类器
            return x, aux2, aux1

        return x # 如果不是训练过程,或者不使用辅助分类器,就直接返回x

    def _initialize_weights(self):
        for m in self.modules():  # 遍历每一层网络结构
            if isinstance(m, torch.nn.Conv2d):  # 如果是卷积层
                torch.nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu') # (何)恺明初始化方法
                # torch.nn.init.xavier_uniform_(m.weight)
                if m.bias is not None:  # 如果有偏置参数
                    torch.nn.init.constant_(m.bias, 0)  # 把偏置参数初始化为0
            elif isinstance(m, torch.nn.Linear):  # 如果是全连接层
                # torch.nn.init.xavier_uniform_(m.weight)
                torch.nn.init.normal_(m.weight, 0, 0.01)
                torch.nn.init.constant_(m.bias, 0)  # 把偏置参数初始化为0

2.2 模型训练

import os
import torch
from torch.utils.data import DataLoader
from torchvision import transforms, datasets
import matplotlib.pyplot as plt
from GoogLeNet_model import Model

batch_size = 32
transform = {
    "train":transforms.Compose([transforms.RandomResizedCrop(224),
                                transforms.RandomHorizontalFlip(),
                                transforms.ToTensor(),
                                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))]),

    "test":transforms.Compose([transforms.Resize((224, 224)),
                               transforms.ToTensor(),
                               transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
}


# 获得数据集的路径
data_root = os.path.abspath(os.path.join(os.getcwd(), "../"))
data_path = data_root + "/dataset/SIPaKMeD/"

# 加载训练集
train_dataset = datasets.ImageFolder(root=os.path.join(data_path, "train"), transform=transform["train"])
train_loader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=batch_size)
# 加载测试集
test_dataset= datasets.ImageFolder(root=os.path.join(data_path, "test"), transform=transform["test"])
test_loader = DataLoader(dataset=test_dataset, shuffle=True, batch_size=batch_size)

print("using {} images for training, {} images for test".format(len(train_dataset), len(test_dataset)))


model = Model(num_classes=5, aux_logits=True, init_weights=True)
# 如果要使用官方的预训练权重,注意是将权重载入官方的模型,不是我们自己实现的模型
# 官方的模型中使用了bn层以及改了一些参数,不能混用
# import torchvision
# net = torchvision.models.googlenet(num_classes=5)
# model_dict = net.state_dict()
# # 预训练权重下载地址: https://download.pytorch.org/models/googlenet-1378be20.pth
# pretrain_model = torch.load("googlenet.pth")
# del_list = ["aux1.fc2.weight", "aux1.fc2.bias",
#             "aux2.fc2.weight", "aux2.fc2.bias",
#             "fc.weight", "fc.bias"]
# pretrain_dict = {k: v for k, v in pretrain_model.items() if k not in del_list}
# model_dict.update(pretrain_dict)
# net.load_state_dict(model_dict)
loss_function = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.0003)

loss_list = []
accuracy_list = []
best_acc = 0
for epoch in range(10):
    model.train()
    loss_sum = 0
    for i, (inputs, targets) in enumerate(train_loader):
        logits, logits_aux2, logits_aux1 = model(inputs)
        loss0 = loss_function(logits, targets)
        loss1 = loss_function(logits_aux1, targets)
        loss2 = loss_function(logits_aux2, targets)
        loss = loss0 + 0.3 * loss1 + 0.3 * loss2

        loss_sum += loss.item()
        loss_list.append(loss.item())

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        rate = (i+1) / len(train_loader)
        a = "*" * int(rate * 50)
        b = "-" * int(50 * (1-rate))
        print("\rtrain loss:{:.3f}% [{}->{}] {:.3f}".format(rate*100, a, b, loss), end="")
    print()


    model.eval()
    total = 0
    correct = 0
    with torch.no_grad():
        for (images, labels) in test_loader:
            y_pred = model(images)
            _,predicted = torch.max(y_pred.data, dim=1)

            total += labels.size(0)
            correct += (predicted == labels).sum().item()

        accuracy_list.append(correct / total)

        print("[epoch %d], train_loss:%.3f, test_accuracy:%.3f%% " % (epoch+1, loss_sum / (i+1), 100 * correct / total))

        if correct / total > best_acc:
            best_acc = correct / total
            torch.save(model.state_dict(), "GoogLeNet.pth")


print("Finishing Training")
plt.subplot(121)
plt.plot(range(len(loss_list)), loss_list)
plt.xlabel("step")
plt.ylabel("loss")
plt.subplot(122)
plt.plot(range(epoch + 1), accuracy_list)
plt.xlabel("epoch")
plt.ylabel("accuracy")
plt.show()

2.3 模型预测

import torch
from torchvision import transforms
from PIL import Image
import matplotlib.pyplot as plt
from GoogLeNet_model import Model

transform = transforms.Compose([transforms.Resize((224, 224)),
                                transforms.ToTensor(),
                                transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

img = Image.open("im_Dyskeratotic.bmp")
plt.imshow(img)
img = transform(img)
img = torch.unsqueeze(img, dim=0)

model = Model(num_classes=5, aux_logits=False)

missing_keys, unexpected_keys = model.load_state_dict(torch.load("GoogLeNet.pth"), strict=False)
classes = ["im_Dyskeratotic", "im_Koilocytotic", "im_Metaplastic", "im_Parabasal", "im_Superficial_Intermediate"]
model.eval()
with torch.no_grad():
    output = model(img)

    predict = torch.softmax(output, dim=1).numpy() # torch.softmax返回的是一个张量形式的二维矩阵,通过.numpy()转换成数组形式的二维矩阵
    print(predict)
    predict_cla = torch.argmax(output).item() # 把张量形式的值取出来
    # print(predict_cla)
    print("img is predicted as %s, accuracy is %.3f" % (classes[predict_cla], predict[0][predict_cla]))

plt.show()

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,这个问题我可以回答。首先需要了解一下 GoogLeNet 的结构。GoogLeNet 是一种深度卷积神经网络,它在 2014 年的 ImageNet 挑战赛上取得了第一名的好成绩。它的主要特点是使用了 Inception 模块,这是一种可以同时使用不同大小的卷积核和池化层的模块,可以大幅提高网络的准确率。 然后,我们需要准备 CIFAR-10 数据集。CIFAR-10 是一个包含 60000 张 32x32 像素彩色图像的数据集,其中有 50000 张用于训练,10000 张用于测试。数据集中的图像分为 10 个类别,每个类别有 6000 张图像。 接下来,我们可以使用 PyTorch实现基于 GoogLeNet 的 CIFAR-10 图像分类。具体的实现过程可以分为以下几个步骤: 1. 定义网络结构。我们需要定义一个包含多个 Inception 模块的网络结构,并添加全局平均池化层和一个全连接层来输出分类结果。 2. 加载数据集。我们需要使用 PyTorch 的 DataLoader 来加载 CIFAR-10 数据集,并对数据进行预处理。 3. 定义损失函数和优化器。我们可以使用交叉熵损失函数来计算网络的误差,并使用 Adam 优化器来更新网络参数。 4. 训练网络。我们可以使用 PyTorch 的训练循环来训练网络,并在每个 epoch 后对网络在测试集上的准确率进行评估。 5. 测试网络。我们可以使用训练好的网络来对新的图像进行分类,并计算分类准确率。 以上就是基于 GoogLeNet 的 CIFAR-10 图像分类实现过程。如果你需要更具体的代码实现,可以参考一些相关的 PyTorch 教程或者代码库。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值