Mi informacion de contacto
Correo[email protected]
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
ResNet resuelve principalmente el problema de "degradación" de las redes convolucionales profundas cuando se profundiza la profundidad. En las redes neuronales convolucionales generales, el primer problema causado por el aumento de la profundidad de la red es la desaparición y explosión del gradiente. Este problema se resolvió con éxito después de que Szegedy propuso la capa BN. La capa BN puede normalizar la salida de cada capa, de modo que el gradiente aún pueda permanecer estable en tamaño después de pasar capa por capa en reversa, y no será demasiado pequeño ni demasiado grande. Sin embargo, el autor descubrió que todavía no es fácil converger después de agregar BN y aumentar la profundidad. Mencionó el segundo problema: el problema de la disminución de la precisión: cuando el nivel es lo suficientemente grande hasta cierto punto, la precisión se saturará. y luego disminuir rápidamente Esta disminución no es La desaparición de los gradientes no se debe a un sobreajuste, sino a que la red es demasiado compleja, por lo que es difícil lograr la tasa de error ideal solo con un entrenamiento de rango libre sin restricciones.
El problema de la disminución de la precisión no es un problema de la estructura de la red en sí, sino que es causado por los métodos de entrenamiento existentes que no son ideales. Los optimizadores ampliamente utilizados actualmente, ya sea SGD, RMSProp o Adam, no pueden lograr los resultados de convergencia teóricamente óptimos cuando la profundidad de la red aumenta.
Siempre que exista una estructura de red adecuada, una red más profunda definitivamente funcionará mejor que una red menos profunda. El proceso de prueba también es muy simple: suponga que se agregan algunas capas detrás de una red A para formar una nueva red B. Si las capas agregadas solo realizan un mapeo de identidad en la salida de A, es decir, se agrega la salida de A a través de la nueva red A. No hay cambios después de que el nivel se convierte en la salida de B, por lo que las tasas de error de la red A y la red B son iguales, lo que demuestra que la red después de la profundización no será peor que la red antes de la profundización.
La clasificación de imágenes es la aplicación de visión por computadora más básica y pertenece a la categoría de aprendizaje supervisado. Por ejemplo, dada una imagen (gato, perro, avión, automóvil, etc.), determine la categoría a la que pertenece la imagen. Este capítulo presentará el uso de la red ResNet50 para clasificar el conjunto de datos CIFAR-10.
La red ResNet50 fue propuesta por He Kaiming de Microsoft Labs en 2015 y ganó el primer lugar en el concurso de clasificación de imágenes ILSVRC2015. Antes de que se propusiera la red ResNet, las redes neuronales convolucionales tradicionales se obtenían apilando una serie de capas convolucionales y agrupando capas. Sin embargo, cuando la red se apila hasta una cierta profundidad, se producirán problemas de degradación. La siguiente figura es un gráfico del error de entrenamiento y el error de prueba utilizando una red de 56 capas y una red de 20 capas en el conjunto de datos CIFAR-10. Se puede ver en los datos de la figura que el error de entrenamiento y el error de prueba. La red de 56 capas es más grande que la de la red de 20 capas. A medida que la red se profundiza, el error no disminuye como se esperaba.
La red ResNet propone una estructura de red residual (red residual) para aliviar el problema de degradación. El uso de la red ResNet puede construir una estructura de red más profunda (más de 1000 capas). El gráfico de error de entrenamiento y error de prueba de la red ResNet utilizado en el documento sobre el conjunto de datos CIFAR-10 se muestra en la siguiente figura. La línea de puntos en la figura representa el error de entrenamiento y la línea continua representa el error de prueba. Se puede ver en los datos de la figura que cuanto más profunda es la capa de red ResNet, menor es el error de entrenamiento y el error de prueba.
El conjunto de datos CIFAR-10 tiene un total de 60.000 imágenes en color de 32 * 32, divididas en 10 categorías, cada categoría tiene 6.000 imágenes y el conjunto de datos tiene un total de 50.000 imágenes de entrenamiento y 10.000 imágenes de evaluación. Primero, el siguiente ejemplo utiliza la interfaz de descarga para descargar y descomprimir. Actualmente, solo se admite la versión binaria de los archivos CIFAR-10 (versión 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)
'./conjuntos-de-datos-cifar10-bin'
La estructura de directorios del conjunto de datos descargado es la siguiente:
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
Luego, use la interfaz mindspore.dataset.Cifar10Dataset para cargar el conjunto de datos y realizar operaciones de mejora de imágenes 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()
Visualice el conjunto de datos de entrenamiento 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()
Forma de la imagen: (256, 3, 32, 32), Forma de la etiqueta: (256,)
Etiquetas: [3 2 7 6 0 4]
La estructura de red residual (red residual) es el punto culminante principal de la red ResNet. El uso de la estructura de red residual por parte de ResNet puede aliviar eficazmente el problema de degradación, lograr un diseño de estructura de red más profundo y mejorar la precisión del entrenamiento de la red. Esta sección primero describe cómo construir una estructura de red residual y luego construye una red ResNet50 apilando redes residuales.
Construir una estructura de red residual
残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shotcuts从输入直接到输出,主分支输出的特征矩阵 𝐹(𝑥)
加上shortcuts输出的特征矩阵 𝑥 得到 𝐹(𝑥)+𝑥,通过Relu激活函数后即为残差网络最后的输出。
Hay dos tipos principales de estructuras de red residuales: uno es Building Block, que es adecuado para redes ResNet menos profundas, como ResNet18 y ResNet34; el otro es Bottleneck, que es adecuado para redes ResNet más profundas, como ResNet50, ResNet101 y ResNet152. .
El diagrama de estructura del bloque de construcción se muestra en la siguiente figura. La rama principal tiene una estructura de red convolucional de dos capas:
La red de primera capa de la rama principal toma el canal de entrada 64 como ejemplo. Primero, pasa un 3 × 3.
La capa de convolución, luego la capa de normalización por lotes y finalmente la capa de función de activación Relu, el canal de salida es 64;
El canal de entrada de la red de segunda capa de la rama principal es 64. Primero pasa un 3×3
Luego, la capa convolucional pasa a través de la capa de normalización por lotes y el canal de salida es 64.
Finalmente, se agregan la salida de la matriz de características de la rama principal y la salida de la matriz de características de los accesos directos, y la salida final del Building Block se obtiene a través de la función de activación de Relu.
Al agregar las matrices de características generadas por la rama principal y los accesos directos, debe asegurarse de que la forma de las matrices de características generadas por la rama principal y los accesos directos sea la misma. Si la forma de la matriz de características generada por la rama principal y los accesos directos es diferente, por ejemplo, el canal de salida es el doble del canal de entrada, los accesos directos deben usar el mismo número que el canal de salida y un tamaño de núcleo de convolución 1×1 para el operación de convolución; si la imagen de salida es dos veces más pequeña que la imagen de entrada, el paso en la operación de convolución en los accesos directos debe establecerse en 2, y el paso en la operación de convolución de la primera capa de la rama principal también debe ser establecido en 2.
El siguiente código define la clase ResidualBlockBase para implementar la estructura de 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
El diagrama de la estructura del cuello de botella se muestra en la figura siguiente. Cuando la entrada es la misma, la estructura del cuello de botella tiene menos parámetros que la estructura del bloque de construcción y es más adecuada para redes con capas más profundas. La estructura residual utilizada por ResNet50 es el cuello de botella. La rama principal de esta estructura tiene tres capas de estructura convolucional, cada una de las cuales es 1×1
Capa convolucional, 3×3
Capas convolucionales y 1×1
capa convolucional, donde 1×1
Las capas convolucionales desempeñan el papel de reducción de dimensionalidad y dimensionalidad respectivamente.
La red de primera capa de la rama principal toma el canal de entrada como 256 como ejemplo. El número del primer paso es 64 y el tamaño es 1 × 1.
El núcleo de convolución realiza una reducción de dimensionalidad, luego pasa a través de la capa de normalización por lotes y finalmente pasa a través de la capa de función de activación Relu, y su canal de salida es 64;
El número de pases de red de segunda capa de la sucursal principal es 64 y el tamaño es 3 × 3
El núcleo de convolución extrae características, luego pasa a través de la capa de normalización por lotes y finalmente pasa a través de la capa de función de activación de Relu, y su canal de salida es 64;
El número de pases en el tercer nivel de la sucursal principal es 256 y el tamaño es 1×1
El núcleo de convolución se dimensiona y luego se pasa a través de la capa de normalización por lotes y su canal de salida es 256.
Finalmente, se agregan la salida de la matriz de características de la rama principal y la salida de la matriz de características de los accesos directos, y la salida final de Bottleneck se obtiene a través de la función de activación de Relu.
Al agregar las matrices de características generadas por la rama principal y los accesos directos, debe asegurarse de que la forma de las matrices de características generadas por la rama principal y los accesos directos sea la misma. Si la forma de la matriz de características generada por la rama principal y los atajos es diferente, por ejemplo, si el canal de salida es el doble del canal de entrada, el número de atajos debe ser igual al canal de salida y el tamaño es 1 × 1.
El núcleo de convolución realiza la operación de convolución; si la imagen de salida es dos veces más pequeña que la imagen de entrada, el paso en la operación de convolución en los accesos directos debe establecerse en 2, y el paso en la operación de convolución de la segunda capa de la rama principal también debe establecerse en 2. que se establecerá en 2.
El siguiente código define la clase ResidualBlock para implementar la estructura 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 estructura de la capa de red ResNet se muestra en la siguiente figura. Tomando la imagen en color de entrada 224 × 224 como ejemplo, primero pase la capa convolucional conv1 con una cantidad de 64, un tamaño de núcleo de convolución de 7 × 7 y un paso de 2. El tamaño de la imagen de salida de esta capa es 112 × 112, el canal de salida es 64; luego, a través de una capa de agrupación de reducción de resolución máxima de 3 × 3, el tamaño de la imagen de salida de esta capa es 56 × 56, y luego el canal de salida es 64; apile 4 bloques de red residuales (conv2_x, conv3_x, conv4_x y conv5_x). En este momento, el tamaño de la imagen de salida es 7 × 7 y el canal de salida es 2048. Finalmente, la probabilidad de clasificación se obtiene a través de una capa de agrupación promedio, una capa completamente conectada; y softmax.
Para cada bloque de red residual, tomando conv2_x en la red ResNet50 como ejemplo, está apilado por 3 estructuras de cuello de botella. El canal de entrada de cada cuello de botella es 64 y el canal de salida es 256.
El siguiente ejemplo define make_layer para implementar la construcción del bloque residual y sus parámetros son los siguientes:
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)
La red ResNet50 tiene un total de 5 estructuras convolucionales, una capa de agrupación promedio y una capa completamente conectada. Tome el conjunto de datos CIFAR-10 como ejemplo:
conv1: el tamaño de la imagen de entrada es 32 × 32 y el canal de entrada es 3. Primero, pasa por una capa de convolución con un número de núcleo de convolución de 64, un tamaño de núcleo de convolución de 7 × 7 y un paso de 2; luego pasa por una capa de normalización por lotes y finalmente pasa por la función de activación de Reul; El tamaño del mapa de características de salida de esta capa es 16 × 16 y el canal de salida es 64.
conv2_x: el tamaño del mapa de características de entrada es 16 × 16 y el canal de entrada es 64. Primero, realiza una operación de agrupación de reducción de resolución máxima con un tamaño de núcleo de convolución de 3 × 3 y un paso de 2, luego apila tres cuellos de botella con una estructura de [1 × 1, 3 × 3, 1 × 1; , 256]. El tamaño del mapa de características de salida de esta capa es 8 × 8 y el canal de salida es 256.
conv3_x: el tamaño del mapa de características de entrada es 8 × 8 y el canal de entrada es 256. Esta capa apila 4 cuellos de botella con una estructura de [1×1, 128; 3×3, 128; El tamaño del mapa de características de salida de esta capa es 4 × 4 y el canal de salida es 512.
conv4_x: el tamaño del mapa de características de entrada es 4 × 4 y el canal de entrada es 512. Esta capa apila 6 cuellos de botella con una estructura de [1×1, 256; 3×3, 256; El tamaño del mapa de características de salida de esta capa es 2 × 2 y el canal de salida es 1024.
conv5_x: el tamaño del mapa de características de entrada es 2 × 2 y el canal de entrada es 1024. Esta capa apila tres cuellos de botella con una estructura de [1×1, 512; 3×3, 512; El tamaño del mapa de características de salida de esta capa es 1 × 1 y el canal de salida es 2048.
pool promedio y fc: el canal de entrada es 2048 y el canal de salida es el número de categorías de clasificación.
El siguiente código de ejemplo implementa la construcción del modelo ResNet50. El modelo ResNet50 se puede construir llamando a la función resnet50. Los parámetros de la función resnet50 son los siguientes:
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)
Esta sección utiliza el modelo previamente entrenado ResNet50 para realizar ajustes. Llame a resnet50 para construir el modelo ResNet50 y establezca el parámetro previamente entrenado en True. El modelo ResNet50 previamente entrenado se descargará automáticamente y los parámetros del modelo previamente entrenado se cargarán en la red. Luego defina el optimizador y la función de pérdida, imprima el valor de pérdida de entrenamiento y la precisión de la evaluación época por época y guarde el archivo ckpt con la mayor precisión de evaluación (resnet50-best.ckpt) en ./BestCheckPoint en la ruta actual.
Dado que el tamaño de salida (parámetro correspondiente num_classes) de la capa completamente conectada (fc) del modelo previamente entrenado es 1000, para cargar con éxito los pesos previamente entrenados, configuramos el tamaño de salida completamente conectado del modelo al valor predeterminado. 1000. Hay 10 categorías en el conjunto de datos CIFAR10. Cuando se utiliza este conjunto de datos para el entrenamiento, el tamaño de salida de la capa completamente conectada del modelo cargado con pesos previamente entrenados debe restablecerse a 10.
Aquí mostramos el proceso de entrenamiento de 5 épocas. Si desea lograr el efecto de entrenamiento ideal, se recomienda entrenar durante 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)
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
Defina la función visualize_model, utilice el modelo mencionado anteriormente con la mayor precisión de verificación para predecir el conjunto de datos de prueba CIFAR-10 y visualice los resultados de la predicción. Si el color de la fuente de predicción es azul, significa que la predicción es correcta y si el color de la fuente de predicción es rojo, significa que la predicción es incorrecta.
De los resultados anteriores se puede ver que la precisión de la predicción del modelo en el conjunto de datos de verificación en 5 épocas es aproximadamente del 70%, es decir, en circunstancias normales, 2 de cada 6 imágenes no podrán predecir. Si desea lograr el efecto de entrenamiento ideal, se recomienda entrenar 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)