课程学习
本节课主要对于大白AI课程:https://mp.weixin.qq.com/s/STbdSoI7xLeHrNyLlw9GOg
《Pytorch模型推理及多任务通用范式》课程中的第三节课进行学习。
作业题目
包含以下必做题和思考题
1、必做题:
1.1 把模型改为 resnet18,加载相应的模型权重( Lesson2 的物料包中有),跑一下 0.jpg和 1.jpg,看一下输出结果。官方 torchvision 训练 mobilenet 和训练 resnet 的方式是一样的,所以数据预处理和数据后处理部分完全相同。
1.2 自己找 2 张其他图,用 resnet18 做下推理。
2、思考题:
2.1 以 ResNet18 为例,用 time 模块和 for 循环,对”./images/0.jpg” 连续推理 100
次,统计时间开销,比如:
model_classify=ModelPipline()
import time
image=cv2.imread("./images/0.jpg")
t_all=0
for i in range(100):
t_start=time.time()
result=model_classify.predict(image)
t_end=time.time()
t_all+=t_end-t_start
print(t_all)
有 CUDA 的同学,改下代码: self.device=torch.device(‘cuda’)。用上述相同方法测试时间开销。
2.2 在数据预处理和数据后处理的代码实现中,到处在用 numpy, opencv, torch 对数组做相应变换,大家至少要把课程中出现的函数们给理解。
3、总结
作业答案
1、必做题:
1.1 resnet18输出结果
修改get_model()代码如下:
def get_model():
# 修改模型为resnet18
model = models.resnet18(num_classes=1000)
# 加载resnet18的预训练模型
pretrained_state_dict = torch.load('./weights/resnet18-5c106cde.pth', map_location=lambda storage, loc: storage)
model.load_state_dict(pretrained_state_dict, strict=True)
model.to(device=device)
model.eval()
return model
输出结果如下:
(‘umbrella’, 0.9995712637901306)
(‘peacock’, 0.9999839067459106)
1.2 使用自己找的图片进行resnet18分类
图片如下,图片为百度的图片,图1为柯基狗,图2为暹罗猫:
输出结果如下:
(‘Pomeranian’, 0.5854851603507996)
(‘Siamese cat’, 0.9999914169311523)
结论:
resnet的模型参数量很小,预测准确率不是很高,同样的图片,使用mobilenet_v2模型进行预测的结果如下,预测正确。
(‘Pembroke’, 0.5984793901443481)
(‘Siamese cat’, 0.999940037727356)
2、思考题:
2.1 以 ResNet18 为例,计算推理时间开销,分别使用gpu和cpu进行测试
首先测试cpu的结果,修改__main__函数为以下代码:
if __name__=='__main__':
model_classify = ModelPipline()
import time
image=cv2.imread('./images/2.jpg')
t_all = 0
for i in range(100):
t_start = time.time()
result = model_classify.predict(image)
t_end = time.time()
t_inter = t_end - t_start
# print(t_inter)
t_all += t_inter
print("推理一百次运行时间:{}".format(t_all))
运行结果如下:
推理一百次运行时间:6.4180748462677
接下来在ModelPipline中的__init__函数中修改self.device为gpu:
self.device=torch.device('cuda')
运行结果:
推理一百次运行时间:1.042039155960083
结论:
相比较于使用cpu的推理时间,我的gpu(1050ti)推理时间是cpu的6倍,正如大潘老师在第二节中讲到的,算力是决定模型推理速度的重要因素,cpu的算力相较于gpu的算力要小很多,然而对于不同的设备拥有不同的算力来说,同样的代码在不同的设备的测试结果是不同的,如嵌入式端的设备的算力通常因为体积和成本等问题,会非常小。在部署的过程中,如何在不降低模型过多精度的同时,尽可能地提升算法的推理速度,这是一个很重要的问题。
2.2 理解numpy, opencv, torch 数组的相应变换
我们来重头捋一遍整个数组的变换。
0) 经过opencv读取的图片,会直接转化为numpy数组:
image=cv2.imread('./images/2.jpg')
print(type(image))
输出结果为numpy数组:
<class ‘numpy.ndarray’>
1)numpy数组经过前处理阶段中的数据变换:
首先使用opencv中的函数将读取到的图片数组转换成推理阶段的通道对齐(cv2.cvtColor)和模型推理尺寸(cv2.resize)。
# opencv默认读入是BGR,需要转为RGB,和训练时保持一致
image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)
# resize成模型输入的大小,和训练时保持一致
image = cv2.resize(image, dsize=inputs_size)
print(type(image))
输出结果是numpy数组,但是操作使用的为opencv的函数:
<class ‘numpy.ndarray’>
接着对数组进行归一化和标准化操作,并将数组维度调整到与模型输入维度对齐。
# 归一化和标准化处理,和训练时保持一致
inputs = image / 255
inputs = (inputs - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225])
# 与模型输入维度对齐
# (H, W, C) -> (C, H, W),transpose修改维度顺序
inputs = inputs.transpose(2, 0, 1)
# (C, H, W) -> (1, C, H, W), 1为新增batch_size,默认为1即可
inputs = inputs[np.newaxis, :, :, :]
print(type(inputs))
输出结果是numpy数组,但是此时的操作使用的为numpy的函数:
<class ‘numpy.ndarray’>
最后将numpy通过torch.from_numpy函数将numpy数组转换为torch的Tensor,并设置其数据类型和推理设备,与模型加载保持一致。
# NumpyArray -> Tensor
inputs = torch.from_numpy(inputs)
# dtype float32
inputs = inputs.type(torch.float32)
# 与model放在相同设备上
inputs = inputs.to(device=device)
print(type(inputs))
输出结果为Tensor,此时的输出即为模型推理的输入,前处理结束。
<class ‘torch.Tensor’>
2)模型推理过程中的数据变换:
模型推理过程中的数据变换很简单,经过了网络输出的推理结果,维度发生了很大的变化,但是数据类型没有改变。
outputs = model(inputs)
print(type(outputs), outputs.size())
输出结果为1*1000维度的Tensor张量:
<class ‘torch.Tensor’> torch.Size([1, 1000])
3)模型后处理过程中的数据变换:
后处理的过程,主要是将模型推理的结果,解码为我们需要得到的输出结果,即网络推理得到的图片的类别和置信度。
输出结果首先通过softmax函数得到1000类中每个类的置信度,并通过max函数选择置信度最高的类别的作为推理的结果,即将维度从1000 -> 1,此时的数据类型还是Tensor。
# 取softmax得到每个类别的置信度
outputs = torch.softmax(outputs, dim=1)
# 取最高置信度的类别和分数
score, label_id = torch.max(outputs, dim=1)
print(type(score), score.size())
print(type(label_id), label_id.size())
输出结果为1维的Tensor张量:
<class ‘torch.Tensor’> torch.Size([1])
<class ‘torch.Tensor’> torch.Size([1])
最后一步,从score和label_id的Tensor中提取出真正的置信度分数和标签id,并使用标签id从label_names中提取出真正的标签名字。
# Tensor -> Float
score, label_id = score.item(), label_id.item()
# 查找标签名字
label_name = label_names[label_id]
print(type(score), type(label_id), type(label_name))
输出结果分别为置信度分数、标签id和标签名字的数据类型:
<class ‘float’> <class ‘int’> <class ‘str’>
到此,一次完整的推理过程结束。
3、总结
非常感谢大潘老师的课程!!上课的时候因为有事没有时间听,但是后面按照笔记自己完整的实现了一遍pytorch模型推理的完整过程,从最基础的数据读取,到数据转换到最后的数据处理,都有一定的清晰思路。之前在看源码的过程中,分开看每个模块,却缺少了整体模块的完整性结构,(加载模型)前处理、模型推理、后处理,完整的模型推理结构,是后续我学习不同算法的基本框架,受益匪浅。