Condivisione della tecnologia

Giorno 12 del campo di check-in di apprendimento di 25 giorni di Shengsi |. Semplice classificazione delle immagini ResNet50 con apprendimento profondo: creazione di una rete ResNet50

2024-07-12

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

ResNet risolve principalmente il problema del "degrado" delle reti convoluzionali profonde quando la profondità viene approfondita. Nelle reti neurali convoluzionali generali, il primo problema causato dall'aumento della profondità della rete è la scomparsa e l'esplosione del gradiente. Questo problema è stato risolto con successo dopo che Szegedy ha proposto lo strato BN. Il livello BN può normalizzare l'output di ciascun livello, in modo che il gradiente possa rimanere di dimensioni stabili dopo essere stato passato strato per strato al contrario e non sarà né troppo piccolo né troppo grande. Tuttavia, l'autore ha scoperto che non è ancora facile convergere dopo aver aggiunto BN e aumentato la profondità. Ha menzionato il secondo problema: il problema del calo della precisione: quando il livello è sufficientemente grande fino a un certo punto, la precisione sarà saturata. La scomparsa dei gradienti non è causata da un eccessivo adattamento, ma dal fatto che la rete è troppo complessa, per cui è difficile raggiungere il tasso di errore ideale attraverso il solo addestramento all'aperto senza vincoli.

Il problema della diminuzione della precisione non è dovuto alla struttura della rete stessa, ma è causato dai metodi di formazione esistenti che non sono ideali. Gli ottimizzatori attualmente ampiamente utilizzati, siano essi SGD, RMSProp o Adam, non possono raggiungere i risultati di convergenza teoricamente ottimali quando la profondità della rete aumenta.

Finché esiste una struttura di rete adeguata, una rete più profonda funzionerà sicuramente meglio di una rete meno profonda. Anche il processo di dimostrazione è molto semplice: supponiamo che alcuni strati vengano aggiunti dietro una rete A per formare una nuova rete B. Se gli strati aggiunti eseguono solo una mappatura di identità sull'output di A, ovvero l'output di A viene aggiunto attraverso la nuova rete A. Non vi è alcun cambiamento dopo che il livello diventa l'output di B, quindi i tassi di errore della rete A e della rete B sono uguali, il che dimostra che la rete dopo l'approfondimento non sarà peggiore della rete prima dell'approfondimento.


Classificazione delle immagini ResNet50

La classificazione delle immagini è l'applicazione più basilare della visione artificiale e appartiene alla categoria dell'apprendimento supervisionato. Ad esempio, data un'immagine (gatto, cane, aereo, automobile, ecc.), determinare la categoria a cui appartiene l'immagine. Questo capitolo introdurrà l'uso della rete ResNet50 per classificare il set di dati CIFAR-10.

Introduzione alla rete ResNet

La rete ResNet50 è stata proposta da He Kaiming di Microsoft Labs nel 2015 e ha vinto il primo posto nel concorso di classificazione delle immagini ILSVRC2015. Prima che la rete ResNet fosse proposta, le reti neurali convoluzionali tradizionali venivano ottenute impilando una serie di strati convoluzionali e livelli di pooling. Tuttavia, quando la rete viene impilata fino a una certa profondità, si verificano problemi di degrado. La figura seguente è un grafico dell'errore di addestramento e dell'errore di test utilizzando una rete a 56 strati e una rete a 20 strati sul set di dati CIFAR-10. Dai dati nella figura si può vedere che l'errore di addestramento e l'errore di test di la rete a 56 livelli è più grande di quella della rete a 20 livelli. Man mano che la rete si approfondisce, l'errore non diminuisce come previsto.

Resnet-1

La rete ResNet propone una struttura di rete residua (Residual Network) per alleviare il problema del degrado. Utilizzando la rete ResNet è possibile costruire una struttura di rete più profonda (oltre 1000 livelli). Il grafico dell'errore di addestramento e dell'errore di test della rete ResNet utilizzato nel documento sul set di dati CIFAR-10 è mostrato nella figura seguente. La linea tratteggiata nella figura rappresenta l'errore di addestramento e la linea continua rappresenta l'errore di test. Dai dati nella figura si può vedere che quanto più profondo è lo strato di rete ResNet, tanto minori sono l'errore di addestramento e l'errore di test.

rete di recupero-4

Preparazione e caricamento del set di dati

Il set di dati CIFAR-10 ha un totale di 60.000 immagini a colori 32*32, suddivise in 10 categorie, ciascuna categoria ha 6.000 immagini e il set di dati ha un totale di 50.000 immagini di addestramento e 10.000 immagini di valutazione. Innanzitutto, l'esempio seguente utilizza l'interfaccia di download per scaricare e decomprimere. Attualmente è supportata solo la versione binaria dei file CIFAR-10 (versione binaria CIFAR-10).

%%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

'./datasets-cifar10-bin'
La struttura della directory del set di dati scaricato è la seguente:

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

Inserisci qui la descrizione dell'immagine

Quindi, utilizzare l'interfaccia mindspore.dataset.Cifar10Dataset per caricare il set di dati ed eseguire le operazioni di miglioramento dell'immagine correlate.

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

Visualizza il set di dati di addestramento 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

Inserisci qui la descrizione dell'immagine

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

Forma dell'immagine: (256, 3, 32, 32), Forma dell'etichetta: (256,)
Etichette: [3 2 7 6 0 4]

Costruisci una rete

La struttura di rete residua (Rete residua) è il punto forte della rete ResNet. L'uso della struttura di rete residua da parte di ResNet può alleviare efficacemente il problema del degrado, ottenere una progettazione della struttura di rete più profonda e migliorare la precisione dell'addestramento della rete. Questa sezione descrive innanzitutto come costruire una struttura di rete residua, quindi crea una rete ResNet50 impilando le reti residue.

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

residuo

Esistono due tipi principali di strutture di rete residue: uno è Building Block, adatto per reti ResNet meno profonde, come ResNet18 e ResNet34; l'altro è Bottleneck, adatto per reti ResNet più profonde, come ResNet50, ResNet101 e ResNet152. .

Elemento costitutivo

Il diagramma della struttura a blocchi è mostrato nella figura seguente. Il ramo principale ha una struttura di rete convoluzionale a due strati:

La rete del primo strato del ramo principale prende come esempio il canale di ingresso 64. Innanzitutto passa un 3×3
Attraverso lo strato di convoluzione, poi attraverso lo strato di normalizzazione batch e infine attraverso lo strato di funzione di attivazione Relu, il canale di uscita è 64;
Il canale di ingresso della rete di secondo livello del ramo principale è 64. Passa prima un 3×3
Lo strato convoluzionale viene quindi fatto passare attraverso lo strato di normalizzazione batch e il canale di output è 64.
Infine, vengono aggiunte la matrice delle caratteristiche in uscita dal ramo principale e la matrice delle caratteristiche in uscita dalle scorciatoie, e l'output finale del Building Block viene ottenuto tramite la funzione di attivazione di Relu.

elemento-di-costruzione-5

Quando si aggiungono le matrici di caratteristiche generate dal ramo principale e dai collegamenti, è necessario assicurarsi che la forma delle matrici di caratteristiche generate dal ramo principale e dai collegamenti sia la stessa. Se la forma della matrice delle caratteristiche generata dal ramo principale e dalle scorciatoie è diversa, ad esempio, il canale di output è il doppio del canale di input, le scorciatoie devono utilizzare lo stesso numero del canale di output e una dimensione del kernel di convoluzione 1×1 per il operazione di convoluzione; se l'output Se l'immagine è due volte più piccola dell'immagine di input, il passo nell'operazione di convoluzione nelle scorciatoie deve essere impostato su 2 e anche il passo nell'operazione di convoluzione del primo strato del ramo principale deve essere impostato su 2.

Il codice seguente definisce la classe ResidualBlockBase per implementare la struttura 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

Collo di bottiglia

Il diagramma della struttura Bottleneck è mostrato nella figura seguente Quando l'input è lo stesso, la struttura Bottleneck ha meno parametri della struttura Building Block ed è più adatta per reti con strati più profondi. La struttura residua utilizzata da ResNet50 è Bottleneck. Il ramo principale di questa struttura ha tre strati di struttura a convoluzione, ciascuno dei quali è 1×1
Strato convoluzionale, 3×3
Strati convoluzionali e 1×1
strato convoluzionale, dove 1×1
Gli strati convoluzionali svolgono rispettivamente il ruolo di riduzione della dimensionalità e dimensionalità.

La rete del primo livello del ramo principale prende come esempio il canale di ingresso 256. Il numero del primo passaggio è 64 e la dimensione è 1×1
Il kernel di convoluzione esegue la riduzione della dimensionalità, quindi passa attraverso lo strato di normalizzazione batch e infine passa attraverso lo strato della funzione di attivazione Relu e il suo canale di uscita è 64;
Il numero di passaggi di rete di secondo livello del ramo principale è 64 e la dimensione è 3×3
Il kernel di convoluzione estrae le caratteristiche, quindi passa attraverso il livello di normalizzazione batch e infine passa attraverso il livello della funzione di attivazione Relu e il suo canale di output è 64;
Il numero di passaggi al terzo livello del ramo principale è 256 e la dimensione è 1×1
Il kernel di convoluzione viene dimensionato e quindi fatto passare attraverso il livello di normalizzazione batch e il suo canale di output è 256.
Infine, vengono aggiunte la matrice delle caratteristiche in uscita dal ramo principale e la matrice delle caratteristiche in uscita dalle scorciatoie, e l'output finale di Bottleneck viene ottenuto tramite la funzione di attivazione di Relu.

blocco-di-costruzione-6

Quando si aggiungono le matrici di caratteristiche generate dal ramo principale e dai collegamenti, è necessario assicurarsi che la forma delle matrici di caratteristiche generate dal ramo principale e dai collegamenti sia la stessa. Se la forma della matrice delle caratteristiche generata dal ramo principale e dalle scorciatoie è diversa, ad esempio, se il canale di output è il doppio del canale di input, il numero di scorciatoie deve essere uguale al canale di output e la dimensione è 1×1
Il kernel di convoluzione esegue l'operazione di convoluzione; se l'immagine di output è due volte più piccola dell'immagine di input, lo stride nell'operazione di convoluzione nei collegamenti deve essere impostato su 2 e anche lo stride nell'operazione di convoluzione del secondo livello del ramo principale da impostare a 2.

Il codice seguente definisce la classe ResidualBlock per implementare la struttura 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

Costruisci la rete ResNet50

La struttura del livello di rete ResNet è mostrata nella figura seguente. Prendendo come esempio l'immagine a colori di input 224×224, passare prima il livello di convoluzione conv1 con una quantità di 64, una dimensione del kernel di convoluzione di 7×7 e uno stride di 2. . La dimensione dell'immagine in uscita di questo livello è 112×112, il canale di uscita è 64; quindi attraverso un livello di pooling di downsampling massimo 3×3, la dimensione dell'immagine in uscita di questo livello è 56×56, il canale di uscita è 64; impilare 4 blocchi di rete residui (conv2_x, conv3_x, conv4_x e conv5_x), in questo momento la dimensione dell'immagine di output è 7×7 e il canale di output è 2048 infine, la probabilità di classificazione è ottenuta attraverso uno strato di pooling medio, uno strato completamente connesso; e softmax.

Per ogni blocco di rete residuo, prendendo come esempio conv2_x nella rete ResNet50, è impilato da 3 strutture Bottleneck. Il canale di input di ciascun Bottleneck è 64 e il canale di output è 256.

L'esempio seguente definisce make_layer per implementare la costruzione del blocco residuo e i suoi parametri sono i seguenti:

  • last_out_channel: il numero di canali emessi dalla precedente rete residua.
  • block: Categoria della rete residua, rispettivamente ResidualBlockBase e ResidualBlock.
  • canale: il numero di canali immessi nella rete residua.
  • block_nums: il numero di blocchi di rete residui in pila.
  • passo: il passo del movimento di convoluzione.
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

La rete ResNet50 ha un totale di 5 strutture convoluzionali, un livello di pooling medio e un livello completamente connesso. Prendiamo come esempio il set di dati CIFAR-10:

conv1: la dimensione dell'immagine in ingresso è 32×32 e il canale in ingresso è 3. Innanzitutto, passa attraverso uno strato di convoluzione con un numero di kernel di convoluzione di 64, una dimensione del kernel di convoluzione di 7×7 e uno stride di 2, quindi passa attraverso uno strato di normalizzazione batch e infine passa attraverso la funzione di attivazione di Reul; La dimensione della mappa delle caratteristiche di output di questo livello è 16×16 e il canale di output è 64.

conv2_x: la dimensione della mappa delle funzionalità di input è 16×16 e il canale di input è 64. Innanzitutto, esegue un'operazione di pooling di downsampling massimo con una dimensione del kernel di convoluzione di 3×3 e uno stride di 2; quindi impila tre colli di bottiglia con una struttura di [1×1, 64; 1×1; , 256]. La dimensione della mappa delle caratteristiche di output di questo livello è 8×8 e il canale di output è 256.

conv3_x: la dimensione della mappa delle funzionalità di input è 8×8 e il canale di input è 256. Questo livello raggruppa 4 Colli di bottiglia con una struttura di [1×1, 128; 3×3, 128; La dimensione della mappa delle caratteristiche di output di questo livello è 4×4 e il canale di output è 512.

conv4_x: la dimensione della mappa delle funzionalità di input è 4×4 e il canale di input è 512. Questo livello raggruppa 6 colli di bottiglia con una struttura di [1×1, 256; 3×3, 256; La dimensione della mappa delle caratteristiche di output di questo livello è 2×2 e il canale di output è 1024.

conv5_x: la dimensione della mappa delle funzionalità di input è 2×2 e il canale di input è 1024. Questo livello raggruppa tre colli di bottiglia con una struttura di [1×1, 512; 3×3, 512; La dimensione della mappa delle caratteristiche di output di questo livello è 1×1 e il canale di output è 2048.

pool medio e fc: il canale di input è 2048 e il canale di output è il numero di categorie di classificazione.

Il seguente codice di esempio implementa la costruzione del modello ResNet50. Il modello ResNet50 può essere costruito chiamando la funzione resnet50. I parametri della funzione resnet50 sono i seguenti:

  • num_classes: il numero di categorie per la classificazione. Il numero predefinito di categorie è 1000.
  • preaddestrato: scarica il modello di addestramento corrispondente e carica nella rete i parametri del modello preaddestrato.
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

Inserisci qui la descrizione dell'immagine

Formazione e valutazione del modello

Questa sezione utilizza il modello pre-addestrato ResNet50 per la messa a punto. Chiamare resnet50 per costruire il modello ResNet50 e impostare il parametro preaddestrato su True. Il modello preaddestrato ResNet50 verrà scaricato automaticamente e i parametri nel modello preaddestrato verranno caricati nella rete. Quindi definire l'ottimizzatore e la funzione di perdita, stampare il valore di perdita di addestramento e l'accuratezza della valutazione epoca per epoca e salvare il file ckpt con la massima precisione di valutazione (resnet50-best.ckpt) in ./BestCheckPoint nel percorso corrente.

Poiché la dimensione di output (parametro corrispondente num_classes) del livello completamente connesso (fc) del modello pre-addestrato è 1000, per caricare correttamente i pesi pre-addestrati, impostiamo la dimensione di output completamente connessa del modello su quella predefinita 1000. Sono presenti 10 categorie nel set di dati CIFAR10 Quando si utilizza questo set di dati per l'addestramento, la dimensione di output del livello completamente connesso del modello caricato con pesi preaddestrati deve essere reimpostata su 10.

Qui mostriamo il processo di allenamento di 5 epoche Se si desidera ottenere l'effetto di allenamento ideale, si consiglia di allenarsi per 80 epoche.

# 定义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

Inserisci qui la descrizione dell'immagine

Previsioni del modello visivo

Definire la funzione visualize_model, utilizzare il modello sopra menzionato con la massima precisione di verifica per prevedere il set di dati del test CIFAR-10 e visualizzare i risultati della previsione. Se il colore del carattere della previsione è blu, significa che la previsione è corretta, mentre se il colore del carattere della previsione è rosso significa che la previsione è sbagliata.

Dai risultati di cui sopra si può vedere che l'accuratezza della previsione del modello nel set di dati di verifica in 5 epoche è di circa il 70%, ovvero, in circostanze normali, 2 immagini su 6 non riusciranno a prevedere. Se si desidera ottenere l'effetto di allenamento ideale, si consiglia di allenarsi per 80 epoche.

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

Inserisci qui la descrizione dell'immagine