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

  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在压缩模型中很好的保证模型精度和效果。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值