今天给大家带来AlexNet 网络详解部分, 废话不多说,开整!
B站讲解:经典神经网络超详细(四): AlexNet网络(论文精读+网络详解+代码实战)下_哔哩哔哩_bilibili
首先,代码部分还是分为3个文件 model.py, train.py, predict.py ,以及一个数据集dataset
dataset
数据集来自霹雳吧啦,数据集我放在了当前目录下!
使用步骤如下
-
(1)在data_set文件夹下创建新文件夹"flower_data"
-
(2)点击链接下载花分类数据集 https://storage.googleapis.com/download.tensorflow.org/example_images/flower_photos.tgz
-
(3)解压数据集到flower_data文件夹下
-
(4)执行"data_split.py"脚本自动将数据集划分成训练集train和验证集val (data_split.py脚本在dataset文件下)
├── flower_data
├── flower_photos(解压的数据集文件夹,3670个样本)
├── train(生成的训练集,3306个样本)
└── test(生成的验证集,364个样本)
model.py
AlexNet 一共分为5层卷积层,3层全连接层,代码如下:
每层的数据的维度计算公式:
经过卷积的矩阵尺寸大小计算公式为: N = (W - F + 2P)/S +1
a: 输入图片的大小为 w x w
b: Filter 大小 F x F (卷积核的大小)
c: 步长s
d: padding 的像素数p
import torch
import torch.nn as nn
class AlexNet(nn.Module):
def __init__(self,num_classes=1000):
super(AlexNet,self).__init__()
self.net = nn.Sequential(
# (3,224,224) --> (48,55,55) 也可以设置成(3,227,227)作为原始图像,就不用设置padding
nn.Conv2d(in_channels=3,out_channels=48,kernel_size=11,stride=4,padding=2),
nn.ReLU(inplace=True),
#(48,55,55)->(48,27,27)
nn.MaxPool2d(kernel_size=3,stride=2),
#LRN函数 (对5个通道进行归一化(必须是奇数),alpha控制调节的幅度,
# beta控制归一化后的放大或压缩效果,k为偏移量)
nn.LocalResponseNorm(size=5,alpha=0.0001,beta=0.75,k=2), #[论文创新点]
#(48,27,27) -> (128,27,27)
nn.Conv2d(48,128,5,padding=2),
nn.ReLU(inplace=True),
#(128,27,27) -> (128,13,13)
nn.MaxPool2d(kernel_size=3,stride=2),
# 同上局部归一化 [论文创新点]
nn.LocalResponseNorm(size=5,alpha=0.0001,beta=0.75,k=2),
#(128,13,13) --> (192,13,13)
nn.Conv2d(128,192,3,padding=1),
nn.ReLU(inplace=True),
#(192,13,13) --> (192,13,13)
nn.Conv2d(192,192,3,padding=1),
nn.ReLU(inplace=True),
# (192,13,13) -> (128,13,13)
nn.Conv2d(192,128,3,padding=1),
nn.ReLU(inplace=True),
# (128,13,13) --> (128,6,6)
nn.MaxPool2d(kernel_size=3,stride=2),
)
self.classifier = nn.Sequential(
nn.Dropout(p=0.5), # 随机失活 [论文创新点]
nn.Linear(in_features=(128*6*6),out_features=2048),
nn.ReLU(inplace=True),#就地操作,直接修改张量
nn.Dropout(p=0.5),
nn.Linear(in_features=2048,out_features=2048),
nn.ReLU(inplace=True),
nn.Linear(in_features=2048,out_features=num_classes),
)
def forward(self,x):
x = self.net(x)
x = torch.flatten(x,start_dim=1) #[b,c,h,w] 从第一维度展开
x = self.classifier(x)
return x
代码结果:
image-20241223185533502
AlexNet 论文创新点:
-
nn.LocalResponseNorm(size=5,alpha=0.0001,beta=0.75,k=2) 这里的的LRN是论文当中的创新点
-
nn.Dropout(p=0.5) 随机失活的方法, p=0.5 在训练和测试的时候,全连接的每一层的神经元会随机失活一半
nn.Sequential
适用与不适用场景
适用场景:
- 线性堆叠的模型结构:
-
适用于模型中的操作是简单的顺序结构,每一层的输出都直接作为下一层的输入,且没有复杂的条件判断或分支。
-
示例:传统的卷积神经网络(CNN)模型、基本的全连接网络(FC)等。
-
- 没有多个输入或输出:
-
当模型只需要一个输入并且产生一个输出时,
nn.Sequential
非常方便。它自动将多个层(如卷积层、激活层、池化层)串联在一起,不需要手动写forward()
方法。
-
- 原型设计和快速实验:
-
如果你需要快速搭建一个简单的神经网络,
nn.Sequential
可以帮助你简化代码结构,不必手动定义每一层的执行顺序。
-
- 不需要动态调整计算图:
-
如果模型的计算图结构是静态的(即在定义时已经确定),并且不需要在训练过程中根据输入或条件调整图结构,可以使用
nn.Sequential
。
-
- 不涉及共享层或参数:
-
如果每一层都是独立的,并且不需要跨层共享权重或参数,
nn.Sequential
非常适合。它不支持层之间的参数共享,适用于每层都有独立参数的情况。
-
不适用场景:
- 具有条件判断或分支结构的网络:
-
如果模型的结构涉及条件判断(如 if-else 语句),或者根据不同输入选择不同的路径(即多分支网络),
nn.Sequential
无法处理,因为它假设每一层的操作是顺序的,并且每层的输出都作为下一层的输入。 -
例子:在
forward()
方法中,如果你需要根据输入大小、内容或者其他因素选择不同的层或路径。
-
- 需要多个输入的模型:
-
如果你的模型需要接受多个输入并且对这些输入进行不同的处理,
nn.Sequential
不适合。nn.Sequential
只能处理单一输入并顺序传递。 -
例子:多输入模型(例如多模态学习),或者模型中需要将不同输入合并后再进行处理的场景。
-
- 需要灵活的前向传播逻辑:
-
如果
forward()
方法中需要执行一些复杂的操作(如循环、动态生成层、基于输入的动态选择等),nn.Sequential
就不适用了。nn.Sequential
对forward()
方法的操作是非常简单和线性的,无法支持灵活的控制流或动态的计算图构建。
-
- 需要共享参数的情况:
-
在某些情况下,模型中的不同层需要共享相同的权重参数(例如,同一卷积核在不同位置应用)。
nn.Sequential
不支持层之间共享参数,因此对于这种需求,必须手动编写forward()
方法来实现共享。
-
- 需要复用相同层多次:
-
如果你需要在
forward()
中多次使用同一层(例如,多个地方调用相同的卷积层),nn.Sequential
无法满足这种需求,因为每一层在nn.Sequential
中都只会被执行一次。你需要手动将层的实例传递给不同的地方。
-
- 复杂的计算图:
-
如果模型中的层之间有复杂的依赖关系,或者有循环、跳跃连接(例如,残差连接、递归神经网络等),
nn.Sequential
不适用,因为它只能处理顺序连接的层,不能处理复杂的依赖关系。
-
总结
-
适合使用
nn.Sequential
的场景:简单的顺序模型,静态计算图,单输入单输出,快速原型设计,不涉及共享参数和复杂的计算图。 -
不适合使用
nn.Sequential
的场景:需要条件判断、多输入、多输出、共享参数、复杂计算图或灵活控制流的模型。
train.py
import os
import sys
import json
import torch
import torch.nn as nn
from torchvision import transforms,datasets,utils
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
from tqdm import tqdm
from model import AlexNet
def main():
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
data_transform = {
"train": transforms.Compose([
transforms.RandomResizedCrop(224), #训练的时候需要数据增强的数据 [论文创新点]
transforms.RandomHorizontalFlip(), #水平翻转 [论文创新点]
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
]),
"test":transforms.Compose([
transforms.Resize((224,224)),
transforms.ToTensor(),
transforms.Normalize((0.5,0.5,0.5),(0.5,0.5,0.5))
])
}
image_path = 'data_set/flower_data'
#返回二元组(image,label)
train_dataset = datasets.ImageFolder(root=os.path.join(image_path,"train"),
transform=data_transform["train"])
flower_list = train_dataset.class_to_idx
cla_dict = dict((value,key) for key,value in flower_list.items())
json_str = json.dumps(cla_dict,indent=4) #由字典转为json格式的字符串,并首行缩进
with open("class_indices.json","w") as json_file:
json_file.write(json_str)
batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset,
batch_size=batch_size,
shuffle=True,
num_workers=0)
train_num = len(train_dataset)
test_dataset = datasets.ImageFolder(root=os.path.join(image_path,"test"),
transform=data_transform["test"])
test_num = len(test_dataset)
test_loader = torch.utils.data.DataLoader(test_dataset,
batch_size=4,
shuffle=False,
num_workers=0)
print(f"using {train_num} images for training,{test_num} images for test")
net = AlexNet(num_classes=5)
net.to(device)
loss_function = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(),lr=0.001)
epochs = 10
save_path = "./AlexNet.pth"
best_accurate = 0.0
train_steps = len(train_loader)
for epoch in range(epochs):
net.train() # 训练模式 主要影响模型中的dropout
running_loss = 0.0
#train_loader = tqdm(train_loader, file = sys.stdout) # 显示训练的进度条
for _,data in enumerate(train_loader):
images,labels = data
optimizer.zero_grad()
outputs = net(images.to(device))
loss = loss_function(outputs,labels.to(device))
loss.backward()
optimizer.step()
running_loss += loss.item()
#print(f"train epoch[{epoch+1}/{epochs}] loss:{loss:.3f}")
net.eval() # 评估模式 主要影响模型中的dropout
acc = 0.0
with torch.no_grad():
test_loader = tqdm(test_loader,file=sys.stdout)
for test_data in test_loader:
test_image,test_labels = test_data
outputs = net(test_image.to(device))
predict_y = torch.argmax(outputs,dim=1)
acc += torch.eq(predict_y,test_labels.to(device)).sum().item()
test_accurate = acc/test_num
print("[epoch %d] train_loss: %3.f test_accuracy:%.3f" %
(epoch+1,running_loss/train_steps,test_accurate))
if test_accurate > best_accurate: # 每一轮数据进行对比
print(best_accurate)
best_accurate = test_accurate
torch.save(net.state_dict(),save_path)
print("Finished Training")
if __name__ == "__main__":
main()
这段代码实现了一个简单的图像分类任务,使用了 AlexNet
模型并训练它来识别图像数据。代码中还涉及了数据预处理、模型训练、测试和保存最优模型的步骤。下面是对代码的详细解析:
import os
import sys
import json
import torch
import torch.nn as nn
from torchvision import transforms, datasets, utils
import matplotlib.pyplot as plt
import numpy as np
import torch.optim as optim
from tqdm import tqdm
from model import AlexNet
-
os
和sys
:用于文件路径操作和系统级的功能。 -
json
:用于将类标签(class labels)字典保存为 JSON 格式。 -
torch
和torch.nn
:PyTorch 的核心库和神经网络模块。 -
torchvision.transforms
:数据预处理和数据增强工具。 -
torchvision.datasets
:用于加载常见数据集(如 CIFAR-10、ImageNet 等)的模块。 -
matplotlib.pyplot
:用于可视化图像数据。 -
torch.optim
:PyTorch 中优化算法的模块,用于优化模型的参数。 -
tqdm
:用于显示训练和测试的进度条。 -
AlexNet
:导入自定义的 AlexNet 模型类。
数据预处理
data_transform = {
"train": transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
]),
"test": transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
])
}
train
数据转换:-
RandomResizedCrop(224)
:随机裁剪图像并调整到 224x224 的大小,增加数据的多样性。 -
RandomHorizontalFlip()
:随机水平翻转图像,进一步增强数据。 -
ToTensor()
:将图像转换为 PyTorch 张量,且将像素值从[0, 255]
转换到[0.0, 1.0]
。 -
Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
:对图像进行标准化,使每个通道的均值为 0.5,标准差为 0.5。
-
test
数据转换:-
Resize((224, 224))
:调整图像大小为 224x224。 -
ToTensor()
和Normalize()
同样用于转换和标准化测试数据。
-
数据加载
image_path = 'data_set/flower_data'
train_dataset = datasets.ImageFolder(root=os.path.join(image_path, "train"), transform=data_transform["train"])
flower_list = train_dataset.class_to_idx
cla_dict = dict((value, key) for key, value in flower_list.items())
json_str = json.dumps(cla_dict, indent=4)
with open("class_indices.json", "w") as json_file:
json_file.write(json_str)
-
datasets.ImageFolder
:ImageFolder
是一个用于加载图像数据集的类,要求数据集按照子文件夹组织,每个子文件夹代表一个类别。 -
train_dataset.class_to_idx
:返回类名到索引的映射字典。比如'rose': 0
,'tulip': 1
等。 -
json.dumps()
:将类名到索引的字典转换为 JSON 格式字符串并保存到文件class_indices.json
中。
数据加载器
batch_size = 32
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=0)
train_num = len(train_dataset)
test_dataset = datasets.ImageFolder(root=os.path.join(image_path, "test"), transform=data_transform["test"])
test_num = len(test_dataset)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=4, shuffle=False, num_workers=0)
-
DataLoader
:PyTorch 提供的数据加载器,用于批量加载训练和测试数据。
-
train_loader
:加载训练数据集,每批大小为 32,且启用了shuffle=True
,使得每次训练时数据顺序不同,避免模型过拟合。 -
test_loader
:加载测试数据集,每批大小为 4,且不打乱数据顺序。
-
初始化模型和优化器
net = AlexNet(num_classes=5)
net.to(device)
loss_function = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
-
AlexNet(num_classes=5)
:实例化 AlexNet 模型,指定输出类别数量为 5。 -
loss_function = nn.CrossEntropyLoss()
:指定损失函数为交叉熵损失(用于分类任务)。 -
optimizer = optim.Adam(net.parameters(), lr=0.001)
:使用 Adam 优化器来优化模型的参数,学习率设为 0.001。
训练与测试
epochs = 10
save_path = "./AlexNet.pth"
best_accurate = 0.0
train_steps = len(train_loader)
for epoch in range(epochs):
net.train()
running_loss = 0.0
for _, data in enumerate(train_loader):
images, labels = data
optimizer.zero_grad()
outputs = net(images.to(device))
loss = loss_function(outputs, labels.to(device))
loss.backward()
optimizer.step()
running_loss += loss.item()
net.eval()
acc = 0.0
with torch.no_grad():
test_loader = tqdm(test_loader, file=sys.stdout)
for test_data in test_loader:
test_image, test_labels = test_data
outputs = net(test_image.to(device))
predict_y = torch.argmax(outputs, dim=1)
acc += torch.eq(predict_y, test_labels.to(device)).sum().item()
test_accurate = acc / test_num
print("[epoch %d] train_loss: %.3f test_accuracy: %.3f" % (epoch+1, running_loss/train_steps, test_accurate))
if test_accurate > best_accurate:
best_accurate = test_accurate
torch.save(net.state_dict(), save_path)
- 训练阶段:
-
net.train()
:将模型设置为训练模式,影响诸如 Dropout 层等行为。 -
每一轮训练中,通过
train_loader
加载一批训练数据,计算输出并计算损失。 -
optimizer.zero_grad()
:清除之前的梯度,防止梯度累积。 -
loss.backward()
:反向传播计算梯度。 -
optimizer.step()
:更新模型参数。
-
- 测试阶段:
-
net.eval()
:将模型设置为评估模式,影响如 Dropout 层等的行为。 -
使用
torch.no_grad()
禁用梯度计算,减少内存消耗。 -
通过
test_loader
加载测试数据,计算预测结果和准确率。 -
torch.argmax(outputs, dim=1)
:获取最大值对应的索引,即预测的类别。
-
- 模型保存:
-
如果当前测试准确率超过了之前的最佳准确率(
best_accurate
),则保存当前模型的权重(net.state_dict()
)。 -
torch.save(net.state_dict(), save_path)
:保存模型权重到指定路径AlexNet.pth
。
-
总结
-
数据处理:对训练和测试数据进行适当的变换和标准化。
-
模型训练:每一轮训练中,计算损失,反向传播并更新参数。测试阶段计算准确率,保存最优模型。
-
保存最优模型:根据测试准确率保存当前最优模型的参数,避免过拟合并提高模型的泛化能力。
predict.py
import os
import json
import torch
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
from model import AlexNet
def main():
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
data_transform = transforms.Compose(
[transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
img_path = "tulip.png"
img = Image.open(img_path).convert("RGB")
plt.imshow(img)
img = data_transform(img)
img = torch.unsqueeze(img, dim=0) #在第0维度上增加一个batch维度
json_path = 'class_indices.json'
with open(json_path, "r") as f:
class_indict = json.load(f) #json转换为字典
model = AlexNet(num_classes=5).to(device)
weights_path = "AlexNet.pth"
model.load_state_dict(torch.load(weights_path))
model.eval() #开启评估模式
with torch.no_grad():
output = torch.squeeze(model(img.to(device))).cpu() #去掉维度=1的维度
predict = torch.softmax(output, dim=0) # 用softmax 改为概率分布,一维计算
predict_cla = torch.argmax(predict).numpy()
print_res = "class: {} prob: {:.3}".format(class_indict[str(predict_cla)], predict[predict_cla].numpy())
plt.title(print_res)
for i in range(len(predict)):
print("class: {:10} prob: {:.3}".format(class_indict[str(i)],
predict[i].numpy()))
plt.show()
if __name__ == '__main__':
main()
这段代码的目的是使用训练好的 AlexNet
模型对一张图像进行分类预测,并输出预测结果。下面是对代码的详细解析:
1. 导入必要的库和模块
import os
import json
import torch
from PIL import Image
from torchvision import transforms
import matplotlib.pyplot as plt
from model import AlexNet
-
os:用于路径操作和文件管理(虽然在此代码中没有显式使用
os
,但可能在未来扩展时使用)。 -
json:用于加载保存的类索引(
class_indices.json
)。 -
torch:PyTorch 的核心库,用于进行张量运算和加载模型。
-
PIL:Python Imaging Library,用于图像读取和处理。这里使用
Image.open
来加载图像。 -
torchvision.transforms:用于图像预处理,如调整大小、标准化等。
-
matplotlib.pyplot:用于可视化图像(
plt.imshow
和plt.show
)。 -
model.AlexNet:从自定义模块
model
导入已经定义好的AlexNet
类,假设它是一个定义好的深度学习模型。
2. 设置设备和数据转换
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
data_transform = transforms.Compose(
[transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
-
device
确定运行设备cuda:0
表示使用第一个 GPU,如果没有 GPU 则使用 CPU。
-
torch.cuda.is_available()
:检查是否有 GPU 可用。
-
-
data_transform
:定义数据预处理流程,用于图像预处理:
-
Resize((224, 224))
:将图像调整为 224x224 的大小,这是AlexNet
输入层要求的尺寸。 -
ToTensor()
:将图像数据从[0, 255]
范围转换为[0, 1]
,并转化为 PyTorch 的张量格式。 -
Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
:将每个通道的均值和标准差都设为 0.5,用于标准化图像。
-
3. 加载图像并进行处理
img_path = "tulip.png"
img = Image.open(img_path).convert("RGB")
plt.imshow(img)
img = data_transform(img)
img = torch.unsqueeze(img, dim=0) # 在第0维度上增加一个batch维度
-
**Image.open(img_path).convert("RGB")**:加载并将图像转换为 RGB 模式,这样无论原始图像是否有透明通道(RGBA),都会统一成 3 通道的图像。
-
**plt.imshow(img)**:用
matplotlib
显示原始图像。 -
**data_transform(img)**:应用定义好的预处理操作,将图像转换为符合模型要求的格式。
-
**torch.unsqueeze(img, dim=0)**:因为
AlexNet
模型要求输入是批量的图像,所以需要将图像张量的形状从[C, H, W]
(单张图像)变成[1, C, H, W]
(批量大小为 1)。dim=0
表示在第 0 维增加一个维度(即批量维度)。
4. 加载类索引字典
json_path = 'class_indices.json'
with open(json_path, "r") as f:
class_indict = json.load(f) # json 转换为字典
-
json.load(f)
:读取
class_indices.json
文件,并将其内容解析为 Python 字典。该文件应该包含类别的名称和索引的映射关系。例如:
{"0": "daisy", "1": "dandelion", "2": "roses", "3": "sunflowers", "4": "tulips"}
5. 加载预训练模型
model = AlexNet(num_classes=5).to(device)
weights_path = "AlexNet.pth"
model.load_state_dict(torch.load(weights_path))
-
**model = AlexNet(num_classes=5)**:实例化
AlexNet
模型并指定输出类别数为 5。 -
**model.to(device)**:将模型转移到适当的设备(GPU 或 CPU)。
-
**model.load_state_dict(torch.load(weights_path))**:加载保存的预训练模型权重,
weights_path
指向模型权重文件AlexNet.pth
。
6. 评估模式和预测
python复制代码model.eval() # 开启评估模式
with torch.no_grad():
output = torch.squeeze(model(img.to(device))).cpu() # 去掉维度=1的维度
predict = torch.softmax(output, dim=0) # 用 softmax 转换为概率分布
predict_cla = torch.argmax(predict).numpy()
-
**model.eval()**:将模型设置为评估模式,这样模型中的某些层(如 Dropout 和 BatchNorm)会以评估模式运行。
-
**torch.no_grad()**:禁用梯度计算,减少内存使用并加速推理过程。
-
**model(img.to(device))**:将图像输入模型并获取输出。注意,
img.to(device)
会将图像数据传输到 GPU 或 CPU。 -
**torch.squeeze(output)**:去掉输出张量中维度为 1 的维度,确保输出是一个 1D 向量。
-
**torch.softmax(output, dim=0)**:将模型的输出通过 Softmax 函数转换为概率分布。
-
**torch.argmax(predict)**:获取概率最高的类别索引,
torch.argmax
返回沿指定维度的最大值的索引。
7. 显示和打印预测结果
print_res = "class: {} prob: {:.3}".format(class_indict[str(predict_cla)], predict[predict_cla].numpy())
plt.title(print_res)
for i in range(len(predict)):
print("class: {:10} prob: {:.3}".format(class_indict[str(i)], predict[i].numpy()))
plt.show()
-
print_res:格式化输出预测类别及其对应的概率。
class_indict[str(predict_cla)]
获取预测的类别名称,predict[predict_cla].numpy()
获取对应类别的概率。 -
**plt.title(print_res)**:将预测结果显示在图像的标题上。
-
**for i in range(len(predict))**:遍历每个类别并打印它的名称和概率。
-
**plt.show()**:显示图像和预测结果。
8. 程序入口
if __name__ == '__main__':
main()
-
**if *name* == '*main*':**:确保只有当此文件作为主程序运行时才执行
main()
函数。如果这个文件作为模块导入到其他程序中时,main()
不会被自动执行。
总结
-
该代码首先加载一张图像,并对其进行预处理(调整大小、转换为张量和标准化)。
-
然后加载事先训练好的
AlexNet
模型,并加载对应的权重。 -
将预处理后的图像传入模型,使用
softmax
函数将模型的输出转换为概率分布。 -
最后,输出预测结果,并显示图像及其预测类别和概率。
这段代码是一个典型的图像分类推理过程,适用于已经训练好的模型,能够用来对新的图像进行分类。