learning-loop-node 0.26.0__tar.gz → 0.27.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/PKG-INFO +2 -1
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/README.md +1 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/general.py +2 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/training.py +11 -5
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/rest/backdoor_controls.py +1 -2
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/trainer_logic.py +17 -7
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/trainer_logic_generic.py +29 -14
- learning_loop_node-0.27.0/learning_loop_node/trainer/validation_set.py +63 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/PKG-INFO +2 -1
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/SOURCES.txt +1 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/pyproject.toml +1 -1
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/annotation/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/annotation/annotator_logic.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/annotation/annotator_node.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/annotation_data.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/annotations.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/detections.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/image_metadata.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/socket_response.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_exchanger.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/categories.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/detector_logic.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/detector_node.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/exceptions.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/geometry.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/inbox_filter/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/inbox_filter/cam_observation_history.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/inbox_filter/relevance_filter.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/outbox.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/postprocess.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/about.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/backdoor_controls.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/detect.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/model_version_control.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/operation_mode.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/outbox_mode.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/upload.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/annotator.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/detector.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/general.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/trainer.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/globals.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/background_tasks.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/entrypoint.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/environment_reader.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/gdrive_downloader.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/log_conf.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/misc.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/run.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/loop_communication.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/node.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/py.typed +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/rest.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/batch_size.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/cuda.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/downloader.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/exceptions.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/executor.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/io_helpers.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/metrics.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/rest/__init__.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/subprocess.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/test_executor.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/trainer_node.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/training_slot.py +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/dependency_links.txt +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/requires.txt +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/top_level.txt +0 -0
- {learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: learning_loop_node
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
4
4
|
Summary: Python Library for Nodes which connect to the Zauberzeug Learning Loop
|
|
5
5
|
Author-email: Zauberzeug GmbH <info@zauberzeug.com>
|
|
6
6
|
License: MIT
|
|
@@ -349,3 +349,4 @@ After a short time the converted model should be available as well.
|
|
|
349
349
|
- Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
|
|
350
350
|
- `resolution`: resolution in which the model expects images (as `int`, since the resolution is mostly square - later, `resolution_x`` resolution_y` would also be conceivable or `resolutions` to give a list of possible resolutions)
|
|
351
351
|
- `categories`: list of categories with name, id, (later also type), in the order in which they are used by the model -- this is neccessary to be robust about renamings
|
|
352
|
+
- `conf_threshold` (optional): the minimum confidence the trainer chose for this model's detections
|
|
@@ -316,3 +316,4 @@ After a short time the converted model should be available as well.
|
|
|
316
316
|
- Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
|
|
317
317
|
- `resolution`: resolution in which the model expects images (as `int`, since the resolution is mostly square - later, `resolution_x`` resolution_y` would also be conceivable or `resolutions` to give a list of possible resolutions)
|
|
318
318
|
- `categories`: list of categories with name, id, (later also type), in the order in which they are used by the model -- this is neccessary to be robust about renamings
|
|
319
|
+
- `conf_threshold` (optional): the minimum confidence the trainer chose for this model's detections
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/general.py
RENAMED
|
@@ -59,6 +59,8 @@ class ModelInformation():
|
|
|
59
59
|
default=None, metadata={"description": "The path of the parent directory of the model in the file system."})
|
|
60
60
|
model_size: Optional[str] = field(default=None, metadata={
|
|
61
61
|
"description": "The size of the model (i.e. the specification or variant of the model architecture)."})
|
|
62
|
+
conf_threshold: Optional[float] = field(default=None, metadata={
|
|
63
|
+
"description": "The minimum confidence the trainer chose for the detections of this model, if it chose one."})
|
|
62
64
|
|
|
63
65
|
@property
|
|
64
66
|
def context(self):
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/training.py
RENAMED
|
@@ -103,13 +103,19 @@ class Training():
|
|
|
103
103
|
assert self.image_data is not None, 'Image data not set'
|
|
104
104
|
return [image['id'] for image in self.image_data]
|
|
105
105
|
|
|
106
|
-
def
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
def train_images(self) -> list[dict]:
|
|
107
|
+
return self._images_of_set('train')
|
|
108
|
+
|
|
109
|
+
def val_images(self) -> list[dict]:
|
|
110
|
+
"""The images to select models on: the `val` images if there are any, else the test images."""
|
|
111
|
+
return self._images_of_set('val') or self.test_images()
|
|
112
|
+
|
|
113
|
+
def test_images(self) -> list[dict]:
|
|
114
|
+
return self._images_of_set('test')
|
|
109
115
|
|
|
110
|
-
def
|
|
116
|
+
def _images_of_set(self, set_name: str) -> list[dict]:
|
|
111
117
|
assert self.image_data is not None, 'Image data not set'
|
|
112
|
-
return
|
|
118
|
+
return [image for image in self.image_data if image['set'] == set_name]
|
|
113
119
|
|
|
114
120
|
|
|
115
121
|
@dataclass(**KWONLY_SLOTS)
|
|
@@ -38,8 +38,7 @@ async def reset(request: Request):
|
|
|
38
38
|
trainer_node: 'TrainerNode' = request.app
|
|
39
39
|
async with trainer_node.repeat_loop_lock:
|
|
40
40
|
|
|
41
|
-
await trainer_node.trainer_logic.
|
|
42
|
-
await trainer_node.trainer_logic.stop()
|
|
41
|
+
await trainer_node.trainer_logic.abort()
|
|
43
42
|
trainer_node.last_training_io.delete()
|
|
44
43
|
trainer_node.status.reset_all_errors()
|
|
45
44
|
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/trainer_logic.py
RENAMED
|
@@ -25,6 +25,8 @@ class TrainerLogic(TrainerLogicGeneric):
|
|
|
25
25
|
super().__init__(model_format)
|
|
26
26
|
self._detection_progress: Optional[float] = None
|
|
27
27
|
self._executor: Optional[Executor] = None
|
|
28
|
+
self._process_started: bool = False
|
|
29
|
+
self._stop_requested: bool = False
|
|
28
30
|
self.start_training_task: Optional[Coroutine] = None
|
|
29
31
|
self.inference_batch_size = self._environment_vars.inference_batch_size
|
|
30
32
|
|
|
@@ -47,10 +49,13 @@ class TrainerLogic(TrainerLogicGeneric):
|
|
|
47
49
|
previous_state = TrainerState.TrainModelDownloaded
|
|
48
50
|
error_key = 'run_training'
|
|
49
51
|
self._executor = Executor(self.training.training_folder)
|
|
52
|
+
self._process_started = False
|
|
53
|
+
self._stop_requested = False
|
|
50
54
|
self.training.training_state = TrainerState.TrainingRunning
|
|
51
55
|
|
|
52
56
|
try:
|
|
53
57
|
await self._start_training()
|
|
58
|
+
self._process_started = True
|
|
54
59
|
last_sync_time = datetime.now()
|
|
55
60
|
|
|
56
61
|
while True:
|
|
@@ -77,11 +82,17 @@ class TrainerLogic(TrainerLogicGeneric):
|
|
|
77
82
|
# if self.executor.return_code != 0:
|
|
78
83
|
# raise TrainingError(cause=f'Executor returned with error code: {self.executor.return_code}')
|
|
79
84
|
|
|
80
|
-
except TrainingError:
|
|
85
|
+
except TrainingError as e:
|
|
81
86
|
logging.exception('Exception in trainer_logic._train')
|
|
87
|
+
if self._stop_requested:
|
|
88
|
+
# ending the process may be what failed it; a stopped training must not start over, and the
|
|
89
|
+
# wind-down uploads the model it has (or ends with a CriticalError if there is none)
|
|
90
|
+
logging.warning('Training process failed after a stop (%s), winding down with its model', e.cause)
|
|
91
|
+
return
|
|
82
92
|
self.training.training_state = previous_state
|
|
83
93
|
raise
|
|
84
94
|
finally:
|
|
95
|
+
self._process_started = False
|
|
85
96
|
# shield so the subprocess is stopped even when the task is cancelled (e.g. on abort())
|
|
86
97
|
if self._executor and self._executor.is_running():
|
|
87
98
|
await asyncio.shield(self.executor.stop_and_wait())
|
|
@@ -147,15 +158,14 @@ class TrainerLogic(TrainerLogicGeneric):
|
|
|
147
158
|
|
|
148
159
|
# ---------------------------------------- OVERWRITTEN METHODS ----------------------------------------
|
|
149
160
|
|
|
150
|
-
async def
|
|
151
|
-
"""
|
|
152
|
-
|
|
153
|
-
|
|
161
|
+
async def _stop_running_training(self) -> None:
|
|
162
|
+
"""End the training process; before it has started, abort."""
|
|
163
|
+
if not self._process_started:
|
|
164
|
+
await super()._stop_running_training()
|
|
154
165
|
return
|
|
166
|
+
self._stop_requested = True
|
|
155
167
|
if self._executor and self._executor.is_running():
|
|
156
168
|
await self.executor.stop_and_wait()
|
|
157
|
-
return
|
|
158
|
-
await super().stop()
|
|
159
169
|
|
|
160
170
|
# ---------------------------------------- ABSTRACT METHODS ----------------------------------------
|
|
161
171
|
|
|
@@ -29,6 +29,7 @@ from .downloader import TrainingsDownloader
|
|
|
29
29
|
from .exceptions import CriticalError, NodeNeedsRestartError, TrainingRefusedError
|
|
30
30
|
from .io_helpers import ActiveTrainingIO, EnvironmentVars, LastTrainingIO
|
|
31
31
|
from .training_slot import TrainingSlot, training_slot_from_env
|
|
32
|
+
from .validation_set import assign_validation_set
|
|
32
33
|
|
|
33
34
|
if TYPE_CHECKING:
|
|
34
35
|
from .trainer_node import TrainerNode
|
|
@@ -328,7 +329,7 @@ class TrainerLogicGeneric(ABC):
|
|
|
328
329
|
async def _perform_state(self, error_key: str, state_during: TrainerState, state_after: TrainerState, action: Callable[[], Coroutine], reset_early=False):
|
|
329
330
|
'''
|
|
330
331
|
Perform a training state and handle errors.
|
|
331
|
-
- If the
|
|
332
|
+
- If the training is aborted, the CancelledError moves it to ReadyForCleanup; on shutdown it is re-raised.
|
|
332
333
|
- States can raise a CriticalError indicating that there is no point in retrying the state.
|
|
333
334
|
- If any other error occurs, the error is stored in the errors object and the state is reset to the previous state.
|
|
334
335
|
The state is retried after a wait until the training is aborted.
|
|
@@ -377,11 +378,11 @@ class TrainerLogicGeneric(ABC):
|
|
|
377
378
|
self.last_training_io.save(self.training)
|
|
378
379
|
|
|
379
380
|
async def _prepare(self) -> None:
|
|
380
|
-
"""Downloads images to the images_folder and saves annotations to training.data.image_data.
|
|
381
|
-
"""
|
|
381
|
+
"""Downloads images to the images_folder and saves annotations to training.data.image_data."""
|
|
382
382
|
self.node.data_exchanger.set_context(self.training.context)
|
|
383
383
|
downloader = TrainingsDownloader(self.node.data_exchanger)
|
|
384
384
|
image_data, skipped_image_count = await downloader.download_training_data(self.training.images_folder)
|
|
385
|
+
assign_validation_set(image_data, self.training.hyperparameters)
|
|
385
386
|
|
|
386
387
|
self.training.image_data = image_data
|
|
387
388
|
self.training.skipped_image_count = skipped_image_count
|
|
@@ -421,11 +422,12 @@ class TrainerLogicGeneric(ABC):
|
|
|
421
422
|
try:
|
|
422
423
|
new_best_model = self._get_new_best_training_state()
|
|
423
424
|
if new_best_model:
|
|
425
|
+
sets = [image['set'] for image in self.training.image_data or []]
|
|
424
426
|
new_training = TrainingOut(trainer_id=self.node.uuid,
|
|
425
427
|
trainer_name=self.node.name,
|
|
426
428
|
confusion_matrix=new_best_model.confusion_matrix,
|
|
427
|
-
train_image_count=
|
|
428
|
-
test_image_count=
|
|
429
|
+
train_image_count=sets.count('train') + sets.count('val'),
|
|
430
|
+
test_image_count=sets.count('test'),
|
|
429
431
|
hyperparameters=self.hyperparameters_for_state_sync,
|
|
430
432
|
best_epoch=new_best_model.epoch)
|
|
431
433
|
await asyncio.sleep(0.1) # NOTE needed for tests.
|
|
@@ -475,7 +477,7 @@ class TrainerLogicGeneric(ABC):
|
|
|
475
477
|
already_uploaded_formats = self.active_training_io.load_model_upload_progress()
|
|
476
478
|
|
|
477
479
|
for file_format in [f for f in files if f not in already_uploaded_formats]:
|
|
478
|
-
_files = files[file_format] + [self.
|
|
480
|
+
_files = files[file_format] + [self._dump_model_json()]
|
|
479
481
|
assert len([f for f in _files if 'model.json' in f]) == 1, "model.json must be included exactly once"
|
|
480
482
|
|
|
481
483
|
model_uuid = await self.node.data_exchanger.upload_model_get_uuid(context, _files, self.training.training_number, file_format)
|
|
@@ -490,10 +492,11 @@ class TrainerLogicGeneric(ABC):
|
|
|
490
492
|
except FileNotFoundError as e:
|
|
491
493
|
raise CriticalError('The model is uploaded, but the loop never answered with its id') from e
|
|
492
494
|
|
|
493
|
-
def
|
|
494
|
-
"""Dumps the categories to a json file and returns the path to the file.
|
|
495
|
+
def _dump_model_json(self) -> str:
|
|
496
|
+
"""Dumps the categories and the trainer's model metadata to a json file and returns the path to the file.
|
|
495
497
|
"""
|
|
496
|
-
content = {'categories': [asdict(c) for c in self._training.categories],
|
|
498
|
+
content = {'categories': [asdict(c) for c in self._training.categories],
|
|
499
|
+
**self._get_model_metadata()} if self._training else None
|
|
497
500
|
json_path = '/tmp/model.json'
|
|
498
501
|
with open(json_path, 'w') as f:
|
|
499
502
|
json.dump(content, f)
|
|
@@ -515,12 +518,17 @@ class TrainerLogicGeneric(ABC):
|
|
|
515
518
|
|
|
516
519
|
async def on_shutdown(self) -> None:
|
|
517
520
|
self.shutdown_event.set()
|
|
518
|
-
await self.
|
|
519
|
-
await self.stop()
|
|
521
|
+
await self.abort()
|
|
520
522
|
|
|
521
|
-
async def stop(self):
|
|
522
|
-
"""Stop
|
|
523
|
-
|
|
523
|
+
async def stop(self) -> None:
|
|
524
|
+
"""Stop a running training but keep its model. In every other state a stop does nothing."""
|
|
525
|
+
if not self.training_active or self.training.training_state != TrainerState.TrainingRunning:
|
|
526
|
+
logger.info('Received stop in state %s. This is ignored.', self.training.training_state if self.training_active else None)
|
|
527
|
+
return
|
|
528
|
+
await self._stop_running_training()
|
|
529
|
+
|
|
530
|
+
async def _stop_running_training(self) -> None:
|
|
531
|
+
"""End the running training so it winds down with its model. This base class cannot, so it aborts."""
|
|
524
532
|
await self.abort()
|
|
525
533
|
|
|
526
534
|
async def abort(self):
|
|
@@ -606,6 +614,13 @@ class TrainerLogicGeneric(ABC):
|
|
|
606
614
|
"""
|
|
607
615
|
raise NotImplementedError
|
|
608
616
|
|
|
617
|
+
def _get_model_metadata(self) -> dict[str, Any]:
|
|
618
|
+
"""Called when the files of `_get_latest_model_files` are uploaded.
|
|
619
|
+
Returns values the trainer determined for this model, merged into its model.json.
|
|
620
|
+
The keys must be fields of `ModelInformation` (e.g. `conf_threshold`) so detectors can read them.
|
|
621
|
+
"""
|
|
622
|
+
return {}
|
|
623
|
+
|
|
609
624
|
@abstractmethod
|
|
610
625
|
async def _clear_training_data(self, training_folder: str) -> None:
|
|
611
626
|
"""Called after a training has finished. Deletes all data that is not needed anymore after a training run.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""Which images a training selects its models on.
|
|
2
|
+
|
|
3
|
+
A node selects models (best checkpoint, early stopping, confidence threshold) on
|
|
4
|
+
`Training.val_images()` and reads `Training.test_images()` only for the counts it reports. It never
|
|
5
|
+
decides between the two itself: `val_images()` always holds a set to select on, which is the test
|
|
6
|
+
images unless the training has `val` images of its own. A node may notice that both hold the same
|
|
7
|
+
images and spare itself a second pass, but nothing more.
|
|
8
|
+
|
|
9
|
+
Where the `val` images come from, in this order:
|
|
10
|
+
|
|
11
|
+
1. The loop sent a dedicated validation set (`set == 'val'`); it is used as it is.
|
|
12
|
+
2. The `compute_validation_set` hyperparameter is true: `assign_validation_set` moves about a tenth of the
|
|
13
|
+
train images to `val`, chosen by a hash of the image id, so an image stays on its side when new
|
|
14
|
+
images arrive or a training is continued.
|
|
15
|
+
3. Otherwise there are no `val` images and `val_images()` falls back to the test images, so the
|
|
16
|
+
model is selected on the test set.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
import hashlib
|
|
20
|
+
import logging
|
|
21
|
+
from collections.abc import Mapping
|
|
22
|
+
from typing import Any
|
|
23
|
+
|
|
24
|
+
from .exceptions import CriticalError
|
|
25
|
+
|
|
26
|
+
logger = logging.getLogger(__name__)
|
|
27
|
+
|
|
28
|
+
COMPUTE_VALIDATION_SET = 'compute_validation_set'
|
|
29
|
+
"""The hyperparameter that asks to split a validation set off the train images when the loop sent none."""
|
|
30
|
+
|
|
31
|
+
VALIDATION_FRACTION = 0.1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def compute_validation_set(hyperparameters: Mapping[str, Any]) -> bool:
|
|
35
|
+
"""Whether `compute_validation_set` is set, defaulting to false; anything but a boolean is a CriticalError."""
|
|
36
|
+
value = hyperparameters.get(COMPUTE_VALIDATION_SET, False)
|
|
37
|
+
if not isinstance(value, bool):
|
|
38
|
+
raise CriticalError(f"'{COMPUTE_VALIDATION_SET}' must be true or false, got {value!r}")
|
|
39
|
+
return value
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def assign_validation_set(image_data: list[dict], hyperparameters: Mapping[str, Any]) -> None:
|
|
43
|
+
"""Move part of the train images to `val` if `compute_validation_set` asks for it and the loop sent no `val` images.
|
|
44
|
+
"""
|
|
45
|
+
if any(image['set'] == 'val' for image in image_data):
|
|
46
|
+
logger.info('Selecting models on the validation set of the loop')
|
|
47
|
+
return
|
|
48
|
+
if not compute_validation_set(hyperparameters):
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
train_images = [image for image in image_data if image['set'] == 'train']
|
|
52
|
+
if len(train_images) < 2:
|
|
53
|
+
raise CriticalError(f'{len(train_images)} training image(s) are too few to split off a validation set, '
|
|
54
|
+
'add more training images')
|
|
55
|
+
val_images = [image for image in train_images if _position(image) < VALIDATION_FRACTION] \
|
|
56
|
+
or [min(train_images, key=_position)]
|
|
57
|
+
for image in val_images:
|
|
58
|
+
image['set'] = 'val'
|
|
59
|
+
logger.info('Split %d of %d training images off as validation set', len(val_images), len(train_images))
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _position(image: dict) -> float:
|
|
63
|
+
return int(hashlib.sha256(image['id'].encode()).hexdigest()[:8], 16) / 16**8
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: learning_loop_node
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.27.0
|
|
4
4
|
Summary: Python Library for Nodes which connect to the Zauberzeug Learning Loop
|
|
5
5
|
Author-email: Zauberzeug GmbH <info@zauberzeug.com>
|
|
6
6
|
License: MIT
|
|
@@ -349,3 +349,4 @@ After a short time the converted model should be available as well.
|
|
|
349
349
|
- Nodes add properties to `model.json`, which contains all the information which are needed by subsequent nodes. These are typically the properties:
|
|
350
350
|
- `resolution`: resolution in which the model expects images (as `int`, since the resolution is mostly square - later, `resolution_x`` resolution_y` would also be conceivable or `resolutions` to give a list of possible resolutions)
|
|
351
351
|
- `categories`: list of categories with name, id, (later also type), in the order in which they are used by the model -- this is neccessary to be robust about renamings
|
|
352
|
+
- `conf_threshold` (optional): the minimum confidence the trainer chose for this model's detections
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/SOURCES.txt
RENAMED
|
@@ -69,5 +69,6 @@ learning_loop_node/trainer/trainer_logic.py
|
|
|
69
69
|
learning_loop_node/trainer/trainer_logic_generic.py
|
|
70
70
|
learning_loop_node/trainer/trainer_node.py
|
|
71
71
|
learning_loop_node/trainer/training_slot.py
|
|
72
|
+
learning_loop_node/trainer/validation_set.py
|
|
72
73
|
learning_loop_node/trainer/rest/__init__.py
|
|
73
74
|
learning_loop_node/trainer/rest/backdoor_controls.py
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "learning_loop_node"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.27.0"
|
|
4
4
|
description = "Python Library for Nodes which connect to the Zauberzeug Learning Loop"
|
|
5
5
|
authors = [{ name = "Zauberzeug GmbH", email = "info@zauberzeug.com" }]
|
|
6
6
|
license = { text = "MIT" }
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/annotation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_classes/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/data_exchanger.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/__init__.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/categories.py
RENAMED
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/detector_node.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/exceptions.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/geometry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/outbox.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/postprocess.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/__init__.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/about.py
RENAMED
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/detect.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/detector/rest/upload.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/__init__.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/annotator.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/enums/detector.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/entrypoint.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/helpers/log_conf.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/loop_communication.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/__init__.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/batch_size.py
RENAMED
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/downloader.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/exceptions.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/executor.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/io_helpers.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/metrics.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/rest/__init__.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/subprocess.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/test_executor.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/trainer_node.py
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node/trainer/training_slot.py
RENAMED
|
File without changes
|
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/requires.txt
RENAMED
|
File without changes
|
{learning_loop_node-0.26.0 → learning_loop_node-0.27.0}/learning_loop_node.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|