ultralytics 8.2.99 faster JSONDict settings (#16427)

Signed-off-by: UltralyticsAssistant <web@ultralytics.com>
Co-authored-by: UltralyticsAssistant <web@ultralytics.com>
This commit is contained in:
Glenn Jocher 2024-09-22 22:38:35 +02:00 committed by GitHub
parent f5a60c6340
commit 43726d699f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 64 additions and 51 deletions

View file

@ -253,7 +253,7 @@ For example, users can load a model, train it, evaluate its performance on a val
## Ultralytics Settings ## Ultralytics Settings
The Ultralytics library provides a powerful settings management system to enable fine-grained control over your experiments. By making use of the `SettingsManager` housed within the `ultralytics.utils` module, users can readily access and alter their settings. These are stored in a YAML file and can be viewed or modified either directly within the Python environment or via the Command-Line Interface (CLI). The Ultralytics library provides a powerful settings management system to enable fine-grained control over your experiments. By making use of the `SettingsManager` housed within the `ultralytics.utils` module, users can readily access and alter their settings. These are stored in a JSON file in the environment user configuration directory, and can be viewed or modified directly within the Python environment or via the Command-Line Interface (CLI).
### Inspecting Settings ### Inspecting Settings

View file

@ -27,6 +27,7 @@ def test_mlflow():
"""Test training with MLflow tracking enabled (see https://mlflow.org/ for details).""" """Test training with MLflow tracking enabled (see https://mlflow.org/ for details)."""
SETTINGS["mlflow"] = True SETTINGS["mlflow"] = True
YOLO("yolov8n-cls.yaml").train(data="imagenet10", imgsz=32, epochs=3, plots=False, device="cpu") YOLO("yolov8n-cls.yaml").train(data="imagenet10", imgsz=32, epochs=3, plots=False, device="cpu")
SETTINGS["mlflow"] = False
@pytest.mark.skipif(True, reason="Test failing in scheduled CI https://github.com/ultralytics/ultralytics/pull/8868") @pytest.mark.skipif(True, reason="Test failing in scheduled CI https://github.com/ultralytics/ultralytics/pull/8868")
@ -58,6 +59,7 @@ def test_mlflow_keep_run_active():
YOLO("yolov8n-cls.yaml").train(data="imagenet10", imgsz=32, epochs=1, plots=False, device="cpu") YOLO("yolov8n-cls.yaml").train(data="imagenet10", imgsz=32, epochs=1, plots=False, device="cpu")
status = mlflow.get_run(run_id=run_id).info.status status = mlflow.get_run(run_id=run_id).info.status
assert status == "FINISHED", "MLflow run should be ended by default when MLFLOW_KEEP_RUN_ACTIVE is not set" assert status == "FINISHED", "MLflow run should be ended by default when MLFLOW_KEEP_RUN_ACTIVE is not set"
SETTINGS["mlflow"] = False
@pytest.mark.skipif(not check_requirements("tritonclient", install=False), reason="tritonclient[all] not installed") @pytest.mark.skipif(not check_requirements("tritonclient", install=False), reason="tritonclient[all] not installed")

View file

@ -1,6 +1,6 @@
# Ultralytics YOLO 🚀, AGPL-3.0 license # Ultralytics YOLO 🚀, AGPL-3.0 license
__version__ = "8.2.98" __version__ = "8.2.99"
import os import os

View file

@ -19,7 +19,7 @@ from ultralytics.utils import (
ROOT, ROOT,
RUNS_DIR, RUNS_DIR,
SETTINGS, SETTINGS,
SETTINGS_YAML, SETTINGS_FILE,
TESTS_RUNNING, TESTS_RUNNING,
IterableSimpleNamespace, IterableSimpleNamespace,
__version__, __version__,
@ -532,7 +532,7 @@ def handle_yolo_settings(args: List[str]) -> None:
try: try:
if any(args): if any(args):
if args[0] == "reset": if args[0] == "reset":
SETTINGS_YAML.unlink() # delete the settings file SETTINGS_FILE.unlink() # delete the settings file
SETTINGS.reset() # create new settings SETTINGS.reset() # create new settings
LOGGER.info("Settings reset successfully") # inform the user that settings have been reset LOGGER.info("Settings reset successfully") # inform the user that settings have been reset
else: # save a new setting else: # save a new setting
@ -540,8 +540,8 @@ def handle_yolo_settings(args: List[str]) -> None:
check_dict_alignment(SETTINGS, new) check_dict_alignment(SETTINGS, new)
SETTINGS.update(new) SETTINGS.update(new)
LOGGER.info(f"💡 Learn about settings at {url}") print(SETTINGS) # print the current settings
yaml_print(SETTINGS_YAML) # print the current settings LOGGER.info(f"💡 Learn more about Ultralytics Settings at {url}")
except Exception as e: except Exception as e:
LOGGER.warning(f"WARNING ⚠️ settings error: '{e}'. Please see {url} for help.") LOGGER.warning(f"WARNING ⚠️ settings error: '{e}'. Please see {url} for help.")

View file

@ -22,7 +22,7 @@ from ultralytics.utils import (
LOGGER, LOGGER,
NUM_THREADS, NUM_THREADS,
ROOT, ROOT,
SETTINGS_YAML, SETTINGS_FILE,
TQDM, TQDM,
clean_url, clean_url,
colorstr, colorstr,
@ -324,7 +324,7 @@ def check_det_dataset(dataset, autodownload=True):
if s and autodownload: if s and autodownload:
LOGGER.warning(m) LOGGER.warning(m)
else: else:
m += f"\nNote dataset download directory is '{DATASETS_DIR}'. You can update this in '{SETTINGS_YAML}'" m += f"\nNote dataset download directory is '{DATASETS_DIR}'. You can update this in '{SETTINGS_FILE}'"
raise FileNotFoundError(m) raise FileNotFoundError(m)
t = time.time() t = time.time()
r = None # success r = None # success

View file

@ -79,7 +79,6 @@ def logout():
``` ```
""" """
SETTINGS["api_key"] = "" SETTINGS["api_key"] = ""
SETTINGS.save()
LOGGER.info(f"{PREFIX}logged out ✅. To log in again, use 'yolo hub login'.") LOGGER.info(f"{PREFIX}logged out ✅. To log in again, use 'yolo hub login'.")

View file

@ -802,7 +802,7 @@ IS_RASPBERRYPI = is_raspberrypi()
GIT_DIR = get_git_dir() GIT_DIR = get_git_dir()
IS_GIT_DIR = is_git_dir() IS_GIT_DIR = is_git_dir()
USER_CONFIG_DIR = Path(os.getenv("YOLO_CONFIG_DIR") or get_user_config_dir()) # Ultralytics settings dir USER_CONFIG_DIR = Path(os.getenv("YOLO_CONFIG_DIR") or get_user_config_dir()) # Ultralytics settings dir
SETTINGS_YAML = USER_CONFIG_DIR / "settings.yaml" SETTINGS_FILE = USER_CONFIG_DIR / "settings.json"
def colorstr(*input): def colorstr(*input):
@ -1108,6 +1108,10 @@ class JSONDict(dict):
super().__delitem__(key) super().__delitem__(key)
self._save() self._save()
def __str__(self):
"""Return a pretty-printed JSON string representation of the dictionary."""
return f'JSONDict("{self.file_path}"):\n{json.dumps(dict(self), indent=2, ensure_ascii=False)}'
def update(self, *args, **kwargs): def update(self, *args, **kwargs):
"""Update the dictionary and persist changes.""" """Update the dictionary and persist changes."""
with self.lock: with self.lock:
@ -1121,21 +1125,36 @@ class JSONDict(dict):
self._save() self._save()
class SettingsManager(dict): class SettingsManager(JSONDict):
""" """
Manages Ultralytics settings stored in a YAML file. SettingsManager class for managing and persisting Ultralytics settings.
Args: This class extends JSONDict to provide JSON persistence for settings, ensuring thread-safe operations and default
file (str | Path): Path to the Ultralytics settings YAML file. Default is USER_CONFIG_DIR / 'settings.yaml'. values. It validates settings on initialization and provides methods to update or reset settings.
version (str): Settings version. In case of local version mismatch, new default settings will be saved.
Attributes:
file (Path): The path to the JSON file used for persistence.
version (str): The version of the settings schema.
defaults (Dict): A dictionary containing default settings.
help_msg (str): A help message for users on how to view and update settings.
Methods:
_validate_settings: Validates the current settings and resets if necessary.
update: Updates settings, validating keys and types.
reset: Resets the settings to default and saves them.
Examples:
Initialize and update settings:
>>> settings = SettingsManager()
>>> settings.update(runs_dir="/new/runs/dir")
>>> print(settings["runs_dir"])
/new/runs/dir
""" """
def __init__(self, file=SETTINGS_YAML, version="0.0.5"): def __init__(self, file=SETTINGS_FILE, version="0.0.6"):
"""Initializes the SettingsManager with default settings and loads user settings.""" """Initializes the SettingsManager with default settings and loads user settings."""
import copy
import hashlib import hashlib
from ultralytics.utils.checks import check_version
from ultralytics.utils.torch_utils import torch_distributed_zero_first from ultralytics.utils.torch_utils import torch_distributed_zero_first
root = GIT_DIR or Path() root = GIT_DIR or Path()
@ -1164,45 +1183,42 @@ class SettingsManager(dict):
"vscode_msg": True, "vscode_msg": True,
} }
self.help_msg = ( self.help_msg = (
f"\nView settings with 'yolo settings' or at '{self.file}'" f"\nView Ultralytics Settings with 'yolo settings' or at '{self.file}'"
"\nUpdate settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. " "\nUpdate Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. "
"For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings." "For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings."
) )
super().__init__(copy.deepcopy(self.defaults))
with torch_distributed_zero_first(RANK): with torch_distributed_zero_first(RANK):
if not self.file.exists(): super().__init__(self.file)
self.save()
self.load() if not self.file.exists() or not self: # Check if file doesn't exist or is empty
correct_keys = self.keys() == self.defaults.keys() LOGGER.info(f"Creating new Ultralytics Settings v{version} file ✅ {self.help_msg}")
correct_types = all(type(a) is type(b) for a, b in zip(self.values(), self.defaults.values()))
correct_version = check_version(self["settings_version"], self.version)
if not (correct_keys and correct_types and correct_version):
LOGGER.warning(
"WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
f"with your settings or a recent ultralytics package update. {self.help_msg}"
)
self.reset() self.reset()
if self.get("datasets_dir") == self.get("runs_dir"): self._validate_settings()
LOGGER.warning(
f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
f"Please change one to avoid possible issues during training. {self.help_msg}"
)
def load(self): def _validate_settings(self):
"""Loads settings from the YAML file.""" """Validate the current settings and reset if necessary."""
super().update(yaml_load(self.file)) correct_keys = set(self.keys()) == set(self.defaults.keys())
correct_types = all(isinstance(self.get(k), type(v)) for k, v in self.defaults.items())
correct_version = self.get("settings_version", "") == self.version
def save(self): if not (correct_keys and correct_types and correct_version):
"""Saves the current settings to the YAML file.""" LOGGER.warning(
yaml_save(self.file, dict(self)) "WARNING ⚠️ Ultralytics settings reset to default values. This may be due to a possible problem "
f"with your settings or a recent ultralytics package update. {self.help_msg}"
)
self.reset()
if self.get("datasets_dir") == self.get("runs_dir"):
LOGGER.warning(
f"WARNING ⚠️ Ultralytics setting 'datasets_dir: {self.get('datasets_dir')}' "
f"must be different than 'runs_dir: {self.get('runs_dir')}'. "
f"Please change one to avoid possible issues during training. {self.help_msg}"
)
def update(self, *args, **kwargs): def update(self, *args, **kwargs):
"""Updates a setting value in the current settings.""" """Updates settings, validating keys and types."""
for k, v in kwargs.items(): for k, v in kwargs.items():
if k not in self.defaults: if k not in self.defaults:
raise KeyError(f"No Ultralytics setting '{k}'. {self.help_msg}") raise KeyError(f"No Ultralytics setting '{k}'. {self.help_msg}")
@ -1210,20 +1226,16 @@ class SettingsManager(dict):
if not isinstance(v, t): if not isinstance(v, t):
raise TypeError(f"Ultralytics setting '{k}' must be of type '{t}', not '{type(v)}'. {self.help_msg}") raise TypeError(f"Ultralytics setting '{k}' must be of type '{t}', not '{type(v)}'. {self.help_msg}")
super().update(*args, **kwargs) super().update(*args, **kwargs)
self.save()
def reset(self): def reset(self):
"""Resets the settings to default and saves them.""" """Resets the settings to default and saves them."""
self.clear() self.clear()
self.update(self.defaults) self.update(self.defaults)
self.save()
def deprecation_warn(arg, new_arg): def deprecation_warn(arg, new_arg):
"""Issue a deprecation warning when a deprecated argument is used, suggesting an updated argument.""" """Issue a deprecation warning when a deprecated argument is used, suggesting an updated argument."""
LOGGER.warning( LOGGER.warning(f"WARNING ⚠️ '{arg}' is deprecated and will be removed in in the future. Use '{new_arg}' instead.")
f"WARNING ⚠️ '{arg}' is deprecated and will be removed in in the future. " f"Please use '{new_arg}' instead."
)
def clean_url(url): def clean_url(url):