私の連絡先情報
郵便メール:
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
ResNet は主に、深さが深くなるときの深い畳み込みネットワークの「劣化」問題を解決します。一般的な畳み込みニューラル ネットワークでは、ネットワークの深さが増すことで生じる最初の問題は、勾配の消失と爆発です。この問題は、Szegedy が BN 層を提案したことで解決されました。 BN レイヤーは各レイヤーの出力を正規化できるため、逆にレイヤーごとに通過した後でも勾配のサイズは安定したままとなり、小さすぎたり大きすぎたりすることはありません。しかし、BN を追加して深さを深くしても収束するのはまだ簡単ではないことがわかり、2 番目の問題である精度低下の問題について言及しました。つまり、レベルがある程度大きくなると精度が飽和してしまいます。この減少は、過学習によって引き起こされるのではなく、ネットワークが複雑すぎるため、制約のないフリーレンジ トレーニングだけでは理想的なエラー率を達成することが困難です。
精度の低下の問題はネットワーク構造自体に問題があるのではなく、既存の学習方法が理想的ではないことが原因です。現在広く使用されているオプティマイザは、SGD、RMSProp、Adam のいずれであっても、ネットワークの深さが深くなると、理論的に最適な収束結果を達成できなくなります。
適切なネットワーク構造がある限り、深いネットワークの方が浅いネットワークよりもパフォーマンスが向上するのは間違いありません。証明プロセスも非常に簡単です。ネットワーク A の背後にいくつかの層が追加されて、新しいネットワーク B が形成されているとします。追加された層が A の出力に対して ID マッピングのみを実行する場合、つまり、A の出力が追加されるとします。レベルがBの出力になっても変化がないため、ネットワークAとネットワークBのエラー率は同等となり、深化後のネットワークが深化前のネットワークよりも悪くならないことがわかります。
画像分類は最も基本的なコンピューター ビジョン アプリケーションであり、教師あり学習のカテゴリに属します。たとえば、与えられた画像 (猫、犬、飛行機、車など) が属するカテゴリを決定します。この章では、CIFAR-10 データ セットを分類するための ResNet50 ネットワークの使用法を紹介します。
ResNet50 ネットワークは、2015 年に Microsoft Labs の He Kaiming によって提案され、ILSVRC2015 画像分類コンペティションで 1 位を獲得しました。 ResNet ネットワークが提案される前は、従来の畳み込みニューラル ネットワークは一連の畳み込み層とプーリング層を積み重ねることによって得られていましたが、ネットワークが一定の深さまで積み重ねられると、劣化の問題が発生します。下の図は、CIFAR-10 データセット上の 56 層ネットワークと 20 層ネットワークを使用したトレーニング誤差とテスト誤差のグラフです。図のデータから、トレーニング誤差とテスト誤差が大きく異なることがわかります。 56 層ネットワークは 20 層ネットワークよりも大きいため、ネットワークが深くなるにつれて、誤差は期待どおりに減少しません。
ResNet ネットワークは、劣化問題を軽減するために残留ネットワーク構造 (Residual Network) を提案しています。ResNet ネットワークを使用すると、より深いネットワーク構造 (1000 層以上) を構築できます。 CIFAR-10 データセットに関する論文で使用された ResNet ネットワークのトレーニング誤差とテスト誤差のグラフを次の図に示します。図の点線はトレーニング誤差を表し、実線はテスト誤差を表します。図のデータから、ResNet ネットワーク層が深くなるほど、トレーニング エラーとテスト エラーが小さくなることがわかります。
CIFAR-10 データセットには、合計 60,000 枚の 32*32 カラー画像があり、10 のカテゴリに分割され、各カテゴリには 6,000 枚の画像があり、データセットには合計 50,000 枚のトレーニング画像と 10,000 枚の評価画像が含まれています。まず、次の例では、ダウンロード インターフェイスを使用してダウンロードと解凍を行っています。現在、CIFAR-10 ファイルのバイナリ バージョン (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)
'./datasets-cifar10-bin'
ダウンロードしたデータセットのディレクトリ構造は次のとおりです。
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
次に、mindspore.dataset.Cifar10Dataset インターフェイスを使用してデータセットを読み込み、関連する画像強調操作を実行します。
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()
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()
画像形状: (256, 3, 32, 32)、ラベル形状: (256,)
ラベル: [3 2 7 6 0 4]
残差ネットワーク構造 (Residual Network) は、ResNet ネットワークの主なハイライトです。ResNet が残差ネットワーク構造を使用すると、劣化の問題が効果的に軽減され、より深いネットワーク構造の設計が実現され、ネットワークのトレーニング精度が向上します。このセクションでは、最初に残差ネットワーク構造を構築する方法を説明し、次に残差ネットワークを積み重ねることによって ResNet50 ネットワークを構築します。
残留ネットワーク構造を構築する
残差网络结构图如下图所示,残差网络由两个分支构成:一个主分支,一个shortcuts(图中弧线表示)。主分支通过堆叠一系列的卷积操作得到,shotcuts从输入直接到输出,主分支输出的特征矩阵 𝐹(𝑥)
加上shortcuts输出的特征矩阵 𝑥 得到 𝐹(𝑥)+𝑥,通过Relu激活函数后即为残差网络最后的输出。
残留ネットワーク構造には主に 2 つのタイプがあります。1 つはビルディング ブロックで、ResNet18 や ResNet34 などの浅い ResNet ネットワークに適しています。もう 1 つはボトルネックで、ResNet50、ResNet101、ResNet152 などのより深い ResNet ネットワークに適しています。 。
ビルディング ブロックの構造図を次の図に示します。メイン ブランチには 2 層の畳み込みネットワーク構造があります。
メインブランチの第 1 層ネットワークは、例として入力チャネルを 64 とします。まず、3×3 を渡します。
畳み込み層、次にバッチ正規化層、最後に Relu 活性化関数層を経由すると、出力チャネルは 64 になります。
メインブランチの第 2 層ネットワークの入力チャネルは 64 です。最初に 3×3 を通過します。
次に、畳み込み層はバッチ正規化層を通過し、出力チャネルは 64 になります。
最後に、メイン ブランチによって出力された特徴行列とショートカットによって出力された特徴行列が加算され、Relu アクティベーション関数を通じてビルディング ブロックの最終出力が得られます。
メイン ブランチとショートカットによって出力される特徴行列を追加するときは、メイン ブランチとショートカットによって出力される特徴行列の形状が同じであることを確認する必要があります。メイン ブランチとショートカットによって出力される特徴行列の形状が異なる場合 (たとえば、出力チャネルが入力チャネルの 2 倍である場合)、ショートカットは、出力チャネルと同じ数と、1×1 コンボリューション カーネルのサイズを使用する必要があります。畳み込み演算; 出力画像が入力画像の 2 倍小さい場合、ショートカットの畳み込み演算のストライドは 2 に設定する必要があり、メイン ブランチの最初の層の畳み込み演算のストライドも 2 に設定する必要があります。 2に設定します。
次のコードは、ビルディング ブロック構造を実装するための ResidualBlockBase クラスを定義します。
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
ボトルネック構造図を次の図に示します。入力が同じ場合、ボトルネック構造はビルディング ブロック構造よりもパラメーターが少なく、より深い層を持つネットワークに適しています。ResNet50 で使用される残差構造はボトルネックです。この構造のメインブランチには 3 層の畳み込み構造があり、それぞれが 1×1
畳み込み層、3×3
畳み込み層と1×1
畳み込み層、1×1
畳み込み層は、それぞれ次元削減と次元化の役割を果たします。
メインブランチの第 1 層ネットワークは、入力チャネルを 256 として例に挙げ、最初のパス数は 64、サイズは 1×1 です。
コンボリューション カーネルは次元削減を実行し、次にバッチ正規化層を通過し、最後に Relu 活性化関数層を通過します。その出力チャネルは 64 です。
本支店の第 2 層ネットワークパス数は 64、サイズは 3×3
コンボリューション カーネルは特徴を抽出し、次にバッチ正規化層を通過し、最後に Relu 活性化関数層を通過します。その出力チャネルは 64 です。
メインブランチの 3 レベル目のパス数は 256、サイズは 1×1
コンボリューション カーネルはディメンション化されてからバッチ正規化層を通過し、その出力チャネルは 256 です。
最後に、メイン ブランチによって出力された特徴行列とショートカットによって出力された特徴行列が加算され、Relu アクティベーション関数を通じてボトルネックの最終出力が得られます。
メイン ブランチとショートカットによって出力される特徴行列を追加するときは、メイン ブランチとショートカットによって出力される特徴行列の形状が同じであることを確認する必要があります。メインブランチとショートカットによって出力される特徴行列の形状が異なる場合、たとえば、出力チャネルが入力チャネルの 2 倍である場合、ショートカットの数は出力チャネルと同じである必要があり、サイズは 1×1 になります。
コンボリューション カーネルはコンボリューション演算を実行します。出力イメージが入力イメージより 2 倍小さい場合、ショートカットのコンボリューション演算のストライドを 2 に設定する必要があり、メイン ブランチの第 2 層のコンボリューション演算のストライドも必要になります。 2に設定されます。
次のコードは、ボトルネック構造を実装するための ResidualBlock クラスを定義します。
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 ネットワーク層の構造を次の図に示します。入力カラー画像 224×224 を例として、量が 64、コンボリューション カーネル サイズが 7×7、ストライドが 2 のコンボリューション層 conv1 を渡します。この層の出力画像サイズは 112×112、出力チャネルは 64、3×3 の最大ダウンサンプリング プーリング層を介すると、この層の出力画像サイズは 56×56、出力チャネルは 64 になります。 4 つの残りのネットワーク ブロック (conv2_x、conv3_x、conv4_x、conv5_x) をスタックします。このときの出力画像サイズは 7×7、出力チャネルは 2048 です。最後に、分類確率は平均プーリング層、全結合層を通じて取得されます。そしてソフトマックス。
ResNet50 ネットワークの conv2_x を例に取ると、各残余ネットワーク ブロックは 3 つのボトルネック構造によってスタックされます。各ボトルネックの入力チャネルは 64 で、出力チャネルは 256 です。
次の例では、残差ブロックの構築を実装するために make_layer を定義しており、そのパラメーターは次のとおりです。
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 ネットワークには、平均プーリング層と完全接続層の合計 5 つの畳み込み構造があります。CIFAR-10 データ セットを例に挙げます。
conv1: 入力画像サイズは 32×32、入力チャンネルは 3 です。まず、畳み込みカーネル番号 64、畳み込みカーネル サイズ 7×7、ストライド 2 の畳み込み層を通過し、次にバッチ正規化層を通過し、最後に Reul 活性化関数を通過します。このレイヤーの出力特徴マップ サイズは 16×16、出力チャネルは 64 です。
conv2_x: 入力特徴マップのサイズは 16×16、入力チャネルは 64 です。まず、畳み込みカーネル サイズ 3×3、ストライド 2 で最大ダウンサンプリング プーリング操作を実行し、[1×1, 64; 1×1; 、256]。このレイヤーの出力特徴マップ サイズは 8×8、出力チャネルは 256 です。
conv3_x: 入力特徴マップのサイズは 8×8、入力チャネルは 256 です。この層は [1×1, 128; 1×1, 512] の構造を持つ 4 つのボトルネックを積み重ねます。このレイヤーの出力特徴マップ サイズは 4×4、出力チャネルは 512 です。
conv4_x: 入力特徴マップのサイズは 4×4、入力チャネルは 512 です。この層は [1×1, 256; 1×1, 1024] の構造を持つ 6 つのボトルネックを積み重ねます。このレイヤーの出力特徴マップ サイズは 2×2、出力チャネルは 1024 です。
conv5_x: 入力特徴マップ サイズは 2×2、入力チャネルは 1024 です。この層は、[1×1, 512; 3×1, 2048] の構造を持つ 3 つのボトルネックを積み重ねます。このレイヤーの出力特徴マップ サイズは 1×1、出力チャネルは 2048 です。
Average pool & fc: 入力チャネルは 2048、出力チャネルは分類カテゴリの数です。
次のコード例は、ResNet50 モデルの構築を実装しています。ResNet50 モデルは、関数 resnet50 を呼び出すことで構築できます。
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)
このセクションでは、微調整に ResNet50 事前トレーニング済みモデルを使用します。 resnet50 を呼び出して ResNet50 モデルを構築し、事前トレーニング済みパラメーターを True に設定します。ResNet50 事前トレーニング済みモデルが自動的にダウンロードされ、事前トレーニング済みモデルのパラメーターがネットワークにロードされます。次に、オプティマイザと損失関数を定義し、トレーニング損失値と評価精度をエポックごとに出力し、最も高い評価精度を持つ ckpt ファイル (resnet50-best.ckpt) を現在のパスの ./BestCheckPoint に保存します。
事前トレーニングされたモデルの全結合層 (fc) の出力サイズ (対応するパラメーター num_classes) は 1000 であるため、事前トレーニングされた重みを正常に読み込むために、モデルの全結合出力サイズをデフォルトに設定します。 1000。 CIFAR10 データ セットには 10 のカテゴリがあります。このデータ セットをトレーニングに使用する場合、事前トレーニングされた重みが読み込まれたモデルの全結合層の出力サイズを 10 にリセットする必要があります。
ここでは 5 エポックのトレーニング プロセスを示します。理想的なトレーニング効果を達成したい場合は、80 エポックでトレーニングすることをお勧めします。
# 定义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
Visualize_model 関数を定義し、上記の最も検証精度の高いモデルを使用して CIFAR-10 テスト データセットを予測し、予測結果を視覚化します。予測フォントの色が青の場合は予測が正しいことを意味し、予測フォントの色が赤の場合は予測が間違っていることを意味します。
上記の結果から、5 エポックでの検証データ セット内のモデルの予測精度は約 70% であることがわかります。つまり、通常の状況では、6 枚中 2 枚の画像が予測に失敗することになります。理想的なトレーニング効果を得たい場合は、80 エポックでトレーニングすることをお勧めします。
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)