2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
ResNet löst hauptsächlich das „Degradations“-Problem tiefer Faltungsnetzwerke, wenn die Tiefe vertieft wird. Bei allgemeinen Faltungs-Neuronalen Netzen besteht das erste Problem, das durch die Erhöhung der Tiefe des Netzwerks verursacht wird, darin, dass der Gradient verschwindet und explodiert. Dieses Problem wurde erfolgreich gelöst, nachdem Szegedy die BN-Schicht vorgeschlagen hatte. Die BN-Schicht kann die Ausgabe jeder Schicht normalisieren, sodass die Größe des Gradienten nach dem umgekehrten Durchlaufen von Schicht für Schicht immer noch stabil bleibt und nicht zu klein oder zu groß wird. Der Autor stellte jedoch fest, dass es nach dem Hinzufügen von BN und der Erhöhung der Tiefe immer noch nicht einfach ist, zu konvergieren. Er erwähnte das zweite Problem – das Problem des Genauigkeitsabfalls: Wenn der Pegel bis zu einem gewissen Grad groß genug ist, ist die Genauigkeit gesättigt. und dann schnell abnehmen. Das Verschwinden von Gradienten ist nicht auf eine Überanpassung zurückzuführen, sondern darauf, dass das Netzwerk zu komplex ist, sodass es schwierig ist, die ideale Fehlerrate allein durch uneingeschränktes Freilandtraining zu erreichen.
Das Problem der verringerten Genauigkeit ist kein Problem der Netzwerkstruktur selbst, sondern wird durch die vorhandenen Trainingsmethoden verursacht, die nicht ideal sind. Die derzeit weit verbreiteten Optimierer, sei es SGD, RMSProp oder Adam, können mit zunehmender Netzwerktiefe nicht die theoretisch optimalen Konvergenzergebnisse erzielen.
Solange eine geeignete Netzwerkstruktur vorhanden ist, wird ein tieferes Netzwerk definitiv eine bessere Leistung erbringen als ein flacheres Netzwerk. Der Beweisprozess ist ebenfalls sehr einfach: Angenommen, hinter einem Netzwerk A werden einige Schichten hinzugefügt, um ein neues Netzwerk B zu bilden. Wenn die hinzugefügten Schichten nur eine Identitätszuordnung für die Ausgabe von A durchführen, wird die Ausgabe von A hinzugefügt durch das neue Netzwerk A. Es gibt keine Änderung, nachdem die Ebene zur Ausgabe von B wird, sodass die Fehlerraten von Netzwerk A und Netzwerk B gleich sind, was beweist, dass das Netzwerk nach der Vertiefung nicht schlechter ist als das Netzwerk vor der Vertiefung.
Die Bildklassifizierung ist die grundlegendste Computer-Vision-Anwendung und gehört zur Kategorie des überwachten Lernens. Bestimmen Sie beispielsweise anhand eines Bildes (Katze, Hund, Flugzeug, Auto usw.), zu welcher Kategorie das Bild gehört. In diesem Kapitel wird die Verwendung des ResNet50-Netzwerks zur Klassifizierung des CIFAR-10-Datensatzes vorgestellt.
Das ResNet50-Netzwerk wurde 2015 von He Kaiming von Microsoft Labs vorgeschlagen und gewann den ersten Platz im Bildklassifizierungswettbewerb ILSVRC2015. Bevor das ResNet-Netzwerk vorgeschlagen wurde, wurden herkömmliche Faltungs-Neuronale Netze durch Stapeln einer Reihe von Faltungsschichten und Poolschichten erhalten. Wenn das Netzwerk jedoch bis zu einer bestimmten Tiefe gestapelt wird, treten Verschlechterungsprobleme auf. Die folgende Abbildung ist ein Diagramm des Trainingsfehlers und des Testfehlers unter Verwendung eines 56-Schicht-Netzwerks und eines 20-Schicht-Netzwerks im CIFAR-10-Datensatz. Aus den Daten in der Abbildung ist ersichtlich, dass der Trainingsfehler und der Testfehler vorliegen des 56-Schichten-Netzwerks sind größer als die des 20-Schichten-Netzwerks. Mit zunehmender Tiefe des Netzwerks nimmt der Fehler nicht wie erwartet ab.
Das ResNet-Netzwerk schlägt eine Restnetzwerkstruktur (Restnetzwerk) vor, um das Degradationsproblem zu lindern. Mithilfe des ResNet-Netzwerks kann eine tiefere Netzwerkstruktur (über 1000 Schichten) aufgebaut werden. Das in der Arbeit zum CIFAR-10-Datensatz verwendete Trainingsfehler- und Testfehlerdiagramm des ResNet-Netzwerks ist in der folgenden Abbildung dargestellt. Die gepunktete Linie in der Abbildung stellt den Trainingsfehler und die durchgezogene Linie den Testfehler dar. Aus den Daten in der Abbildung ist ersichtlich, dass der Trainingsfehler und der Testfehler umso kleiner sind, je tiefer die ResNet-Netzwerkschicht liegt.
Der CIFAR-10-Datensatz enthält insgesamt 60.000 32 * 32-Farbbilder, unterteilt in 10 Kategorien, jede Kategorie enthält 6.000 Bilder und der Datensatz enthält insgesamt 50.000 Trainingsbilder und 10.000 Bewertungsbilder. Im folgenden Beispiel wird zunächst die Download-Schnittstelle zum Herunterladen und Dekomprimieren verwendet. Derzeit wird nur die Binärversion von CIFAR-10-Dateien (CIFAR-10-Binärversion) unterstützt.
%%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'
Die Verzeichnisstruktur des heruntergeladenen Datensatzes ist wie folgt:
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
Verwenden Sie dann die Schnittstelle mindspore.dataset.Cifar10Dataset, um den Datensatz zu laden und entsprechende Bildverbesserungsvorgänge durchzuführen.
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()
Visualisieren Sie den CIFAR-10-Trainingsdatensatz.
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()
Bildform: (256, 3, 32, 32), Etikettenform: (256,)
Beschriftungen: [3 2 7 6 0 4]
Die Restnetzwerkstruktur (Restnetzwerk) ist das Hauptmerkmal des ResNet-Netzwerks. Durch die Verwendung der Restnetzwerkstruktur kann ResNet das Verschlechterungsproblem wirksam lindern, ein tieferes Netzwerkstrukturdesign erreichen und die Trainingsgenauigkeit des Netzwerks verbessern. In diesem Abschnitt wird zunächst beschrieben, wie eine Restnetzwerkstruktur erstellt wird, und anschließend wird durch Stapeln von Restnetzwerken ein ResNet50-Netzwerk erstellt.
Bauen Sie eine verbleibende Netzwerkstruktur auf
残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shotcuts从输入直接到输出,主分支输出的特征矩阵 𝐹(𝑥)
加上shortcuts输出的特征矩阵 𝑥 得到 𝐹(𝑥)+𝑥,通过Relu激活函数后即为残差网络最后的输出。
Es gibt zwei Haupttypen von Restnetzwerkstrukturen: Die eine ist Building Block und eignet sich für flachere ResNet-Netzwerke wie ResNet18 und ResNet34. Die andere ist Bottleneck und eignet sich für tiefere ResNet-Netzwerke wie ResNet50, ResNet101 und ResNet152. .
Das Strukturdiagramm des Bausteins ist in der folgenden Abbildung dargestellt. Der Hauptzweig hat eine zweischichtige Faltungsnetzwerkstruktur:
Das Netzwerk der ersten Schicht des Hauptzweigs verwendet als Beispiel den Eingangskanal 64. Zuerst wird ein 3×3 übergeben
Die Faltungsschicht, dann die Batch-Normalisierungsschicht und schließlich die Relu-Aktivierungsfunktionsschicht, der Ausgabekanal ist 64;
Der Eingangskanal des Netzwerks der zweiten Schicht des Hauptzweigs ist 64. Er übergibt zunächst ein 3×3
Die Faltungsschicht wird dann durch die Stapelnormalisierungsschicht geleitet und der Ausgabekanal ist 64.
Schließlich werden die vom Hauptzweig ausgegebene Merkmalsmatrix und die von Verknüpfungen ausgegebene Merkmalsmatrix hinzugefügt, und die endgültige Ausgabe des Bausteins wird über die Relu-Aktivierungsfunktion erhalten.
Wenn Sie die vom Hauptzweig und den Verknüpfungen ausgegebenen Feature-Matrizen hinzufügen, müssen Sie sicherstellen, dass die Form der vom Hauptzweig und den Verknüpfungen ausgegebenen Feature-Matrix dieselbe ist. Wenn die vom Hauptzweig und den Verknüpfungen ausgegebene Merkmalsmatrixform unterschiedlich ist, z. B. der Ausgabekanal doppelt so groß ist wie der Eingabekanal, müssen die Verknüpfungen die gleiche Anzahl von Faltungskernen wie der Ausgabekanal und die Größe 1 × 1 verwenden die Faltungsoperation; wenn das Ausgabebild doppelt so klein ist wie das Eingabebild, muss der Schritt in der Faltungsoperation in Verknüpfungen auf 2 gesetzt werden, und der Schritt in der Faltungsoperation der ersten Schicht des Hauptzweigs muss ebenfalls eingestellt werden auf 2 gesetzt werden.
Der folgende Code definiert die ResidualBlockBase-Klasse zur Implementierung der Building Block-Struktur.
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
Das Bottleneck-Strukturdiagramm ist in der folgenden Abbildung dargestellt. Bei gleicher Eingabe hat die Bottleneck-Struktur weniger Parameter als die Building Block-Struktur und eignet sich besser für Netzwerke mit tieferen Schichten. Die von ResNet50 verwendete Reststruktur ist Bottleneck. Der Hauptzweig dieser Struktur besteht aus drei Schichten einer Faltungsstruktur, von denen jede 1 × 1 ist
Faltungsschicht, 3×3
Faltungsschichten und 1×1
Faltungsschicht, wo 1×1
Die Faltungsschichten spielen die Rolle der Dimensionsreduktion bzw. der Dimensionalität.
Das Netzwerk der ersten Schicht des Hauptzweigs verwendet als Beispiel den Eingangskanal 256. Die erste Durchgangsnummer beträgt 64 und die Größe beträgt 1 × 1
Der Faltungskern führt eine Dimensionsreduzierung durch, durchläuft dann die Stapelnormalisierungsschicht und schließlich die Relu-Aktivierungsfunktionsschicht. Sein Ausgabekanal ist 64;
Die Anzahl der Hauptzweig-Netzwerkdurchgänge der zweiten Schicht beträgt 64 und die Größe beträgt 3×3
Der Faltungskern extrahiert Merkmale, durchläuft dann die Stapelnormalisierungsschicht und schließlich die Relu-Aktivierungsfunktionsschicht. Sein Ausgabekanal ist 64.
Die Anzahl der Durchgänge auf der dritten Ebene des Hauptzweigs beträgt 256 und die Größe beträgt 1 × 1
Der Faltungskern wird dimensioniert und dann durch die Stapelnormalisierungsschicht geleitet. Sein Ausgabekanal ist 256.
Schließlich werden die vom Hauptzweig ausgegebene Merkmalsmatrix und die von Verknüpfungen ausgegebene Merkmalsmatrix hinzugefügt, und die endgültige Ausgabe von Bottleneck wird über die Relu-Aktivierungsfunktion erhalten.
Wenn Sie die vom Hauptzweig und den Verknüpfungen ausgegebenen Feature-Matrizen hinzufügen, müssen Sie sicherstellen, dass die Form der vom Hauptzweig und den Verknüpfungen ausgegebenen Feature-Matrix dieselbe ist. Wenn die vom Hauptzweig und den Verknüpfungen ausgegebene Feature-Matrix-Form unterschiedlich ist, beispielsweise wenn der Ausgabekanal doppelt so groß ist wie der Eingabekanal, muss die Anzahl der Verknüpfungen gleich dem Ausgabekanal sein und die Größe beträgt 1 × 1
Der Faltungskern führt die Faltungsoperation aus. Wenn das Ausgabebild doppelt so groß ist wie das Eingabebild, muss der Schritt in der Faltungsoperation in Verknüpfungen auf 2 gesetzt werden, und der Schritt in der Faltungsoperation der zweiten Ebene des Hauptzweigs muss ebenfalls festgelegt werden auf 2 gesetzt werden.
Der folgende Code definiert die ResidualBlock-Klasse zur Implementierung der Bottleneck-Struktur.
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
Die Struktur der ResNet-Netzwerkschicht ist in der folgenden Abbildung dargestellt. Am Beispiel des Eingabefarbbilds 224 × 224 übergeben Sie zunächst die Faltungsschicht conv1 mit einer Menge von 64, einer Faltungskerngröße von 7 × 7 und einem Schritt von 2 Die Ausgabebildgröße dieser Ebene beträgt 112 × 112, der Ausgabekanal beträgt dann 64; Stapeln Sie 4 verbleibende Netzwerkblöcke (conv2_x, conv3_x, conv4_x und conv5_x). Zu diesem Zeitpunkt beträgt die Ausgabebildgröße 7 × 7 und der Ausgabekanal beträgt 2048. Schließlich wird die Klassifizierungswahrscheinlichkeit über eine durchschnittliche Pooling-Schicht, eine vollständig verbundene Schicht, ermittelt und Softmax.
Für jeden verbleibenden Netzwerkblock wird am Beispiel von conv2_x im ResNet50-Netzwerk 3 Engpassstrukturen gestapelt. Der Eingangskanal jedes Engpasses beträgt 64 und der Ausgangskanal beträgt 256.
Das folgende Beispiel definiert make_layer, um die Konstruktion des Restblocks zu implementieren, und seine Parameter lauten wie folgt:
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)
Das ResNet50-Netzwerk verfügt über insgesamt 5 Faltungsstrukturen, eine durchschnittliche Pooling-Schicht und eine vollständig verbundene Schicht. Nehmen Sie den CIFAR-10-Datensatz als Beispiel:
conv1: Die Eingabebildgröße beträgt 32 × 32 und der Eingabekanal ist 3. Zuerst durchläuft es eine Faltungsschicht mit einer Faltungskernzahl von 64, einer Faltungskerngröße von 7×7 und einem Schritt von 2; dann durchläuft es eine Batch-Normalisierungsschicht und schließlich die Reul-Aktivierungsfunktion. Die Ausgabe-Feature-Map-Größe dieser Ebene beträgt 16 × 16 und der Ausgabekanal beträgt 64.
conv2_x: Die Größe der Eingabe-Feature-Map beträgt 16 × 16 und der Eingabekanal beträgt 64. Zuerst durchläuft es eine maximale Downsampling-Pooling-Operation mit einer Faltungskerngröße von 3×3 und einem Schritt von 2; dann werden drei Engpässe mit einem [1×1, 64; 1×1, 256; ] Struktur. Die Ausgabe-Feature-Map-Größe dieser Ebene beträgt 8 × 8 und der Ausgabekanal beträgt 256.
conv3_x: Die Größe der Eingabe-Feature-Map beträgt 8 × 8 und der Eingabekanal beträgt 256. Diese Schicht stapelt 4 Engpässe mit einer Struktur von [1×1, 128; 1×1, 512]. Die Ausgabe-Feature-Map-Größe dieser Ebene beträgt 4 × 4 und der Ausgabekanal beträgt 512.
conv4_x: Die Größe der Eingabe-Feature-Map beträgt 4 × 4 und der Eingabekanal beträgt 512. Diese Schicht stapelt 6 Engpässe mit einer Struktur von [1×1, 256; 1×1, 1024]. Die Ausgabe-Feature-Map-Größe dieser Ebene beträgt 2 × 2 und der Ausgabekanal beträgt 1024.
conv5_x: Die Größe der Eingabe-Feature-Map beträgt 2 × 2 und der Eingabekanal beträgt 1024. Diese Schicht stapelt drei Engpässe mit einer Struktur von [1×1, 512; 1×1, 2048]. Die Ausgabe-Feature-Map-Größe dieser Ebene beträgt 1 × 1 und der Ausgabekanal beträgt 2048.
Durchschnittlicher Pool und fc: Der Eingabekanal ist 2048 und der Ausgabekanal ist die Anzahl der Klassifizierungskategorien.
Der folgende Beispielcode implementiert die Konstruktion des ResNet50-Modells. Das ResNet50-Modell kann durch Aufrufen der Funktion resnet50 erstellt werden.
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)
In diesem Abschnitt wird das vorab trainierte ResNet50-Modell zur Feinabstimmung verwendet. Rufen Sie resnet50 auf, um das ResNet50-Modell zu erstellen, und setzen Sie den vorab trainierten Parameter auf True. Das vorab trainierte ResNet50-Modell wird automatisch heruntergeladen und die Parameter im vorab trainierten Modell werden in das Netzwerk geladen. Definieren Sie dann den Optimierer und die Verlustfunktion, drucken Sie den Trainingsverlustwert und die Bewertungsgenauigkeit Epoche für Epoche aus und speichern Sie die ckpt-Datei mit der höchsten Bewertungsgenauigkeit (resnet50-best.ckpt) unter ./BestCheckPoint im aktuellen Pfad.
Da die Ausgabegröße (entsprechender Parameter num_classes) der vollständig verbundenen Schicht (fc) des vorab trainierten Modells 1000 beträgt, setzen wir die vollständig verbundene Ausgabegröße des Modells auf den Standardwert, um die vorab trainierten Gewichte erfolgreich zu laden 1000. Der CIFAR10-Datensatz verfügt über insgesamt 10 Kategorien. Wenn dieser Datensatz für das Training verwendet wird, muss die Ausgabegröße der vollständig verbundenen Schicht des mit vorab trainierten Gewichten geladenen Modells auf 10 zurückgesetzt werden.
Hier zeigen wir den Trainingsablauf von 5 Epochen. Wenn Sie den idealen Trainingseffekt erzielen möchten, empfiehlt es sich, 80 Epochen lang zu trainieren.
# 定义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
Definieren Sie die Funktion visualize_model, verwenden Sie das oben genannte Modell mit der höchsten Verifizierungsgenauigkeit, um den CIFAR-10-Testdatensatz vorherzusagen, und visualisieren Sie die Vorhersageergebnisse. Wenn die Schriftfarbe der Vorhersage blau ist, bedeutet dies, dass die Vorhersage korrekt ist, und wenn die Schriftfarbe der Vorhersage rot ist, bedeutet dies, dass die Vorhersage falsch ist.
Aus den obigen Ergebnissen ist ersichtlich, dass die Vorhersagegenauigkeit des Modells im Verifizierungsdatensatz unter 5 Epochen etwa 70 % beträgt, was bedeutet, dass unter normalen Umständen 2 von 6 Bildern nicht vorhergesagt werden können. Wenn Sie den idealen Trainingseffekt erzielen möchten, empfiehlt es sich, 80 Epochen lang zu trainieren.
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)