【Intel校企合作课程】基于VGG-16的猫狗识别

1.作业简介:猫狗大战

注:本文章按照学习要求,学习如何使用oneAPI,cuda进行深度学习,学习参照博客:【Intel校企合作课程】基于VGG-16的医学成像诊断检测_基于vgg16医学成像诊断检测-CSDN博客

1.1问题描述

     在问题中,你将面一个典的机器学分类挑——猫狗大。你的任是建立一个分类模型,能够准确地区分像中是猫是狗

1.2预期解决方案

     你的目标是通过训练一个机器学习模型,使其在给定一张图像时能够准确地预测图像中是猫还是狗。模型应该能够推广到未见过的图像,并在测试数据上表现良好。期待您将其部署到模的生产环境中——里推理时间和二分类准确度(F1分数)将作为评分的主要依据

1.3数据集

链接:百度网盘 请输入提取码 

提取码:jc34

测试集见本文章绑定数据集

1.4图像展示

2.数据预处理

2.1数据划分

     将train文件夹划分为训练集(train)和验证集(val),并且依据标签,在train和val文件下将图片分类,分为cat和dog,并且存入对应文件夹。代码如下:

import os
import random
import shutil
import numpy as np  # 导入NumPy库
from numpy.random import seed
#此代码划分训练集和数据集
def spiltTrainTest(image_folder,tarin_ratio=0.8,seed=42):
    image_file=[file for file in os.listdir(image_folder) if file.lower().endswith('.jpg')]#访问jpg
    random.seed(seed)
    random.shuffle(image_file)#打乱列表
    split=int(len(image_file)*tarin_ratio)#计算切分点
    train_set=image_file[:split]
    test_set=image_file[split:]
    return train_set,test_set

def moveImage_tofolders(file_list,source_folder,target_folder):
    for file_name in file_list:
        source_path=os.path.join(source_folder,file_name)#image/cat.ssss原来的路径
        target_path=os.path.join(target_folder,file_name)#train/cat.sss
        shutil.move(source_path,target_path)

if __name__=='__main__':
    image_folder="image"#图片文件夹,此处image为原train文件名修改image
    train_set, test_set=spiltTrainTest(image_folder)

    train_folder="train"
    test_folder="test"
    os.makedirs(train_folder, exist_ok=True)
    os.makedirs(test_folder, exist_ok=True)
    moveImage_tofolders(train_set,image_folder,train_folder)
    moveImage_tofolders(test_set,image_folder,test_folder)

将train文件下内容分类(test,val文件同样参考下面代码进行分类):

import os  
import shutil  
  
def move_images_to_folders(source_folder):  
    # 遍历train文件夹中的所有文件  
    for filename in os.listdir(source_folder):  
        file_path = os.path.join(source_folder, filename)  
        # 如果文件是目录,则跳过  
        if os.path.isdir(file_path):  
            continue  
        # 如果文件名前三个字母为cat,则将其移动到cat文件夹中  
        elif filename[:3] == 'cat':  
            shutil.move(file_path, os.path.join(source_folder, 'cat'))  
        # 如果文件名前三个字母为dog,则将其移动到dog文件夹中  
        elif filename[:3] == 'dog':  
            shutil.move(file_path, os.path.join(source_folder, 'dog'))  
  
# 调用函数,传入train文件夹路径  
move_images_to_folders('./train')

查看数据集内容:

train_dir = './train' # 图片路径
 
cat_imgs = [fn for fn in os.listdir(f'{train_dir}/cat') if fn.endswith('.jpg')]
dog_imgs = [fn for fn in os.listdir(f'{train_dir}/dog') if fn.endswith('.jpg')]
 
print(f'猫的数量为: {len(cat_imgs)}')
print(f'狗的数量为: {len(dog_imgs)}')

但是上述划分会可能会导致猫狗划分不均衡。

2.2数据集结构

     划分后,共三个数据集,训练集(train),测试集(test),验证集(val)。

每个文件下分为cat,dog两个子文件。

2.3探索性数据分析

      分别取了train数据集下的随机不重样10只狗,10只猫的图像进行展示:

source_path = r"./train"
#分别从Dog,Cat文件夹中选取10张图片显示
train_Dog_dir = os.path.join(source_path, "dog")
train_Cat_dir = os.path.join(source_path, "cat")
Dog_image_list = os.listdir(train_Dog_dir)
 
Cat_image_list = os.listdir(train_Cat_dir)
show_image = [os.path.join(train_Dog_dir,Dog_image_list[i]) for i in range(10)]
show_image.extend([os.path.join(train_Cat_dir,Cat_image_list[i]) for i in range(10)])
for i in show_image:
    print(i)
plt.figure()
 
for i in range(1,20):
    plt.subplot(4,5,i)
    img = Image.open(show_image[i-1])
    plt.imshow(img)
 
plt.show()

2.4提取数据集

      在本项目中,为了更好地提取出图像,我构建了一个函数,能够将每个主文件夹下的图片提取出来,并且打好了标签。

# 创建自定义数据集
class SelfDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = root_dir
        self.transform = transform
        self.classes = ['cat', 'dog']
        self.data = self.load_data()
 
    def load_data(self):
        data = []
        for class_idx, class_name in enumerate(self.classes):
            class_path = os.path.join(self.root_dir, class_name)
            for file_name in os.listdir(class_path):
                file_path = os.path.join(class_path, file_name)
                if os.path.isfile(file_path) and file_name.lower().endswith('.jpg'):
                    data.append((file_path, class_idx))
        return data
 
    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.5数据增强

# 数据增强
transform = transforms.Compose([
    transforms.RandomResizedCrop(256),
    transforms.RandomHorizontalFlip(),
    transforms.Resize((256, 256)),  # 添加此行以确保图像大小相同
    transforms.ToTensor(),
])

     在预处理阶段,我运用了 transforms.RandomResizedCrop(256) 方法。这一步骤涉及对图像进行随机的裁剪,并随机调整裁剪后图像的大小至 256x256 像素。这种处理方式有助于模型更好地适应不同大小和位置的物体,从而增强模型的泛化能力。这种方法帮助模型更好地理解和识别出现在不同情境中的物体,提高了其在实际应用中的表现。

2.6构建数据集

# 创建数据集实例
train_dataset = SelfDataset(root_dir='./train', transform=transform)
test_dataset = SelfDataset(root_dir='./test', transform=transform)
val_dataset = SelfDataset(root_dir='./val', transform=transform)
 
# 创建 DataLoader
batch_size = 32
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
vgg16_model = models.vgg16(pretrained=True)

     根据本地GPU容量选择合适的batch_size,当batch_size过大时,可能会导致CUDA内存错误,表明你的GPU内存已满,无法为新分配的内存空间提供更多空间。这通常发生在深度学习或大规模数据处理任务中,因为这些任务需要大量的GPU内存。可以

    减小批量大小(batch size):这是一个常见的解决方案,可以减少每次前向和后向传递所需的数据量。

3.使用卷积神经网络识别猫狗图像

3.1VGG-16架构

        VGG16是由Karen Simonyan和Andrew Zisserman于2014年在论文“VERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE SCALE IMAGE RECOGNITION”中提出的一种处理多分类、大范围图像识别问题的卷积神经网络架构,成功对ImageNet数据集的14万张图片进行了1000个类别的归类并有92.7%的准确率。

        本项目将分类层的最后一层修改为(1x1x2)即可将分类结果从1000类 修改为二分类。

3.2更改VGG-16网络结构

       传统的VGG-16网络的输出是1000的大小,为了适合本项目,将网络改成了2分类问题并进行以下优化:

# 解冻最后几层
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())。交叉熵损失对于分类任务是一种常见的损失函数,它在训练期间衡量模型的预测和真实标签之间的差异。

        (2):Adam 优化器 (optim.Adam)。是一种基于梯度的优化算法,通常在深度学习中表现较好。

        (3): ReduceLROnPlateau 学习率调度器(optim.lr_scheduler.ReduceLROnPlateau)。该调度器在验证集上监测模型性能,并在性能停滞时降低学习率。

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(vgg16_model.parameters(), lr=0.0005, 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上训练

在这里,我使用了三个数据集,分别为train,val,test。

训练集(train):

  • 用途: 用于训练机器学习模型。模型通过学习训练集中的样本来调整参数,使其能够捕捉输入数据的模式和特征。
  • 特点: 训练集通常是最大的数据集,包含用于模型训练的大量样本。高质量、多样性的训练集有助于提高模型的泛化能力,使其在未见过的数据上表现良好。

验证集(val):

  • 用途: 用于调整模型超参数、选择模型架构和进行早停等操作。验证集上的性能评估有助于避免模型在训练集上过拟合,提高对未知数据的泛化能力。
  • 特点: 验证集通常是从独立于训练集的数据中划分出来的,模型在训练过程中不使用验证集的信息。在训练过程中,通过监控验证集上的性能来调整模型的参数和架构。

测试集(test):

  • 用途: 用于评估训练好的模型的性能。测试集中的样本是模型在训练和验证过程中未曾见过的数据,因此测试集上的性能评估更接近模型在真实场景中的表现。
  • 特点: 测试集应该是完全独立于训练集和验证集的,确保模型在测试集上的表现不受过拟合或过度调整的影响。测试集上的性能评估是对模型泛化能力的最终验证。
    # 训练循环
    num_epochs = 0
    consecutive_f1_count = 0
     
    # 设置迭代次数上限为50
    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保存模型  

# 保存模型
torch.save(vgg16_model.state_dict(), 'vgg16_model.pth')
 
# 打印保存成功的消息
print("模型已保存为 vgg16_model.pth")

4.4查看test数据集F1分数及时间

在GPU上查看F1分数和时间时,大概F1分数达到0.93以上,时间为10s左右。

4.5使用模型进行推理测试 

import matplotlib.pyplot as plt
import numpy as np
 
# 选择一张 test_loader 中的图片
sample_image, true_label = 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()
 
true_label = true_label[0].item()  # 直接获取标量值
 
# 获取类别标签
class_labels = ['cat', 'dog']
 
# 显示图像
plt.imshow(np.transpose(sample_image, (1, 2, 0)))  # 转置图片的维度顺序
plt.title(f'TRUE LABEL IS: {class_labels[true_label]}, PREDICT LABEL IS: {class_labels[predicted_label]}')
plt.axis('off')
plt.show()

5.在CPU上验证 

 5.1 加载模型到cpu环境

          这里将GPU训练的模型保存到了vgg16_model.pth中,在CPU上进行加载。

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}')

测试结果:

在CPU上直接进行推理的话,时间会非常慢。大概为24s左右

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_model.pth', map_location=torch.device('cpu')))
 
 

 6.2使用Intel Extension for PyTorch进行优化

 
# 将模型移动到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)

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 = 32
 
# 使用 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.总结

在猫狗大战图像分类任务中,进行了以下步骤:

  1. 数据预处理:对猫和狗的图像进行划分,分为训练集、验证集和测试集。
  2. 自定义数据集类:创建SelfDataset类,进行数据增强,如随机裁剪和水平翻转,以提升模型泛化能力。
  3. 选择模型:采用VGG-16架构,通过迁移学习调整分类层为2类,并使用GPU进行训练。
  4. 优化训练:使用Adam优化器和交叉熵损失函数,结合学习率调度器。
  5. 模型优化:利用Intel Extension for PyTorch进行优化,将模型移至CPU并加速训练。
  6. 模型量化:使用Intel Neural Compressor进行量化,进一步减小模型体积并保存。
  7. 推理阶段:在CPU上加载量化后的模型,实现高效图像分类,使推理时间从20多秒缩短至10秒。
  8. 使用OneAPI工具包:有效提升训练和推理效率。

整体上,我们通过数据预处理、模型选择与优化、量化等技术,提升了模型的训练和推理效率,并最终实现了高效准确的猫狗图像分类。

  • 19
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值