当前位置: 首页 > article >正文

深度学习J8周 Inception v1算法实战与解析

  • 🍨 本文为🔗365天深度学习训练营中的学习记录博客
  • 🍖 原作者:K同学啊

本周任务:

1.了解并学习图2中的卷积层运算量的计算过程

2.了解并学习卷积层的并行结构域1×1卷积核部分内容(重点)

3.尝试根据模型框架图写出相应的pytorch代码,并使用Inception v1完成识别任务

一、Inception v1

1.理论知识

Inception v1有22层深,参数量为5M。同一时期VGGNet性能和Inception v1差不多,但是参数量也是远大于Inception。

Inception Module是Inception v1的核心组成单元,提出卷积层的并行结构,实现了在同一层就可以提取不同的特征

通过这样的结构增加网络的深度,虽然可以提升性能,但还面临计算量大(参数多)的问题。为改善这种现象,Inception Module借鉴Network-in-Network的思想,使用1×1的卷积核实现降维操作(间接增加网络的深度),以此减小网络的参数量与计算量。

1×1卷积核的作用:降低输入特征图的通道数,减小网络的参数量与计算量

Inception Module基本由1×1卷积、3×3卷积、5×5卷积,3×3最大池化四个基本单元组成,对四个基本单元运算结果进行通道上组合, 不同大小的卷积核赋予不同大小的感受野,从而提取到图像不同尺度的信息,进行融合,得到图像更好的表征(Inception Module核心思想)。

2.算法结构

实现Inception v1网络结构图:

注:另外增加两个辅助分支,作用:①避免梯度消失,用于向前传导梯度。反向传播时,如果有一层求导为0,链式求导结果则为0。②将中间某一层输出用作分类,起到模型融合作用,实际测试时,这两个辅助softmax分支会被去掉,在后续模型发展中,该方法被采用较少,可以直接绕过,重点学习卷积层的并行结构与1×1卷积核部分的内容。

二、pytorch复现

1.前期准备

1.1 配置GPU+导入数据集

import os,PIL,random,pathlib
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision
from torchvision import transforms, datasets
 
 
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
 
print(device)
 
data_dir = './data/'
data_dir = pathlib.Path(data_dir)
 
data_paths = list(data_dir.glob('*'))
classeNames = [str(path).split("\\")[1] for path in data_paths]
print(classeNames)
 
image_count = len(list(data_dir.glob('*/*')))
print("图片总数为:", image_count)

1.2数据预处理+划分数据集

train_transforms = transforms.Compose([
    transforms.Resize([224, 224]),  # 将输入图片resize成统一尺寸
    # transforms.RandomHorizontalFlip(), # 随机水平翻转
    transforms.ToTensor(),  # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
    transforms.Normalize(  # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225])  # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
 
test_transform = transforms.Compose([
    transforms.Resize([224, 224]),  # 将输入图片resize成统一尺寸
    transforms.ToTensor(),  # 将PIL Image或numpy.ndarray转换为tensor,并归一化到[0,1]之间
    transforms.Normalize(  # 标准化处理-->转换为标准正太分布(高斯分布),使模型更容易收敛
        mean=[0.485, 0.456, 0.406],
        std=[0.229, 0.224, 0.225])  # 其中 mean=[0.485,0.456,0.406]与std=[0.229,0.224,0.225] 从数据集中随机抽样计算得到的。
])
 
total_data = datasets.ImageFolder("./data/", transform=train_transforms)
print(total_data.class_to_idx)
 
train_size = int(0.8 * len(total_data))
test_size = len(total_data) - train_size
train_dataset, test_dataset = torch.utils.data.random_split(total_data, [train_size, test_size])
 
batch_size = 32
train_dl = torch.utils.data.DataLoader(train_dataset,
                                       batch_size=batch_size,
                                       shuffle=True,
                                       num_workers=0)
test_dl = torch.utils.data.DataLoader(test_dataset,
                                      batch_size=batch_size,
                                      shuffle=True,
                                      num_workers=0)
for X, y in test_dl:
    print("Shape of X [N, C, H, W]: ", X.shape)
    print("Shape of y: ", y.shape, y.dtype)
    break

输出结果: 

2.代码复现

定义四个分支:

class inception_block(nn.Module):
    def __init__(self, in_channels, ch1x1, ch3x3red, ch3x3, ch5x5red, ch5x5, pool_proj):
        super().__init__()
        # 1x1 conv branch
        self.branch1 = nn.Sequential(
            nn.Conv2d(in_channels, ch1x1, kernel_size=1),
            nn.BatchNorm2d(ch1x1),
            nn.ReLU(inplace=True)
        )
        # 1x1 conv -> 3x3 conv branch
        self.branch2 = nn.Sequential(
            nn.Conv2d(in_channels, ch3x3red, kernel_size=1),
            nn.BatchNorm2d(ch3x3red),
            nn.ReLU(inplace=True),
            nn.Conv2d(ch3x3red, ch3x3, kernel_size=3, padding=1),
            nn.BatchNorm2d(ch3x3),
            nn.ReLU(inplace=True)
        )
        # 1x1 conv -> 5x5 conv branch
        self.branch3 = nn.Sequential(
            nn.Conv2d(in_channels, ch5x5red, kernel_size=1),
            nn.BatchNorm2d(ch5x5red),
            nn.ReLU(inplace=True),
            nn.Conv2d(ch5x5red, ch5x5, kernel_size=3, padding=1),
            nn.BatchNorm2d(ch5x5),
            nn.ReLU(inplace=True)
        )
        self.branch4 = nn.Sequential(
            nn.MaxPool2d(kernel_size=3, stride=1, padding=1),
            nn.Conv2d(in_channels, pool_proj, kernel_size=1),
            nn.BatchNorm2d(pool_proj),
            nn.ReLU(inplace=True)
        )
 
    def forward(self, x):
        branch1_output = self.branch1(x)
        branch2_output = self.branch2(x)
        branch3_output = self.branch3(x)
        branch4_output = self.branch4(x)
 
        outputs = [branch1_output, branch2_output, branch3_output, branch4_output]
        return torch.cat(outputs, 1)
 
class InceptionV1(nn.Module):
    def __init__(self, num_classes=1000):
        super(InceptionV1, self).__init__()
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3)
        self.maxpool1 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.conv2 = nn.Conv2d(64, 64, kernel_size=1, stride=1, padding=1)
        self.conv3 = nn.Conv2d(64, 192, kernel_size=3, stride=1, padding=1)
        self.maxpool2 = nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
        self.inception3a = inception_block(192, 64, 96, 128, 16, 32, 32)
        self.inception3b = inception_block(256, 128, 128, 192, 32, 96, 64)
        self.maxpool3 = nn.MaxPool2d(3, stride=2)
        self.inception4a = inception_block(480, 192, 96, 208, 16, 48, 64)
        self.inception4b = inception_block(512, 160, 112, 224, 24, 64, 64)
        self.inception4c = inception_block(512, 128, 128, 256, 24, 64, 64)
        self.inception4d = inception_block(512, 112, 144, 288, 32, 64, 64)
        self.inception4e = inception_block(528, 256, 160, 320, 32, 128, 128)
        self.maxpool4 = nn.MaxPool2d(2, stride=2)
        self.inception5a = inception_block(832, 256, 160, 320, 32, 128, 128)
 
        self.inception5b=nn.Sequential(
        inception_block(832, 384, 192, 384, 48, 128, 128),
        nn.AvgPool2d(kernel_size=7,stride=1,padding=0),
        nn.Dropout(0.4)
        )
        # 全连接网络层,用于分类
        self.classifier = nn.Sequential(
        nn.Linear(in_features=1024, out_features=1024),
        nn.ReLU(),
        nn.Linear(in_features=1024, out_features=num_classes),
        nn.Softmax(dim=1)
        )
    def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.maxpool1(x)
        x = self.conv2(x)
        x = F.relu(x)
        x = self.conv3(x)
        x = F.relu(x)
        x = self.maxpool2(x)
        x = self.inception3a(x)
        x = self.inception3b(x)
        x = self.maxpool3(x)
 
        x = self.inception4a(x)
        x = self.inception4b(x)
        x = self.inception4c(x)
        x = self.inception4d(x)
        x = self.inception4e(x)
        x = self.maxpool4(x)
        x = self.inception5a(x)
        x = self.inception5b(x)
        x = torch.flatten(x, start_dim=1)
        x = self.classifier(x)
        return x;
# 定义完成,测试一下
model = InceptionV1(4)
model.to(device)
 
# 统计模型参数量以及其他指标
import torchsummary as summary
summary.summary(model, (3, 224, 224))

3.训练运行

 
# 训练循环
def train(dataloader, model, loss_fn, optimizer):
    size = len(dataloader.dataset)  # 训练集的大小
    num_batches = len(dataloader)  # 批次数目, (size/batch_size,向上取整)
 
    train_loss, train_acc = 0, 0  # 初始化训练损失和正确率
 
    for X, y in dataloader:  # 获取图片及其标签
        X, y = X.to(device), y.to(device)
 
        # 计算预测误差
        pred = model(X)  # 网络输出
        loss = loss_fn(pred, y)  # 计算网络输出和真实值之间的差距,targets为真实值,计算二者差值即为损失
 
        # 反向传播
        optimizer.zero_grad()  # grad属性归零
        loss.backward()  # 反向传播
        optimizer.step()  # 每一步自动更新
 
        # 记录acc与loss
        train_acc += (pred.argmax(1) == y).type(torch.float).sum().item()
        train_loss += loss.item()
 
    train_acc /= size
    train_loss /= num_batches
 
    return train_acc, train_loss
 
 
def test(dataloader, model, loss_fn):
    size = len(dataloader.dataset)  # 测试集的大小
    num_batches = len(dataloader)  # 批次数目
    test_loss, test_acc = 0, 0
 
    # 当不进行训练时,停止梯度更新,节省计算内存消耗
    with torch.no_grad():
        for imgs, target in dataloader:
            imgs, target = imgs.to(device), target.to(device)
 
            # 计算loss
            target_pred = model(imgs)
            loss = loss_fn(target_pred, target)
 
            test_loss += loss.item()
            test_acc += (target_pred.argmax(1) == target).type(torch.float).sum().item()
 
    test_acc /= size
    test_loss /= num_batches
 
    return test_acc, test_loss

输出结果:

 

跑10轮,并保存

 
import copy
 
optimizer = torch.optim.Adam(model.parameters(), lr=1e-4)
loss_fn = nn.CrossEntropyLoss()  # 创建损失函数
 
epochs = 10
 
train_loss = []
train_acc = []
test_loss = []
test_acc = []
 
best_acc = 0  # 设置一个最佳准确率,作为最佳模型的判别指标
 
for epoch in range(epochs):
    # 更新学习率(使用自定义学习率时使用)
    # adjust_learning_rate(optimizer, epoch, learn_rate)
 
    model.train()
    epoch_train_acc, epoch_train_loss = train(train_dl, model, loss_fn, optimizer)
    # scheduler.step() # 更新学习率(调用官方动态学习率接口时使用)
 
    model.eval()
    epoch_test_acc, epoch_test_loss = test(test_dl, model, loss_fn)
 
    # 保存最佳模型到 best_model
    if epoch_test_acc > best_acc:
        best_acc = epoch_test_acc
        best_model = copy.deepcopy(model)
 
    train_acc.append(epoch_train_acc)
    train_loss.append(epoch_train_loss)
    test_acc.append(epoch_test_acc)
    test_loss.append(epoch_test_loss)
 
    # 获取当前的学习率
    lr = optimizer.state_dict()['param_groups'][0]['lr']
 
    template = ('Epoch:{:2d}, Train_acc:{:.1f}%, Train_loss:{:.3f}, Test_acc:{:.1f}%, Test_loss:{:.3f}, Lr:{:.2E}')
    print(template.format(epoch + 1, epoch_train_acc * 100, epoch_train_loss,
                          epoch_test_acc * 100, epoch_test_loss, lr))
 
# 保存最佳模型到文件中
PATH = './best_model.pth'  # 保存的参数文件名
torch.save(model.state_dict(), PATH)
 
print('Done')

输出结果:

 

打印训练记录图:

import matplotlib.pyplot as plt
# 隐藏警告
import warnings
 
warnings.filterwarnings("ignore")  # 忽略警告信息
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
plt.rcParams['figure.dpi'] = 100  # 分辨率
 
epochs_range = range(epochs)
 
plt.figure(figsize=(12, 3))
plt.subplot(1, 2, 1)
 
plt.plot(epochs_range, train_acc, label='Training Accuracy')
plt.plot(epochs_range, test_acc, label='Test Accuracy')
plt.legend(loc='lower right')
plt.title('Training and Validation Accuracy')
 
plt.subplot(1, 2, 2)
plt.plot(epochs_range, train_loss, label='Training Loss')
plt.plot(epochs_range, test_loss, label='Test Loss')
plt.legend(loc='upper right')
plt.title('Training and Validation Loss')
plt.show()

输出结果:

 

代码学习:

①文章开头声明的训练营

②深度学习第J8周:Inception v1算法实战与解析_inceptionv1训练教程-CSDN博客

三、总结

1.遇到问题:

解决:粗心,少了InceptionV1模型

2.遇到问题:

 解决:粗心,训练代码丢了

3.Inception用多路分支并行,采用不同卷积核提取不同大小感受野所代表的特征。同时,通过使用1×1卷积将channel维度进行降维,提取特征后再次回升,虽然看似复杂,但参数量减少。

4.自己的粗心导致很多框架结构,没有复现成功,每次都是很小的错误,希望下次提高警惕。


http://www.kler.cn/a/472622.html

相关文章:

  • (leetcode算法题)2271. 毯子覆盖的最多白色砖块数
  • C++ 复习总结记录三
  • minibatch时,损失如何记录
  • 机器学习之随机森林算法实现和特征重要性排名可视化
  • Three.js 12中利用着色器进行材质加工深度解析
  • Backend - C# asp .net core MVC
  • 制造业该怎么做数据治理?
  • 【免费】2000-2010年各省第二产业就业人数数据
  • HarmonyOS 应用开发实践——基于 `Index` 组件的多语言、主题模式与文件存储管理
  • json报文的序列化与反序列化问题总结(对比fastjson和jackson)
  • QT鼠标、键盘事件
  • JavaAPI.02.包装类与正则表达式
  • 在vue3项目中利用自定义ref实现防抖
  • C++和Python中负数取余结果的区别
  • imageio 图片转mp4 保存mp4
  • 深度学习从入门到实战——卷积神经网络原理解析及其应用
  • js 根据条件判断样式
  • ElasticSearch内存占用率过高怎么办?
  • Java中将特征向量转换为矩阵的实现
  • CentOS 8 系统中添加 4G 大小的swap(交换空间)