Skip to content

Reference for ultralytics/engine/tuner.py

Note

This file is available at https://github.com/ultralytics/ultralytics/blob/main/ultralytics/engine/tuner.py. If you spot a problem please help fix it by contributing a Pull Request 🛠️. Thank you 🙏!


ultralytics.engine.tuner.Tuner

Tuner(args=DEFAULT_CFG, _callbacks: list | None = None)

A class for hyperparameter tuning of YOLO models.

The class evolves YOLO model hyperparameters over a given number of iterations by mutating them according to the search space and retraining the model to evaluate their performance. Supports both local CSV storage and distributed MongoDB Atlas coordination for multi-machine hyperparameter optimization.

Attributes:

Name Type Description
space dict[str, tuple]

Hyperparameter search space containing bounds and scaling factors for mutation.

tune_dir Path

Directory where evolution logs and results will be saved.

tune_csv Path

Path to the CSV file where evolution logs are saved.

args dict

Configuration arguments for the tuning process.

callbacks list

Callback functions to be executed during tuning.

prefix str

Prefix string for logging messages.

mongodb MongoClient

Optional MongoDB client for distributed tuning.

collection Collection

MongoDB collection for storing tuning results.

Methods:

Name Description
_mutate

Mutate hyperparameters based on bounds and scaling factors.

__call__

Execute the hyperparameter evolution across multiple iterations.

Examples:

Tune hyperparameters for YOLO11n on COCO8 at imgsz=640 and epochs=30 for 300 tuning iterations.

>>> from ultralytics import YOLO
>>> model = YOLO("yolo11n.pt")
>>> model.tune(
>>>     data="coco8.yaml",
>>>     epochs=10,
>>>     iterations=300,
>>>     plots=False,
>>>     save=False,
>>>     val=False
>>> )

Tune with distributed MongoDB Atlas coordination across multiple machines:

>>> model.tune(
>>>     data="coco8.yaml",
>>>     epochs=10,
>>>     iterations=300,
>>>     mongodb_uri="mongodb+srv://user:pass@cluster.mongodb.net/",
>>>     mongodb_db="ultralytics",
>>>     mongodb_collection="tune_results"
>>> )

Tune with custom search space:

>>> model.tune(space={"lr0": (1e-5, 1e-1), "momentum": (0.6, 0.98)})

Parameters:

Name Type Description Default
args dict

Configuration for hyperparameter evolution.

DEFAULT_CFG
_callbacks list | None

Callback functions to be executed during tuning.

None
Source code in ultralytics/engine/tuner.py
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def __init__(self, args=DEFAULT_CFG, _callbacks: list | None = None):
    """
    Initialize the Tuner with configurations.

    Args:
        args (dict): Configuration for hyperparameter evolution.
        _callbacks (list | None, optional): Callback functions to be executed during tuning.
    """
    self.space = args.pop("space", None) or {  # key: (min, max, gain(optional))
        # 'optimizer': tune.choice(['SGD', 'Adam', 'AdamW', 'NAdam', 'RAdam', 'RMSProp']),
        "lr0": (1e-5, 1e-1),  # initial learning rate (i.e. SGD=1E-2, Adam=1E-3)
        "lrf": (0.0001, 0.1),  # final OneCycleLR learning rate (lr0 * lrf)
        "momentum": (0.7, 0.98, 0.3),  # SGD momentum/Adam beta1
        "weight_decay": (0.0, 0.001),  # optimizer weight decay 5e-4
        "warmup_epochs": (0.0, 5.0),  # warmup epochs (fractions ok)
        "warmup_momentum": (0.0, 0.95),  # warmup initial momentum
        "box": (1.0, 20.0),  # box loss gain
        "cls": (0.1, 4.0),  # cls loss gain (scale with pixels)
        "dfl": (0.4, 6.0),  # dfl loss gain
        "hsv_h": (0.0, 0.1),  # image HSV-Hue augmentation (fraction)
        "hsv_s": (0.0, 0.9),  # image HSV-Saturation augmentation (fraction)
        "hsv_v": (0.0, 0.9),  # image HSV-Value augmentation (fraction)
        "degrees": (0.0, 45.0),  # image rotation (+/- deg)
        "translate": (0.0, 0.9),  # image translation (+/- fraction)
        "scale": (0.0, 0.95),  # image scale (+/- gain)
        "shear": (0.0, 10.0),  # image shear (+/- deg)
        "perspective": (0.0, 0.001),  # image perspective (+/- fraction), range 0-0.001
        "flipud": (0.0, 1.0),  # image flip up-down (probability)
        "fliplr": (0.0, 1.0),  # image flip left-right (probability)
        "bgr": (0.0, 1.0),  # image channel bgr (probability)
        "mosaic": (0.0, 1.0),  # image mosaic (probability)
        "mixup": (0.0, 1.0),  # image mixup (probability)
        "cutmix": (0.0, 1.0),  # image cutmix (probability)
        "copy_paste": (0.0, 1.0),  # segment copy-paste (probability)
        "close_mosaic": (0.0, 10.0),  # close dataloader mosaic (epochs)
    }
    mongodb_uri = args.pop("mongodb_uri", None)
    mongodb_db = args.pop("mongodb_db", "ultralytics")
    mongodb_collection = args.pop("mongodb_collection", "tuner_results")

    self.args = get_cfg(overrides=args)
    self.args.exist_ok = self.args.resume  # resume w/ same tune_dir
    self.tune_dir = get_save_dir(self.args, name=self.args.name or "tune")
    self.args.name, self.args.exist_ok, self.args.resume = (None, False, False)  # reset to not affect training
    self.tune_csv = self.tune_dir / "tune_results.csv"
    self.callbacks = _callbacks or callbacks.get_default_callbacks()
    self.prefix = colorstr("Tuner: ")
    callbacks.add_integration_callbacks(self)

    # MongoDB Atlas support (optional)
    self.mongodb = None
    if mongodb_uri:
        self._init_mongodb(mongodb_uri, mongodb_db, mongodb_collection)

    LOGGER.info(
        f"{self.prefix}Initialized Tuner instance with 'tune_dir={self.tune_dir}'\n"
        f"{self.prefix}💡 Learn about tuning at https://docs.ultralytics.com/guides/hyperparameter-tuning"
    )

__call__

__call__(model=None, iterations: int = 10, cleanup: bool = True)

Execute the hyperparameter evolution process when the Tuner instance is called.

This method iterates through the specified number of iterations, performing the following steps: 1. Sync MongoDB results to CSV (if using distributed mode) 2. Mutate hyperparameters using the best previous results or defaults 3. Train a YOLO model with the mutated hyperparameters 4. Log fitness scores and hyperparameters to MongoDB and/or CSV 5. Track the best performing configuration across all iterations

Parameters:

Name Type Description Default
model Model | None

A pre-initialized YOLO model to be used for training.

None
iterations int

The number of generations to run the evolution for.

10
cleanup bool

Whether to delete iteration weights to reduce storage space during tuning.

True
Source code in ultralytics/engine/tuner.py
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
def __call__(self, model=None, iterations: int = 10, cleanup: bool = True):
    """
    Execute the hyperparameter evolution process when the Tuner instance is called.

    This method iterates through the specified number of iterations, performing the following steps:
    1. Sync MongoDB results to CSV (if using distributed mode)
    2. Mutate hyperparameters using the best previous results or defaults
    3. Train a YOLO model with the mutated hyperparameters
    4. Log fitness scores and hyperparameters to MongoDB and/or CSV
    5. Track the best performing configuration across all iterations

    Args:
        model (Model | None, optional): A pre-initialized YOLO model to be used for training.
        iterations (int): The number of generations to run the evolution for.
        cleanup (bool): Whether to delete iteration weights to reduce storage space during tuning.
    """
    t0 = time.time()
    best_save_dir, best_metrics = None, None
    (self.tune_dir / "weights").mkdir(parents=True, exist_ok=True)

    # Sync MongoDB to CSV at startup for proper resume logic
    if self.mongodb:
        self._sync_mongodb_to_csv()

    start = 0
    if self.tune_csv.exists():
        x = np.loadtxt(self.tune_csv, ndmin=2, delimiter=",", skiprows=1)
        start = x.shape[0]
        LOGGER.info(f"{self.prefix}Resuming tuning run {self.tune_dir} from iteration {start + 1}...")
    for i in range(start, iterations):
        # Linearly decay sigma from 0.2 → 0.1 over first 300 iterations
        frac = min(i / 300.0, 1.0)
        sigma_i = 0.2 - 0.1 * frac

        # Mutate hyperparameters
        mutated_hyp = self._mutate(sigma=sigma_i)
        LOGGER.info(f"{self.prefix}Starting iteration {i + 1}/{iterations} with hyperparameters: {mutated_hyp}")

        metrics = {}
        train_args = {**vars(self.args), **mutated_hyp}
        save_dir = get_save_dir(get_cfg(train_args))
        weights_dir = save_dir / "weights"
        try:
            # Train YOLO model with mutated hyperparameters (run in subprocess to avoid dataloader hang)
            launch = [__import__("sys").executable, "-m", "ultralytics.cfg.__init__"]  # workaround yolo not found
            cmd = [*launch, "train", *(f"{k}={v}" for k, v in train_args.items())]
            return_code = subprocess.run(cmd, check=True).returncode
            ckpt_file = weights_dir / ("best.pt" if (weights_dir / "best.pt").exists() else "last.pt")
            metrics = torch_load(ckpt_file)["train_metrics"]
            assert return_code == 0, "training failed"

            # Cleanup
            time.sleep(1)
            gc.collect()
            torch.cuda.empty_cache()

        except Exception as e:
            LOGGER.error(f"training failure for hyperparameter tuning iteration {i + 1}\n{e}")

        # Save results - MongoDB takes precedence
        fitness = metrics.get("fitness", 0.0)
        if self.mongodb:
            self._save_to_mongodb(fitness, mutated_hyp, metrics, i + 1)
            self._sync_mongodb_to_csv()
            total_mongo_iterations = self.collection.count_documents({})
            if total_mongo_iterations >= iterations:
                LOGGER.info(
                    f"{self.prefix}Target iterations ({iterations}) reached in MongoDB ({total_mongo_iterations}). Stopping."
                )
                break
        else:
            # Save to CSV only if no MongoDB
            log_row = [round(fitness, 5)] + [mutated_hyp[k] for k in self.space.keys()]
            headers = "" if self.tune_csv.exists() else (",".join(["fitness"] + list(self.space.keys())) + "\n")
            with open(self.tune_csv, "a", encoding="utf-8") as f:
                f.write(headers + ",".join(map(str, log_row)) + "\n")

        # Get best results
        x = np.loadtxt(self.tune_csv, ndmin=2, delimiter=",", skiprows=1)
        fitness = x[:, 0]  # first column
        best_idx = fitness.argmax()
        best_is_current = best_idx == (i - start)
        if best_is_current:
            best_save_dir = str(save_dir)
            best_metrics = {k: round(v, 5) for k, v in metrics.items()}
            for ckpt in weights_dir.glob("*.pt"):
                shutil.copy2(ckpt, self.tune_dir / "weights")
        elif cleanup and best_save_dir:
            shutil.rmtree(best_save_dir, ignore_errors=True)  # remove iteration dirs to reduce storage space

        # Plot tune results
        plot_tune_results(str(self.tune_csv))

        # Save and print tune results
        header = (
            f"{self.prefix}{i + 1}/{iterations} iterations complete ✅ ({time.time() - t0:.2f}s)\n"
            f"{self.prefix}Results saved to {colorstr('bold', self.tune_dir)}\n"
            f"{self.prefix}Best fitness={fitness[best_idx]} observed at iteration {best_idx + 1}\n"
            f"{self.prefix}Best fitness metrics are {best_metrics}\n"
            f"{self.prefix}Best fitness model is {best_save_dir}"
        )
        LOGGER.info("\n" + header)
        data = {k: float(x[best_idx, i + 1]) for i, k in enumerate(self.space.keys())}
        YAML.save(
            self.tune_dir / "best_hyperparameters.yaml",
            data=data,
            header=remove_colorstr(header.replace(self.prefix, "# ")) + "\n",
        )
        YAML.print(self.tune_dir / "best_hyperparameters.yaml")





📅 Created 1 year ago ✏️ Updated 1 year ago