今天是参加昇思打卡营的第16天,学习内容是Transformer图像分类。
以下是内容关键点概要:
-
ViT模型简介:
介绍ViT模型的背景、原理和在图像分类任务中的应用。 -
模型结构:
详细描述ViT模型的结构,包括Patch Embedding、Multi-Head Attention、Feed Forward网络等组件。 -
模型特点:
讨论ViT模型的主要特点,如将图像转换为序列化的数据以适应Transformer架构。 -
环境准备与数据读取:
指导如何在本地环境中安装MindSpore,并准备ImageNet数据集。 -
模型训练:
展示如何使用MindSpore框架配置和启动ViT模型的训练过程。 -
模型评估:
描述如何评估ViT模型的性能,包括使用不同的评价指标,如Top-1 Accuracy和Top-5 Accuracy。 -
模型推理:
展示如何使用训练好的ViT模型进行图像推理,并展示推理结果。
Vision Transformer(ViT)简介
近些年,随着基于自注意(Self-Attention)结构的模型的发展,特别是Transformer模型的提出,极大地促进了自然语言处理模型的发展。由于Transformers的计算效率和可扩展性,它已经能够训练具有超过100B参数的空前规模的模型。
ViT则是自然语言处理和计算机视觉两个领域的融合结晶。在不依赖卷积操作的情况下,依然可以在图像分类任务上达到很好的效果。
模型结构
ViT模型的主体结构是基于Transformer模型的Encoder部分(部分结构顺序有调整,如:Normalization的位置与标准Transformer不同),其结构图[1]如下:
模型特点
ViT模型主要应用于图像分类领域。因此,其模型结构相较于传统的Transformer有以下几个特点:
- 数据集的原图像被划分为多个patch(图像块)后,将二维patch(不考虑channel)转换为一维向量,再加上类别向量与位置向量作为模型输入。
- 模型主体的Block结构是基于Transformer的Encoder结构,但是调整了Normalization的位置,其中,最主要的结构依然是Multi-head Attention结构。
- 模型在Blocks堆叠后接全连接层,接受类别向量的输出作为输入并用于分类。通常情况下,我们将最后的全连接层称为Head,Transformer Encoder部分为backbone。
下面将通过代码实例来详细解释基于ViT实现ImageNet分类任务。
环境准备与数据读取
from download import download # 导入download函数,用于下载文件
# 定义数据集的URL地址
dataset_url = "https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/vit_imagenet_dataset.zip"
# 指定下载后文件存放的目录
path = "./"
# 调用download函数下载数据集
# 参数解释:
# dataset_url: 数据集的URL地址
# path: 下载数据集后存放的目录
# kind="zip": 指定下载文件的类型,这里是zip格式的压缩包
# replace=True: 如果目标文件已存在,是否替换它
path = download(dataset_url, path, kind="zip", replace=True)
import os
import mindspore as ms
from mindspore.dataset import ImageFolderDataset
import mindspore.dataset.vision as transforms
# 设置数据集的根目录
data_path = './dataset/'
# 定义图像数据的均值和标准差,用于归一化处理
mean = [0.485 * 255, 0.456 * 255, 0.406 * 255]
std = [0.229 * 255, 0.224 * 255, 0.225 * 255]
# 使用ImageFolderDataset加载训练数据集
# 假设数据集的目录结构是:data_path/train/class_x/xxx.jpg
dataset_train = ImageFolderDataset(os.path.join(data_path, "train"), shuffle=True)
# 定义训练数据集的预处理操作
trans_train = [
transforms.RandomCropDecodeResize(size=224, # 随机裁剪并调整图像大小到224x224
scale=(0.08, 1.0), # 裁剪的面积比例
ratio=(0.75, 1.333)), # 裁剪的宽高比
transforms.RandomHorizontalFlip(prob=0.5), # 随机水平翻转,概率为0.5
transforms.Normalize(mean=mean, std=std), # 归一化操作
transforms.HWC2CHW() # 将图像的HWC格式转换为CHW格式
]
# 应用预处理操作
dataset_train = dataset_train.map(operations=trans_train, input_columns=["image"])
# 将数据集转换为批处理,批量大小为16,丢弃不能形成完整批次的数据
dataset_train = dataset_train.batch(batch_size=16, drop_remainder=True)
模型解析
下面将通过代码来细致剖析ViT模型的内部结构。
Transformer基本原理
Transformer模型源于2017年的一篇文章[2]。在这篇文章中提出的基于Attention机制的编码器-解码器型结构在自然语言处理领域获得了巨大的成功。模型结构如下图所示:
其主要结构为多个Encoder和Decoder模块所组成,其中Encoder和Decoder的详细结构如下图[2]所示:
Encoder与Decoder由许多结构组成,如:多头注意力(Multi-Head Attention)层,Feed Forward层,Normaliztion层,甚至残差连接(Residual Connection,图中的“Add”)。不过,其中最重要的结构是多头注意力(Multi-Head Attention)结构,该结构基于自注意力(Self-Attention)机制,是多个Self-Attention的并行组成。
所以,理解了Self-Attention就抓住了Transformer的核心。
from mindspore import nn, ops
class Attention(nn.Cell):
def __init__(self,
dim: int,
num_heads: int = 8,
keep_prob: float = 1.0,
attention_keep_prob: float = 1.0):
super(Attention, self).__init__()
self.num_heads = num_heads # 多头注意力的头数
head_dim = dim // num_heads # 每个头的维度
self.scale = ms.Tensor(head_dim ** -0.5) # 缩放因子,用于平衡注意力分数
# 定义Q(Query)、K(Key)、V(Value)的线性变换层
self.qkv = nn.Dense(dim, dim * 3)
# 定义注意力输出的dropout层
self.attn_drop = nn.Dropout(p=1.0 - attention_keep_prob)
# 定义最终输出的dropout层
self.out_drop = nn.Dropout(p=1.0 - keep_prob)
# 定义Value与注意力分数相乘的矩阵乘法操作
self.attn_matmul_v = ops.BatchMatMul()
# 定义Q与K相乘的矩阵乘法操作,需要转置K
self.q_matmul_k = ops.BatchMatMul(transpose_b=True)
# 定义Softmax激活函数,用于获取注意力分数的归一化权重
self.softmax = nn.Softmax(axis=-1)
def construct(self, x):
"""构建多头注意力机制的前向传播逻辑。"""
b, n, c = x.shape # 输入x的批量大小、序列长度、特征维度
# 通过线性层生成Q、K、V
qkv = self.qkv(x)
# 将Q、K、V重塑为适合多头注意力的形式
qkv = ops.reshape(qkv, (b, n, 3, self.num_heads, c // self.num_heads))
qkv = ops.transpose(qkv, (2, 0, 3, 1, 4)) # 调整维度顺序
q, k, v = ops.unstack(qkv, axis=0) # 将Q、K、V分离
# 计算注意力分数(Q与K的点积),并应用缩放因子
attn = self.q_matmul_k(q, k)
attn = ops.mul(attn, self.scale)
# 应用Softmax获取归一化的注意力权重
attn = self.softmax(attn)
# 应用注意力dropout
attn = self.attn_drop(attn)
# 根据注意力权重计算加权的Value(out = Attention(Q, K, V))
out = self.attn_matmul_v(attn, v)
# 调整输出的维度顺序,使其与输入x的顺序一致
out = ops.transpose(out, (0, 2, 1, 3))
# 将输出重塑为原始的批量大小和序列长度
out = ops.reshape(out, (b, n, c))
# 通过线性层进行输出变换
out = self.out(out)
# 应用输出dropout
return out
Transformer Encoder
在了解了Self-Attention结构之后,通过与Feed Forward,Residual Connection等结构的拼接就可以形成Transformer的基础结构,下面代码实现了Feed Forward,Residual Connection结构。
from typing import Optional, Dict
import mindspore as ms
import mindspore.nn as nn
class FeedForward(nn.Cell):
def __init__(self,
in_features: int,
hidden_features: Optional[int] = None,
out_features: Optional[int] = None,
activation: nn.Cell = nn.GELU,
keep_prob: float = 1.0):
super(FeedForward, self).__init__()
# 如果没有指定隐藏层特征数量,则使用输入特征数量
out_features = out_features or in_features
hidden_features = hidden_features or in_features
# 第一个全连接层
self.dense1 = nn.Dense(in_features, hidden_features)
# 激活函数,默认为GELU
self.activation = activation()
# 第二个全连接层
self.dense2 = nn.Dense(hidden_features, out_features)
# Dropout层,用于正则化,防止过拟合
self.dropout = nn.Dropout(p=1.0 - keep_prob)
def construct(self, x):
"""前馈网络的构建函数."""
x = self.dense1(x) # 通过第一个全连接层
x = self.activation(x) # 应用激活函数
x = self.dropout(x) # 应用Dropout
x = self.dense2(x) # 通过第二个全连接层
x = self.dropout(x) # 再次应用Dropout
return x
class ResidualCell(nn.Cell):
def __init__(self, cell):
super(ResidualCell, self).__init__()
# 残差连接中的基本单元,可以是任何MindSpore的Cell
self.cell = cell
def construct(self, x):
"""残差单元的构建函数."""
# 将输入x通过残差单元cell,然后与原始输入x相加,形成残差连接
return self.cell(x) + x
从以下源码中就可以清晰看到Transformer的结构。将TransformerEncoder结构和一个多层感知器(MLP)结合,就构成了ViT模型的backbone部分。
import mindspore as ms
import mindspore.nn as nn
from mindspore import ops
class TransformerEncoder(nn.Cell):
def __init__(self,
dim: int,
num_layers: int,
num_heads: int,
mlp_dim: int,
keep_prob: float = 1.0,
attention_keep_prob: float = 1.0,
drop_path_keep_prob: float = 1.0,
activation: nn.Cell = nn.GELU,
norm: nn.Cell = nn.LayerNorm):
super(TransformerEncoder, self).__init__()
# 初始化编码器层列表
layers = []
# 循环创建num_layers个编码器层
for _ in range(num_layers):
# 创建第一个层归一化
normalization1 = norm((dim,))
# 创建第二个层归一化
normalization2 = norm((dim,))
# 创建多头注意力模块
attention = Attention(dim=dim,
num_heads=num_heads,
keep_prob=keep_prob,
attention_keep_prob=attention_keep_prob)
# 创建前馈网络
feedforward = FeedForward(in_features=dim,
hidden_features=mlp_dim,
activation=activation,
keep_prob=keep_prob)
# 为每个编码器层创建残差连接结构
layers.append(
nn.SequentialCell([
ResidualCell(nn.SequentialCell([normalization1, attention])),
ResidualCell(nn.SequentialCell([normalization2, feedforward]))
])
)
# 将所有编码器层组合成一个序列单元
self.layers = nn.SequentialCell(layers)
def construct(self, x):
"""Transformer编码器的构建函数."""
# 通过所有编码器层
return self.layers(x)
ViT模型的输入
传统的Transformer结构主要用于处理自然语言领域的词向量(Word Embedding or Word Vector),词向量与传统图像数据的主要区别在于,词向量通常是一维向量进行堆叠,而图片则是二维矩阵的堆叠,多头注意力机制在处理一维词向量的堆叠时会提取词向量之间的联系也就是上下文语义,这使得Transformer在自然语言处理领域非常好用,而二维图片矩阵如何与一维词向量进行转化就成为了Transformer进军图像处理领域的一个小门槛。
在ViT模型中:
-
通过将输入图像在每个channel上划分为1616个patch,这一步是通过卷积操作来完成的,当然也可以人工进行划分,但卷积操作也可以达到目的同时还可以进行一次而外的数据处理;*例如一幅输入224 x 224的图像,首先经过卷积处理得到16 x 16个patch,那么每一个patch的大小就是14 x 14。
-
再将每一个patch的矩阵拉伸成为一个一维向量,从而获得了近似词向量堆叠的效果。上一步得到的14 x 14的patch就转换为长度为196的向量。
这是图像输入网络经过的第一步处理。具体Patch Embedding的代码如下所示:
import mindspore as ms
import mindspore.nn as nn
import mindspore.ops as ops
class PatchEmbedding(nn.Cell):
MIN_NUM_PATCHES = 4 # 最小的patch数量,用于验证
def __init__(self,
image_size: int = 224,
patch_size: int = 16,
embed_dim: int = 768,
input_channels: int = 3):
super(PatchEmbedding, self).__init__() # 调用父类构造函数
# 保存初始化参数
self.image_size = image_size # 输入图像的尺寸
self.patch_size = patch_size # 每个patch的尺寸
self.num_patches = (image_size // patch_size) ** 2 # 计算patch的总数
self.conv = nn.Conv2d(input_channels, embed_dim,
kernel_size=patch_size, stride=patch_size, has_bias=True) # 定义卷积层
def construct(self, x):
"""Patch Embedding 层的构建函数."""
# 使用卷积层将输入图像转换为嵌入特征
x = self.conv(x)
# 获取转换后的特征的批量大小、通道数、高度和宽度
b, c, h, w = x.shape
# 将特征重塑为 (batch_size, sequence_length, channel_size)
# 其中 sequence_length = num_patches * patch_size * patch_size
x = ops.reshape(x, (b, c, h * w))
# 转置操作,将特征从 (batch_size, sequence_length, channel_size) 转换为 (batch_size, channel_size, sequence_length)
x = ops.transpose(x, (0, 2, 1))
return x
整体构建ViT
以下代码构建了一个完整的ViT模型。
from mindspore import nn, ops, Parameter
from mindspore.common.initializer import Normal, initializer
# 自定义初始化函数,方便创建可训练的参数
def init(init_type, shape, dtype, name, requires_grad):
initial = initializer(init_type, shape, dtype).init_data()
return Parameter(initial, name=name, requires_grad=requires_grad)
class ViT(nn.Cell):
def __init__(self,
image_size: int = 224,
input_channels: int = 3,
patch_size: int = 16,
embed_dim: int = 768,
num_layers: int = 12,
num_heads: int = 12,
mlp_dim: int = 3072,
keep_prob: float = 1.0,
attention_keep_prob: float = 1.0,
drop_path_keep_prob: float = 1.0,
activation: nn.Cell = nn.GELU,
norm: Optional[nn.Cell] = nn.LayerNorm,
pool: str = 'cls'):
super(ViT, self).__init__()
# 初始化PatchEmbedding层,将图像转换为序列化特征
self.patch_embedding = PatchEmbedding(image_size, patch_size, embed_dim, input_channels)
num_patches = self.patch_embedding.num_patches
# 初始化类别token和位置编码
self.cls_token = init(Normal(sigma=1.0),
shape=(1, 1, embed_dim),
dtype=ms.float32,
name='cls',
requires_grad=True)
self.pos_embedding = init(Normal(sigma=1.0),
shape=(1, num_patches + 1, embed_dim),
dtype=ms.float32,
name='pos_embedding',
requires_grad=True)
# 其他配置参数
self.pool = pool
self.pos_dropout = nn.Dropout(p=1.0 - keep_prob)
self.norm = norm((embed_dim,))
self.transformer = TransformerEncoder(dim=embed_dim,
num_layers=num_layers,
num_heads=num_heads,
mlp_dim=mlp_dim,
keep_prob=keep_prob,
attention_keep_prob=attention_keep_prob,
drop_path_keep_prob=drop_path_keep_prob,
activation=activation,
norm=norm)
self.dropout = nn.Dropout(p=1.0 - keep_prob)
self.dense = nn.Dense(embed_dim, num_classes)
def construct(self, x):
# 前向传播函数
x = self.patch_embedding(x)
cls_tokens = ops.tile(self.cls_token.astype(x.dtype), (x.shape[0], 1, 1))
x = ops.concat((cls_tokens, x), axis=1)
x += self.pos_embedding
x = self.pos_dropout(x)
x = self.transformer(x)
x = self.norm(x)
x = x[:, 0] # 采用 [CLS] token 的输出
if self.training:
x = self.dropout(x)
x = self.dense(x) # 通过全连接层进行分类
return x
整体流程图如下所示:
模型训练与推理
模型训练
模型开始训练前,需要设定损失函数,优化器,回调函数等。
完整训练ViT模型需要很长的时间,实际应用时建议根据项目需要调整epoch_size,当正常输出每个Epoch的step信息时,意味着训练正在进行,通过模型输出可以查看当前训练的loss值和时间等指标。
from mindspore.nn import LossBase
from mindspore.train import LossMonitor, TimeMonitor, CheckpointConfig, ModelCheckpoint
from mindspore import train, nn, ops, load_checkpoint, load_param_into_net
# 定义超参数
epoch_size = 10 # 训练的轮数
momentum = 0.9 # 动量参数
num_classes = 1000 # 类别数量
resize = 224 # 输入图像的尺寸
step_size = dataset_train.get_dataset_size() # 每个epoch的步数
# 构建模型
network = ViT()
# 加载预训练的检查点
vit_url = "https://download.mindspore.cn/vision/classification/vit_b_16_224.ckpt"
path = "./ckpt/vit_b_16_224.ckpt"
# 假设download函数已经定义,用于下载文件
vit_path = download(vit_url, path, replace=True)
param_dict = load_checkpoint(vit_path)
load_param_into_net(network, param_dict)
# 定义学习率
lr = nn.cosine_decay_lr(min_lr=float(0),
max_lr=0.00005,
total_step=epoch_size * step_size,
step_per_epoch=step_size,
decay_epoch=10)
# 定义优化器
network_opt = nn.Adam(network.trainable_params(), lr, momentum)
# 定义损失函数
class CrossEntropySmooth(LossBase):
"""交叉熵损失,带有标签平滑."""
def __init__(self, sparse=True, reduction='mean', smooth_factor=0., num_classes=1000):
super(CrossEntropySmooth, self).__init__()
self.onehot = ops.OneHot()
self.sparse = sparse
self.on_value = ms.Tensor(1.0 - smooth_factor, ms.float32)
self.off_value = ms.Tensor(1.0 * smooth_factor / (num_classes - 1), ms.float32)
self.ce = nn.SoftmaxCrossEntropyWithLogits(reduction=reduction)
def construct(self, logit, label):
if self.sparse:
label = self.onehot(label, ops.shape(logit)[1], self.on_value, self.off_value)
loss = self.ce(logit, label)
return loss
network_loss = CrossEntropySmooth(sparse=True,
reduction="mean",
smooth_factor=0.1,
num_classes=num_classes)
# 设置检查点
ckpt_config = CheckpointConfig(save_checkpoint_steps=step_size, keep_checkpoint_max=100)
ckpt_callback = ModelCheckpoint(prefix='vit_b_16', directory='./ViT', config=ckpt_config)
# 初始化模型
# "Ascend + mixed precision" 可以提高性能
ascend_target = (ms.get_context("device_target") == "Ascend")
if ascend_target:
model = train.Model(network, loss_fn=network_loss, optimizer=network_opt, metrics={"acc"}, amp_level="O2")
else:
model = train.Model(network, loss_fn=network_loss, optimizer=network_opt, metrics={"acc"}, amp_level="O0")
# 训练模型
model.train(epoch_size,
dataset_train,
callbacks=[ckpt_callback, LossMonitor(125), TimeMonitor(125)],
dataset_sink_mode=False,)
模型验证
模型验证过程主要应用了ImageFolderDataset,CrossEntropySmooth和Model等接口。
ImageFolderDataset主要用于读取数据集。
CrossEntropySmooth是损失函数实例化接口。
Model主要用于编译模型。
与训练过程相似,首先进行数据增强,然后定义ViT网络结构,加载预训练模型参数。随后设置损失函数,评价指标等,编译模型后进行验证。本案例采用了业界通用的评价标准Top_1_Accuracy和Top_5_Accuracy评价指标来评价模型表现。
在本案例中,这两个指标代表了在输出的1000维向量中,以最大值或前5的输出值所代表的类别为预测结果时,模型预测的准确率。这两个指标的值越大,代表模型准确率越高。
# 导入ImageFolderDataset类,用于加载和处理验证集数据
dataset_val = ImageFolderDataset(os.path.join(data_path, "val"), shuffle=True)
# 定义验证集数据的转换操作,包括解码、调整大小、中心裁剪、归一化和格式转换
trans_val = [
transforms.Decode(), # 解码图像
transforms.Resize(224 + 32), # 调整图像大小到256(224+32)
transforms.CenterCrop(224), # 从图像中心裁剪出224x224的区域
transforms.Normalize(mean=mean, std=std), # 归一化操作,参数mean和std是均值和标准差
transforms.HWC2CHW() # 将图像格式从高度x宽度x通道(HWC)转换为通道x高度x宽度(CHW)
]
# 将转换操作应用到验证集数据集
dataset_val = dataset_val.map(operations=trans_val, input_columns=["image"])
# 将数据集转换为批次处理,每批16个样本,drop_remainder=True表示丢弃最后一个不完整的批次
dataset_val = dataset_val.batch(batch_size=16, drop_remainder=True)
# 构建模型,这里使用的是ViT(Vision Transformer)模型
network = ViT()
# 加载模型参数,从指定的ckpt文件路径加载
param_dict = ms.load_checkpoint(vit_path)
# 将加载的参数应用到网络模型中
ms.load_param_into_net(network, param_dict)
# 定义损失函数,使用CrossEntropySmooth,它是一种带有平滑正则化的交叉熵损失
network_loss = CrossEntropySmooth(sparse=True,
reduction="mean",
smooth_factor=0.1,
num_classes=num_classes)
# 定义评估指标,这里使用Top-1和Top-5的准确率
eval_metrics = {'Top_1_Accuracy': train.Top1CategoricalAccuracy(),
'Top_5_Accuracy': train.Top5CategoricalAccuracy()}
# 根据是否使用华为Ascend芯片,设置混合精度训练的级别
if ascend_target:
model = train.Model(network, loss_fn=network_loss, optimizer=network_opt, metrics=eval_metrics, amp_level="O2")
else:
model = train.Model(network, loss_fn=network_loss, optimizer=network_opt, metrics=eval_metrics, amp_level="O0")
# 使用模型进行评估,并传入验证集数据集
result = model.eval(dataset_val)
# 打印评估结果
print(result)
模型推理
在进行模型推理之前,首先要定义一个对推理图片进行数据预处理的方法。该方法可以对我们的推理图片进行resize和normalize处理,这样才能与我们训练时的输入数据匹配。
本案例采用了一张Doberman的图片作为推理图片来测试模型表现,期望模型可以给出正确的预测结果。
# 导入ImageFolderDataset类,用于加载和处理推理数据集
dataset_infer = ImageFolderDataset(os.path.join(data_path, "infer"), shuffle=True)
# 定义推理数据集的转换操作,包括解码、调整大小、归一化和格式转换
trans_infer = [
transforms.Decode(), # 解码图像
transforms.Resize([224, 224]), # 将图像大小调整为224x224
transforms.Normalize(mean=mean, std=std), # 归一化操作,参数mean和std是均值和标准差
transforms.HWC2CHW() # 将图像格式从高度x宽度x通道(HWC)转换为通道x高度x宽度(CHW)
]
# 将转换操作应用到推理数据集,num_parallel_workers=1表示使用1个线程进行转换操作
dataset_infer = dataset_infer.map(operations=trans_infer,
input_columns=["image"],
num_parallel_workers=1)
# 将数据集转换为批次处理,这里设置每批只有1个样本,适用于单张图像的推理
dataset_infer = dataset_infer.batch(1)
接下来,我们将调用模型的predict方法进行模型。
在推理过程中,通过index2label就可以获取对应标签,再通过自定义的show_result接口将结果写在对应图片上。
import os # 导入操作系统接口模块
import pathlib # 导入路径处理模块
import cv2 # 导入OpenCV库
import numpy as np # 导入NumPy库
from PIL import Image # 导入PIL图像处理库
from enum import Enum # 导入枚举类型模块
from scipy import io # 导入SciPy库的io模块
# 定义一个枚举类Color,包含常见的颜色值
class Color(Enum):
"""Define enum for colors."""
red = (0, 0, 255)
green = (0, 255, 0)
blue = (255, 0, 0)
cyan = (255, 255, 0)
yellow = (0, 255, 255)
magenta = (255, 0, 255)
white = (255, 255, 255)
black = (0, 0, 0)
# 检查文件是否存在
def check_file_exist(file_name: str):
"""Check if a file exists."""
if not os.path.isfile(file_name):
raise FileNotFoundError(f"File `{file_name}` does not exist.")
# 根据输入获取颜色值
def color_val(color):
"""Get color value from different types of input."""
# 这里省略了color_val函数的实现,它根据输入返回颜色的RGB值
# 读取图像
def imread(image, mode=None):
"""Read an image from a file or a numpy array."""
# 这里省略了imread函数的实现,它根据输入返回一个图像数组
# 写入图像到文件
def imwrite(image, image_path, auto_mkdir=True):
"""Write an image to a file."""
# 这里省略了imwrite函数的实现,它将图像数组保存到文件
# 显示图像
def imshow(img, win_name='', wait_time=0):
"""Display an image using OpenCV."""
# 这里省略了imshow函数的实现,它使用OpenCV显示图像
# 显示推理结果
def show_result(img: str,
result: Dict[int, float],
text_color: str = 'green',
font_scale: float = 0.5,
row_width: int = 20,
show: bool = False,
win_name: str = '',
wait_time: int = 0,
out_file: Optional[str] = None) -> None:
"""Mark the prediction results on the picture."""
# 这里省略了show_result函数的实现,它在图像上标记推理结果
# 将ImageNet数据集的索引转换为标签
def index2label():
"""Convert index to label for ImageNet dataset."""
# 这里省略了index2label函数的实现,它返回一个映射,将索引转换为类别名称
# 推理数据并显示结果
# 这里省略了推理循环的实现,它使用模型对图像进行推理,并将结果打印和显示在图像上
推理过程完成后,在推理文件夹下可以找到图片的推理结果,可以看出预测结果是Doberman,与期望结果相同,验证了模型的准确性。
心得:
-
Vision Transformer是自然语言处理(NLP)中Transformer模型与计算机视觉(CV)结合的一个成功案例。它打破了以往图像处理依赖卷积神经网络(CNN)的传统,展示了跨领域技术融合的巨大潜力。
-
通过学习ViT,我更加理解了自注意力机制在处理序列数据时的优势。它能够捕捉长距离依赖关系,并使模型能够更好地理解图像中的空间结构信息。
-
ViT的模型结构展示了极高的灵活性。通过简单地调整模型参数,如patch大小、嵌入维度、层数等,就可以适应不同大小和复杂度的任务。
-
教程提供了从环境搭建到模型训练、验证和推理的完整流程,让我体验到了端到端的深度学习项目开发过程。
-
在数据预处理、模型微调、超参数选择等环节,教程都给出了详细的指导,使我意识到在深度学习项目中,细节处理对于最终结果的影响至关重要。
-
通过阅读和实践教程中的代码,我学习到了如何在MindSpore框架下实现ViT模型。代码的可读性和实用性让我对模型的工作原理有了更深刻的理解。
-
随着深度学习领域的快速发展,不断有新的模型和方法被提出。学习ViT后,我意识到了持续学习和跟进最新研究的重要性。
-
通过跟随教程进行实验,我体会到了理论与实践相结合的重要性。亲自动手实践能够帮助我更好地理解概念,并解决实际问题。
学习Vision Transformer图像分类不仅加深了我对深度学习模型的理解,也激发了我探索新领域和新技术的兴趣。
加油!!!