赞
踩
在这个问题中,你将面临一个经典的机器学习分类挑战——猫狗大战。你的任务是建立一个分类模型,能够准确地区分图像中是猫还是狗
通过训练一个机器学习模型,使其在给定一张图像时能够准确地预测图像中是猫还是狗。模型应该能够推广到未见过的图像,并在测试数据上表现良好。我们期待您将其部署到模拟的生产环境中——这里推理时间和二分类准确度(F1分数)将作为评分的主要依据。
数据集:链接:百度网盘 请输入提取码
提取码:esip
本题是基于CNN的猫狗图像识别,其中训练数据集包括25000张图片,其中类别为猫的图片有12500张图片,类别为狗的图片有12500张图片,比例为1:1。数据集命名方式为type.num.jpg。
本项目数据集共由两部分组成,分别包含为test,train,文件夹。其中test文件由train文件划分,比例为8:2。
图2-1 数据集结构
train与test文件下包含两个子文件,分别为dog和cat。
图2-2 子文件夹结构
其中,dog子文件夹下包含了分类为狗的图像,cat子文件夹下包含了分类为猫的图像。
图2-3 test目录下cat子文件夹图像展示
图2-4 test目录下dog子文件夹图像展示
数据集划分,通过定义make_dir()创建与源文件类似的文件路径函数,将每类数据文件进行操作,通过random给出随机数,按照8:2的比例划分,并将图片转移至对应的文件夹里
关键代码如下:
- def make_dir(source, target):
- #############################
- # 创建和源文件相似的文件路径函数
- # :param source: 源文件位置
- # :param target: 目标文件位置
- #############################
- dir_names = os.listdir(source)
- for names in dir_names:
- for i in ['train', 'test']:
- path = target + '/' + i + '/' + names
- if not os.path.exists(path):
- os.makedirs(path)
-
-
- def divideTrain_Test(source, target):
- #############################
- # 创建和源文件相似的文件路径
- # :param source: 源文件位置
- # :param target: 目标文件位置
- #############################
- # 得到源文件下的种类
- pic_name = os.listdir(source)
-
- # 对于每一类里的数据进行操作
- for classes in pic_name:
- # 得到这一种类的图片的名字
- pic_classes_name = os.listdir(os.path.join(source, classes))
- random.shuffle(pic_classes_name)
-
- # 按照8:2比例划分
- train_list = pic_classes_name[0:int(0.8 * len(pic_classes_name))]
- test_list = pic_classes_name[int(0.8 * len(pic_classes_name)):]
-
- # 对于每个图片,移入到对应的文件夹里面
- for train_pic in train_list:
- shutil.copyfile(source + '/' + classes + '/' + train_pic, target + '/train/' + classes + '/' + train_pic)
- for test_pic in test_list:
- shutil.copyfile(source + '/' + classes + '/' + test_pic, target + '/test/' + classes + '/' + test_pic)
本文在transforms.Compose(),首先对图像大小进行调整,transforms.Resize(),使其调整为224*224;transforms.RandomVerticalFlip()依概率p对PIL图片进行垂直翻转。
其次,通过transforms.Normalize ()对三个通道进行标准化,数据全部符合均值为0、标准差为1的标准正太分布。
关键代码如下:
- transform = transforms.Compose([
- transforms.Resize([224, 224]), # 调整大小,使原始图像至224*224大小
- transforms.RandomVerticalFlip(p=0.4), # 依据概率p对PIL图片进行垂直翻转
- transforms.ToTensor(),
- transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 对三个通道进行标准化,数据全部符合均值为0、标准差为1的标准正态分布
- ])
本文事先对数据文件进行分类处理,使数据文件具有如下结构
图2-5 文件结构图
这里直接采用ImageFolder对文件进行读取,ImageFolder假设所有的文件按文件夹保存,每个文件夹下存储同一类别的图片,文件夹名为类名。
关键代码如下:
- # ImageFolder读取文件
- train_data = datasets.ImageFolder(r'.\data\train', transform=transform)
- test_data = datasets.ImageFolder(r'.\data\test', transform=transform)
数据集文件没有划分验证集,故读取train文件后,需要对train进行验证集划分,验证集比例为0.2。
关键代码如下:
- num_train = len(train_data)
- indices = list(range(num_train)) # 确定验证集图片个数
- np.random.shuffle(indices) # 打乱顺序
- split = int(np.floor(valid_size * num_train))
- train_idx, valid_idx = indices[split:], indices[:split] # 起始从第几个:末尾从第几个:步长
- train_sampler = SubsetRandomSampler(train_idx) # 确定采样的顺序,后面在制作train_loader的时候用这个列表得索引值取样本
- valid_sampler = SubsetRandomSampler(valid_idx)
卷积神经网络(Convolutional Neural Network,CNN),主要分为3个部分:输入层(Input), 卷积层(Conv),池化层(Pool), 和 全连层(FC)。
残差神经网络(ResNet)是由微软研究院的何恺明、张祥雨、任少卿、孙剑等人提出的。ResNet 在2015 年的ILSVRC(ImageNet Large Scale Visual Recognition Challenge)中取得了冠军。残差神经网络的主要贡献是发现了“退化现象(Degradation)”,并针对退化现象发明了 “快捷连接(Shortcut connection)”,极大的消除了深度过大的神经网络训练困难问题
本文采用的是Resnet-18模型,ResNet18的基本含义是,网络的基本架构是ResNet,网络的深度是18层。
下图是一个基本残差块。它的操作是把某层输入跳跃连接到下一层乃至更深层的激活层之前,同本层输出一起经过激活函数输出。
图3-1 Resnet架构图
卷积层,该层卷积核大小为7*7,stride为2
池化层,该层卷积核大小为3*3,stride为1
Conv2_x,该卷积核大小为3*3,stride为1,这里有两个层,不改变数据的大小和通道数
Conv3_x,通过一个1*1的卷积层,输出size为128*28*28,通道翻倍,数据大小减半
Conv4_x,同样1*1卷积,输出size为256*14*14
Conv5_x,和上述一样,输出size为512*7*7
平均池化层,最后输出size为512*1*1
图3-2 Resnet-18结构图
本模块实现残差部分,layer中,实现卷积-归一化-激活-卷积-归一化的过程,该部分不会改变特征图尺寸,shortcut实现残差,shortcut模块就是将原来的特征图通过1*1卷积提高通道数。
关键代码如下:
- class BasicBlock(nn.Module):
- def __init__(self, in_channels, out_channels, stride=[1, 1], padding=1) -> None:
- super(BasicBlock, self).__init__()
- # 残差部分
- # 一、layer部分不改变特征图尺寸
- # 卷积-归一化-激活-卷积-归一化
- self.layer = nn.Sequential(
- nn.Conv2d(in_channels, out_channels, kernel_size=3, stride=stride[0], padding=padding, bias=False), # 尺寸不变
- # 卷积层,二维卷积 inchannel思维张量,out期望的四维输出张量数,kernel卷积和
- nn.BatchNorm2d(out_channels),
- nn.ReLU(inplace=True), # 原地替换 节省内存开销
- nn.Conv2d(out_channels, out_channels, kernel_size=3, stride=stride[1], padding=padding, bias=False), # 尺寸不变
- nn.BatchNorm2d(out_channels)
- )
-
- # 二、shortcut 部分,实现残差
- # 如果输入输出通道不同,或者卷积步长做了改变,那就用if中的模型,否则不变。这里肯定是输入输出通道要改变的
- # 这里的shortcut模块就是将原来的特征图通过1*1卷积提高通道数
- # 由于存在维度不一致的情况 所以分情况
- # 卷积-归一化
- self.shortcut = nn.Sequential()
- if stride[0] != 1 or in_channels != out_channels:
- self.shortcut = nn.Sequential(
- # 卷积核为1 进行升降维
- # 注意跳变时 都是stride==2的时候 也就是每次输出信道升维的时候
- nn.Conv2d(in_channels, out_channels, kernel_size=1, stride=stride[0], bias=False), # 卷积
- nn.BatchNorm2d(out_channels)
- )
最后用forward顺接整个过程,其中out += self.shortcut(x)较为关键,也是Resnet的精华所在,shortcut卷积后和原特征图相加,如果相加后对模型提升并不明显也不影响后续进程。
- def forward(self, x): # block的前向反馈,包含主体的2个卷积和shortcut可能触发的一个卷积
- out = self.layer(x)
- # SE模块
- # out = self.se(out)
- out += self.shortcut(x) # shortcut卷积后和原特征图相加
- out = F.relu(out)
- return out
在Resnet-18中,堆叠了多个残差块来构建整个网络,每个残差块会将输入的特征图进行处理,并输出更加丰富的特征图。需要注意的是,在Resnet-18中,第一卷积层没有残差块,所以需要单独写。
在__init__中,num_classes值为1,最后输出一个0-1之间的数,故这里二分类判断,大于0.5为一类,小于0.5为另一类。
关键代码如下:
- class ResNet18(nn.Module):
- def __init__(self, BasicBlock, num_classes=1) -> None: # num_class是1,因为最后要输出一个0-1之间的数,小于0.5的作为一类,大于0.5的又作为一类
- super(ResNet18, self).__init__()
- self.in_channels = 64
- # 第一卷积层作为单独的 因为没有残差块。224*224*3
- self.conv1 = nn.Sequential(
- nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False),
- nn.BatchNorm2d(64), # 卷积后,对应上一层的所有特征图做归一化
- # nn.ReLU(inplace=True)
- nn.MaxPool2d(kernel_size=3, stride=2, padding=1)
- # 最大池化,3x3 的池化层窗口,112*112*64-56*56*64 (112-3)/2 +1 =55.5=56
- )
- # conv2_x 4个卷积是 3x3,64层的
- self.conv2 = self._make_layer(BasicBlock, 64, [[1, 1], [1, 1]]) # 56*56*64-56*56*64
- # conv3_x 4个卷积是 3x3,128层的
- self.conv3 = self._make_layer(BasicBlock, 128, [[2, 1], [1, 1]]) # 56*56*64-56*56*128
- # conv4_x 4个卷积是 3x3,256层的
- self.conv4 = self._make_layer(BasicBlock, 256, [[2, 1], [1, 1]]) # 56*56*128-56*56*256
- # conv5_x 4个卷积是 3x3,512层的
- self.conv5 = self._make_layer(BasicBlock, 512, [[2, 1], [1, 1]]) # 56*56*256-56*56*512
- self.avgpool = nn.AdaptiveAvgPool2d((1, 1)) # 自适应平均池化
- self.fc = nn.Linear(512, num_classes) # 设置网络中的全连接层的,输入的二维张量的大小,即输入的[batch_size, size]中的size
利用make_layer()函数实现对基本残差块的堆叠,关键代码如下:
- def _make_layer(self, block, out_channels, strides): # 整合
- layers = [] # 每个convi_x的结构保存在layers列表
- for stride in strides: # 通过循环堆叠其余残差块
- layers.append(block(self.in_channels, out_channels, stride))
- self.in_channels = out_channels
- return nn.Sequential(*layers) # ‘*’的作用是将list转换为非关键字参数传入
同样通过forward()顺接整个过程,关键代码如下:
- def forward(self, x): # 顺接过程
- out = self.conv1(x) # 只有最开始的一个卷积
- out = self.conv2(out) # 4个64层 3*3卷积
- out = self.conv3(out) # 4个128层 3*3卷积
- out = self.conv4(out) # 4个256层 3*3卷积
- out = self.conv5(out) # 4个512层 3*3卷积
-
- out = self.avgpool(out) # 平均池化
- out = out.reshape(x.shape[0], -1) # 平展
- out = torch.sigmoid(self.fc(out)) # 这里加入sigmoid以保证在0-1之间。而且最好调用torch.sigmod而非F.sigmod
- return out
SGD优化器,SGD全称Stochastic Gradient Descent,随机梯度下降,每次选择一个mini-batch,而不是全部样本,使用梯度下降来更新模型参数。
ReduceLROnPlateau 学习率调度器,本文学习率设置为0.01
n_epochs = 20
valid_loss_min = np.Inf,损失值,从无穷大开始,本文通过二值交叉熵损失函数Binary Cross Entropy Loss,来观测train_loss和vaild_loss
由于计算机算力限制,本文仅对模型进行了20训练。
- for epoch in range(1, n_epochs + 1):
- # 设置损失
- train_loss = 0.0
- valid_loss = 0.0
- ##########
- # 训练模块
- ##########
- model.train()
- for data, target in train_loader:
- # 在gpu上去训练
- if train_on_gpu: # 在gpu上运行
- data, target = data.cuda(), target.cuda().float().unsqueeze(1)
-
- # 这里的target要注意,原本的数据格式和data是不同的,两个区别:1.差了一个维度2.data是float二target是int。相同点:数据是一样的
- # 两者的类型必须完全相同才能放进BCE中去算loss。这个float是把int变float。unsqueeze是把维度加1
- # 另外target在后面计算精确度的时候一定注意必须是和从data直接提取的数据的格式相同!!!
-
- optimizer.zero_grad() # 将梯度清零
- output = model(data) # 得到输出
- # 如果,模型中用的sigmoid,那输出必须都在0-1,否则就有问题
- loss = F.binary_cross_entropy(output,target) # 求loss,loss是个tensor是一个数,loss: tensor(2.2984,device='cuda:0',grad_fn=<NllLossBackward>)
- loss.backward() # 求方向传播得梯度
- optimizer.step() # 反向传播
- train_loss += loss.item() * data.size(0)
通过验证集,对模型性能进行评估,避免模型在训练集上过拟合。
- model.eval() # 下面的不做反向传播,即为验证部分
- for data, target in valid_loader:
- # 在gpu上计算
- if train_on_gpu:
- data, target = data.cuda(), target.cuda().float().unsqueeze(1)
- output = model(data)
- loss = F.binary_cross_entropy(output, target) # 计算loss
- valid_loss += loss.item() * data.size(0) # 计算验证损失
这里对精确度、recall、F1_score和时间进行了统计。
图4-1 各项数据统计
关键代码如下:
- test_loss = 0.0 # 设置好test_loss认定为一个精度值
- class_correct = list(0. for i in range(2)) # [0.0, 0.0] (猫正确,狗正确)
- class_total = list(0. for i in range(2)) # [0.0, 0.0] (猫总共,狗总共)
-
- cat_correct = class_correct[0]
- dog_correct = class_correct[1]
- cat_total = class_total[0]
- dog_total = class_total[1]
-
- r1 = class_correct[0] / (class_correct[0] + (class_total[1] - class_correct[1]))
- r2 = class_correct[1] / (class_correct[1] + (class_total[0] - class_correct[0]))
- f1_cat = 2 * r1 * (class_correct[0] / class_total[0]) / ((class_correct[0] / class_total[0]) + r1)
- f1_dog = 2 * r2 * (class_correct[1] / class_total[1]) / ((class_correct[1] / class_total[1]) + r2)
- print('recall%5s: %.2f%%' % (classes[0], 100 * r1))
- print('recall%5s: %.2f%%' % (classes[1], 100 * r2))
- print('F1_score%5s: %.2f' % (classes[0], f1_cat))
- print('F1_score%5s: %.2f' % (classes[1], f1_dog))
- print('F1_score: %.2f' % ((f1_dog + f1_cat) / 2))
- elapsed_time = (end_time - start_time)
- print(f'测试集用的时间为: {elapsed_time:.2f} seconds')
这里对每一轮训练后的模型都进行了保存,同时验证当前轮次是否比上一轮次损失小,如果成立则保存新的损失和新的权重
关键代码如下:
- # 保存模型权重
- if valid_loss <= valid_loss_min:
- # valid_loss_min = np.Inf,第一轮验证一下是否是无穷不是无穷则保存此valid_loss
- # 第二轮,验证一下此轮损失是否比上一轮小,小的话则保存新的这个损失并且保存新的权重
- print('Validation loss decreased ({:.6f} --> {:.6f}). Saving model ...'.format(
- valid_loss_min,
- valid_loss))
- torch.save(model.state_dict(), 'Res18.pth')
- # torch.save(model.state_dict(), 'Res18_Se.pth')
- valid_loss_min = valid_loss
GPU单张图片推理预测
- # 6.1GPU单张推理推理测试
- state_dict = torch.load('Res18_Se.pth') # 提取训练权重
- # state_dict = torch.load('Res18.pth') # 提取训练权重
- model.load_state_dict(state_dict)
-
- sample_img, true_label = next(iter(test_loader)) # 选择一张测试图片
- if train_on_gpu:
- sample_img = sample_img.cuda()
- true_label = true_label.cuda()
-
- output = model(sample_img)
- pred = torch.tensor([[1] if num[0] >= 0.5 else [0] for num in output]).cuda() # 对batch_size大小数据进行预测
-
- sample_img = sample_img.cpu().numpy()[0]
- plt.imshow(np.transpose(sample_img, (1, 2, 0))) # 转置图片的维度顺序
- plt.title(f'TRUE LABEL IS: {classes[true_label[0].cpu().numpy()]},'
- f' PREDICT LABEL IS: {classes[pred[0].cpu().numpy()[0]]}')
- plt.axis('off')
- plt.show()
预测结果如图所示
本次预测值与真实标签不一致
将在GPU训练的模型保存至Resnet-18.pth中,在CPU上加载
- model.eval() # model不反向传播
- model.load_state_dict(torch.load('Res18.pth', map_location=torch.device('cpu'))) # 提取训练权重
- # model.load_state_dict(torch.load('Res18_Se.pth', map_location=torch.device('cpu'))) # 提取训练权重
- device = torch.device('cpu') # 网络传入CPU
- print(device)
-
- start_time = time.time() # 测试时间
-
- for data, target in test_loader: # 按照test_loader设置好的batch_size进行采样
- output = model(data)
- target1 = target.float().unsqueeze(1) # 为了与output格式对应
- loss = F.binary_cross_entropy(output, target1)
- test_loss += loss.item() * data.size(0)
-
- pred = torch.tensor([[1] if num[0] >= 0.5 else [0] for num in output])
- # 通过此行代码求得索引值,这里预测得到的小于0.5的shiwei0,大于0.5的视为1。以此分类
-
- # 获得预测结果的正确与否
- correct_tensor = pred.eq(target.data.view_as(pred))
- # x.eq(y)判断x和y的值是否相等,作比较以后输出新的tensor 这里的view_as和view一样都是调整格式只不过view是将target调整成和pred一样的格式,这里其实没变他俩格式都是Tensor(20,)
- correct = np.squeeze(correct_tensor.numpy()) # 改成np格式[ True True True True True]
- # 计算准确值
- for i in range(batch_size): # 这里再按照batch_size的值遍历
- label = target.data[i] # 第一轮就是第一个图片的标签是3 tensor(3, device='cuda:0')
- # label=tensor([0.], device='cuda:0')
- class_correct[label] += correct[
- i].item() # 【40,46】#注意这里头的label应该是:1.1个整数2.是张量的话只能是包含一个数的张量。所以这里用label做索引,labela选的target的数据就不能去增加维数,也不能变为精度型
- class_total[label] += 1 # 【50,50】
-
- end_time = time.time() # 记录结束时间
-
- test_loss = test_loss / len(test_loader.dataset) # 计算平均损失
- print('Test Loss: {:.6f}\n'.format(test_loss))
图5-1 CPU各项数据
这里可以对比发现,CPU上推理所需时间与GPU相差甚多,CPU上消耗的时间是GPU的5倍左右。
SE是Squeeze-and-Excitation(SE)的缩写,该模块的提出主要是考虑到模型通道之间的相互依赖性。SE网络的使用结构如下图所示:
图6-1 Senet结构
Senet网络的创新点在于关注channel之间的关系,希望模型可以自动学习到不同channel特征的重要程度,在Se-Resnet模型中,只需要将Se模块加入到残差单元(应用在残差学习部分)即可,下图是在残差单元应用的结构图
图6-2 Senet在残差网络的应用
关键代码如下:
- # se模块
- class SELayer(nn.Module):
- def __init__(self, channel, reduction=16):
- super(SELayer, self).__init__()
- self.avg_pool = nn.AdaptiveAvgPool2d(1)
- self.fc = nn.Sequential(
- nn.Linear(channel, channel // reduction, bias=False),
- nn.ReLU(inplace=True),
- nn.Linear(channel // reduction, channel, bias=False),
- nn.Sigmoid()
- )
-
- def forward(self, x):
- b, c, _, _ = x.size()
- y = self.avg_pool(x).view(b, c)
- y = self.fc(y).view(b, c, 1, 1)
- return x * y.expand_as(x)
和上文一样,通过GPU进行训练
图6-3 Senet在GPU上的数据
图6-4 Senet在CPU上的数据
Senet模组的加入,模型的精准度、F1_Score和时间仅有小部分提升,在基数为5000的test中,F1_Score分数上升1%,所花时间减少2-3s左右。在数据量小的时候有较明显的提升。
在上一章中,我发现使用CPU直接进行训练的话会相当慢,在这里使用Intel Extension for PyTorch大大提高了速度。上文中CPU会消耗大约150s,而通过Intel Extension for PyTorch进行优化后,仅需60s,提升大概缩短了两倍的时间,f1值并没有变化
图7.1 Resnet-18在CPU训练
图7.2 Resnet-18-Se 在CPU训练
为了避免数据的偶然性,我们重新采用了新的test进行测试,其中dog类有500张,cat类有500.test集共1000张,以下是Resnet18和Resnet18-Se分别在CPU上的各项测试值(以下test集均为1000的新test测试)。
图7.3 Resnet-18在CPU训练
图7.4 Resnet-18-Se在CPU训练
- # model.load_state_dict(torch.load('Res18.pth', map_location=torch.device('cpu'))) # 提取训练权重
- model.load_state_dict(torch.load('Res18_Se.pth', map_location=torch.device('cpu'))) # 提取训练权重
- device = torch.device('cpu') # 网络传入CPU
- print(device)
-
- optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # 实例化一个优化器,即调整网络参数,优化方式为adam方法
- model,optimizer = ipex.optimize(model=model,optimizer=optimizer,dtype=torch.float32)
-
- model.eval() # model不反向传播
- torch.save(model.state_dict(), 'new_Res18_Se.pth')
- torch.save(model.state_dict(), 'new_Res18.pth')
这里对优化后的模型new_Res18.pth和new_Res18_Se.pth进行加载
- # 加载模型
- # model.load_state_dict(torch.load('new_Res18_Se.pth',map_location=torch.device('cpu')))
- model.load_state_dict(torch.load('new_Res18.pth',map_location=torch.device('cpu')))
- device = torch.device('cpu') # 网络传入CPU
- model.eval()
加载完成以后以准确度为评估函数进行量化
- # 定义评估函数
- def eval_func(model):
- with torch.no_grad():
- y_true = []
- y_pred = []
-
- for inputs, labels in train_loader:
- inputs = inputs.to('cpu')
- labels = labels.to('cpu')
- preds_probs = model(inputs)
- preds_class = torch.argmax(preds_probs, dim=-1)
- y_true.extend(labels.numpy())
- y_pred.extend(preds_class.numpy())
-
- return accuracy_score(y_true, y_pred)
-
- # 配置量化参数
- conf = PostTrainingQuantConfig(backend='ipex', # 使用 Intel PyTorch Extension
- accuracy_criterion=AccuracyCriterion(higher_is_better=True,
- criterion='relative',
- tolerable_loss=0.01))
-
-
- conf = PostTrainingQuantConfig(backend='default', # or 'qnnpack'
- accuracy_criterion=AccuracyCriterion(higher_is_better=True,
- criterion='relative',
- tolerable_loss=0.01))
-
-
-
- # 执行量化
- q_model = quantization.fit(model,
- conf,
- calib_dataloader=valid_loader, # 验证集
- eval_func=eval_func)
- # 保存量化模型
- quantized_model_path = './quantized_Res18'
- if not os.path.exists(quantized_model_path):
- os.makedirs(quantized_model_path)
-
- q_model.save(quantized_model_path)
量化成功以后会出现如下代码
加载模型
- # quantized_model_path = './quantized_Res18_Se'
- quantized_model_path = './quantized_Res18'
- new_model_path = f'{quantized_model_path}/best_model.pt'
-
- model = torch.jit.load(new_model_path, map_location='cpu')
- model.eval() # model不反向传播
-
- model.load_state_dict(torch.load(new_model_path, map_location=torch.device('cpu')))
- optimizer = torch.optim.Adam(model.parameters(), lr=0.01) # 实例化一个优化器,即调整网络参数,优化方式为adam方法
-
- device = torch.device('cpu') # 网络传入CPU
- print(device)
模型推理并查看test上的F1_Score和时间
图7.5 Resnet-18
图7.6 Resnet-18-Se
以下是对新test集不同版本的F1_Score和时间:
本地GPU版本Resnet-18(左)和Resnet-18-Se(右):
本地CPU版本Resnet-18(左)和Resnet-18-Se(右):
Intel Extension for PyTorch 版Resnet-18(左)和Resnet-18-Se(右):
在此版本中,CPU推理已经有了十分明显的提升,本地CPU需要30余秒,而Intel Extension for PyTorch优化后仅需10秒,是原来的1/3。
量化优化版本Resnet-18(左)和Resnet-18-Se(右):
在量化优化版本中,CPU推理又有了明显的提升,从最开始的30秒到最后的6秒,是原来的1/5,由此可见,OneApi优化组件,即可以大幅度下降推理时间,还能保证模型精确度。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。