Threadpool fixes and CLI improvements (#550)

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
Co-authored-by: Ayush Chaurasia <ayush.chaurarsia@gmail.com>
single_channel
Glenn Jocher 2 years ago committed by GitHub
parent d9a0fba251
commit 21b701c4ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2,40 +2,51 @@ YOLO settings and hyperparameters play a critical role in the model's performanc
and hyperparameters can affect the model's behavior at various stages of the model development process, including
training, validation, and prediction.
Properly setting and tuning these parameters can have a significant impact on the model's ability to learn effectively
from the training data and generalize to new data. For example, choosing an appropriate learning rate, batch size, and
optimization algorithm can greatly affect the model's convergence speed and accuracy. Similarly, setting the correct
confidence threshold and non-maximum suppression (NMS) threshold can affect the model's performance on detection tasks.
YOLOv8 'yolo' CLI commands use the following syntax:
It is important to carefully consider and experiment with these settings and hyperparameters to achieve the best
possible performance for a given task. This can involve trial and error, as well as using techniques such as
hyperparameter optimization to search for the optimal set of parameters.
!!! example ""
In summary, YOLO settings and hyperparameters are a key factor in the success of a YOLO model, and it is important to
pay careful attention to them to achieve the desired results.
=== "CLI"
### Setting the operation type
```bash
yolo TASK MODE ARGS
```
Where:
- `TASK` (optional) is one of `[detect, segment, classify]`. If it is not passed explicitly YOLOv8 will try to guess
the `TASK` from the model type.
- `MODE` (required) is one of `[train, val, predict, export]`
- `ARGS` (optional) are any number of custom `arg=value` pairs like `imgsz=320` that override defaults.
For a full list of available `ARGS` see the [Configuration](cfg.md) page and `defaults.yaml`
GitHub [source](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/cfg/default.yaml).
#### Tasks
YOLO models can be used for a variety of tasks, including detection, segmentation, and classification. These tasks
differ in the type of output they produce and the specific problem they are designed to solve.
- Detection: Detection tasks involve identifying and localizing objects or regions of interest in an image or video.
- **Detect**: Detection tasks involve identifying and localizing objects or regions of interest in an image or video.
YOLO models can be used for object detection tasks by predicting the bounding boxes and class labels of objects in an
image.
- Segmentation: Segmentation tasks involve dividing an image or video into regions or pixels that correspond to
- **Segment**: Segmentation tasks involve dividing an image or video into regions or pixels that correspond to
different objects or classes. YOLO models can be used for image segmentation tasks by predicting a mask or label for
each pixel in an image.
- Classification: Classification tasks involve assigning a class label to an input, such as an image or text. YOLO
- **Classify**: Classification tasks involve assigning a class label to an input, such as an image or text. YOLO
models can be used for image classification tasks by predicting the class label of an input image.
#### Modes
YOLO models can be used in different modes depending on the specific problem you are trying to solve. These modes
include train, val, and predict.
- Train: The train mode is used to train the model on a dataset. This mode is typically used during the development and
- **Train**: The train mode is used to train the model on a dataset. This mode is typically used during the development
and
testing phase of a model.
- Val: The val mode is used to evaluate the model's performance on a validation dataset. This mode is typically used to
- **Val**: The val mode is used to evaluate the model's performance on a validation dataset. This mode is typically used
to
tune the model's hyperparameters and detect overfitting.
- Predict: The predict mode is used to make predictions with the model on new data. This mode is typically used in
- **Predict**: The predict mode is used to make predictions with the model on new data. This mode is typically used in
production or when deploying the model to users.
| Key | Value | Description |

@ -17,7 +17,8 @@ Where:
the `TASK` from the model type.
- `MODE` (required) is one of `[train, val, predict, export]`
- `ARGS` (optional) are any number of custom `arg=value` pairs like `imgsz=320` that override defaults.
For a full list of available `ARGS` see the [Configuration](cfg.md) page.
For a full list of available `ARGS` see the [Configuration](cfg.md) page and `defaults.yaml`
GitHub [source](https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/cfg/default.yaml).
!!! note ""

@ -1,53 +1,67 @@
Inference or prediction of a task returns a list of `Results` objects. Alternatively, in the streaming mode, it returns a generator of `Results` objects which is memory efficient. Streaming mode can be enabled by passing `stream=True` in predictor's call method.
Inference or prediction of a task returns a list of `Results` objects. Alternatively, in the streaming mode, it returns
a generator of `Results` objects which is memory efficient. Streaming mode can be enabled by passing `stream=True` in
predictor's call method.
!!! example "Predict"
=== "Getting a List"
```python
inputs = [img, img] # list of np arrays
results = model(inputs) # List of Results objects
for result in results:
boxes = result.boxes # Boxes object for bbox outputs
masks = result.masks # Masks object for segmenation masks outputs
probs = result.probs # Class probabilities for classification outputs
...
```
=== "Getting a Generator"
```python
inputs = [img, img] # list of np arrays
results = model(inputs, stream=True) # Generator of Results objects
for result in results:
boxes = result.boxes # Boxes object for bbox outputs
masks = result.masks # Masks object for segmenation masks outputs
probs = result.probs # Class probabilities for classification outputs
...
inputs = [img, img] # list of numpy arrays
results = model(inputs, stream=True) # generator of Results objects
for r in results:
boxes = r.boxes # Boxes object for bbox outputs
masks = r.masks # Masks object for segmenation masks outputs
probs = r.probs # Class probabilities for classification outputs
```
## Working with Results
Results object consists of these component objects:
- `results.boxes` : It is an object of class `Boxes`. It has properties and methods for manipulating bboxes
- `results.masks` : It is an object of class `Masks`. It can be used to index masks or to get segment coordinates.
- `results.prob` : It is a `Tensor` object. It contains the class probabilities/logits.
- `Results.boxes` : `Boxes` object with properties and methods for manipulating bboxes
- `Results.masks` : `Masks` object used to index masks or to get segment coordinates.
- `Results.prob` : `torch.Tensor` containing the class probabilities/logits.
Each result is composed of torch.Tensor by default, in which you can easily use following functionality:
```python
results = results.cuda()
results = results.cpu()
results = results.to("cpu")
results = results.numpy()
```
### Boxes
`Boxes` object can be used index, manipulate and convert bboxes to different formats. The box format conversion operations are cached, which means they're only calculated once per object and those values are reused for future calls.
`Boxes` object can be used index, manipulate and convert bboxes to different formats. The box format conversion
operations are cached, which means they're only calculated once per object and those values are reused for future calls.
- Indexing a `Boxes` objects returns a `Boxes` object
```python
boxes = results.boxes
results = model(inputs)
boxes = results[0].boxes
box = boxes[0] # returns one box
box.xyxy
```
- Properties and conversions
```
```python
boxes.xyxy # box with xyxy format, (N, 4)
boxes.xywh # box with xywh format, (N, 4)
boxes.xyxyn # box with xyxy format but normalized, (N, 4)
@ -56,19 +70,25 @@ boxes.conf # confidence score, (N, 1)
boxes.cls # cls, (N, 1)
boxes.data # raw bboxes tensor, (N, 6) or boxes.boxes .
```
### Masks
`Masks` object can be used index, manipulate and convert masks to segments. The segment conversion operation is cached.
```python
masks = results.masks # Masks object
results = model(inputs)
masks = results[0].masks # Masks object
masks.segments # bounding coordinates of masks, List[segment] * N
masks.data # raw masks tensor, (N, H, W) or masks.masks
```
### probs
`probs` attribute of `Results` class is a `Tensor` containing class probabilities of a classification operation.
```python
results.probs # cls prob, (num_class, )
results = model(inputs)
results[0].probs # cls prob, (num_class, )
```
Class reference documentation for `Results` module and its components can be found [here](reference/results.md)

@ -90,8 +90,8 @@ nav:
- Ultralytics HUB: hub.md
- iOS and Android App: app.md
- Reference:
- Python Model interface: reference/model.md
- Engine:
- Model: reference/model.md
- Trainer: reference/base_trainer.md
- Validator: reference/base_val.md
- Predictor: reference/base_pred.md

@ -1,5 +1,5 @@
# Ultralytics YOLO 🚀, GPL-3.0 license
import signal
from pathlib import Path
from time import sleep
@ -13,22 +13,6 @@ AGENT_NAME = f'python-{__version__}-colab' if is_colab() else f'python-{__versio
session = None
# Causing problems in tests (non-authenticated)
# import signal
# import sys
# def signal_handler(signum, frame):
# """ Confirm exit """
# global hub_logger
# LOGGER.info(f'Signal received. {signum} {frame}')
# if isinstance(session, HubTrainingSession):
# hub_logger.alive = False
# del hub_logger
# sys.exit(signum)
#
#
# signal.signal(signal.SIGTERM, signal_handler)
# signal.signal(signal.SIGINT, signal_handler)
class HubTrainingSession:
@ -43,10 +27,11 @@ class HubTrainingSession:
self.alive = True # for heartbeats
self.model = self._get_model()
self._heartbeats() # start heartbeats
signal.signal(signal.SIGTERM, self.shutdown) # register the shutdown function to be called on exit
signal.signal(signal.SIGINT, self.shutdown)
def __del__(self):
# Class destructor
self.alive = False
def shutdown(self, *args): # noqa
self.alive = False # stop heartbeats
def upload_metrics(self):
payload = {"metrics": self.metrics_queue.copy(), "type": "metrics"}
@ -100,13 +85,6 @@ class HubTrainingSession:
if not check_dataset_disk_space(self.model['data']):
raise MemoryError("Not enough disk space")
# COMMENT: Should not be needed as HUB is now considered an integration and is in integrations_callbacks
# import ultralytics.yolo.utils.callbacks.hub as hub_callbacks
# @staticmethod
# def register_callbacks(trainer):
# for k, v in hub_callbacks.callbacks.items():
# trainer.add_callback(k, v)
@threaded
def _heartbeats(self):
while self.alive:

@ -4,6 +4,7 @@ import os
import shutil
import threading
import time
from random import random
import requests
@ -14,7 +15,7 @@ HELP_MSG = 'If this issue persists please visit https://github.com/ultralytics/h
HUB_API_ROOT = os.environ.get("ULTRALYTICS_HUB_API", "https://api.ultralytics.com")
def check_dataset_disk_space(url='https://github.com/ultralytics/yolov5/releases/download/v1.0/coco128.zip', sf=2.0):
def check_dataset_disk_space(url='https://ultralytics.com/assets/coco128.zip', sf=2.0):
# Check that url fits on disk with safety factor sf, i.e. require 2GB free if url size is 1GB with sf=2.0
gib = 1 << 30 # bytes per GiB
data = int(requests.head(url).headers['Content-Length']) / gib # dataset size (GB)
@ -130,18 +131,18 @@ def smart_request(*args, retry=3, timeout=30, thread=True, code=-1, method="post
return func(*args, **kwargs)
@TryExcept()
def sync_analytics(cfg, all_keys=False, enabled=False):
@TryExcept(verbose=False)
def traces(cfg, all_keys=False, traces_sample_rate=0.0):
"""
Sync analytics data if enabled in the global settings
Sync traces data if enabled in the global settings
Args:
cfg (UltralyticsCFG): Configuration for the task and mode.
cfg (IterableSimpleNamespace): Configuration for the task and mode.
all_keys (bool): Sync all items, not just non-default values.
enabled (bool): For debugging.
traces_sample_rate (float): Fraction of traces captured from 0.0 to 1.0
"""
if SETTINGS['sync'] and RANK in {-1, 0} and enabled:
cfg = dict(cfg) # convert type from UltralyticsCFG to dict
if SETTINGS['sync'] and RANK in {-1, 0} and (random() < traces_sample_rate):
cfg = vars(cfg) # convert type from IterableSimpleNamespace to dict
if not all_keys:
cfg = {k: v for k, v in cfg.items() if v != DEFAULT_CFG_DICT.get(k, None)} # retain non-default values
cfg['uuid'] = SETTINGS['uuid'] # add the device UUID to the configuration data

@ -1,5 +1,4 @@
# Ultralytics YOLO 🚀, GPL-3.0 license
import argparse
import re
import shutil
import sys
@ -9,46 +8,39 @@ from types import SimpleNamespace
from typing import Dict, Union
from ultralytics import __version__, yolo
from ultralytics.yolo.utils import DEFAULT_CFG_PATH, LOGGER, PREFIX, checks, colorstr, print_settings, yaml_load
DIR = Path(__file__).parent
from ultralytics.yolo.utils import (DEFAULT_CFG_DICT, DEFAULT_CFG_PATH, LOGGER, PREFIX, USER_CONFIG_DIR,
IterableSimpleNamespace, checks, colorstr, yaml_load, yaml_print)
CLI_HELP_MSG = \
"""
YOLOv8 CLI Usage examples:
1. Install the ultralytics package:
pip install ultralytics
2. Train, Val, Predict and Export using 'yolo' commands:
YOLOv8 'yolo' CLI commands use the following syntax:
yolo TASK MODE ARGS
Where TASK (optional) is one of [detect, segment, classify]
MODE (required) is one of [train, val, predict, export]
ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults.
For a full list of available ARGS see https://docs.ultralytics.com/cfg.
See all ARGS at https://docs.ultralytics.com/cfg or with 'yolo cfg'
Train a detection model for 10 epochs with an initial learning_rate of 0.01
1. Train a detection model for 10 epochs with an initial learning_rate of 0.01
yolo detect train data=coco128.yaml model=yolov8n.pt epochs=10 lr0=0.01
Predict a YouTube video using a pretrained segmentation model at image size 320:
2. Predict a YouTube video using a pretrained segmentation model at image size 320:
yolo segment predict model=yolov8n-seg.pt source=https://youtu.be/Zgi9g1ksQHc imgsz=320
Validate a pretrained detection model at batch-size 1 and image size 640:
3. Val a pretrained detection model at batch-size 1 and image size 640:
yolo detect val model=yolov8n.pt data=coco128.yaml batch=1 imgsz=640
Export a YOLOv8n classification model to ONNX format at image size 224 by 128 (no TASK required)
4. Export a YOLOv8n classification model to ONNX format at image size 224 by 128 (no TASK required)
yolo export model=yolov8n-cls.pt format=onnx imgsz=224,128
3. Run special commands:
5. Run special commands:
yolo help
yolo checks
yolo version
yolo settings
yolo copy-cfg
yolo cfg
Docs: https://docs.ultralytics.com/cli
Community: https://community.ultralytics.com
@ -56,15 +48,6 @@ CLI_HELP_MSG = \
"""
class UltralyticsCFG(SimpleNamespace):
"""
UltralyticsCFG iterable SimpleNamespace class to allow SimpleNamespace to be used with dict() and in for loops
"""
def __iter__(self):
return iter(vars(self).items())
def cfg2dict(cfg):
"""
Convert a configuration object to a dictionary.
@ -104,7 +87,7 @@ def get_cfg(cfg: Union[str, Path, Dict, SimpleNamespace], overrides: Dict = None
cfg = {**cfg, **overrides} # merge cfg and overrides dicts (prefer overrides)
# Return instance
return UltralyticsCFG(**cfg)
return IterableSimpleNamespace(**cfg)
def check_cfg_mismatch(base: Dict, custom: Dict):
@ -118,12 +101,19 @@ def check_cfg_mismatch(base: Dict, custom: Dict):
"""
base, custom = (set(x.keys()) for x in (base, custom))
mismatched = [x for x in custom if x not in base]
for option in mismatched:
LOGGER.info(f"{colorstr(option)} is not a valid key. Similar keys: {get_close_matches(option, base, 3, 0.6)}")
if mismatched:
for x in mismatched:
matches = get_close_matches(x, base, 3, 0.6)
match_str = f"Similar arguments are {matches}." if matches else 'There are no similar arguments.'
LOGGER.warning(f"'{colorstr('red', 'bold', x)}' is not a valid YOLO argument. {match_str}")
LOGGER.warning(CLI_HELP_MSG)
sys.exit()
def argument_error(arg):
return SyntaxError(f"'{arg}' is not a valid YOLO argument.\n{CLI_HELP_MSG}")
def entrypoint(debug=False):
"""
This function is the ultralytics package entrypoint, it's responsible for parsing the command line arguments passed
@ -139,67 +129,61 @@ def entrypoint(debug=False):
It uses the package's default cfg and initializes it using the passed overrides.
Then it calls the CLI function with the composed cfg
"""
if debug:
args = ['train', 'predict', 'model=yolov8n.pt'] # for testing
else:
if len(sys.argv) == 1: # no arguments passed
args = ['train', 'predict', 'model=yolov8n.pt'] if debug else sys.argv[1:]
if not args: # no arguments passed
LOGGER.info(CLI_HELP_MSG)
return
parser = argparse.ArgumentParser(description='YOLO parser')
parser.add_argument('args', type=str, nargs='+', help='YOLO args')
args = parser.parse_args().args
args = re.sub(r'\s*=\s*', '=', ' '.join(args)).split(' ') # remove whitespaces around = sign
tasks = 'detect', 'segment', 'classify'
modes = 'train', 'val', 'predict', 'export'
special_modes = {
special = {
'help': lambda: LOGGER.info(CLI_HELP_MSG),
'checks': checks.check_yolo,
'version': lambda: LOGGER.info(__version__),
'settings': print_settings,
'settings': lambda: yaml_print(USER_CONFIG_DIR / 'settings.yaml'),
'cfg': lambda: yaml_print(DEFAULT_CFG_PATH),
'copy-cfg': copy_default_config}
overrides = {} # basic overrides, i.e. imgsz=320
defaults = yaml_load(DEFAULT_CFG_PATH)
for a in args:
if '=' in a:
if a.startswith('cfg='): # custom.yaml passed
custom_config = Path(a.split('=')[-1])
LOGGER.info(f"{PREFIX}Overriding {DEFAULT_CFG_PATH} with {custom_config}")
overrides = {k: v for k, v in yaml_load(custom_config).items() if k not in {'cfg'}}
else:
k, v = a.split('=')
try:
if k == 'device': # special DDP handling, i.e. device='0,1,2,3'
v = v.replace('[', '').replace(']', '') # handle device=[0,1,2,3]
v = v.replace(" ", "") # handle device=[0, 1, 2, 3]
v = v.replace('\\', '') # handle device=\'0,1,2,3\'
overrides[k] = v
re.sub(r' *= *', '=', a) # remove spaces around equals sign
k, v = a.split('=')
if k == 'cfg': # custom.yaml passed
LOGGER.info(f"{PREFIX}Overriding {DEFAULT_CFG_PATH} with {v}")
overrides = {k: val for k, val in yaml_load(v).items() if k != 'cfg'}
else:
overrides[k] = eval(v) # convert strings to integers, floats, bools, etc.
except (NameError, SyntaxError):
if v.isnumeric():
v = eval(v)
elif v.lower() == 'none':
v = None
elif v.lower() == 'true':
v = True
elif v.lower() == 'false':
v = False
elif ',' in v:
v = eval(v)
overrides[k] = v
except (NameError, SyntaxError, ValueError) as e:
raise argument_error(a) from e
elif a in tasks:
overrides['task'] = a
elif a in modes:
overrides['mode'] = a
elif a in special_modes:
special_modes[a]()
elif a in special:
special[a]()
return
elif a in defaults and defaults[a] is False:
elif a in DEFAULT_CFG_DICT and DEFAULT_CFG_DICT[a] is False:
overrides[a] = True # auto-True for default False args, i.e. 'yolo show' sets show=True
elif a in defaults:
raise SyntaxError(f"'{a}' is a valid YOLO argument but is missing an '=' sign to set its value, "
f"i.e. try '{a}={defaults[a]}'"
f"\n{CLI_HELP_MSG}")
elif a in DEFAULT_CFG_DICT:
raise SyntaxError(f"'{colorstr('red', 'bold', a)}' is a valid YOLO argument but is missing an '=' sign "
f"to set its value, i.e. try '{a}={DEFAULT_CFG_DICT[a]}'\n{CLI_HELP_MSG}")
else:
raise SyntaxError(
f"'{a}' is not a valid YOLO argument. For a full list of valid arguments see "
f"https://github.com/ultralytics/ultralytics/blob/main/ultralytics/yolo/configs/default.yaml"
f"\n{CLI_HELP_MSG}")
raise argument_error(a)
cfg = get_cfg(defaults, overrides) # create CFG instance
cfg = get_cfg(DEFAULT_CFG_DICT, overrides) # create CFG instance
# Mapping from task to module
module = {"detect": yolo.v8.detect, "segment": yolo.v8.segment, "classify": yolo.v8.classify}.get(cfg.task)
@ -223,8 +207,8 @@ def copy_default_config():
new_file = Path.cwd() / DEFAULT_CFG_PATH.name.replace('.yaml', '_copy.yaml')
shutil.copy2(DEFAULT_CFG_PATH, new_file)
LOGGER.info(f"{PREFIX}{DEFAULT_CFG_PATH} copied to {new_file}\n"
f"Usage for running YOLO with this new custom cfg:\nyolo cfg={new_file} args...")
f"Example YOLO command with this new custom cfg:\n yolo cfg='{new_file}' imgsz=320 batch=8")
if __name__ == '__main__':
entrypoint()
entrypoint(debug=True)

@ -93,7 +93,7 @@ class BaseDataset(Dataset):
# self.img_files = sorted([x for x in f if x.suffix[1:].lower() in IMG_FORMATS]) # pathlib
assert im_files, f"{self.prefix}No images found"
except Exception as e:
raise FileNotFoundError(f"{self.prefix}Error loading data from {img_path}: {e}\n{HELP_URL}") from e
raise FileNotFoundError(f"{self.prefix}Error loading data from {img_path}\n{HELP_URL}") from e
return im_files
def update_labels(self, include_class: Optional[list]):
@ -134,7 +134,8 @@ class BaseDataset(Dataset):
gb = 0 # Gigabytes of cached images
self.im_hw0, self.im_hw = [None] * self.ni, [None] * self.ni
fcn = self.cache_images_to_disk if cache == "disk" else self.load_image
results = ThreadPool(NUM_THREADS).imap(fcn, range(self.ni))
with ThreadPool(NUM_THREADS) as pool:
results = pool.imap(fcn, range(self.ni))
pbar = tqdm(enumerate(results), total=self.ni, bar_format=TQDM_BAR_FORMAT, disable=LOCAL_RANK > 0)
for i, x in pbar:
if cache == "disk":

@ -13,7 +13,7 @@ import random
import shutil
import time
from itertools import repeat
from multiprocessing.pool import Pool, ThreadPool
from multiprocessing.pool import ThreadPool
from pathlib import Path
from threading import Thread
from urllib.parse import urlparse
@ -580,7 +580,7 @@ class LoadImagesAndLabels(Dataset):
b, gb = 0, 1 << 30 # bytes of cached images, bytes per gigabytes
self.im_hw0, self.im_hw = [None] * n, [None] * n
fcn = self.cache_images_to_disk if cache_images == 'disk' else self.load_image
with (Pool if n > 10000 else ThreadPool)(NUM_THREADS) as pool:
with ThreadPool(NUM_THREADS) as pool:
results = pool.imap(fcn, range(n))
pbar = tqdm(enumerate(results), total=n, bar_format=TQDM_BAR_FORMAT, disable=LOCAL_RANK > 0)
for i, x in pbar:
@ -1150,7 +1150,7 @@ class HUBDatasetStats():
dataset = LoadImagesAndLabels(self.data[split]) # load dataset
desc = f'{split} images'
total = dataset.n
with (Pool if total > 10000 else ThreadPool)(NUM_THREADS) as pool:
with ThreadPool(NUM_THREADS) as pool:
for _ in tqdm(pool.imap(self._hub_ops, dataset.im_files), total=total, desc=desc):
pass
print(f'Done. All images saved to {self.im_dir}')

@ -185,9 +185,9 @@ def polygons2masks_overlap(imgsz, segments, downsample_ratio=1):
return masks, index
def check_dataset_yaml(data, autodownload=True):
def check_dataset_yaml(dataset, autodownload=True):
# Download, check and/or unzip dataset if not found locally
data = check_file(data)
data = check_file(dataset)
# Download (optional)
extract_dir = ''
@ -227,9 +227,11 @@ def check_dataset_yaml(data, autodownload=True):
if val:
val = [Path(x).resolve() for x in (val if isinstance(val, list) else [val])] # val path
if not all(x.exists() for x in val):
LOGGER.info('\nDataset not found ⚠️, missing paths %s' % [str(x) for x in val if not x.exists()])
if not s or not autodownload:
raise FileNotFoundError('Dataset not found ❌')
msg = f"\nDataset '{dataset}' not found ⚠️, missing paths %s" % [str(x) for x in val if not x.exists()]
if s and autodownload:
LOGGER.warning(msg)
else:
raise FileNotFoundError(s)
t = time.time()
if s.startswith('http') and s.endswith('.zip'): # URL
f = Path(s).name # filename

@ -126,15 +126,15 @@ class Exporter:
save_dir (Path): Directory to save results.
"""
def __init__(self, config=DEFAULT_CFG, overrides=None):
def __init__(self, cfg=DEFAULT_CFG, overrides=None):
"""
Initializes the Exporter class.
Args:
config (str, optional): Path to a configuration file. Defaults to DEFAULT_CONFIG.
cfg (str, optional): Path to a configuration file. Defaults to DEFAULT_CONFIG.
overrides (dict, optional): Configuration overrides. Defaults to None.
"""
self.args = get_cfg(config, overrides)
self.args = get_cfg(cfg, overrides)
self.callbacks = defaultdict(list, {k: [v] for k, v in callbacks.default_callbacks.items()}) # add callbacks
callbacks.add_integration_callbacks(self)
@ -151,7 +151,7 @@ class Exporter:
# Load PyTorch model
self.device = select_device('cpu' if self.args.device is None else self.args.device)
if self.args.half:
if self.device.type == 'cpu' and not coreml:
if self.device.type == 'cpu' and not coreml and not xml:
LOGGER.info('half=True only compatible with GPU or CoreML export, i.e. use device=0 or format=coreml')
self.args.half = False
assert not self.args.dynamic, '--half not compatible with --dynamic, i.e. use either --half or --dynamic'
@ -184,7 +184,7 @@ class Exporter:
y = None
for _ in range(2):
y = model(im) # dry runs
if self.args.half and not coreml:
if self.args.half and not coreml and not xml:
im, model = im.half(), model.half() # to FP16
shape = tuple((y[0] if isinstance(y, tuple) else y).shape) # model output shape
LOGGER.info(
@ -332,7 +332,7 @@ class Exporter:
f = str(self.file).replace(self.file.suffix, f'_openvino_model{os.sep}')
f_onnx = self.file.with_suffix('.onnx')
cmd = f"mo --input_model {f_onnx} --output_dir {f} --data_type {'FP16' if self.args.half else 'FP32'}"
cmd = f"mo --input_model {f_onnx} --output_dir {f} {'--compress_to_fp16' * self.args.half}"
subprocess.run(cmd.split(), check=True, env=os.environ) # export
yaml_save(Path(f) / self.file.with_suffix('.yaml').name, self.metadata) # add metadata.yaml
return f, None

@ -6,7 +6,7 @@ from ultralytics import yolo # noqa
from ultralytics.nn.tasks import ClassificationModel, DetectionModel, SegmentationModel, attempt_load_one_weight
from ultralytics.yolo.cfg import get_cfg
from ultralytics.yolo.engine.exporter import Exporter
from ultralytics.yolo.utils import DEFAULT_CFG_PATH, LOGGER, yaml_load
from ultralytics.yolo.utils import DEFAULT_CFG, LOGGER, yaml_load
from ultralytics.yolo.utils.checks import check_yaml
from ultralytics.yolo.utils.torch_utils import guess_task_from_head, smart_inference_mode
@ -151,7 +151,7 @@ class YOLO:
overrides = self.overrides.copy()
overrides.update(kwargs)
overrides["mode"] = "val"
args = get_cfg(cfg=DEFAULT_CFG_PATH, overrides=overrides)
args = get_cfg(cfg=DEFAULT_CFG, overrides=overrides)
args.data = data or args.data
args.task = self.task
@ -169,7 +169,7 @@ class YOLO:
overrides = self.overrides.copy()
overrides.update(kwargs)
args = get_cfg(cfg=DEFAULT_CFG_PATH, overrides=overrides)
args = get_cfg(cfg=DEFAULT_CFG, overrides=overrides)
args.task = self.task
print(args)
@ -181,8 +181,7 @@ class YOLO:
Trains the model on a given dataset.
Args:
**kwargs (Any): Any number of arguments representing the training configuration. List of all args can be found in 'config' section.
You can pass all arguments as a yaml file in `cfg`. Other args are ignored if `cfg` file is passed
**kwargs (Any): Any number of arguments representing the training configuration.
"""
overrides = self.overrides.copy()
overrides.update(kwargs)
@ -192,7 +191,7 @@ class YOLO:
overrides["task"] = self.task
overrides["mode"] = "train"
if not overrides.get("data"):
raise AttributeError("dataset not provided! Please define `data` in config.yaml or pass as an argument.")
raise AttributeError("Dataset required but missing, i.e. pass 'data=coco128.yaml'")
if overrides.get("resume"):
overrides["resume"] = self.ckpt_path
@ -223,6 +222,13 @@ class YOLO:
return model_class, trainer_class, validator_class, predictor_class
@property
def names(self):
"""
Returns class names of the loaded model.
"""
return self.model.names
@staticmethod
def _reset_ckpt_args(args):
args.pop("project", None)

@ -27,7 +27,6 @@ Usage - formats:
"""
import platform
from collections import defaultdict
from itertools import chain
from pathlib import Path
import cv2
@ -62,15 +61,15 @@ class BasePredictor:
data_path (str): Path to data.
"""
def __init__(self, config=DEFAULT_CFG_PATH, overrides=None):
def __init__(self, cfg=DEFAULT_CFG_PATH, overrides=None):
"""
Initializes the BasePredictor class.
Args:
config (str, optional): Path to a configuration file. Defaults to DEFAULT_CONFIG.
cfg (str, optional): Path to a configuration file. Defaults to DEFAULT_CONFIG.
overrides (dict, optional): Configuration overrides. Defaults to None.
"""
self.args = get_cfg(config, overrides)
self.args = get_cfg(cfg, overrides)
project = self.args.project or Path(SETTINGS['runs_dir']) / self.args.task
name = self.args.name or f"{self.args.mode}"
self.save_dir = increment_path(Path(project) / name, exist_ok=self.args.exist_ok)
@ -219,7 +218,7 @@ class BasePredictor:
self.run_callbacks("on_predict_batch_end")
# Print results
if verbose:
if verbose and self.seen:
t = tuple(x.t / self.seen * 1E3 for x in self.dt) # speeds per image
LOGGER.info(f'Speed: %.1fms pre-process, %.1fms inference, %.1fms postprocess per image at shape '
f'{(1, 3, *self.imgsz)}' % t)

@ -31,7 +31,8 @@ from ultralytics.yolo.utils.autobatch import check_train_batch_size
from ultralytics.yolo.utils.checks import check_file, check_imgsz, print_args
from ultralytics.yolo.utils.dist import ddp_cleanup, generate_ddp_command
from ultralytics.yolo.utils.files import get_latest_run, increment_path
from ultralytics.yolo.utils.torch_utils import ModelEMA, de_parallel, init_seeds, one_cycle, strip_optimizer
from ultralytics.yolo.utils.torch_utils import (EarlyStopping, ModelEMA, de_parallel, init_seeds, one_cycle,
strip_optimizer)
class BaseTrainer:
@ -71,15 +72,15 @@ class BaseTrainer:
csv (Path): Path to results CSV file.
"""
def __init__(self, config=DEFAULT_CFG_PATH, overrides=None):
def __init__(self, cfg=DEFAULT_CFG_PATH, overrides=None):
"""
Initializes the BaseTrainer class.
Args:
config (str, optional): Path to a configuration file. Defaults to DEFAULT_CONFIG.
cfg (str, optional): Path to a configuration file. Defaults to DEFAULT_CONFIG.
overrides (dict, optional): Configuration overrides. Defaults to None.
"""
self.args = get_cfg(config, overrides)
self.args = get_cfg(cfg, overrides)
self.device = utils.torch_utils.select_device(self.args.device, self.args.batch)
self.check_resume()
self.console = LOGGER
@ -225,6 +226,7 @@ class BaseTrainer:
self.lf = lambda x: (1 - x / self.epochs) * (1.0 - self.args.lrf) + self.args.lrf # linear
self.scheduler = lr_scheduler.LambdaLR(self.optimizer, lr_lambda=self.lf)
self.scheduler.last_epoch = self.start_epoch - 1 # do not move
self.stopper, self.stop = EarlyStopping(patience=self.args.patience), False
# dataloaders
batch_size = self.batch_size // world_size if world_size > 1 else self.batch_size
@ -333,10 +335,12 @@ class BaseTrainer:
# Validation
self.ema.update_attr(self.model, include=['yaml', 'nc', 'args', 'names', 'stride', 'class_weights'])
final_epoch = (epoch + 1 == self.epochs)
final_epoch = (epoch + 1 == self.epochs) or self.stopper.possible_stop
if self.args.val or final_epoch:
self.metrics, self.fitness = self.validate()
self.save_metrics(metrics={**self.label_loss_items(self.tloss), **self.metrics, **self.lr})
self.stop = self.stopper(epoch + 1, self.fitness)
# Save model
if self.args.save or (epoch + 1 == self.epochs):
@ -347,7 +351,15 @@ class BaseTrainer:
self.epoch_time = tnow - self.epoch_time_start
self.epoch_time_start = tnow
self.run_callbacks("on_fit_epoch_end")
# TODO: termination condition
# Early Stopping
if RANK != -1: # if DDP training
broadcast_list = [self.stop if RANK == 0 else None]
dist.broadcast_object_list(broadcast_list, 0) # broadcast 'stop' to all ranks
if RANK != 0:
self.stop = broadcast_list[0]
if self.stop:
break # must break all DDP ranks
if rank in {-1, 0}:
# Do final val with best.pt

@ -8,9 +8,9 @@ import platform
import sys
import tempfile
import threading
import types
import uuid
from pathlib import Path
from types import SimpleNamespace
from typing import Union
import cv2
@ -55,10 +55,34 @@ HELP_MSG = \
3. Use the command line interface (CLI):
yolo task=detect mode=train model=yolov8n.yaml args...
classify predict yolov8n-cls.yaml args...
segment val yolov8n-seg.yaml args...
export yolov8n.pt format=onnx args...
YOLOv8 'yolo' CLI commands use the following syntax:
yolo TASK MODE ARGS
Where TASK (optional) is one of [detect, segment, classify]
MODE (required) is one of [train, val, predict, export]
ARGS (optional) are any number of custom 'arg=value' pairs like 'imgsz=320' that override defaults.
See all ARGS at https://docs.ultralytics.com/cfg or with 'yolo cfg'
- Train a detection model for 10 epochs with an initial learning_rate of 0.01
yolo detect train data=coco128.yaml model=yolov8n.pt epochs=10 lr0=0.01
- Predict a YouTube video using a pretrained segmentation model at image size 320:
yolo segment predict model=yolov8n-seg.pt source=https://youtu.be/Zgi9g1ksQHc imgsz=320
- Val a pretrained detection model at batch-size 1 and image size 640:
yolo detect val model=yolov8n.pt data=coco128.yaml batch=1 imgsz=640
- Export a YOLOv8n classification model to ONNX format at image size 224 by 128 (no TASK required)
yolo export model=yolov8n-cls.pt format=onnx imgsz=224,128
- Run special commands:
yolo help
yolo checks
yolo version
yolo settings
yolo copy-cfg
yolo cfg
Docs: https://docs.ultralytics.com
Community: https://community.ultralytics.com
@ -73,11 +97,24 @@ cv2.setNumThreads(0) # prevent OpenCV from multithreading (incompatible with Py
os.environ['NUMEXPR_MAX_THREADS'] = str(NUM_THREADS) # NumExpr max threads
os.environ['CUBLAS_WORKSPACE_CONFIG'] = ':4096:8' # for deterministic training
# Default config dictionary
class IterableSimpleNamespace(SimpleNamespace):
"""
Iterable SimpleNamespace class to allow SimpleNamespace to be used with dict() and in for loops
"""
def __iter__(self):
return iter(vars(self).items())
def __str__(self):
return '\n'.join(f"{k}={v}" for k, v in vars(self).items())
# Default configuration
with open(DEFAULT_CFG_PATH, errors='ignore') as f:
DEFAULT_CFG_DICT = yaml.safe_load(f)
DEFAULT_CFG_KEYS = DEFAULT_CFG_DICT.keys()
DEFAULT_CFG = types.SimpleNamespace(**DEFAULT_CFG_DICT)
DEFAULT_CFG = IterableSimpleNamespace(**DEFAULT_CFG_DICT)
def is_colab():
@ -307,14 +344,15 @@ def set_logging(name=LOGGING_NAME, verbose=True):
class TryExcept(contextlib.ContextDecorator):
# YOLOv8 TryExcept class. Usage: @TryExcept() decorator or 'with TryExcept():' context manager
def __init__(self, msg=''):
def __init__(self, msg='', verbose=True):
self.msg = msg
self.verbose = verbose
def __enter__(self):
pass
def __exit__(self, exc_type, value, traceback):
if value:
if self.verbose and value:
print(emojis(f"{self.msg}{': ' if self.msg else ''}{value}"))
return True
@ -366,6 +404,21 @@ def yaml_load(file='data.yaml', append_filename=False):
return {**yaml.safe_load(f), 'yaml_file': str(file)} if append_filename else yaml.safe_load(f)
def yaml_print(yaml_file: Union[str, Path, dict]) -> None:
"""
Pretty prints a yaml file or a yaml-formatted dictionary.
Args:
yaml_file: The file path of the yaml file or a yaml-formatted dictionary.
Returns:
None
"""
yaml_dict = yaml_load(yaml_file) if isinstance(yaml_file, (str, Path)) else yaml_file
dump = yaml.dump(yaml_dict, default_flow_style=False)
LOGGER.info(f"Printing '{colorstr('bold', 'black', yaml_file)}'\n\n{dump}")
def set_sentry(dsn=None):
"""
Initialize the Sentry SDK for error tracking and reporting if pytest is not currently running.
@ -379,7 +432,6 @@ def set_sentry(dsn=None):
debug=False,
traces_sample_rate=1.0,
release=ultralytics.__version__,
send_default_pii=True,
environment='production', # 'dev' or 'production'
ignore_errors=[KeyboardInterrupt])
@ -439,17 +491,6 @@ def set_settings(kwargs, file=USER_CONFIG_DIR / 'settings.yaml'):
yaml_save(file, SETTINGS)
def print_settings():
"""
Function that prints Ultralytics settings
"""
import json
s = f'\n{PREFIX}Settings:\n'
s += json.dumps(SETTINGS, indent=2)
s += f"\n\nUpdate settings at {USER_CONFIG_DIR / 'settings.yaml'}"
LOGGER.info(s)
# Run below code on utils init -----------------------------------------------------------------------------------------
# Set logger

@ -3,7 +3,7 @@
import json
from time import time
from ultralytics.hub.utils import PREFIX, sync_analytics
from ultralytics.hub.utils import PREFIX, traces
from ultralytics.yolo.utils import LOGGER
@ -43,24 +43,24 @@ def on_train_end(trainer):
LOGGER.info(f"{PREFIX}Training completed successfully ✅\n"
f"{PREFIX}Uploading final {session.model_id}")
session.upload_model(trainer.epoch, trainer.best, map=trainer.metrics['metrics/mAP50-95(B)'], final=True)
session.alive = False # stop heartbeats
session.shutdown() # stop heartbeats
LOGGER.info(f"{PREFIX}View model at https://hub.ultralytics.com/models/{session.model_id} 🚀")
def on_train_start(trainer):
sync_analytics(trainer.args)
traces(trainer.args, traces_sample_rate=0.0)
def on_val_start(validator):
sync_analytics(validator.args)
traces(validator.args, traces_sample_rate=0.0)
def on_predict_start(predictor):
sync_analytics(predictor.args)
traces(predictor.args, traces_sample_rate=0.0)
def on_export_start(exporter):
sync_analytics(exporter.args)
traces(exporter.args, traces_sample_rate=0.0)
callbacks = {

@ -154,7 +154,7 @@ def check_python(minimum: str = '3.7.0') -> bool:
Returns:
None
"""
check_version(platform.python_version(), minimum, name='Python ', hard=True)
return check_version(platform.python_version(), minimum, name='Python ', hard=True)
@TryExcept()
@ -223,8 +223,10 @@ def check_file(file, suffix=''):
files = []
for d in 'models', 'yolo/data': # search directories
files.extend(glob.glob(str(ROOT / d / '**' / file), recursive=True)) # find file
assert len(files), f'File not found: {file}' # assert file was found
assert len(files) == 1, f"Multiple files match '{file}', specify exact path: {files}" # assert unique
if not files:
raise FileNotFoundError(f"{file} does not exist")
elif len(files) > 1:
raise FileNotFoundError(f"Multiple files match '{file}', specify exact path: {files}")
return files[0] # return file

@ -141,7 +141,11 @@ def download(url, dir=Path.cwd(), unzip=True, delete=True, curl=False, threads=1
dir = Path(dir)
dir.mkdir(parents=True, exist_ok=True) # make directory
if threads > 1:
pool = ThreadPool(threads)
# pool = ThreadPool(threads)
# pool.imap(lambda x: download_one(*x), zip(url, repeat(dir))) # multithreaded
# pool.close()
# pool.join()
with ThreadPool(threads) as pool:
pool.imap(lambda x: download_one(*x), zip(url, repeat(dir))) # multithreaded
pool.close()
pool.join()

@ -62,7 +62,9 @@ def select_device(device='', batch_size=0, newline=False):
# device = None or 'cpu' or 0 or '0' or '0,1,2,3'
ver = git_describe() or ultralytics.__version__ # git commit or pip package version
s = f'Ultralytics YOLOv{ver} 🚀 Python-{platform.python_version()} torch-{torch.__version__} '
device = str(device).strip().lower().replace('cuda:', '').replace('none', '') # to string, 'cuda:0' to '0'
device = str(device).lower()
for remove in 'cuda:', 'none', '(', ')', '[', ']', "'", ' ':
device = device.replace(remove, '') # to string, 'cuda:0' -> '0' and '(0, 1)' -> '0,1'
cpu = device == 'cpu'
mps = device == 'mps' # Apple Metal Performance Shaders (MPS)
if cpu or mps:
@ -369,3 +371,26 @@ def profile(input, ops, n=10, device=None):
results.append(None)
torch.cuda.empty_cache()
return results
class EarlyStopping:
# early stopper
def __init__(self, patience=30):
self.best_fitness = 0.0 # i.e. mAP
self.best_epoch = 0
self.patience = patience or float('inf') # epochs to wait after fitness stops improving to stop
self.possible_stop = False # possible stop may occur next epoch
def __call__(self, epoch, fitness):
if fitness >= self.best_fitness: # >= 0 to allow for early zero-fitness stage of training
self.best_epoch = epoch
self.best_fitness = fitness
delta = epoch - self.best_epoch # epochs without improvement
self.possible_stop = delta >= (self.patience - 1) # possible stop may occur next epoch
stop = delta >= self.patience # stop training if patience exceeded
if stop:
LOGGER.info(f'Stopping training early as no improvement observed in last {self.patience} epochs. '
f'Best results observed at epoch {self.best_epoch}, best model saved as best.pt.\n'
f'To update EarlyStopping(patience={self.patience}) pass a new patience value, '
f'i.e. `patience=300` or use `patience=0` to disable EarlyStopping.')
return stop

@ -4,7 +4,7 @@ import torch
from ultralytics.yolo.engine.predictor import BasePredictor
from ultralytics.yolo.engine.results import Results
from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, is_git_directory
from ultralytics.yolo.utils import DEFAULT_CFG, ROOT
from ultralytics.yolo.utils.plotting import Annotator
@ -65,7 +65,7 @@ class ClassificationPredictor(BasePredictor):
def predict(cfg=DEFAULT_CFG):
cfg.model = cfg.model or "yolov8n-cls.pt" # or "resnet18"
cfg.source = cfg.source if cfg.source is not None else ROOT / "assets" if is_git_directory() \
cfg.source = cfg.source if cfg.source is not None else ROOT / "assets" if (ROOT / "assets").exists() \
else "https://ultralytics.com/images/bus.jpg"
predictor = ClassificationPredictor(cfg)
predictor.predict_cli()

@ -4,7 +4,7 @@ import torch
from ultralytics.yolo.engine.predictor import BasePredictor
from ultralytics.yolo.engine.results import Results
from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, is_git_directory, ops
from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, ops
from ultralytics.yolo.utils.plotting import Annotator, colors, save_one_box
@ -83,7 +83,7 @@ class DetectionPredictor(BasePredictor):
def predict(cfg=DEFAULT_CFG):
cfg.model = cfg.model or "yolov8n.pt"
cfg.source = cfg.source if cfg.source is not None else ROOT / "assets" if is_git_directory() \
cfg.source = cfg.source if cfg.source is not None else ROOT / "assets" if (ROOT / "assets").exists() \
else "https://ultralytics.com/images/bus.jpg"
predictor = DetectionPredictor(cfg)
predictor.predict_cli()

@ -3,7 +3,7 @@
import torch
from ultralytics.yolo.engine.results import Results
from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, is_git_directory, ops
from ultralytics.yolo.utils import DEFAULT_CFG, ROOT, ops
from ultralytics.yolo.utils.plotting import colors, save_one_box
from ultralytics.yolo.v8.detect.predict import DetectionPredictor
@ -100,7 +100,7 @@ class SegmentationPredictor(DetectionPredictor):
def predict(cfg=DEFAULT_CFG):
cfg.model = cfg.model or "yolov8n-seg.pt"
cfg.source = cfg.source if cfg.source is not None else ROOT / "assets" if is_git_directory() \
cfg.source = cfg.source if cfg.source is not None else ROOT / "assets" if (ROOT / "assets").exists() \
else "https://ultralytics.com/images/bus.jpg"
predictor = SegmentationPredictor(cfg)
predictor.predict_cli()

Loading…
Cancel
Save