1.研究背景与意义
项目参考AAAI Association for the Advancement of Artificial Intelligence
研究背景与意义
随着人工智能技术的不断发展,计算机视觉领域的研究也取得了长足的进步。其中,目标检测是计算机视觉领域的一个重要研究方向,其在自动驾驶、智能监控、工业自动化等领域具有广泛的应用前景。目标检测的核心任务是在图像或视频中准确地识别和定位目标物体。
近年来,基于深度学习的目标检测方法取得了显著的成果。其中,YOLO(You Only Look Once)是一种非常流行的目标检测算法,其通过将目标检测问题转化为一个回归问题,实现了实时目标检测的能力。然而,YOLO算法在检测小目标和遮挡目标等方面仍然存在一定的局限性。
为了进一步提升目标检测的性能,研究者们提出了一种改进的骨干网络LCNet(Lightweight Context Network)。LCNet通过引入轻量级的上下文模块,能够有效地提取图像的上下文信息,从而提升目标检测的准确性和鲁棒性。然而,LCNet在处理复杂场景和大规模数据集时仍然存在一定的挑战。
因此,本研究旨在基于改进的LCNet骨干网络,设计和实现一个高效准确的元器件检测系统。该系统将应用于电子制造行业,用于检测电路板上的元器件,如电容、电阻、集成电路等。该系统的研究意义主要体现在以下几个方面:
首先,元器件检测系统在电子制造行业具有重要的应用价值。随着电子产品的普及和需求的增加,电子制造行业对于高效准确的元器件检测系统的需求也越来越迫切。该系统的研究和应用将有助于提高电子制造行业的生产效率和产品质量。
其次,基于改进的LCNet骨干网络的元器件检测系统能够提升目标检测的准确性和鲁棒性。通过引入轻量级的上下文模块,该系统能够更好地理解图像的语义信息,从而提高目标检测的精度。同时,该系统还能够有效地处理复杂场景和大规模数据集,具有较强的适应性和泛化能力。
此外,该研究还可以为其他领域的目标检测问题提供借鉴和参考。元器件检测系统的研究不仅仅局限于电子制造行业,还可以应用于其他领域,如智能交通、智能安防等。通过改进LCNet骨干网络,可以为其他目标检测问题提供一种高效准确的解决方案。
综上所述,基于改进的LCNet骨干网络的元器件检测系统具有重要的研究意义和应用价值。通过提高目标检测的准确性和鲁棒性,该系统将有助于提高电子制造行业的生产效率和产品质量,同时也为其他领域的目标检测问题提供了一种高效准确的解决方案。
2.图片演示
3.视频演示
基于改进LCNet骨干网络YOLOv5的元器件检测系统_哔哩哔哩_bilibili
4.数据集的采集&标注和整理
图片的收集
首先,我们需要收集所需的图片。这可以通过不同的方式来实现,例如使用现有的公开数据集ElementDatasets。
下面是一个简单的方法是使用Python脚本,该脚本读取分类图片文件,然后将其转换为所需的格式。
import os
import shutil
import random
# 指定输入和输出文件夹的路径
input_dir = 'train'
output_dir = 'output'
# 确保输出文件夹存在
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# 遍历输入文件夹中的所有子文件夹
for subdir in os.listdir(input_dir):
input_subdir_path = os.path.join(input_dir, subdir)
# 确保它是一个子文件夹
if os.path.isdir(input_subdir_path):
output_subdir_path = os.path.join(output_dir, subdir)
# 在输出文件夹中创建同名的子文件夹
if not os.path.exists(output_subdir_path):
os.makedirs(output_subdir_path)
# 获取所有文件的列表
files = [f for f in os.listdir(input_subdir_path) if os.path.isfile(os.path.join(input_subdir_path, f))]
# 随机选择四分之一的文件
files_to_move = random.sample(files, len(files) // 4)
# 移动文件
for file_to_move in files_to_move:
src_path = os.path.join(input_subdir_path, file_to_move)
dest_path = os.path.join(output_subdir_path, file_to_move)
shutil.move(src_path, dest_path)
print("任务完成!")
整理数据文件夹结构
我们需要将数据集整理为以下结构:
-----dataset
-----dataset
|-----train
| |-----class1
| |-----class2
| |-----.......
|
|-----valid
| |-----class1
| |-----class2
| |-----.......
|
|-----test
| |-----class1
| |-----class2
| |-----.......
模型训练
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 common.py
common.py是一个通用模块文件,包含了一些常用的函数和类。
-
autopad
函数:用于自动计算padding的大小,使得卷积操作后的输出大小与输入大小相同。 -
DepthSepConv
类:深度可分离卷积模块,包含了一系列卷积、归一化和激活函数操作。 -
drop_connect
类:用于实现DropConnect操作,用于在训练过程中随机丢弃部分连接。 -
stem
类:模型的起始部分,包含了一个卷积层和批归一化层。 -
MBConvBlock
类:MobileNetV3中的MBConv模块,包含了一系列卷积、归一化和激活函数操作。 -
conv_bn_hswish
类:MobileNetV3中的卷积、归一化和激活函数操作。 -
MobileNetV3_InvertedResidual
类:MobileNetV3中的倒残差模块,包含了一系列卷积、归一化和激活函数操作。 -
Conv_maxpool
类:ShuffleNetV2中的卷积和最大池化操作。 -
ShuffleNetV2_InvertedResidual
类:ShuffleNetV2中的倒残差模块,包含了一系列卷积、归一化和激活函数操作。
5.1 export.py
def export_formats():
# YOLOv5 export formats
x = [
['PyTorch', '-', '.pt', True, True],
['TorchScript', 'torchscript', '.torchscript', True, True],
['ONNX', 'onnx', '.onnx', True, True],
['OpenVINO', 'openvino', '_openvino_model', True, False],
['TensorRT', 'engine', '.engine', False, True],
['CoreML', 'coreml', '.mlmodel', True, False],
['TensorFlow SavedModel', 'saved_model', '_saved_model', True, True],
['TensorFlow GraphDef', 'pb', '.pb', True, True],
['TensorFlow Lite', 'tflite', '.tflite', True, False],
['TensorFlow Edge TPU', 'edgetpu', '_edgetpu.tflite', False, False],
['TensorFlow.js', 'tfjs', '_web_model', False, False],
['PaddlePaddle', 'paddle', '_paddle_model', True, True],]
return pd.DataFrame(x, columns=['Format', 'Argument', 'Suffix', 'CPU', 'GPU'])
def try_export(inner_func):
# YOLOv5 export decorator, i..e @try_export
inner_args = get_default_args(inner_func)
def outer_func(*args, **kwargs):
prefix = inner_args['prefix']
try:
with Profile() as dt:
f, model = inner_func(*args, **kwargs)
LOGGER.info(f'{prefix} export success ✅ {dt.t:.1f}s, saved as {f} ({file_size(f):.1f} MB)')
return f, model
except Exception as e:
LOGGER.info(f'{prefix} export failure ❌ {dt.t:.1f}s: {e}')
return None, None
return outer_func
@try_export
def export_torchscript(model, im, file, optimize, prefix=colorstr('TorchScript:')):
# YOLOv5 TorchScript model export
LOGGER.info(f'\n{prefix} starting export with torch {torch.__version__}...')
f = file.with_suffix('.torchscript')
ts = torch.jit.trace(model, im, strict=False)
d = {'shape': im.shape, 'stride': int(max(model.stride)), 'names': model.names}
extra_files = {'config.txt': json.dumps(d)} # torch._C.ExtraFilesMap()
if optimize: # https://pytorch.org/tutorials/recipes/mobile_interpreter.html
optimize_for_mobile(ts)._save_for_lite_interpreter(str(f), _extra_files=extra_files)
else:
ts.save(str(f), _extra_files=extra_files)
return f, None
@try_export
def export_onnx(model, im, file, opset, dynamic, simplify, prefix=colorstr('ONNX:')):
# YOLOv5 ONNX export
check_requirements('onnx>=1.12.0')
import onnx
LOGGER.info(f'\n{prefix} starting export with onnx {onnx.__version__}...')
f = file.with_suffix('.onnx')
output_names = ['output0', 'output1'] if isinstance(model, SegmentationModel) else ['output0']
if dynamic:
dynamic = {'images': {0: 'batch', 2: 'height', 3: 'width'}} # shape(1,3,640,640)
if isinstance(model, SegmentationModel):
dynamic['output0'] = {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
dynamic['output1'] = {0: 'batch', 2: 'mask_height', 3: 'mask_width'} # shape(1,32,160,160)
elif isinstance(model, DetectionModel):
dynamic['output0'] = {0: 'batch', 1: 'anchors'} # shape(1,25200,85)
torch.onnx.export(
model.cpu() if dynamic else model, # --dynamic only compatible with cpu
im.cpu() if dynamic else im,
f,
verbose=False,
opset_version=opset,
do_constant_folding=True, # WARNING: DNN inference with torch>=1.12 may require do_constant_folding=False
input_names=['images'],
output_names=output_names,
dynamic_axes=dynamic or None)
# Checks
model_onnx = onnx.load(f) # load onnx model
onnx.checker.check_model(model_onnx) # check onnx model
# Metadata
d = {'stride': int(max(model.stride)), 'names': model.names}
for k, v in d.items():
meta = model_onnx.metadata_props.add()
meta.key, meta.value = k, str(v)
onnx.save(model_onnx, f)
# Simplify
if simplify:
try:
cuda = torch.cuda.is_available()
check_requirements(('onnxruntime-gpu' if cuda else 'onnxruntime', 'onnx-simplifier>=0.4.1'))
import onnxsim
LOGGER.info(f'{prefix} simplifying with onnx-simplifier {onnxsim.__version__}...')
model_onnx, check = onnxsim.simplify(model_onnx)
assert check, 'assert check failed'
onnx.save(model_onnx, f)
except Exception as e:
LOGGER.info(f'{prefix} simplifier failure {e}')
return f, None
5.1 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_name = 'lcnet_150'
input_shape = (1, 3, 640, 640)
profiler = ModelProfiler(model_name, input_shape)
profiler.run()
这个程序文件名为model.py,主要功能是使用torch和timm库来计算模型的FLOPS和参数数量。
首先,程序会列出所有可用的模型名称。
然后,程序会检查是否有可用的GPU设备,如果有则使用cuda,否则使用cpu。
接下来,程序创建一个随机输入张量dummy_input,并将其移动到设备上。
然后,程序使用timm库创建一个名为’lcnet_150’的模型,该模型没有预训练权重,并且只返回特征。
接着,程序将模型移动到设备上,并设置为评估模式。
接下来,程序打印出模型的特征通道数。
然后,程序使用dummy_input作为输入,遍历模型的每个特征,并打印出每个特征的大小。
接着,程序使用profile函数计算模型在给定输入上的FLOPS和参数数量,并使用clever_format函数将其格式化为字符串。
最后,程序打印出计算得到的总FLOPS和总参数数量。
5.2 pruneEagleEye.py
class PruneAndEval:
def __init__(self, weights_path, cfg_path, data_path, hyp_path, device, batch_size, img_size, workers, path, min_remain_ratio, max_iter, remain_ratio, delta):
self.weights_path = weights_path
self.cfg_path = cfg_path
self.data_path = data_path
self.hyp_path = hyp_path
self.device = device
self.batch_size = batch_size
self.img_size = img_size
self.workers = workers
self.path = path
self.min_remain_ratio = min_remain_ratio
self.max_iter = max_iter
self.remain_ratio = remain_ratio
self.delta = delta
def rand_prune_and_eval(self):
origin_flops = self.model.flops
ignore_conv_idx = [i.replace('bn','conv') for i in self.ignore_idx]
max_remain_ratio = 1.0
candidates = 0
max_mAP = 0
maskbndict = {}
maskconvdict = {}
with open(self.cfg_path) as f:
oriyaml = yaml.load(f, Loader=yaml.SafeLoader) # model dict
ABE = AdaptiveBNEval(self.model, self.opt, self.device, self.hyp)
while True:
pruned_yaml = deepcopy(oriyaml)
for name, module in self.model.named_modules():
if isinstance(module, nn.Conv2d):
if name in ignore_conv_idx:
mask = torch.ones(module.weight.data.size()[0]).to(self.device)
else:
rand_remain_ratio = (max_remain_ratio - self.min_remain_ratio) * (np.random.rand(1)) + self.min_remain_ratio
mask = obtain_filtermask_l1(module, rand_remain_ratio).to(self.device)
maskbndict[(name[:-4] + 'bn')] = mask
maskconvdict[name] = mask
pruned_yaml = update_yaml(pruned_yaml, self.model, ignore_conv_idx, maskconvdict, self.opt)
compact_model = Model(pruned_yaml, pruning=True).to(self.device)
current_flops = compact_model.flops
if (current_flops/origin_flops > self.remain_ratio+self.delta) or (current_flops/origin_flops < self.remain_ratio-self.delta):
del compact_model
del pruned_yaml
continue
weights_inheritance(self.model, compact_model, self.from_to_map, maskbndict)
mAP = ABE(compact_model)
print('mAP@0.5 of candidate sub-network is {:f}'.format(mAP))
if mAP > max_mAP:
max_mAP = mAP
with open(self.path, "w", encoding='utf-8') as f:
yaml.safe_dump(pruned_yaml,f,encoding='utf-8', allow_unicode=True, default_flow_style=True, sort_keys=False)
ckpt = {'epoch': -1,
'best_fitness': [max_mAP],
'model': deepcopy(de_parallel(compact_model)).half(),
'ema': None,
'updates': None,
'optimizer': None,
'wandb_id': None}
torch.save(ckpt, self.weights_path[:-3]+'-EagleEyepruned.pt')
candidates = candidates + 1
del compact_model
del pruned_yaml
if candidates > self.max_iter:
break
def run(self):
self.cfg_path = check_file(self.cfg_path)
set_logging()
self.device = select_device(self.device)
with open(self.hyp_path) as f:
hyp = yaml.load(f, Loader=yaml.SafeLoader)
self.model = Model(self.cfg_path).to(self.device)
ckpt = torch.load(self.weights_path, map_location=self.device)
exclude = []
state_dict = ckpt['model'].float().state_dict()
state_dict = intersect_dicts(state_dict, self.model.state_dict(), exclude=exclude)
self.model.load_state_dict(state_dict, strict=True)
CBL_idx, self.ignore_idx, self.from_to_map = parse_module_defs(self.model.yaml)
self.rand_prune_and_eval()
if __name__ == '__main__':
parser = argparse.ArgumentParser()
parser.add_argument('--weights', type=str, default="runs/train/exp3/weights/best.pt", help='initial weights path')
parser.add_argument('--cfg', type=str, default='models/yolov5s-visdrone.yaml', help='model.yaml')
parser.add_argument('--data', type=str, default='data/VisDrone.yaml', help='data.yaml path')
parser.add_argument('--single-cls', action='store_true', help='train multi-class data as single-class')
parser.add_argument('--hyp', type=str, default='data/hyps/hyp.scratch.yaml', help='hyperparameters path')
parser.add_argument('--device', default='0', help='cuda device, i.e. 0 or 0,1,2,3 or cpu')
parser.add_argument('--batch-size', type=int, default=32, help='total batch size for all GPUs')
parser.add_argument('--img-size', nargs='+', type=int, default=[640, 640], help='[train, test] image sizes')
parser.add_argument('--workers', type=int, default=8, help='maximum number of dataloader workers')
parser.add_argument('--path', type=str, default='models/yolov5s-visdrone-pruned.yaml', help='the path to save pruned yaml')
parser.add_argument('--min_remain_ratio', type=float, default=0.2)
parser.add_argument('--max_iter', type=int, default=700, help='maximum number of arch search')
parser.add_argument('--remain_ratio', type=float, default=0.5, help='the whole parameters/FLOPs remain ratio')
parser.add_argument('--delta', type=float, default=0.02, help='scale of arch search')
opt = parser.parse_args()
prune_and_eval = PruneAndEval(opt.weights, opt.cfg, opt.data, opt.hyp, opt.device, opt.batch_size, opt.img_size, opt.workers, opt.path, opt.min_remain_ratio, opt.max_iter, opt.remain_ratio, opt.delta)
prune_and_eval.run()
这个程序文件名为pruneEagleEye.py,它是一个用于剪枝模型的Python脚本。该脚本导入了一些必要的库和模块,包括argparse、sys、copy、pathlib、torch、numpy、yaml等。它定义了一个函数rand_prune_and_eval,该函数接受一个模型、一个忽略索引列表和一些选项作为输入,然后执行随机剪枝和评估的操作。在主函数中,它解析了命令行参数,加载了模型和权重,并调用rand_prune_and_eval函数进行剪枝和评估。
5.3 pruneSlim.py
class Pruner:
def __init__(self, weights_path, cfg_path, data_path, hyp_path, device, batch_size, img_size, workers, path, global_percent):
self.weights_path = weights_path
self.cfg_path = cfg_path
self.data_path = data_path
self.hyp_path = hyp_path
self.device = device
self.batch_size = batch_size
self.img_size = img_size
self.workers = workers
self.path = path
self.global_percent = global_percent
def prune_and_eval(self):
model = self.load_model()
ignore_idx = self.get_ignore_idx(model)
bn_weights = gather_bn_weights(model, ignore_idx)
sorted_bn, _ = torch.sort(bn_weights)
thresh_index = int(len(bn_weights) * self.global_percent)
thresh = sorted_bn[thresh_index].cuda()
maskbndict, maskconvdict = self.get_masks(model, ignore_idx, thresh)
oriyaml = self.load_yaml()
ignore_conv_idx = [i.replace('bn','conv') for i in ignore_idx]
pruned_yaml = self.update_yaml(oriyaml, model, ignore_conv_idx, maskconvdict)
compact_model = self.create_compact_model(pruned_yaml)
self.weights_inheritance(model, compact_model, from_to_map, maskbndict)
ABE = AdaptiveBNEval(model, opt, device, hyp)
mAP = ABE(compact_model)
self.save_yaml(pruned_yaml)
self.save_checkpoint(compact_model, mAP)
def load_model(self):
set_logging()
device = select_device(self.device)
with open(self.hyp_path) as f:
hyp = yaml.load(f, Loader=yaml.SafeLoader)
model = Model(self.cfg_path).to(device)
ckpt = torch.load(self.weights_path, map_location=device)
exclude = []
state_dict = ckpt['model'].float().state_dict()
state_dict = intersect_dicts(state_dict, model.state_dict(), exclude=exclude)
model.load_state_dict(state_dict, strict=True)
return model
def get_ignore_idx(self, model):
CBL_idx, ignore_idx, from_to_map = parse_module_defs(model.yaml)
return ignore_idx
def get_masks(self, model, ignore_idx, thresh):
maskbndict = {}
maskconvdict = {}
for name, module in model.named_modules():
if isinstance(module, nn.BatchNorm2d):
if name in ignore_idx:
mask = torch.ones(module.weight.data.shape)
else:
mask = obtain_filtermask_bn(module, thresh)
maskbndict[name] = mask
maskconvdict[name[:-2] + 'conv'] = mask
return maskbndict, maskconvdict
def load_yaml(self):
with open(self.cfg_path) as f:
oriyaml = yaml.load(f, Loader=yaml.SafeLoader)
return oriyaml
def update_yaml(self, oriyaml, model, ignore_conv_idx, maskconvdict):
pruned_yaml = update_yaml(oriyaml, model, ignore_conv_idx, maskconvdict, opt)
return pruned_yaml
def create_compact_model(self, pruned_yaml):
compact_model = Model(pruned_yaml, pruning=True).to(device)
return compact_model
def weights_inheritance(self, model, compact_model, from_to_map, maskbndict):
weights_inheritance(model, compact_model, from_to_map, maskbndict)
def save_yaml(self, pruned_yaml):
with open(self.path, "w", encoding='utf-8') as f:
yaml.safe_dump(pruned_yaml,f,encoding='utf-8', allow_unicode=True, default_flow_style=True, sort_keys=False)
def save_checkpoint(self, compact_model, mAP):
ckpt = {'epoch': -1,
'best_fitness': [mAP],
'model': deepcopy(de_parallel(compact_model)).half(),
'ema': None,
'updates': None,
'optimizer': None,
'wandb_id': None}
torch.save(ckpt, self.weights_path[:-3]+'-Slimpruned.pt')
这个程序文件名为pruneSlim.py,它的功能是对模型进行剪枝并评估剪枝后的性能。程序的主要步骤如下:
- 导入所需的库和模块。
- 定义了一个名为prune_and_eval的函数,该函数接受模型、忽略的索引和选项作为输入。
- 在prune_and_eval函数中,首先收集模型中的批归一化层的权重。
- 对批归一化层的权重进行排序,并根据全局剪枝比例选择保留的通道数。
- 根据阈值获取卷积层和批归一化层的掩码。
- 使用yaml库加载模型配置文件。
- 更新模型配置文件,根据掩码和忽略的索引进行剪枝。
- 创建一个剪枝后的模型对象。
- 将原始模型的权重传递给剪枝后的模型,并进行权重继承。
- 创建AdaptiveBNEval对象,并使用剪枝后的模型进行评估。
- 将剪枝后的模型配置文件保存到指定路径。
- 创建一个包含剪枝后模型的检查点对象,并保存到指定路径。
- 在主函数中,解析命令行参数。
- 加载模型和权重。
- 解析模块定义。
- 调用prune_and_eval函数进行剪枝和评估。
5.5 ui.py
这个程序文件是一个用于目标检测的UI界面程序。它使用了PyQt5库来创建用户界面,并使用了OpenCV和PyTorch库来进行目标检测。
程序的主要功能是加载预训练的目标检测模型,并对输入的图像或视频进行目标检测。用户可以选择输入图像或视频的路径,选择模型的权重文件,设置推理尺寸和设备类型等参数。程序会将检测结果显示在界面上,并可以选择保存结果到文件。
程序的运行流程如下:
- 导入所需的库和模块。
- 定义了一个
run
函数,用于执行目标检测的主要逻辑。 - 定义了一个
parse_opt
函数,用于解析命令行参数。 - 定义了一个
main
函数,用于检查依赖库并调用run
函数进行目标检测。 - 定义了一个
det
函数,用于解析命令行参数并调用main
函数进行目标检测。 - 定义了一个
Thread_1
类,继承自QThread
,用于创建一个线程来执行目标检测。
在Thread_1
类中,通过重写run
方法来执行目标检测。在run
方法中,首先调用parse_opt
函数解析命令行参数,然后调用main
函数进行目标检测。
整个程序的目标是创建一个可视化的目标检测界面,用户可以通过界面选择输入图像或视频,选择模型和参数,并实时查看检测结果。
6.系统整体结构
根据以上分析,该程序是一个基于改进LCNet骨干网络YOLOv5的元器件检测系统。它的整体功能是进行元器件的目标检测,包括模型训练、模型剪枝、模型评估和模型推理。
下面是每个文件的功能整理:
文件名 | 功能 |
---|---|
common.py | 包含一系列卷积、归一化和激活函数操作的定义 |
export.py | 导出模型的特征 |
model.py | 定义了模型的结构 |
pruneEagleEye.py | 执行随机剪枝和评估的操作 |
pruneSlim.py | 根据掩码和忽略的索引进行剪枝 |
test.py | 解析命令行参数并调用主函数进行目标检测 |
train.py | 根据指定的超参数和配置文件进行模型训练 |
ui.py | 创建一个可视化的目标检测界面 |
val.py | 解析命令行参数并进行模型评估 |
yolo.py | 进行目标检测的主要逻辑 |
classify/predict.py | 进行元器件分类的预测 |
classify/train.py | 进行元器件分类的训练 |
classify/val.py | 进行元器件分类的评估 |
models/common.py | 包含一些模型的公共函数和类 |
models/experimental.py | 包含一些实验性的模型定义 |
models/tf.py | 包含一些TensorFlow模型定义 |
models/yolo.py | 包含YOLO模型的定义 |
models/init.py | 模型模块的初始化文件 |
segment/predict.py | 进行元器件分割的预测 |
segment/train.py | 进行元器件分割的训练 |
segment/val.py | 进行元器件分割的评估 |
utils/activations.py | 包含一些激活函数的定义 |
utils/augmentations.py | 包含一些数据增强的函数和类 |
utils/autoanchor.py | 包含自动锚框生成的函数和类 |
utils/autobatch.py | 包含自动批量大小调整的函数和类 |
utils/callbacks.py | 包含一些回调函数的定义 |
utils/dataloaders.py | 包含数据加载器的定义 |
utils/downloads.py | 包含下载数据集的函数 |
utils/general.py | 包含一些通用的函数和类 |
utils/loss.py | 包含一些损失函数的定义 |
utils/metrics.py | 包含一些评估指标的定义 |
utils/plots.py | 包含绘图函数的定义 |
utils/torch_utils.py | 包含一些PyTorch相关的函数和类 |
utils/triton.py | 包含与Triton Inference Server相关的函数和类 |
utils/init.py | 工具模块的初始化文件 |
utils/aws/resume.py | 包含恢复训练的函数 |
utils/aws/init.py | AWS模块的初始化文件 |
utils/flask_rest_api/example_request.py | 包含示例请求的函数 |
utils/flask_rest_api/restapi.py | 包含REST API的定义 |
utils/loggers/init.py | 日志记录器模块的初始化文件 |
utils/loggers/clearml/clearml_utils.py | 包含与ClearML日志记录器相关的函数和类 |
utils/loggers/clearml/hpo.py | 包含与ClearML超参数优化相关的函数和类 |
utils/loggers/clearml/init.py | ClearML日志记录器模块的初始化文件 |
utils/loggers/comet/comet_utils.py | 包含与Comet日志记录器相关的函数和类 |
utils/loggers/comet/hpo.py | 包含与Comet超参数优化相关的函数和类 |
utils/loggers/comet/init.py | Comet日志记录器模块的初始化文件 |
utils/loggers/wandb/wandb_utils.py | 包含与Weights & Biases日志记录器相关的函数和类 |
utils/loggers/wandb/init.py | Weights & Biases日志记录器模块的初始化文件 |
utils/segment/augmentations.py | 包含元器件分割的数据增强函数和类 |
utils/segment/dataloaders.py | 包含元器件分割的数据加载器的定义 |
utils/segment/general.py | 包含元器件分割的通用函数和类 |
utils/segment/loss.py | 包含元器件分割的损失函数的定义 |
utils/segment/metrics.py | 包含元器件分割的评估指标的定义 |
utils/segment/plots.py | 包含元器件分割的绘图函数的定义 |
utils/segment/init.py | 元器件分割模块的初始化文件 |
7.LCNet网络结构
随着GPU硬件的发展,主要关注点已经从手工设计的架构转向了自适应地对特定任务进行系统搜索的架构。NAS生成的网络大多使用与MobileNetV2类似的搜索空间,包括EfficientNet、MobileNetV3、FBNet、DNANet、OFANet等。
MixNet提出在一层中混合不同核大小的深度卷积。NAS生成的网络依赖于手工生成的块,如“BottleNeck”、“Inverted-block”等。该方法可以减少神经结构搜索的搜索空间,提高搜索效率,并有可能提高整体性能。
虽然有许多轻量级网络在基于ARM的设备上的推断速度很快,但很少有网络考虑到Intel CPU上的速度,特别是在启用了MKLDNN之类的加速策略时。
许多提高模型精度的方法在ARM设备上不会增加太多的推理时间,但是当切换到Intel CPU设备时,情况会有所不同。本文总结了一些在不增加推理时间的情况下提高模型性能的方法。下面将详细描述这些方法。
作者使用MobileNetV1提到的DepthSepConv作为基本块。该块没有shortcut方式之类的操作,因此没有concat或elementwise-add之类的附加操作,这些操作不仅会降低类的推理速度模型,而且对小模型也不会提高精度。
此外,该块经过Intel CPU加速库的深度优化,推理速度可以超过其他轻量级块,如 inverted-block或shufflenet-block。将这些块堆叠起来形成一个类似于MobileNetV1的BaseNet。将BaseNet和一些现有的技术结合在一起,形成一个更强大的网络,即PP-LCNet。
更好的激活函数
众所周知,激活函数的质量往往决定着网络的性能。由于网络的激活函数由Sigmoid变为ReLU,网络的性能得到了很大的提高。近年来,出现了越来越多超越ReLU的激活函数。当EfficientNet使用Swish激活函数表现出更好的性能后,MobileNetV3的作者将其升级为HSwish,从而避免了大量的指数运算。从那时起,许多轻量级网络也使用这个激活函数。作者还将BaseNet中的激活函数从ReLU替换为HSwish,性能有了很大的提高,而推理时间几乎没有改变。
将SE Block放在适当的位置
自SE-Block被提出以来,它已经被大量的网络所使用。该模块还帮助SENet赢得了2017年ImageNet分类竞赛。它在权衡网络通道以获得更好的特性方面做得很好,它的速度改进版本也用于许多轻量级网络,如MobileNetV3。
但是,在Intel cpu上,SE模块增加了推理时间,所以不能将其用于整个网络。事实上作者做了大量的实验和观察,当SE模块位于网络的末端时,它可以起到更好的作用。因此,只需将SE模块添加到网络尾部附近的模块中。这带来了一个更好的精度-速度平衡。与MobileNetV3一样,SE模块的2层激活函数分别为ReLU和HSigmoid。
更大的卷积核
卷积核的大小常常影响网络的最终性能。在MixNet中,作者分析了不同大小的卷积核对网络性能的影响,最终在网络的同一层中混合了不同大小的卷积核。但是这样的混合降低了模型的推理速度,所以作者尝试在单层中只使用一种大小的卷积核,并确保在低延迟和高精度的情况下使用大的卷积核。
实验发现,类似于SE模块的位置,取代3x3卷积内核只有5x5卷积内核的末端网络将实现替换的影响几乎所有层的网络,所以只在网络的末端做了这个替换操作。
8.改进LCNet骨干网络的YOLOv5
要将LCNet作为YOLOv5的骨干网络,需要进行以下步骤:
下载和安装YOLOv5:首先,确保你已经安装了Python和PyTorch深度学习框架。然后,从GitHub上克隆YOLOv5的源代码库。
修改配置文件:打开YOLOv5的配置文件(通常位于yolov5/models/yolov5s.yaml),并将骨干网络设置为LCNet。在配置文件中,找到与骨干网络相关的部分,并将其替换为LCNet的定义。
下载和加载预训练模型:为了使用LCNet作为骨干网络,你需要下载一个预训练的LCNet模型。可以从其官方GitHub仓库或其他可用的资源中获取该模型。将预训练模型加载到YOLOv5中,以便进行后续的训练或推理。
此改进如何增强模型的准确性和效率?
将LCNet作为YOLOv5的骨干网络可以带来以下改进:
准确性提升:LCNet是一种轻量级的高效卷积神经网络,具有较少的参数和计算量。通过将LCNet作为骨干网络,可以减少模型的复杂性,并提高模型对元器件检测的准确性。
效率提升:LCNet的设计注重于减少计算量和参数数量,以提高模型的效率。通过将LCNet作为骨干网络,可以减少YOLOv5的计算需求,并在较短的时间内完成元器件检测任务。
改进LCNet骨干网络的YOLOv5
针对LCNet和YOLOv5的特定改进包括以下几个方面:
特征融合:LCNet采用了多尺度特征融合的方法,以充分利用不同尺度的特征信息。在改进的YOLOv5中,可以进一步优化特征融合的方式,以更好地捕捉元器件的细节信息。
注意力机制:LCNet引入了注意力机制来增强对重要特征的关注程度。在改进的YOLOv5中,可以尝试引入更复杂的注意力机制,以提高模型对元器件的定位和识别能力。
数据增强:为了进一步提高模型的准确性和泛化能力,可以在训练过程中应用更多的数据增强技术,如随机裁剪、旋转、缩放等。这些技术可以帮助模型学习到更多不同的元器件形态和位置信息。
展示这些改进如何提高元器件检测的准确率和速度
通过上述改进,可以预期元器件检测的准确率和速度都会得到提高:
准确率提升:通过将LCNet作为骨干网络,可以减少模型的复杂性,并提高对元器件的定位和识别能力。同时,通过优化特征融合和引入注意力机制,可以进一步增强模型对细节信息的捕捉能力,从而提高元器件检测的准确率。
速度提升:LCNet的设计注重于减少计算量和参数数量,以提高模型的效率。通过将LCNet作为骨干网络,可以减少YOLOv5的计算需求,并在较短的时间内完成元器件检测任务。此外,通过应用更多的数据增强技术,可以增加训练数据的多样性,从而进一步提高模型的速度和泛化能力。
9.训练结果可视化分析
数据反映了基于改进LCNet骨干网络的YOLOv5电子元器件检测系统在训练过程中的表现。数据包括每个训练周期(epoch)的训练损失(train/loss)、测试损失(test/loss)、顶级精度(metrics/accuracy_top1和metrics/accuracy_top5),以及学习率(lr/0)。接下来,我将对这些指标进行可视化,以便我们更好地理解模型的学习过程和性能。
为了进行深入分析,我们需要观察损失值的变化趋势,以判断模型是否收敛以及是否存在过拟合或欠拟合的问题;同时,观察准确率的变化,以评估模型的分类能力。学习率的变化可以帮助我们了解训练过程中参数优化的速度和调整。
根据这些信息,我们可以评估模型的性能,如它在检测不同电子元件上的准确性,以及模型在经历了不同训练周期后的稳定性和可靠性。此外,这些数据可以揭示模型在训练过程中的学习效率,以及是否需要调整学习率或其他超参数以优化性能。
现在,让我们先进行可视化:
# Setting up the plot layout
fig, axes = plt.subplots(3, 1, figsize=(10, 15))
fig.suptitle('Training Metrics Over Epochs')
# Plotting train and test losses
axes[0].plot(data_results['epoch'], data_results['train/loss'], label='Train Loss')
axes[0].plot(data_results['epoch'], data_results['test/loss'], label='Test Loss')
axes[0].set_title('Loss')
axes[0].set_xlabel('Epoch')
axes[0].set_ylabel('Loss')
axes[0].legend()
# Plotting accuracy
axes[1].plot(data_results['epoch'], data_results['metrics/accuracy_top1'], label='Accuracy Top 1', color='orange')
axes[1].plot(data_results['epoch'], data_results['metrics/accuracy_top5'], label='Accuracy Top 5', color='purple')
axes[1].set_title('Accuracy')
axes[1].set_xlabel('Epoch')
axes[1].set_ylabel('Accuracy')
axes[1].legend()
# Plotting learning rate
axes[2].plot(data_results['epoch'], data_results['lr/0'], label='Learning Rate', color='green')
axes[2].set_title('Learning Rate')
axes[2].set_xlabel('Epoch')
axes[2].set_ylabel('Learning Rate')
axes[2].legend()
plt.tight_layout(rect=[0, 0.03, 1, 0.95])
plt.show()
结果分析
现在我们有了针对电子元器件检测系统实验结果的可视化图表,接下来,我将从不同角度对数据进行详细的分析。
训练和测试损失
在损失图中,训练损失(Train Loss)和测试损失(Test Loss)都随着训练周期的增加而下降,这表明模型的性能在提升。然而,测试损失的波动较大,特别是在初始阶段,这可能表明模型在适应新数据时的不稳定性。模型在经过一定训练周期后,测试损失稳定下来,这可能是由于模型开始泛化并更好地适应测试数据。
准确率
准确率图表显示,Top 1准确率(Accuracy Top 1)经历了初期的波动后逐渐稳定,并在某些周期达到较高值。Top 5准确率(Accuracy Top 5)保持在100%,这表明模型至少能将真实标签排在前五的预测中。这是一个积极的信号,表明模型的预测相对准确。
学习率
学习率(Learning Rate)随着训练周期的增加而递减,这是一个常见的技术,称为学习率衰减。通过逐步减小学习率,可以帮助模型在训练过程中更细致地调整权重,从而提高模型的稳定性和性能。
综合分析
从整体趋势来看,该模型在初期的训练阶段有较大的损失和准确率波动,这可能是由于模型参数的初始随机性造成的。随着训练的进行,模型变得更加稳定,损失降低,准确率提高,这表明模型正在从训练数据中学习并提高其预测能力。
尽管模型的Top 5准确率表现优秀,但Top 1准确率的波动表明,可能还需要进一步的优化。例如,可以通过增加正则化、调整网络结构、或者增强数据来减少过拟合和提高模型的泛化能力。
另外,模型的实时处理能力和在不同环境(如不同光照和背景)下的鲁棒性也是重要的考量因素。在实际应用中,模型需要能够快速准确地识别电子元器件,以及能够适应生产线上的变化。
最后,对于检测系统的未来改进,除了继续优化模型的准确率和泛化能力之外,还应考虑到计算效率。因为在实际部署中,模型需要在有限的计算资源下运行,因此模型的大小和速度也是需要权衡的关键因素。
综上所述,通过这些可视化图表,我们可以得出结论:该电子元器件检测系统表现出了良好的学习能力和准确性,但仍有改进空间,特别是在模型稳定性和实时检测能力方面。随着训练的继续,我们可以期待进一步的性能提升。
10.系统整合
参考博客《基于改进LCNet骨干网络YOLOv5的元器件检测系统》
11.参考文献
[1]杨静宜,王静红,崔建弘.深度学习的车间零件分拣机器人目标识别方法[J].机械设计与制造.2023,389(7).DOI:10.3969/j.issn.1001-3997.2023.07.055 .
[2]陈新,陈云,陈桪,等.我国后端电子制造装备及其关键零部件发展研究[J].中国工程科学.2022,24(4).DOI:10.15302/J-SSCAE-2022.04.008 .
[3]蒙敏荣,张勰.ARM NEON平台的D2C算法实现与优化[J].单片机与嵌入式系统应用.2022,22(3).
[4]李昭阳,关广丰,熊伟,等.电液振动台正弦振动自适应控制方法[J].液压与气动.2022,46(10).DOI:10.11832/j.issn.1000-4858.2022.10.003 .
[5]魏哲,王盼.面向珍珠分拣机器人的形状视觉检测方法[J].机械与电子.2021,(8).DOI:10.3969/j.issn.1001-2257.2021.08.015 .
[6]李祥瑞.机器视觉研究进展及工业应用综述[J].数字通信世界.2021,(11).DOI:10.3969/J.ISSN.1672-7274.2021.11.031 .
[7]崔铁军,王凌霄.YOLOv4目标检测算法在煤矿工人口罩佩戴监测工作中的应用研究[J].中国安全生产科学技术.2021,(10).DOI:10.11731/j.issn.1673-193x.2021.10.010 .
[8]鞠默然,罗海波,刘广琦,等.采用空间注意力机制的红外弱小目标检测网络[J].光学精密工程.2021,(4).DOI:10.37188/OPE.20212904.0843 .
[9]谭子会.基于双目视觉的包装过程分拣技术研究[J].包装与食品机械.2020,(5).DOI:10.3969/j.issn.1005-1295.2020.05.013 .
[10]梅江平,王浩,张舵,等.基于单目视觉的高速并联机器人动态目标跟踪算法[J].天津大学学报.2020,(2).DOI:10.11784/tdxbz201903009 .