π [Merge] branch 'DATASET'
Browse files- LICENSE +1 -1
- yolo/config/config.py +1 -1
- yolo/config/hyper/default.yaml +6 -6
- yolo/tools/log_helper.py +29 -0
- yolo/tools/trainer.py +30 -20
- yolo/utils/dataloader.py +9 -7
- yolo/utils/loss.py +29 -8
LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
MIT License
|
2 |
|
3 |
-
Copyright (c) 2024 Kin-Yiu, Wong
|
4 |
|
5 |
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
of this software and associated documentation files (the "Software"), to deal
|
|
|
1 |
MIT License
|
2 |
|
3 |
+
Copyright (c) 2024 Kin-Yiu, Wong and Hao-Tang, Tsui
|
4 |
|
5 |
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6 |
of this software and associated documentation files (the "Software"), to deal
|
yolo/config/config.py
CHANGED
@@ -72,7 +72,7 @@ class MatcherConfig:
|
|
72 |
@dataclass
|
73 |
class LossConfig:
|
74 |
objective: List[List]
|
75 |
-
aux: bool
|
76 |
matcher: MatcherConfig
|
77 |
|
78 |
|
|
|
72 |
@dataclass
|
73 |
class LossConfig:
|
74 |
objective: List[List]
|
75 |
+
aux: Union[bool, float]
|
76 |
matcher: MatcherConfig
|
77 |
|
78 |
|
yolo/config/hyper/default.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
data:
|
2 |
-
batch_size:
|
3 |
shuffle: True
|
4 |
-
num_workers:
|
5 |
pin_memory: True
|
6 |
class_num: 80
|
7 |
image_size: [640, 640]
|
@@ -13,11 +13,11 @@ train:
|
|
13 |
weight_decay: 0.0001
|
14 |
loss:
|
15 |
objective:
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
aux:
|
20 |
-
|
21 |
matcher:
|
22 |
iou: CIoU
|
23 |
topk: 10
|
|
|
1 |
data:
|
2 |
+
batch_size: 16
|
3 |
shuffle: True
|
4 |
+
num_workers: 16
|
5 |
pin_memory: True
|
6 |
class_num: 80
|
7 |
image_size: [640, 640]
|
|
|
13 |
weight_decay: 0.0001
|
14 |
loss:
|
15 |
objective:
|
16 |
+
BCELoss: 0.5
|
17 |
+
BoxLoss: 7.5
|
18 |
+
DFLoss: 1.5
|
19 |
aux:
|
20 |
+
0.25
|
21 |
matcher:
|
22 |
iou: CIoU
|
23 |
topk: 10
|
yolo/tools/log_helper.py
CHANGED
@@ -16,6 +16,7 @@ from typing import List
|
|
16 |
|
17 |
from loguru import logger
|
18 |
from rich.console import Console
|
|
|
19 |
from rich.table import Table
|
20 |
|
21 |
from yolo.config.config import YOLOLayer
|
@@ -29,6 +30,34 @@ def custom_logger():
|
|
29 |
)
|
30 |
|
31 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
32 |
def log_model(model: List[YOLOLayer]):
|
33 |
console = Console()
|
34 |
table = Table(title="Model Layers")
|
|
|
16 |
|
17 |
from loguru import logger
|
18 |
from rich.console import Console
|
19 |
+
from rich.progress import BarColumn, Progress, TextColumn, TimeRemainingColumn
|
20 |
from rich.table import Table
|
21 |
|
22 |
from yolo.config.config import YOLOLayer
|
|
|
30 |
)
|
31 |
|
32 |
|
33 |
+
class CustomProgress:
|
34 |
+
def __init__(self):
|
35 |
+
self.progress = Progress(
|
36 |
+
TextColumn("[progress.description]{task.description}"),
|
37 |
+
BarColumn(bar_width=None),
|
38 |
+
TextColumn("{task.completed}/{task.total}"),
|
39 |
+
TimeRemainingColumn(),
|
40 |
+
)
|
41 |
+
|
42 |
+
def start_train(self, num_epochs: int):
|
43 |
+
self.task_epoch = self.progress.add_task("[cyan]Epochs", total=num_epochs)
|
44 |
+
|
45 |
+
def one_epoch(self):
|
46 |
+
self.progress.update(self.task_epoch, advance=1)
|
47 |
+
|
48 |
+
def start_batch(self, num_batches):
|
49 |
+
self.batch_task = self.progress.add_task("[green]Batches", total=num_batches)
|
50 |
+
|
51 |
+
def one_batch(self, loss_each):
|
52 |
+
loss_iou, loss_dfl, loss_cls = loss_each
|
53 |
+
# TODO: make it flexible? if need add more loss
|
54 |
+
loss_str = f"Loss IoU: {loss_iou:.3f}, DFL: {loss_dfl:.3f}, CLS: {loss_cls:.3f}"
|
55 |
+
self.progress.update(self.batch_task, advance=1, description=f"[green]Batches {loss_str}")
|
56 |
+
|
57 |
+
def finish_batch(self):
|
58 |
+
self.progress.remove_task(self.batch_task)
|
59 |
+
|
60 |
+
|
61 |
def log_model(model: List[YOLOLayer]):
|
62 |
console = Console()
|
63 |
table = Table(title="Model Layers")
|
yolo/tools/trainer.py
CHANGED
@@ -1,11 +1,13 @@
|
|
1 |
import torch
|
2 |
from loguru import logger
|
3 |
from torch import Tensor
|
|
|
|
|
4 |
from torch.cuda.amp import GradScaler, autocast
|
5 |
-
from tqdm import tqdm
|
6 |
|
7 |
from yolo.config.config import Config, TrainConfig
|
8 |
from yolo.model.yolo import YOLO
|
|
|
9 |
from yolo.tools.model_helper import EMA, get_optimizer, get_scheduler
|
10 |
from yolo.utils.loss import get_loss_function
|
11 |
|
@@ -26,16 +28,13 @@ class Trainer:
|
|
26 |
self.ema = None
|
27 |
self.scaler = GradScaler()
|
28 |
|
29 |
-
def train_one_batch(self, data: Tensor, targets: Tensor
|
30 |
data, targets = data.to(self.device), targets.to(self.device)
|
31 |
self.optimizer.zero_grad()
|
32 |
|
33 |
with autocast():
|
34 |
outputs = self.model(data)
|
35 |
loss, loss_item = self.loss_fn(outputs, targets)
|
36 |
-
loss_iou, loss_dfl, loss_cls = loss_item
|
37 |
-
|
38 |
-
progress.set_description(f"Loss IoU: {loss_iou:.5f}, DFL: {loss_dfl:.5f}, CLS: {loss_cls:.5f}")
|
39 |
|
40 |
self.scaler.scale(loss).backward()
|
41 |
self.scaler.step(self.optimizer)
|
@@ -43,17 +42,21 @@ class Trainer:
|
|
43 |
|
44 |
return loss.item(), loss_item
|
45 |
|
46 |
-
|
47 |
-
|
48 |
-
def train_one_epoch(self, dataloader):
|
49 |
self.model.train()
|
50 |
total_loss = 0
|
51 |
-
|
52 |
-
|
53 |
-
|
54 |
-
|
55 |
-
|
56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
57 |
return total_loss / len(dataloader)
|
58 |
|
59 |
def save_checkpoint(self, epoch: int, filename="checkpoint.pt"):
|
@@ -69,9 +72,16 @@ class Trainer:
|
|
69 |
torch.save(checkpoint, filename)
|
70 |
|
71 |
def train(self, dataloader, num_epochs):
|
72 |
-
logger.info("
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import torch
|
2 |
from loguru import logger
|
3 |
from torch import Tensor
|
4 |
+
|
5 |
+
# TODO: We may can't use CUDA?
|
6 |
from torch.cuda.amp import GradScaler, autocast
|
|
|
7 |
|
8 |
from yolo.config.config import Config, TrainConfig
|
9 |
from yolo.model.yolo import YOLO
|
10 |
+
from yolo.tools.log_helper import CustomProgress
|
11 |
from yolo.tools.model_helper import EMA, get_optimizer, get_scheduler
|
12 |
from yolo.utils.loss import get_loss_function
|
13 |
|
|
|
28 |
self.ema = None
|
29 |
self.scaler = GradScaler()
|
30 |
|
31 |
+
def train_one_batch(self, data: Tensor, targets: Tensor):
|
32 |
data, targets = data.to(self.device), targets.to(self.device)
|
33 |
self.optimizer.zero_grad()
|
34 |
|
35 |
with autocast():
|
36 |
outputs = self.model(data)
|
37 |
loss, loss_item = self.loss_fn(outputs, targets)
|
|
|
|
|
|
|
38 |
|
39 |
self.scaler.scale(loss).backward()
|
40 |
self.scaler.step(self.optimizer)
|
|
|
42 |
|
43 |
return loss.item(), loss_item
|
44 |
|
45 |
+
def train_one_epoch(self, dataloader, progress: CustomProgress):
|
|
|
|
|
46 |
self.model.train()
|
47 |
total_loss = 0
|
48 |
+
progress.start_batch(len(dataloader))
|
49 |
+
|
50 |
+
for data, targets in dataloader:
|
51 |
+
loss, loss_each = self.train_one_batch(data, targets)
|
52 |
+
|
53 |
+
total_loss += loss
|
54 |
+
progress.one_batch(loss_each)
|
55 |
+
|
56 |
+
if self.scheduler:
|
57 |
+
self.scheduler.step()
|
58 |
+
|
59 |
+
progress.finish_batch()
|
60 |
return total_loss / len(dataloader)
|
61 |
|
62 |
def save_checkpoint(self, epoch: int, filename="checkpoint.pt"):
|
|
|
72 |
torch.save(checkpoint, filename)
|
73 |
|
74 |
def train(self, dataloader, num_epochs):
|
75 |
+
logger.info("π Start Training!")
|
76 |
+
progress = CustomProgress()
|
77 |
+
|
78 |
+
with progress.progress:
|
79 |
+
progress.start_train(num_epochs)
|
80 |
+
for epoch in range(num_epochs):
|
81 |
+
|
82 |
+
epoch_loss = self.train_one_epoch(dataloader, progress)
|
83 |
+
progress.one_epoch()
|
84 |
+
|
85 |
+
logger.info(f"Epoch {epoch+1}/{num_epochs}, Loss: {epoch_loss:.4f}")
|
86 |
+
if (epoch + 1) % 5 == 0:
|
87 |
+
self.save_checkpoint(epoch, f"checkpoint_epoch_{epoch+1}.pth")
|
yolo/utils/dataloader.py
CHANGED
@@ -178,13 +178,15 @@ class YoloDataLoader(DataLoader):
|
|
178 |
- A tensor of batched images.
|
179 |
- A list of tensors, each corresponding to bboxes for each image in the batch.
|
180 |
"""
|
181 |
-
batch_size
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
batch_targets[idx, :
|
187 |
-
|
|
|
|
|
188 |
return batch_images, batch_targets
|
189 |
|
190 |
|
|
|
178 |
- A tensor of batched images.
|
179 |
- A list of tensors, each corresponding to bboxes for each image in the batch.
|
180 |
"""
|
181 |
+
batch_size = len(batch)
|
182 |
+
target_sizes = [item[1].size(0) for item in batch]
|
183 |
+
# TODO: Improve readability of these proccess
|
184 |
+
batch_targets = torch.zeros(batch_size, max(target_sizes), 5)
|
185 |
+
for idx, target_size in enumerate(target_sizes):
|
186 |
+
batch_targets[idx, :target_size] = batch[idx][1]
|
187 |
+
|
188 |
+
batch_images = torch.stack([item[0] for item in batch])
|
189 |
+
|
190 |
return batch_images, batch_targets
|
191 |
|
192 |
|
yolo/utils/loss.py
CHANGED
@@ -15,6 +15,7 @@ from yolo.tools.bbox_helper import (
|
|
15 |
make_anchor,
|
16 |
transform_bbox,
|
17 |
)
|
|
|
18 |
|
19 |
|
20 |
class BCELoss(nn.Module):
|
@@ -135,14 +136,10 @@ class YOLOLoss:
|
|
135 |
anchors_box = anchors_box / self.scaler[None, :, None]
|
136 |
return anchors_cls, anchors_box
|
137 |
|
138 |
-
@torch.autocast("cuda" if torch.cuda.is_available() else "cpu")
|
139 |
def __call__(self, predicts: List[Tensor], targets: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
|
140 |
# Batch_Size x (Anchor + Class) x H x W
|
141 |
# TODO: check datatype, why targets has a little bit error with origin version
|
142 |
-
predicts, predicts_anc = self.parse_predicts(predicts
|
143 |
-
# TODO: Refactor this operator
|
144 |
-
# targets = self.parse_targets(targets, batch_size=predicts.size(0))
|
145 |
-
targets[:, :, 1:] = targets[:, :, 1:] * self.scale_up
|
146 |
|
147 |
align_targets, valid_masks = self.matcher(targets, predicts)
|
148 |
# calculate loss between with instance and predict
|
@@ -160,11 +157,35 @@ class YOLOLoss:
|
|
160 |
## -- DFL -- ##
|
161 |
loss_dfl = self.dfl(predicts_anc, targets_bbox, valid_masks, box_norm, cls_norm)
|
162 |
|
163 |
-
|
164 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
165 |
|
166 |
|
167 |
def get_loss_function(cfg: Config) -> YOLOLoss:
|
168 |
-
loss_function =
|
169 |
logger.info("β
Success load loss function")
|
170 |
return loss_function
|
|
|
15 |
make_anchor,
|
16 |
transform_bbox,
|
17 |
)
|
18 |
+
from yolo.tools.module_helper import make_chunk
|
19 |
|
20 |
|
21 |
class BCELoss(nn.Module):
|
|
|
136 |
anchors_box = anchors_box / self.scaler[None, :, None]
|
137 |
return anchors_cls, anchors_box
|
138 |
|
|
|
139 |
def __call__(self, predicts: List[Tensor], targets: Tensor) -> Tuple[Tensor, Tensor, Tensor]:
|
140 |
# Batch_Size x (Anchor + Class) x H x W
|
141 |
# TODO: check datatype, why targets has a little bit error with origin version
|
142 |
+
predicts, predicts_anc = self.parse_predicts(predicts)
|
|
|
|
|
|
|
143 |
|
144 |
align_targets, valid_masks = self.matcher(targets, predicts)
|
145 |
# calculate loss between with instance and predict
|
|
|
157 |
## -- DFL -- ##
|
158 |
loss_dfl = self.dfl(predicts_anc, targets_bbox, valid_masks, box_norm, cls_norm)
|
159 |
|
160 |
+
return loss_iou, loss_dfl, loss_cls
|
161 |
+
|
162 |
+
|
163 |
+
class DualLoss:
|
164 |
+
def __init__(self, cfg: Config) -> None:
|
165 |
+
self.loss = YOLOLoss(cfg)
|
166 |
+
self.aux_rate = cfg.hyper.train.loss.aux
|
167 |
+
|
168 |
+
self.iou_rate = cfg.hyper.train.loss.objective["BoxLoss"]
|
169 |
+
self.dfl_rate = cfg.hyper.train.loss.objective["DFLoss"]
|
170 |
+
self.cls_rate = cfg.hyper.train.loss.objective["BCELoss"]
|
171 |
+
|
172 |
+
def __call__(self, predicts: List[Tensor], targets: Tensor) -> Tuple[Tensor, Tuple[Tensor]]:
|
173 |
+
targets[:, :, 1:] = targets[:, :, 1:] * self.loss.scale_up
|
174 |
+
|
175 |
+
# TODO: Need Refactor this region, make it flexible!
|
176 |
+
predicts = make_chunk(predicts[0], 2)
|
177 |
+
aux_iou, aux_dfl, aux_cls = self.loss(predicts[0], targets)
|
178 |
+
main_iou, main_dfl, main_cls = self.loss(predicts[1], targets)
|
179 |
+
|
180 |
+
loss_iou = self.iou_rate * (aux_iou * self.aux_rate + main_iou)
|
181 |
+
loss_dfl = self.dfl_rate * (aux_dfl * self.aux_rate + main_dfl)
|
182 |
+
loss_cls = self.cls_rate * (aux_cls * self.aux_rate + main_cls)
|
183 |
+
|
184 |
+
loss = (loss_iou + loss_dfl + loss_cls) / 3
|
185 |
+
return loss, (loss_iou, loss_dfl, loss_cls)
|
186 |
|
187 |
|
188 |
def get_loss_function(cfg: Config) -> YOLOLoss:
|
189 |
+
loss_function = DualLoss(cfg)
|
190 |
logger.info("β
Success load loss function")
|
191 |
return loss_function
|