NNDL 实验六 卷积神经网络(5)使用预训练resnet18实现CIFAR-10分类

5.5 实践:基于ResNet18网络完成图像分类任务

图像分类(Image Classification)
计算机视觉中的一个基础任务,将图像的语义将不同图像划分到不同类别。
很多任务可以转换为图像分类任务。
比如人脸检测就是判断一个区域内是否有人脸,可以看作一个二分类的图像分类任务。
这里,我们使用的计算机视觉领域的经典数据集:CIFAR-10数据集,网络为ResNet18模型,损失函数为交叉熵损失,优化器为Adam优化器,评价指标为准确率。

5.5.1 数据处理

5.5.1.1 数据集介绍

CIFAR-10数据集包含了10种不同的类别、共60,000张图像,其中每个类别的图像都是6000张,图像大小均为32×3232×32像素。CIFAR-10数据集的示例如图所示。
在这里插入图片描述

  • 数据集:CIFAR-10数据集,
  • 网络:ResNet18模型,
  • 损失函数:交叉熵损失,
  • 优化器:Adam优化器,Adam优化器的介绍参考NNDL第7.2.4.3节。
  • 评价指标:准确率。

5.5.1.2 数据读取

在本实验中,将原始训练集拆分成了train_set、dev_set两个部分,分别包括40 000条和10 000条样本。将data_batch_1到data_batch_4作为训练集,data_batch_5作为验证集,test_batch作为测试集。
最终的数据集构成为:

训练集:40 000条样本。
验证集:10 000条样本。
测试集:10 000条样本。
读取一个batch数据的代码如下所示:

# coding=gbk
# 解压数据集
# 初次运行时将注释取消,以便解压文件
# 如果已经解压过,不需要运行此段代码,否则由于文件已经存在,解压时会报错
import os
import pickle
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.utils.data as data
import torchvision.transforms
from torchvision.transforms import Compose, Resize, Normalize,ToTensor
from torchvision.models import resnet18
import torch.nn.functional as F
import torch.optim as opt
from nndl import RunnerV3, Accuracy
from nndl import plot

def load_cifar10_batch(folder_path, batch_id=1, mode='train'):
    if mode == 'test':
        file_path = os.path.join(folder_path, 'test_batch')
    else:
        file_path = os.path.join(folder_path, 'data_batch_'+str(batch_id))
 
    #加载数据集文件
    with open(file_path, 'rb') as batch_file:
        batch = pickle.load(batch_file, encoding = 'latin1')
 
    imgs = batch['data'].reshape((len(batch['data']),3,32,32)) / 255.
    labels = batch['labels']
 
    return np.array(imgs, dtype='float32'), np.array(labels)
 
imgs_batch, labels_batch = load_cifar10_batch(folder_path='cifar-10-batches-py',
                                                batch_id=1, mode='train')
                                                #打印一下每个batch中X和y的维度
print ("batch of imgs shape: ",imgs_batch.shape, "batch of labels shape: ", labels_batch.shape)

运行结果:

batch of imgs shape:  (10000, 3, 32, 32) batch of labels shape:  (10000,)

可视化观察其中的一张样本图像和对应的标签,代码如下所示:

image, label = imgs_batch[1], labels_batch[1]
print("The label in the picture is {}".format(label))
plt.figure(figsize=(2, 2))
plt.imshow(image.transpose(1,2,0))
plt.savefig('cnn-car.pdf')
plt.show()

运行结果:
在这里插入图片描述

The label in the picture is 9

5.5.1.3 构造Dataset类

构造一个CIFAR10Dataset类,其将继承自paddle.io.DataSet类,可以逐个数据进行处理。代码实现如下:

import torch
from torch.utils.data import Dataset,DataLoader
from torchvision.transforms import transforms

class CIFAR10Dataset(data.Dataset):
    def __init__(self, folder_path='cifar-10-batches-py', mode='train'):
        if mode == 'train':
            #加载batch1-batch4作为训练集
            self.imgs, self.labels = load_cifar10_batch(folder_path=folder_path, batch_id=1, mode='train')
            for i in range(2, 5):
                imgs_batch, labels_batch = load_cifar10_batch(folder_path=folder_path, batch_id=i, mode='train')
                self.imgs, self.labels = np.concatenate([self.imgs, imgs_batch]), np.concatenate([self.labels, labels_batch])
        elif mode == 'dev':
            #加载batch5作为验证集
            self.imgs, self.labels = load_cifar10_batch(folder_path=folder_path, batch_id=5, mode='dev')
        elif mode == 'test':
            #加载测试集
            self.imgs, self.labels = load_cifar10_batch(folder_path=folder_path, mode='test')
        self.transforms = Compose([ToTensor(),Normalize(mean=[0.4914, 0.4822, 0.4465],
                                                                   std=[0.2023, 0.1994, 0.2010])])
    def __getitem__(self, idx):
        img, label = self.imgs[idx], self.labels[idx]
        img=img.transpose(1, 2, 0)
        img = self.transforms(img).cuda()
        label=torch.tensor(label).cuda()
        return img, label
 
    def __len__(self):
        return len(self.imgs)
 
torch.manual_seed(100)
train_dataset = CIFAR10Dataset(folder_path='cifar-10-batches-py', mode='train')
dev_dataset = CIFAR10Dataset(folder_path='cifar-10-batches-py', mode='dev')
test_dataset = CIFAR10Dataset(folder_path='cifar-10-batches-py', mode='test')

5.5.2 模型构建

使用Resnet18进行图像分类实验。

from torchvision.models import resnet18
 
resnet18_model = resnet18()

什么是“预训练模型”?什么是“迁移学习”?(必做)

预训练模型:首先,在一个原始任务上预先训练一个初始模型,然后在目标任务上使用该模型,针对目标任务的特性,对该初始模型进行精调,从而达到提高目标任务的目的。
在本质上,这是一种迁移学习的方法,在自己的目标任务上使用别人训练好的模型。对于文本语言来说,是有天然的标注特征的存在的,原因就在于文本可以根据之前的输入词语进行预测,而且文本大多是有很多词语,所以就可以构成很大的预训练数据,进而可以自监督(不是无监督,因为词语学习过程是依据之前词语的输出的,所以应该是自监督学习)的预训练。
BERT是一个预训练的模型
那么什么是预训练呢?举例子进行简单的介绍
假设已有A训练集,先用A对网络进行预训练,在A任务上学会网络参数,然后保存以备后用,当来一个新的任务B,采取相同的网络结构,网络参数初始化的时候可以加载A学习好的参数,其他的高层参数随机初始化,之后用B任务的训练数据来训练网络,当加载的参数保持不变时,称为"frozen",当加载的参数随着B任务的训练进行不断的改变,称为“fine-tuning”,即更好地把参数进行调整使得更适合当前的B任务
(1)优点
BERT是截止至2018年10月的最新的的state of the art模型,通过预训练和精调可以解决11项NLP的任务。使用的是Transformer,相对于rnn而言更加高效、能捕捉更长距离的依赖。与之前的预训练模型相比,它捕捉到的是真正意义上的bidirectional context信息
(2)缺点
作者在文中主要提到的就是MLM预训练时的mask问题:
1)[MASK]标记在实际预测中不会出现,训练时用过多[MASK]影响模型表现;
2)每个batch只有15%的token被预测,所以BERT收敛得比left-to-right模型要慢(它们会预测每个token)
在这里插入图片描述
迁移学习:迁移学习(Transfer Learning)是机器学习中的一个名词,是指一种学习对另一种学习的影响,或习得的经验对完成其它活动的影响。迁移广泛存在于各种知识、技能与社会规范的学习中。
迁移学习专注于存储已有问题的解决模型,并将其利用在其他不同但相关问题上。比如说,用来辨识汽车的知识(或者是模型)也可以被用来提升识别卡车的能力。计算机领域的迁移学习和心理学常常提到的学习迁移在概念上有一定关系,但是两个领域在学术上的关系非常有限。
从技术上来说,迁移学习只是一种学习的方式,一种基于以前学习的基础上继续学习的方式。但现在大家讲的最多的还是基于神经网络基础之上的迁移学习。这里我们以卷积神经网络(CNN)为例,做一个简单的介绍。
在这里插入图片描述
在CNN中,我们反复的将一张图片的局部区域卷积,减少面积,并提升通道数。
为什么卷积神经网络可以工作?最核心的原因在于:局部一致性。一只猫爪子,你把它放在图片的左下角是一只猫爪子,你把它放到右上角也是一只猫爪子。

比较“使用预训练模型”和“不使用预训练模型”的效果。(必做)

resnet = models.resnet18(pretrained=True)
resnet = models.resnet18(pretrained=False)

5.5.3 模型训练

复用RunnerV3类,实例化RunnerV3类,并传入训练配置。
使用训练集和验证集进行模型训练,共训练30个epoch。
在实验中,保存准确率最高的模型作为最佳模型。代码实现如下:

# 指定运行设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(device)
# 学习率大小
lr = 0.001
# 批次大小
batch_size = 64
# 加载数据
 
train_loader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
dev_loader = data.DataLoader(dev_dataset, batch_size=batch_size)
test_loader = data.DataLoader(test_dataset, batch_size=batch_size)
 
# 定义网络
model = resnet18_model
model = model.to(device)
# 定义优化器,这里使用Adam优化器以及l2正则化策略,相关内容在7.3.3.2和7.6.2中会进行详细介绍
optimizer = opt.Adam(lr=lr, params=model.parameters(), weight_decay=0.005)
# 定义损失函数
loss_fn = F.cross_entropy
# 定义评价指标
metric =Accuracy(is_logist=True)
# 实例化RunnerV3
 
strat_time = time.time()
runner = RunnerV3(model, optimizer, loss_fn, metric)
 
# 启动训练
log_steps = 1000
eval_steps = 1000
runner.train(train_loader, dev_loader, num_epochs=30, log_steps=log_steps,
             eval_steps=eval_steps, save_path="best_model.pdparams")

运行结果:

[Train] epoch: 0/30, step: 0/18750, loss: 11.94789
[Train] epoch: 1/30, step: 1000/18750, loss: 1.08444
[Evaluate]  dev score: 0.56800, dev loss: 1.25183
[Evaluate] best accuracy performence has been updated: 0.00000 --> 0.56800
[Train] epoch: 3/30, step: 2000/18750, loss: 0.93303
[Evaluate]  dev score: 0.56870, dev loss: 1.24630
[Evaluate] best accuracy performence has been updated: 0.56800 --> 0.56870
[Train] epoch: 4/30, step: 3000/18750, loss: 1.10861
[Evaluate]  dev score: 0.66820, dev loss: 0.95978
[Evaluate] best accuracy performence has been updated: 0.56870 --> 0.66820
[Train] epoch: 6/30, step: 4000/18750, loss: 0.82029
[Evaluate]  dev score: 0.67690, dev loss: 0.95927
[Evaluate] best accuracy performence has been updated: 0.66820 --> 0.67690
[Train] epoch: 8/30, step: 5000/18750, loss: 0.69778
[Evaluate]  dev score: 0.71650, dev loss: 0.84400
[Evaluate] best accuracy performence has been updated: 0.67690 --> 0.71650
[Train] epoch: 9/30, step: 6000/18750, loss: 0.82190
[Evaluate]  dev score: 0.68230, dev loss: 0.95333
[Train] epoch: 11/30, step: 7000/18750, loss: 0.56318
[Evaluate]  dev score: 0.71430, dev loss: 0.84213
[Train] epoch: 12/30, step: 8000/18750, loss: 0.78213
[Evaluate]  dev score: 0.71240, dev loss: 0.85661
[Train] epoch: 14/30, step: 9000/18750, loss: 0.56063
[Evaluate]  dev score: 0.69230, dev loss: 0.93725
[Train] epoch: 16/30, step: 10000/18750, loss: 0.57713
[Evaluate]  dev score: 0.72350, dev loss: 0.82346
[Evaluate] best accuracy performence has been updated: 0.71650 --> 0.72350
[Train] epoch: 17/30, step: 11000/18750, loss: 0.50005
[Evaluate]  dev score: 0.70620, dev loss: 0.87751
[Train] epoch: 19/30, step: 12000/18750, loss: 0.55691
[Evaluate]  dev score: 0.71680, dev loss: 0.86619
[Train] epoch: 20/30, step: 13000/18750, loss: 0.77892
[Evaluate]  dev score: 0.71880, dev loss: 0.86092
[Train] epoch: 22/30, step: 14000/18750, loss: 0.48677
[Evaluate]  dev score: 0.74160, dev loss: 0.79807
[Evaluate] best accuracy performence has been updated: 0.72350 --> 0.74160
[Train] epoch: 24/30, step: 15000/18750, loss: 0.50528
[Evaluate]  dev score: 0.72300, dev loss: 0.86205
[Train] epoch: 25/30, step: 16000/18750, loss: 0.59597
[Evaluate]  dev score: 0.70250, dev loss: 0.91597
[Train] epoch: 27/30, step: 17000/18750, loss: 0.45800
[Evaluate]  dev score: 0.72890, dev loss: 0.84346
[Train] epoch: 28/30, step: 18000/18750, loss: 0.49239
[Evaluate]  dev score: 0.71990, dev loss: 0.83751
[Evaluate]  dev score: 0.72280, dev loss: 0.85737
[Train] Training done!

 [Test] accuracy/loss: 0.7262/0.8333

可视化观察训练集与验证集的准确率及损失变化情况

plot(runner, fig_name='cnn-loss4.pdf')

在这里插入图片描述
在本实验中,使用了第7章中介绍的Adam优化器进行网络优化,如果使用SGD优化器,会造成过拟合的现象,在验证集上无法得到很好的收敛效果。

RunnerV3类:

class RunnerV3(object):
    def __init__(self, model, optimizer, loss_fn, metric, **kwargs):
        self.model = model
        self.optimizer = optimizer
        self.loss_fn = loss_fn
        self.metric = metric  # 只用于计算评价指标
 
        # 记录训练过程中的评价指标变化情况
        self.dev_scores = []
 
        # 记录训练过程中的损失函数变化情况
        self.train_epoch_losses = []  # 一个epoch记录一次loss
        self.train_step_losses = []  # 一个step记录一次loss
        self.dev_losses = []
 
        # 记录全局最优指标
        self.best_score = 0
 
    def train(self, train_loader, dev_loader=None, **kwargs):
        # 将模型切换为训练模式
        self.model.train()
 
        # 传入训练轮数,如果没有传入值则默认为0
        num_epochs = kwargs.get("num_epochs", 0)
        # 传入log打印频率,如果没有传入值则默认为100
        log_steps = kwargs.get("log_steps", 100)
        # 评价频率
        eval_steps = kwargs.get("eval_steps", 0)
 
        # 传入模型保存路径,如果没有传入值则默认为"best_model.pdparams"
        save_path = kwargs.get("save_path", "best_model.pdparams")
 
        custom_print_log = kwargs.get("custom_print_log", None)
 
        # 训练总的步数
        num_training_steps = num_epochs * len(train_loader)
 
        if eval_steps:
            if self.metric is None:
                raise RuntimeError('Error: Metric can not be None!')
            if dev_loader is None:
                raise RuntimeError('Error: dev_loader can not be None!')
 
        # 运行的step数目
        global_step = 0
 
        # 进行num_epochs轮训练
        for epoch in range(num_epochs):
            # 用于统计训练集的损失
            total_loss = 0
            for step, data in enumerate(train_loader):
                X, y = data
                # 获取模型预测
                logits = self.model(X)
                loss = self.loss_fn(logits, y.long())  # 默认求mean
                total_loss += loss
 
                # 训练过程中,每个step的loss进行保存
                self.train_step_losses.append((global_step, loss.item()))
 
                if log_steps and global_step % log_steps == 0:
                    print(
                        f"[Train] epoch: {epoch}/{num_epochs}, step: {global_step}/{num_training_steps}, loss: {loss.item():.5f}")
 
                # 梯度反向传播,计算每个参数的梯度值
                loss.backward()
 
                if custom_print_log:
                    custom_print_log(self)
 
                # 小批量梯度下降进行参数更新
                self.optimizer.step()
                # 梯度归零
                self.optimizer.zero_grad()
 
                # 判断是否需要评价
                if eval_steps > 0 and global_step > 0 and \
                        (global_step % eval_steps == 0 or global_step == (num_training_steps - 1)):
 
                    dev_score, dev_loss = self.evaluate(dev_loader, global_step=global_step)
                    print(f"[Evaluate]  dev score: {dev_score:.5f}, dev loss: {dev_loss:.5f}")
 
                    # 将模型切换为训练模式
                    self.model.train()
 
                    # 如果当前指标为最优指标,保存该模型
                    if dev_score > self.best_score:
                        self.save_model(save_path)
                        print(
                            f"[Evaluate] best accuracy performence has been updated: {self.best_score:.5f} --> {dev_score:.5f}")
                        self.best_score = dev_score
 
                global_step += 1
 
            # 当前epoch 训练loss累计值
            trn_loss = (total_loss / len(train_loader)).item()
            # epoch粒度的训练loss保存
            self.train_epoch_losses.append(trn_loss)
 
        print("[Train] Training done!")
 
    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def evaluate(self, dev_loader, **kwargs):
        assert self.metric is not None
 
        # 将模型设置为评估模式
        self.model.eval()
 
        global_step = kwargs.get("global_step", -1)
 
        # 用于统计训练集的损失
        total_loss = 0
 
        # 重置评价
        self.metric.reset()
 
        # 遍历验证集每个批次
        for batch_id, data in enumerate(dev_loader):
            X, y = data
 
            # 计算模型输出
            logits = self.model(X)
 
            # 计算损失函数
            loss = self.loss_fn(logits, y.long()).item()
            # 累积损失
            total_loss += loss
 
            # 累积评价
            self.metric.update(logits, y)
 
        dev_loss = (total_loss / len(dev_loader))
        dev_score = self.metric.accumulate()
 
        # 记录验证集loss
        if global_step != -1:
            self.dev_losses.append((global_step, dev_loss))
            self.dev_scores.append(dev_score)
 
        return dev_score, dev_loss
 
    # 模型评估阶段,使用'paddle.no_grad()'控制不计算和存储梯度
    @torch.no_grad()
    def predict(self, x, **kwargs):
        # 将模型设置为评估模式
        self.model.eval()
        # 运行模型前向计算,得到预测值
        logits = self.model(x)
        return logits
 
    def save_model(self, save_path):
        torch.save(self.model.state_dict(), save_path)
 
    def load_model(self, model_path):
        state_dict = torch.load(model_path)
        self.model.load_state_dict(state_dict)

Accuracy类:

class Accuracy():
    def __init__(self, is_logist=True):
        """
        输入:
           - is_logist: outputs是logist还是激活后的值
        """
 
        # 用于统计正确的样本个数
        self.num_correct = 0
        # 用于统计样本的总数
        self.num_count = 0
 
        self.is_logist = is_logist
 
    def update(self, outputs, labels):
        """
        输入:
           - outputs: 预测值, shape=[N,class_num]
           - labels: 标签值, shape=[N,1]
        """
 
        # 判断是二分类任务还是多分类任务,shape[1]=1时为二分类任务,shape[1]>1时为多分类任务
        if outputs.shape[1] == 1:  # 二分类
            outputs = torch.squeeze(outputs, dim=-1)
            if self.is_logist:
                # logist判断是否大于0
                preds = torch.tensor((outputs >= 0), dtype=torch.float32)
            else:
                # 如果不是logist,判断每个概率值是否大于0.5,当大于0.5时,类别为1,否则类别为0
                preds = torch.tensor((outputs >= 0.5), dtype=torch.float32)
        else:
            # 多分类时,使用'torch.argmax'计算最大元素索引作为类别
            preds = torch.argmax(outputs, dim=1)
 
        # 获取本批数据中预测正确的样本个数
        labels = torch.squeeze(labels, dim=-1)
        batch_correct = torch.sum(torch.tensor(preds == labels, dtype=torch.float32)).cpu().numpy()
        batch_count = len(labels)
 
        # 更新num_correct 和 num_count
        self.num_correct += batch_correct
        self.num_count += batch_count
 
    def accumulate(self):
        # 使用累计的数据,计算总的指标
        if self.num_count == 0:
            return 0
        return self.num_correct / self.num_count
 
    def reset(self):
        # 重置正确的数目和总数
        self.num_correct = 0
        self.num_count = 0
 
    def name(self):
        return "Accuracy"

plot函数:

# 可视化
def plot(runner, fig_name):
    plt.figure(figsize=(10, 5))
 
    plt.subplot(1, 2, 1)
    train_items = runner.train_step_losses[::30]
    train_steps = [x[0] for x in train_items]
    train_losses = [x[1] for x in train_items]
 
    plt.plot(train_steps, train_losses, color='#8E004D', label="Train loss")
    if runner.dev_losses[0][0] != -1:
        dev_steps = [x[0] for x in runner.dev_losses]
        dev_losses = [x[1] for x in runner.dev_losses]
        plt.plot(dev_steps, dev_losses, color='#E20079', linestyle='--', label="Dev loss")
    # 绘制坐标轴和图例
    plt.ylabel("loss", fontsize='x-large')
    plt.xlabel("step", fontsize='x-large')
    plt.legend(loc='upper right', fontsize='x-large')
 
    plt.subplot(1, 2, 2)
    # 绘制评价准确率变化曲线
    if runner.dev_losses[0][0] != -1:
        plt.plot(dev_steps, runner.dev_scores,
                 color='#E20079', linestyle="--", label="Dev accuracy")
    else:
        plt.plot(list(range(len(runner.dev_scores))), runner.dev_scores,
                 color='#E20079', linestyle="--", label="Dev accuracy")
    # 绘制坐标轴和图例
    plt.ylabel("score", fontsize='x-large')
    plt.xlabel("step", fontsize='x-large')
    plt.legend(loc='lower right', fontsize='x-large')
 
    plt.savefig(fig_name)
    plt.show()

5.5.4 模型评价

使用测试数据对在训练过程中保存的最佳模型进行评价,观察模型在测试集上的准确率以及损失情况。代码实现如下:

# 加载最优模型
runner.load_model('best_model.pdparams')
# 模型评价
score, loss = runner.evaluate(test_loader)
print("[Test] accuracy/loss: {:.4f}/{:.4f}".format(score, loss))

运行结果:

[Test] accuracy/loss: 0.7945/0.4730

5.5.5 模型预测

同样地,也可以使用保存好的模型,对测试集中的数据进行模型预测,观察模型效果,具体代码实现如下:

#获取测试集中的一个batch的数据
X, label = next(iter(test_loader))
logits = runner.predict(X)
#多分类,使用softmax计算预测概率
pred = F.softmax(logits)
#获取概率最大的类别
pred_class = torch.argmax(pred[2]).cpu().numpy()
print(pred_class)
print(label)
label = label[2].cpu().numpy()
#输出真实类别与预测类别
print("The true category is {} and the predicted category is {}".format(label, pred_class))
#可视化图片
plt.figure(figsize=(2, 2))
imgs, labels = load_cifar10_batch(folder_path='cifar-10-batches-py', mode='test')
plt.imshow(imgs[2].transpose(1,2,0))
plt.savefig('cnn-test-vis.pdf')

运行结果:

 The true category is 8 and the predicted category is 8

思考题

1.阅读《Deep Residual Learning for Image Recognition》,了解5种深度的ResNet(18,34,50,101和152),并简单谈谈自己的看法。(选做)

1.resnet意义
随着网络的加深,出现了训练集准确率下降的现象,即“网络退化”。
深度残差网络,它允许网络尽可能的加深。
2.resnet结构
在这里插入图片描述
这两种结构分别针对resnet34(左图)和resnet50/101/152(右图),一般称整个结构为一个”building block“。其中右图又称为”bottleneck design”,目的一目了然,就是为了降低参数的数目。
看右图,输入是一个3×3×256的特征,第一个步骤用64个1x1的卷积把256维channel降到64维,然后在最后通过1x1卷积恢复到256个channel,整体上用的参数数目:1x1x256x64 + 3x3x64x64 + 1x1x64x256 = 69632,而不使用bottleneck的话参考左图,输入假设是3x3x256,第一步经过256个卷积核3×3×256,第二部再经过256个卷积核3×3×256。所以参数数目: 3x3x256x256x2 = 1179648,差了16.94倍。
对于常规resnet,可以用于34层或者更少的网络中,对于bottleneck design的resnet通常用于更深的如101这样的网络中,目的是减少计算和参数量(实用目的)。

resnet提出了两种mapping:
一种是identity mapping,指的就是上图中”弯弯的曲线”
另一种residual mapping,指的就是除了”弯弯的曲线“那部分,所以最后的输出是
在这里插入图片描述
identity mapping顾名思义,就是指本身,也就是公式中的x,而residual mapping指的是“差”,也就是y−x,所以残差指的就是F(x)部分。

我们先来看一个实验,对常规的网络(plain network,也称平原网络)直接堆叠很多层次,经对图像识别结果进行检验,训练集、测试集的误差结果如下图:
在这里插入图片描述
上面两个图可以看出,在网络很深的时候(56层相比20层),模型效果却越来越差了(误差率越高),并不是网络越深越好。

通过实验可以发现:随着网络层级的不断增加,模型精度不断得到提升,而当网络层级增加到一定的数目以后,训练精度和测试精度迅速下降,这说明当网络变得很深以后,深度网络就变得更加难以训练了。
下图是一个简单神经网络图,由输入层、隐含层、输出层构成:
在这里插入图片描述
这里把ResNet50和ResNet101特别提出,主要因为它们的出镜率很高,所以需要做特别的说明。给出了它们具体的结构:
在这里插入图片描述
101层网络仅仅指卷积或者全连接层,而激活层或者Pooling层并没有计算在内; 这里我们关注50-layer和101-layer这两列,可以发现,它们唯一的不同在于conv4_x,ResNet50有6个block,而ResNet101有23个block,查了17个block,也就是17 x 3 = 51层。

2.用自己的话简单评价:LeNet、AlexNet、VGG、GoogLeNet、ResNet(选做)

在这里插入图片描述
Le-Net
Lenet也称Lenet-5,共5个隐藏层(不考虑磁化层),网络结构为:
在这里插入图片描述

  • Conv(55,6,1)+Conv(55,16,1)+Conv(5*5,120,1)+FC(84)+FC(10)
  • 或Conv(55,6,1)+Conv(55,16)+FC(120)+FC(84)+FC(10)

AlexNet
提出背景:解决Lenet识别大尺寸图片进行的效果不尽人意的问题

与LeNet相比,AlexNet具有更深的网络结构,共8个隐藏层,包含5层卷积和3层全连接,网络结构为:
在这里插入图片描述 Conv(1111,96,2)+Conv(55,256,1)+Conv(33,384,1)+Conv(33,384,1)+Conv(3*3,256,1)+FC(4096)+FC(4096)+FC(1000)
同时使用了如下方法改进模型的训练过程:

  • 数据增广:深度学习中常用的一种处理方式,通过对训练随机加一些变化,比如平移、缩放、裁剪、旋转、翻转或者增减亮度等,产生一系列跟原始图片相似但又不完全相同的样本,从而扩大训练数据集。通过这种方式,可以随机改变训练样本,避免模型过度依赖于某些属性,能从一定程度上抑制过拟合。(过拟合:算法模型比数据模型复杂,解决是降低算法模型复杂度,例如dropout,L1,L2正则化,或者增加数据复杂度,例如获得更多数据。当然,Early stopping也可以,不让学习过度,集成学习也可以,毕竟解决一切)
  • 使用Dropout抑制过拟合
  • 使用ReLU激活函数减少梯度消失现象(梯度消失和梯度爆炸):在- AlexNet之前,神经网络一般都使用sigmoid或tanh作为激活函数,这类函数在自变量非常大或者非常小时,函数输出基本不变,称之为饱和函数。为了提高训练速度,AlexNet使用了修正线性函数ReLU,它是一种非饱和函数,与 sigmoid 和tanh 函数相比,ReLU分片的线性结构实现了非线性结构的表达能力,梯度消失现象相对较弱,有助于训练更深层的网络。
  • 使用GPU训练。与CPU不同的是,GPU转为执行复杂的数学和几何计算而设计,AlexNet使用了2个GPU来提升速度,分别放置一半卷积核。
    局部响应归一化。AlexNet使用局部响应归一化技巧,将ImageNet上的top-1与top-5错误率分别减少了1.4%和1.2%。
  • 重叠池化层。与不重叠池化层相比,重叠池化层有助于缓解过拟合,使得AlexNet的top-1和top-5错误率分别降低了0.4%和0.3%。

VggNet
提出背景:alexNet虽然效果好,但是没有给出深度神经网络的设计方向。即,如何把网络做到更深。

在论文中有VGG-11,VGG-13,VGG-16,VGG-19的实验比较,VGG-16的效果最佳,这里给出网络结构。

VGG11:Conv(33,64,1)1+Conv(33,128,1)1+Conv(33,256,1)2+Conv(33,512,1)2+Conv(33,512,1)2+FC(4096)+FC(4096)+FC(1000)
VGG13:Conv(3
3,64,1)2+Conv(33,128,1)2+Conv(33,256,1)2+Conv(33,512,1)2+Conv(33,512,1)2+FC(4096)+FC(4096)+FC(1000)
VGG16:Conv(3
3,64,1)2+Conv(33,128,1)2+Conv(33,256,1)3+Conv(33,512,1)3+Conv(33,512,1)3+FC(4096)+FC(4096)+FC(1000)
VGG19:Conv(3
3,64,1)2+Conv(33,128,1)2+Conv(33,256,1)4+Conv(33,512,1)4+Conv(33,512,1)4+FC(4096)+FC(4096)+FC(1000)
vggnet严格使用3
3小尺寸卷积和池化层构造深度CNN,取得较好的效果。小卷积能减少参数,方便堆叠卷积层来增加深度(加深了网络,减少了卷积)。即vggnet=更深的Alex net+conv(3
3)

googlenet
背景:alexNet虽然效果好,但是没有给出深度神经网络的设计方向。即,如何把网络做到更深。
googlenet设计了inception结构来降低通道数,减少计算复杂度,其中inception结构包括以下几种
在这里插入图片描述

  • inception v1(使用1*1卷积降低通道数)
  • inception v2=inception v1+3*3(学习自vggnet)+BN
  • inception v3=inception v2+n1+1n(将33变成13+3*1)
  • googlenet:Alex net+inception=conv1+inception9+FC*1

Resnet
提出背景:alexNet虽然效果好,但是没有给出深度神经网络的设计方向。即,如何把网络做到更深。
Resnet从避免梯度消失或爆炸的角度,使用残差连接结构使网络可以更深,共5个版本
在这里插入图片描述

总结

使用思维导图全面总结CNN
在这里插入图片描述

参考文章

https://blog.csdn.net/gaoluan6052
https://blog.csdn.net/ZhangJingHuaJYO/article/details/124617277
https://blog.csdn.net/qq_42951560/article/details/110244616
https://blog.csdn.net/qq_26413875/article/details/100917690
https://blog.csdn.net/Sept_Oct/article/details/117447858
https://blog.csdn.net/pingguolou/article/details/126413829

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值