π [Rename] All utils, tools, higher readability
Browse files- docs/HOWTO.md +58 -0
- examples/example_train.py +7 -7
- tests/test_utils/test_dataaugment.py +1 -1
- tests/test_utils/test_loss.py +1 -1
- yolo/model/module.py +3 -3
- yolo/model/yolo.py +4 -4
- yolo/{utils/data_augment.py β tools/data_augmentation.py} +1 -1
- yolo/{utils/converter_json2txt.py β tools/data_conversion.py} +0 -0
- yolo/{utils/dataloader.py β tools/data_loader.py} +19 -13
- yolo/{utils/get_dataset.py β tools/dataset_preparation.py} +0 -0
- yolo/{utils β tools}/drawer.py +0 -0
- yolo/tools/layer_helper.py +0 -5
- yolo/{utils/loss.py β tools/loss_functions.py} +10 -5
- yolo/tools/trainer.py +12 -8
- yolo/utils/README.md +0 -7
- yolo/{tools β utils}/__init__.py +0 -0
- yolo/{tools/bbox_helper.py β utils/bounding_box_utils.py} +3 -3
- yolo/{tools/dataset_helper.py β utils/dataset_utils.py} +6 -6
- yolo/{tools/log_helper.py β utils/logging_utils.py} +3 -3
- yolo/{tools/model_helper.py β utils/model_utils.py} +3 -3
- yolo/{tools/module_helper.py β utils/module_utils.py} +16 -16
docs/HOWTO.md
CHANGED
@@ -88,3 +88,61 @@ Custom transformations should be designed to accept an image and its bounding bo
|
|
88 |
# ... (Other Transform)
|
89 |
CustomTransform: 0.5
|
90 |
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
88 |
# ... (Other Transform)
|
89 |
CustomTransform: 0.5
|
90 |
```
|
91 |
+
|
92 |
+
|
93 |
+
- **Utils**
|
94 |
+
- **bbox_utils**
|
95 |
+
- `class` Anchor2Box: transform predicted anchor to bounding box
|
96 |
+
- `class` Matcher: given prediction and groudtruth, find the groundtruth for each prediction
|
97 |
+
- `func` calculate_iou: calculate iou for given two list of bbox
|
98 |
+
- `func` transform_bbox: transform bbox from {xywh, xyxy, xcycwh} to {xywh, xyxy, xcycwh}
|
99 |
+
- `func` generate_anchors: given image size, make the anchor point for the given size
|
100 |
+
- **dataset_utils**
|
101 |
+
- `func` locate_label_paths:
|
102 |
+
- `func` create_image_metadata:
|
103 |
+
- `func` organize_annotations_by_image:
|
104 |
+
- `func` scale_segmentation:
|
105 |
+
- **logging_utils**
|
106 |
+
- `func` custom_log: custom loguru, overiding the origin logger
|
107 |
+
- `class` ProgressTracker: A class to handle output for each batch, epoch
|
108 |
+
- `func` log_model_structure: give a torch model, print it as a table
|
109 |
+
- `func` validate_log_directory: for given experiemnt, check if the log folder already existed
|
110 |
+
- **model_utils**
|
111 |
+
- `class` ExponentialMovingAverage: a mirror of model, do ema on model
|
112 |
+
- `func` create_optimizer: return a optimzer, for example SDG, ADAM
|
113 |
+
- `func` create_scheduler: return a scheduler, for example Step, Lambda
|
114 |
+
- **module_utils**
|
115 |
+
- `func` get_layer_map:
|
116 |
+
- `func` auto_pad: given a convolution block, return how many pixel should conv padding
|
117 |
+
- `func` create_activation_function: given a `func` name, return a activation `func`tion
|
118 |
+
- `func` round_up: given number and divider, return a number is mutliplcation of divider
|
119 |
+
- `func` divide_into_chunks: for a given list and n, seperate list to n sub list
|
120 |
+
- **trainer**
|
121 |
+
- `class` Trainer: a class can automatic train the model
|
122 |
+
- **Tools**
|
123 |
+
- **converter_json2txt**
|
124 |
+
- `func` discretize_categories: given the dictionary class, turn id from 1: class
|
125 |
+
- `func` process_annotations: handle the whole dataset annotations
|
126 |
+
- `func` process_annotation: handle a annotation(a list of bounding box)
|
127 |
+
- `func` normalize_segmentation: normalize segmentation position to 0~1
|
128 |
+
- `func` convert_annotations: convert json annotations to txt file structure
|
129 |
+
- **data_augment**
|
130 |
+
- `class` AugmentationComposer: Compose a list of data augmentation strategy
|
131 |
+
- `class` VerticalFlip: a custom data augmentation, Random Vertical Flip
|
132 |
+
- `class` Mosaic: a data augmentation strategy, follow YOLOv5
|
133 |
+
- **dataloader**
|
134 |
+
- `class` YoloDataset: a custom dataset for training yolo's model
|
135 |
+
- `class` YoloDataLoader: a dataloader base on torch's dataloader, with custom allocate function
|
136 |
+
- `func` create_dataloader: given a config file, return a YOLO dataloader
|
137 |
+
- **drawer**
|
138 |
+
- `func` draw_bboxes: given a image and list of bbox, draw bbox on the image
|
139 |
+
- `func` draw_model: visualize the given model
|
140 |
+
- **get_dataset**
|
141 |
+
- `func` download_file: for a given link, downlaod the file
|
142 |
+
- `func` unzip_file: unzip the downlaoded zip to data/
|
143 |
+
- `func` check_files: check if the dataset file numbers is correct
|
144 |
+
- `func` prepare_dataset: automatic downlaod the dataset and check if it is correct
|
145 |
+
- **loss**
|
146 |
+
- `class` BoxLoss: a Custom Loss for bounding box
|
147 |
+
- `class` YOLOLoss: a implementation of yolov9 loss
|
148 |
+
- `class` DualLoss: a implementation of yolov9 loss with auxiliary detection head
|
examples/example_train.py
CHANGED
@@ -9,23 +9,23 @@ project_root = Path(__file__).resolve().parent.parent
|
|
9 |
sys.path.append(str(project_root))
|
10 |
|
11 |
from yolo.config.config import Config
|
12 |
-
from yolo.tools.
|
13 |
-
from yolo.tools.
|
14 |
-
from yolo.
|
15 |
-
from yolo.utils.
|
16 |
|
17 |
|
18 |
@hydra.main(config_path="../yolo/config", config_name="config", version_base=None)
|
19 |
def main(cfg: Config):
|
20 |
custom_logger()
|
21 |
-
save_path =
|
22 |
if cfg.download.auto:
|
23 |
prepare_dataset(cfg.download)
|
24 |
|
25 |
-
dataloader =
|
26 |
# TODO: get_device or rank, for DDP mode
|
27 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
28 |
-
trainer =
|
29 |
trainer.train(dataloader, cfg.hyper.train.epoch)
|
30 |
|
31 |
|
|
|
9 |
sys.path.append(str(project_root))
|
10 |
|
11 |
from yolo.config.config import Config
|
12 |
+
from yolo.tools.data_loader import create_dataloader
|
13 |
+
from yolo.tools.dataset_preparation import prepare_dataset
|
14 |
+
from yolo.tools.trainer import ModelTrainer
|
15 |
+
from yolo.utils.logging_utils import custom_logger, validate_log_directory
|
16 |
|
17 |
|
18 |
@hydra.main(config_path="../yolo/config", config_name="config", version_base=None)
|
19 |
def main(cfg: Config):
|
20 |
custom_logger()
|
21 |
+
save_path = validate_log_directory(cfg.hyper.general, cfg.name)
|
22 |
if cfg.download.auto:
|
23 |
prepare_dataset(cfg.download)
|
24 |
|
25 |
+
dataloader = create_dataloader(cfg)
|
26 |
# TODO: get_device or rank, for DDP mode
|
27 |
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
28 |
+
trainer = ModelTrainer(cfg, save_path, device)
|
29 |
trainer.train(dataloader, cfg.hyper.train.epoch)
|
30 |
|
31 |
|
tests/test_utils/test_dataaugment.py
CHANGED
@@ -9,7 +9,7 @@ from torchvision.transforms import functional as TF
|
|
9 |
project_root = Path(__file__).resolve().parent.parent.parent
|
10 |
sys.path.append(str(project_root))
|
11 |
|
12 |
-
from yolo.utils.
|
13 |
|
14 |
|
15 |
def test_horizontal_flip():
|
|
|
9 |
project_root = Path(__file__).resolve().parent.parent.parent
|
10 |
sys.path.append(str(project_root))
|
11 |
|
12 |
+
from yolo.utils.data_augmentation import Compose, HorizontalFlip, Mosaic, VerticalFlip
|
13 |
|
14 |
|
15 |
def test_horizontal_flip():
|
tests/test_utils/test_loss.py
CHANGED
@@ -8,7 +8,7 @@ from hydra import compose, initialize
|
|
8 |
project_root = Path(__file__).resolve().parent.parent.parent
|
9 |
sys.path.append(str(project_root))
|
10 |
|
11 |
-
from yolo.utils.
|
12 |
|
13 |
|
14 |
@pytest.fixture
|
|
|
8 |
project_root = Path(__file__).resolve().parent.parent.parent
|
9 |
sys.path.append(str(project_root))
|
10 |
|
11 |
+
from yolo.utils.loss_functions import YOLOLoss
|
12 |
|
13 |
|
14 |
@pytest.fixture
|
yolo/model/module.py
CHANGED
@@ -6,7 +6,7 @@ from loguru import logger
|
|
6 |
from torch import Tensor, nn
|
7 |
from torch.nn.common_types import _size_2_t
|
8 |
|
9 |
-
from yolo.
|
10 |
|
11 |
|
12 |
# ----------- Basic Class ----------- #
|
@@ -26,7 +26,7 @@ class Conv(nn.Module):
|
|
26 |
kwargs.setdefault("padding", auto_pad(kernel_size, **kwargs))
|
27 |
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, bias=False, **kwargs)
|
28 |
self.bn = nn.BatchNorm2d(out_channels, eps=1e-3, momentum=3e-2)
|
29 |
-
self.act =
|
30 |
|
31 |
def forward(self, x: Tensor) -> Tensor:
|
32 |
return self.act(self.bn(self.conv(x)))
|
@@ -109,7 +109,7 @@ class RepConv(nn.Module):
|
|
109 |
**kwargs
|
110 |
):
|
111 |
super().__init__()
|
112 |
-
self.act =
|
113 |
self.conv1 = Conv(in_channels, out_channels, kernel_size, activation=False, **kwargs)
|
114 |
self.conv2 = Conv(in_channels, out_channels, 1, activation=False, **kwargs)
|
115 |
|
|
|
6 |
from torch import Tensor, nn
|
7 |
from torch.nn.common_types import _size_2_t
|
8 |
|
9 |
+
from yolo.utils.module_utils import auto_pad, create_activation_function, round_up
|
10 |
|
11 |
|
12 |
# ----------- Basic Class ----------- #
|
|
|
26 |
kwargs.setdefault("padding", auto_pad(kernel_size, **kwargs))
|
27 |
self.conv = nn.Conv2d(in_channels, out_channels, kernel_size, bias=False, **kwargs)
|
28 |
self.bn = nn.BatchNorm2d(out_channels, eps=1e-3, momentum=3e-2)
|
29 |
+
self.act = create_activation_function(activation)
|
30 |
|
31 |
def forward(self, x: Tensor) -> Tensor:
|
32 |
return self.act(self.bn(self.conv(x)))
|
|
|
109 |
**kwargs
|
110 |
):
|
111 |
super().__init__()
|
112 |
+
self.act = create_activation_function(activation)
|
113 |
self.conv1 = Conv(in_channels, out_channels, kernel_size, activation=False, **kwargs)
|
114 |
self.conv2 = Conv(in_channels, out_channels, 1, activation=False, **kwargs)
|
115 |
|
yolo/model/yolo.py
CHANGED
@@ -5,9 +5,9 @@ from loguru import logger
|
|
5 |
from omegaconf import ListConfig, OmegaConf
|
6 |
|
7 |
from yolo.config.config import Config, Model, YOLOLayer
|
8 |
-
from yolo.tools.
|
9 |
-
from yolo.
|
10 |
-
from yolo.utils.
|
11 |
|
12 |
|
13 |
class YOLO(nn.Module):
|
@@ -125,6 +125,6 @@ def get_model(cfg: Config) -> YOLO:
|
|
125 |
OmegaConf.set_struct(cfg.model, False)
|
126 |
model = YOLO(cfg.model, cfg.hyper.data.class_num)
|
127 |
logger.info("β
Success load model")
|
128 |
-
|
129 |
# draw_model(model=model)
|
130 |
return model
|
|
|
5 |
from omegaconf import ListConfig, OmegaConf
|
6 |
|
7 |
from yolo.config.config import Config, Model, YOLOLayer
|
8 |
+
from yolo.tools.drawer import draw_model
|
9 |
+
from yolo.utils.logging_utils import log_model_structure
|
10 |
+
from yolo.utils.module_utils import get_layer_map
|
11 |
|
12 |
|
13 |
class YOLO(nn.Module):
|
|
|
125 |
OmegaConf.set_struct(cfg.model, False)
|
126 |
model = YOLO(cfg.model, cfg.hyper.data.class_num)
|
127 |
logger.info("β
Success load model")
|
128 |
+
log_model_structure(model.model)
|
129 |
# draw_model(model=model)
|
130 |
return model
|
yolo/{utils/data_augment.py β tools/data_augmentation.py}
RENAMED
@@ -4,7 +4,7 @@ from PIL import Image
|
|
4 |
from torchvision.transforms import functional as TF
|
5 |
|
6 |
|
7 |
-
class
|
8 |
"""Composes several transforms together."""
|
9 |
|
10 |
def __init__(self, transforms, image_size: int = 640):
|
|
|
4 |
from torchvision.transforms import functional as TF
|
5 |
|
6 |
|
7 |
+
class AugmentationComposer:
|
8 |
"""Composes several transforms together."""
|
9 |
|
10 |
def __init__(self, transforms, image_size: int = 640):
|
yolo/{utils/converter_json2txt.py β tools/data_conversion.py}
RENAMED
File without changes
|
yolo/{utils/dataloader.py β tools/data_loader.py}
RENAMED
@@ -13,13 +13,19 @@ from torchvision.transforms import functional as TF
|
|
13 |
from tqdm.rich import tqdm
|
14 |
|
15 |
from yolo.config.config import Config
|
16 |
-
from yolo.tools.
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
)
|
21 |
-
from yolo.utils.data_augment import Compose, HorizontalFlip, MixUp, Mosaic, VerticalFlip
|
22 |
-
from yolo.utils.drawer import draw_bboxes
|
23 |
|
24 |
|
25 |
class YoloDataset(Dataset):
|
@@ -30,7 +36,7 @@ class YoloDataset(Dataset):
|
|
30 |
self.image_size = image_size
|
31 |
|
32 |
transforms = [eval(aug)(prob) for aug, prob in augment_cfg.items()]
|
33 |
-
self.transform =
|
34 |
self.transform.get_more_data = self.get_more_data
|
35 |
self.data = self.load_data(dataset_cfg.path, phase_name)
|
36 |
|
@@ -68,10 +74,10 @@ class YoloDataset(Dataset):
|
|
68 |
list: A list of tuples, each containing the path to an image file and its associated segmentation as a tensor.
|
69 |
"""
|
70 |
images_path = path.join(dataset_path, "images", phase_name)
|
71 |
-
labels_path, data_type =
|
72 |
images_list = sorted(os.listdir(images_path))
|
73 |
if data_type == "json":
|
74 |
-
annotations_index, image_info_dict =
|
75 |
|
76 |
data = []
|
77 |
valid_inputs = 0
|
@@ -85,7 +91,7 @@ class YoloDataset(Dataset):
|
|
85 |
if image_info is None:
|
86 |
continue
|
87 |
annotations = annotations_index.get(image_info["id"], [])
|
88 |
-
image_seg_annotations =
|
89 |
if not image_seg_annotations:
|
90 |
continue
|
91 |
|
@@ -191,13 +197,13 @@ class YoloDataLoader(DataLoader):
|
|
191 |
return batch_images, batch_targets
|
192 |
|
193 |
|
194 |
-
def
|
195 |
return YoloDataLoader(config)
|
196 |
|
197 |
|
198 |
@hydra.main(config_path="../config", config_name="config", version_base=None)
|
199 |
def main(cfg):
|
200 |
-
dataloader =
|
201 |
draw_bboxes(*next(iter(dataloader)))
|
202 |
|
203 |
|
@@ -205,7 +211,7 @@ if __name__ == "__main__":
|
|
205 |
import sys
|
206 |
|
207 |
sys.path.append("./")
|
208 |
-
from tools.
|
209 |
|
210 |
custom_logger()
|
211 |
main()
|
|
|
13 |
from tqdm.rich import tqdm
|
14 |
|
15 |
from yolo.config.config import Config
|
16 |
+
from yolo.tools.data_augmentation import (
|
17 |
+
AugmentationComposer,
|
18 |
+
HorizontalFlip,
|
19 |
+
MixUp,
|
20 |
+
Mosaic,
|
21 |
+
VerticalFlip,
|
22 |
+
)
|
23 |
+
from yolo.tools.drawer import draw_bboxes
|
24 |
+
from yolo.utils.dataset_utils import (
|
25 |
+
create_image_metadata,
|
26 |
+
locate_label_paths,
|
27 |
+
scale_segmentation,
|
28 |
)
|
|
|
|
|
29 |
|
30 |
|
31 |
class YoloDataset(Dataset):
|
|
|
36 |
self.image_size = image_size
|
37 |
|
38 |
transforms = [eval(aug)(prob) for aug, prob in augment_cfg.items()]
|
39 |
+
self.transform = AugmentationComposer(transforms, self.image_size)
|
40 |
self.transform.get_more_data = self.get_more_data
|
41 |
self.data = self.load_data(dataset_cfg.path, phase_name)
|
42 |
|
|
|
74 |
list: A list of tuples, each containing the path to an image file and its associated segmentation as a tensor.
|
75 |
"""
|
76 |
images_path = path.join(dataset_path, "images", phase_name)
|
77 |
+
labels_path, data_type = locate_label_paths(dataset_path, phase_name)
|
78 |
images_list = sorted(os.listdir(images_path))
|
79 |
if data_type == "json":
|
80 |
+
annotations_index, image_info_dict = create_image_metadata(labels_path)
|
81 |
|
82 |
data = []
|
83 |
valid_inputs = 0
|
|
|
91 |
if image_info is None:
|
92 |
continue
|
93 |
annotations = annotations_index.get(image_info["id"], [])
|
94 |
+
image_seg_annotations = scale_segmentation(annotations, image_info)
|
95 |
if not image_seg_annotations:
|
96 |
continue
|
97 |
|
|
|
197 |
return batch_images, batch_targets
|
198 |
|
199 |
|
200 |
+
def create_dataloader(config):
|
201 |
return YoloDataLoader(config)
|
202 |
|
203 |
|
204 |
@hydra.main(config_path="../config", config_name="config", version_base=None)
|
205 |
def main(cfg):
|
206 |
+
dataloader = create_dataloader(cfg)
|
207 |
draw_bboxes(*next(iter(dataloader)))
|
208 |
|
209 |
|
|
|
211 |
import sys
|
212 |
|
213 |
sys.path.append("./")
|
214 |
+
from tools.logging_utils import custom_logger
|
215 |
|
216 |
custom_logger()
|
217 |
main()
|
yolo/{utils/get_dataset.py β tools/dataset_preparation.py}
RENAMED
File without changes
|
yolo/{utils β tools}/drawer.py
RENAMED
File without changes
|
yolo/tools/layer_helper.py
DELETED
@@ -1,5 +0,0 @@
|
|
1 |
-
import inspect
|
2 |
-
|
3 |
-
import torch.nn as nn
|
4 |
-
|
5 |
-
from yolo.model import module
|
|
|
|
|
|
|
|
|
|
|
|
yolo/{utils/loss.py β tools/loss_functions.py}
RENAMED
@@ -8,8 +8,13 @@ from torch import Tensor, nn
|
|
8 |
from torch.nn import BCEWithLogitsLoss
|
9 |
|
10 |
from yolo.config.config import Config
|
11 |
-
from yolo.
|
12 |
-
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
|
15 |
class BCELoss(nn.Module):
|
@@ -78,14 +83,14 @@ class YOLOLoss:
|
|
78 |
self.reverse_reg = torch.arange(self.reg_max, dtype=torch.float32, device=device)
|
79 |
self.scale_up = torch.tensor(self.image_size * 2, device=device)
|
80 |
|
81 |
-
self.anchors, self.scaler =
|
82 |
|
83 |
self.cls = BCELoss()
|
84 |
self.dfl = DFLoss(self.anchors, self.scaler, self.reg_max)
|
85 |
self.iou = BoxLoss()
|
86 |
|
87 |
self.matcher = BoxMatcher(cfg.hyper.train.loss.matcher, self.class_num, self.anchors)
|
88 |
-
self.box_converter =
|
89 |
|
90 |
def separate_anchor(self, anchors):
|
91 |
"""
|
@@ -132,7 +137,7 @@ class DualLoss:
|
|
132 |
targets[:, :, 1:] = targets[:, :, 1:] * self.loss.scale_up
|
133 |
|
134 |
# TODO: Need Refactor this region, make it flexible!
|
135 |
-
predicts =
|
136 |
aux_iou, aux_dfl, aux_cls = self.loss(predicts[0], targets)
|
137 |
main_iou, main_dfl, main_cls = self.loss(predicts[1], targets)
|
138 |
|
|
|
8 |
from torch.nn import BCEWithLogitsLoss
|
9 |
|
10 |
from yolo.config.config import Config
|
11 |
+
from yolo.utils.bounding_box_utils import (
|
12 |
+
AnchorBoxConverter,
|
13 |
+
BoxMatcher,
|
14 |
+
calculate_iou,
|
15 |
+
generate_anchors,
|
16 |
+
)
|
17 |
+
from yolo.utils.module_utils import divide_into_chunks
|
18 |
|
19 |
|
20 |
class BCELoss(nn.Module):
|
|
|
83 |
self.reverse_reg = torch.arange(self.reg_max, dtype=torch.float32, device=device)
|
84 |
self.scale_up = torch.tensor(self.image_size * 2, device=device)
|
85 |
|
86 |
+
self.anchors, self.scaler = generate_anchors(self.image_size, self.strides, device)
|
87 |
|
88 |
self.cls = BCELoss()
|
89 |
self.dfl = DFLoss(self.anchors, self.scaler, self.reg_max)
|
90 |
self.iou = BoxLoss()
|
91 |
|
92 |
self.matcher = BoxMatcher(cfg.hyper.train.loss.matcher, self.class_num, self.anchors)
|
93 |
+
self.box_converter = AnchorBoxConverter(cfg, device)
|
94 |
|
95 |
def separate_anchor(self, anchors):
|
96 |
"""
|
|
|
137 |
targets[:, :, 1:] = targets[:, :, 1:] * self.loss.scale_up
|
138 |
|
139 |
# TODO: Need Refactor this region, make it flexible!
|
140 |
+
predicts = divide_into_chunks(predicts[0], 2)
|
141 |
aux_iou, aux_dfl, aux_cls = self.loss(predicts[0], targets)
|
142 |
main_iou, main_dfl, main_cls = self.loss(predicts[1], targets)
|
143 |
|
yolo/tools/trainer.py
CHANGED
@@ -7,25 +7,29 @@ from torch.cuda.amp import GradScaler, autocast
|
|
7 |
|
8 |
from yolo.config.config import Config, TrainConfig
|
9 |
from yolo.model.yolo import get_model
|
10 |
-
from yolo.tools.
|
11 |
-
from yolo.
|
12 |
-
from yolo.utils.
|
|
|
|
|
|
|
|
|
13 |
|
14 |
|
15 |
-
class
|
16 |
def __init__(self, cfg: Config, save_path: str, device):
|
17 |
train_cfg: TrainConfig = cfg.hyper.train
|
18 |
model = get_model(cfg)
|
19 |
|
20 |
self.model = model.to(device)
|
21 |
self.device = device
|
22 |
-
self.optimizer =
|
23 |
-
self.scheduler =
|
24 |
self.loss_fn = get_loss_function(cfg)
|
25 |
-
self.progress =
|
26 |
|
27 |
if getattr(train_cfg.ema, "enabled", False):
|
28 |
-
self.ema =
|
29 |
else:
|
30 |
self.ema = None
|
31 |
self.scaler = GradScaler()
|
|
|
7 |
|
8 |
from yolo.config.config import Config, TrainConfig
|
9 |
from yolo.model.yolo import get_model
|
10 |
+
from yolo.tools.loss_functions import get_loss_function
|
11 |
+
from yolo.utils.logging_utils import ProgressTracker
|
12 |
+
from yolo.utils.model_utils import (
|
13 |
+
ExponentialMovingAverage,
|
14 |
+
create_optimizer,
|
15 |
+
create_scheduler,
|
16 |
+
)
|
17 |
|
18 |
|
19 |
+
class ModelTrainer:
|
20 |
def __init__(self, cfg: Config, save_path: str, device):
|
21 |
train_cfg: TrainConfig = cfg.hyper.train
|
22 |
model = get_model(cfg)
|
23 |
|
24 |
self.model = model.to(device)
|
25 |
self.device = device
|
26 |
+
self.optimizer = create_optimizer(model, train_cfg.optimizer)
|
27 |
+
self.scheduler = create_scheduler(self.optimizer, train_cfg.scheduler)
|
28 |
self.loss_fn = get_loss_function(cfg)
|
29 |
+
self.progress = ProgressTracker(cfg, save_path, use_wandb=True)
|
30 |
|
31 |
if getattr(train_cfg.ema, "enabled", False):
|
32 |
+
self.ema = ExponentialMovingAverage(model, decay=train_cfg.ema.decay)
|
33 |
else:
|
34 |
self.ema = None
|
35 |
self.scaler = GradScaler()
|
yolo/utils/README.md
DELETED
@@ -1,7 +0,0 @@
|
|
1 |
-
task/train.py
|
2 |
-
|
3 |
-
task/validate.py
|
4 |
-
|
5 |
-
task/inference.py
|
6 |
-
|
7 |
-
etc.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
yolo/{tools β utils}/__init__.py
RENAMED
File without changes
|
yolo/{tools/bbox_helper.py β utils/bounding_box_utils.py}
RENAMED
@@ -106,7 +106,7 @@ def transform_bbox(bbox: Tensor, indicator="xywh -> xyxy"):
|
|
106 |
return bbox.to(dtype=data_type)
|
107 |
|
108 |
|
109 |
-
def
|
110 |
W, H = image_size
|
111 |
anchors = []
|
112 |
scaler = []
|
@@ -124,7 +124,7 @@ def make_anchor(image_size: List[int], strides: List[int], device):
|
|
124 |
return all_anchors, all_scalers
|
125 |
|
126 |
|
127 |
-
class
|
128 |
def __init__(self, cfg: Config, device: torch.device) -> None:
|
129 |
self.reg_max = cfg.model.anchor.reg_max
|
130 |
self.class_num = cfg.hyper.data.class_num
|
@@ -132,7 +132,7 @@ class Anchor2Box:
|
|
132 |
self.strides = cfg.model.anchor.strides
|
133 |
|
134 |
self.scale_up = torch.tensor(self.image_size * 2, device=device)
|
135 |
-
self.anchors, self.scaler =
|
136 |
self.reverse_reg = torch.arange(self.reg_max, dtype=torch.float32, device=device)
|
137 |
|
138 |
def __call__(self, predicts: List[Tensor], with_logits=False) -> Tensor:
|
|
|
106 |
return bbox.to(dtype=data_type)
|
107 |
|
108 |
|
109 |
+
def generate_anchors(image_size: List[int], strides: List[int], device):
|
110 |
W, H = image_size
|
111 |
anchors = []
|
112 |
scaler = []
|
|
|
124 |
return all_anchors, all_scalers
|
125 |
|
126 |
|
127 |
+
class AnchorBoxConverter:
|
128 |
def __init__(self, cfg: Config, device: torch.device) -> None:
|
129 |
self.reg_max = cfg.model.anchor.reg_max
|
130 |
self.class_num = cfg.hyper.data.class_num
|
|
|
132 |
self.strides = cfg.model.anchor.strides
|
133 |
|
134 |
self.scale_up = torch.tensor(self.image_size * 2, device=device)
|
135 |
+
self.anchors, self.scaler = generate_anchors(self.image_size, self.strides, device)
|
136 |
self.reverse_reg = torch.arange(self.reg_max, dtype=torch.float32, device=device)
|
137 |
|
138 |
def __call__(self, predicts: List[Tensor], with_logits=False) -> Tensor:
|
yolo/{tools/dataset_helper.py β utils/dataset_utils.py}
RENAMED
@@ -6,10 +6,10 @@ from typing import Any, Dict, List, Optional, Tuple
|
|
6 |
|
7 |
import numpy as np
|
8 |
|
9 |
-
from yolo.
|
10 |
|
11 |
|
12 |
-
def
|
13 |
"""
|
14 |
Find the path to label files for a specified dataset and phase(e.g. training).
|
15 |
|
@@ -35,7 +35,7 @@ def find_labels_path(dataset_path: str, phase_name: str):
|
|
35 |
raise FileNotFoundError("No labels found in the specified dataset path and phase name.")
|
36 |
|
37 |
|
38 |
-
def
|
39 |
"""
|
40 |
Create a dictionary containing image information and annotations indexed by image ID.
|
41 |
|
@@ -49,12 +49,12 @@ def create_image_info_dict(labels_path: str) -> Tuple[Dict[str, List], Dict[str,
|
|
49 |
with open(labels_path, "r") as file:
|
50 |
labels_data = json.load(file)
|
51 |
id_to_idx = discretize_categories(labels_data.get("categories", [])) if "categories" in labels_data else None
|
52 |
-
annotations_index =
|
53 |
image_info_dict = {path.splitext(img["file_name"])[0]: img for img in labels_data["images"]}
|
54 |
return annotations_index, image_info_dict
|
55 |
|
56 |
|
57 |
-
def
|
58 |
"""
|
59 |
Use image index to lookup every annotations
|
60 |
Args:
|
@@ -78,7 +78,7 @@ def index_annotations_by_image(data: Dict[str, Any], id_to_idx: Optional[Dict[in
|
|
78 |
return annotation_lookup
|
79 |
|
80 |
|
81 |
-
def
|
82 |
annotations: List[Dict[str, Any]], image_dimensions: Dict[str, int]
|
83 |
) -> Optional[List[List[float]]]:
|
84 |
"""
|
|
|
6 |
|
7 |
import numpy as np
|
8 |
|
9 |
+
from yolo.tools.data_conversion import discretize_categories
|
10 |
|
11 |
|
12 |
+
def locate_label_paths(dataset_path: str, phase_name: str):
|
13 |
"""
|
14 |
Find the path to label files for a specified dataset and phase(e.g. training).
|
15 |
|
|
|
35 |
raise FileNotFoundError("No labels found in the specified dataset path and phase name.")
|
36 |
|
37 |
|
38 |
+
def create_image_metadata(labels_path: str) -> Tuple[Dict[str, List], Dict[str, Dict]]:
|
39 |
"""
|
40 |
Create a dictionary containing image information and annotations indexed by image ID.
|
41 |
|
|
|
49 |
with open(labels_path, "r") as file:
|
50 |
labels_data = json.load(file)
|
51 |
id_to_idx = discretize_categories(labels_data.get("categories", [])) if "categories" in labels_data else None
|
52 |
+
annotations_index = organize_annotations_by_image(labels_data, id_to_idx) # check lookup is a good name?
|
53 |
image_info_dict = {path.splitext(img["file_name"])[0]: img for img in labels_data["images"]}
|
54 |
return annotations_index, image_info_dict
|
55 |
|
56 |
|
57 |
+
def organize_annotations_by_image(data: Dict[str, Any], id_to_idx: Optional[Dict[int, int]]):
|
58 |
"""
|
59 |
Use image index to lookup every annotations
|
60 |
Args:
|
|
|
78 |
return annotation_lookup
|
79 |
|
80 |
|
81 |
+
def scale_segmentation(
|
82 |
annotations: List[Dict[str, Any]], image_dimensions: Dict[str, int]
|
83 |
) -> Optional[List[List[float]]]:
|
84 |
"""
|
yolo/{tools/log_helper.py β utils/logging_utils.py}
RENAMED
@@ -35,7 +35,7 @@ def custom_logger():
|
|
35 |
)
|
36 |
|
37 |
|
38 |
-
class
|
39 |
def __init__(self, cfg: Config, save_path: str, use_wandb: bool = False):
|
40 |
self.progress = Progress(
|
41 |
TextColumn("[progress.description]{task.description}"),
|
@@ -87,7 +87,7 @@ def custom_wandb_log(string="", level=int, newline=True, repeat=True, prefix=Tru
|
|
87 |
logger.opt(raw=not newline, colors=True).info("π " + line)
|
88 |
|
89 |
|
90 |
-
def
|
91 |
console = Console()
|
92 |
table = Table(title="Model Layers")
|
93 |
|
@@ -108,7 +108,7 @@ def log_model(model: List[YOLOLayer]):
|
|
108 |
console.print(table)
|
109 |
|
110 |
|
111 |
-
def
|
112 |
base_path = os.path.join(general_cfg.out_path, general_cfg.task)
|
113 |
save_path = os.path.join(base_path, exp_name)
|
114 |
|
|
|
35 |
)
|
36 |
|
37 |
|
38 |
+
class ProgressTracker:
|
39 |
def __init__(self, cfg: Config, save_path: str, use_wandb: bool = False):
|
40 |
self.progress = Progress(
|
41 |
TextColumn("[progress.description]{task.description}"),
|
|
|
87 |
logger.opt(raw=not newline, colors=True).info("π " + line)
|
88 |
|
89 |
|
90 |
+
def log_model_structure(model: List[YOLOLayer]):
|
91 |
console = Console()
|
92 |
table = Table(title="Model Layers")
|
93 |
|
|
|
108 |
console.print(table)
|
109 |
|
110 |
|
111 |
+
def validate_log_directory(general_cfg: GeneralConfig, exp_name):
|
112 |
base_path = os.path.join(general_cfg.out_path, general_cfg.task)
|
113 |
save_path = os.path.join(base_path, exp_name)
|
114 |
|
yolo/{tools/model_helper.py β utils/model_utils.py}
RENAMED
@@ -8,7 +8,7 @@ from yolo.config.config import OptimizerConfig, SchedulerConfig
|
|
8 |
from yolo.model.yolo import YOLO
|
9 |
|
10 |
|
11 |
-
class
|
12 |
def __init__(self, model: torch.nn.Module, decay: float):
|
13 |
self.model = model
|
14 |
self.decay = decay
|
@@ -32,7 +32,7 @@ class EMA:
|
|
32 |
self.shadow[name].copy_(param.data)
|
33 |
|
34 |
|
35 |
-
def
|
36 |
"""Create an optimizer for the given model parameters based on the configuration.
|
37 |
|
38 |
Returns:
|
@@ -52,7 +52,7 @@ def get_optimizer(model: YOLO, optim_cfg: OptimizerConfig) -> Optimizer:
|
|
52 |
return optimizer_class(model_parameters, **optim_cfg.args)
|
53 |
|
54 |
|
55 |
-
def
|
56 |
"""Create a learning rate scheduler for the given optimizer based on the configuration.
|
57 |
|
58 |
Returns:
|
|
|
8 |
from yolo.model.yolo import YOLO
|
9 |
|
10 |
|
11 |
+
class ExponentialMovingAverage:
|
12 |
def __init__(self, model: torch.nn.Module, decay: float):
|
13 |
self.model = model
|
14 |
self.decay = decay
|
|
|
32 |
self.shadow[name].copy_(param.data)
|
33 |
|
34 |
|
35 |
+
def create_optimizer(model: YOLO, optim_cfg: OptimizerConfig) -> Optimizer:
|
36 |
"""Create an optimizer for the given model parameters based on the configuration.
|
37 |
|
38 |
Returns:
|
|
|
52 |
return optimizer_class(model_parameters, **optim_cfg.args)
|
53 |
|
54 |
|
55 |
+
def create_scheduler(optimizer: Optimizer, schedule_cfg: SchedulerConfig) -> _LRScheduler:
|
56 |
"""Create a learning rate scheduler for the given optimizer based on the configuration.
|
57 |
|
58 |
Returns:
|
yolo/{tools/module_helper.py β utils/module_utils.py}
RENAMED
@@ -5,20 +5,6 @@ from torch import Tensor, nn
|
|
5 |
from torch.nn.common_types import _size_2_t
|
6 |
|
7 |
|
8 |
-
def auto_pad(kernel_size: _size_2_t, dilation: _size_2_t = 1, **kwargs) -> Tuple[int, int]:
|
9 |
-
"""
|
10 |
-
Auto Padding for the convolution blocks
|
11 |
-
"""
|
12 |
-
if isinstance(kernel_size, int):
|
13 |
-
kernel_size = (kernel_size, kernel_size)
|
14 |
-
if isinstance(dilation, int):
|
15 |
-
dilation = (dilation, dilation)
|
16 |
-
|
17 |
-
pad_h = ((kernel_size[0] - 1) * dilation[0]) // 2
|
18 |
-
pad_w = ((kernel_size[1] - 1) * dilation[1]) // 2
|
19 |
-
return (pad_h, pad_w)
|
20 |
-
|
21 |
-
|
22 |
def get_layer_map():
|
23 |
"""
|
24 |
Dynamically generates a dictionary mapping class names to classes,
|
@@ -34,7 +20,21 @@ def get_layer_map():
|
|
34 |
return layer_map
|
35 |
|
36 |
|
37 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
38 |
"""
|
39 |
Retrieves an activation function from the PyTorch nn module based on its name, case-insensitively.
|
40 |
"""
|
@@ -59,7 +59,7 @@ def round_up(x: Union[int, Tensor], div: int = 1) -> Union[int, Tensor]:
|
|
59 |
return x + (-x % div)
|
60 |
|
61 |
|
62 |
-
def
|
63 |
"""
|
64 |
Args: input_list: [0, 1, 2, 3, 4, 5], chunk: 2
|
65 |
Return: [[0, 1, 2], [3, 4, 5]]
|
|
|
5 |
from torch.nn.common_types import _size_2_t
|
6 |
|
7 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
def get_layer_map():
|
9 |
"""
|
10 |
Dynamically generates a dictionary mapping class names to classes,
|
|
|
20 |
return layer_map
|
21 |
|
22 |
|
23 |
+
def auto_pad(kernel_size: _size_2_t, dilation: _size_2_t = 1, **kwargs) -> Tuple[int, int]:
|
24 |
+
"""
|
25 |
+
Auto Padding for the convolution blocks
|
26 |
+
"""
|
27 |
+
if isinstance(kernel_size, int):
|
28 |
+
kernel_size = (kernel_size, kernel_size)
|
29 |
+
if isinstance(dilation, int):
|
30 |
+
dilation = (dilation, dilation)
|
31 |
+
|
32 |
+
pad_h = ((kernel_size[0] - 1) * dilation[0]) // 2
|
33 |
+
pad_w = ((kernel_size[1] - 1) * dilation[1]) // 2
|
34 |
+
return (pad_h, pad_w)
|
35 |
+
|
36 |
+
|
37 |
+
def create_activation_function(activation: str) -> nn.Module:
|
38 |
"""
|
39 |
Retrieves an activation function from the PyTorch nn module based on its name, case-insensitively.
|
40 |
"""
|
|
|
59 |
return x + (-x % div)
|
60 |
|
61 |
|
62 |
+
def divide_into_chunks(input_list, chunk_num):
|
63 |
"""
|
64 |
Args: input_list: [0, 1, 2, 3, 4, 5], chunk: 2
|
65 |
Return: [[0, 1, 2], [3, 4, 5]]
|