基于VGG16的农业作物与杂草检测

该博客围绕利用计算机视觉技术自动检测杂草展开。先介绍任务,包括问题描述、预期方案和原始数据集。接着进行数据预处理,如探索性分析、提取数据集等。分析并修改深度神经网络结构,采用VGG16架构。进行50轮训练后,使用Intel one API优化,包括PyTorch扩展和模型量化,优化效果显著。
部署运行你感兴趣的模型镜像
  1. 目录

    1.任务介绍

    1.1问题描述

    1.2预期解决方案

    1.3原始数据集

    1.4数据展示

    2数据预处理

    2.1探索性分析

    2.2提取数据集

    2.4数据增强

    2.5构建数据集

    3.深度神经网络结构分析和修改

    3.1传统CNN

    3.2 深度神经网络

    3.1VGG16架构

    4.进行训练

    4.1定损、决定优化

    4.2进行50轮次训练,输出每轮测试结果

    5.使用intel one API优化

    5.1使用Intel Extension for PyTorch进行优化

    5.2使用 Intel® Neural Compressor 量化模型 

    5.3测试量化模型

    5.4总结


  2. 1.任务介绍

1.1问题描述

杂草是农业经营中不受欢迎的入侵者,它们通过窃取营养、水、土地和其他关键资源来破坏种植,这些入侵者会导致产量下降和资源部署效率低下。一种已知的方法是使用杀虫剂来清除杂草,但杀虫剂会给人类带来健康风险。我们的目标是利用计算机视觉技术可以自动检测杂草的存在,开发一种只在杂草上而不是在作物上喷洒农药的系统,并使用针对性的修复技术将其从田地中清除,从而最小化杂草对环境的负面影响。

1.2预期解决方案

期待您将其部署到模的生产环境中——里推理时间和二分类准确度(F1分数)将作为评分的主要依据

1.3原始数据集

figure 1 原始数据集

如图1所示,原始数据集以agri_0_n.jpeg, agri_0_n.txt命名,n从0到1399,一共2600项,1300对数据。

对于每组数据,jpeg文件存储其图像信息,txt文件第一个int型数据代表该组数据标签,该值为1代表该图像数据为杂草;

该值为0代表该图像数据标签是农作物。

1.4数据展示

figure 2 图像数据展示

Crop与Weed图像展示如上图,均为分辨率=512*512,位深度=22bits的jpeg文件。

figure 3 文本数据展示

以agri_0_3.txt为例,首位数字代表其标签,标志该同名图像是否杂草。

2数据预处理

2.1探索性分析

随机抽取3个Weed与Crop,统计其数量信息。

import cv2
import os
import numpy as np
import matplotlib.pyplot as plt

train_dir = 'data/'

# 获取图片和对应的标签
image_files = [fn for fn in os.listdir(train_dir) if fn.endswith('.jpeg')]

data = []

for image_file in image_files:
    # 获取对应的txt文件路径
    txt_file = image_file.replace('.jpeg', '.txt')
    txt_path = os.path.join(train_dir, txt_file)


# 统计作物和杂草的数量
crop_count = sum(1 for _, label in data if label == 0)
weed_count = sum(1 for _, label in data if label == 1)

print(f'作物数量为: {crop_count}')
print(f'杂草数量为: {weed_count}')

# 随机选择3个作物和3个杂草图片
select_crop = np.random.choice([img for img, label in data if label == 0], 3, replace=False)
select_weed = np.random.choice([img for img, label in data if label == 1], 3, replace=False)
# 使用subplot打印出来,只显示彩色图像
fig = plt.figure(figsize=(20, 10))
for i in range(6):
    if i < 3:
        fp = f'{select_crop[i]}'
        label = 'Crop'
    else:
        fp = f'{select_weed[i-3]}'
        label = 'Weed'
    
    ax = fig.add_subplot(2, 3, i + 1)
    #print(fp)
    img = cv2.imread(fp)
    #print(img)
    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    
    plt.imshow(img)
    plt.title(label)
    plt.axis('off')
plt.show()

figure 4 Crop与Weed样本量对比

杂草与作物数据量基本一致。

2.2提取数据集

这里首先对混在一起的数据集进行处理,目标是将其分配为train、test、val三种数据集,依照txt文件的标签分别在每种数据集下分为Weed与Crop,所以先进行相应文件目录的创建,方便后续处理。

import os
import shutil
from sklearn.model_selection import train_test_split

data_dir = 'data/'  # 数据集目录
output_dir = 'data/'  # 输出目录(train, test, val)

# 创建输出文件夹
for split in ['train', 'test', 'val']:
    split_dir = os.path.join(output_dir, split)
    for category in ['Crop', 'Weed']:
        category_dir = os.path.join(split_dir, category)
        os.makedirs(category_dir, exist_ok=True)

这里对原始数据集进行处理,按照0.8,0.1,0.1的比例分别分配给train,test,val,根据图像标签分类。

训练集(train set)、验证集(validation set )、测试集(test set),每个数据集都有不同的用途和特点:

训练集(Train set):

        用途:训练模型的最大的数据集。用来拟合模型的参数,使其能够学习到数据的模式和特征。

        特点:通常是最大的数据集,用于模型的训练和参数优化。模型通过与训练集中的数据进行交互来学习。

验证集(Validation set):

        用途:评估模型在训练过程中的性能和调整超参数。用于模型选择、调整和验证。

        特点:用于调整模型的超参数(例如学习率、正则化参数等),以避免过拟合。通常不参与模型参数的训练,而是用来选择最佳模型或确定模型的泛化能力。

测试集(Test set):

        用途:评估模型的泛化能力和性能,检验模型对未见过数据的泛化情况。

        特点:完全独立于训练过程。模型在测试集上的表现能够反映其在实际应用场景中的效果。

# 获取图像和标签的对应关系
data = []
for file in os.listdir(data_dir):
    if file.endswith('.txt'):
        txt_path = os.path.join(data_dir, file)
        with open(txt_path, 'r') as txt_file:
            label = int(txt_file.readline().strip().split()[0])
            img_path = os.path.join(data_dir, file.replace('.txt', '.jpeg'))
            data.append((img_path, label))

# 按比例划分数据集
train_data, test_val_data = train_test_split(data, test_size=0.2, random_state=42)
test_data, val_data = train_test_split(test_val_data, test_size=0.5, random_state=42)

分配后当前目录如上图所示,分为三级结构,方便后续提取。

接着通过自定义函数提取数据,配合所在目录打好标签。

# 创建自定义数据集
class SelfDataset(Dataset):
    def __init__(self, root_dir, transform=None):
        self.root_dir = root_dir
        self.transform = transform
        self.classes = ['Crop', 'Weed']
        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('.jpeg'):
                    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.4数据增强

# 数据增强
transform = transforms.Compose([
    transforms.RandomResizedCrop(64),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
])

针对图像数据进行数据增强的操作,用于在训练深度学习模型时扩充训练数据集,以提高模型的泛化能力和鲁棒性。

这里定义了一个数据增强的操作序列,使用了以下几种变换:

  • transforms.RandomResizedCrop(64):

在图像上进行随机裁剪和缩放。它会随机地从图像中裁剪出一个区域,并将其缩放为指定的大小(64x64像素)。这个操作有助于模型对不同大小和位置的物体具有更好的识别能力。

  • transforms.RandomHorizontalFlip():

进行随机水平翻转图像。

这种操作通过随机翻转图像,增加了训练数据的多样性。物体在水平翻转后仍然具有相同的类别标签,因此这种变换不会改变图像的标签,但可以为模型提供更多变化的样本。

  • transforms.ToTensor():

将图像数据转换为 PyTorch 的张量格式。

模型通常需要输入张量形式的数据进行训练,因此这个操作将图像数据转换为模型可接受的格式。

2.5构建数据集

# 创建数据集实例
train_dataset = SelfDataset(root_dir=train_dataset_path, transform=transform)
test_dataset = SelfDataset(root_dir=test_dataset_path, transform=transform)
val_dataset = SelfDataset(root_dir=val_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)
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)

使用 DataLoader 来加载自定义数据集,并为训练、测试和验证集创建数据加载器。

3.深度神经网络结构分析和修改

3.1传统CNN

传统 CNN 是最早应用于计算机视觉任务的神经网络类型之一。它包括卷积层、池化层和全连接层。卷积层用于提取图像的特征,池化层则用于减少特征图的大小。全连接层在提取了特征之后进行分类或回归等任务。

3.2 深度神经网络

深度神经网络是指具有多个隐藏层的神经网络,允许学习非常复杂的函数映射。这种结构的网络通常包含多个隐藏层,如深度卷积神经网络(DCNNs)或深度残差网络(ResNets),它们通过堆叠多个层来提高模型的表达能力。

3.1VGG16架构

VGG(Visual Geometry Group)是一个视觉几何组在2014年提出的深度卷积神经网络架构。

VGG在2014年ImageNet图像分类竞赛亚军,定位竞赛冠军;VGG网络采用连续的小卷积核(3x3)和池化层构建深度神经网络,网络深度可以达到16层或19层,其中VGG16和VGG19最为著名。

VGG网络被广泛应用于图像分类、目标检测、语义分割等计算机视觉任务中,并且其网络结构的简单性和易实现性使得VGG成为了深度学习领域的经典模型之一。

VGG的参数数量非常大,使用3*3的卷积核,优点如下:

  • 参数少: 一个3x3卷积核拥有9个权重参数,而一个5x5卷积核则需要25个权重参数,因此采用3x3卷积核可以大幅度减少网络的参数数量,从而减少过拟合的风险;
  • 提高非线性能力: 多个3x3卷积核串联起来可以形成一个感受野更大的卷积核,而且这个组合具有更强的非线性能力。在VGG中,多次使用3x3卷积核相当于采用了更大的卷积核,可以提高网络的特征提取能力;
  • 减少计算量: 一个3x3的卷积核可以通过步长为1的卷积操作,得到与一个5x5卷积核步长为2相同的感受野,但计算量更小(即2个3x3代替一个5x5);3个3x3代替一个7x7的卷积;因此,VGG网络采用多个3x3的卷积核,可以在不增加计算量的情况下增加感受野,提高网络的性能。

在图像去雾、超分辨率、风格迁移等领域,感知损失被广泛使用。而感知损失采用较多的正是VGG16网路,后续虽然出现了残差网络ResNet、密集连接网络DenseNet、快速推理的MobileNet等,VGG16网络(及其变种VGG19等)仍然被研究人员普遍使用。

​​​​​VGG16结构图​​

如图可知,传统VGG16网络的全连接层经过激活层后有1000个输出,这里需要针对本文的二分类问题进行全连接层的修改。

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.进行训练

4.1定损、决定优化

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)

这里提供了训练神经网络所需的核心组件:损失函数、优化器、学习率调度器以及设备的选择,以便进行高效的模型训练和参数优化。

下面是修改后的vgg结构:

4.2进行50轮次训练,输出每轮测试结果

# 训练循环
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

训练部分结果如下:

5.使用intel one API优化

5.1使用Intel Extension for PyTorch进行优化

# 使用Intel Extension for PyTorch进行优化
vgg16_model, optimizer = ipex.optimize(model=vgg16_model, optimizer=optimizer, dtype=torch.float32)

对比训练集,用时没有明显差异,这可能是本任务训练集和测试集过小的原因。

5.2使用 Intel® Neural Compressor 量化模型 

以准确度为评估函数进行量化。

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)

量化正常进行的输出结果:

保存量化后的模型:

对比初始接近520M的模型,量化后的文件只有120M左右,空间得到了极大的压缩。

5.3测试量化模型

加载量化:

#使用量化的模型
##加载
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}")
 

测试结果:

5.4总结

在多次测试下,使用Intel Extension for PyTorch进行优化的模型参数耗时稳定在1.03s左右,f1稳定在0.94左右,效率略好于普通模型,针对本任务中100多项的测试集,该优化工具的效果已经有所体现。后续我将继续测试更复杂的数据增强,相信在更大的测试集下使用Intel Extension for PyTorch的优化效果会更突出。

对于使用Intel® Neural Compressor的量化模型,参数的空间占用得到了极大的减少,针对100个所有的测试样本,测试时间在0.4s到1s浮动,耗时得到了极大的优化。且f1稳定在了0.93左右,说明该compressor在压缩模型中很好的保证模型精度和效果。

您可能感兴趣的与本文相关的镜像

PyTorch 2.7

PyTorch 2.7

PyTorch
Cuda

PyTorch 是一个开源的 Python 机器学习库,基于 Torch 库,底层由 C++ 实现,应用于人工智能领域,如计算机视觉和自然语言处理

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值