主页 > 开源代码  > 

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

深度学习实战——卷积神经网络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]进行相加。

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

标签:

深度学习实战——卷积神经网络CNN在图片识别中的应用以及原理由讯客互联开源代码栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“深度学习实战——卷积神经网络CNN在图片识别中的应用以及原理