henry000 commited on
Commit
b1431c7
Β·
2 Parent(s): c0153fd c3b133e

πŸ”€ [Merge] branch 'SETUP' into TEST

Browse files
yolo/config/dataset/dev.yaml CHANGED
@@ -1,5 +1,5 @@
1
  path: data/dev
2
  train: train
3
- validation: test
4
 
5
  auto_download:
 
1
  path: data/dev
2
  train: train
3
+ validation: val
4
 
5
  auto_download:
yolo/config/general.yaml CHANGED
@@ -9,7 +9,7 @@ out_path: runs
9
  exist_ok: True
10
 
11
  lucky_number: 10
12
- use_wandb: False
13
  use_TensorBoard: False
14
 
15
  weight: True # Path to weight or True for auto, False for no pretrained weight
 
9
  exist_ok: True
10
 
11
  lucky_number: 10
12
+ use_wandb: True
13
  use_TensorBoard: False
14
 
15
  weight: True # Path to weight or True for auto, False for no pretrained weight
yolo/config/task/validation.yaml CHANGED
@@ -8,5 +8,5 @@ data:
8
  pin_memory: True
9
  data_augment: {}
10
  nms:
11
- min_confidence: 0.001
12
- min_iou: 0.7
 
8
  pin_memory: True
9
  data_augment: {}
10
  nms:
11
+ min_confidence: 0.05
12
+ min_iou: 0.9
yolo/lazy.py CHANGED
@@ -28,18 +28,14 @@ def main(cfg: Config):
28
  model = model.to(device)
29
 
30
  vec2box = Vec2Box(model, cfg.image_size, device)
31
-
32
  if cfg.task.task == "train":
33
- trainer = ModelTrainer(cfg, model, vec2box, progress, device, use_ddp)
34
- trainer.solve(dataloader)
35
-
36
- if cfg.task.task == "inference":
37
- tester = ModelTester(cfg, model, vec2box, progress, device)
38
- tester.solve(dataloader)
39
-
40
  if cfg.task.task == "validation":
41
- valider = ModelValidator(cfg.task, model, vec2box, progress, device)
42
- valider.solve(dataloader)
 
 
 
43
 
44
 
45
  if __name__ == "__main__":
 
28
  model = model.to(device)
29
 
30
  vec2box = Vec2Box(model, cfg.image_size, device)
 
31
  if cfg.task.task == "train":
32
+ solver = ModelTrainer(cfg, model, vec2box, progress, device, use_ddp)
 
 
 
 
 
 
33
  if cfg.task.task == "validation":
34
+ solver = ModelValidator(cfg.task, model, vec2box, progress, device)
35
+ if cfg.task.task == "inference":
36
+ solver = ModelTester(cfg, model, vec2box, progress, device)
37
+ progress.start()
38
+ solver.solve(dataloader)
39
 
40
 
41
  if __name__ == "__main__":
yolo/model/yolo.py CHANGED
@@ -82,17 +82,14 @@ class YOLO(nn.Module):
82
  return output
83
 
84
  def get_out_channels(self, layer_type: str, layer_args: dict, output_dim: list, source: Union[int, list]):
85
- # TODO refactor, check out_channels in layer_args, next check source is list, CBFuse|Concat
86
- if any(module in layer_type for module in ["Conv", "ELAN", "ADown", "AConv"]):
87
  return layer_args["out_channels"]
88
  if layer_type == "CBFuse":
89
  return output_dim[source[-1]]
90
- if layer_type in ["Pool", "UpSample"]:
91
  return output_dim[source]
92
- if layer_type == "Concat":
93
  return sum(output_dim[idx] for idx in source)
94
- if layer_type == "IDetect":
95
- return None
96
 
97
  def get_source_idx(self, source: Union[ListConfig, str, int], layer_idx: int):
98
  if isinstance(source, ListConfig):
@@ -128,7 +125,6 @@ def create_model(model_cfg: ModelConfig, weight_path: Union[bool, str] = True, c
128
  Returns:
129
  YOLO: An instance of the model defined by the given configuration.
130
  """
131
- # TODO: "weight_path -> weight = [True|None-False|Path]: True should be default of model name?"
132
  OmegaConf.set_struct(model_cfg, False)
133
  model = YOLO(model_cfg, class_num)
134
  if weight_path:
@@ -138,7 +134,6 @@ def create_model(model_cfg: ModelConfig, weight_path: Union[bool, str] = True, c
138
  logger.info(f"🌐 Weight {weight_path} not found, try downloading")
139
  prepare_weight(weight_path=weight_path)
140
  if os.path.exists(weight_path):
141
- # TODO: fix map_location
142
  model.model.load_state_dict(torch.load(weight_path, map_location=torch.device("cpu")), strict=False)
143
  logger.info("βœ… Success load model & weight")
144
  else:
 
82
  return output
83
 
84
  def get_out_channels(self, layer_type: str, layer_args: dict, output_dim: list, source: Union[int, list]):
85
+ if hasattr(layer_args, "out_channels"):
 
86
  return layer_args["out_channels"]
87
  if layer_type == "CBFuse":
88
  return output_dim[source[-1]]
89
+ if isinstance(source, int):
90
  return output_dim[source]
91
+ if isinstance(source, list):
92
  return sum(output_dim[idx] for idx in source)
 
 
93
 
94
  def get_source_idx(self, source: Union[ListConfig, str, int], layer_idx: int):
95
  if isinstance(source, ListConfig):
 
125
  Returns:
126
  YOLO: An instance of the model defined by the given configuration.
127
  """
 
128
  OmegaConf.set_struct(model_cfg, False)
129
  model = YOLO(model_cfg, class_num)
130
  if weight_path:
 
134
  logger.info(f"🌐 Weight {weight_path} not found, try downloading")
135
  prepare_weight(weight_path=weight_path)
136
  if os.path.exists(weight_path):
 
137
  model.model.load_state_dict(torch.load(weight_path, map_location=torch.device("cpu")), strict=False)
138
  logger.info("βœ… Success load model & weight")
139
  else:
yolo/tools/data_augmentation.py CHANGED
@@ -46,12 +46,8 @@ class PadAndResize:
46
  padded_image = Image.new("RGB", (self.target_width, self.target_height), self.background_color)
47
  padded_image.paste(resized_image, (pad_left, pad_top))
48
 
49
- boxes[:, 1] *= scale # xmin
50
- boxes[:, 2] *= scale # ymin
51
- boxes[:, 3] *= scale # xmax
52
- boxes[:, 4] *= scale # ymax
53
- boxes[:, [1, 3]] += pad_left
54
- boxes[:, [2, 4]] += pad_top
55
 
56
  transform_info = torch.tensor([scale, pad_left, pad_top, pad_left, pad_top])
57
  return padded_image, boxes, transform_info
 
46
  padded_image = Image.new("RGB", (self.target_width, self.target_height), self.background_color)
47
  padded_image.paste(resized_image, (pad_left, pad_top))
48
 
49
+ boxes[:, [1, 3]] = (boxes[:, [1, 3]] * new_width + pad_left) / self.target_width
50
+ boxes[:, [2, 4]] = (boxes[:, [2, 4]] * new_height + pad_top) / self.target_height
 
 
 
 
51
 
52
  transform_info = torch.tensor([scale, pad_left, pad_top, pad_left, pad_top])
53
  return padded_image, boxes, transform_info
yolo/tools/data_loader.py CHANGED
@@ -199,7 +199,7 @@ class YoloDataLoader(DataLoader):
199
  batch_images = torch.stack(batch_images)
200
  batch_reverse = torch.stack(batch_reverse)
201
 
202
- return batch_images, batch_targets, batch_reverse, batch_path
203
 
204
 
205
  def create_dataloader(data_cfg: DataConfig, dataset_cfg: DatasetConfig, task: str = "train", use_ddp: bool = False):
 
199
  batch_images = torch.stack(batch_images)
200
  batch_reverse = torch.stack(batch_reverse)
201
 
202
+ return batch_size, batch_images, batch_targets, batch_reverse, batch_path
203
 
204
 
205
  def create_dataloader(data_cfg: DataConfig, dataset_cfg: DatasetConfig, task: str = "train", use_ddp: bool = False):
yolo/tools/solver.py CHANGED
@@ -1,9 +1,12 @@
1
  import json
2
  import os
 
3
  import time
 
4
 
5
  import torch
6
  from loguru import logger
 
7
  from torch import Tensor
8
 
9
  # TODO: We may can't use CUDA?
@@ -16,7 +19,7 @@ from yolo.model.yolo import YOLO
16
  from yolo.tools.data_loader import StreamDataLoader, create_dataloader
17
  from yolo.tools.drawer import draw_bboxes, draw_model
18
  from yolo.tools.loss_functions import create_loss_function
19
- from yolo.utils.bounding_box_utils import Vec2Box
20
  from yolo.utils.logging_utils import ProgressLogger, log_model_structure
21
  from yolo.utils.model_utils import (
22
  ExponentialMovingAverage,
@@ -25,6 +28,7 @@ from yolo.utils.model_utils import (
25
  create_scheduler,
26
  predicts_to_json,
27
  )
 
28
 
29
 
30
  class ModelTrainer:
@@ -69,22 +73,28 @@ class ModelTrainer:
69
  self.scaler.step(self.optimizer)
70
  self.scaler.update()
71
 
72
- return loss.item(), loss_item
73
 
74
  def train_one_epoch(self, dataloader):
75
  self.model.train()
76
- total_loss = 0
 
77
 
78
- for images, targets, *_ in dataloader:
79
- loss, loss_each = self.train_one_batch(images, targets)
80
 
81
- total_loss += loss
 
 
82
  self.progress.one_batch(loss_each)
83
 
 
 
 
84
  if self.scheduler:
85
  self.scheduler.step()
86
 
87
- return total_loss / len(dataloader)
88
 
89
  def save_checkpoint(self, epoch: int, filename="checkpoint.pt"):
90
  checkpoint = {
@@ -107,12 +117,11 @@ class ModelTrainer:
107
  if self.use_ddp:
108
  dataloader.sampler.set_epoch(epoch)
109
 
110
- self.progress.start_one_epoch(len(dataloader), self.optimizer, epoch)
111
- # TODO: calculate epoch loss
112
  epoch_loss = self.train_one_epoch(dataloader)
113
- self.progress.finish_one_epoch()
114
 
115
- self.validator.solve(self.validation_dataloader)
116
 
117
 
118
  class ModelTester:
@@ -187,19 +196,30 @@ class ModelValidator:
187
  self.post_proccess = PostProccess(vec2box, validation_cfg.nms)
188
  self.json_path = os.path.join(self.progress.save_path, f"predict.json")
189
 
190
- def solve(self, dataloader):
 
 
 
 
 
191
  # logger.info("πŸ§ͺ Start Validation!")
192
  self.model.eval()
193
- predict_json = []
194
- self.progress.start_one_epoch(len(dataloader))
195
- for images, targets, rev_tensor, img_paths in dataloader:
196
  images, targets, rev_tensor = images.to(self.device), targets.to(self.device), rev_tensor.to(self.device)
197
  with torch.no_grad():
198
  predicts = self.model(images)
199
- predicts = self.post_proccess(predicts, rev_tensor)
200
- self.progress.one_batch()
 
 
201
 
202
- predict_json.extend(predicts_to_json(img_paths, predicts))
203
- self.progress.finish_one_epoch()
204
  with open(self.json_path, "w") as f:
205
  json.dump(predict_json, f)
 
 
 
 
 
1
  import json
2
  import os
3
+ import sys
4
  import time
5
+ from collections import defaultdict
6
 
7
  import torch
8
  from loguru import logger
9
+ from pycocotools.coco import COCO
10
  from torch import Tensor
11
 
12
  # TODO: We may can't use CUDA?
 
19
  from yolo.tools.data_loader import StreamDataLoader, create_dataloader
20
  from yolo.tools.drawer import draw_bboxes, draw_model
21
  from yolo.tools.loss_functions import create_loss_function
22
+ from yolo.utils.bounding_box_utils import Vec2Box, calculate_map
23
  from yolo.utils.logging_utils import ProgressLogger, log_model_structure
24
  from yolo.utils.model_utils import (
25
  ExponentialMovingAverage,
 
28
  create_scheduler,
29
  predicts_to_json,
30
  )
31
+ from yolo.utils.solver_utils import calculate_ap
32
 
33
 
34
  class ModelTrainer:
 
73
  self.scaler.step(self.optimizer)
74
  self.scaler.update()
75
 
76
+ return loss_item
77
 
78
  def train_one_epoch(self, dataloader):
79
  self.model.train()
80
+ total_loss = defaultdict(lambda: torch.tensor(0.0, device=self.device))
81
+ total_samples = 0
82
 
83
+ for batch_size, images, targets, *_ in dataloader:
84
+ loss_each = self.train_one_batch(images, targets)
85
 
86
+ for loss_name, loss_val in loss_each.items():
87
+ total_loss[loss_name] += loss_val * batch_size
88
+ total_samples += batch_size
89
  self.progress.one_batch(loss_each)
90
 
91
+ for loss_val in total_loss.values():
92
+ loss_val /= total_samples
93
+
94
  if self.scheduler:
95
  self.scheduler.step()
96
 
97
+ return total_loss
98
 
99
  def save_checkpoint(self, epoch: int, filename="checkpoint.pt"):
100
  checkpoint = {
 
117
  if self.use_ddp:
118
  dataloader.sampler.set_epoch(epoch)
119
 
120
+ self.progress.start_one_epoch(len(dataloader), "Train", self.optimizer, epoch)
 
121
  epoch_loss = self.train_one_epoch(dataloader)
122
+ self.progress.finish_one_epoch(epoch_loss, epoch)
123
 
124
+ self.validator.solve(self.validation_dataloader, epoch_idx=epoch)
125
 
126
 
127
  class ModelTester:
 
196
  self.post_proccess = PostProccess(vec2box, validation_cfg.nms)
197
  self.json_path = os.path.join(self.progress.save_path, f"predict.json")
198
 
199
+ sys.stdout = open(os.devnull, "w")
200
+ # TODO: load with config file
201
+ self.coco_gt = COCO("data/coco/annotations/instances_val2017.json")
202
+ sys.stdout = sys.__stdout__
203
+
204
+ def solve(self, dataloader, epoch_idx=-1):
205
  # logger.info("πŸ§ͺ Start Validation!")
206
  self.model.eval()
207
+ mAPs, predict_json = [], []
208
+ self.progress.start_one_epoch(len(dataloader), task="Validate")
209
+ for batch_size, images, targets, rev_tensor, img_paths in dataloader:
210
  images, targets, rev_tensor = images.to(self.device), targets.to(self.device), rev_tensor.to(self.device)
211
  with torch.no_grad():
212
  predicts = self.model(images)
213
+ predicts = self.post_proccess(predicts)
214
+ for idx, predict in enumerate(predicts):
215
+ mAPs.append(calculate_map(predict, targets[idx]))
216
+ self.progress.one_batch(Tensor(mAPs))
217
 
218
+ predict_json.extend(predicts_to_json(img_paths, predicts, rev_tensor))
219
+ self.progress.finish_one_epoch(Tensor(mAPs), epoch_idx=epoch_idx)
220
  with open(self.json_path, "w") as f:
221
  json.dump(predict_json, f)
222
+
223
+ self.progress.start_pycocotools()
224
+ result = calculate_ap(self.coco_gt, predict_json)
225
+ self.progress.finish_pycocotools(result, epoch_idx)
yolo/utils/bounding_box_utils.py CHANGED
@@ -5,7 +5,7 @@ import torch
5
  import torch.nn.functional as F
6
  from einops import rearrange
7
  from loguru import logger
8
- from torch import Tensor
9
  from torchvision.ops import batched_nms
10
 
11
  from yolo.config.config import MatcherConfig, ModelConfig, NMSConfig
@@ -338,8 +338,8 @@ def bbox_nms(cls_dist: Tensor, bbox: Tensor, nms_cfg: NMSConfig):
338
  return predicts_nms
339
 
340
 
341
- def calculate_map(predictions, ground_truths, iou_thresholds):
342
- # TODO: Refactor this block
343
  device = predictions.device
344
  n_preds = predictions.size(0)
345
  n_gts = (ground_truths[:, 0] != -1).sum()
@@ -369,13 +369,16 @@ def calculate_map(predictions, ground_truths, iou_thresholds):
369
  precision = tp_cumsum / (tp_cumsum + fp_cumsum + 1e-6)
370
  recall = tp_cumsum / (n_gts + 1e-6)
371
 
372
- recall_thresholds = torch.arange(0, 1, 0.1)
373
- precision_at_recall = torch.zeros_like(recall_thresholds)
374
- for i, r in enumerate(recall_thresholds):
375
- precision_at_recall[i] = precision[recall >= r].max().item() if torch.any(recall >= r) else 0
 
 
 
 
376
 
377
- ap = precision_at_recall.mean()
378
  aps.append(ap)
379
 
380
  mean_ap = torch.mean(torch.stack(aps))
381
- return mean_ap, aps
 
5
  import torch.nn.functional as F
6
  from einops import rearrange
7
  from loguru import logger
8
+ from torch import Tensor, arange
9
  from torchvision.ops import batched_nms
10
 
11
  from yolo.config.config import MatcherConfig, ModelConfig, NMSConfig
 
338
  return predicts_nms
339
 
340
 
341
+ def calculate_map(predictions, ground_truths, iou_thresholds=arange(0.5, 1, 0.05)):
342
+ # TODO: Refactor this block, Flexible for calculate different mAP condition?
343
  device = predictions.device
344
  n_preds = predictions.size(0)
345
  n_gts = (ground_truths[:, 0] != -1).sum()
 
369
  precision = tp_cumsum / (tp_cumsum + fp_cumsum + 1e-6)
370
  recall = tp_cumsum / (n_gts + 1e-6)
371
 
372
+ precision = torch.cat([torch.ones(1, device=device), precision, torch.zeros(1, device=device)])
373
+ recall = torch.cat([torch.zeros(1, device=device), recall, torch.ones(1, device=device)])
374
+
375
+ precision, _ = torch.cummax(precision.flip(0), dim=0)
376
+ precision = precision.flip(0)
377
+
378
+ indices = (recall[1:] != recall[:-1]).nonzero(as_tuple=True)[0]
379
+ ap = torch.sum((recall[indices + 1] - recall[indices]) * precision[indices + 1])
380
 
 
381
  aps.append(ap)
382
 
383
  mean_ap = torch.mean(torch.stack(aps))
384
+ return mean_ap, aps[0]
yolo/utils/logging_utils.py CHANGED
@@ -13,18 +13,27 @@ Example:
13
 
14
  import os
15
  import sys
16
- from typing import Dict, List
 
17
 
18
  import wandb
19
  import wandb.errors.term
20
  from loguru import logger
21
- from rich.console import Console
22
- from rich.progress import BarColumn, Progress, TextColumn, TimeRemainingColumn
 
 
 
 
 
 
 
23
  from rich.table import Table
24
  from torch import Tensor
25
  from torch.optim import Optimizer
26
 
27
  from yolo.config.config import Config, YOLOLayer
 
28
 
29
 
30
  def custom_logger(quite: bool = False):
@@ -38,20 +47,24 @@ def custom_logger(quite: bool = False):
38
  )
39
 
40
 
41
- class ProgressLogger:
42
- def __init__(self, cfg: Config, exp_name: str):
43
  local_rank = int(os.getenv("LOCAL_RANK", "0"))
44
  self.quite_mode = local_rank or getattr(cfg, "quite", False)
45
  custom_logger(self.quite_mode)
46
  self.save_path = validate_log_directory(cfg, exp_name=cfg.name)
47
 
48
- self.progress = Progress(
 
49
  TextColumn("[progress.description]{task.description}"),
50
  BarColumn(bar_width=None),
51
  TextColumn("{task.completed:.0f}/{task.total:.0f}"),
52
  TimeRemainingColumn(),
53
  )
54
- self.progress.start()
 
 
 
55
 
56
  self.use_wandb = cfg.use_wandb
57
  if self.use_wandb:
@@ -60,35 +73,71 @@ class ProgressLogger:
60
  project="YOLO", resume="allow", mode="online", dir=self.save_path, id=None, name=exp_name
61
  )
62
 
 
 
 
 
63
  def start_train(self, num_epochs: int):
64
- self.task_epoch = self.progress.add_task("[cyan]Epochs [white]| Loss | Box | DFL | BCE |", total=num_epochs)
65
 
66
- def start_one_epoch(self, num_batches: int, optimizer: Optimizer = None, epoch_idx: int = None):
 
 
67
  self.num_batches = num_batches
68
- if self.use_wandb:
 
 
 
 
69
  lr_values = [params["lr"] for params in optimizer.param_groups]
70
- lr_names = ["bias", "norm", "conv"]
71
  for lr_name, lr_value in zip(lr_names, lr_values):
72
- self.wandb.log({f"Learning Rate/{lr_name}": lr_value}, step=epoch_idx)
73
- self.batch_task = self.progress.add_task("[green]Batches", total=num_batches)
74
-
75
- def one_batch(self, loss_dict: Dict[str, Tensor] = None):
76
- if loss_dict is None:
77
- self.progress.update(self.batch_task, advance=1, description=f"[green]Validating")
78
- return
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  if self.use_wandb:
80
- for loss_name, loss_value in loss_dict.items():
81
- self.wandb.log({f"Loss/{loss_name}": loss_value})
82
 
83
- loss_str = "| -.-- |"
84
- for loss_name, loss_val in loss_dict.items():
85
- loss_str += f" {loss_val:2.2f} |"
86
 
87
- self.progress.update(self.batch_task, advance=1, description=f"[green]Batches [white]{loss_str}")
88
- self.progress.update(self.task_epoch, advance=1 / self.num_batches)
 
 
89
 
90
- def finish_one_epoch(self):
91
- self.progress.remove_task(self.batch_task)
 
 
 
92
 
93
  def finish_train(self):
94
  self.wandb.finish()
@@ -115,7 +164,11 @@ def log_model_structure(model: List[YOLOLayer]):
115
  layer_param = sum(x.numel() for x in layer.parameters()) # number parameters
116
  in_channels, out_channels = getattr(layer, "in_c", None), getattr(layer, "out_c", None)
117
  if in_channels and out_channels:
118
- channels = f"{in_channels:4} -> {out_channels:4}"
 
 
 
 
119
  else:
120
  channels = "-"
121
  table.add_row(str(idx), layer.layer_type, layer.tags, f"{layer_param:,}", channels)
 
13
 
14
  import os
15
  import sys
16
+ from collections import deque
17
+ from typing import Any, Dict, List
18
 
19
  import wandb
20
  import wandb.errors.term
21
  from loguru import logger
22
+ from omegaconf import ListConfig
23
+ from rich.console import Console, Group
24
+ from rich.progress import (
25
+ BarColumn,
26
+ Progress,
27
+ SpinnerColumn,
28
+ TextColumn,
29
+ TimeRemainingColumn,
30
+ )
31
  from rich.table import Table
32
  from torch import Tensor
33
  from torch.optim import Optimizer
34
 
35
  from yolo.config.config import Config, YOLOLayer
36
+ from yolo.utils.solver_utils import make_ap_table
37
 
38
 
39
  def custom_logger(quite: bool = False):
 
47
  )
48
 
49
 
50
+ class ProgressLogger(Progress):
51
+ def __init__(self, cfg: Config, exp_name: str, *args, **kwargs):
52
  local_rank = int(os.getenv("LOCAL_RANK", "0"))
53
  self.quite_mode = local_rank or getattr(cfg, "quite", False)
54
  custom_logger(self.quite_mode)
55
  self.save_path = validate_log_directory(cfg, exp_name=cfg.name)
56
 
57
+ progress_bar = (
58
+ SpinnerColumn(),
59
  TextColumn("[progress.description]{task.description}"),
60
  BarColumn(bar_width=None),
61
  TextColumn("{task.completed:.0f}/{task.total:.0f}"),
62
  TimeRemainingColumn(),
63
  )
64
+ self.ap_table = Table()
65
+ # TODO: load maxlen by config files
66
+ self.ap_past_list = deque(maxlen=5)
67
+ super().__init__(*args, *progress_bar, **kwargs)
68
 
69
  self.use_wandb = cfg.use_wandb
70
  if self.use_wandb:
 
73
  project="YOLO", resume="allow", mode="online", dir=self.save_path, id=None, name=exp_name
74
  )
75
 
76
+ def get_renderable(self):
77
+ renderable = Group(*self.get_renderables(), self.ap_table)
78
+ return renderable
79
+
80
  def start_train(self, num_epochs: int):
81
+ self.task_epoch = self.add_task(f"[cyan]Start Training {num_epochs} epochs", total=num_epochs)
82
 
83
+ def start_one_epoch(
84
+ self, num_batches: int, task: str = "Train", optimizer: Optimizer = None, epoch_idx: int = None
85
+ ):
86
  self.num_batches = num_batches
87
+ self.task = task
88
+ if hasattr(self, "task_epoch"):
89
+ self.update(self.task_epoch, description=f"[cyan] Preparing Data")
90
+
91
+ if self.use_wandb and optimizer is not None:
92
  lr_values = [params["lr"] for params in optimizer.param_groups]
93
+ lr_names = ["Learning Rate/bias", "Learning Rate/norm", "Learning Rate/conv"]
94
  for lr_name, lr_value in zip(lr_names, lr_values):
95
+ self.wandb.log({lr_name: lr_value}, step=epoch_idx)
96
+ self.batch_task = self.add_task(f"[green] Phase: {task}", total=num_batches)
97
+
98
+ def one_batch(self, batch_info: Dict[str, Tensor] = None):
99
+ epoch_descript = "[cyan]" + self.task + "[white] |"
100
+ batch_descript = "|"
101
+ if self.task == "Train":
102
+ self.update(self.task_epoch, advance=1 / self.num_batches)
103
+ elif self.task == "Validate":
104
+ batch_info = {
105
+ "mAP.5": batch_info.mean(dim=0)[0],
106
+ "mAP.5:.95": batch_info.mean(dim=0)[1],
107
+ }
108
+ for info_name, info_val in batch_info.items():
109
+ epoch_descript += f"{info_name: ^9}|"
110
+ batch_descript += f" {info_val:2.2f} |"
111
+ self.update(self.batch_task, advance=1, description=f"[green]{self.task} [white]{batch_descript}")
112
+ if hasattr(self, "task_epoch"):
113
+ self.update(self.task_epoch, description=epoch_descript)
114
+
115
+ def finish_one_epoch(self, batch_info: Dict[str, Any] = None, epoch_idx: int = -1):
116
+ if self.task == "Train":
117
+ for loss_name in batch_info.keys():
118
+ batch_info["Loss/" + loss_name] = batch_info.pop(loss_name)
119
+ elif self.task == "Validate":
120
+ batch_info = {
121
+ "Metrics/mAP.5": batch_info.mean(dim=0)[0],
122
+ "Metrics/mAP.5:.95": batch_info.mean(dim=0)[1],
123
+ }
124
  if self.use_wandb:
125
+ self.wandb.log(batch_info, step=epoch_idx)
126
+ self.remove_task(self.batch_task)
127
 
128
+ def start_pycocotools(self):
129
+ self.batch_task = self.add_task("[green] run pycocotools", total=1)
 
130
 
131
+ def finish_pycocotools(self, result, epoch_idx=-1):
132
+ ap_table, ap_main = make_ap_table(result, self.ap_past_list, epoch_idx)
133
+ self.ap_past_list.append((epoch_idx, ap_main))
134
+ self.ap_table = ap_table
135
 
136
+ if self.use_wandb:
137
+ self.wandb.log({"PyCOCO/AP @ .5:.95": ap_main[1], "PyCOCO/AP @ .5": ap_main[3]})
138
+ self.update(self.batch_task, advance=1)
139
+ self.refresh()
140
+ self.remove_task(self.batch_task)
141
 
142
  def finish_train(self):
143
  self.wandb.finish()
 
164
  layer_param = sum(x.numel() for x in layer.parameters()) # number parameters
165
  in_channels, out_channels = getattr(layer, "in_c", None), getattr(layer, "out_c", None)
166
  if in_channels and out_channels:
167
+ if isinstance(in_channels, (list, ListConfig)):
168
+ in_channels = "M"
169
+ if isinstance(out_channels, (list, ListConfig)):
170
+ out_channels = "M"
171
+ channels = f"{str(in_channels): >4} -> {str(out_channels): >4}"
172
  else:
173
  channels = "-"
174
  table.add_row(str(idx), layer.layer_type, layer.tags, f"{layer_param:,}", channels)
yolo/utils/model_utils.py CHANGED
@@ -106,7 +106,7 @@ class PostProccess:
106
  self.vec2box = vec2box
107
  self.nms = nms_cfg
108
 
109
- def __call__(self, predict, rev_tensor: Optional[Tensor]):
110
  pred_class, _, pred_bbox = self.vec2box(predict["Main"])
111
  if rev_tensor is not None:
112
  pred_bbox = (pred_bbox - rev_tensor[:, None, 1:]) / rev_tensor[:, 0:1, None]
@@ -114,13 +114,15 @@ class PostProccess:
114
  return pred_bbox
115
 
116
 
117
- def predicts_to_json(img_paths, predicts):
118
  """
119
  TODO: function document
120
  turn a batch of imagepath and predicts(n x 6 for each image) to a List of diction(Detection output)
121
  """
122
  batch_json = []
123
- for img_path, bboxes in zip(img_paths, predicts):
 
 
124
  bboxes[:, 1:5] = transform_bbox(bboxes[:, 1:5], "xyxy -> xywh")
125
  for cls, *pos, conf in bboxes:
126
  bbox = {
 
106
  self.vec2box = vec2box
107
  self.nms = nms_cfg
108
 
109
+ def __call__(self, predict, rev_tensor: Optional[Tensor] = None):
110
  pred_class, _, pred_bbox = self.vec2box(predict["Main"])
111
  if rev_tensor is not None:
112
  pred_bbox = (pred_bbox - rev_tensor[:, None, 1:]) / rev_tensor[:, 0:1, None]
 
114
  return pred_bbox
115
 
116
 
117
+ def predicts_to_json(img_paths, predicts, rev_tensor):
118
  """
119
  TODO: function document
120
  turn a batch of imagepath and predicts(n x 6 for each image) to a List of diction(Detection output)
121
  """
122
  batch_json = []
123
+ for img_path, bboxes, box_reverse in zip(img_paths, predicts, rev_tensor):
124
+ scale, shift = box_reverse.split([1, 4])
125
+ bboxes[:, 1:5] = (bboxes[:, 1:5] - shift[None]) / scale[None]
126
  bboxes[:, 1:5] = transform_bbox(bboxes[:, 1:5], "xyxy -> xywh")
127
  for cls, *pos, conf in bboxes:
128
  bbox = {
yolo/utils/solver_utils.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+
4
+ from pycocotools.coco import COCO
5
+ from pycocotools.cocoeval import COCOeval
6
+ from rich.table import Table
7
+
8
+
9
+ def calculate_ap(coco_gt: COCO, pd_path):
10
+ sys.stdout = open(os.devnull, "w")
11
+ coco_dt = coco_gt.loadRes(pd_path)
12
+ coco_eval = COCOeval(coco_gt, coco_dt, "bbox")
13
+ coco_eval.evaluate()
14
+ coco_eval.accumulate()
15
+ coco_eval.summarize()
16
+ sys.stdout = sys.__stdout__
17
+ return coco_eval.stats
18
+
19
+
20
+ def make_ap_table(score, past_result=[], epoch=-1):
21
+ ap_table = Table()
22
+ ap_table.add_column("Epoch", justify="center", style="white", width=5)
23
+ ap_table.add_column("Avg. Precision", justify="left", style="cyan")
24
+ ap_table.add_column("", justify="right", style="green", width=5)
25
+ ap_table.add_column("Avg. Recall", justify="left", style="cyan")
26
+ ap_table.add_column("", justify="right", style="green", width=5)
27
+
28
+ for eps, (ap_name1, ap_value1, ap_name2, ap_value2) in past_result:
29
+ ap_table.add_row(f"{eps: 3d}", ap_name1, f"{ap_value1:.2f}", ap_name2, f"{ap_value2:.2f}")
30
+ if past_result:
31
+ ap_table.add_row()
32
+
33
+ this_ap = ("AP @ .5:.95", score[0], "AP @ .5", score[1])
34
+ metrics = [
35
+ ("AP @ .5:.95", score[0], "AR maxDets 1", score[6]),
36
+ ("AP @ .5", score[1], "AR maxDets 10", score[7]),
37
+ ("AP @ .75", score[2], "AR maxDets 100", score[8]),
38
+ ("AP (small)", score[3], "AR (small)", score[9]),
39
+ ("AP (medium)", score[4], "AR (medium)", score[10]),
40
+ ("AP (large)", score[5], "AR (large)", score[11]),
41
+ ]
42
+
43
+ for ap_name, ap_value, ar_name, ar_value in metrics:
44
+ ap_table.add_row(f"{epoch: 3d}", ap_name, f"{ap_value:.2f}", ar_name, f"{ar_value:.2f}")
45
+
46
+ return ap_table, this_ap