2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
ResNet résout principalement le problème de « dégradation » des réseaux convolutifs profonds lorsque la profondeur s'approfondit. Dans les réseaux de neurones convolutifs généraux, le premier problème causé par l'augmentation de la profondeur du réseau est la disparition et l'explosion du gradient. Ce problème a été résolu avec succès après que Szegedy a proposé la couche BN. La couche BN peut normaliser la sortie de chaque couche, de sorte que le dégradé puisse toujours rester stable en taille après avoir été passé couche par couche en sens inverse, et ne soit ni trop petit ni trop grand. Cependant, l'auteur a constaté qu'il n'est toujours pas facile de converger après avoir ajouté du BN et augmenté la profondeur. Il a mentionné le deuxième problème - le problème de la baisse de la précision : lorsque le niveau est suffisamment grand dans une certaine mesure, la précision sera saturée. puis diminuer rapidement. Cette diminution n'est pas causée par un surajustement, mais parce que le réseau est trop complexe, de sorte qu'il est difficile d'atteindre le taux d'erreur idéal grâce au seul entraînement en liberté sans contrainte.
Le problème de la diminution de la précision n’est pas un problème lié à la structure du réseau elle-même, mais est dû aux méthodes de formation existantes qui ne sont pas idéales. Les optimiseurs actuellement largement utilisés, qu'il s'agisse de SGD, RMSProp ou Adam, ne peuvent pas atteindre les résultats de convergence théoriquement optimaux lorsque la profondeur du réseau devient plus grande.
Tant qu’il existe une structure de réseau appropriée, un réseau plus profond fonctionnera certainement mieux qu’un réseau moins profond. Le processus de preuve est également très simple : supposons que quelques couches soient ajoutées derrière un réseau A pour former un nouveau réseau B. Si les couches ajoutées effectuent uniquement un mappage d'identité sur la sortie de A, c'est-à-dire que la sortie de A est ajoutée à travers le nouveau réseau A. Il n'y a aucun changement une fois que le niveau devient la sortie de B, donc les taux d'erreur du réseau A et du réseau B sont égaux, ce qui prouve que le réseau après l'approfondissement ne sera pas pire que le réseau avant l'approfondissement.
La classification d'images est l'application de vision par ordinateur la plus basique et appartient à la catégorie de l'apprentissage supervisé. Par exemple, étant donné une image (chat, chien, avion, voiture, etc.), déterminez la catégorie à laquelle appartient l'image. Ce chapitre présentera l'utilisation du réseau ResNet50 pour classer l'ensemble de données CIFAR-10.
Le réseau ResNet50 a été proposé par He Kaiming de Microsoft Labs en 2015 et a remporté la première place au concours de classification d'images ILSVRC2015. Avant la proposition du réseau ResNet, les réseaux neuronaux convolutifs traditionnels étaient obtenus en empilant une série de couches convolutives et en regroupant des couches. Cependant, lorsque le réseau est empilé à une certaine profondeur, des problèmes de dégradation se produiront. La figure ci-dessous est un graphique de l'erreur de formation et de l'erreur de test utilisant un réseau à 56 couches et un réseau à 20 couches sur l'ensemble de données CIFAR-10. Les données de la figure montrent que l'erreur de formation et l'erreur de test. du réseau à 56 couches sont plus grandes que celles du réseau à 20 couches. À mesure que le réseau s'approfondit, l'erreur ne diminue pas comme prévu.
Le réseau ResNet propose une structure de réseau résiduelle (réseau résiduel) pour atténuer le problème de dégradation. L'utilisation du réseau ResNet peut construire une structure de réseau plus profonde (plus de 1000 couches). Le graphique des erreurs de formation et des erreurs de test du réseau ResNet utilisé dans l'article sur l'ensemble de données CIFAR-10 est présenté dans la figure ci-dessous. La ligne pointillée dans la figure représente l'erreur de formation et la ligne continue représente l'erreur de test. Il ressort des données de la figure que plus la couche réseau ResNet est profonde, plus son erreur de formation et son erreur de test sont faibles.
L'ensemble de données CIFAR-10 contient un total de 60 000 images couleur 32*32, divisées en 10 catégories, chaque catégorie contient 6 000 images et l'ensemble de données contient un total de 50 000 images d'entraînement et 10 000 images d'évaluation. Tout d'abord, l'exemple suivant utilise l'interface de téléchargement pour télécharger et décompresser. Actuellement, seule la version binaire des fichiers CIFAR-10 (version binaire CIFAR-10) est prise en charge.
%%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)
'./datasets-cifar10-bin'
La structure des répertoires de l'ensemble de données téléchargé est la suivante :
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
Ensuite, utilisez l'interface mindspore.dataset.Cifar10Dataset pour charger l'ensemble de données et effectuer les opérations d'amélioration d'image associées.
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()
Visualisez l’ensemble de données de formation 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]}")
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()
Forme de l'image : (256, 3, 32, 32), Forme de l'étiquette : (256,)
Étiquettes : [3 2 7 6 0 4]
La structure de réseau résiduelle (réseau résiduel) est le principal point fort du réseau ResNet. L'utilisation par ResNet de la structure de réseau résiduelle peut atténuer efficacement le problème de dégradation, parvenir à une conception de structure de réseau plus profonde et améliorer la précision de la formation du réseau. Cette section décrit d'abord comment créer une structure de réseau résiduelle, puis construit un réseau ResNet50 en empilant les réseaux résiduels.
Construire une structure de réseau résiduelle
残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shotcuts从输入直接到输出,主分支输出的特征矩阵 𝐹(𝑥)
加上shortcuts输出的特征矩阵 𝑥 得到 𝐹(𝑥)+𝑥,通过Relu激活函数后即为残差网络最后的输出。
Il existe deux principaux types de structures de réseau résiduelles : l'une est Building Block, qui convient aux réseaux ResNet moins profonds, tels que ResNet18 et ResNet34 ; l'autre est Bottleneck, qui convient aux réseaux ResNet plus profonds, tels que ResNet50, ResNet101 et ResNet152. .
Le diagramme de structure du Building Block est présenté dans la figure ci-dessous. La branche principale a une structure de réseau convolutionnel à deux couches :
Le réseau de première couche de la branche principale prend comme exemple le canal d'entrée 64. Tout d'abord, il passe un 3×3.
La couche de convolution, puis via la couche de normalisation par lots, et enfin via la couche de fonction d'activation Relu, le canal de sortie est 64 ;
Le canal d'entrée du réseau de deuxième couche de la branche principale est 64. Il passe d'abord un 3×3
La couche convolutive passe ensuite par la couche de normalisation par lots et le canal de sortie est 64.
Enfin, la matrice de fonctionnalités sortie par la branche principale et la matrice de fonctionnalités sortie par raccourcis sont ajoutées, et la sortie finale du Building Block est obtenue via la fonction d'activation Relu.
Lors de l'ajout des matrices de fonctionnalités générées par la branche principale et les raccourcis, vous devez vous assurer que la forme de la matrice de fonctionnalités générée par la branche principale et les raccourcis est la même. Si la forme de la matrice de fonctionnalités produite par la branche principale et les raccourcis est différente, par exemple, le canal de sortie est deux fois le canal d'entrée, les raccourcis doivent utiliser le même nombre de noyaux de convolution que le canal de sortie et la taille de 1 × 1 pour l'opération de convolution ; si la sortie Si l'image est deux fois plus petite que l'image d'entrée, la foulée dans l'opération de convolution dans les raccourcis doit être définie sur 2, et la foulée dans l'opération de convolution de la première couche de la branche principale doit également être réglé sur 2.
Le code suivant définit la classe ResidualBlockBase pour implémenter la structure 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
Le diagramme de structure du goulot d'étranglement est présenté dans la figure ci-dessous. Lorsque l'entrée est la même, la structure du goulot d'étranglement a moins de paramètres que la structure du bloc de construction et est plus adaptée aux réseaux avec des couches plus profondes. La structure résiduelle utilisée par ResNet50 est le goulot d'étranglement. La branche principale de cette structure comporte trois couches de structure de convolution, chacune étant de 1 × 1.
Couche convolutive, 3×3
Couches convolutives et 1×1
couche convolutive, où 1 × 1
Les couches convolutives jouent respectivement le rôle de réduction de dimensionnalité et de dimensionnalité.
Le réseau de première couche de la branche principale prend comme exemple le canal d'entrée 256. Le premier numéro de passe est 64 et la taille est 1 × 1.
Le noyau de convolution effectue une réduction de dimensionnalité, puis passe par la couche de normalisation par lots et passe enfin par la couche de fonction d'activation Relu, et son canal de sortie est 64 ;
Le nombre de passes du réseau de deuxième couche de branche principale est de 64 et la taille est de 3 × 3.
Le noyau de convolution extrait les fonctionnalités, puis passe par la couche de normalisation par lots, et enfin passe par la couche de fonction d'activation Relu, et son canal de sortie est 64 ;
Le nombre de passes au troisième niveau de la branche principale est de 256 et la taille est de 1×1.
Le noyau de convolution est dimensionné, puis passe par la couche de normalisation par lots, et son canal de sortie est 256.
Enfin, la matrice de fonctionnalités sortie par la branche principale et la matrice de fonctionnalités sortie par raccourcis sont ajoutées, et la sortie finale de Bottleneck est obtenue via la fonction d'activation Relu.
Lors de l'ajout des matrices de fonctionnalités générées par la branche principale et les raccourcis, vous devez vous assurer que la forme de la matrice de fonctionnalités générée par la branche principale et les raccourcis est la même. Si la forme de la matrice de fonctionnalités générée par la branche principale et les raccourcis est différente, par exemple, si le canal de sortie est deux fois supérieur au canal d'entrée, le nombre de raccourcis doit être égal au canal de sortie et la taille est de 1 × 1.
Le noyau de convolution effectue l'opération de convolution ; si l'image de sortie est deux fois plus petite que l'image d'entrée, la foulée dans l'opération de convolution dans les raccourcis doit être définie sur 2, et la foulée dans l'opération de convolution de deuxième couche de la branche principale doit également à régler sur 2.
Le code suivant définit la classe ResidualBlock pour implémenter la structure 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
La structure de la couche réseau ResNet est illustrée dans la figure ci-dessous. En prenant l'image couleur d'entrée 224 × 224 comme exemple, passez d'abord la couche de convolution conv1 avec une quantité de 64, une taille de noyau de convolution de 7 × 7 et une foulée de 2. . La taille de l'image de sortie de cette couche est de 112 × 112, le canal de sortie est de 64 ; puis via une couche de regroupement de sous-échantillonnage maximum de 3 × 3, la taille de l'image de sortie de cette couche est de 56 × 56, le canal de sortie est de 64 ; empilez 4 blocs de réseau résiduels (conv2_x, conv3_x, conv4_x et conv5_x), à ce moment la taille de l'image de sortie est de 7 × 7 et le canal de sortie est de 2048, enfin, la probabilité de classification est obtenue via une couche de pooling moyenne, une couche entièrement connectée ; et softmax.
Pour chaque bloc de réseau résiduel, en prenant comme exemple conv2_x dans le réseau ResNet50, il est empilé par 3 structures de goulot d'étranglement. Le canal d'entrée de chaque goulot d'étranglement est 64 et le canal de sortie est 256.
L'exemple suivant définit make_layer pour implémenter la construction du bloc résiduel, et ses paramètres sont les suivants :
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)
Le réseau ResNet50 comprend un total de 5 structures convolutives, une couche de pooling moyenne et une couche entièrement connectée. Prenons l'exemple de l'ensemble de données CIFAR-10 :
conv1 : la taille de l'image d'entrée est de 32 × 32 et le canal d'entrée est de 3. Tout d'abord, il passe par une couche de convolution avec un numéro de noyau de convolution de 64, une taille de noyau de convolution de 7 × 7 et une foulée de 2 ; puis il passe par une couche de normalisation par lots et enfin par la fonction d'activation Reul ; La taille de la carte des entités en sortie de cette couche est de 16 × 16 et le canal de sortie est de 64.
conv2_x : la taille de la carte des caractéristiques d'entrée est de 16 × 16 et le canal d'entrée est de 64. Tout d'abord, il passe par une opération de pooling de sous-échantillonnage maximum avec une taille de noyau de convolution de 3×3 et une foulée de 2 ; puis il empile trois goulots d'étranglement avec un [1×1, 64 ; 3×3, 64 ; ] structure. La taille de la carte des entités en sortie de cette couche est de 8 × 8 et le canal de sortie est de 256.
conv3_x : la taille de la carte des caractéristiques d'entrée est de 8 × 8 et le canal d'entrée est de 256. Cette couche empile 4 goulots d'étranglement avec une structure de [1×1, 128 ; 3×3, 128 ; 1×1, 512]. La taille de la carte des entités en sortie de cette couche est de 4 × 4 et le canal de sortie est de 512.
conv4_x : la taille de la carte des caractéristiques d'entrée est de 4 × 4 et le canal d'entrée est de 512. Cette couche empile 6 goulots d'étranglement avec une structure de [1×1, 256 ; 3×3, 256 ; 1×1, 1024]. La taille de la carte des entités en sortie de cette couche est de 2 × 2 et le canal de sortie est de 1 024.
conv5_x : la taille de la carte des caractéristiques d'entrée est de 2 × 2 et le canal d'entrée est de 1 024. Cette couche empile trois goulots d'étranglement avec une structure de [1×1, 512 ; 3×3, 512 ; 1×1, 2048]. La taille de la carte des entités en sortie de cette couche est de 1 × 1 et le canal de sortie est de 2 048.
pool moyen & fc : Le canal d'entrée est 2048, et le canal de sortie est le nombre de catégories de classification.
L'exemple de code suivant implémente la construction du modèle ResNet50. Le modèle ResNet50 peut être construit en appelant la fonction resnet50. Les paramètres de la fonction resnet50 sont les suivants :
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)
Cette section utilise le modèle pré-entraîné ResNet50 pour un réglage précis. Appelez resnet50 pour construire le modèle ResNet50 et définissez le paramètre pré-entraîné sur True. Le modèle pré-entraîné ResNet50 sera automatiquement téléchargé et les paramètres du modèle pré-entraîné seront chargés dans le réseau. Définissez ensuite l'optimiseur et la fonction de perte, imprimez la valeur de perte d'entraînement et la précision de l'évaluation époque par époque, et enregistrez le fichier ckpt avec la précision d'évaluation la plus élevée (resnet50-best.ckpt) dans ./BestCheckPoint dans le chemin actuel.
Étant donné que la taille de sortie (paramètre num_classes correspondant) de la couche entièrement connectée (fc) du modèle pré-entraîné est de 1 000, afin de charger avec succès les poids pré-entraînés, nous définissons la taille de sortie entièrement connectée du modèle à la valeur par défaut. 1000. L'ensemble de données CIFAR10 comporte un total de 10 catégories. Lors de l'utilisation de cet ensemble de données pour la formation, la taille de sortie de la couche entièrement connectée du modèle chargée avec des poids pré-entraînés doit être réinitialisée à 10.
Nous montrons ici le processus d'entraînement de 5 époques. Si vous souhaitez obtenir l'effet d'entraînement idéal, il est recommandé de s'entraîner pendant 80 époques.
# 定义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)
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
Définissez la fonction visualize_model, utilisez le modèle mentionné ci-dessus avec la précision de vérification la plus élevée pour prédire l'ensemble de données de test CIFAR-10 et visualisez les résultats de la prédiction. Si la couleur de la police de prédiction est bleue, cela signifie que la prédiction est correcte, et si la couleur de la police de prédiction est rouge, cela signifie que la prédiction est fausse.
Il ressort des résultats ci-dessus que la précision de prédiction du modèle dans l'ensemble de données de vérification sous 5 époques est d'environ 70 %, c'est-à-dire que dans des circonstances normales, 2 images sur 6 ne parviendront pas à prédire. Si vous souhaitez obtenir l'effet d'entraînement idéal, il est recommandé de s'entraîner pendant 80 époques.
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)