• YOLOv9训练损失、精度、mAP绘图功能 | 支持多结果对比,多结果绘在一个图片(消融实验、科研必备)


    一、本文介绍

    本文给大家带来的是YOLOv9系列的绘图功能,我将向大家介绍YOLO系列的绘图功能。我们在进行实验时,经常需要比较多个结果,针对这一问题,我写了点代码来解决这个问题,它可以根据训练结果绘制损失(loss)和mAP(平均精度均值)的对比图。这个工具不仅支持多个文件的对比分析,还允许大家在现有代码的基础上进行修,从而达到数据可视化的功能,大家也可以将对比图放在论文中进行对比也是非常不错的选择。

    先展示一下效果图-> 

    专栏地址:YOLOv9有效涨点专栏-持续复现各种顶会内容-有效涨点-全网改进最全的专栏 

    损失对比图片->

    目录

    一、本文介绍

    二、绘图工具核心代码 

    三、使用讲解 

    四、本文总结


    二、绘图工具核心代码 

    1. import os
    2. import pandas as pd
    3. import matplotlib.pyplot as plt
    4. def plot_metrics_and_loss(experiment_names, metrics_info, loss_info, metrics_subplot_layout, loss_subplot_layout,
    5. metrics_figure_size=(15, 10), loss_figure_size=(15, 10), base_directory='runs/train'):
    6. # Plot metrics
    7. plt.figure(figsize=metrics_figure_size)
    8. for i, (metric_name, title) in enumerate(metrics_info):
    9. plt.subplot(*metrics_subplot_layout, i + 1)
    10. for name in experiment_names:
    11. file_path = os.path.join(base_directory, name, 'results.csv')
    12. data = pd.read_csv(file_path)
    13. column_name = [col for col in data.columns if col.strip() == metric_name][0]
    14. plt.plot(data[column_name], label=name)
    15. plt.xlabel('Epoch')
    16. plt.title(title)
    17. plt.legend()
    18. plt.tight_layout()
    19. metrics_filename = 'metrics_curves.png'
    20. plt.savefig(metrics_filename)
    21. plt.show()
    22. # Plot loss
    23. plt.figure(figsize=loss_figure_size)
    24. for i, (loss_name, title) in enumerate(loss_info):
    25. plt.subplot(*loss_subplot_layout, i + 1)
    26. for name in experiment_names:
    27. file_path = os.path.join(base_directory, name, 'results.csv')
    28. data = pd.read_csv(file_path)
    29. column_name = [col for col in data.columns if col.strip() == loss_name][0]
    30. plt.plot(data[column_name], label=name)
    31. plt.xlabel('Epoch')
    32. plt.title(title)
    33. plt.legend()
    34. plt.tight_layout()
    35. loss_filename = 'loss_curves.png'
    36. plt.savefig(loss_filename)
    37. plt.show()
    38. return metrics_filename, loss_filename
    39. # Metrics to plot
    40. metrics_info = [
    41. ('metrics/precision', 'Precision'),
    42. ('metrics/recall', 'Recall'),
    43. ('metrics/mAP_0.5', 'mAP at IoU=0.5'),
    44. ('metrics/mAP_0.5:0.95', 'mAP for IoU Range 0.5-0.95')
    45. ]
    46. # Loss to plot
    47. loss_info = [
    48. ('train/box_loss', 'Training Box Loss'),
    49. ('train/cls_loss', 'Training Classification Loss'),
    50. ('train/obj_loss', 'Training OBJ Loss'),
    51. ('val/box_loss', 'Validation Box Loss'),
    52. ('val/cls_loss', 'Validation Classification Loss'),
    53. ('val/obj_loss', 'Validation obj Loss')
    54. ]
    55. # Plot the metrics and loss from multiple experiments
    56. metrics_filename, loss_filename = plot_metrics_and_loss(
    57. experiment_names=['exp40', 'exp38'],
    58. metrics_info=metrics_info,
    59. loss_info=loss_info,
    60. metrics_subplot_layout=(2, 2),
    61. loss_subplot_layout=(2, 3)
    62. )


    三、使用讲解 

    使用方式非常简单,我们首先创建一个文件,将核心代码粘贴进去,其中experiment_names这个参数就代表我们的每个训练结果的名字, 我们只需要修改这个即可,我这里就是五个结果进行对比,修改完成之后大家运行该文件即可。

    五、热力图代码 

    使用方式我会单独更一篇,这个热力图代码的进阶版,这里只是先放一下。 

    1. import warnings
    2. warnings.filterwarnings('ignore')
    3. warnings.simplefilter('ignore')
    4. import torch, yaml, cv2, os, shutil
    5. import numpy as np
    6. np.random.seed(0)
    7. import matplotlib.pyplot as plt
    8. from tqdm import trange
    9. from PIL import Image
    10. from ultralytics.nn.tasks import DetectionModel as Model
    11. from ultralytics.utils.torch_utils import intersect_dicts
    12. from ultralytics.utils.ops import xywh2xyxy
    13. from pytorch_grad_cam import GradCAMPlusPlus, GradCAM, XGradCAM
    14. from pytorch_grad_cam.utils.image import show_cam_on_image
    15. from pytorch_grad_cam.activations_and_gradients import ActivationsAndGradients
    16. def letterbox(im, new_shape=(640, 640), color=(114, 114, 114), auto=True, scaleFill=False, scaleup=True, stride=32):
    17. # Resize and pad image while meeting stride-multiple constraints
    18. shape = im.shape[:2] # current shape [height, width]
    19. if isinstance(new_shape, int):
    20. new_shape = (new_shape, new_shape)
    21. # Scale ratio (new / old)
    22. r = min(new_shape[0] / shape[0], new_shape[1] / shape[1])
    23. if not scaleup: # only scale down, do not scale up (for better val mAP)
    24. r = min(r, 1.0)
    25. # Compute padding
    26. ratio = r, r # width, height ratios
    27. new_unpad = int(round(shape[1] * r)), int(round(shape[0] * r))
    28. dw, dh = new_shape[1] - new_unpad[0], new_shape[0] - new_unpad[1] # wh padding
    29. if auto: # minimum rectangle
    30. dw, dh = np.mod(dw, stride), np.mod(dh, stride) # wh padding
    31. elif scaleFill: # stretch
    32. dw, dh = 0.0, 0.0
    33. new_unpad = (new_shape[1], new_shape[0])
    34. ratio = new_shape[1] / shape[1], new_shape[0] / shape[0] # width, height ratios
    35. dw /= 2 # divide padding into 2 sides
    36. dh /= 2
    37. if shape[::-1] != new_unpad: # resize
    38. im = cv2.resize(im, new_unpad, interpolation=cv2.INTER_LINEAR)
    39. top, bottom = int(round(dh - 0.1)), int(round(dh + 0.1))
    40. left, right = int(round(dw - 0.1)), int(round(dw + 0.1))
    41. im = cv2.copyMakeBorder(im, top, bottom, left, right, cv2.BORDER_CONSTANT, value=color) # add border
    42. return im, ratio, (dw, dh)
    43. class yolov8_heatmap:
    44. def __init__(self, weight, cfg, device, method, layer, backward_type, conf_threshold, ratio):
    45. device = torch.device(device)
    46. ckpt = torch.load(weight)
    47. model_names = ckpt['model'].names
    48. csd = ckpt['model'].float().state_dict() # checkpoint state_dict as FP32
    49. model = Model(cfg, ch=3, nc=len(model_names)).to(device)
    50. csd = intersect_dicts(csd, model.state_dict(), exclude=['anchor']) # intersect
    51. model.load_state_dict(csd, strict=False) # load
    52. model.eval()
    53. print(f'Transferred {len(csd)}/{len(model.state_dict())} items')
    54. target_layers = [eval(layer)]
    55. method = eval(method)
    56. colors = np.random.uniform(0, 255, size=(len(model_names), 3)).astype(np.int)
    57. self.__dict__.update(locals())
    58. def post_process(self, result):
    59. logits_ = result[:, 4:]
    60. boxes_ = result[:, :4]
    61. sorted, indices = torch.sort(logits_.max(1)[0], descending=True)
    62. return torch.transpose(logits_[0], dim0=0, dim1=1)[indices[0]], torch.transpose(boxes_[0], dim0=0, dim1=1)[indices[0]], xywh2xyxy(torch.transpose(boxes_[0], dim0=0, dim1=1)[indices[0]]).cpu().detach().numpy()
    63. def draw_detections(self, box, color, name, img):
    64. xmin, ymin, xmax, ymax = list(map(int, list(box)))
    65. cv2.rectangle(img, (xmin, ymin), (xmax, ymax), tuple(int(x) for x in color), 2)
    66. cv2.putText(img, str(name), (xmin, ymin - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.8, tuple(int(x) for x in color), 2, lineType=cv2.LINE_AA)
    67. return img
    68. def __call__(self, img_path, save_path):
    69. # remove dir if exist
    70. if os.path.exists(save_path):
    71. shutil.rmtree(save_path)
    72. # make dir if not exist
    73. os.makedirs(save_path, exist_ok=True)
    74. # img process
    75. img = cv2.imread(img_path)
    76. img = letterbox(img)[0]
    77. img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
    78. img = np.float32(img) / 255.0
    79. tensor = torch.from_numpy(np.transpose(img, axes=[2, 0, 1])).unsqueeze(0).to(self.device)
    80. # init ActivationsAndGradients
    81. grads = ActivationsAndGradients(self.model, self.target_layers, reshape_transform=None)
    82. # get ActivationsAndResult
    83. result = grads(tensor)
    84. activations = grads.activations[0].cpu().detach().numpy()
    85. # postprocess to yolo output
    86. post_result, pre_post_boxes, post_boxes = self.post_process(result[0])
    87. for i in trange(int(post_result.size(0) * self.ratio)):
    88. if float(post_result[i].max()) < self.conf_threshold:
    89. break
    90. self.model.zero_grad()
    91. # get max probability for this prediction
    92. if self.backward_type == 'class' or self.backward_type == 'all':
    93. score = post_result[i].max()
    94. score.backward(retain_graph=True)
    95. if self.backward_type == 'box' or self.backward_type == 'all':
    96. for j in range(4):
    97. score = pre_post_boxes[i, j]
    98. score.backward(retain_graph=True)
    99. # process heatmap
    100. if self.backward_type == 'class':
    101. gradients = grads.gradients[0]
    102. elif self.backward_type == 'box':
    103. gradients = grads.gradients[0] + grads.gradients[1] + grads.gradients[2] + grads.gradients[3]
    104. else:
    105. gradients = grads.gradients[0] + grads.gradients[1] + grads.gradients[2] + grads.gradients[3] + grads.gradients[4]
    106. b, k, u, v = gradients.size()
    107. weights = self.method.get_cam_weights(self.method, None, None, None, activations, gradients.detach().numpy())
    108. weights = weights.reshape((b, k, 1, 1))
    109. saliency_map = np.sum(weights * activations, axis=1)
    110. saliency_map = np.squeeze(np.maximum(saliency_map, 0))
    111. saliency_map = cv2.resize(saliency_map, (tensor.size(3), tensor.size(2)))
    112. saliency_map_min, saliency_map_max = saliency_map.min(), saliency_map.max()
    113. if (saliency_map_max - saliency_map_min) == 0:
    114. continue
    115. saliency_map = (saliency_map - saliency_map_min) / (saliency_map_max - saliency_map_min)
    116. # add heatmap and box to image
    117. cam_image = show_cam_on_image(img.copy(), saliency_map, use_rgb=True)
    118. cam_image = self.draw_detections(post_boxes[i], self.colors[int(post_result[i, :].argmax())], f'{self.model_names[int(post_result[i, :].argmax())]} {float(post_result[i].max()):.2f}', cam_image)
    119. cam_image = Image.fromarray(cam_image)
    120. cam_image.save(f'{save_path}/{i}.png')
    121. def get_params():
    122. params = {
    123. 'weight': 'yolov8n.pt',
    124. 'cfg': 'ultralytics/cfg/models/v8/yolov8n.yaml',
    125. 'device': 'cuda:0',
    126. 'method': 'GradCAM', # GradCAMPlusPlus, GradCAM, XGradCAM
    127. 'layer': 'model.model[9]',
    128. 'backward_type': 'all', # class, box, all
    129. 'conf_threshold': 0.6, # 0.6
    130. 'ratio': 0.02 # 0.02-0.1
    131. }
    132. return params
    133. if __name__ == '__main__':
    134. model = yolov8_heatmap(**get_params())
    135. model(r'ultralytics/assets/bus.jpg', 'result')


    四、本文总结

    到此本文的正式分享内容就结束了,在这里给大家推荐我的YOLOv9改进有效涨点专栏,本专栏目前为新开的平均质量分98分,后期我会根据各种最新的前沿顶会进行论文复现,也会对一些老的改进机制进行补充,目前本专栏免费阅读(暂时,大家尽早关注不迷路~),如果大家觉得本文帮助到你了,订阅本专栏,关注后续更多的更新~

    专栏地址:YOLOv9有效涨点专栏-持续复现各种顶会内容-有效涨点-全网改进最全的专栏 

  • 相关阅读:
    如何才能避免辛苦开发出来的产品惨遭市场冷遇?
    vue3中刷新当前页面的三种方法
    Linux 主进程管理
    Sublime Merge Git Client, done Sublime
    Linux Shell编程相关的书籍
    nacos服务注册源码过程阅读
    线程的状态
    2000-2020上市公司全要素生产率LP方法含原始数据和Stata代码
    「 WEB测试工程师 」岗位一面总结
    华为机试真题 C++ 实现【找终点】
  • 原文地址:https://blog.csdn.net/java1314777/article/details/138168848