Compartilhamento de tecnologia

Dia 12 do acampamento de check-in de aprendizagem de 25 dias de Shengsi | Classificação de imagem ResNet50 de aprendizagem profunda simples - construindo uma rede ResNet50

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

ResNet resolve principalmente o problema de “degradação” de redes convolucionais profundas quando a profundidade é aprofundada. Em redes neurais convolucionais gerais, o primeiro problema causado pelo aumento da profundidade da rede é o desaparecimento e a explosão do gradiente. Este problema foi resolvido com sucesso depois que Szegedy propôs a camada BN. A camada BN pode normalizar a saída de cada camada, de modo que o gradiente ainda possa manter um tamanho estável após ser passado camada por camada ao contrário, e não seja muito pequeno ou muito grande. No entanto, o autor descobriu que ainda não é fácil convergir após adicionar BN e depois aumentar a profundidade. Ele mencionou o segundo problema - o problema do declínio da precisão: quando o nível for grande o suficiente até certo ponto, a precisão ficará saturada. e, em seguida, diminuir rapidamente. Essa diminuição não é O desaparecimento dos gradientes não é causado por overfitting, mas porque a rede é muito complexa, de modo que é difícil atingir a taxa de erro ideal apenas através do treinamento livre e irrestrito.

O problema da diminuição da precisão não é um problema da estrutura da rede em si, mas é causado pelo fato de os métodos de treinamento existentes não serem ideais. Os otimizadores amplamente utilizados atualmente, sejam eles SGD, RMSProp ou Adam, não conseguem alcançar os resultados de convergência teoricamente ideais quando a profundidade da rede se torna maior.

Contanto que haja uma estrutura de rede adequada, uma rede mais profunda certamente terá um desempenho melhor do que uma rede mais superficial. O processo de prova também é muito simples: suponha que algumas camadas sejam adicionadas atrás de uma rede A para formar uma nova rede B. Se as camadas adicionadas realizam apenas um mapeamento de identidade na saída de A, ou seja, a saída de A é adicionada através da nova rede A. Não há mudança depois que o nível se torna a saída de B, então as taxas de erro da rede A e da rede B são iguais, o que prova que a rede após o aprofundamento não será pior do que a rede antes do aprofundamento.


Classificação de imagem ResNet50

A classificação de imagens é a aplicação mais básica de visão computacional e pertence à categoria de aprendizagem supervisionada. Por exemplo, dada uma imagem (gato, cachorro, avião, carro, etc.), determine a categoria à qual a imagem pertence. Este capítulo apresentará o uso da rede ResNet50 para classificar o conjunto de dados CIFAR-10.

Introdução à rede ResNet

A rede ResNet50 foi proposta por He Kaiming do Microsoft Labs em 2015 e conquistou o primeiro lugar na competição de classificação de imagens ILSVRC2015. Antes da rede ResNet ser proposta, as redes neurais convolucionais tradicionais eram obtidas empilhando uma série de camadas convolucionais e agrupando camadas. No entanto, quando a rede é empilhada até uma certa profundidade, ocorrerão problemas de degradação. A figura abaixo é um gráfico de erro de treinamento e erro de teste usando uma rede de 56 camadas e uma rede de 20 camadas no conjunto de dados CIFAR-10. Pode-se ver nos dados da figura que o erro de treinamento e o erro de teste de. a rede de 56 camadas é maior do que a rede de 20 camadas. À medida que a rede se aprofunda, o erro não diminui conforme o esperado.

resnet-1

A rede ResNet propõe uma estrutura de rede residual (Rede Residual) para aliviar o problema de degradação. O uso da rede ResNet pode construir uma estrutura de rede mais profunda (mais de 1000 camadas). O gráfico de erro de treinamento e erro de teste da rede ResNet usado no artigo sobre o conjunto de dados CIFAR-10 é mostrado na figura abaixo. A linha pontilhada na figura representa o erro de treinamento e a linha sólida representa o erro de teste. Pode-se observar pelos dados da figura que quanto mais profunda a camada de rede ResNet, menor será o erro de treinamento e o erro de teste.

resnet-4

Preparação e carregamento do conjunto de dados

O conjunto de dados CIFAR-10 possui um total de 60.000 imagens coloridas de 32*32, divididas em 10 categorias, cada categoria possui 6.000 imagens e o conjunto de dados possui um total de 50.000 imagens de treinamento e 10.000 imagens de avaliação. Primeiro, o exemplo a seguir usa a interface de download para baixar e descompactar. Atualmente, apenas a versão binária dos arquivos CIFAR-10 (versão binária CIFAR-10) é suportada.

%%capture captured_output
# 实验环境已经预装了mindspore==2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.2.14
%%capture captured_output
# 实验环境已经预装了mindspore==2.2.14,如需更换mindspore版本,可更改下面mindspore的版本号
!pip uninstall mindspore -y
!pip install -i https://pypi.mirrors.ustc.edu.cn/simple mindspore==2.2.14
# 查看当前 mindspore 版本
!pip show mindspore
# 查看当前 mindspore 版本
!pip show mindspore
from download import download

url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz"

download(url, "./datasets-cifar10-bin", kind="tar.gz", replace=True)
from download import download
​
url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz"
​
download(url, "./datasets-cifar10-bin", kind="tar.gz", replace=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

'./conjuntos de dados-cifar10-bin'
A estrutura de diretório do conjunto de dados baixado é a seguinte:

datasets-cifar10-bin/cifar-10-batches-bin
├── batches.meta.text
├── data_batch_1.bin
├── data_batch_2.bin
├── data_batch_3.bin
├── data_batch_4.bin
├── data_batch_5.bin
├── readme.html
└── test_batch.bin
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

Insira a descrição da imagem aqui

Em seguida, use a interface mindspore.dataset.Cifar10Dataset para carregar o conjunto de dados e executar operações de aprimoramento de imagem relacionadas.

import mindspore as ms
import mindspore.dataset as ds
import mindspore.dataset.vision as vision
import mindspore.dataset.transforms as transforms
from mindspore import dtype as mstype

data_dir = "./datasets-cifar10-bin/cifar-10-batches-bin"  # 数据集根目录
batch_size = 256  # 批量大小
image_size = 32  # 训练图像空间大小
workers = 4  # 并行线程个数
num_classes = 10  # 分类数量

def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):

    data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,
                                 usage=usage,
                                 num_parallel_workers=workers,
                                 shuffle=True)

    trans = []
    if usage == "train":
        trans += [
            vision.RandomCrop((32, 32), (4, 4, 4, 4)),
            vision.RandomHorizontalFlip(prob=0.5)
        ]

    trans += [
        vision.Resize(resize),
        vision.Rescale(1.0 / 255.0, 0.0),
        vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),
        vision.HWC2CHW()
    ]

    target_trans = transforms.TypeCast(mstype.int32)

    # 数据映射操作
    data_set = data_set.map(operations=trans,
                            input_columns='image',
                            num_parallel_workers=workers)

    data_set = data_set.map(operations=target_trans,
                            input_columns='label',
                            num_parallel_workers=workers)

    # 批量操作
    data_set = data_set.batch(batch_size)

    return data_set


# 获取处理后的训练与测试数据集

dataset_train = create_dataset_cifar10(dataset_dir=data_dir,
                                       usage="train",
                                       resize=image_size,
                                       batch_size=batch_size,
                                       workers=workers)
step_size_train = dataset_train.get_dataset_size()

dataset_val = create_dataset_cifar10(dataset_dir=data_dir,
                                     usage="test",
                                     resize=image_size,
                                     batch_size=batch_size,
                                     workers=workers)
step_size_val = dataset_val.get_dataset_size()
import mindspore as ms
import mindspore.dataset as ds
import mindspore.dataset.vision as vision
import mindspore.dataset.transforms as transforms
from mindspore import dtype as mstype
​
data_dir = "./datasets-cifar10-bin/cifar-10-batches-bin"  # 数据集根目录
batch_size = 256  # 批量大小
image_size = 32  # 训练图像空间大小
workers = 4  # 并行线程个数
num_classes = 10  # 分类数量
​
​
def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):
​
    data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,
                                 usage=usage,
                                 num_parallel_workers=workers,
                                 shuffle=True)
​
    trans = []
    if usage == "train":
        trans += [
            vision.RandomCrop((32, 32), (4, 4, 4, 4)),
            vision.RandomHorizontalFlip(prob=0.5)
        ]
​
    trans += [
        vision.Resize(resize),
        vision.Rescale(1.0 / 255.0, 0.0),
        vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),
        vision.HWC2CHW()
    ]
​
    target_trans = transforms.TypeCast(mstype.int32)
​
    # 数据映射操作
    data_set = data_set.map(operations=trans,
                            input_columns='image',
                            num_parallel_workers=workers)
​
    data_set = data_set.map(operations=target_trans,
                            input_columns='label',
                            num_parallel_workers=workers)
​
    # 批量操作
    data_set = data_set.batch(batch_size)
​
    return data_set
​
​
# 获取处理后的训练与测试数据集
​
dataset_train = create_dataset_cifar10(dataset_dir=data_dir,
                                       usage="train",
                                       resize=image_size,
                                       batch_size=batch_size,
                                       workers=workers)
step_size_train = dataset_train.get_dataset_size()
​
dataset_val = create_dataset_cifar10(dataset_dir=data_dir,
                                     usage="test",
                                     resize=image_size,
                                     batch_size=batch_size,
                                     workers=workers)
step_size_val = dataset_val.get_dataset_size()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131

Visualize o conjunto de dados de treinamento CIFAR-10.


import matplotlib.pyplot as plt
import numpy as np

data_iter = next(dataset_train.create_dict_iterator())

images = data_iter["image"].asnumpy()
labels = data_iter["label"].asnumpy()
print(f"Image shape: {images.shape}, Label shape: {labels.shape}")

# 训练数据集中,前六张图片所对应的标签
print(f"Labels: {labels[:6]}")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

Insira a descrição da imagem aqui

classes = []

with open(data_dir + "/batches.meta.txt", "r") as f:
    for line in f:
        line = line.rstrip()
        if line:
            classes.append(line)

# 训练数据集的前六张图片
plt.figure()
for i in range(6):
    plt.subplot(2, 3, i + 1)
    image_trans = np.transpose(images[i], (1, 2, 0))
    mean = np.array([0.4914, 0.4822, 0.4465])
    std = np.array([0.2023, 0.1994, 0.2010])
    image_trans = std * image_trans + mean
    image_trans = np.clip(image_trans, 0, 1)
    plt.title(f"{classes[labels[i]]}")
    plt.imshow(image_trans)
    plt.axis("off")
plt.show()
import matplotlib.pyplot as plt
import numpy as np
​
data_iter = next(dataset_train.create_dict_iterator())
​
images = data_iter["image"].asnumpy()
labels = data_iter["label"].asnumpy()
print(f"Image shape: {images.shape}, Label shape: {labels.shape}")
​
# 训练数据集中,前六张图片所对应的标签
print(f"Labels: {labels[:6]}")
​
classes = []with open(data_dir + "/batches.meta.txt", "r") as f:
    for line in f:
        line = line.rstrip()
        if line:
            classes.append(line)
​
# 训练数据集的前六张图片
plt.figure()
for i in range(6):
    plt.subplot(2, 3, i + 1)
    image_trans = np.transpose(images[i], (1, 2, 0))
    mean = np.array([0.4914, 0.4822, 0.4465])
    std = np.array([0.2023, 0.1994, 0.2010])
    image_trans = std * image_trans + mean
    image_trans = np.clip(image_trans, 0, 1)
    plt.title(f"{classes[labels[i]]}")
    plt.imshow(image_trans)
    plt.axis("off")
plt.show()
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

Formato da imagem: (256, 3, 32, 32), Formato do rótulo: (256,)
Etiquetas: [3 2 7 6 0 4]

Construa uma rede

A estrutura de rede residual (Rede Residual) é o principal destaque da rede ResNet. O uso da estrutura de rede residual pela ResNet pode efetivamente aliviar o problema de degradação, alcançar um projeto de estrutura de rede mais profundo e melhorar a precisão do treinamento da rede. Esta seção descreve primeiro como construir uma estrutura de rede residual e, em seguida, constrói uma rede ResNet50 empilhando redes residuais.

Construa uma estrutura de rede residual
残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shotcuts从输入直接到输出,主分支输出的特征矩阵 𝐹(𝑥)
加上shortcuts输出的特征矩阵 𝑥 得到 𝐹(𝑥)+𝑥,通过Relu激活函数后即为残差网络最后的输出。

residual

Existem dois tipos principais de estruturas de rede residuais. Um é o Building Block, que é adequado para redes ResNet mais rasas, como ResNet18 e ResNet34; o outro é o Bottleneck, que é adequado para redes ResNet mais profundas, como ResNet50, ResNet101 e ResNet152. .

Bloco de construção

O diagrama da estrutura do Building Block é mostrado na figura abaixo. O ramo principal possui uma estrutura de rede convolucional de duas camadas:

A rede de primeira camada do ramal principal toma o canal de entrada 64 como exemplo. Primeiro, ela passa um 3×3.
A camada de convolução, depois através da camada de normalização em lote e, finalmente, através da camada de função de ativação Relu, o canal de saída é 64;
O canal de entrada da rede de segunda camada do ramal principal é 64. Ele primeiro passa por um 3×3
A camada convolucional é então passada pela camada de normalização em lote e o canal de saída é 64.
Finalmente, a saída da matriz de recursos pela ramificação principal e a saída da matriz de recursos por atalhos são adicionadas, e a saída final do Building Block é obtida por meio da função de ativação Relu.

bloco de construção-5

Ao adicionar a saída das matrizes de recursos pela ramificação principal e pelos atalhos, você precisa garantir que o formato da saída da matriz de recursos pela ramificação principal e pelos atalhos seja o mesmo. Se a saída do formato da matriz de recursos do ramo principal e dos atalhos for diferente, por exemplo, o canal de saída é duas vezes o canal de entrada, os atalhos precisam usar o mesmo número que o canal de saída e um tamanho de kernel de convolução 1×1 para o operação de convolução; se a imagem de saída for duas vezes menor que a imagem de entrada, o passo na operação de convolução nos atalhos deve ser definido como 2, e o passo na operação de convolução da primeira camada do ramo principal também deve ser definido como definido como 2.

O código a seguir define a classe ResidualBlockBase para implementar a estrutura do Building Block.

from typing import Type, Union, List, Optional
import mindspore.nn as nn
from mindspore.common.initializer import Normal
​
# 初始化卷积层与BatchNorm的参数
weight_init = Normal(mean=0, sigma=0.02)
gamma_init = Normal(mean=1, sigma=0.02)
​
class ResidualBlockBase(nn.Cell):
    expansion: int = 1  # 最后一个卷积核数量与第一个卷积核数量相等
​
    def __init__(self, in_channel: int, out_channel: int,
                 stride: int = 1, norm: Optional[nn.Cell] = None,
                 down_sample: Optional[nn.Cell] = None) -> None:
        super(ResidualBlockBase, self).__init__()
        if not norm:
            self.norm = nn.BatchNorm2d(out_channel)
        else:
            self.norm = norm
​
        self.conv1 = nn.Conv2d(in_channel, out_channel,
                               kernel_size=3, stride=stride,
                               weight_init=weight_init)
        self.conv2 = nn.Conv2d(in_channel, out_channel,
                               kernel_size=3, weight_init=weight_init)
        self.relu = nn.ReLU()
        self.down_sample = down_sample
​
    def construct(self, x):
        """ResidualBlockBase construct."""
        identity = x  # shortcuts分支
​
        out = self.conv1(x)  # 主分支第一层:3*3卷积层
        out = self.norm(out)
        out = self.relu(out)
        out = self.conv2(out)  # 主分支第二层:3*3卷积层
        out = self.norm(out)
​
        if self.down_sample is not None:
            identity = self.down_sample(x)
        out += identity  # 输出为主分支与shortcuts之和
        out = self.relu(out)
​
        return out
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

Gargalo

O diagrama da estrutura do Gargalo é mostrado na figura abaixo. Quando a entrada é a mesma, a estrutura do Gargalo possui menos parâmetros que a estrutura do Building Block e é mais adequada para redes com camadas mais profundas. A estrutura residual usada pelo ResNet50 é o Gargalo. O ramo principal desta estrutura possui três camadas de estrutura de convolução, cada uma das quais é 1×1
Camada convolucional, 3×3
Camadas convolucionais e 1×1
camada convolucional, onde 1×1
As camadas convolucionais desempenham o papel de redução de dimensionalidade e dimensionalidade, respectivamente.

A rede de primeira camada do ramal principal toma o canal de entrada 256 como exemplo. O número da primeira passagem é 64 e o tamanho é 1×1.
O kernel de convolução realiza redução de dimensionalidade, depois passa pela camada de normalização em lote e, finalmente, passa pela camada de função de ativação Relu e seu canal de saída é 64;
O número de passagens da rede de segunda camada do ramal principal é 64 e o tamanho é 3×3
O kernel de convolução extrai recursos, depois passa pela camada de normalização em lote e, finalmente, passa pela camada de função de ativação Relu e seu canal de saída é 64;
O número de passagens no terceiro nível do ramal principal é 256 e o ​​tamanho é 1×1
O kernel de convolução é dimensionado e depois passado pela camada de normalização em lote e seu canal de saída é 256.
Finalmente, a saída da matriz de recursos pelo ramo principal e a saída da matriz de recursos por atalhos são adicionadas, e a saída final do Gargalo é obtida por meio da função de ativação Relu.

bloco de construção-6

Ao adicionar a saída das matrizes de recursos pela ramificação principal e pelos atalhos, você precisa garantir que o formato da saída da matriz de recursos pela ramificação principal e pelos atalhos seja o mesmo. Se a saída do formato da matriz de recursos da ramificação principal e dos atalhos for diferente, por exemplo, se o canal de saída for duas vezes o canal de entrada, o número de atalhos precisa ser igual ao canal de saída e o tamanho é 1×1
O kernel de convolução executa a operação de convolução; se a imagem de saída for duas vezes menor que a imagem de entrada, o passo na operação de convolução nos atalhos precisa ser definido como 2, e o passo na operação de convolução da segunda camada do ramo principal também precisa ser definido. a ser definido como 2.

O código a seguir define a classe ResidualBlock para implementar a estrutura Bottleneck.

class ResidualBlock(nn.Cell):
    expansion = 4  # 最后一个卷积核的数量是第一个卷积核数量的4倍
​
    def __init__(self, in_channel: int, out_channel: int,
                 stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:
        super(ResidualBlock, self).__init__()
​
        self.conv1 = nn.Conv2d(in_channel, out_channel,
                               kernel_size=1, weight_init=weight_init)
        self.norm1 = nn.BatchNorm2d(out_channel)
        self.conv2 = nn.Conv2d(out_channel, out_channel,
                               kernel_size=3, stride=stride,
                               weight_init=weight_init)
        self.norm2 = nn.BatchNorm2d(out_channel)
        self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion,
                               kernel_size=1, weight_init=weight_init)
        self.norm3 = nn.BatchNorm2d(out_channel * self.expansion)
​
        self.relu = nn.ReLU()
        self.down_sample = down_sample
​
    def construct(self, x):
​
        identity = x  # shortscuts分支
​
        out = self.conv1(x)  # 主分支第一层:1*1卷积层
        out = self.norm1(out)
        out = self.relu(out)
        out = self.conv2(out)  # 主分支第二层:3*3卷积层
        out = self.norm2(out)
        out = self.relu(out)
        out = self.conv3(out)  # 主分支第三层:1*1卷积层
        out = self.norm3(out)
​
        if self.down_sample is not None:
            identity = self.down_sample(x)
​
        out += identity  # 输出为主分支与shortcuts之和
        out = self.relu(out)
​
        return out
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

Construir rede ResNet50

A estrutura da camada de rede ResNet é mostrada na figura abaixo Tomando a imagem colorida de entrada 224×224 como exemplo, primeiro passe a camada de convolução conv1 com uma quantidade de 64, um tamanho de kernel de convolução de 7×7 e um passo de 2. O tamanho da imagem de saída desta camada é 112×112, o canal de saída é 64, então através de uma camada de pooling de redução da resolução máxima de 3×3, o tamanho da imagem de saída desta camada é 56×56, o canal de saída é 64; empilhar 4 blocos de rede residuais (conv2_x, conv3_x, conv4_x e conv5_x), neste momento o tamanho da imagem de saída é 7×7 e o canal de saída é 2048, finalmente, a probabilidade de classificação é obtida através de uma camada de pooling média, uma camada totalmente conectada; e softmax.

Para cada bloco de rede residual, tomando como exemplo conv2_x na rede ResNet50, ele é empilhado por 3 estruturas de gargalo. O canal de entrada de cada gargalo é 64 e o canal de saída é 256.

O exemplo a seguir define make_layer para implementar a construção do bloco residual e seus parâmetros são os seguintes:

  • last_out_channel: O número de canais emitidos pela rede residual anterior.
  • block: Categoria da rede residual, respectivamente ResidualBlockBase e ResidualBlock.
  • canal: O número de canais de entrada na rede residual.
  • block_nums: O número de blocos de rede residuais empilhados.
  • passada: a passada do movimento de convolução.
def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],
               channel: int, block_nums: int, stride: int = 1):
    down_sample = None  # shortcuts分支
​
    if stride != 1 or last_out_channel != channel * block.expansion:
​
        down_sample = nn.SequentialCell([
            nn.Conv2d(last_out_channel, channel * block.expansion,
                      kernel_size=1, stride=stride, weight_init=weight_init),
            nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)
        ])
​
    layers = []
    layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))
​
    in_channel = channel * block.expansion
    # 堆叠残差网络
    for _ in range(1, block_nums):
​
        layers.append(block(in_channel, channel))
​
    return nn.SequentialCell(layers)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

A rede ResNet50 tem um total de 5 estruturas convolucionais, uma camada de pooling média e uma camada totalmente conectada. Tomemos o conjunto de dados CIFAR-10 como exemplo:

conv1: O tamanho da imagem de entrada é 32×32 e o canal de entrada é 3. Primeiro, ele passa por uma camada de convolução com um número de kernel de convolução de 64, um tamanho de kernel de convolução de 7×7 e um avanço de 2; em seguida, passa por uma camada de normalização em lote e, finalmente, passa pela função de ativação Reul; O tamanho do mapa de recursos de saída desta camada é 16×16 e o ​​canal de saída é 64.

conv2_x: O tamanho do mapa de recursos de entrada é 16×16 e o ​​canal de entrada é 64. Primeiro, ele passa por uma operação máxima de downsampling pooling com um tamanho de kernel de convolução de 3×3 e um passo de 2, depois empilha três Gargalos com uma estrutura de [1×1, 64; , 256]. O tamanho do mapa de recursos de saída desta camada é 8×8 e o canal de saída é 256.

conv3_x: O tamanho do mapa de recursos de entrada é 8×8 e o canal de entrada é 256. Esta camada empilha 4 Gargalos com uma estrutura de [1×1, 128; O tamanho do mapa de recursos de saída desta camada é 4×4 e o canal de saída é 512.

conv4_x: O tamanho do mapa de recursos de entrada é 4×4 e o canal de entrada é 512. Esta camada empilha 6 gargalos com uma estrutura de [1×1, 256; O tamanho do mapa de recursos de saída desta camada é 2×2 e o canal de saída é 1024.

conv5_x: O tamanho do mapa de recursos de entrada é 2×2 e o canal de entrada é 1024. Esta camada empilha três Gargalos com uma estrutura de [1×1, 512; O tamanho do mapa de recursos de saída desta camada é 1×1 e o canal de saída é 2048.

pool médio & fc: O canal de entrada é 2048 e o canal de saída é o número de categorias de classificação.

O código de exemplo a seguir implementa a construção do modelo ResNet50. O modelo ResNet50 pode ser construído chamando a função resnet50. Os parâmetros da função resnet50 são os seguintes:

  • num_classes: O número de categorias para classificação. O número padrão de categorias é 1000.
  • pré-treinado: Baixe o modelo de treinamento correspondente e carregue os parâmetros do modelo pré-treinado na rede.
from mindspore import load_checkpoint, load_param_into_net
​
​
class ResNet(nn.Cell):
    def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],
                 layer_nums: List[int], num_classes: int, input_channel: int) -> None:
        super(ResNet, self).__init__()
​
        self.relu = nn.ReLU()
        # 第一个卷积层,输入channel为3(彩色图像),输出channel为64
        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)
        self.norm = nn.BatchNorm2d(64)
        # 最大池化层,缩小图片的尺寸
        self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')
        # 各个残差网络结构块定义
        self.layer1 = make_layer(64, block, 64, layer_nums[0])
        self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)
        self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)
        self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)
        # 平均池化层
        self.avg_pool = nn.AvgPool2d()
        # flattern层
        self.flatten = nn.Flatten()
        # 全连接层
        self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)
​
    def construct(self, x):
​
        x = self.conv1(x)
        x = self.norm(x)
        x = self.relu(x)
        x = self.max_pool(x)
​
        x = self.layer1(x)
        x = self.layer2(x)
        x = self.layer3(x)
        x = self.layer4(x)
​
        x = self.avg_pool(x)
        x = self.flatten(x)
        x = self.fc(x)
​
        return x
def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],
            layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,
            input_channel: int):
    model = ResNet(block, layers, num_classes, input_channel)
​
    if pretrained:
        # 加载预训练模型
        download(url=model_url, path=pretrained_ckpt, replace=True)
        param_dict = load_checkpoint(pretrained_ckpt)
        load_param_into_net(model, param_dict)
​
    return model
​
​
def resnet50(num_classes: int = 1000, pretrained: bool = False):
    """ResNet50模型"""
    resnet50_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt"
    resnet50_ckpt = "./LoadPretrainedModel/resnet50_224_new.ckpt"
    return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,
                   pretrained, resnet50_ckpt, 2048)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

Insira a descrição da imagem aqui

Treinamento e avaliação de modelo

Esta seção usa o modelo pré-treinado ResNet50 para ajuste fino. Chame resnet50 para construir o modelo ResNet50 e defina o parâmetro pré-treinado como True. O modelo pré-treinado ResNet50 será baixado automaticamente e os parâmetros do modelo pré-treinado serão carregados na rede. Em seguida, defina o otimizador e a função de perda, imprima o valor da perda de treinamento e a precisão da avaliação época por época e salve o arquivo ckpt com a maior precisão de avaliação (resnet50-best.ckpt) em ./BestCheckPoint no caminho atual.

Como o tamanho de saída (parâmetro correspondente num_classes) da camada totalmente conectada (fc) do modelo pré-treinado é 1000, para carregar com sucesso os pesos pré-treinados, definimos o tamanho de saída totalmente conectado do modelo para o padrão 1000. O conjunto de dados CIFAR10 tem um total de 10 categorias. Ao usar este conjunto de dados para treinamento, o tamanho de saída da camada totalmente conectada do modelo carregado com pesos pré-treinados precisa ser redefinido para 10.

Aqui mostramos o processo de treinamento de 5 épocas. Se você deseja obter o efeito de treinamento ideal, é recomendado treinar por 80 épocas.

# 定义ResNet50网络
network = resnet50(pretrained=True)
​
# 全连接层输入层的大小
in_channel = network.fc.in_channels
fc = nn.Dense(in_channels=in_channel, out_channels=10)
# 重置全连接层
network.fc = fc
# 设置学习率
num_epochs = 5
lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,
                        step_per_epoch=step_size_train, decay_epoch=num_epochs)
# 定义优化器和损失函数
opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)
loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')
​
​
def forward_fn(inputs, targets):
    logits = network(inputs)
    loss = loss_fn(logits, targets)
    return loss
​
​
grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)
​
​
def train_step(inputs, targets):
    loss, grads = grad_fn(inputs, targets)
    opt(grads)
    return loss
import os
​
# 创建迭代器
data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)
data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)
​
# 最佳模型存储路径
best_acc = 0
best_ckpt_dir = "./BestCheckpoint"
best_ckpt_path = "./BestCheckpoint/resnet50-best.ckpt"
​
if not os.path.exists(best_ckpt_dir):
    os.mkdir(best_ckpt_dir)
import mindspore.ops as ops
​
​
def train(data_loader, epoch):
    """模型训练"""
    losses = []
    network.set_train(True)
​
    for i, (images, labels) in enumerate(data_loader):
        loss = train_step(images, labels)
        if i % 100 == 0 or i == step_size_train - 1:
            print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %
                  (epoch + 1, num_epochs, i + 1, step_size_train, loss))
        losses.append(loss)
​
    return sum(losses) / len(losses)
​
​
def evaluate(data_loader):
    """模型验证"""
    network.set_train(False)
​
    correct_num = 0.0  # 预测正确个数
    total_num = 0.0  # 预测总数
​
    for images, labels in data_loader:
        logits = network(images)
        pred = logits.argmax(axis=1)  # 预测结果
        correct = ops.equal(pred, labels).reshape((-1, ))
        correct_num += correct.sum().asnumpy()
        total_num += correct.shape[0]
​
    acc = correct_num / total_num  # 准确率
​
    return acc
# 开始循环训练
print("Start Training Loop ...")
​
for epoch in range(num_epochs):
    curr_loss = train(data_loader_train, epoch)
    curr_acc = evaluate(data_loader_val)
​
    print("-" * 50)
    print("Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]" % (
        epoch+1, num_epochs, curr_loss, curr_acc
    ))
    print("-" * 50)
​
    # 保存当前预测准确率最高的模型
    if curr_acc > best_acc:
        best_acc = curr_acc
        ms.save_checkpoint(network, best_ckpt_path)
​
print("=" * 80)
print(f"End of validation the best Accuracy is: {best_acc: 5.3f}, "
      f"save the best ckpt file in {best_ckpt_path}", flush=True)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
Start Training Loop ...
Epoch: [  1/  5], Steps: [  1/196], Train Loss: [2.389]
Epoch: [  1/  5], Steps: [101/196], Train Loss: [1.467]
Epoch: [  1/  5], Steps: [196/196], Train Loss: [1.093]
--------------------------------------------------
Epoch: [  1/  5], Average Train Loss: [1.641], Accuracy: [0.595]
--------------------------------------------------
Epoch: [  2/  5], Steps: [  1/196], Train Loss: [1.253]
Epoch: [  2/  5], Steps: [101/196], Train Loss: [0.974]
Epoch: [  2/  5], Steps: [196/196], Train Loss: [0.832]
--------------------------------------------------
Epoch: [  2/  5], Average Train Loss: [1.019], Accuracy: [0.685]
--------------------------------------------------
Epoch: [  3/  5], Steps: [  1/196], Train Loss: [0.917]
Epoch: [  3/  5], Steps: [101/196], Train Loss: [0.879]
Epoch: [  3/  5], Steps: [196/196], Train Loss: [0.743]
--------------------------------------------------
Epoch: [  3/  5], Average Train Loss: [0.852], Accuracy: [0.721]
--------------------------------------------------
Epoch: [  4/  5], Steps: [  1/196], Train Loss: [0.911]
Epoch: [  4/  5], Steps: [101/196], Train Loss: [0.703]
Epoch: [  4/  5], Steps: [196/196], Train Loss: [0.768]
--------------------------------------------------
Epoch: [  4/  5], Average Train Loss: [0.777], Accuracy: [0.737]
--------------------------------------------------
Epoch: [  5/  5], Steps: [  1/196], Train Loss: [0.793]
Epoch: [  5/  5], Steps: [101/196], Train Loss: [0.809]
Epoch: [  5/  5], Steps: [196/196], Train Loss: [0.734]
--------------------------------------------------
Epoch: [  5/  5], Average Train Loss: [0.745], Accuracy: [0.742]
--------------------------------------------------
================================================================================
End of validation the best Accuracy is:  0.742, save the best ckpt file in ./BestCheckpoint/resnet50-best.ckpt
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

Insira a descrição da imagem aqui

Previsões de modelos visuais

Defina a função visualize_model, use o modelo mencionado acima com a maior precisão de verificação para prever o conjunto de dados de teste CIFAR-10 e visualize os resultados da previsão. Se a cor da fonte de previsão for azul, significa que a previsão está correta, e se a cor da fonte de previsão for vermelha, significa que a previsão está errada.

Pode-se observar pelos resultados acima que a precisão da previsão do modelo no conjunto de dados de verificação em 5 épocas é de cerca de 70%, ou seja, em circunstâncias normais, 2 em cada 6 imagens não conseguirão prever. Se quiser obter o efeito de treino ideal, recomenda-se treinar durante 80 épocas.

import matplotlib.pyplot as plt


def visualize_model(best_ckpt_path, dataset_val):
    num_class = 10  # 对狼和狗图像进行二分类
    net = resnet50(num_class)
    # 加载模型参数
    param_dict = ms.load_checkpoint(best_ckpt_path)
    ms.load_param_into_net(net, param_dict)
    # 加载验证集的数据进行验证
    data = next(dataset_val.create_dict_iterator())
    images = data["image"]
    labels = data["label"]
    # 预测图像类别
    output = net(data['image'])
    pred = np.argmax(output.asnumpy(), axis=1)

    # 图像分类
    classes = []

    with open(data_dir + "/batches.meta.txt", "r") as f:
        for line in f:
            line = line.rstrip()
            if line:
                classes.append(line)

    # 显示图像及图像的预测值
    plt.figure()
    for i in range(6):
        plt.subplot(2, 3, i + 1)
        # 若预测正确,显示为蓝色;若预测错误,显示为红色
        color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'
        plt.title('predict:{}'.format(classes[pred[i]]), color=color)
        picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))
        mean = np.array([0.4914, 0.4822, 0.4465])
        std = np.array([0.2023, 0.1994, 0.2010])
        picture_show = std * picture_show + mean
        picture_show = np.clip(picture_show, 0, 1)
        plt.imshow(picture_show)
        plt.axis('off')

    plt.show()


# 使用测试数据集进行验证
visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)
import matplotlib.pyplot as plt
​
​
def visualize_model(best_ckpt_path, dataset_val):
    num_class = 10  # 对狼和狗图像进行二分类
    net = resnet50(num_class)
    # 加载模型参数
    param_dict = ms.load_checkpoint(best_ckpt_path)
    ms.load_param_into_net(net, param_dict)
    # 加载验证集的数据进行验证
    data = next(dataset_val.create_dict_iterator())
    images = data["image"]
    labels = data["label"]
    # 预测图像类别
    output = net(data['image'])
    pred = np.argmax(output.asnumpy(), axis=1)
​
    # 图像分类
    classes = []
​
    with open(data_dir + "/batches.meta.txt", "r") as f:
        for line in f:
            line = line.rstrip()
            if line:
                classes.append(line)
​
    # 显示图像及图像的预测值
    plt.figure()
    for i in range(6):
        plt.subplot(2, 3, i + 1)
        # 若预测正确,显示为蓝色;若预测错误,显示为红色
        color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'
        plt.title('predict:{}'.format(classes[pred[i]]), color=color)
        picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))
        mean = np.array([0.4914, 0.4822, 0.4465])
        std = np.array([0.2023, 0.1994, 0.2010])
        picture_show = std * picture_show + mean
        picture_show = np.clip(picture_show, 0, 1)
        plt.imshow(picture_show)
        plt.axis('off')
​
    plt.show()
​
​
# 使用测试数据集进行验证
visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93

Insira a descrição da imagem aqui