【改进YOLOv8】生猪胖瘦评价分级系统:可重参化EfficientRepBiPAN优化Neck

本文主要是介绍【改进YOLOv8】生猪胖瘦评价分级系统:可重参化EfficientRepBiPAN优化Neck,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1.研究背景与意义

项目参考AAAI Association for the Advancement of Artificial Intelligence

研究背景与意义:

随着计算机视觉和深度学习的快速发展,目标检测成为了计算机视觉领域的一个重要研究方向。目标检测的目标是在图像或视频中准确地识别和定位出物体的位置。在过去的几年中,YOLO(You Only Look Once)系列算法以其高效的实时目标检测能力而备受关注。YOLOv8是YOLO系列算法的最新版本,它在准确性和速度方面都有了显著的提升。

然而,尽管YOLOv8在目标检测任务中表现出色,但仍然存在一些潜在的问题和局限性。首先,YOLOv8在处理小目标时存在一定的困难,容易出现漏检和误检的情况。其次,YOLOv8在复杂背景下的目标检测效果也不够理想,容易受到背景干扰的影响。此外,YOLOv8对于目标形状的变化和姿态的变化也不够鲁棒,容易出现定位不准确的情况。

因此,改进YOLOv8的研究具有重要的理论和实际意义。首先,改进YOLOv8可以提高目标检测的准确性和鲁棒性,使其更适用于各种复杂场景和应用场景。其次,改进YOLOv8可以进一步提高目标检测的实时性能,使其能够更好地应用于实时视频监控、自动驾驶等领域。此外,改进YOLOv8还可以为其他相关研究提供有益的借鉴和参考,推动目标检测算法的发展。

为了改进YOLOv8,可以从多个方面进行研究。首先,可以探索更加有效的特征提取方法,以提高目标检测的准确性和鲁棒性。例如,可以引入更深的卷积神经网络结构或者使用注意力机制来提取更具有判别性的特征。其次,可以研究目标检测中的数据增强方法,以增加模型对于小目标、复杂背景和姿态变化的适应能力。此外,还可以探索目标检测中的多尺度处理方法,以提高模型对于不同尺度目标的检测能力。

总之,改进YOLOv8的研究具有重要的学术和实际意义。通过改进YOLOv8,可以提高目标检测的准确性、鲁棒性和实时性能,推动计算机视觉领域的发展。同时,改进YOLOv8还可以为其他相关研究提供有益的借鉴和参考,促进目标检测算法的进一步研究和应用。因此,改进YOLOv8的研究是一个具有挑战性和前景广阔的课题,值得深入探索和研究。

2.图片演示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.视频演示

群山科技工作室的个人空间-群山科技工作室个人主页-哔哩哔哩视频 (bilibili.com)

4.数据集的采集&标注和整理

图片的收集

首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集PIGDatasets。

在这里插入图片描述

eiseg是一个图形化的图像注释工具,支持COCO和YOLO格式。以下是使用eiseg将图片标注为COCO格式的步骤:

(1)下载并安装eiseg。
(2)打开eiseg并选择“Open Dir”来选择你的图片目录。
(3)为你的目标对象设置标签名称。
(4)在图片上绘制矩形框,选择对应的标签。
(5)保存标注信息,这将在图片目录下生成一个与图片同名的JSON文件。
(6)重复此过程,直到所有的图片都标注完毕。

由于YOLO使用的是txt格式的标注,我们需要将VOC格式转换为YOLO格式。可以使用各种转换工具或脚本来实现。

下面是一个简单的方法是使用Python脚本,该脚本读取XML文件,然后将其转换为YOLO所需的txt格式。

import contextlib
import jsonimport cv2
import pandas as pd
from PIL import Image
from collections import defaultdictfrom utils import *# Convert INFOLKS JSON file into YOLO-format labels ----------------------------
def convert_infolks_json(name, files, img_path):# Create folderspath = make_dirs()# Import jsondata = []for file in glob.glob(files):with open(file) as f:jdata = json.load(f)jdata['json_file'] = filedata.append(jdata)# Write images and shapesname = path + os.sep + namefile_id, file_name, wh, cat = [], [], [], []for x in tqdm(data, desc='Files and Shapes'):f = glob.glob(img_path + Path(x['json_file']).stem + '.*')[0]file_name.append(f)wh.append(exif_size(Image.open(f)))  # (width, height)cat.extend(a['classTitle'].lower() for a in x['output']['objects'])  # categories# filenamewith open(name + '.txt', 'a') as file:file.write('%s\n' % f)# Write *.names filenames = sorted(np.unique(cat))# names.pop(names.index('Missing product'))  # removewith open(name + '.names', 'a') as file:[file.write('%s\n' % a) for a in names]# Write labels filefor i, x in enumerate(tqdm(data, desc='Annotations')):label_name = Path(file_name[i]).stem + '.txt'with open(path + '/labels/' + label_name, 'a') as file:for a in x['output']['objects']:# if a['classTitle'] == 'Missing product':#    continue  # skipcategory_id = names.index(a['classTitle'].lower())# The INFOLKS bounding box format is [x-min, y-min, x-max, y-max]box = np.array(a['points']['exterior'], dtype=np.float32).ravel()box[[0, 2]] /= wh[i][0]  # normalize x by widthbox[[1, 3]] /= wh[i][1]  # normalize y by heightbox = [box[[0, 2]].mean(), box[[1, 3]].mean(), box[2] - box[0], box[3] - box[1]]  # xywhif (box[2] > 0.) and (box[3] > 0.):  # if w > 0 and h > 0file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box))# Split data into train, test, and validate filessplit_files(name, file_name)write_data_data(name + '.data', nc=len(names))print(f'Done. Output saved to {os.getcwd() + os.sep + path}')# Convert vott JSON file into YOLO-format labels -------------------------------
def convert_vott_json(name, files, img_path):# Create folderspath = make_dirs()name = path + os.sep + name# Import jsondata = []for file in glob.glob(files):with open(file) as f:jdata = json.load(f)jdata['json_file'] = filedata.append(jdata)# Get all categoriesfile_name, wh, cat = [], [], []for i, x in enumerate(tqdm(data, desc='Files and Shapes')):with contextlib.suppress(Exception):cat.extend(a['tags'][0] for a in x['regions'])  # categories# Write *.names filenames = sorted(pd.unique(cat))with open(name + '.names', 'a') as file:[file.write('%s\n' % a) for a in names]# Write labels filen1, n2 = 0, 0missing_images = []for i, x in enumerate(tqdm(data, desc='Annotations')):f = glob.glob(img_path + x['asset']['name'] + '.jpg')if len(f):f = f[0]file_name.append(f)wh = exif_size(Image.open(f))  # (width, height)n1 += 1if (len(f) > 0) and (wh[0] > 0) and (wh[1] > 0):n2 += 1# append filename to listwith open(name + '.txt', 'a') as file:file.write('%s\n' % f)# write labelsfilelabel_name = Path(f).stem + '.txt'with open(path + '/labels/' + label_name, 'a') as file:for a in x['regions']:category_id = names.index(a['tags'][0])# The INFOLKS bounding box format is [x-min, y-min, x-max, y-max]box = a['boundingBox']box = np.array([box['left'], box['top'], box['width'], box['height']]).ravel()box[[0, 2]] /= wh[0]  # normalize x by widthbox[[1, 3]] /= wh[1]  # normalize y by heightbox = [box[0] + box[2] / 2, box[1] + box[3] / 2, box[2], box[3]]  # xywhif (box[2] > 0.) and (box[3] > 0.):  # if w > 0 and h > 0file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box))else:missing_images.append(x['asset']['name'])print('Attempted %g json imports, found %g images, imported %g annotations successfully' % (i, n1, n2))if len(missing_images):print('WARNING, missing images:', missing_images)# Split data into train, test, and validate filessplit_files(name, file_name)print(f'Done. Output saved to {os.getcwd() + os.sep + path}')# Convert ath JSON file into YOLO-format labels --------------------------------
def convert_ath_json(json_dir):  # dir contains json annotations and images# Create foldersdir = make_dirs()  # output directoryjsons = []for dirpath, dirnames, filenames in os.walk(json_dir):jsons.extend(os.path.join(dirpath, filename)for filename in [f for f in filenames if f.lower().endswith('.json')])# Import jsonn1, n2, n3 = 0, 0, 0missing_images, file_name = [], []for json_file in sorted(jsons):with open(json_file) as f:data = json.load(f)# # Get classes# try:#     classes = list(data['_via_attributes']['region']['class']['options'].values())  # classes# except:#     classes = list(data['_via_attributes']['region']['Class']['options'].values())  # classes# # Write *.names file# names = pd.unique(classes)  # preserves sort order# with open(dir + 'data.names', 'w') as f:#     [f.write('%s\n' % a) for a in names]# Write labels filefor x in tqdm(data['_via_img_metadata'].values(), desc=f'Processing {json_file}'):image_file = str(Path(json_file).parent / x['filename'])f = glob.glob(image_file)  # image fileif len(f):f = f[0]file_name.append(f)wh = exif_size(Image.open(f))  # (width, height)n1 += 1  # all imagesif len(f) > 0 and wh[0] > 0 and wh[1] > 0:label_file = dir + 'labels/' + Path(f).stem + '.txt'nlabels = 0try:with open(label_file, 'a') as file:  # write labelsfile# try:#     category_id = int(a['region_attributes']['class'])# except:#     category_id = int(a['region_attributes']['Class'])category_id = 0  # single-classfor a in x['regions']:# bounding box format is [x-min, y-min, x-max, y-max]box = a['shape_attributes']box = np.array([box['x'], box['y'], box['width'], box['height']],dtype=np.float32).ravel()box[[0, 2]] /= wh[0]  # normalize x by widthbox[[1, 3]] /= wh[1]  # normalize y by heightbox = [box[0] + box[2] / 2, box[1] + box[3] / 2, box[2],box[3]]  # xywh (left-top to center x-y)if box[2] > 0. and box[3] > 0.:  # if w > 0 and h > 0file.write('%g %.6f %.6f %.6f %.6f\n' % (category_id, *box))n3 += 1nlabels += 1if nlabels == 0:  # remove non-labelled images from datasetos.system(f'rm {label_file}')# print('no labels for %s' % f)continue  # next file# write imageimg_size = 4096  # resize to maximumimg = cv2.imread(f)  # BGRassert img is not None, 'Image Not Found ' + fr = img_size / max(img.shape)  # size ratioif r < 1:  # downsize if necessaryh, w, _ = img.shapeimg = cv2.resize(img, (int(w * r), int(h * r)), interpolation=cv2.INTER_AREA)ifile = dir + 'images/' + Path(f).nameif cv2.imwrite(ifile, img):  # if success append image to listwith open(dir + 'data.txt', 'a') as file:file.write('%s\n' % ifile)n2 += 1  # correct imagesexcept Exception:os.system(f'rm {label_file}')print(f'problem with {f}')else:missing_images.append(image_file)nm = len(missing_images)  # number missingprint('\nFound %g JSONs with %g labels over %g images. Found %g images, labelled %g images successfully' %(len(jsons), n3, n1, n1 - nm, n2))if len(missing_images):print('WARNING, missing images:', missing_images)# Write *.names filenames = ['knife']  # preserves sort orderwith open(dir + 'data.names', 'w') as f:[f.write('%s\n' % a) for a in names]# Split data into train, test, and validate filessplit_rows_simple(dir + 'data.txt')write_data_data(dir + 'data.data', nc=1)print(f'Done. Output saved to {Path(dir).absolute()}')def convert_coco_json(json_dir='../coco/annotations/', use_segments=False, cls91to80=False):save_dir = make_dirs()  # output directorycoco80 = coco91_to_coco80_class()# Import jsonfor json_file in sorted(Path(json_dir).resolve().glob('*.json')):fn = Path(save_dir) / 'labels' / json_file.stem.replace('instances_', '')  # folder namefn.mkdir()with open(json_file) as f:data = json.load(f)# Create image dictimages = {'%g' % x['id']: x for x in data['images']}# Create image-annotations dictimgToAnns = defaultdict(list)for ann in data['annotations']:imgToAnns[ann['image_id']].append(ann)# Write labels filefor img_id, anns in tqdm(imgToAnns.items(), desc=f'Annotations {json_file}'):img = images['%g' % img_id]h, w, f = img['height'], img['width'], img['file_name']bboxes = []segments = []for ann in anns:if ann['iscrowd']:continue# The COCO box format is [top left x, top left y, width, height]box = np.array(ann['bbox'], dtype=np.float64)box[:2] += box[2:] / 2  # xy top-left corner to centerbox[[0, 2]] /= w  # normalize xbox[[1, 3]] /= h  # normalize yif box[2] <= 0 or box[3] <= 0:  # if w <= 0 and h <= 0continuecls = coco80[ann['category_id'] - 1] if cls91to80 else ann['category_id'] - 1  # classbox = [cls] + box.tolist()if box not in bboxes:bboxes.append(box)# Segmentsif use_segments:if len(ann['segmentation']) > 1:s = merge_multi_segment(ann['segmentation'])s = (np.concatenate(s, axis=0) / np.array([w, h])).reshape(-1).tolist()else:s = [j for i in ann['segmentation'] for j in i]  # all segments concatenateds = (np.array(s).reshape(-1, 2) / np.array([w, h])).reshape(-1).tolist()s = [cls] + sif s not in segments:segments.append(s)# Writewith open((fn / f).with_suffix('.txt'), 'a') as file:for i in range(len(bboxes)):line = *(segments[i] if use_segments else bboxes[i]),  # cls, box or segmentsfile.write(('%g ' * len(line)).rstrip() % line + '\n')def min_index(arr1, arr2):"""Find a pair of indexes with the shortest distance. Args:arr1: (N, 2).arr2: (M, 2).Return:a pair of indexes(tuple)."""dis = ((arr1[:, None, :] - arr2[None, :, :]) ** 2).sum(-1)return np.unravel_index(np.argmin(dis, axis=None), dis.shape)def merge_multi_segment(segments):"""Merge multi segments to one list.Find the coordinates with min distance between each segment,then connect these coordinates with one thin line to merge all segments into one.Args:segments(List(List)): original segmentations in coco's json file.like [segmentation1, segmentation2,...], each segmentation is a list of coordinates."""s = []segments = [np.array(i).reshape(-1, 2) for i in segments]idx_list = [[] for _ in range(len(segments))]# record the indexes with min distance between each segmentfor i in range(1, len(segments)):idx1, idx2 = min_index(segments[i - 1], segments[i])idx_list[i - 1].append(idx1)idx_list[i].append(idx2)# use two round to connect all the segmentsfor k in range(2):# forward connectionif k == 0:for i, idx in enumerate(idx_list):# middle segments have two indexes# reverse the index of middle segmentsif len(idx) == 2 and idx[0] > idx[1]:idx = idx[::-1]segments[i] = segments[i][::-1, :]segments[i] = np.roll(segments[i], -idx[0], axis=0)segments[i] = np.concatenate([segments[i], segments[i][:1]])# deal with the first segment and the last oneif i in [0, len(idx_list) - 1]:s.append(segments[i])else:idx = [0, idx[1] - idx[0]]s.append(segments[i][idx[0]:idx[1] + 1])else:for i in range(len(idx_list) - 1, -1, -1):if i not in [0, len(idx_list) - 1]:idx = idx_list[i]nidx = abs(idx[1] - idx[0])s.append(segments[i][nidx:])return sdef delete_dsstore(path='../datasets'):# Delete apple .DS_store filesfrom pathlib import Pathfiles = list(Path(path).rglob('.DS_store'))print(files)for f in files:f.unlink()if __name__ == '__main__':source = 'COCO'if source == 'COCO':convert_coco_json('./annotations',  # directory with *.jsonuse_segments=True,cls91to80=True)elif source == 'infolks':  # Infolks https://infolks.info/convert_infolks_json(name='out',files='../data/sm4/json/*.json',img_path='../data/sm4/images/')elif source == 'vott':  # VoTT https://github.com/microsoft/VoTTconvert_vott_json(name='data',files='../../Downloads/athena_day/20190715/*.json',img_path='../../Downloads/athena_day/20190715/')  # images folderelif source == 'ath':  # ath formatconvert_ath_json(json_dir='../../Downloads/athena/')  # images folder# zip results# os.system('zip -r ../coco.zip ../coco')
整理数据文件夹结构

我们需要将数据集整理为以下结构:

-----datasets-----coco128-seg|-----images|   |-----train|   |-----valid|   |-----test||-----labels|   |-----train|   |-----valid|   |-----test|
模型训练
 Epoch   gpu_mem       box       obj       cls    labels  img_size1/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.0843Epoch   gpu_mem       box       obj       cls    labels  img_size2/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.0845Epoch   gpu_mem       box       obj       cls    labels  img_size3/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 predict.py
from ultralytics.engine.predictor import BasePredictor
from ultralytics.engine.results import Results
from ultralytics.utils import opsclass DetectionPredictor(BasePredictor):def postprocess(self, preds, img, orig_imgs):preds = ops.non_max_suppression(preds,self.args.conf,self.args.iou,agnostic=self.args.agnostic_nms,max_det=self.args.max_det,classes=self.args.classes)if not isinstance(orig_imgs, list):orig_imgs = ops.convert_torch2numpy_batch(orig_imgs)results = []for i, pred in enumerate(preds):orig_img = orig_imgs[i]pred[:, :4] = ops.scale_boxes(img.shape[2:], pred[:, :4], orig_img.shape)img_path = self.batch[0][i]results.append(Results(orig_img, path=img_path, names=self.model.names, boxes=pred))return results

这段代码定义了一个名为DetectionPredictor的类,继承自BasePredictor类。该类用于基于检测模型进行预测。其中,postprocess方法用于对预测结果进行后处理,并返回一个Results对象的列表。在postprocess方法中,首先对预测结果进行非最大抑制处理,然后根据输入图片的形状对预测框进行缩放,最后将处理后的结果存储在Results对象中,并添加到结果列表中。

该程序文件名为predict.py,主要包含一个名为DetectionPredictor的类,该类继承自BasePredictor类,用于基于检测模型进行预测。

该类中包含一个postprocess方法,用于对预测结果进行后处理,并返回一个Results对象的列表。在postprocess方法中,首先使用ops.non_max_suppression函数对预测结果进行非最大值抑制处理,根据设定的阈值和IOU阈值进行筛选。然后,根据输入的原始图像和预测结果,对预测框进行缩放和转换。最后,将原始图像、图像路径、类别名称和预测框作为参数,创建Results对象,并将其添加到结果列表中。

该文件还包含一个示例代码,用于演示如何使用DetectionPredictor类进行预测。首先导入所需的模块和资源,然后创建DetectionPredictor对象,并调用predict_cli方法进行预测。

总体而言,该程序文件实现了一个基于检测模型的预测器类,提供了预测和后处理的功能,并提供了一个示例代码供参考。

5.2 train.py
# Ultralytics YOLO 🚀, AGPL-3.0 licensefrom copy import copyimport numpy as npfrom ultralytics.data import build_dataloader, build_yolo_dataset
from ultralytics.engine.trainer import BaseTrainer
from ultralytics.models import yolo
from ultralytics.nn.tasks import DetectionModel
from ultralytics.utils import LOGGER, RANK
from ultralytics.utils.torch_utils import de_parallel, torch_distributed_zero_firstclass DetectionTrainer(BaseTrainer):def build_dataset(self, img_path, mode='train', batch=None):gs = max(int(de_parallel(self.model).stride.max() if self.model else 0), 32)return build_yolo_dataset(self.args, img_path, batch, self.data, mode=mode, rect=mode == 'val', stride=gs)def get_dataloader(self, dataset_path, batch_size=16, rank=0, mode='train'):assert mode in ['train', 'val']with torch_distributed_zero_first(rank):dataset = self.build_dataset(dataset_path, mode, batch_size)shuffle = mode == 'train'if getattr(dataset, 'rect', False) and shuffle:LOGGER.warning("WARNING ⚠️ 'rect=True' is incompatible with DataLoader shuffle, setting shuffle=False")shuffle = Falseworkers = 0return build_dataloader(dataset, batch_size, workers, shuffle, rank)def preprocess_batch(self, batch):batch['img'] = batch['img'].to(self.device, non_blocking=True).float() / 255return batchdef set_model_attributes(self):self.model.nc = self.data['nc']self.model.names = self.data['names']self.model.args = self.argsdef get_model(self, cfg=None, weights=None, verbose=True):model = DetectionModel(cfg, nc=self.data['nc'], verbose=verbose and RANK == -1)if weights:model.load(weights)return modeldef get_validator(self):self.loss_names = 'box_loss', 'cls_loss', 'dfl_loss'return yolo.detect.DetectionValidator(self.test_loader, save_dir=self.save_dir, args=copy(self.args))def label_loss_items(self, loss_items=None, prefix='train'):keys = [f'{prefix}/{x}' for x in self.loss_names]if loss_items is not None:loss_items = [round(float(x), 5) for x in loss_items]return dict(zip(keys, loss_items))else:return keysdef progress_string(self):return ('\n' + '%11s' *(4 + len(self.loss_names))) % ('Epoch', 'GPU_mem', *self.loss_names, 'Instances', 'Size')def plot_training_samples(self, batch, ni):plot_images(images=batch['img'],batch_idx=batch['batch_idx'],cls=batch['cls'].squeeze(-1),bboxes=batch['bboxes'],paths=batch['im_file'],fname=self.save_dir / f'train_batch{ni}.jpg',on_plot=self.on_plot)def plot_metrics(self):plot_results(file=self.csv, on_plot=self.on_plot)def plot_training_labels(self):boxes = np.concatenate([lb['bboxes'] for lb in self.train_loader.dataset.labels], 0)cls = np.concatenate([lb['cls'] for lb in self.train_loader.dataset.labels], 0)plot_labels(boxes, cls.squeeze(), names=self.data['names'], save_dir=self.save_dir, on_plot=self.on_plot)

这是一个用于训练基于检测模型的程序文件train.py。它使用了Ultralytics YOLO库,主要包括以下几个部分:

  1. 导入所需的库和模块。
  2. 定义了一个名为DetectionTrainer的类,该类继承自BaseTrainer类,用于基于检测模型进行训练。
  3. 类中包含了一些方法,用于构建数据集、构建数据加载器、预处理批次数据、设置模型属性等。
  4. 类中还包含了一些用于可视化训练样本、绘制训练指标和绘制训练标签的方法。
  5. 在主函数中,创建了一个DetectionTrainer对象,并调用其train方法进行训练。

该程序文件使用了Ultralytics YOLO库提供的功能,可以用于训练和验证YOLO检测模型。

5.3 backbone\EfficientFormerV2.py
import torch
import torch.nn as nn
import math
from typing import Dict
import itertools
import numpy as np
from timm.models.layers import DropPath, trunc_normal_, to_2tupleclass Attention4D(torch.nn.Module):def __init__(self, dim=384, key_dim=32, num_heads=8,attn_ratio=4,resolution=7,act_layer=nn.ReLU,stride=None):super().__init__()self.num_heads = num_headsself.scale = key_dim ** -0.5self.key_dim = key_dimself.nh_kd = nh_kd = key_dim * num_headsif stride is not None:self.resolution = math.ceil(resolution / stride)self.stride_conv = nn.Sequential(nn.Conv2d(dim, dim, kernel_size=3, stride=stride, padding=1, groups=dim),nn.BatchNorm2d(dim), )self.upsample = nn.Upsample(scale_factor=stride, mode='bilinear')else:self.resolution = resolutionself.stride_conv = Noneself.upsample = Noneself.N = self.resolution ** 2self.N2 = self.Nself.d = int(attn_ratio * key_dim)self.dh = int(attn_ratio * key_dim) * num_headsself.attn_ratio = attn_ratioh = self.dh + nh_kd * 2self.q = nn.Sequential(nn.Conv2d(dim, self.num_heads * self.key_dim, 1),nn.BatchNorm2d(self.num_heads * self.key_dim), )self.k = nn.Sequential(nn.Conv2d(dim, self.num_heads * self.key_dim, 1),nn.BatchNorm2d(self.num_heads * self.key_dim), )self.v = nn.Sequential(nn.Conv2d(dim, self.num_heads * self.d, 1),nn.BatchNorm2d(self.num_heads * self.d),)self.v_local = nn.Sequential(nn.Conv2d(self.num_heads * self.d, self.num_heads * self.d,kernel_size=3, stride=1, padding=1, groups=self.num_heads * self.d),nn.BatchNorm2d(self.num_heads * self.d), )self.talking_head1 = nn.Conv2d(self.num_heads, self.num_heads, kernel_size=1, stride=1, padding=0)self.talking_head2 = nn.Conv2d(self.num_heads, self.num_heads, kernel_size=1, stride=1, padding=0)self.proj = nn.Sequential(act_layer(),nn.Conv2d(self.dh, dim, 1),nn.BatchNorm2d(dim), )points = list(itertools.product(range(self.resolution), range(self.resolution)))N = len(points)attention_offsets = {}idxs = []......

EfficientFormerV2.py是一个用于图像分类的模型文件。该文件定义了EfficientFormerV2模型的结构和各个组件的实现。

EfficientFormerV2模型是一个基于Transformer的图像分类模型,它使用了多层的Attention机制来提取图像特征。模型的输入是一张图像,经过一系列的卷积和注意力操作后,输出图像的特征表示。最后,通过全局平均池化和全连接层将特征映射到类别空间,得到最终的分类结果。

EfficientFormerV2模型的主要组件包括:

  • Attention4D:四维注意力模块,用于提取图像特征。
  • stem:模型的初始卷积层,用于将输入图像转换为特征图。
  • LGQuery:局部和全局查询模块,用于生成查询向量。
  • Attention4DDownsample:下采样的四维注意力模块,用于降低特征图的分辨率。
  • Embedding:特征嵌入模块,用于将输入图像转换为特征表示。

EfficientFormerV2模型提供了多个不同规模的预训练模型,包括efficientformerv2_s0、efficientformerv2_s1、efficientformerv2_s2和efficientformerv2_l。每个模型的输入图像尺寸和特征通道数不同,可以根据具体任务的需求选择合适的模型。

EfficientFormerV2模型的参数可以通过EfficientFormer_width和EfficientFormer_depth字典来配置,以控制模型的宽度和深度。模型的宽度和深度越大,模型的容量和表达能力越强,但计算和存储开销也会增加。

总的来说,EfficientFormerV2.py是一个用于图像分类的模型文件,定义了EfficientFormerV2模型的结构和各个组件的实现,提供了多个不同规模的预训练模型,可以根据具体任务的需求选择合适的模型。

5.4 backbone\efficientViT.py
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.utils.checkpoint as checkpoint
import itertoolsfrom timm.models.layers import SqueezeExciteimport numpy as np
import itertoolsclass EfficientViT_M0(nn.Module):def __init__(self, num_classes=1000, img_size=224, patch_size=16, in_chans=3, embed_dim=768, depth=12, num_heads=12, mlp_ratio=4.0, qkv_bias=False, drop_rate=0.0, attn_drop_rate=0.0, drop_path_rate=0.0):super(EfficientViT_M0, self).__init__()self.num_classes = num_classesself.num_features = self.embed_dim = embed_dimself.patch_embed = PatchEmbed(img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim)num_patches = self.patch_embed.num_patchesself.pos_embed = nn.Parameter(torch.zeros(1, num_patches + 1, embed_dim))self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))self.pos_drop = nn.Dropout(p=drop_rate)dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)]self.blocks = nn.ModuleList([Block(dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qkv_bias=qkv_bias, drop=drop_rate, attn_drop=attn_drop_rate, drop_path=dpr[i])for i in range(depth)])self.norm = nn.LayerNorm(embed_dim)self.head = nn.Linear(embed_dim, num_classes) if num_classes > 0 else nn.Identity()trunc_normal_(self.pos_embed, std=.02)trunc_normal_(self.cls_token, std=.02)self.apply(self._init_weights)def _init_weights(self, m):if isinstance(m, nn.Linear):trunc_normal_(m.weight, std=.02)if isinstance(m, nn.Linear) and m.bias is not None:nn.init.constant_(m.bias, 0)elif isinstance(m, nn.LayerNorm):nn.init.constant_(m.bias, 0)nn.init.constant_(m.weight, 1.0)def forward_features(self, x):B = x.shape[0]x = self.patch_embed(x)cls_tokens = self.cls_token.expand(B, -1, -1)x = torch.cat((cls_tokens, x), dim=1)x = x + self.pos_embedx = self.pos_drop(x)for blk in self.blocks:x = blk(x)x = self.norm(x)return x[:, 0]def forward(self, x):x = self.forward_features(x)x = self.head(x)return x

该程序文件是一个EfficientViT模型的架构,用于下游任务。它定义了一系列的模块和函数,包括Conv2d_BN、replace_batchnorm、PatchMerging、Residual、FFN、CascadedGroupAttention和LocalWindowAttention等。

Conv2d_BN是一个包含卷积和批归一化的序列模块。

replace_batchnorm函数用于替换模型中的批归一化层。

PatchMerging模块用于将输入的图像分块合并。

Residual模块实现了残差连接。

FFN模块是一个前馈神经网络。

CascadedGroupAttention模块实现了级联的组注意力机制。

LocalWindowAttention模块实现了局部窗口注意力机制。

这些模块和函数组合在一起构成了EfficientViT模型的架构。

5.5 backbone\fasternet.py
import torch
import torch.nn as nn
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
from functools import partial
from typing import List
from torch import Tensor
import copy
import os
import numpy as npclass Partial_conv3(nn.Module):def __init__(self, dim, n_div, forward):super().__init__()self.dim_conv3 = dim // n_divself.dim_untouched = dim - self.dim_conv3self.partial_conv3 = nn.Conv2d(self.dim_conv3, self.dim_conv3, 3, 1, 1, bias=False)if forward == 'slicing':self.forward = self.forward_slicingelif forward == 'split_cat':self.forward = self.forward_split_catelse:raise NotImplementedErrordef forward_slicing(self, x: Tensor) -> Tensor:# only for inferencex = x.clone()   # !!! Keep the original input intact for the residual connection laterx[:, :self.dim_conv3, :, :] = self.partial_conv3(x[:, :self.dim_conv3, :, :])return xdef forward_split_cat(self, x: Tensor) -> Tensor:# for training/inferencex1, x2 = torch.split(x, [self.dim_conv3, self.dim_untouched], dim=1)x1 = self.partial_conv3(x1)x = torch.cat((x1, x2), 1)return xclass MLPBlock(nn.Module):def __init__(self,dim,n_div,mlp_ratio,drop_path,layer_scale_init_value,act_layer,norm_layer,pconv_fw_type):super().__init__()self.dim = dimself.mlp_ratio = mlp_ratioself.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()self.n_div = n_divmlp_hidden_dim = int(dim * mlp_ratio)mlp_layer: List[nn.Module] = [nn.Conv2d(dim, mlp_hidden_dim, 1, bias=False),norm_layer(mlp_hidden_dim),act_layer(),nn.Conv2d(mlp_hidden_dim, dim, 1, bias=False)]self.mlp = nn.Sequential(*mlp_layer)self.spatial_mixing = Partial_conv3(dim,n_div,pconv_fw_type)if layer_scale_init_value > 0:self.layer_scale = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)self.forward = self.forward_layer_scaleelse:self.forward = self.forwarddef forward(self, x: Tensor) -> Tensor:shortcut = xx = self.spatial_mixing(x)x = shortcut + self.drop_path(self.mlp(x))return xdef forward_layer_scale(self, x: Tensor) -> Tensor:shortcut = xx = self.spatial_mixing(x)x = shortcut + self.drop_path(self.layer_scale.unsqueeze(-1).unsqueeze(-1) * self.mlp(x))return xclass BasicStage(nn.Module):def __init__(self,dim,depth,n_div,mlp_ratio,drop_path,layer_scale_init_value,norm_layer,act_layer,pconv_fw_type):super().__init__()blocks_list = [MLPBlock(dim=dim,n_div=n_div,mlp_ratio=mlp_ratio,drop_path=drop_path[i],layer_scale_init_value=layer_scale_init_value,norm_layer=norm_layer,act_layer=act_layer,pconv_fw_type=pconv_fw_type)for i in range(depth)]self.blocks = nn.Sequential(*blocks_list)def forward(self, x: Tensor) -> Tensor:x = self.blocks(x)return xclass PatchEmbed(nn.Module):def __init__(self, patch_size, patch_stride, in_chans, embed_dim, norm_layer):super().__init__()self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_stride, bias=False)if norm_layer is not None:self.norm = norm_layer(embed_dim)else:self.norm = nn.Identity()def forward(self, x: Tensor) -> Tensor:x = self.norm(self.proj(x))return xclass PatchMerging(nn.Module):def __init__(self, patch_size2, patch_stride2, dim, norm_layer):super().__init__()self.reduction = nn.Conv2d(dim, 2 * dim, kernel_size=patch_size2, stride=patch_stride2, bias=False)if norm_layer is not None:self.norm = norm_layer(2 * dim)else:self.norm = nn.Identity()def forward(self, x: Tensor) -> Tensor:x = self.norm(self.reduction(x))return xclass FasterNet(nn.Module):def __init__(self,in_chans=3,num_classes=1000,embed_dim=96,depths=(1, 2, 8, 2),mlp_ratio=2.,n_div=4,patch_size=4,patch_stride=4,patch_size2=2,  # for subsequent layerspatch_stride2=2,patch_norm=True,feature_dim=1280,drop_path_rate=0.1,layer_scale_init_value=0,norm_layer='BN',act_layer='RELU',init_cfg=None,pretrained=None,pconv_fw_type='split_cat',**kwargs):super().__init__()if norm_layer == 'BN':norm_layer = nn.BatchNorm2delse:raise NotImplementedErrorif act_layer == 'GELU':act_layer = nn.GELUelif act_layer == 'RELU':act_layer = partial(nn.ReLU, inplace=True)else:raise NotImplementedErrorself.num_stages = len(depths)self.embed_dim = embed_dim

该程序文件是一个名为fasternet.py的Python程序文件,用于定义了一个名为FasterNet的神经网络模型。该模型是基于PyTorch框架实现的,用于图像分类任务。

该程序文件中定义了多个类和函数,包括Partial_conv3MLPBlockBasicStagePatchEmbedPatchMergingFasterNet等类,以及update_weightfasternet_t0fasternet_t1fasternet_t2fasternet_sfasternet_mfasternet_l等函数。

FasterNet类是该程序文件的核心部分,它继承自nn.Module类,并定义了神经网络模型的结构和前向传播方法。该模型包括多个阶段(stage),每个阶段由多个MLP块(MLPBlock)组成。每个MLP块包括一个部分卷积层(Partial_conv3)和一个MLP层(MLP),用于提取特征和进行特征变换。模型还包括图像分割和特征融合的操作,以及一些其他辅助函数。

update_weight函数用于更新模型的权重,将预训练的权重加载到模型中。

fasternet_t0fasternet_t1fasternet_t2fasternet_sfasternet_mfasternet_l函数分别用于创建不同配置的FasterNet模型,并加载预训练的权重。

if __name__ == '__main__':部分,程序创建了一个FasterNet模型,并打印了模型的通道数。然后,程序创建了一个输入张量,并对模型进行前向传播,打印了每个阶段输出的张量大小。

总体来说,该程序文件定义了一个用于图像分类任务的FasterNet神经网络模型,并提供了加载预训练权重的函数。

5.6 backbone\lsknet.py
import torch
import torch.nn as nn
from torch.nn.modules.utils import _pair as to_2tuple
from timm.layers import DropPath, to_2tuple
from functools import partial
import numpy as npclass Mlp(nn.Module):def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):super().__init__()out_features = out_features or in_featureshidden_features = hidden_features or in_featuresself.fc1 = nn.Conv2d(in_features, hidden_features, 1)self.dwconv = DWConv(hidden_features)self.act = act_layer()self.fc2 = nn.Conv2d(hidden_features, out_features, 1)self.drop = nn.Dropout(drop)def forward(self, x):x = self.fc1(x)x = self.dwconv(x)x = self.act(x)x = self.drop(x)x = self.fc2(x)x = self.drop(x)return xclass LSKblock(nn.Module):def __init__(self, dim):super().__init__()self.conv0 = nn.Conv2d(dim, dim, 5, padding=2, groups=dim)self.conv_spatial = nn.Conv2d(dim, dim, 7, stride=1, padding=9, groups=dim, dilation=3)self.conv1 = nn.Conv2d(dim, dim//2, 1)self.conv2 = nn.Conv2d(dim, dim//2, 1)self.conv_squeeze = nn.Conv2d(2, 2, 7, padding=3)self.conv = nn.Conv2d(dim//2, dim, 1)def forward(self, x):   attn1 = self.conv0(x)attn2 = self.conv_spatial(attn1)attn1 = self.conv1(attn1)attn2 = self.conv2(attn2)attn = torch.cat([attn1, attn2], dim=1)avg_attn = torch.mean(attn, dim=1, keepdim=True)max_attn, _ = torch.max(attn, dim=1, keepdim=True)agg = torch.cat([avg_attn, max_attn], dim=1)sig = self.conv_squeeze(agg).sigmoid()attn = attn1 * sig[:,0,:,:].unsqueeze(1) + attn2 * sig[:,1,:,:].unsqueeze(1)attn = self.conv(attn)return x * attnclass Attention(nn.Module):def __init__(self, d_model):super().__init__()self.proj_1 = nn.Conv2d(d_model, d_model, 1)self.activation = nn.GELU()self.spatial_gating_unit = LSKblock(d_model)self.proj_2 = nn.Conv2d(d_model, d_model, 1)def forward(self, x):shorcut = x.clone()x = self.proj_1(x)x = self.activation(x)x = self.spatial_gating_unit(x)x = self.proj_2(x)x = x + shorcutreturn xclass Block(nn.Module):def __init__(self, dim, mlp_ratio=4., drop=0.,drop_path=0., act_layer=nn.GELU, norm_cfg=None):super().__init__()self.norm1 = nn.BatchNorm2d(dim)self.norm2 = nn.BatchNorm2d(dim)self.attn = Attention(dim)self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()mlp_hidden_dim = int(dim * mlp_ratio)self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)layer_scale_init_value = 1e-2            self.layer_scale_1 = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)self.layer_scale_2 = nn.Parameter(layer_scale_init_value * torch.ones((dim)), requires_grad=True)def forward(self, x):x = x + self.drop_path(self.layer_scale_1.unsqueeze(-1).unsqueeze(-1) * self.attn(self.norm1(x)))x = x + self.drop_path(self.layer_scale_2.unsqueeze(-1).unsqueeze(-1) * self.mlp(self.norm2(x)))return xclass OverlapPatchEmbed(nn.Module):""" Image to Patch Embedding"""def __init__(self, img_size=224, patch_size=7, stride=4, in_chans=3, embed_dim=768, norm_cfg=None):super().__init__()patch_size = to_2tuple(patch_size)self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=stride,padding=(patch_size[0] // 2, patch_size[1] // 2))self.norm = nn.BatchNorm2d(embed_dim)def forward(self, x):x = self.proj(x)_, _, H, W = x.shapex = self.norm(x)        return x, H, Wclass LSKNet(nn.Module):def __init__(self, img_size=224, in_chans=3, embed_dims=[64, 128, 256, 512],mlp_ratios=[8, 8, 4, 4], drop_rate=0., drop_path_rate=0., norm_layer=partial(nn.LayerNorm, eps=1e-6),depths=[3, 4, 6, 3], num_stages=4, norm_cfg=None):super().__init__()self.depths = depthsself.num_stages = num_stagesdpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]  # stochastic depth decay rulecur = 0for i in range(num_stages):patch_embed = OverlapPatchEmbed(img_size=img_size if i == 0 else img_size // (2 ** (i + 1)),patch_size=7 if i == 0 else 3,stride=4 if i == 0 else 2,in_chans=in_chans if i == 0 else embed_dims[i - 1],embed_dim=embed_dims[i], norm_cfg=norm_cfg)block = nn.ModuleList([Block(dim=embed_dims[i], mlp_ratio=mlp_ratios[i], drop=drop_rate, drop_path=dpr[cur + j],norm_cfg=norm_cfg)for j in range(depths[i])])norm = norm_layer(embed_dims[i])cur += depths[i]setattr(self, f"patch_embed{i + 1}", patch_embed)setattr(self, f"block{i + 1}", block)setattr(self, f"norm{i + 1}", norm)self.channel = [i.size(1) for i in self.forward(torch.randn(1, 3, 640, 640))]def forward(self, x):B = x.shape[0]outs = []for i in range(self.num_stages):patch_embed = getattr(self, f"patch_embed{i + 1}")block = getattr(self, f"block{i + 1}")norm = getattr(self, f"norm{i + 1}")x, H, W = patch_embed(x)for blk in block:x = blk(x)x = x.flatten(2).transpose(1, 2)x = norm(x)x = x.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous()outs.append(x)return outsclass DWConv(nn.Module):def __init__(self, dim=768):super(DWConv, self).__init__()self.dwconv = nn.Conv2d(dim, dim, 3, 1, 1, bias=True, groups=dim)def forward(self, x):x = self.dwconv(x)return x

该程序文件是一个实现LSKNet模型的Python代码。LSKNet是一个用于图像分类任务的卷积神经网络模型。该文件包含了LSKNet模型的定义以及一些辅助函数。

主要包含以下几个类和函数:

  1. Mlp类:多层感知机模块,包含了两个卷积层和一个激活函数层。

  2. LSKblock类:LSK模块,包含了多个卷积层和特殊的注意力机制。

  3. Attention类:注意力机制模块,包含了两个卷积层和LSK模块。

  4. Block类:LSKNet模型的基本模块,包含了注意力机制和多层感知机模块。

  5. OverlapPatchEmbed类:将图像转换为补丁表示的模块。

  6. LSKNet类:LSKNet模型的定义,包含了多个Block模块和PatchEmbed模块。

  7. DWConv类:深度可分离卷积模块。

  8. update_weight函数:用于加载预训练权重。

  9. lsknet_t函数:创建一个LSKNet模型实例,用于训练。

  10. lsknet_s函数:创建一个LSKNet模型实例,用于训练。

  11. main函数:加载预训练权重并对输入数据进行前向传播。

LSKNet模型由多个Block模块组成,每个Block模块包含了注意力机制和多层感知机模块。PatchEmbed模块将输入图像转换为补丁表示,然后通过多个Block模块进行特征提取和分类。最后输出多个尺度的特征图。

LSKNet模型有两个版本,分别是lsknet_t和lsknet_s,它们的网络结构和参数设置略有不同。可以通过调用lsknet_t和lsknet_s函数来创建对应版本的LSKNet模型实例,并可以选择加载预训练权重。

6.系统整体结构

以下是每个文件的功能总结和整理:

文件路径功能
predict.py实现了预测器类和预测函数,用于模型的预测操作
train.py实现了训练器类和训练函数,用于模型的训练操作
backbone\EfficientFormerV2.py定义了EfficientFormerV2模型的结构和组件实现
backbone\efficientViT.py定义了efficientViT模型的结构和组件实现
backbone\fasternet.py定义了Fasternet模型的结构和组件实现
backbone\lsknet.py定义了LSKNet模型的结构和组件实现
backbone\repvit.py定义了RepVIT模型的结构和组件实现
backbone\revcol.py定义了RevCol模型的结构和组件实现
backbone\SwinTransformer.py定义了SwinTransformer模型的结构和组件实现
backbone\VanillaNet.py定义了VanillaNet模型的结构和组件实现
extra_modules\block.py定义了一些常用的模块和函数,如注意力机制、残差模块等
extra_modules\dynamic_snake_conv.py定义了动态蛇形卷积模块的实现
extra_modules\head.py定义了模型的头部结构,用于输出预测结果
extra_modules\kernel_warehouse.py定义了一些卷积核的存储和管理功能
extra_modules\orepa.py定义了一种特

7.YOLOv8简介

Backbone

借鉴了其他算法的这些设计思想

借鉴了VGG的思想,使用了较多的3×3卷积,在每一次池化操作后,将通道数翻倍;

借鉴了network in network的思想,使用全局平均池化(global average pooling)做预测,并把1×1的卷积核置于3×3的卷积核之间,用来压缩特征;(我没找到这一步体现在哪里)

使用了批归一化层稳定模型训练,加速收敛,并且起到正则化作用。

以上三点为Darknet19借鉴其他模型的点。Darknet53当然是在继承了Darknet19的这些优点的基础上再新增了下面这些优点的。因此列在了这里

借鉴了ResNet的思想,在网络中大量使用了残差连接,因此网络结构可以设计的很深,并且缓解了训练中梯度消失的问题,使得模型更容易收敛。

使用步长为2的卷积层代替池化层实现降采样。(这一点在经典的Darknet-53上是很明显的,output的长和宽从256降到128,再降低到64,一路降低到8,应该是通过步长为2的卷积层实现的;在YOLOv8的卷积层中也有体现,比如图中我标出的这些位置)

特征融合

模型架构图如下

Darknet-53的特点可以这样概括:(Conv卷积模块+Residual Block残差块)串行叠加4次

Conv卷积层+Residual Block残差网络就被称为一个stage

在这里插入图片描述

上面红色指出的那个,原始的Darknet-53里面有一层 卷积,在YOLOv8里面,把一层卷积移除了

为什么移除呢?

    原始Darknet-53模型中间加的这个卷积层做了什么?滤波器(卷积核)的个数从 上一个卷积层的512个,先增加到1024个卷积核,然后下一层卷积的卷积核的个数又降低到512个移除掉这一层以后,少了1024个卷积核,就可以少做1024次卷积运算,同时也少了1024个3×3的卷积核的参数,也就是少了9×1024个参数需要拟合。这样可以大大减少了模型的参数,(相当于做了轻量化吧)移除掉这个卷积层,可能是因为作者发现移除掉这个卷积层以后,模型的score有所提升,所以才移除掉的。为什么移除掉以后,分数有所提高呢?可能是因为多了这些参数就容易,参数过多导致模型在训练集删过拟合,但是在测试集上表现很差,最终模型的分数比较低。你移除掉这个卷积层以后,参数减少了,过拟合现象不那么严重了,泛化能力增强了。当然这个是,拿着你做实验的结论,反过来再找补,再去强行解释这种现象的合理性。

过拟合
在这里插入图片描述

通过MMdetection官方绘制册这个图我们可以看到,进来的这张图片经过一个“Feature Pyramid Network(简称FPN)”,然后最后的P3、P4、P5传递给下一层的Neck和Head去做识别任务。 PAN(Path Aggregation Network)

在这里插入图片描述

“FPN是自顶向下,将高层的强语义特征传递下来。PAN就是在FPN的后面添加一个自底向上的金字塔,对FPN补充,将低层的强定位特征传递上去,

FPN是自顶(小尺寸,卷积次数多得到的结果,语义信息丰富)向下(大尺寸,卷积次数少得到的结果),将高层的强语义特征传递下来,对整个金字塔进行增强,不过只增强了语义信息,对定位信息没有传递。PAN就是针对这一点,在FPN的后面添加一个自底(卷积次数少,大尺寸)向上(卷积次数多,小尺寸,语义信息丰富)的金字塔,对FPN补充,将低层的强定位特征传递上去,又被称之为“双塔战术”。

FPN层自顶向下传达强语义特征,而特征金字塔则自底向上传达强定位特征,两两联手,从不同的主干层对不同的检测层进行参数聚合,这样的操作确实很皮。

自底向上增强

而 PAN(Path Aggregation Network)是对 FPN 的一种改进,它的设计理念是在 FPN 后面添加一个自底向上的金字塔。PAN 引入了路径聚合的方式,通过将浅层特征图(低分辨率但语义信息较弱)和深层特征图(高分辨率但语义信息丰富)进行聚合,并沿着特定的路径传递特征信息,将低层的强定位特征传递上去。这样的操作能够进一步增强多尺度特征的表达能力,使得 PAN 在目标检测任务中表现更加优秀。

8.可重参化EfficientRepBiPAN优化Neck

Repvgg-style

Repvgg-style的卷积层包含
卷积+ReLU结构,该结构能够有效地利用硬件资源。

在训练时,Repvgg-style的卷积层包含
卷积、
卷积、identity。(下图左图)
在这里插入图片描述

在推理时,通过重参数化(re-parameterization),上述的多分支结构可以转换为单分支的
卷积。(下图右图)

基于上述思想,作者设计了对GPU硬件友好的EfficientRep Backbone和Rep-PAN Neck,将它们用于YOLOv6中。

EfficientRep Backbone的结构图:
在这里插入图片描述

Rep-PAN Neck结构图:
在这里插入图片描述

Multi-path

只使用repvgg-style不能达到很好的精度-速度平衡,对于大模型,作者探索了多路径的网络结构。

参考该博客提出了Bep unit,其结构如下图所示:
在这里插入图片描述

CSP(Cross Stage Partial)-style计算量小,且有丰富的梯度融合信息,广泛应用于YOLO系列中,比如YOLOv5、PPYOLOE。

作者将Bep unit与CSP-style结合,设计了一种新的网络结构BepC3,如下图所示:
在这里插入图片描述

基于BepC3模块,作者设计了新的CSPBep Backbone和CSPRepPAN Neck,以达到很好的精度-速度平衡。

其他YOLO系列在使用CSP-stype结构时,partial ratio设置为1/2。为了达到更好的性能,在YOLOv6m中partial ratio的值为2/3,在YOLOv6l中partial ratio的值为1/2。

对于YOLOv6m,单纯使用Rep-style结构和使用BepC3结构的对比如下图所示:

BIFPN

BiFPN 全称 Bidirectional Feature Pyramid Network 加权双向(自顶向下 + 自低向上)特征金字塔网络。

相比较于PANet,BiFPN在设计上的改变:

总结下图:
图d 蓝色部分为自顶向下的通路,传递的是高层特征的语义信息;红色部分是自底向上的通路,传递的是低层特征的位置信息;紫色部分是上述第二点提到的同一层在输入节点和输入节点间新加的一条边。

在这里插入图片描述

我们删除那些只有一条输入边的节点。这么做的思路很简单:如果一个节点只有一条输入边而没有特征融合,那么它对旨在融合不同特征的特征网络的贡献就会很小。删除它对我们的网络影响不大,同时简化了双向网络;如上图d 的 P7右边第一个节点

如果原始输入节点和输出节点处于同一层,我们会在原始输入节点和输出节点之间添加一条额外的边。思路:以在不增加太多成本的情况下融合更多的特性;

与只有一个自顶向下和一个自底向上路径的PANet不同,我们处理每个双向路径(自顶向下和自底而上)路径作为一个特征网络层,并重复同一层多次,以实现更高层次的特征融合。如下图EfficientNet 的网络结构所示,我们对BiFPN是重复使用多次的。而这个使用次数也不是我们认为设定的,而是作为参数一起加入网络的设计当中,使用NAS技术算出来的。

Weighted Feature Fusion 带权特征融合:学习不同输入特征的重要性,对不同输入特征有区分的融合。
设计思路:传统的特征融合往往只是简单的 feature map 叠加/相加 (sum them up),比如使用concat或者shortcut连接,而不对同时加进来的 feature map 进行区分。然而,不同的输入 feature map 具有不同的分辨率,它们对融合输入 feature map 的贡献也是不同的,因此简单的对他们进行相加或叠加处理并不是最佳的操作。所以这里我们提出了一种简单而高效的加权特融合的机制。
常见的带权特征融合有三种方法,分别是:

在这里插入图片描述


这种方法比较简单,直接加一个可学习的权重。但是由于这个权重不受限制,所有可能引起训练的不稳定,所有并不推荐。
Softmax-based fusion: O = ∑ i e w i ∗ I i ϵ + ∑ j e w j O = \sum_{i} \frac{e^{w_i} * I_i}{ \epsilon+\sum_{j}e^{w_j}}O=∑

在这里插入图片描述


使用这种方法可以将范围放缩到 [ 0 , 1 ] [0, 1][0,1] 之间,训练稳定,但是训练很慢,所有也不推荐。
Fast normalized fusion: O = ∑ i w i ∗ I i ϵ + ∑ j w j O = \sum_{i} \frac{w_i * I_i}{ \epsilon+\sum_{j}w_j}O=∑

9.训练结果可视化分析

评价指标

Epochs:实验在多个 epoch 中进行,显示训练过程的进度。

训练损失指标:训练损失指标有多种,包括框损失(train/box_loss)、分段损失(train/seg_loss)、对象损失(train/obj_loss)和类损失(train/cls_loss)。

评估指标:数据包括品种 (B) 和肉类 (M) 在不同 IoU 阈值下的精度、召回率和平均精度 (mAP)。这些指标对于评估模型在猪品种和肉类类型分类和本地化方面的性能至关重要。

验证损失指标:与训练损失类似,也有框损失 (val/box_loss)、分段损失 (< a i=3>)、对象损失 () 和类损失 ()。val/seg_lossval/obj_lossval/cls_loss

学习率:数据集还跟踪三种不同的学习率(x/lr0、x/lr1、x/lr2),这可能是训练期间自适应学习率策略的一部分。

训练结果可视化

为了进行详细分析,我将可视化这些指标,以了解训练和验证损失趋势、模型准确性和学习率变化。让我们首先绘制各个时期的训练和验证损失趋势。

import matplotlib.pyplot as plt# Plotting Training and Validation Losses
plt.figure(figsize=(15, 10))# Training Losses
plt.subplot(2, 2, 1)
plt.plot(data['epoch'], data['train/box_loss'], label='Box Loss')
plt.plot(data['epoch'], data['train/seg_loss'], label='Segmentation 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 per Epoch')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()# Validation Losses
plt.subplot(2, 2, 2)
plt.plot(data['epoch'], data['val/box_loss'], label='Box Loss')
plt.plot(data['epoch'], data['val/seg_loss'], label='Segmentation 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 per Epoch')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()# Show the plot
plt.tight_layout()
plt.show()

在这里插入图片描述
现在我们已经绘制了训练和验证损失的趋势图。接下来,我将对模型的精度和召回率进行可视化分析,这将有助于更深入地了解模型在分类和定位生猪品种和肉类方面的表现。然后,我还会分析学习率的变化情况。让我们继续进行这些分析。
在这里插入图片描述
通过上面的图表,我们可以看到生猪品种和肉类的精度、召回率和平均精度(mAP)随着训练周期的变化情况。

接下来,我将分析学习率的变化。这一点很重要,因为学习率的调整通常会影响模型的训练效率和最终性能。让我们绘制学习率随着训练周期的变化图。
在这里插入图片描述
学习率随训练周期的变化图已经展示出来了。从图中可以看出,学习率在整个训练过程中的调整情况。

现在,让我们从多个角度对这些数据进行详细分析:

训练和验证损失:

训练损失(包括盒损失、分割损失、对象损失和类别损失)随着训练周期的增加而逐渐下降,这表明模型正在有效学习。
验证损失也呈现类似的下降趋势,这表明模型在未见数据上的泛化能力良好。

精度和召回率:

(B)和(M)的精度和召回率都随着训练周期的增加而提高,说明模型在正确分类和定位方面表现越来越好。
特别是,精度(precision)指标表明模型在预测正类时的准确性很高,而召回率(recall)的提高表明模型越来越少地错过正类。

平均精度(mAP):

mAP指标在不同的交并比(IoU)阈值下的表现也提高,这进一步证实了模型在检测和分类方面的有效性。
mAP@0.5和mAP@0.5:0.95两个指标的提高表明模型在不同严格度的评价标准下都有良好的表现。

学习率:

学习率的变化显示了模型训练过程中学习率调整策略的实施情况。
学习率的适时调整对于避免过拟合、加快收敛速度和提高模型性能至关重要。

10.系统整合

下图完整源码&数据集&环境部署视频教程&自定义UI界面

在这里插入图片描述

参考博客《【改进YOLOv8】生猪胖瘦评价分级系统:可重参化EfficientRepBiPAN优化Neck》

11.参考文献


[1]李承明,张斌,夏雨阳,等.胶园植保硫磺粉颗粒离散元模型参数标定[J].江西农业大学学报.2022,44(3).DOI:10.13836/j.jjau.2022061 .

[2]贾乐心,江应星,孙佳,等.基于EDEM的圆盘弹齿组合式水田平地机圆盘转辊仿真与试验[J].江西农业大学学报.2021,43(6).DOI:10.13836/j.jjau.2021150 .

[3]韩树杰,戚江涛,坎杂,等.新疆果园深施散体厩肥离散元参数标定研究[J].农业机械学报.2021,(4).DOI:10.6041/j.issn.1000-1298.2021.04.010 .

[4]邢洁洁,张锐,吴鹏,等.海南热区砖红壤颗粒离散元仿真模型参数标定[J].农业工程学报.2020,(5).DOI:10.11975/j.issn.1002-6819.2020.05.018 .

[5]彭才望,许道军,贺喜,等.黑水虻处理的猪粪有机肥离散元仿真模型参数标定[J].农业工程学报.2020,(17).DOI:10.11975/j.issn.1002-6819.2020.17.025 .

[6]王黎明,范盛远,程红胜,等.基于EDEM的猪粪接触参数标定[J].农业工程学报.2020,(15).DOI:10.11975/j.issn.1002-6819.2020.15.012 .

[7]张珂,于天行,于文达,等.基于JKR黏结模型的混凝土离散元参数标定[J].混凝土.2020,(8).DOI:10.3969/j.issn.1002-3550.2020.08.011 .

[8]向伟,吴明亮,吕江南,等.基于堆积试验的黏壤土仿真物理参数标定[J].农业工程学报.2019,(12).DOI:10.11975/j.issn.1002-6819.2019.12.014 .

[9]林嘉聪,罗帅,袁巧霞,等.不同含水率蚯蚓粪颗粒物料流动性研究[J].农业工程学报.2019,(9).DOI:10.11975/j.issn.1002-6819.2019.09.027 .

[10]李永祥,李飞翔,徐雪萌,等.基于颗粒缩放的小麦粉离散元参数标定[J].农业工程学报.2019,(16).DOI:10.11975/j.issn.1002-6819.2019.16.035 .

这篇关于【改进YOLOv8】生猪胖瘦评价分级系统:可重参化EfficientRepBiPAN优化Neck的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/504083

相关文章

Vue3 的 shallowRef 和 shallowReactive:优化性能

大家对 Vue3 的 ref 和 reactive 都很熟悉,那么对 shallowRef 和 shallowReactive 是否了解呢? 在编程和数据结构中,“shallow”(浅层)通常指对数据结构的最外层进行操作,而不递归地处理其内部或嵌套的数据。这种处理方式关注的是数据结构的第一层属性或元素,而忽略更深层次的嵌套内容。 1. 浅层与深层的对比 1.1 浅层(Shallow) 定义

不懂推荐算法也能设计推荐系统

本文以商业化应用推荐为例,告诉我们不懂推荐算法的产品,也能从产品侧出发, 设计出一款不错的推荐系统。 相信很多新手产品,看到算法二字,多是懵圈的。 什么排序算法、最短路径等都是相对传统的算法(注:传统是指科班出身的产品都会接触过)。但对于推荐算法,多数产品对着网上搜到的资源,都会无从下手。特别当某些推荐算法 和 “AI”扯上关系后,更是加大了理解的难度。 但,不了解推荐算法,就无法做推荐系

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

HDFS—存储优化(纠删码)

纠删码原理 HDFS 默认情况下,一个文件有3个副本,这样提高了数据的可靠性,但也带来了2倍的冗余开销。 Hadoop3.x 引入了纠删码,采用计算的方式,可以节省约50%左右的存储空间。 此种方式节约了空间,但是会增加 cpu 的计算。 纠删码策略是给具体一个路径设置。所有往此路径下存储的文件,都会执行此策略。 默认只开启对 RS-6-3-1024k

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

嵌入式QT开发:构建高效智能的嵌入式系统

摘要: 本文深入探讨了嵌入式 QT 相关的各个方面。从 QT 框架的基础架构和核心概念出发,详细阐述了其在嵌入式环境中的优势与特点。文中分析了嵌入式 QT 的开发环境搭建过程,包括交叉编译工具链的配置等关键步骤。进一步探讨了嵌入式 QT 的界面设计与开发,涵盖了从基本控件的使用到复杂界面布局的构建。同时也深入研究了信号与槽机制在嵌入式系统中的应用,以及嵌入式 QT 与硬件设备的交互,包括输入输出设

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

【区块链 + 人才服务】可信教育区块链治理系统 | FISCO BCOS应用案例

伴随着区块链技术的不断完善,其在教育信息化中的应用也在持续发展。利用区块链数据共识、不可篡改的特性, 将与教育相关的数据要素在区块链上进行存证确权,在确保数据可信的前提下,促进教育的公平、透明、开放,为教育教学质量提升赋能,实现教育数据的安全共享、高等教育体系的智慧治理。 可信教育区块链治理系统的顶层治理架构由教育部、高校、企业、学生等多方角色共同参与建设、维护,支撑教育资源共享、教学质量评估、