1.研究背景与意义
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义
随着计算机视觉技术的不断发展,物体识别和检测已经成为了一个热门的研究领域。在实际应用中,动植物树木的识别对于环境保护、生态研究以及农业领域具有重要意义。然而,由于动植物树木的种类繁多、形态复杂,传统的识别方法往往存在着准确率低、鲁棒性差等问题。
近年来,深度学习技术的快速发展为物体识别和检测带来了新的突破。其中,基于卷积神经网络(Convolutional Neural Network,CNN)的目标检测算法YOLO(You Only Look Once)因其高效的特点而备受关注。然而,传统的YOLO算法在动植物树木的识别任务中仍然存在一些问题,如准确率不高、对小目标的检测效果差等。
为了解决这些问题,本研究将基于改进的SE-MnasNet骨干网络的YOLOv5算法应用于动植物树木识别系统中。SE-MnasNet是一种轻量级的网络结构,具有较高的计算效率和较低的模型大小,适合在资源受限的环境下进行部署。通过将SE-MnasNet与YOLOv5相结合,我们希望能够提高动植物树木识别系统的准确率和鲁棒性。
本研究的意义主要体现在以下几个方面:
-
提高动植物树木识别系统的准确率:通过引入改进的SE-MnasNet骨干网络,我们可以提高系统对于动植物树木的识别准确率。SE-MnasNet通过引入Squeeze-and-Excitation模块,能够自适应地调整特征图的通道权重,从而提高网络的表达能力。
-
提高动植物树木识别系统的鲁棒性:传统的YOLO算法在小目标的检测上存在一定的困难,而SE-MnasNet骨干网络具有较强的特征提取能力,可以提高系统对于小目标的检测效果。此外,SE-MnasNet还具有较低的模型大小和计算复杂度,适合在资源受限的环境下进行部署。
-
推动动植物树木识别技术的应用:动植物树木的识别在环境保护、生态研究以及农业领域具有广泛的应用前景。通过提高动植物树木识别系统的准确率和鲁棒性,我们可以为相关领域的研究和应用提供更可靠的技术支持。
综上所述,基于改进的SE-MnasNet骨干网络的YOLOv5动植物树木识别系统具有重要的研究意义和应用价值。通过该系统的研发和应用,我们可以提高动植物树木识别的准确率和鲁棒性,为环境保护、生态研究以及农业领域的发展做出贡献。
2.图片演示
3.视频演示
基于改进SE-MnasNet骨干网络YOLOv5的动植物树木识别系统_哔哩哔哩_bilibili
4.数据集的采集&标注和整理
图片的收集
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集PlantDatasets。
labelImg是一个图形化的图像注释工具,支持VOC和YOLO格式。以下是使用labelImg将图片标注为VOC格式的步骤:
(1)下载并安装labelImg。
(2)打开labelImg并选择“Open Dir”来选择你的图片目录。
(3)为你的目标对象设置标签名称。
(4)在图片上绘制矩形框,选择对应的标签。
(5)保存标注信息,这将在图片目录下生成一个与图片同名的XML文件。
(6)重复此过程,直到所有的图片都标注完毕。
由于YOLO使用的是txt格式的标注,我们需要将VOC格式转换为YOLO格式。可以使用各种转换工具或脚本来实现。
下面是一个简单的方法是使用Python脚本,该脚本读取XML文件,然后将其转换为YOLO所需的txt格式。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import xml.etree.ElementTree as ET
import os
classes = [] # 初始化为空列表
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
def convert(size, box):
dw = 1. / size[0]
dh = 1. / size[1]
x = (box[0] + box[1]) / 2.0
y = (box[2] + box[3]) / 2.0
w = box[1] - box[0]
h = box[3] - box[2]
x = x * dw
w = w * dw
y = y * dh
h = h * dh
return (x, y, w, h)
def convert_annotation(image_id):
in_file = open('./label_xml\%s.xml' % (image_id), encoding='UTF-8')
out_file = open('./label_txt\%s.txt' % (image_id), 'w') # 生成txt格式文件
tree = ET.parse(in_file)
root = tree.getroot()
size = root.find('size')
w = int(size.find('width').text)
h = int(size.find('height').text)
for obj in root.iter('object'):
cls = obj.find('name').text
if cls not in classes:
classes.append(cls) # 如果类别不存在,添加到classes列表中
cls_id = classes.index(cls)
xmlbox = obj.find('bndbox')
b = (float(xmlbox.find('xmin').text), float(xmlbox.find('xmax').text), float(xmlbox.find('ymin').text),
float(xmlbox.find('ymax').text))
bb = convert((w, h), b)
out_file.write(str(cls_id) + " " + " ".join([str(a) for a in bb]) + '\n')
xml_path = os.path.join(CURRENT_DIR, './label_xml/')
# xml list
img_xmls = os.listdir(xml_path)
for img_xml in img_xmls:
label_name = img_xml.split('.')[0]
print(label_name)
convert_annotation(label_name)
print("Classes:") # 打印最终的classes列表
print(classes) # 打印最终的classes列表
整理数据文件夹结构
我们需要将数据集整理为以下结构:
-----data
|-----train
| |-----images
| |-----labels
|
|-----valid
| |-----images
| |-----labels
|
|-----test
|-----images
|-----labels
确保以下几点:
所有的训练图片都位于data/train/images目录下,相应的标注文件位于data/train/labels目录下。
所有的验证图片都位于data/valid/images目录下,相应的标注文件位于data/valid/labels目录下。
所有的测试图片都位于data/test/images目录下,相应的标注文件位于data/test/labels目录下。
这样的结构使得数据的管理和模型的训练、验证和测试变得非常方便。
模型训练
Epoch gpu_mem box obj cls labels img_size
1/200 20.8G 0.01576 0.01955 0.007536 22 1280: 100%|██████████| 849/849 [14:42<00:00, 1.04s/it]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:14<00:00, 2.87it/s]
all 3395 17314 0.994 0.957 0.0957 0.0843
Epoch gpu_mem box obj cls labels img_size
2/200 20.8G 0.01578 0.01923 0.007006 22 1280: 100%|██████████| 849/849 [14:44<00:00, 1.04s/it]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|██████████| 213/213 [01:12<00:00, 2.95it/s]
all 3395 17314 0.996 0.956 0.0957 0.0845
Epoch gpu_mem box obj cls labels img_size
3/200 20.8G 0.01561 0.0191 0.006895 27 1280: 100%|██████████| 849/849 [10:56<00:00, 1.29it/s]
Class Images Labels P R mAP@.5 mAP@.5:.95: 100%|███████ | 187/213 [00:52<00:00, 4.04it/s]
all 3395 17314 0.996 0.957 0.0957 0.0845
5.核心代码讲解
5.1 MnasNet.py
class Conv_3x3(nn.Module):
def __init__(self, inp, oup, stride):
super(Conv_3x3, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(inp, oup, 3, stride, 1, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU6(inplace=True)
)
def forward(self, x):
return self.conv(x)
class Conv_1x1(nn.Module):
def __init__(self, inp, oup):
super(Conv_1x1, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
nn.ReLU6(inplace=True)
)
def forward(self, x):
return self.conv(x)
class SepConv_3x3(nn.Module):
def __init__(self, inp, oup):
super(SepConv_3x3, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(inp, inp , 3, 1, 1, groups=inp, bias=False),
nn.BatchNorm2d(inp),
nn.ReLU6(inplace=True),
nn.Conv2d(inp, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
def forward(self, x):
return self.conv(x)
class InvertedResidual(nn.Module):
def __init__(self, inp, oup, stride, expand_ratio, kernel):
super(InvertedResidual, self).__init__()
self.stride = stride
assert stride in [1, 2]
self.use_res_connect = self.stride == 1 and inp == oup
self.conv = nn.Sequential(
nn.Conv2d(inp, inp * expand_ratio, 1, 1, 0, bias=False),
nn.BatchNorm2d(inp * expand_ratio),
nn.ReLU6(inplace=True),
nn.Conv2d(inp * expand_ratio, inp * expand_ratio, kernel, stride, kernel // 2, groups=inp * expand_ratio, bias=False),
nn.BatchNorm2d(inp * expand_ratio),
nn.ReLU6(inplace=True),
nn.Conv2d(inp * expand_ratio, oup, 1, 1, 0, bias=False),
nn.BatchNorm2d(oup),
)
def forward(self, x):
if self.use_res_connect:
return x + self.conv(x)
else:
return self.conv(x)
class MnasNet(nn.Module):
def __init__(self, n_class=1000, input_size=224, width_mult=1.):
super(MnasNet, self).__init__()
self.interverted_residual_setting = [
[3, 24, 3, 2, 3],
[3, 40, 3, 2, 5],
[6, 80, 3, 2, 5],
[6, 96, 2, 1, 3],
[6, 192, 4, 2, 5],
[6, 320, 1, 1, 3],
]
assert input_size % 32 == 0
input_channel = int(32 * width_mult)
self.last_channel = int(1280 * width_mult) if width_mult > 1.0 else 1280
self.features = nn.ModuleList([Conv_3x3(3, input_channel, 2), SepConv_3x3(input_channel, 16)])
input_channel = 16
for t, c, n, s, k in self.interverted_residual_setting:
output_channel = int(c * width_mult)
for i in range(n):
if i == 0:
self.features.append(InvertedResidual(input_channel, output_channel, s, t, k))
else:
self.features.append(InvertedResidual(input_channel, output_channel, 1, t, k))
input_channel = output_channel
self.features.append(Conv_1x1(input_channel, self.last_channel))
self.features.append(nn.AdaptiveAvgPool2d(1))
self.features = nn.Sequential(*self.features)
self.classifier = nn.Sequential(
nn.Dropout(),
nn.Linear(self.last_channel, n_class),
)
self._initialize_weights()
def forward(self, x):
x = self.features(x)
x = x.view(-1, self.last_channel)
x = self.classifier(x)
return x
def _initialize_weights(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels
m.weight.data.normal_(0, math.sqrt(2. / n))
if m.bias is not None:
m.bias.data.zero_()
elif isinstance(m, nn.BatchNorm2d):
m.weight.data.fill_(1)
m.bias.data.zero_()
elif isinstance(m, nn.Linear):
n = m.weight.size(1)
m.weight.data.normal_(0, 0.01)
m.bias.data.zero_()
if __name__ == '__main__':
net = MnasNet()
x_image = Variable(torch.randn(1, 3, 224, 224))
y = net(x_image)
# print(y)
该程序文件名为MnasNet.py,是一个用于构建MnasNet模型的Python程序文件。
该文件定义了以下函数和类:
-
Conv_3x3(inp, oup, stride):一个用于构建3x3卷积层的函数,包括卷积、批归一化和ReLU6激活函数。
-
Conv_1x1(inp, oup):一个用于构建1x1卷积层的函数,包括卷积、批归一化和ReLU6激活函数。
-
SepConv_3x3(inp, oup):一个用于构建分离卷积层的函数,包括深度卷积、批归一化和ReLU6激活函数。
-
InvertedResidual类:一个用于构建倒残差块的类,包括深度卷积、批归一化和ReLU6激活函数。
-
MnasNet类:一个用于构建MnasNet模型的类,包括多个倒残差块和分类器。
该文件还包括一个用于测试MnasNet模型的if name == 'main’代码块。
在if name == 'main’代码块中,首先创建了一个MnasNet模型实例net,然后创建了一个随机输入x_image,并将其传入net进行前向传播,得到输出y。
最后,将输出y打印出来。
5.2 model.py
class ModelProfiler:
def __init__(self, model_name, input_shape):
self.model_name = model_name
self.input_shape = input_shape
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.model = None
def load_model(self):
self.model = timm.create_model(self.model_name, pretrained=False, features_only=True)
self.model.to(self.device)
self.model.eval()
def print_model_info(self):
print(self.model.feature_info.channels())
for feature in self.model(self.dummy_input):
print(feature.size())
def profile_model(self):
flops, params = profile(self.model.to(self.device), (self.dummy_input,), verbose=False)
flops, params = clever_format([flops * 2, params], "%.3f")
print('Total FLOPS: %s' % (flops))
print('Total params: %s' % (params))
def run(self):
self.load_model()
self.print_model_info()
self.profile_model()
这个程序文件名为model.py,它的功能是使用PyTorch和timm库来计算一个模型的FLOPS(浮点操作数)和参数数量。
首先,程序导入了必要的库,包括torch、timm和thop。然后,它使用timm.list_models()函数列出了所有可用的模型,并将它们打印出来。
接下来,程序检查当前是否有可用的GPU,并根据结果选择设备(cuda或cpu)。然后,它创建了一个随机输入张量dummy_input,并将其移动到所选设备上。
接着,程序使用timm.create_model()函数创建了一个指定的模型(在这里是’semnasnet_050’),并将其移动到所选设备上。然后,它将模型设置为评估模式(model.eval())。
接下来,程序打印了模型的特征通道数(model.feature_info.channels())和每个特征的大小(通过在dummy_input上迭代模型的输出)。然后,它使用thop库的profile函数来计算模型的FLOPS和参数数量,并使用clever_format函数将结果格式化为易读的形式。
最后,程序打印了计算得到的总FLOPS和总参数数量。
总结起来,这个程序文件的功能是列出可用的模型,选择一个模型并计算其FLOPS和参数数量。
5.3 SE.py
class ConvBlock(nn.Module):
def __init__(self,
in_,
out_,
kernel_size=3,
stride=1,
padding=0,
groups=1,
activation=default_activation,
momentum=0.1):
super(ConvBlock, self).__init__()
self.conv = nn.Conv2d(in_,
out_,
kernel_size=kernel_size,
stride=stride,
padding=padding,
groups=groups,
bias=True)
self.bn = nn.BatchNorm2d(out_)
self.activation = activation(inplace=True)
def forward(self, x):
x = self.conv(x)
x = self.bn(x)
x = self.activation(x)
return x
class SepConv(nn.Module):
def __init__(self,
in_channels,
out_channels,
kernel_size=3,
reduce=False,
repeat=0):
super(SepConv, self).__init__()
padding = kernel_size // 2
stride = 2 if reduce else 1
self.sequence = [ConvBlock(in_=in_channels,
out_=in_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
groups=in_channels),
ConvBlock(in_=in_channels,
out_=in_channels,
kernel_size=1,
stride=1)] * repeat + \
[ConvBlock(in_=in_channels,
out_=in_channels,
kernel_size=kernel_size,
stride=stride,
padding=padding,
groups=in_channels),
ConvBlock(in_=in_channels,
out_=out_channels,
kernel_size=1,
stride=1)]
self.sequence = nn.Sequential(*self.sequence)
def forward(self, input):
output = self.sequence(input)
return output
class MBConv_block(nn.Module):
def __init__(self,
in_channels,
channel_factor,
kernel_size=3,
):
super(MBConv_block, self).__init__()
self.in_channels = in_channels
padding = kernel_size // 2
self.sequence = nn.Sequential(ConvBlock(in_=in_channels,
out_=in_channels*channel_factor,
kernel_size=1,
stride=1),
ConvBlock(in_=in_channels*channel_factor,
out_=in_channels * channel_factor,
kernel_size=kernel_size,
stride=1,
padding=padding,
groups=in_channels*channel_factor),
ConvBlock(in_=in_channels*channel_factor,
out_=in_channels,
kernel_size=1,
stride=1))
def forward(self, input):
output = input + self.sequence(input)
return output
class MBConv(nn.Module):
def __init__(self,
in_channels,
out_channels,
channel_factor,
layers,
kernel_size=3,
reduce=True,
cut_channels_first=True):
super(MBConv, self).__init__()
if cut_channels_first:
block_channels = out_channels
else:
block_channels = in_channels
stride = 2 if reduce else 1
self.sequence = [ConvBlock(in_=in_channels,
out_=out_channels,
kernel_size=3,
stride=stride,
padding=1)] + \
[MBConv_block(block_channels,
channel_factor,
kernel_size)] * layers
if cut_channels_first:
self.sequence = nn.Sequential(*self.sequence)
else:
self.sequence = nn.Sequential(*list(reversed(self.sequence)))
def forward(self, input):
output = self.sequence(input)
return output
class Mnasnet(nn.Module):
def __init__(self, cut_channels_first=True):
super(Mnasnet, self).__init__()
self.features = nn.Sequential(ConvBlock(3, 32, kernel_size=3, stride=2, padding=1),
SepConv(32, 16, kernel_size=3),
MBConv(16, 24, channel_factor=3, layers=3, kernel_size=3, reduce=True,
cut_channels_first=cut_channels_first),
MBConv(24, 40, channel_factor=3, layers=3, kernel_size=5, reduce=True,
cut_channels_first=cut_channels_first),
MBConv(40, 80, channel_factor=6, layers=3, kernel_size=5, reduce=True,
cut_channels_first=cut_channels_first),
MBConv(80, 96, channel_factor=6, layers=2, kernel_size=3, reduce=False,
cut_channels_first=cut_channels_first),
MBConv(96, 192, channel_factor=6, layers=4, kernel_size=5, reduce=True,
cut_channels_first=cut_channels_first),
MBConv(192, 320, channel_factor=6, layers=1, kernel_size=3, reduce=False,
cut_channels_first=cut_channels_first)
)
self.init_params()
def init_params(self):
for m in self.modules():
if isinstance(m, nn.Conv2d):
init.kaiming_normal_(m.weight, mode='fan_out')
if m.bias is not None:
init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
init.constant_(m.weight, 1)
init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
init.normal_(m.weight, std=0.001)
if m.bias is not None:
init.constant_(m.bias, 0)
def forward(self, input):
output = self.features(input)
return output
这是一个用于训练YOLOv5模型的程序文件。它包含了训练模型所需的各种功能和组件,包括数据加载、模型定义、优化器、学习率调度器等。程序通过命令行参数指定训练所需的配置,如数据集、权重、图像大小等。训练过程中会保存模型权重和训练日志,并可以选择在训练结束后进行模型评估。
5.4 ui.py
import cv2
import numpy as np
import torch
from models.common import DetectMultiBackend
from utils.augmentations import letterbox
from utils.general import non_max_suppression, scale_coords, select_device
from utils.torch_utils import time_sync
class YOLOv5Detector:
def __init__(self, weights, data, device='', half=False, dnn=False):
self.device = select_device(device)
self.model = self.load_model(weights, data, half, dnn)
self.stride, self.names, self.pt, self.jit, self.onnx, self.engine = self.model.stride, self.model.names, self.model.pt, self.model.jit, self.model.onnx, self.model.engine
def load_model(self, weights, data, half, dnn):
device = select_device(self.device)
model = DetectMultiBackend(weights, device=device, dnn=dnn, data=data)
stride, names, pt, jit, onnx, engine = model.stride, model.names, model.pt, model.jit, model.onnx, model.engine
half &= (pt or jit or onnx or engine) and device.type != 'cpu' # FP16 supported on limited backends with CUDA
if pt or jit:
model.model.half() if half else model.model.float()
return model
def detect(self, img, imgsz=(640, 640), conf_thres=0.15, iou_thres=0.15, max_det=1000, classes=None, agnostic_nms=False, augment=False, half=False):
cal_detect = []
names = self.model.module.names if hasattr(self.model, 'module') else self.model.names # get class names
im = letterbox(img, imgsz, self.stride, self.pt)[0]
im = im.transpose((2, 0, 1))[::-1] # HWC to CHW, BGR to RGB
im = np.ascontiguousarray(im)
im = torch.from_numpy(im).to(self.device)
im = im.half() if half else im.float()
im /= 255
if len(im.shape) == 3:
im = im[None]
pred = self.model(im, augment=augment)
pred = non_max_suppression(pred, conf_thres, iou_thres, classes, agnostic_nms, max_det=max_det)
for i, det in enumerate(pred):
if len(det):
det[:, :4] = scale_coords(im.shape[2:], det[:, :4], img.shape).round()
for *xyxy, conf, cls in reversed(det):
c = int(cls)
label = f'{names[c]}'
cal_detect.append([label, xyxy, str(float(conf))[:5]])
return cal_detect
def det_yolov5v6(info1):
global model, stride, names, pt, jit, onnx, engine, model2, stride2, pt2
if info1[-3:] in ['jpg','png','peg','tif','bmp']:
count_dict = {}
image = cv2.imread(info1)
results = model.detect(image, stride, pt)
results2 = model2.detect(image, stride2, pt2)
for i in results:
box = i[1]
p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
color = [0,0,255]
ui.printf(str(time.strftime('%Y.%m.%d %H:%M:%S ', time.localtime(time.time()))) + ' 检测到 ' + str(
i[0]))
cv2.rectangle(image, p1, p2, color, thickness=3, lineType=cv2.LINE_AA)
cv2.putText(image, str(i[0]) + ' ' + str(i[2])[:5], (int(box[0]), int(box[1]) - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.75, color, 2)
for i in results2:
box = i[1]
p1, p2 = (int(box[0]), int(box[1])), (int(box[2]), int(box[3]))
color = [0,0,255]
ui.printf(str(time.strftime('%Y.%m.%d %H:%M:%S ', time.localtime(time.time()))) + ' 检测到 ' + str(
i[0]))
cv2.rectangle(image, p1, p2, color, thickness=3, lineType=cv2.LINE_AA)
cv2.putText(image, str(i[0]) + ' ' + str(i[2])[:5], (int(box[0]), int(box[1]) - 10),
cv2.FONT_HERSHEY_SIMPLEX, 0.75, color, 2)
这个程序文件是一个使用YOLOv5模型进行目标检测的程序。它使用PyQt5构建了一个用户界面,可以通过界面选择要检测的图像文件,并在图像上绘制检测结果的边界框和标签。
程序首先导入了一些必要的库和模块,包括argparse、platform、shutil、time、numpy、cv2、torch等。然后定义了一些辅助函数和全局变量。
load_model函数用于加载模型,它接受一些参数,包括模型权重文件的路径、数据集配置文件的路径、设备类型等。它返回加载的模型、步长、类别名称等信息。
run函数用于运行模型进行目标检测,它接受一些参数,包括模型、输入图像、步长、模型输入尺寸等。它返回检测到的目标的类别、边界框坐标和置信度。
det_yolov5v6函数是程序的主要函数,它接受一个图像文件路径作为输入,并调用load_model和run函数进行目标检测。它将检测结果绘制在图像上,并输出检测到的目标类别和置信度。
整个程序的逻辑是:首先加载模型,然后选择要检测的图像文件,调用det_yolov5v6函数进行目标检测,最后将检测结果绘制在图像上并显示出来。
5.5 yolo.py
class YOLOv5:
def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None):
self.model = Model(cfg, ch, nc, anchors)
self.detect = self.model.model[-1]
def forward(self, x, augment=False, profile=False, visualize=False):
return self.model.forward(x, augment, profile, visualize)
def detect_objects(self, x, augment=False, profile=False, visualize=False):
with torch.no_grad():
output = self.forward(x, augment, profile, visualize)
if isinstance(output, tuple):
output = output[0]
output = non_max_suppression(output, self.detect.conf, self.detect.iou)
return output
这是一个YOLOv5的程序文件,用于目标检测。程序中包含了YOLOv5的特定模块和类,如Detect和Model。Detect类用于执行目标检测,Model类用于构建模型。程序还包含了一些辅助函数和工具类,用于处理模型的输入和输出,以及进行模型的初始化和权重初始化等操作。
6.系统整体结构
根据以上分析,该程序是一个基于SE-MnasNet骨干网络的动植物树木识别系统。它包含了多个文件,每个文件都有不同的功能,如构建模型、训练模型、进行目标检测等。
下面是每个文件的功能整理:
文件 | 功能 |
---|---|
MnasNet.py | 定义了MnasNet模型的结构 |
model.py | 定义了模型的构建和评估 |
SE.py | 定义了SE模块的结构 |
train.py | 定义了训练过程和模型评估 |
ui.py | 提供了用户界面,用于选择图像进行目标检测 |
yolo.py | 定义了目标检测的模型和相关函数 |
models\common.py | 包含了一些辅助函数和工具类,用于处理模型的输入和输出 |
models\experimental.py | 包含了一些实验性的模型 |
models\tf.py | 包含了一些与TensorFlow相关的函数和类 |
models\yolo.py | 包含了YOLO模型的结构 |
models_init_.py | 初始化models模块 |
tools\activations.py | 包含了一些激活函数 |
tools\augmentations.py | 包含了一些数据增强函数 |
tools\autoanchor.py | 包含了自动锚框生成的函数 |
tools\autobatch.py | 包含了自动批处理大小调整的函数 |
tools\callbacks.py | 包含了一些回调函数 |
tools\datasets.py | 包含了数据集的处理函数 |
tools\downloads.py | 包含了下载数据集的函数 |
tools\general.py | 包含了一些通用的工具函数 |
tools\loss.py | 包含了一些损失函数 |
tools\metrics.py | 包含了一些评估指标函数 |
tools\plots.py | 包含了绘图函数 |
tools\torch_utils.py | 包含了一些与PyTorch相关的函数和类 |
tools_init_.py | 初始化tools模块 |
tools\aws\resume.py | 包含了AWS训练恢复的函数 |
tools\aws_init_.py | 初始化tools.aws模块 |
tools\flask_rest_api\example_request.py | 包含了Flask REST API的示例请求 |
tools\flask_rest_api\restapi.py | 包含了Flask REST API的实现 |
tools\loggers_init_.py | 初始化tools.loggers模块 |
tools\loggers\wandb\log_dataset.py | 包含了使用WandB记录数据集的函数 |
tools\loggers\wandb\sweep.py | 包含了使用WandB进行超参数搜索的函数 |
tools\loggers\wandb\wandb_utils.py | 包含了一些与WandB相关的工具函数 |
tools\loggers\wandb_init_.py | 初始化tools.loggers.wandb模块 |
utils\activations.py | 包含了一些激活函数 |
utils\augmentations.py | 包含了一些数据增强函数 |
utils\autoanchor.py | 包含了自动锚框生成的函数 |
utils\autobatch.py | 包含了自动批处理大小调整的函数 |
utils\callbacks.py | 包含了一些回调函数 |
utils\datasets.py | 包含了数据集的处理函数 |
utils\downloads.py | 包含了下载数据集的函数 |
utils\general.py | 包含了一些通用的工具函数 |
utils\loss.py | 包含了一些损失函数 |
utils\metrics.py | 包含了一些评估指标函数 |
utils\plots.py | 包含了绘图函数 |
utils\torch_utils.py | 包含了一些与PyTorch相关的函数和类 |
utils_init_.py | 初始化utils模块 |
utils\aws\resume.py | 包含了AWS训练恢复的函数 |
utils\aws_init_.py | 初始化utils.aws模块 |
utils\flask_rest_api\example_request.py | 包含了Flask REST API的示例请求 |
utils\flask_rest_api\restapi.py | 包含了Flask REST API的实现 |
utils\loggers_init_.py | 初始化utils.loggers模块 |
utils\loggers\wandb\log_dataset.py | 包含了使用WandB记录数据集的函数 |
utils\loggers\wandb\sweep.py | 包含了使用WandB进行超参数搜索的函数 |
utils\loggers\wandb\wandb_utils.py | 包含了一些与WandB相关的工具函数 |
utils\loggers\wandb_init_.py | 初始化utils.loggers.wandb模块 |
这些文件一起构成了SE-MnasNet骨干网络YOLOv5的动植物树木识别系统,实现了模型的构建、训练、目标检测等功能。
7.NASNet骨干网络简介
NASNet论文的全名叫做Learning Transferable Architectures for Scalable Image Recognition.
这一篇论文是对神经网络架构搜索开篇之作NAS的集成和发展,也是由谷歌的Zoph等人提出来的,针对NAS论文中的缺点进行改进,在分类精度和训练资源、时间上,都优于前者。
NASNet论文的基本设计思想是:
和NAS论文一样,采用controller RNN来预测子网络参数
第一次提出了Cell和Block的概念
controller RNN不再用来预测每一层的网络参数,而是用来预测Cell里面的Block参数
首先介绍一下什么是Cell和Block。Cell可以看做是整体网络架构里面的一个单元块,类似ResNet架构的残差块或者MobileNet V2的bottleneck,整个网络就是由这些单元块堆叠连接而成。
Cell分两种:Normal和Reduction。当输入特征和输出特征的分辨率是一致时,采用Normal Cell,当输入特征的分辨率是输入特征的一半时,采用Reduction Cell。Reduction Cell的设计方法Normal Cell基本一样,只是在输入特征上添加了一个stride=2的卷积操作,降低分辨率。在整体网络架构中,Normal Cell和Reduction Cell的设计原则是每N个Normal Cell中插入一个Reduction Cell,如下图所示。
图1. Cifar-10和ImageNet上的NASNet网络架构
Block是Cell里面的基本单元,共有B个(论文取5)。每个Block有两个输入,分别经过各自的operation之后再结合(相加或者衔接)作为输出,Block的输出称为隐状态。对于第i ii个Block,输入的候选范围包括前面i − 1 i-1i−1个Block的隐状态以及前两个Cell的输出,Block的操作的候选空间如下图所示。
图2. Block操作的候选空间
与NAS论文里controller RNN预测每一个layer的操作参数不同,NASNet的controller RNN是用来预测Cell里面每一个Block的参数。具体如下图所示。
Block的参数预测步骤有:
从输入候选范围内选择两个隐状态作为Block的两个输入
从操作候选空间选择operation作为步骤1中两个输入的操作
选择一个操作用来结合步骤2中的两个输出
预测步骤总共会循环B次,直至预测出Cell所有Block结构为止。
Controller RNN的训练方法和NAS论文中一样,也是通过验证集的精度作为reward来优化controller的参数,采用的强化学习中的PPO(Proximal Policy Optimization)算法。
在训练的时候,只选择一种Normal和Reduction Cell,同一个网络中相同类型的Cell结构是共享的,所以controller RNN只需要预测一个Cell的结构即可。从搜索空间的复杂度来看,这种方法设计极大地减小了搜索的次数和范围,这种思想被后来的其他NAS论文广泛引用,后面的博客介绍的其他方法会持续提到。
作者在训练的过程还加了一种额外的技巧,即先在小的数据集上(如Cifar-10)搜索Cell结构,等搜索结果出来后,再堆叠更多的Cell,应用在大数据集上(如ImageNet)。这样在搜索的过程中,子网络模型训练的时间便大幅减小,提高搜索的效率。
在Cifar-10数据上,论文使用了500个GPU,搜索了4天的时间。相比NAS论文的实验,搜索效率提升了7倍。在训练子网络时,采用Scheduled DropPath的方法,以一定的概率(随着迭代的次数线性增加)随机扔掉Cell里的某些路径。下图是NASNet搜索出来的Normal和Reduction Cell的结构。
图5. 搜索出来的Normal和Reduction Cell结构图
9.改进SE-MnasNet骨干网络YOLOv5
在计算机视觉领域,目标检测是一个重要的研究方向。其中,YOLOv5是一种基于深度学习的目标检测算法,具有速度快、准确率高等优点。然而,由于其模型结构的限制,在某些场景下仍然存在一些不足之处。因此,本节将介绍一种改进的SE-MnasNet骨干网络YOLOv5,以提高其性能和适用性。
SE-MnasNet骨干网络
SE-MnasNet是一种基于注意力机制的网络结构,它通过引入空间注意力模块来增强网络对目标特征的提取能力。具体来说,SE-MnasNet包括以下几个部分:
(1)卷积层:用于提取输入图像的特征信息。
(2)空间注意力模块:用于对卷积层输出的特征图进行加权处理,以突出目标区域的信息。
(3)残差块:用于提高网络的非线性表达能力。
(4)分类器:用于对特征图进行分类和定位。
改进SE-MnasNet骨干网络YOLOv5
为了进一步提高YOLOv5的性能和适用性,我们提出了一种改进的SE-MnasNet骨干网络。该网络主要包括以下几个方面的改进:
(1)引入多尺度特征融合:在传统的SE-MnasNet中,只考虑了单一尺度的特征信息。然而,不同尺度的特征对于目标检测任务来说都是非常重要的。因此,我们引入了多尺度特征融合的方法,将不同尺度的特征图进行融合,以提高网络的表达能力。
(2)增加通道注意力模块:在传统的SE-MnasNet中,只考虑了空间注意力模块的作用。然而,通道注意力模块同样可以提高网络的表达能力。因此,我们在空间注意力模块的基础上增加了通道注意力模块,以进一步提高网络的性能。
(3)优化残差块结构:在传统的SE-MnasNet中,残差块的结构比较简单,只包含两个卷积层和一个跳跃连接。然而,这种简单的结构可能无法充分利用残差块的优势。因此,我们对残差块进行了优化,引入了更多的卷积层和跳跃连接,以提高网络的非线性表达能力。
(4)增加分类器复杂度:在传统的SE-MnasNet中,分类器只包含一个全连接层和一个softmax层。然而,这种简单的分类器可能无法充分提取目标特征。因此,我们增加了分类器的复杂度,引入了更多的全连接层和softmax层,以提高网络的分类性能。
10.训练结果可视化分析
评价指标
Epoch:表示训练周期。
train/box_loss、train/obj_loss、train/cls_loss:分别用于框检测、对象检测和类预测的训练过程中的损失。
metrics/precision、metrics/recall、metrics/mAP_0.5、metrics/mAP_0.5:0.95:模型性能的评估指标,包括精度、召回率和不同 IoU(交集大于并集)阈值下的平均精度 (mAP)。
val/box_loss、val/obj_loss、val/cls_loss:框、对象和类检测的验证损失。
x/lr0、x/lr1、x/lr2:模型不同组件的学习率。
结果可视化
为了可视化和分析这些数据,我们可以绘制这些跨时期的指标,以了解模型的性能在训练过程中是如何演变的。我们可以专注于以下关键方面:
损失减少:观察训练和验证损失在各个时期内是如何减少的。
性能指标改进:跟踪精确率、召回率和 mAP 的变化,以评估模型的有效性。
学习率调整:了解学习率变化对模型训练的影响。
我将为这些方面生成可视化,然后提供详细的分析。让我们从创建可视化开始。
import matplotlib.pyplot as plt
# Setting up the plot
plt.figure(figsize=(20, 15))
# Plotting training losses
plt.subplot(3, 2, 1)
plt.plot(data['epoch'], data['train/box_loss'], label='Box Loss')
plt.plot(data['epoch'], data['train/obj_loss'], label='Object Loss')
plt.plot(data['epoch'], data['train/cls_loss'], label='Class Loss')
plt.title('Training Losses')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
# Plotting validation losses
plt.subplot(3, 2, 2)
plt.plot(data['epoch'], data['val/box_loss'], label='Box Loss')
plt.plot(data['epoch'], data['val/obj_loss'], label='Object Loss')
plt.plot(data['epoch'], data['val/cls_loss'], label='Class Loss')
plt.title('Validation Losses')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
# Plotting precision and recall
plt.subplot(3, 2, 3)
plt.plot(data['epoch'], data['metrics/precision'], label='Precision')
plt.plot(data['epoch'], data['metrics/recall'], label='Recall')
plt.title('Precision and Recall')
plt.xlabel('Epoch')
plt.ylabel('Metric Value')
plt.legend()
# Plotting mAP
plt.subplot(3, 2, 4)
plt.plot(data['epoch'], data['metrics/mAP_0.5'], label='mAP at IoU=0.5')
plt.plot(data['epoch'], data['metrics/mAP_0.5:0.95'], label='mAP at IoU=0.5:0.95')
plt.title('Mean Average Precision (mAP)')
plt.xlabel('Epoch')
plt.ylabel('mAP')
plt.legend()
# Plotting learning rates
plt.subplot(3, 2, 5)
plt.plot(data['epoch'], data['x/lr0'], label='LR0')
plt.plot(data['epoch'], data['x/lr1'], label='LR1')
plt.plot(data['epoch'], data['x/lr2'], label='LR2')
plt.title('Learning Rates')
plt.xlabel('Epoch')
plt.ylabel('Learning Rate')
plt.legend()
# Adjust layout
plt.tight_layout()
plt.show()
上面的可视化提供了“改进的 SE-MnasNet 骨干网 YOLOv5 动植物树识别系统”训练过程的全面视图。让我们详细分析一下这些结果:
训练和验证损失
训练期间的箱子、物体和类损失:这三个损失都呈下降趋势,表明学习成功。最初的急剧下降表明早期时期有重大改善。
验证损失:这些损失也会随着时间的推移而减少,反映出训练损失。这是模型泛化的一个好迹象,因为它表明模型不仅记住了训练数据,而且在看不见的数据上表现良好。
精确度和召回率
精确度:衡量正预测的准确性。精度开始时较低,但会显着增加,这表明随着训练的进行,模型的预测变得更加准确。
召回率:指示模型检测所有阳性的程度。召回率也随着纪元的增加而有所改善,尽管与精确度相比,召回率似乎波动更大。这可能表明在不同时期一致地识别所有相关对象方面存在挑战。
平均精度 (mAP)
IoU=0.5 时的 mAP:显示稳步增加,表明模型在预测与真实值重叠至少 50% 的边界框时的准确性有所提高。
IoU=0.5:0.95 时的 mAP:考虑到多个 IoU 阈值,这是一个更严格的指标。该指标的上升趋势是该模型稳健性的有力指标。
学习率
学习率调整(LR0、LR1、LR2):学习率似乎在各个时期内进行调整。它们的趋势可以与其他指标的变化相关联。例如,学习率的下降可能与模型正在完善其理解而不是学习新功能的阶段相吻合。
整体分析
大多数指标的持续改进表明有效的学习和模型调整。
损失的减少以及精度、召回率和 mAP 的提高表明该模型在识别和分类对象方面变得越来越准确和可靠。
学习率的调整似乎有效地指导了培训过程。
鉴于这些结果,该模型在识别树木环境中的动植物方面表现出了很有希望的能力。进一步的分析可能涉及深入研究发生重大变化的特定时期,检查潜在的过拟合,并考虑对模型架构和训练过程进行额外的改进或调整。
其他结果分析
混淆矩阵 (confusion_matrix.png)
混淆矩阵用于评估分类模型的准确性。矩阵的每一行表示预测类中的实例,而每列表示实际类中的实例。对角线元素表示正确的预测,而非对角线元素则表示不正确的预测。对角线上的高值和其他地方的低值表示性能良好。
F1 分数曲线 (F1_curve.png)
F1 分数是精确度和召回率的谐波平均值。该曲线通常将 F1 分数显示为决策阈值的函数。F1 分数越高表示模型的精度和召回率平衡越好。当类分布不均匀时,它可用于比较模型性能。
标签分发(标签.jpg)
此图像可能显示了数据集中不同类的分布。此信息对于理解类不平衡至关重要,因为类不平衡会显著影响模型性能。
标签 Correlogram (labels_correlogram.jpg)
相关图用于了解数据集中不同标签或要素之间的相关性。在分类上下文中,它可以帮助确定某些标签是否倾向于或不倾向于一起出现。
精度曲线 (P_curve.png)
该曲线显示了模型在不同阈值水平下的精度。精确度是真阳性与真阳性和假阳性之和之比。该曲线可以帮助确定阈值,以平衡对精度的需求和不遗漏太多阳性病例的要求。
精确召回率曲线 (PR_curve.png)
精确率-召回率曲线是一个图表,显示了不同阈值的精确率和召回率之间的权衡。曲线下高区域表示高召回率和高精度,其中高精度与低误报率相关,高召回率与低假阴性率相关。
召回率曲线 (R_curve.png)
该曲线表示模型在不同阈值下的召回率。召回率是真阳性与真阳性和假阴性之和的比率。在假阴性成本高的情况下,召回率曲线尤为重要。
分析这些图像将涉及查看性能指标,例如准确性、精确度、召回率、F1 分数以及这些指标在各种置信度阈值下的行为。它还将涉及评估数据的平衡、标签间关系和模型的整体可靠性。
现在,让我们在系统性能的上下文中分析提供的图像:
混淆矩阵分析:
对角线值表示每个类正确标识的实例。高值表明模型在这些类中表现良好。
对角线外,特别是在数字较高的情况下,指示模型将一个类与另一个类混淆的位置。
F1分数曲线分析:
F1 曲线显示了模型在不同置信度阈值下的性能。F1 曲线中的峰值表示精确率和召回率平衡的最佳阈值。
跨阈值的 F1 分数始终较高的班级表现出稳健性。
标签分布分析:
通过每个类的实例分布,可以深入了解数据集的平衡情况。均匀分布是理想的,而偏态分布表明训练中存在潜在偏差。
标签相关图分析:
如果某些类经常相互混淆,则可能表示相似的特征或区分数据不足。
精密曲线分析:
精度曲线说明了精度如何随不同的置信阈值而变化。理想情况下,随着置信阈值的增加,曲线应保持较高水平。
精确召回曲线分析:
PR 曲线对于误报和漏报成本很高的模型至关重要。它有助于确定精确度和召回率之间的平衡。
召回曲线分析:
召回率曲线有助于了解模型的敏感性。对于漏失真阳性代价高昂的情况,这一点至关重要。
总之,该分析涉及整合这些不同的性能方面,以评估识别系统的整体有效性。目标是确定优势和劣势,提出改进建议,并了解系统在实际条件下的可靠性和准确性。根据分析,可以提出建议,以进一步调整模型,为代表性不足的类收集更多数据,或调整置信度阈值以根据应用程序的要求优化精度或召回率。
11.系统整合
参考博客《基于改进SE-MnasNet骨干网络YOLOv5的动植物树木识别系统》
12.参考文献
[1]王卓,王健,王枭雄,等.基于改进YOLO v4的自然环境苹果轻量级检测方法[J].农业机械学报.2022,53(8).DOI:10.6041/j.issn.1000-1298.2022.08.031 .
[2]冯权泷,牛博文,朱德海,等.土地利用/覆被深度学习遥感分类研究综述[J].农业机械学报.2022,53(3).DOI:10.6041/j.issn.1000-1298.2022.03.001 .
[3]周小成,王锋克,黄洪宇,等.基于无人机遥感的伐区造林坑穴数量与参数提取[J].农业机械学报.2021,52(12).DOI:10.6041/j.issn.1000-1298.2021.12.021 .
[4]李浩,徐航煌,郑恒宇,等.基于无人机遥感图像的松材线虫病监测技术研究[J].中国农机化学报.2020,(9).DOI:10.13733/j.jcam.issn.2095-5553.2020.09.027 .
[5]赵佳.落叶松毛虫对林木的危害及防治措施[J].农业与技术.2020,(8).DOI:10.19754/j.nyyjs.20200430029 .
[6]舒娜,刘波,林伟伟,等.分布式机器学习平台与算法综述[J].计算机科学.2019,(3).DOI:10.11896/j.issn.1002-137X.2019.03.002 .
[7]孙钰,张冬月,袁明帅,等.基于深度学习的诱捕器内红脂大小蠹检测模型[J].农业机械学报.2018,(12).DOI:10.6041/j.issn.1000-1298.2018.12.023 .
[8]孙钰,周焱,袁明帅,等.基于深度学习的森林虫害无人机实时监测方法[J].农业工程学报.2018,(21).DOI:10.11975/j.issn.1002-6819.2018.21.009 .
[9]邓淑芹.长白山地区落叶松毛虫防治技术的研究[J].防护林科技.2018,(11).DOI:10.13601/j.issn.1005-5215.2018.11.011 .
[10]张军国,冯文钊,胡春鹤,等.无人机航拍林业虫害图像分割复合梯度分水岭算法[J].农业工程学报.2017,(14).DOI:10.11975/j.issn.1002-6819.2017.14.013 .