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

深度学习实战——卷积神经网络CNN在图片识别中的应用以及原理

一、前言

随着人工智能和深度学习技术的迅猛发展,卷积神经网络(Convolutional Neural Networks,CNN)已经成为计算机视觉领域中最为重要的工具之一。无论是在图像分类、目标检测,还是在人脸识别、自动驾驶等应用中,CNN都展现出了其卓越的表现和广泛的应用前景。

本文旨在通过深入的实战案例,带领读者一步步理解并掌握CNN的核心原理及其在图片识别中的应用。我们将通过实际操作和代码演示,帮助读者将理论与实践相结合,全面提升在深度学习方向上的技能,为解决复杂的图像处理问题打下坚实的基础。

二、实战

2.1 下载Jupyter Notebook

# 切换成你想要的环境,这里我是默认base环境。
conda activate base
# 生成notebook的配置
jupyter notebook --generate-config
# 把这个配置打开并调整成你需要打开的默认文件 c.NotebookApp.notebook_dir = '/path/to/your/directory'
vim ~/.jupyter/jupyter_notebook_config.py
jupyter notebook

在这里插入图片描述

2.2 导入需要用到的工具包

# os 模块提供了一系列与操作系统交互的功能
import os
#  Python 中非常流行的绘图库 Matplotlib 的一个子模块,用于创建各种类型的图表和可视化。
import matplotlib.pyplot as plt
# 是一个 IPython 魔法命令(magic command),通常用于 Jupyter Notebook 中。它的作用是使得 Matplotlib 绘制的图表直接嵌入在 Notebook 的输出单元中,而不是弹出一个单独的窗口显示图表。
%matplotlib inline
# NumPy 是用于数值计算的强大库,主要用于处理多维数组、矩阵操作,以及提供了大量数学函数和工具,特别适合科学计算和数据处理。
import numpy as np
# torch 是一个用于深度学习的开源框架,由 Facebook 开发。PyTorch 具有强大的张量运算能力和自动微分功能,并且广泛应用于构建和训练神经网络模型,特别是在研究和生产中的深度学习任务中使用非常流行。
import torch
# 构建神经网络的工具
from torch import nn
# 导入 PyTorch 的优化器模块 torch.optim。该模块包含了多种优化算法,用于训练神经网络时更新模型的参数。常见的优化器包括随机梯度下降(SGD)、Adam、RMSprop 等。
import torch.optim as optim
# 它是 PyTorch 生态系统的一部分,专门为计算机视觉任务提供了便捷的工具和数据集。Torchvision 包含了预训练模型、数据集、数据预处理和数据增强等常用功能
import torchvision
# transforms 图像处理与数据增强模块,提供如裁剪、翻转、旋转、缩放等操作。
# models 提供多种经典的卷积神经网络架构,如 ResNet、VGG、AlexNet、MobileNet 等,且支持加载预训练模型。
# datasets 常见的数据集加载模块,支持的数据集包括 CIFAR-10、MNIST、COCO、ImageNet 等。 
from torchvision import transforms, models, datasets
# imageio 是一个用于读取和写入图像、视频、GIF 和其他多媒体文件的 Python 库,支持多种格式(如 PNG、JPEG、TIFF、GIF、MP4 等),并且具有简单易用的接口。
import imageio
# 提供时间相关的功能,如获取当前时间、延迟执行等。
import time
# 用于发出警告消息,通常用于提示用户程序的潜在问题或使用不推荐的功能。
import warnings
# 提供生成随机数的功能,包括整型、浮点型随机数以及随机选择等。
import random
# 提供对 Python 解释器和环境的访问,包括系统参数、模块路径等。
import sys
# 提供浅拷贝和深拷贝的功能,用于复制对象。
import copy
# 用于处理 JSON 数据的编码和解码。
import json
# 提供对图像的操作功能,包括打开、保存、转换图像格式等。PIL(Python Imaging Library)现在被 Pillow 取代,Pillow 是 PIL 的一个分支并提供了相同的接口。
from PIL import Image

2.3 数据读取与预处理操作

# 获取当前工作目录
current_directory = os.getcwd()
# 使用相对路径拼接文件路径
relative_path = 'flower_data/'
data_dir = os.path.join(current_directory, relative_path)
train_dir = data_dir + '/train'
valid_dir = data_dir + '/valid'

2.4 制作好数据源

  • data_transforms中指定了所有图像预处理操作
  • ImageFolder假设所有的文件按文件夹保存好,每个文件夹下面存贮同一类别的图片,文件夹的名字为分类的名字。
data_transforms = {
    'train': transforms.Compose([transforms.RandomRotation(45),#随机旋转,-45到45度之间随机选
        transforms.CenterCrop(224),#从中心开始裁剪
        transforms.RandomHorizontalFlip(p=0.5),#随机水平翻转 选择一个概率概率
        transforms.RandomVerticalFlip(p=0.5),#随机垂直翻转
        transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),#参数1为亮度,参数2为对比度,参数3为饱和度,参数4为色相
        transforms.RandomGrayscale(p=0.025),#概率转换成灰度率,3通道就是R=G=B
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])#均值,标准差
    ]),
    'valid': transforms.Compose([transforms.Resize(256),
        transforms.CenterCrop(224),
        transforms.ToTensor(),
        transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}
batch_size = 8

image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x), data_transforms[x]) for x in ['train', 'valid']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=batch_size, shuffle=True) for x in ['train', 'valid']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'valid']}
class_names = image_datasets['train'].classes

可以看看他们的结果集:

在这里插入图片描述

2.5 读取标签对应的实际名字

current_directory = os.getcwd()
relative_path = 'cat_to_name.json'
file_path = os.path.join(current_directory, relative_path)

with open(file_path, 'r') as f:
    cat_to_name = json.load(f)

2.6 展示下数据

  • 注意tensor的数据需要转换成numpy的格式,而且还需要还原回标准化的结果。
def im_convert(tensor):
    """ 将 Tensor 转换为可展示的图像 """
    # 将 Tensor 转到 CPU 并复制一份,同时切断与计算图的关系
    image = tensor.to("cpu").clone().detach()
    # 转换为 NumPy 数组并去掉批次维度
    image = image.numpy().squeeze()
    # 转置维度以匹配 (H, W, C) 格式
    image = image.transpose(1,2,0)
    # 反归一化图像
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))
    # 将像素值限制在 [0, 1] 之间
    image = image.clip(0, 1)
    return image

为了创建一个包含 8 张图像(2 行 4 列)的网格,显示从 dataloaders[‘valid’] 中获取的图像, 并在每个图像上方显示相应的标题。这是常用于深度学习中的数据可视化步骤,比如在 PyTorch 中查看图像和对应的分类标签。

# 创建一个图像,大小为 20x12 英寸
fig=plt.figure(figsize=(20, 12))
# 设置列数为 4
columns = 4
# 设置行数为 2
rows = 2
# 获取数据加载器的迭代器
dataiter = iter(dataloaders['valid'])
# 获取下一批数据 (inputs 是图像, classes 是对应的标签)
inputs, classes = dataiter.next()

# 共8张图片
for idx in range (columns*rows):
    # 创建子图
    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])
    # 设置子图的标题为类别名称
    ax.set_title(cat_to_name[str(int(class_names[classes[idx]]))])
    # 显示图像
    plt.imshow(im_convert(inputs[idx]))
# 显示整个图像
plt.show()

跑一下结果:
在这里插入图片描述

2.7 加载models中提供的模型,并且直接用训练好的权重当做初始化参数

  • 第一次执行需要下载,可能会比较慢,我会提供给大家一份下载好的,可以直接放到相应路径
# 可选的比较多 ['resnet', 'alexnet', 'vgg', 'squeezenet', 'densenet', 'inception']
model_name = 'resnet'  
# 是否用人家训练好的特征来做
feature_extract = True 
# 是否用GPU训练
train_on_gpu = torch.cuda.is_available()

if not train_on_gpu:
    print('CUDA is not available. Training on CPU ...')
else:
    print('CUDA is available! Training on GPU ...')
    
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

set_parameter_requires_grad 函数用于设置模型的参数是否需要计算梯度,通常用于迁移学习中的 特征提取(feature extraction)。如果 feature_extracting 参数为 True,函数会将模型的所有参数的 requires_grad 属性设为 False,意味着这些参数在反向传播时不会被更新。

def set_parameter_requires_grad(model, feature_extracting):
    if feature_extracting:
        for param in model.parameters():
            param.requires_grad = False

使用场景:

当你做迁移学习时,如果你只想更新模型的最后几层,而保持其他层的权重不变,可以使用这个函数将不需要更新的层冻结。例如在使用预训练的卷积神经网络(如 ResNet)进行图像分类时,常会冻结卷积层,只更新最后的全连接层。

老周这里在 CPU 上训练深度学习模型,尤其是像 ResNet-152 这样的大型模型,速度会非常慢,因为 ResNet-152 是一个非常深的网络,有 152 层卷积层和残差块,非常适合在 GPU 上运行。而 CPU 在处理深度神经网络时,特别是在大型数据集和复杂模型上,性能会明显下降。我这里选择 ResNet-18。

  • 参数量:约 11M 参数。
  • 特点:相比 ResNet-152,ResNet-18 要浅很多,只有 18 层,但依然保留了 ResNet 的优点——残差连接结构。如果你需要一个比 ResNet-152 更轻量的网络,并且愿意牺牲部分性能,ResNet-18 是一个不错的选择。
  • 使用场景:小型数据集或需要在 CPU 上训练的场景。
model_ft = models.resnet18()
model_ft

在这里插入图片描述
2.8 参考pytorch官网例子

根据指定的模型名称初始化不同的预训练模型,同时可以选择是否进行特征提取(即冻结部分参数)和更改最后的分类层,以适应不同的分类任务。这个函数涵盖了多种经典的卷积神经网络架构,比如 ResNet、AlexNet、VGG、SqueezeNet、DenseNet 和 Inception。

def initialize_model(model_name, num_classes, feature_extract, use_pretrained=True):
    # 选择合适的模型,不同模型的初始化方法稍微有点区别
    model_ft = None
    input_size = 0

    # Resnet152
    if model_name == "resnet":
        model_ft = models.resnet152(pretrained=use_pretrained)
        set_parameter_requires_grad(model_ft, feature_extract)
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Sequential(nn.Linear(num_ftrs, 102),
                                   nn.LogSoftmax(dim=1))
        input_size = 224
    # Alexnet
    elif model_name == "alexnet":
        model_ft = models.alexnet(pretrained=use_pretrained)
        set_parameter_requires_grad(model_ft, feature_extract)
        num_ftrs = model_ft.classifier[6].in_features
        model_ft.classifier[6] = nn.Linear(num_ftrs,num_classes)
        input_size = 224
    # VGG11_bn
    elif model_name == "vgg":
        model_ft = models.vgg16(pretrained=use_pretrained)
        set_parameter_requires_grad(model_ft, feature_extract)
        num_ftrs = model_ft.classifier[6].in_features
        model_ft.classifier[6] = nn.Linear(num_ftrs,num_classes)
        input_size = 224
    # Squeezenet
    elif model_name == "squeezenet":
        model_ft = models.squeezenet1_0(pretrained=use_pretrained)
        set_parameter_requires_grad(model_ft, feature_extract)
        model_ft.classifier[1] = nn.Conv2d(512, num_classes, kernel_size=(1,1), stride=(1,1))
        model_ft.num_classes = num_classes
        input_size = 224
    # Densenet
    elif model_name == "densenet":
        model_ft = models.densenet121(pretrained=use_pretrained)
        set_parameter_requires_grad(model_ft, feature_extract)
        num_ftrs = model_ft.classifier.in_features
        model_ft.classifier = nn.Linear(num_ftrs, num_classes)
        input_size = 224
    # Inception v3 (Be careful, expects (299,299) sized images and has auxiliary output)
    elif model_name == "inception": 
        model_ft = models.inception_v3(pretrained=use_pretrained)
        set_parameter_requires_grad(model_ft, feature_extract)
        # Handle the auxilary net
        num_ftrs = model_ft.AuxLogits.fc.in_features
        model_ft.AuxLogits.fc = nn.Linear(num_ftrs, num_classes)
        # Handle the primary net
        num_ftrs = model_ft.fc.in_features
        model_ft.fc = nn.Linear(num_ftrs,num_classes)
        input_size = 299
    else:
        print("Invalid model name, exiting...")
        exit()
    return model_ft, input_size

2.9 设置哪些层需要训练

# 初始化模型
model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)

# GPU计算
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model_ft = model_ft.to(device)

# 设置要训练的参数
params_to_update = model_ft.parameters()
print("Params to learn:")

# 如果 feature_extract=True,意味着你只会训练最后的分类层,之前的层保持冻结,避免更新权重。
if feature_extract:
    params_to_update = []
    for name, param in model_ft.named_parameters():
        if param.requires_grad:
            params_to_update.append(param)
            print("\t", name)
# 如果 feature_extract=False,则意味着你希望训练所有层,因此无需修改 params_to_update。
else:
    for name, param in model_ft.named_parameters():
        if param.requires_grad:
            print("\t", name)

在这里插入图片描述
在这里插入图片描述

2.10 优化器设置

# 优化器设置(使用 Adam 优化器,并设置了较高的学习率 1e-2)
optimizer_ft = optim.Adam(params_to_update, lr=1e-2)
# 学习率调度器(用了 StepLR 学习率调度器,每 7 个 epoch 后学习率衰减到原来的 1/10。)
# 这种策略有助于在训练过程中逐步减小学习率,防止过拟合,同时也能让模型更稳定地收敛。
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
# 最后一层已经LogSoftmax()了,所以不能nn.CrossEntropyLoss()来计算了,nn.CrossEntropyLoss()相当于logSoftmax()和nn.NLLLoss()整合
# 这是针对对数概率分布的负对数似然损失函数:
criterion = nn.NLLLoss()

2.11 训练模块

def train_model(model, dataloaders, criterion, optimizer, num_epochs=25, is_inception=False,filename=filename):
    since = time.time()
    best_acc = 0
    """
    checkpoint = torch.load(filename)
    best_acc = checkpoint['best_acc']
    model.load_state_dict(checkpoint['state_dict'])
    optimizer.load_state_dict(checkpoint['optimizer'])
    model.class_to_idx = checkpoint['mapping']
    """
    model.to(device)

    val_acc_history = []
    train_acc_history = []
    train_losses = []
    valid_losses = []
    LRs = [optimizer.param_groups[0]['lr']]

    best_model_wts = copy.deepcopy(model.state_dict())

    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        # 训练和验证
        for phase in ['train', 'valid']:
            if phase == 'train':
                model.train()  # 训练
            else:
                model.eval()   # 验证

            running_loss = 0.0
            running_corrects = 0

            # 把数据都取个遍
            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                # 清零
                optimizer.zero_grad()
                # 只有训练的时候计算和更新梯度
                with torch.set_grad_enabled(phase == 'train'):
                    if is_inception and phase == 'train':
                        outputs, aux_outputs = model(inputs)
                        loss1 = criterion(outputs, labels)
                        loss2 = criterion(aux_outputs, labels)
                        loss = loss1 + 0.4*loss2
                    # resnet执行的是这里
                    else:
                        outputs = model(inputs)
                        loss = criterion(outputs, labels)

                    _, preds = torch.max(outputs, 1)

                    # 训练阶段更新权重
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                # 计算损失
                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)

            epoch_loss = running_loss / len(dataloaders[phase].dataset)
            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)
            
            
            time_elapsed = time.time() - since
            print('Time elapsed {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))
            

            # 得到最好那次的模型
            if phase == 'valid' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())
                state = {
                  'state_dict': model.state_dict(),
                  'best_acc': best_acc,
                  'optimizer' : optimizer.state_dict(),
                }
                torch.save(state, filename)
            if phase == 'valid':
                val_acc_history.append(epoch_acc)
                valid_losses.append(epoch_loss)
                scheduler.step(epoch_loss)
            if phase == 'train':
                train_acc_history.append(epoch_acc)
                train_losses.append(epoch_loss)
        
        print('Optimizer learning rate : {:.7f}'.format(optimizer.param_groups[0]['lr']))
        LRs.append(optimizer.param_groups[0]['lr'])
        print()

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    # 训练完后用最好的一次当做模型最终的结果
    model.load_state_dict(best_model_wts)
    return model, val_acc_history, train_acc_history, valid_losses, train_losses, LRs 

2.12 开始训练

model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs  = train_model(model_ft, dataloaders, criterion, optimizer_ft, num_epochs=20, is_inception=(model_name=="inception"))

在这里插入图片描述
两轮Epoch花了1个多钟,因为我这里是CPU训练的,比较慢。我这里调整下num_epochs为2.

2.13 再继续训练所有层

for param in model_ft.parameters():
    param.requires_grad = True

# 再继续训练所有的参数,学习率调小一点
optimizer = optim.Adam(params_to_update, lr=1e-4)
# # 确保optimizer使用的是所有参数
scheduler = optim.lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)

# 损失函数,确保最后一层是 LogSoftmax
criterion = nn.NLLLoss()
# 加载检查点
checkpoint = torch.load(filename)
# 恢复最好的验证准确率
best_acc = checkpoint['best_acc']
# 恢复模型的状态字典
model_ft.load_state_dict(checkpoint['state_dict'])
# 恢复优化器的状态字典
optimizer.load_state_dict(checkpoint['optimizer'])
model_ft, val_acc_history, train_acc_history, valid_losses, train_losses, LRs  = train_model(model_ft, dataloaders, criterion, optimizer, num_epochs=2, is_inception=(model_name=="inception"))

在这里插入图片描述
2.14 加载训练好的模型

# 初始化模型
model_ft, input_size = initialize_model(model_name, 102, feature_extract, use_pretrained=True)
# GPU模式
model_ft = model_ft.to(device)
# 保存文件的名字
filename = 'checkpoint.pth'
# 加载模型
checkpoint = torch.load(filename)
# 恢复最好的验证准确率
best_acc = checkpoint['best_acc']
# 加载模型权重
model_ft.load_state_dict(checkpoint['state_dict'])

2.15 测试数据预处理

  • 测试数据处理方法需要跟训练时一致才可以
  • crop操作的目的是保证输入的大小是一致的
  • 标准化操作也是必须的,用跟训练数据相同的mean和std,但是需要注意一点训练数据是在0-1上进行标准化,所以测试数据也需要先归一化
  • 最后一点,PyTorch中颜色通道是第一个维度,跟很多工具包都不一样,需要转换
def process_image(image_path):
    # 读取测试数据
    img = Image.open(image_path)
    # Resize,thumbnail方法只能进行缩小,所以进行了判断
    if img.size[0] > img.size[1]:
        img.thumbnail((10000, 256))
    else:
        img.thumbnail((256, 10000))
    # Crop操作
    left_margin = (img.width-224)/2
    bottom_margin = (img.height-224)/2
    right_margin = left_margin + 224
    top_margin = bottom_margin + 224
    img = img.crop((left_margin, bottom_margin, right_margin,   
                      top_margin))
    # 相同的预处理方法
    img = np.array(img)/255
    mean = np.array([0.485, 0.456, 0.406]) #provided mean
    std = np.array([0.229, 0.224, 0.225]) #provided std
    img = (img - mean)/std
    
    # 注意颜色通道应该放在第一个位置
    img = img.transpose((2, 0, 1))
    
    return img
def imshow(image, ax=None, title=None):
    """展示数据"""
    if ax is None:
        fig, ax = plt.subplots()
    
    # 颜色通道还原 (H, W, C)
    image = np.array(image).transpose((1, 2, 0))
    
    # 预处理还原(反向标准化)
    mean = np.array([0.485, 0.456, 0.406])
    std = np.array([0.229, 0.224, 0.225])
    image = std * image + mean
    
    # 确保数值在 [0, 1] 之间
    image = np.clip(image, 0, 1)
    
    # 显示图像
    ax.imshow(image)
    ax.set_title(title)
    # 隐藏坐标轴
    ax.axis('off')  
    
    return ax
image_path = 'image_06621.jpg'
img = process_image(image_path)
imshow(img)

在这里插入图片描述

# 从验证集获取一个batch的数据
dataiter = iter(dataloaders['valid'])
images, labels = next(dataiter)  

# 模型切换到评估模式
model_ft.eval()

# 将输入传到模型
if train_on_gpu:
    # 如果在GPU上训练,输入数据也要搬到GPU上
    output = model_ft(images.cuda())
else:
    output = model_ft(images)

output表示对一个batch中每一个数据得到其属于各个类别的可能性

output.shape
torch.Size([2, 102])

2.16 得到概率最大的那个

# 获取预测的类别索引
_, preds_tensor = torch.max(output, 1)

# 将张量移回 CPU 并转换为 NumPy 数组
if train_on_gpu:
    preds = preds_tensor.cpu().numpy()
else:
    preds = preds_tensor.numpy()

# 使用 np.squeeze() 去掉单个批次维度(将预测的结果去掉单一维度,保持数据维度的整洁。)
preds = np.squeeze(preds)

# 打印预测
print(preds)
[71 89]

2.17 展示预测结果

fig = plt.figure(figsize=(20, 20))
columns = 2
rows = 1

for idx in range(columns * rows):
    ax = fig.add_subplot(rows, columns, idx+1, xticks=[], yticks=[])
    
    # 显示图像
    plt.imshow(im_convert(images[idx]))
    
    # 获取预测类别和真实类别的名称
    pred_label = cat_to_name[str(preds[idx])]
    true_label = cat_to_name[str(labels[idx].item())]
    
    # 设置标题,颜色标记是否正确
    ax.set_title("{} ({})".format(pred_label, true_label),
                 color=("green" if pred_label == true_label else "red"))

plt.show()

在这里插入图片描述
2.18 小结

可以看出上诉两张图片识别错误,这也很正常,老周这里只跑了两个批次batch_size,训练所有层的时候我把num_epochs=20调整成2,调整成2都跑了3个钟,所以训练模型还得拿GPU来跑!!!

三、论文解读

上诉代码可以看出,老周这里用的是resnet18的一个网络模型,只有18层,因为我这里用的CPU跑的,层数太深我这边跑不动,需要换GPU。

看其它网络模型,给我的直观感觉就是层数越多表现的效果越好,卷积越多越好吗?不一定,我们还是来看下论文里实践跑的效果。

在这里插入图片描述
论文地址:Deep Residual Learning for Image Recognition

可以看出,红色线是一个56层的网络,黄色线是一个20层的网络,从20层延伸到56层我们可以看到无论是 training error、test error,20层的都比56层的要好。所以,在神经网络当中,越深的神经网络没有我们想象的那么好,即网络层数也不能无限做深下去。我能不能把网络层数做多一些并且效果还很好的,这样网络层数更多并且效果要比浅层神经网络更好是我们的目标,那下面来看看作者是怎么设计的?

在这里插入图片描述
我们可以看到经过了一个卷积以后得到了一个结果,右边还有一条线直接拿过来。刚才我们上面说了卷积神经网络层数越多效果可能不好,在越多的时候其中那两层可能效果不好,右边那个直接拿过来就是以防那两层卷积效果不好,再不好的情况下直接拿上一层的效果最好的值。理解了这一点,我们再来看整体架构就简单了。

在这里插入图片描述
VGG-19我们就不看,我们来对比 34-layer plain(正常网络) 和 34-layer residua(残差网络),感觉没啥区别,参数都是一致的,但是右边的那个传参网络多了一条一条线,在整个神经网络中都加上一个个的小模块,每一层都是通过一个小模块相连的,这样的话就保证了一点,网络架构不会比原来更差的。不知道你有没有注意到,不同颜色连接处是用虚线连接的,不通颜色之间有啥不同?显而易见是特征图的个数不一样,虚线的作用就是做了一个1×1的卷积(把我们的特征图的个数进行翻倍)。

在这里插入图片描述

结论:右边残差网络34层比18层效果要更好,左边的普通网络没有呈现34层比18层效果要更好的趋势。

四、Resnet网络架构

在这里插入图片描述
从左到右看,input输入数据,再到ZERO PAD,也就是边缘上加几层0,让边界点利用的更加充分。我们再看stage 1,conv、Batch Norm、ReLU、MAX POOL,总之进行正常的卷积池化操作。主要来看下stage2(3、4、5一样),这些就是上面论文中介绍的一个模块,这一个个模块做了残差网络当中最核心的计算。

蓝色的是卷积模块,红色的是映射模块。如下图:

在这里插入图片描述
这里解释下identity_block模块,输入的特征图的大小以及个数(如:[32,32,64])与下面经过了三次卷积完成之后的特征图的大小以及个数(如:[32,32,64])要一致,这样才能进行相加操作。

我们再来看convolution_block模块,如果一直让特征图保持一样,那特征就没那么丰富了。比如第一个是[32,32,64],第二个是[32,32,64],第三个是[32,32,256],那要想进行相加,上面那条线就得进行[1,1,256]的卷积得到[32,32,256],这样才能和下面第三个的[32,32,256]进行相加。

理解了这两个模块,那残差网络架构的核心你也就知道了。

相关文章:

  • 如何在 Mac 上下载安装仙剑游戏仙剑世界?可以通过IPA砸壳包安装非常简单
  • Dify搭建旅行规划助手
  • 基于时变天气网络的无人机群配送路径优化
  • .NET + Vue3 的前后端项目在IIS的发布
  • WordPress“更新失败,响应不是有效的JSON响应”问题的修复
  • 基于Open Babel将SDF转为MOL2格式
  • 前端带样式导出excel表格,html表格生成带样式的excel表格
  • PHP 文件与目录操作
  • gitte远程仓库修改后,本地没有更新,本地与远程仓库不一致
  • Ubuntu虚拟机NDK编译ffmpeg
  • 【Java】理解字符串拼接与数值运算的优先级
  • 解压包格式7z怎么解压?8种方法(Win/Mac/手机/网页端)
  • C++核心指导原则: 哲学部分
  • Deepseek-R1推理模型API接入调用指南 ChatGPT Web Midjourney Proxy 开源项目接入Deepseek教程
  • Linux 信号量
  • Python的那些事第二十四篇:Tornado:异步网络编程的“风火轮”
  • 【实战项目】BP神经网络识别人脸朝向----MATLAB实现
  • 蓝桥杯平方差(打表)
  • React创建项目实用教程
  • 使用DeepSeek建立一个智能聊天机器人0.11
  • 最近这75年,谁建造了上海?
  • 伊朗港口爆炸事件已致195人受伤
  • 识味顺德︱顺德菜的醉系列与火滋味
  • 新华时评·首季中国经济观察丨用好用足更加积极的财政政策
  • 仅退款正式成历史?仅退款究竟该不该有?
  • 五矿地产:今年要确保债务“不爆雷”、交付“不烂尾”