Add custom training sample code and dataset (#256)
Browse files* add train custom dataset code
* update custom docs
* fix(evaluator): fix n_samples=0 bug for the len of dataloader is 1
- docs/train_custom_data.md +10 -0
- exps/example/custom/nano.py +46 -0
- exps/example/custom/yolox_s.py +26 -0
- yolox/evaluators/coco_evaluator.py +1 -1
- yolox/exp/yolox_base.py +14 -11
docs/train_custom_data.md
CHANGED
@@ -35,6 +35,8 @@ ln -s /path/to/your/VOCdevkit ./datasets/VOCdevkit
|
|
35 |
```
|
36 |
* The path "VOCdevkit" will be used in your exp file described in next section. Specifically, in `get_data_loader` and `get_eval_loader` function.
|
37 |
|
|
|
|
|
38 |
## 2. Create your Exp file to control everything
|
39 |
We put everything involved in a model to one single Exp file, including model setting, training setting, and testing setting.
|
40 |
|
@@ -59,6 +61,8 @@ Besides, you should also overwrite the `dataset` and `evaluator`, prepared befor
|
|
59 |
|
60 |
Please see [get_data_loader](../exps/example/yolox_voc/yolox_voc_s.py#L20), [get_eval_loader](../exps/example/yolox_voc/yolox_voc_s.py#L82), and [get_evaluator](../exps/example/yolox_voc/yolox_voc_s.py#L113) for more details.
|
61 |
|
|
|
|
|
62 |
## 3. Train
|
63 |
Except special cases, we always recommend to use our [COCO pretrained weights](../README.md) for initializing the model.
|
64 |
|
@@ -72,6 +76,12 @@ or take the `YOLOX-S` VOC training for example:
|
|
72 |
python tools/train.py -f exps/example/yolox_voc/yolox_voc_s.py -d 8 -b 64 --fp16 -o -c /path/to/yolox_s.pth.tar
|
73 |
```
|
74 |
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
(Don't worry for the different shape of detection head between the pretrained weights and your own model, we will handle it)
|
76 |
|
77 |
## 4. Tips for Best Training Results
|
|
|
35 |
```
|
36 |
* The path "VOCdevkit" will be used in your exp file described in next section. Specifically, in `get_data_loader` and `get_eval_loader` function.
|
37 |
|
38 |
+
✧✧✧ You can download the mini-coco128 dataset by the [link](https://drive.google.com/file/d/16N3u36ycNd70m23IM7vMuRQXejAJY9Fs/view?usp=sharing), and then unzip it to the `datasets` directory. The dataset has been converted from YOLO format to COCO format, and can be used directly as a dataset for testing whether the train environment can be runned successfully.
|
39 |
+
|
40 |
## 2. Create your Exp file to control everything
|
41 |
We put everything involved in a model to one single Exp file, including model setting, training setting, and testing setting.
|
42 |
|
|
|
61 |
|
62 |
Please see [get_data_loader](../exps/example/yolox_voc/yolox_voc_s.py#L20), [get_eval_loader](../exps/example/yolox_voc/yolox_voc_s.py#L82), and [get_evaluator](../exps/example/yolox_voc/yolox_voc_s.py#L113) for more details.
|
63 |
|
64 |
+
✧✧✧ You can also see the `exps/example/custom` directory for more details.
|
65 |
+
|
66 |
## 3. Train
|
67 |
Except special cases, we always recommend to use our [COCO pretrained weights](../README.md) for initializing the model.
|
68 |
|
|
|
76 |
python tools/train.py -f exps/example/yolox_voc/yolox_voc_s.py -d 8 -b 64 --fp16 -o -c /path/to/yolox_s.pth.tar
|
77 |
```
|
78 |
|
79 |
+
✧✧✧ For example:
|
80 |
+
- If you download the [mini-coco128](https://drive.google.com/file/d/16N3u36ycNd70m23IM7vMuRQXejAJY9Fs/view?usp=sharing) and unzip it to the `datasets`, you can direct run the following training code.
|
81 |
+
```bash
|
82 |
+
python tools/train.py -f exps/example/custom/yolox_s.py -d 8 -b 64 --fp16 -o -c /path/to/yolox_s.pth.tar
|
83 |
+
```
|
84 |
+
|
85 |
(Don't worry for the different shape of detection head between the pretrained weights and your own model, we will handle it)
|
86 |
|
87 |
## 4. Tips for Best Training Results
|
exps/example/custom/nano.py
ADDED
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
# -*- coding:utf-8 -*-
|
3 |
+
# Copyright (c) Megvii, Inc. and its affiliates.
|
4 |
+
|
5 |
+
import os
|
6 |
+
import torch.nn as nn
|
7 |
+
|
8 |
+
from yolox.exp import Exp as MyExp
|
9 |
+
|
10 |
+
|
11 |
+
class Exp(MyExp):
|
12 |
+
def __init__(self):
|
13 |
+
super(Exp, self).__init__()
|
14 |
+
self.depth = 0.33
|
15 |
+
self.width = 0.25
|
16 |
+
self.scale = (0.5, 1.5)
|
17 |
+
self.random_size = (10, 20)
|
18 |
+
self.test_size = (416, 416)
|
19 |
+
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
20 |
+
self.enable_mixup = False
|
21 |
+
|
22 |
+
# Define yourself dataset path
|
23 |
+
self.data_dir = "datasets/coco128"
|
24 |
+
self.train_ann = "instances_train2017.json"
|
25 |
+
self.val_ann = "instances_val2017.json"
|
26 |
+
|
27 |
+
self.num_classes = 71
|
28 |
+
|
29 |
+
def get_model(self, sublinear=False):
|
30 |
+
|
31 |
+
def init_yolo(M):
|
32 |
+
for m in M.modules():
|
33 |
+
if isinstance(m, nn.BatchNorm2d):
|
34 |
+
m.eps = 1e-3
|
35 |
+
m.momentum = 0.03
|
36 |
+
if "model" not in self.__dict__:
|
37 |
+
from yolox.models import YOLOX, YOLOPAFPN, YOLOXHead
|
38 |
+
in_channels = [256, 512, 1024]
|
39 |
+
# NANO model use depthwise = True, which is main difference.
|
40 |
+
backbone = YOLOPAFPN(self.depth, self.width, in_channels=in_channels, depthwise=True)
|
41 |
+
head = YOLOXHead(self.num_classes, self.width, in_channels=in_channels, depthwise=True)
|
42 |
+
self.model = YOLOX(backbone, head)
|
43 |
+
|
44 |
+
self.model.apply(init_yolo)
|
45 |
+
self.model.head.initialize_biases(1e-2)
|
46 |
+
return self.model
|
exps/example/custom/yolox_s.py
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
#!/usr/bin/env python3
|
2 |
+
# -*- coding:utf-8 -*-
|
3 |
+
# Copyright (c) Megvii, Inc. and its affiliates.
|
4 |
+
import os
|
5 |
+
from pathlib import Path
|
6 |
+
|
7 |
+
from yolox.exp import Exp as MyExp
|
8 |
+
|
9 |
+
|
10 |
+
class Exp(MyExp):
|
11 |
+
def __init__(self):
|
12 |
+
super(Exp, self).__init__()
|
13 |
+
self.depth = 0.33
|
14 |
+
self.width = 0.50
|
15 |
+
self.exp_name = os.path.split(os.path.realpath(__file__))[1].split(".")[0]
|
16 |
+
|
17 |
+
# Define yourself dataset path
|
18 |
+
self.data_dir = "datasets/coco128"
|
19 |
+
self.train_ann = "instances_train2017.json"
|
20 |
+
self.val_ann = "instances_val2017.json"
|
21 |
+
|
22 |
+
self.num_classes = 71
|
23 |
+
|
24 |
+
self.max_epoch = 300
|
25 |
+
self.data_num_workers = 4
|
26 |
+
self.eval_interval = 1
|
yolox/evaluators/coco_evaluator.py
CHANGED
@@ -83,7 +83,7 @@ class COCOEvaluator:
|
|
83 |
|
84 |
inference_time = 0
|
85 |
nms_time = 0
|
86 |
-
n_samples = len(self.dataloader) - 1
|
87 |
|
88 |
if trt_file is not None:
|
89 |
from torch2trt import TRTModule
|
|
|
83 |
|
84 |
inference_time = 0
|
85 |
nms_time = 0
|
86 |
+
n_samples = max(len(self.dataloader) - 1, 1)
|
87 |
|
88 |
if trt_file is not None:
|
89 |
from torch2trt import TRTModule
|
yolox/exp/yolox_base.py
CHANGED
@@ -2,17 +2,18 @@
|
|
2 |
# -*- coding:utf-8 -*-
|
3 |
# Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
|
4 |
|
|
|
|
|
|
|
5 |
import torch
|
6 |
import torch.distributed as dist
|
7 |
import torch.nn as nn
|
8 |
|
9 |
-
import os
|
10 |
-
import random
|
11 |
-
|
12 |
from .base_exp import BaseExp
|
13 |
|
14 |
|
15 |
class Exp(BaseExp):
|
|
|
16 |
def __init__(self):
|
17 |
super().__init__()
|
18 |
|
@@ -26,6 +27,7 @@ class Exp(BaseExp):
|
|
26 |
self.data_num_workers = 4
|
27 |
self.input_size = (640, 640)
|
28 |
self.random_size = (14, 26)
|
|
|
29 |
self.train_ann = "instances_train2017.json"
|
30 |
self.val_ann = "instances_val2017.json"
|
31 |
|
@@ -60,7 +62,7 @@ class Exp(BaseExp):
|
|
60 |
self.nmsthre = 0.65
|
61 |
|
62 |
def get_model(self):
|
63 |
-
from yolox.models import
|
64 |
|
65 |
def init_yolo(M):
|
66 |
for m in M.modules():
|
@@ -81,15 +83,15 @@ class Exp(BaseExp):
|
|
81 |
def get_data_loader(self, batch_size, is_distributed, no_aug=False):
|
82 |
from yolox.data import (
|
83 |
COCODataset,
|
|
|
|
|
84 |
DataLoader,
|
85 |
InfiniteSampler,
|
86 |
MosaicDetection,
|
87 |
-
TrainTransform,
|
88 |
-
YoloBatchSampler
|
89 |
)
|
90 |
|
91 |
dataset = COCODataset(
|
92 |
-
data_dir=
|
93 |
json_file=self.train_ann,
|
94 |
img_size=self.input_size,
|
95 |
preproc=TrainTransform(
|
@@ -121,7 +123,9 @@ class Exp(BaseExp):
|
|
121 |
if is_distributed:
|
122 |
batch_size = batch_size // dist.get_world_size()
|
123 |
|
124 |
-
sampler = InfiniteSampler(
|
|
|
|
|
125 |
|
126 |
batch_sampler = YoloBatchSampler(
|
127 |
sampler=sampler,
|
@@ -141,7 +145,7 @@ class Exp(BaseExp):
|
|
141 |
tensor = torch.LongTensor(2).cuda()
|
142 |
|
143 |
if rank == 0:
|
144 |
-
size_factor = self.input_size[1] * 1.
|
145 |
size = random.randint(*self.random_size)
|
146 |
size = (int(32 * size), 32 * int(size * size_factor))
|
147 |
tensor[0] = size[0]
|
@@ -186,7 +190,6 @@ class Exp(BaseExp):
|
|
186 |
|
187 |
def get_lr_scheduler(self, lr, iters_per_epoch):
|
188 |
from yolox.utils import LRScheduler
|
189 |
-
|
190 |
scheduler = LRScheduler(
|
191 |
self.scheduler,
|
192 |
lr,
|
@@ -203,7 +206,7 @@ class Exp(BaseExp):
|
|
203 |
from yolox.data import COCODataset, ValTransform
|
204 |
|
205 |
valdataset = COCODataset(
|
206 |
-
data_dir=
|
207 |
json_file=self.val_ann if not testdev else "image_info_test-dev2017.json",
|
208 |
name="val2017" if not testdev else "test2017",
|
209 |
img_size=self.test_size,
|
|
|
2 |
# -*- coding:utf-8 -*-
|
3 |
# Copyright (c) 2014-2021 Megvii Inc. All rights reserved.
|
4 |
|
5 |
+
import os
|
6 |
+
import random
|
7 |
+
|
8 |
import torch
|
9 |
import torch.distributed as dist
|
10 |
import torch.nn as nn
|
11 |
|
|
|
|
|
|
|
12 |
from .base_exp import BaseExp
|
13 |
|
14 |
|
15 |
class Exp(BaseExp):
|
16 |
+
|
17 |
def __init__(self):
|
18 |
super().__init__()
|
19 |
|
|
|
27 |
self.data_num_workers = 4
|
28 |
self.input_size = (640, 640)
|
29 |
self.random_size = (14, 26)
|
30 |
+
self.data_dir = None
|
31 |
self.train_ann = "instances_train2017.json"
|
32 |
self.val_ann = "instances_val2017.json"
|
33 |
|
|
|
62 |
self.nmsthre = 0.65
|
63 |
|
64 |
def get_model(self):
|
65 |
+
from yolox.models import YOLOX, YOLOPAFPN, YOLOXHead
|
66 |
|
67 |
def init_yolo(M):
|
68 |
for m in M.modules():
|
|
|
83 |
def get_data_loader(self, batch_size, is_distributed, no_aug=False):
|
84 |
from yolox.data import (
|
85 |
COCODataset,
|
86 |
+
TrainTransform,
|
87 |
+
YoloBatchSampler,
|
88 |
DataLoader,
|
89 |
InfiniteSampler,
|
90 |
MosaicDetection,
|
|
|
|
|
91 |
)
|
92 |
|
93 |
dataset = COCODataset(
|
94 |
+
data_dir=self.data_dir,
|
95 |
json_file=self.train_ann,
|
96 |
img_size=self.input_size,
|
97 |
preproc=TrainTransform(
|
|
|
123 |
if is_distributed:
|
124 |
batch_size = batch_size // dist.get_world_size()
|
125 |
|
126 |
+
sampler = InfiniteSampler(
|
127 |
+
len(self.dataset), seed=self.seed if self.seed else 0
|
128 |
+
)
|
129 |
|
130 |
batch_sampler = YoloBatchSampler(
|
131 |
sampler=sampler,
|
|
|
145 |
tensor = torch.LongTensor(2).cuda()
|
146 |
|
147 |
if rank == 0:
|
148 |
+
size_factor = self.input_size[1] * 1. / self.input_size[0]
|
149 |
size = random.randint(*self.random_size)
|
150 |
size = (int(32 * size), 32 * int(size * size_factor))
|
151 |
tensor[0] = size[0]
|
|
|
190 |
|
191 |
def get_lr_scheduler(self, lr, iters_per_epoch):
|
192 |
from yolox.utils import LRScheduler
|
|
|
193 |
scheduler = LRScheduler(
|
194 |
self.scheduler,
|
195 |
lr,
|
|
|
206 |
from yolox.data import COCODataset, ValTransform
|
207 |
|
208 |
valdataset = COCODataset(
|
209 |
+
data_dir=self.data_dir,
|
210 |
json_file=self.val_ann if not testdev else "image_info_test-dev2017.json",
|
211 |
name="val2017" if not testdev else "test2017",
|
212 |
img_size=self.test_size,
|