2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
ResNet ratkaisee pääasiassa syvien konvoluutioverkkojen "hajoamisongelman", kun syvyyttä syvetään. Yleisissä konvoluutiohermoverkoissa ensimmäinen verkon syvyyden lisäämisen aiheuttama ongelma on gradientin katoaminen ja räjähdys Tämä ongelma ratkaistiin onnistuneesti, kun Szegedy ehdotti BN-kerrosta. BN-kerros voi normalisoida kunkin kerroksen ulostulon niin, että gradientti voi silti pysyä kooltaan vakaana sen jälkeen, kun se on kuljetettu kerros kerrokselta päinvastoin, eikä se ole liian pieni tai liian suuri. Kirjoittaja havaitsi kuitenkin, että BN:n lisäämisen ja syvyyden lisäämisen jälkeen ei ole vielä helppoa lähentää. Hän mainitsi toisen ongelman - tarkkuuden heikkenemisen ongelman: kun taso on riittävän suuri tietyssä määrin, tarkkuus kyllästyy, ja sitten vähenevät nopeasti Tämä lasku ei ole Gradienttien katoaminen ei johdu ylisovituksesta, vaan siitä, että verkko on liian monimutkainen, joten on vaikea saavuttaa ihanteellista virhetasoa pelkällä vapaan kantaman harjoittelulla.
Tarkkuuden heikkenemisen ongelma ei ole itse verkkorakenteen ongelma, vaan se johtuu olemassa olevista koulutusmenetelmistä, jotka eivät ole ihanteellisia. Tällä hetkellä laajasti käytetyt optimoijat, olivatpa kyseessä SGD, RMSProp tai Adam, eivät voi saavuttaa teoreettisesti optimaalisia konvergenssituloksia, kun verkon syvyys kasvaa.
Niin kauan kuin sopiva verkkorakenne on olemassa, syvemmällä verkko toimii varmasti paremmin kuin matalampi verkko. Todistusprosessi on myös hyvin yksinkertainen: Oletetaan, että verkon A taakse lisätään muutama kerros uuden verkon B muodostamiseksi. Jos lisätyt kerrokset suorittavat vain identiteettikartoituksen A:n lähdölle, eli A:n lähtö lisätään. uuden verkon A kautta. Sen jälkeen kun tasosta tulee B:n lähtö, ei tapahdu muutosta, joten verkon A ja verkon B virheprosentit ovat yhtä suuret, mikä osoittaa, että verkko ei syventämisen jälkeen ole huonompi kuin verkko ennen syventämistä.
Kuvan luokittelu on yksinkertaisin tietokonenäkösovellus ja se kuuluu ohjatun oppimisen luokkaan. Määritä esimerkiksi kuva (kissa, koira, lentokone, auto jne.) luokka, johon kuva kuuluu. Tämä luku esittelee ResNet50-verkon käytön CIFAR-10-tietojoukon luokittelussa.
Microsoft Labsin He Kaiming ehdotti ResNet50-verkkoa vuonna 2015, ja se voitti ensimmäisen sijan ILSVRC2015-kuvaluokituskilpailussa. Ennen kuin ResNet-verkko ehdotettiin, perinteiset konvoluutiohermoverkot saatiin pinoamalla sarja konvoluutiokerroksia ja yhdistämällä kerroksia. Kuitenkin, kun verkko pinotaan tiettyyn syvyyteen, ilmenee huononemisongelmia. Alla oleva kuva on kaavio harjoitusvirheestä ja testivirheestä käyttämällä 56-kerroksista verkkoa ja 20-kerroksista verkkoa CIFAR-10-tietojoukossa. Kuvan tiedoista näkyy, että harjoitusvirhe ja testivirhe 56-kerroksisesta verkosta ovat suurempia kuin 20-kerroksisissa verkoissa. Koska verkko syvenee, virhe ei vähene odotetulla tavalla.
ResNet-verkko ehdottaa jäännösverkkorakennetta (Residual Network) vähentämään huononemisongelmaa ResNet-verkon avulla voidaan rakentaa syvempi verkkorakenne (yli 1000 kerrosta). CIFAR-10-tietojoukon paperissa käytetty ResNet-verkon harjoitusvirhe- ja testivirhekaavio on esitetty alla olevassa kuvassa. Kuvan katkoviiva edustaa harjoitusvirhettä ja yhtenäinen viiva testivirhettä. Kuvan tiedoista näkyy, että mitä syvemmällä ResNet-verkkokerros on, sitä pienempi on sen opetus- ja testivirhe.
CIFAR-10-tietojoukossa on yhteensä 60 000 32*32 värikuvaa, jotka on jaettu 10 luokkaan, kussakin kategoriassa on 6 000 kuvaa ja tietojoukossa on yhteensä 50 000 harjoituskuvaa ja 10 000 arviointikuvaa. Ensinnäkin seuraava esimerkki käyttää latausliittymää lataamiseen ja purkamiseen. Tällä hetkellä vain CIFAR-10-tiedostojen binaariversiota tuetaan.
%%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'
Ladatun tietojoukon hakemistorakenne on seuraava:
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
Lataa sitten tietojoukko mindspore.dataset.Cifar10Dataset-käyttöliittymän avulla ja suorita siihen liittyvät kuvanparannustoiminnot.
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()
Visualisoi CIFAR-10 harjoitustietojoukko.
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()
Kuvan muoto: (256, 3, 32, 32), tarran muoto: (256,)
Tunnisteet: [3 2 7 6 0 4]
Jäljellä oleva verkkorakenne (Residual Network) on ResNet-verkon tärkein kohokohta, kun ResNet käyttää jäännösverkkorakennetta, sillä se voi tehokkaasti lievittää huononemisongelmaa, saavuttaa syvemmän verkkorakenteen suunnittelun ja parantaa verkon koulutustarkkuutta. Tässä osiossa kuvataan ensin jäännösverkkorakenteen rakentaminen ja sitten ResNet50-verkon rakentaminen pinoamalla jäännösverkkoja.
Rakenna jäännösverkkorakenne
残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shotcuts从输入直接到输出,主分支输出的特征矩阵 𝐹(𝑥)
加上shortcuts输出的特征矩阵 𝑥 得到 𝐹(𝑥)+𝑥,通过Relu激活函数后即为残差网络最后的输出。
Jäännösverkkorakenteita on kaksi päätyyppiä. Yksi on Building Block, joka sopii matalampiin ResNet-verkkoihin, kuten ResNet18 ja ResNet34, toinen on Bottleneck, joka sopii syvemmille ResNet-verkkoille, kuten ResNet50, ResNet101 ja ResNet152. .
Rakennuslohkon rakennekaavio on esitetty alla olevassa kuvassa. Päähaaralla on kaksikerroksinen konvoluutioverkkorakenne:
Päähaaran ensimmäisen kerroksen verkko ottaa esimerkkinä tulokanavan 64 Ensinnäkin se kulkee läpi 3×3
Konvoluutiokerroksen, sitten Erän normalisointikerroksen ja lopuksi Relu-aktivointifunktiokerroksen kautta lähtökanava on 64;
Päähaaran toisen kerroksen verkon tulokanava on 64. Se läpäisee ensin 3×3
Konvoluutiokerros johdetaan sitten eränormalisointikerroksen läpi, ja lähtökanava on 64.
Lopuksi lisätään päähaaran ominaisuusmatriisilähtö ja pikanäppäinten ominaisuusmatriisilähtö, ja Building Blockin lopullinen tulos saadaan Relu-aktivointitoiminnolla.
Kun lisäät päähaaran ja pikanäppäimien tuottamia piirrematriiseja, sinun on varmistettava, että päähaaran ja pikanäppäimien tuottaman ominaisuusmatriisin muoto on sama. Jos päähaaran ja pikanäppäinten tuottama ominaisuusmatriisin muoto on erilainen, esimerkiksi lähtökanava on kaksinkertainen tulokanavaan verrattuna, pikanäppäimissä on käytettävä samaa määrää konvoluutioytimiä kuin lähtökanavassa ja kokoa 1×1 konvoluutiooperaatio jos tulos Jos kuva on kaksi kertaa pienempi kuin syötekuva, konvoluutiooperaation askeleeksi on asetettava 2 ja myös päähaaran ensimmäisen kerroksen konvoluutiooperaation askeleeksi; asetetaan arvoon 2.
Seuraava koodi määrittää ResidualBlockBase-luokan Building Block -rakenteen toteuttamiseksi.
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
Pullonkaula-rakennekaavio näkyy alla olevassa kuvassa. Kun syöte on sama, pullonkaula-rakenteessa on vähemmän parametreja kuin Building Block -rakenteessa ja se sopii paremmin verkoille, joissa on syvemmät kerrokset. ResNet50:n käyttämä jäännösrakenne on pullonkaula. Tämän rakenteen päähaarassa on kolme konvoluutiorakennekerrosta, joista jokainen on 1 × 1
Konvoluutiokerros, 3×3
Konvoluutiokerrokset ja 1×1
konvoluutiokerros, jossa 1×1
Konvoluutiokerroksilla on vastaavasti ulottuvuuden vähentämisen ja ulottuvuuden rooli.
Päähaaran ensimmäisen kerroksen verkko ottaa esimerkkinä tulokanavan 256. Ensimmäinen kulkunumero on 64 ja koko on 1×1
Konvoluutioydin suorittaa ulottuvuuden pienentämisen, kulkee sitten eränormalisointikerroksen läpi ja lopulta Relu-aktivointifunktiokerroksen läpi, ja sen lähtökanava on 64;
Pääkonttorin toisen kerroksen verkkokorttien määrä on 64 ja koko 3×3
Konvoluutioydin poimii ominaisuuksia, kulkee sitten eränormalisointikerroksen läpi ja lopulta Relu-aktivointifunktiokerroksen läpi, ja sen lähtökanava on 64;
Päähaaran kolmannen tason läpikulkujen määrä on 256 ja koko on 1×1
Konvoluutioydin mitoitetaan ja viedään sitten eränormalisointikerroksen läpi, ja sen lähtökanava on 256.
Lopuksi lisätään päähaaran ominaisuusmatriisitulostus ja pikanäppäinten ominaisuusmatriisitulostus, ja Pullonkaulan lopullinen tulos saadaan Relu-aktivointitoiminnon kautta.
Kun lisäät päähaaran ja pikanäppäimien tuottamia piirrematriiseja, sinun on varmistettava, että päähaaran ja pikanäppäimien tuottaman ominaisuusmatriisin muoto on sama. Jos päähaaran ja pikanäppäinten tuottama ominaisuusmatriisin muoto on erilainen, esimerkiksi jos lähtökanava on kaksi kertaa tulokanava, pikanäppäinten lukumäärän on oltava yhtä suuri kuin lähtökanavan ja koon on 1×1
Konvoluutioydin suorittaa konvoluutiooperaation, jos tulostekuva on kaksi kertaa pienempi kuin syöttökuva, konvoluutiooperaation askeleeksi on asetettava 2, ja myös päähaaran toisen kerroksen konvoluutiooperaation askeleeksi tarvitaan; asetettava arvoon 2.
Seuraava koodi määrittelee ResidualBlock-luokan pullonkaularakenteen toteuttamiseksi.
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
ResNet-verkkokerroksen rakenne on esitetty alla olevassa kuvassa. Esimerkkinä syöttövärikuvasta 224 × 224, ohita ensin konvoluutiokerros conv1, jonka määrä on 64, konvoluutioytimen koko on 7 × 7 ja askeleen 2. Tämän kerroksen ulostulokuvan koko on 112 × 112, sitten 3 × 3:n alasnäytteenottokerroksen kautta tämän kerroksen lähtökuvan koko on 56 × 56 ja sitten pino 4 jäljellä olevaa verkkolohkoa (conv2_x, conv3_x, conv4_x ja conv5_x), tällä hetkellä ulostulokuvan koko on 7 × 7 ja lähtökanava on 2048, lopuksi luokittelun todennäköisyys saadaan keskimääräisen poolauskerroksen, täysin yhdistetyn kerroksen, kautta ja softmax.
Kunkin jäännösverkkolohkon kohdalla ResNet50-verkon conv2_x on pinottu kolmella pullonkaularakenteella. Jokaisen pullonkaularakenteen tulokanava on 64 ja lähtökanava 256.
Seuraava esimerkki määrittelee make_layerin jäännöslohkon rakentamisen toteuttamiseksi, ja sen parametrit ovat seuraavat:
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)
ResNet50-verkossa on yhteensä 5 konvoluutiorakennetta, keskimääräinen poolauskerros ja täysin yhdistetty kerros. Otetaan esimerkkinä CIFAR-10-tietojoukko.
conv1: Tulokuvan koko on 32×32 ja tulokanava 3. Ensin se kulkee konvoluutiokerroksen läpi, jonka konvoluutioytimen koko on 7 × 7, ja sitten se kulkee eränormalisointikerroksen läpi ja lopuksi Reul-aktivointitoiminnon läpi. Tämän kerroksen lähtöominaisuuskartan koko on 16 × 16 ja lähtökanava on 64.
conv2_x: Syöttöominaisuuskartan koko on 16×16 ja tulokanava on 64. Ensin se käy läpi suurimman näytteenotto-operaation konvoluutioytimellä 3 × 3 ja sitten se pinoaa kolme pullonkaulaa [1 × 1, 3 × 1, 256; ] rakennetta. Tämän kerroksen lähtöominaisuuskartan koko on 8 × 8 ja lähtökanava on 256.
conv3_x: Syöttöominaisuuskartan koko on 8×8 ja tulokanava on 256. Tämä kerros pinoaa 4 pullonkaulaa, joiden rakenne on [1 × 1, 128, 1 × 1, 512; Tämän kerroksen lähtöominaisuuskartan koko on 4 × 4 ja lähtökanava on 512.
conv4_x: Syöttöominaisuuskartan koko on 4×4 ja tulokanava on 512. Tämä kerros pinoaa 6 pullonkaulaa, joiden rakenne on [1 × 1, 256, 3 × 3, 1024]. Tämän kerroksen lähtöominaisuuskartan koko on 2×2 ja lähtökanava on 1024.
conv5_x: Syöttöominaisuuskartan koko on 2×2 ja tulokanava on 1024. Tämä kerros pinoaa kolme pullonkaulaa, joiden rakenne on [1 × 1, 512, 1 × 1, 2048; Tämän kerroksen lähtöominaisuuskartan koko on 1×1 ja lähtökanava on 2048.
keskimääräinen pool & fc: Tulokanava on 2048 ja lähtökanava on luokituskategorioiden lukumäärä.
Seuraava esimerkkikoodi toteuttaa ResNet50-mallin rakentamisen kutsumalla funktiota resnet50. Funktio resnet50 ovat seuraavat:
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)
Tässä osiossa käytetään esikoulutettua ResNet50-mallia hienosäätöön. Soita resnet50:lle rakentaaksesi ResNet50-malli ja asetaksesi esiopetetun parametrin arvoon True. Esiopetettu ResNet50-malli ladataan automaattisesti ja esiopetetun mallin parametrit ladataan verkkoon. Määritä sitten optimointi- ja häviötoiminto, tulosta harjoitushäviön arvo ja arvioinnin tarkkuus jaksoittain ja tallenna ckpt-tiedosto korkeimmalla arviointitarkkuudella (resnet50-best.ckpt) nykyisen polun ./BestCheckPointiin.
Koska esiopetetun mallin täysin yhdistetyn kerroksen (fc) lähtökoko (vastaava parametri num_classes) on 1000, esiopetettujen painojen lataamiseksi onnistuneesti asetamme mallin täysin yhdistetyn lähtökoon oletusarvoksi. 1000. CIFAR10-tietojoukossa on yhteensä 10 luokkaa. Kun tätä tietojoukkoa käytetään harjoitteluun, mallin täysin yhdistetyn kerroksen, joka on ladattu esiopetetuilla painoilla, lähtökoko on asetettava 10:een.
Tässä näytämme 5 kauden harjoitusprosessin Jos haluat saavuttaa ihanteellisen harjoitusvaikutuksen, on suositeltavaa harjoitella 80 jaksoa.
# 定义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
Määrittele visualize_model-funktio, käytä yllä mainittua mallia korkeimmalla varmennustarkkuudella ennustaaksesi CIFAR-10-testitietojoukon ja visualisoi ennustetulokset. Jos ennustefontin väri on sininen, se tarkoittaa, että ennuste on oikea, ja jos ennustefontin väri on punainen, se tarkoittaa, että ennuste on väärä.
Yllä olevista tuloksista voidaan nähdä, että mallin ennustetarkkuus varmennustiedoissa 5 epookissa on noin 70 %, eli normaaliolosuhteissa 2 kuudesta kuvasta epäonnistuu ennustamisessa. Jos haluat saavuttaa ihanteellisen harjoitusvaikutuksen, on suositeltavaa harjoitella 80 jaksoa.
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)