目录
6.1Transfer Learning with oneAPI AI Analytics Toolkit进行迁移学习
6.2使用Intel Extension for PyTorch进行优化
6.3保存使用Intel Extension for PyTorch进行优化的模型
6.4使用 Intel® Neural Compressor 量化模型
1.项目简介
1.1问题描述
在这个问题中,将面临一个经典的机器学习分类挑战——猫狗大战。任务是建立一个分类模型,能够准确地区分图像中是猫还是狗。
1.2预期解决方案
通过训练一个机器学习模型,使其在给定一张图像时能够准确地预测图像中是猫还是狗。模型应该能够推广到未见过的图像,并在测试数据上表现良好。将其部署到模拟的生产环境中——这里推理时间和二分类准确度(F1分数)将作为评分的主要依据。
1.3数据集
链接:https://pan.baidu.com/s/1KNdSIwQHiDrJLT-5K-sPmA
提取码:fly8
1.4图像展示


2.数据预处理
2.1数据集结构
本项目数据集共由两部分组成,分别为test,train文件夹。
train文件夹包含125000个猫狗图像文件,test文件夹中包含个猫狗图像文件,猫狗图像文件名统一都为“标签.编号.jpg”格式。
2.2提取数据集
为了方便后期在模型训练和评估中较好地使用数据集,定义了一个自定义数据集类 SelfDataset类
封装数据集的加载和预处理过程,将数据集中提供训练和测试的图片文件按照一定标准进行加载。
1.初始化方法 (__init__
):
def __init__(self, root_dir, transform=None):
self.root_dir = root_dir
self.transform = transform
self.data = self.load_data()
root_dir
: 数据集所在的根目录路径。transform
: 可选参数,用于定义对图像的预处理操作。data
: 存储数据集的列表,通过调用load_data
方法加载。
2. 数据加载方法 (load_data
):
def load_data(self):
data = []
for file_name in os.listdir(self.root_dir):
file_path = os.path.join(self.root_dir, file_name)
# 判断文件是否是jpg格式的图像文件
if os.path.isfile(file_path) and file_name.lower().endswith('.jpg'):
# 提取文件名中的类别信息
if "cat" in file_name:
label = 0
else:
label = 1
data.append((file_path, label))
return data
- 遍历指定目录下的所有文件,判断文件是否是以
.jpg
为后缀的图像文件。 - 根据文件名是否包含 "cat" 来判断图像的类别,标签为 0 表示猫,标签为 1 表示狗。
- 将图像文件路径和对应的标签存储为元组,并添加到
data
列表中。
3. 数据集长度方法 (__len__
)和获取单个样本方法 (__getitem__
):
def __len__(self):
return len(self.data)
def __getitem__(self, idx):
img_path, label = self.data[idx]
img = Image.open(img_path).convert('RGB')
if self.transform:
img = self.transform(img)
return img, label
2.4数据增强
通过数据增强,在训练过程中通过对原始数据进行随机变换来生成更多训练样本的技术,有助于模型更好地泛化到不同的图像变换,提高模型对于训练数据的适应能力。
transforms.RandomResizedCrop(size)
: 这个函数首先会从原始图像中随机裁剪出一块大小为size*size的区域。这有助于模型对不同裁剪和尺寸的图像进行训练,增强了模型的泛化能力。transforms.RandomHorizontalFlip()
: 这个函数以一定的概率随机水平翻转图像。这可以模拟实际场景中物体的不同方向,并且可以提高模型在处理镜像图像时的性能。transforms.ToTensor()
: 将图像数据转换为PyTorch张量。PyTorch中的神经网络模型通常接受张量作为输入,这一步将图像数据从NumPy数组等格式转换为PyTorch张量。
# 数据增强
transform = transforms.Compose([
transforms.RandomResizedCrop(64),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
])
2.5构建数据集
# 创建数据集实例
train_dataset = SelfDataset(root_dir=train_dataset_path, transform=transform)
test_dataset = SelfDataset(root_dir=test_dataset_path, transform=transform)
# 创建 DataLoader
batch_size = 64
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
3.使用卷积神经网络识别猫狗图片
3.1VGG-16架构
VGG-16(Visual Geometry Group-16)是一种深度卷积神经网络架构,由牛津大学的Visual Geometry Group开发。该架构在2014年的ImageNet图像分类比赛中取得了很好的成绩,因此受到广泛关注。
以下是VGG-16的主要特点:
-
深度结构: VGG-16有16个卷积层和3个全连接层,形成了深度的神经网络。这种深度结构有助于网络学习更复杂的特征。
-
卷积层的设计: VGG-16中的卷积层都使用了3x3的小型卷积核,这有助于增加网络的深度,同时减少了参数的数量。多个小型卷积核可以学习更复杂的特征,而且参数共享的效果更好。
-
池化层: 在卷积层之间使用了池化层,通常采用2x2的最大池化,有助于减小特征图的尺寸并保留重要的信息。
-
全连接层: 在网络的顶部有3个全连接层,最后一个全连接层输出分类的概率。
-
激活函数: 激活函数使用的是ReLU(Rectified Linear Unit),它有助于网络学习非线性特征。
VGG-16的成功证明了深度神经网络在计算机视觉任务中的有效性,尤其是在图像分类方面。然而,由于其深度和参数量较大,训练和部署可能需要更多的计算资源。
3.2更改VGG-16网络结构
将 VGG16 模型的最后一个全连接层替换为一个新的层序列,以适应二分类任务。新的层包括一个线性层(nn.Linear
)将输入特征维度从原来的值调整为512,接着是ReLU激活函数、Dropout层和最终的输出线性层,输出维度为2,适应二分类问题。
vgg16_model = models.vgg16(pretrained=True)
# 如果需要微调,可以解冻最后几层
for param in vgg16_model.features.parameters():
param.requires_grad = False
# 修改分类层
num_features = vgg16_model.classifier[6].in_features
vgg16_model.classifier[6] = nn.Sequential(
nn.Linear(num_features, 512),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(512, 2)
)
4.在GPU上训练
4.1参数设置
1.损失函数
nn.CrossEntropyLoss()
:
- 用于分类问题的损失函数。
- 不需要手动对模型的输出进行 softmax 操作,因为这个损失函数内部会处理。
- 该函数没有特定的参数,但是你可以根据需要修改权重类别等。
2.优化器
optim.Adam(vgg16_model.parameters(), lr=0.001, weight_decay=1e-4)
:使用 Adam 优化器,一种基于梯度的优化算法,适用于很多深度学习任务。
vgg16_model.parameters()
提供了需要被优化的模型参数。lr=0.001
是学习率,决定每次参数更新的步长。weight_decay=1e-4
是权重衰减,用于防止过拟合,通过对权重的 L2 范数进行惩罚。
3.学习率调度器
optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.1, patience=3, verbose=True)
:使用 ReduceLROnPlateau 学习率调度器,用于在训练停滞时降低学习率。
optimizer
是被调度的优化器。mode='max'
表示监测的指标是训练准确度,目标是最大化。factor=0.1
表示学习率降低的倍数。patience=3
表示如果在3个 epoch 内监测指标没有改善,则降低学习率。verbose=True
表示在更新学习率时输出相关信息。
4.设备配置
torch.device('cuda' if torch.cuda.is_available() else 'cpu')
:检查是否有可用的 GPU,如果有,则选择 'cuda',否则选择 'cpu'。
vgg16_model.to(device)
:将模型移动到选定的设备上,以便在该设备上进行训练。
# 损失函数
criterion = nn.CrossEntropyLoss()
#优化器
optimizer = optim.Adam(vgg16_model.parameters(), lr=0.001, weight_decay=1e-4)
# 添加学习率调度器 ReduceLROnPlateau
scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.1, patience=3, verbose=True)
# 设备配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
vgg16_model.to(device)
4.2在GPU上训练
在这个场景中,使用了2个不同的数据集,分别是训练集(train)和测试集(test)。
训练集(train):
用途: 用于训练机器学习模型。模型通过学习训练集中的样本来调整参数,使其能够捕捉输入数据的模式和特征。
特点: 训练集通常是最大的数据集,包含大量用于模型训练的样本。高质量且多样化的训练集有助于提高模型的泛化能力,使其在未见过的数据上表现良好。
测试集(test):
用途: 用于评估训练好的模型的性能。测试集中的样本是模型在训练和验证过程中未曾见过的数据,因此测试集上的性能评估更接近模型在真实场景中的表现。
特点: 测试集应该是完全独立于训练集和验证集的,确保模型在测试集上的表现不受过拟合或过度调整的影响。测试集上的性能评估是对模型泛化能力的最终验证。
# 训练循环
num_epochs = 0
consecutive_f1_count = 0
while num_epochs < 50:
print(f'第{num_epochs+1}次训练开始了')
vgg16_model.train() # 设置模型为训练模式
train_loss = 0.0
for inputs, labels in train_loader:
inputs, labels = inputs.to(device), labels.to(device)
# 将数据传递给模型
outputs = vgg16_model(inputs)
# 计算损失
loss = criterion(outputs, labels)
# 反向传播和优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss += loss.item()
# 在每个 epoch 结束时进行验证
val_loss = 0.0
with torch.no_grad():
for inputs, labels in val_loader:
inputs, labels = inputs.to(device), labels.to(device)
# 在验证集上进行推理,可根据需要添加评估代码
val_outputs = vgg16_model(inputs)
val_loss += criterion(val_outputs, labels).item()
# 计算平均训练损失
avg_train_loss = train_loss / len(train_loader)
# 计算平均验证损失
avg_val_loss = val_loss / len(val_loader)
# 打印训练过程中的损失和验证损失
print(f'Epoch [{num_epochs+1}], 第{num_epochs+1}轮:训练集损失: {avg_train_loss:.4f}, 验证集损失: {avg_val_loss:.4f}')
# 在模型训练完后,使用测试集进行最终评估
vgg16_model.eval()
all_predictions = []
all_labels = []
start_time = time.time() # 记录开始时间
with torch.no_grad():
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
# 在测试集上进行推理
outputs = vgg16_model(inputs)
# 将预测结果和真实标签保存
_, predicted = torch.max(outputs, 1)
all_predictions.extend(predicted.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
end_time = time.time() # 记录结束时间
elapsed_time = end_time - start_time
print(f'测试集用的时间为: {elapsed_time:.2f} seconds')
# 计算F1分数
f1 = f1_score(all_labels, all_predictions, average='binary') # 适用于二分类问题
# 打印每轮的测试F1分数
print(f'第{num_epochs+1}轮的测试F1分数: {f1:.4f}')
# 调整学习率
scheduler.step(f1)
# 增加训练次数
num_epochs += 1
4.3查看test数据集F1分数及时间
4.4使用模型进行推理测试并保存为VGG16模型
import matplotlib.pyplot as plt
import numpy as np
# 选择一张 test_loader 中的图片
sample_image = next(iter(test_loader))
# 将图片传递给模型进行预测
sample_image = sample_image.to(device)
with torch.no_grad():
model_output = vgg16_model(sample_image)
# 获取预测结果
_, predicted_label = torch.max(model_output, 1)
# 转换为 NumPy 数组
sample_image = sample_image.cpu().numpy()[0] # 将数据从 GPU 移回 CPU 并取出第一张图片
predicted_label = predicted_label[0].item()
# 获取类别标签
class_labels = ['cat', 'dog']
# 显示图像
plt.imshow(np.transpose(sample_image, (1, 2, 0))) # 转置图片的维度顺序
plt.title(f'PREDICT LABEL IS: {class_labels[predicted_label]}')
plt.axis('off')
plt.show()
图片及标签预测结果如下:
保存模型
# 保存模型
torch.save(vgg16_model.state_dict(), 'vgg16_model.pth')
# 打印保存成功的消息
print("模型已保存为 vgg16_model.pth")
5.转移到CPU上
5.1创建VGG16模型
class CustomVGG16(nn.Module):
def __init__(self):
super(CustomVGG16, self).__init__()
self.vgg16_model = models.vgg16(pretrained=True)
for param in self.vgg16_model.features.parameters():
param.requires_grad = False
num_features = self.vgg16_model.classifier[6].in_features
self.vgg16_model.classifier[6] = nn.Sequential(
nn.Linear(num_features, 512),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(512, 2)
)
def forward(self, x):
return self.vgg16_model(x)
# 创建 CustomVGG16 模型实例
vgg16_model = CustomVGG16()
# 加载权重
vgg16_model.vgg16_model.load_state_dict(torch.load('vgg16_model.pth', map_location=torch.device('cpu')))
5.2尝试直接在CPU上进行训练
vgg16_model.eval()
# Assuming you have a DataLoader for the test dataset (test_loader)
all_predictions = []
all_labels = []
start_time = time.time()
with torch.no_grad():
for inputs, labels in test_loader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = vgg16_model(inputs)
_, predicted = torch.max(outputs, 1)
all_predictions.extend(predicted.cpu().numpy())
all_labels.extend(labels.cpu().numpy())
end_time = time.time() # 记录结束时间
elapsed_time = end_time - start_time
print(f'测试集用的时间为: {elapsed_time:.2f} seconds')
f1 = f1_score(all_labels, all_predictions, average='binary') # 适用于二分类问题
print(f'F1分数为: {f1:.4f}')
F1分数及推理时间
6.使用oneAPI组件
6.1Transfer Learning with oneAPI AI Analytics Toolkit进行迁移学习
class CustomVGG16(nn.Module):
def __init__(self):
super(CustomVGG16, self).__init__()
self.vgg16_model = models.vgg16(pretrained=True)
for param in self.vgg16_model.features.parameters():
param.requires_grad = False
num_features = self.vgg16_model.classifier[6].in_features
self.vgg16_model.classifier[6] = nn.Sequential(
nn.Linear(num_features, 512),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(512, 2)
)
def forward(self, x):
return self.vgg16_model(x)
# 创建 CustomVGG16 模型实例
vgg16_model = CustomVGG16()
# 加载权重
vgg16_model.vgg16_model.load_state_dict(torch.load('vgg16.pth', map_location=torch.device('cpu')))
6.2使用Intel Extension for PyTorch进行优化
在个步骤中,我发现使用CPU直接进行训练的话会相当慢,在这里使用Intel Extension for PyTorch大大提高了速度。大概缩短了一倍的时间,并且F1的值并没有改变。
# 将模型移动到CPU
device = torch.device('cpu')
vgg16_model.to(device)
# 重新构建优化器
optimizer = optim.Adam(vgg16_model.parameters(), lr=0.001, weight_decay=1e-4)
# 使用Intel Extension for PyTorch进行优化
vgg16_model, optimizer = ipex.optimize(model=vgg16_model, optimizer=optimizer, dtype=torch.float32)
F1分数及推理时间
6.3保存使用Intel Extension for PyTorch进行优化的模型
# 保存模型参数
torch.save(vgg16_model.state_dict(), 'vgg16_optimized.pth')
# 加载模型参数
loaded_model = CustomVGG16()
loaded_model.load_state_dict(torch.load('vgg16_optimized.pth'))
6.4使用 Intel® Neural Compressor 量化模型
对优化后的模型vgg16_optimized.pth进行加载
import os
import torch
# 检查文件是否存在
assert os.path.exists("./vgg16_optimized.pth"), "文件不存在"
# 尝试加载模型
model = torch.load("./vgg16_optimized.pth")
print("模型加载成功")
加载完成以后以准确度为评估函数进行量化
from neural_compressor.config import PostTrainingQuantConfig, AccuracyCriterion
from neural_compressor import quantization
import os
# 加载模型
model = CustomVGG16()
model.load_state_dict(torch.load('vgg16_optimized.pth'))
model.to('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))
# 执行量化
q_model = quantization.fit(model,
conf,
calib_dataloader=train_loader,
eval_func=eval_func)
# 保存量化模型
quantized_model_path = './quantized_models'
if not os.path.exists(quantized_model_path):
os.makedirs(quantized_model_path)
q_model.save(quantized_model_path)
量化成功以后会出现如下代码
查看量化后的模型,分别为pt文件和json文件
6.5使用量化后的模型在 CPU上进行推理
加载模型
import torch
import json
from neural_compressor import quantization
# 指定量化模型的路径
quantized_model_path = './quantized_models'
# 加载 Qt 模型和 JSON 配置
vgg16_model_path = f'{quantized_model_path}/best_model.pt'
json_config_path = f'{quantized_model_path}/best_configure.json'
# 加载 Qt 模型
vgg16_model = torch.jit.load(vgg16_model_path, map_location='cpu')
# 加载 JSON 配置
with open(json_config_path, 'r') as json_file:
json_config = json.load(json_file)
# 打印 JSON 配置(可选)
print(json_config)
进行推理
import torch
from sklearn.metrics import f1_score
import time
# 假设 test_loader 是你的测试数据加载器
# 请确保它返回 (inputs, labels) 的形式
# 将模型设置为评估模式
vgg16_model.eval()
# 初始化变量用于存储真实标签和预测标签
y_true = []
y_pred = []
# 开始推理
start_time = time.time()
# 设置 batch_size
batch_size = 64
# 使用 DataLoader 时设置 batch_size
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
# 在推理时处理每个批次
with torch.no_grad():
for inputs, labels in test_loader:
# 将输入数据移动到 CPU(如果尚未在 CPU 上)
inputs = inputs.to('cpu')
labels = labels.to('cpu')
# 获取模型预测
preds_probs = vgg16_model(inputs)
preds_class = torch.argmax(preds_probs, dim=-1)
# 扩展真实标签和预测标签列表
y_true.extend(labels.numpy())
y_pred.extend(preds_class.numpy())
# 计算 F1 分数
f1 = f1_score(y_true, y_pred, average='weighted')
# 计算推理时间
inference_time = time.time() - start_time
# 打印结果
print(f"测试集用的时间为: {inference_time} seconds")
print(f"F1分数: {f1}")
推理结果
F1分数及推理时间
7.总结
在项目的优化过程中,oneAPI的优化组件发挥了关键作用,显著降低了推理时间。这意味着通过使用oneAPI提供的工具和优化策略,成功地提升了整个系统的性能,使得图像分类的推理过程更加高效。随后引入量化工具,进一步将推理时间降至6秒,同时保持F1分数稳定在0.85左右,彰显了oneAPI在模型压缩和性能优化方面的强大能力。
这样的优化不仅在实现更迅速的推理过程中取得了显著成功,而且在经济层面也带来了重要的影响。通过降低推理时间,项目在云服务费用和资源利用效益上都实现了成本的显著降低。这对于项目的经济决策具有积极的意义,为投资和资源分配提供了更具吸引力的方案。
综合来看,通过oneAPI的优化组件和量化工具的巧妙应用,项目成功实现了在性能和经济效益方面的双重突破,为图像分类应用提供了更为可持续和经济高效的解决方案。