dragon-ml-toolbox 3.10.1__py3-none-any.whl → 3.11.0__py3-none-any.whl
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.
Potentially problematic release.
This version of dragon-ml-toolbox might be problematic. Click here for more details.
- {dragon_ml_toolbox-3.10.1.dist-info → dragon_ml_toolbox-3.11.0.dist-info}/METADATA +1 -1
- {dragon_ml_toolbox-3.10.1.dist-info → dragon_ml_toolbox-3.11.0.dist-info}/RECORD +9 -9
- ml_tools/GUI_tools.py +440 -113
- ml_tools/ensemble_learning.py +5 -4
- ml_tools/keys.py +5 -2
- {dragon_ml_toolbox-3.10.1.dist-info → dragon_ml_toolbox-3.11.0.dist-info}/WHEEL +0 -0
- {dragon_ml_toolbox-3.10.1.dist-info → dragon_ml_toolbox-3.11.0.dist-info}/licenses/LICENSE +0 -0
- {dragon_ml_toolbox-3.10.1.dist-info → dragon_ml_toolbox-3.11.0.dist-info}/licenses/LICENSE-THIRD-PARTY.md +0 -0
- {dragon_ml_toolbox-3.10.1.dist-info → dragon_ml_toolbox-3.11.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
dragon_ml_toolbox-3.
|
|
2
|
-
dragon_ml_toolbox-3.
|
|
1
|
+
dragon_ml_toolbox-3.11.0.dist-info/licenses/LICENSE,sha256=2uUFNy7D0TLgHim1K5s3DIJ4q_KvxEXVilnU20cWliY,1066
|
|
2
|
+
dragon_ml_toolbox-3.11.0.dist-info/licenses/LICENSE-THIRD-PARTY.md,sha256=lY4_rJPnLnMu7YBQaY-_iz1JRDcLdQzNCyeLAF1glJY,1837
|
|
3
3
|
ml_tools/ETL_engineering.py,sha256=yeZsW_7zRvEcuMZbM4E2GV1dxwBoWIeJAcFFk2AK0fY,39502
|
|
4
|
-
ml_tools/GUI_tools.py,sha256=
|
|
4
|
+
ml_tools/GUI_tools.py,sha256=g3zZGpGG0IR1lll5ngn3TDE10xy9kdZJ3RBrV3tQBUU,36135
|
|
5
5
|
ml_tools/MICE_imputation.py,sha256=rYqvwQDVtoAJJ0agXWoGzoZEHedWiA6QzcEKEIkiZ08,11388
|
|
6
6
|
ml_tools/ML_callbacks.py,sha256=g_9nSzoA22UJOQZCPKeDz-Ayh0ECFZLzRd6rZ8SokrE,13080
|
|
7
7
|
ml_tools/ML_evaluation.py,sha256=oiDV6HItQloUUKCUpltV-2pogubWLBieGpc-VUwosAQ,10106
|
|
@@ -14,13 +14,13 @@ ml_tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
14
14
|
ml_tools/_pytorch_models.py,sha256=bpWZsrSwCvHJQkR6UfoPpElsMv9AvmiNErNHC8NYB_I,10132
|
|
15
15
|
ml_tools/data_exploration.py,sha256=M7bn2q5XN9zJZJGAmMMFSFFZh8LGzC2arFelrXw3N6Q,25241
|
|
16
16
|
ml_tools/datasetmaster.py,sha256=S3PKHNQZ9cyAOck8xQltVLZhaD1gFLfgHFL-aRjz4JU,30077
|
|
17
|
-
ml_tools/ensemble_learning.py,sha256=
|
|
17
|
+
ml_tools/ensemble_learning.py,sha256=D-9IbOKtCvyAB-LbPu3sdSRtdp0RZIcQEZcyMnarHmQ,45758
|
|
18
18
|
ml_tools/handle_excel.py,sha256=lwds7rDLlGSCWiWGI7xNg-Z7kxAepogp0lstSFa0590,12949
|
|
19
|
-
ml_tools/keys.py,sha256=
|
|
19
|
+
ml_tools/keys.py,sha256=A3mLrtLZrxL27whAs2F1GPqZ1KzJpxBp6QbhxY5ioPI,636
|
|
20
20
|
ml_tools/logger.py,sha256=UkbiU9ihBhw9VKyn3rZzisdClWV94EBV6B09_D0iUU0,6026
|
|
21
21
|
ml_tools/path_manager.py,sha256=OCpESgdftbi6mOxetDMIaHhazt4N-W8pJx11X3-yNOs,8305
|
|
22
22
|
ml_tools/utilities.py,sha256=FW97hMTLLxjDR1so-C-_yDm_iz2z_YfirRXjG_IwSLo,22843
|
|
23
|
-
dragon_ml_toolbox-3.
|
|
24
|
-
dragon_ml_toolbox-3.
|
|
25
|
-
dragon_ml_toolbox-3.
|
|
26
|
-
dragon_ml_toolbox-3.
|
|
23
|
+
dragon_ml_toolbox-3.11.0.dist-info/METADATA,sha256=6Iaz06zWgkGQAQL4w0AdDrDSqDSNEJPnPjv3IZbyc9A,3274
|
|
24
|
+
dragon_ml_toolbox-3.11.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
dragon_ml_toolbox-3.11.0.dist-info/top_level.txt,sha256=wm-oxax3ciyez6VoO4zsFd-gSok2VipYXnbg3TH9PtU,9
|
|
26
|
+
dragon_ml_toolbox-3.11.0.dist-info/RECORD,,
|
ml_tools/GUI_tools.py
CHANGED
|
@@ -3,19 +3,18 @@ from pathlib import Path
|
|
|
3
3
|
import traceback
|
|
4
4
|
import FreeSimpleGUI as sg
|
|
5
5
|
from functools import wraps
|
|
6
|
-
from typing import Any, Dict, Tuple, List, Literal, Union, Any, Optional
|
|
6
|
+
from typing import Any, Dict, Tuple, List, Literal, Union, Any, Optional, Callable
|
|
7
7
|
from .utilities import _script_info
|
|
8
8
|
import numpy as np
|
|
9
9
|
from .logger import _LOGGER
|
|
10
|
-
from abc import ABC, abstractmethod
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
__all__ = [
|
|
14
13
|
"ConfigManager",
|
|
15
14
|
"GUIFactory",
|
|
16
15
|
"catch_exceptions",
|
|
17
|
-
"
|
|
18
|
-
"
|
|
16
|
+
"FeatureMaster",
|
|
17
|
+
"GUIHandler"
|
|
19
18
|
]
|
|
20
19
|
|
|
21
20
|
# --- Configuration Management ---
|
|
@@ -208,7 +207,7 @@ class GUIFactory:
|
|
|
208
207
|
# --- General-Purpose Layout Generators ---
|
|
209
208
|
def generate_continuous_layout(
|
|
210
209
|
self,
|
|
211
|
-
data_dict: Dict[str,
|
|
210
|
+
data_dict: Dict[str, Union[Tuple[Union[int,float,None], Union[int,float,None]],List[Union[int,float,None]]]],
|
|
212
211
|
is_target: bool = False,
|
|
213
212
|
layout_mode: Literal["grid", "row"] = 'grid',
|
|
214
213
|
features_per_column: int = 4
|
|
@@ -232,9 +231,9 @@ class GUIFactory:
|
|
|
232
231
|
columns = []
|
|
233
232
|
for name, value in data_dict.items():
|
|
234
233
|
if value is None:
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
234
|
+
raise ValueError(f"Feature '{name}' was assigned a 'None' value.")
|
|
235
|
+
elif len(value) != 2:
|
|
236
|
+
raise ValueError(f"Feature '{name}' must provide exactly 2 values.")
|
|
238
237
|
else:
|
|
239
238
|
val_min, val_max = value
|
|
240
239
|
key = name
|
|
@@ -268,7 +267,7 @@ class GUIFactory:
|
|
|
268
267
|
|
|
269
268
|
def generate_combo_layout(
|
|
270
269
|
self,
|
|
271
|
-
data_dict: Dict[str, List[Any]],
|
|
270
|
+
data_dict: Dict[str, Union[List[Any],Tuple[Any,...]]],
|
|
272
271
|
layout_mode: Literal["grid", "row"] = 'grid',
|
|
273
272
|
features_per_column: int = 4
|
|
274
273
|
) -> List[List[sg.Column]]:
|
|
@@ -357,159 +356,487 @@ def catch_exceptions(show_popup: bool = True):
|
|
|
357
356
|
return decorator
|
|
358
357
|
|
|
359
358
|
|
|
360
|
-
# ---
|
|
361
|
-
class
|
|
359
|
+
# --- Feature Handler ---
|
|
360
|
+
class FeatureMaster:
|
|
362
361
|
"""
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
362
|
+
Manages and organizes feature definitions for a machine learning model.
|
|
363
|
+
|
|
364
|
+
This class serves as a centralized registry for all features and targets
|
|
365
|
+
used by a model. It is designed to bridge the gap between a user-facing
|
|
366
|
+
application (like a GUI) and the underlying model's data representation.
|
|
367
|
+
|
|
368
|
+
It takes various types of features (continuous, binary, one-hot encoded,
|
|
369
|
+
categorical) and targets, processing them into two key formats:
|
|
370
|
+
1. A mapping from a user-friendly "GUI name" to the corresponding "model name"
|
|
371
|
+
used in the dataset or model training.
|
|
372
|
+
2. A structure containing the acceptable values or ranges for each feature,
|
|
373
|
+
suitable for populating GUI elements like sliders, dropdowns, or checkboxes.
|
|
374
|
+
|
|
375
|
+
By separating the GUI representation from the model's internal logic, this
|
|
376
|
+
class simplifies the process of building user interfaces for model interaction
|
|
377
|
+
and ensures that user input is correctly formatted. At least one type of
|
|
378
|
+
feature must be provided upon initialization.
|
|
379
|
+
|
|
380
|
+
Properties are available to access the processed mappings and GUI-ready values
|
|
381
|
+
for each feature type.
|
|
366
382
|
"""
|
|
367
|
-
def __init__(self,
|
|
383
|
+
def __init__(self,
|
|
384
|
+
targets: Dict[str, str],
|
|
385
|
+
continuous_features: Optional[Dict[str, Tuple[str, float, float]]] = None,
|
|
386
|
+
binary_features: Optional[Dict[str, str]] = None,
|
|
387
|
+
one_hot_features: Optional[Dict[str, Dict[str, str]]] = None,
|
|
388
|
+
categorical_features: Optional[List[Tuple[str, str, Dict[str, int]]]] = None) -> None:
|
|
368
389
|
"""
|
|
369
|
-
|
|
370
|
-
|
|
390
|
+
Initializes the FeatureMaster instance by processing feature and target definitions.
|
|
391
|
+
|
|
392
|
+
This constructor creates internal mappings to translate between GUI-friendly names and model-specific feature names. It also
|
|
393
|
+
prepares data structures needed to populate UI components.
|
|
394
|
+
|
|
371
395
|
Args:
|
|
372
|
-
|
|
396
|
+
targets (Dict[str, str]):
|
|
397
|
+
A dictionary defining the model's target variables.
|
|
398
|
+
- **key** (str): The name to be displayed in the GUI.
|
|
399
|
+
- **value** (str): The corresponding column name in the model's dataset.
|
|
400
|
+
|
|
401
|
+
continuous_features (Dict[str, Tuple[str, float, float]]):
|
|
402
|
+
A dictionary for continuous numerical features.
|
|
403
|
+
- **key** (str): The name to be displayed in the GUI (e.g., for a slider).
|
|
404
|
+
- **value** (Tuple[str, float, float]): A tuple containing:
|
|
405
|
+
- `[0]` (str): The model's internal feature name.
|
|
406
|
+
- `[1]` (float): The minimum allowed value (inclusive).
|
|
407
|
+
- `[2]` (float): The maximum allowed value (inclusive).
|
|
408
|
+
|
|
409
|
+
binary_features (Dict[str, str]):
|
|
410
|
+
A dictionary for binary (True/False) features.
|
|
411
|
+
- **key** (str): The name to be displayed in the GUI (e.g., for a checkbox).
|
|
412
|
+
- **value** (str): The model's internal feature name.
|
|
413
|
+
|
|
414
|
+
one_hot_features (Dict[str, Dict[str, str]]):
|
|
415
|
+
A dictionary for features that will be one-hot encoded from a single
|
|
416
|
+
categorical input.
|
|
417
|
+
- **key** (str): The name for the group to be displayed in the GUI (e.g.,
|
|
418
|
+
for a dropdown menu).
|
|
419
|
+
- **value** (Dict[str, str]): A nested dictionary where:
|
|
420
|
+
- key (str): The user-selectable option (e.g., 'Category A').
|
|
421
|
+
- value (str): The corresponding model column name that will be
|
|
422
|
+
set to 1.
|
|
423
|
+
|
|
424
|
+
categorical_features (List[Tuple[str, str, Dict[str, int]]]):
|
|
425
|
+
A list for ordinal or label-encoded categorical features.
|
|
426
|
+
- **Each element is a tuple** containing:
|
|
427
|
+
- `[0]` (str): The name to be displayed in the GUI (e.g., for a
|
|
428
|
+
dropdown menu).
|
|
429
|
+
- `[1]` (str): The model's internal feature name.
|
|
430
|
+
- `[2]` (Dict[str, int]): A dictionary mapping the user-selectable
|
|
431
|
+
options to their corresponding integer values.
|
|
373
432
|
"""
|
|
374
|
-
#
|
|
375
|
-
if
|
|
376
|
-
raise
|
|
433
|
+
# Validation
|
|
434
|
+
if continuous_features is None and binary_features is None and one_hot_features is None and categorical_features is None:
|
|
435
|
+
raise ValueError("No features provided.")
|
|
436
|
+
|
|
437
|
+
# Targets
|
|
438
|
+
self._targets_values = self._handle_targets(targets)
|
|
439
|
+
self._targets_mapping = targets
|
|
440
|
+
|
|
441
|
+
# continuous features
|
|
442
|
+
if continuous_features is not None:
|
|
443
|
+
self._continuous_values, self._continuous_mapping = self._handle_continuous_features(continuous_features)
|
|
444
|
+
self.has_continuous = True
|
|
445
|
+
else:
|
|
446
|
+
self._continuous_values, self._continuous_mapping = None, None
|
|
447
|
+
self.has_continuous = False
|
|
448
|
+
|
|
449
|
+
# binary features
|
|
450
|
+
if binary_features is not None:
|
|
451
|
+
self._binary_values = self._handle_binary_features(binary_features)
|
|
452
|
+
self._binary_mapping = binary_features
|
|
453
|
+
self.has_binary = True
|
|
454
|
+
else:
|
|
455
|
+
self._binary_values, self._binary_mapping = None, None
|
|
456
|
+
self.has_binary = False
|
|
457
|
+
|
|
458
|
+
# one-hot features
|
|
459
|
+
if one_hot_features is not None:
|
|
460
|
+
self._one_hot_values = self._handle_one_hot_features(one_hot_features)
|
|
461
|
+
self._one_hot_mapping = one_hot_features
|
|
462
|
+
self.has_one_hot = True
|
|
463
|
+
else:
|
|
464
|
+
self._one_hot_values, self._one_hot_mapping = None, None
|
|
465
|
+
self.has_one_hot = False
|
|
466
|
+
|
|
467
|
+
# categorical features
|
|
468
|
+
if categorical_features is not None:
|
|
469
|
+
self._categorical_values, self._categorical_mapping = self._handle_categorical_features(categorical_features)
|
|
470
|
+
self.has_categorical = True
|
|
471
|
+
else:
|
|
472
|
+
self._categorical_values, self._categorical_mapping = None, None
|
|
473
|
+
self.has_categorical = False
|
|
377
474
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
# -----------------------
|
|
475
|
+
# all features attribute
|
|
476
|
+
self._all_features = self._get_all_gui_features()
|
|
381
477
|
|
|
382
|
-
|
|
478
|
+
def _handle_targets(self, targets: Dict[str, str]):
|
|
479
|
+
# Make dictionary GUI name: range values
|
|
480
|
+
gui_values: dict[str, tuple[None,None]] = {gui_key: (None, None) for gui_key in targets.keys()}
|
|
481
|
+
# Map GUI name to Model name (same as input)
|
|
482
|
+
return gui_values
|
|
383
483
|
|
|
484
|
+
def _handle_continuous_features(self, continuous_features: Dict[str, Tuple[str, float, float]]):
|
|
485
|
+
# Make dictionary GUI name: range values
|
|
486
|
+
gui_values: dict[str, tuple[float,float]] = {gui_key: (tuple_values[1], tuple_values[2]) for gui_key, tuple_values in continuous_features.items()}
|
|
487
|
+
# Map GUI name to Model name
|
|
488
|
+
gui_to_model: dict[str,str] = {gui_key: tuple_values[0] for gui_key, tuple_values in continuous_features.items()}
|
|
489
|
+
return gui_values, gui_to_model
|
|
490
|
+
|
|
491
|
+
def _handle_binary_features(self, binary_features: Dict[str, str]):
|
|
492
|
+
# Make dictionary GUI name: range values
|
|
493
|
+
gui_values: dict[str, tuple[Literal["False"],Literal["True"]]] = {gui_key: ("False", "True") for gui_key in binary_features.keys()}
|
|
494
|
+
# Map GUI name to Model name (same as input)
|
|
495
|
+
return gui_values
|
|
496
|
+
|
|
497
|
+
def _handle_one_hot_features(self, one_hot_features: Dict[str, Dict[str,str]]):
|
|
498
|
+
# Make dictionary GUI name: range values
|
|
499
|
+
gui_values: dict[str, tuple[str,...]] = {gui_key: tuple(nested_dict.keys()) for gui_key, nested_dict in one_hot_features.items()}
|
|
500
|
+
# Map GUI name to Model name and preserve internal mapping (same as input)
|
|
501
|
+
return gui_values
|
|
502
|
+
|
|
503
|
+
def _handle_categorical_features(self, categorical_features: List[Tuple[str, str, Dict[str, int]]]):
|
|
504
|
+
# Make dictionary GUI name: range values
|
|
505
|
+
gui_values: dict[str, tuple[str,...]] = {gui_key: tuple(gui_options.keys()) for gui_key, _, gui_options in categorical_features}
|
|
506
|
+
# Map GUI name to Model name and preserve internal mapping
|
|
507
|
+
gui_to_model: dict[str, tuple[str, dict[str, int]]] = {gui_key: (model_key, gui_options) for gui_key, model_key, gui_options in categorical_features}
|
|
508
|
+
return gui_values, gui_to_model
|
|
509
|
+
|
|
510
|
+
def _get_all_gui_features(self) -> dict[str,Any]:
|
|
511
|
+
all_dict: dict[str,Any] = dict()
|
|
512
|
+
# Add all feature GUI keys
|
|
513
|
+
if self._continuous_mapping is not None:
|
|
514
|
+
all_dict.update(self._continuous_mapping)
|
|
515
|
+
if self._binary_mapping is not None:
|
|
516
|
+
all_dict.update(self._binary_mapping)
|
|
517
|
+
if self._one_hot_mapping is not None:
|
|
518
|
+
all_dict.update(self._one_hot_mapping)
|
|
519
|
+
if self._categorical_mapping is not None:
|
|
520
|
+
all_dict.update(self._categorical_mapping)
|
|
521
|
+
return all_dict
|
|
522
|
+
|
|
384
523
|
@property
|
|
385
|
-
|
|
386
|
-
def gui_input_map(self) -> Dict[str, Literal["continuous","categorical"]]:
|
|
524
|
+
def all_features(self):
|
|
387
525
|
"""
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
Should return a dictionary mapping each GUI input name to its type ('continuous' or 'categorical').
|
|
526
|
+
A merged dictionary of all feature mappings.
|
|
391
527
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
'Material Type': 'categorical'
|
|
397
|
-
}
|
|
398
|
-
```
|
|
528
|
+
The value type varies based on the feature type (str, dict, or tuple).
|
|
529
|
+
|
|
530
|
+
Structure:
|
|
531
|
+
Dict[str, Any]
|
|
399
532
|
"""
|
|
400
|
-
|
|
533
|
+
return self._all_features
|
|
401
534
|
|
|
402
535
|
@property
|
|
403
|
-
|
|
404
|
-
def map_gui_to_real(self) -> Dict[str,str]:
|
|
536
|
+
def targets(self):
|
|
405
537
|
"""
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
Should return a dictionary mapping each GUI continuous feature name to its expected model feature name.
|
|
538
|
+
The mapping for target variables from GUI name to model name.
|
|
409
539
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
540
|
+
Structure:
|
|
541
|
+
Dict[str, str]
|
|
542
|
+
"""
|
|
543
|
+
return self._targets_mapping
|
|
544
|
+
|
|
545
|
+
@property
|
|
546
|
+
def targets_gui(self):
|
|
547
|
+
"""
|
|
548
|
+
The GUI value structure for targets.
|
|
549
|
+
|
|
550
|
+
Structure:
|
|
551
|
+
Dict[str, Tuple[None, None]]
|
|
552
|
+
"""
|
|
553
|
+
return self._targets_values
|
|
554
|
+
|
|
555
|
+
@property
|
|
556
|
+
def continuous(self):
|
|
557
|
+
"""
|
|
558
|
+
The mapping for continuous features from GUI name to model name.
|
|
559
|
+
|
|
560
|
+
Structure:
|
|
561
|
+
Dict[str, str]
|
|
562
|
+
"""
|
|
563
|
+
if self._continuous_mapping is not None:
|
|
564
|
+
return self._continuous_mapping
|
|
565
|
+
|
|
566
|
+
@property
|
|
567
|
+
def continuous_gui(self):
|
|
568
|
+
"""
|
|
569
|
+
The GUI value ranges (min, max) for continuous features.
|
|
570
|
+
|
|
571
|
+
Structure:
|
|
572
|
+
Dict[str, Tuple[float, float]]
|
|
573
|
+
"""
|
|
574
|
+
if self._continuous_values is not None:
|
|
575
|
+
return self._continuous_values
|
|
576
|
+
|
|
577
|
+
@property
|
|
578
|
+
def binary(self):
|
|
579
|
+
"""
|
|
580
|
+
The mapping for binary features from GUI name to model name.
|
|
581
|
+
|
|
582
|
+
Structure:
|
|
583
|
+
Dict[str, str]
|
|
584
|
+
"""
|
|
585
|
+
if self._binary_mapping is not None:
|
|
586
|
+
return self._binary_mapping
|
|
587
|
+
|
|
588
|
+
@property
|
|
589
|
+
def binary_gui(self):
|
|
417
590
|
"""
|
|
418
|
-
|
|
591
|
+
The GUI options ('False', 'True') for binary features.
|
|
592
|
+
|
|
593
|
+
Structure:
|
|
594
|
+
Dict[str, Tuple['False', 'True']]
|
|
595
|
+
"""
|
|
596
|
+
if self._binary_values is not None:
|
|
597
|
+
return self._binary_values
|
|
419
598
|
|
|
420
|
-
@
|
|
421
|
-
def
|
|
599
|
+
@property
|
|
600
|
+
def one_hot(self):
|
|
601
|
+
"""
|
|
602
|
+
The mapping for one-hot encoded features.
|
|
603
|
+
|
|
604
|
+
{"GUI NAME": {"GUI OPTION 1": "model_column"}}
|
|
605
|
+
|
|
606
|
+
Structure:
|
|
607
|
+
Dict[str, Dict[str, str]]
|
|
422
608
|
"""
|
|
423
|
-
|
|
609
|
+
if self._one_hot_mapping is not None:
|
|
610
|
+
return self._one_hot_mapping
|
|
611
|
+
|
|
612
|
+
@property
|
|
613
|
+
def one_hot_gui(self):
|
|
614
|
+
"""
|
|
615
|
+
The GUI options for one-hot encoded feature groups.
|
|
616
|
+
|
|
617
|
+
Structure:
|
|
618
|
+
Dict[str, Tuple[str, ...]]
|
|
619
|
+
"""
|
|
620
|
+
if self._one_hot_values is not None:
|
|
621
|
+
return self._one_hot_values
|
|
424
622
|
|
|
425
|
-
|
|
426
|
-
|
|
623
|
+
@property
|
|
624
|
+
def categorical(self):
|
|
625
|
+
"""
|
|
626
|
+
The mapping for categorical features.
|
|
427
627
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
{
|
|
433
|
-
"is_steel": 0,
|
|
434
|
-
"is_aluminum": 1,
|
|
435
|
-
"is_titanium": 0,
|
|
436
|
-
}
|
|
437
|
-
```
|
|
628
|
+
{"GUI NAME": ("model_column", {"GUI OPTION 1": column_value})}
|
|
629
|
+
|
|
630
|
+
Structure:
|
|
631
|
+
Dict[str, Tuple[str, Dict[str, int]]]
|
|
438
632
|
"""
|
|
439
|
-
|
|
633
|
+
if self._categorical_mapping is not None:
|
|
634
|
+
return self._categorical_mapping
|
|
635
|
+
|
|
636
|
+
@property
|
|
637
|
+
def categorical_gui(self):
|
|
638
|
+
"""
|
|
639
|
+
The GUI options for categorical features.
|
|
640
|
+
|
|
641
|
+
Structure:
|
|
642
|
+
Dict[str, Tuple[str, ...]]
|
|
643
|
+
"""
|
|
644
|
+
if self._categorical_values is not None:
|
|
645
|
+
return self._categorical_values
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
# --- GUI-Model API ---
|
|
649
|
+
class GUIHandler:
|
|
650
|
+
"""
|
|
651
|
+
Translates data between a GUI and a machine learning model.
|
|
440
652
|
|
|
441
|
-
|
|
653
|
+
This class acts as the primary interface between a user-facing application
|
|
654
|
+
(FreeSimpleGUI) and the model's expected data format. It uses a `FeatureMaster` instance to correctly process
|
|
655
|
+
and encode user inputs.
|
|
656
|
+
|
|
657
|
+
Its main responsibilities are:
|
|
658
|
+
1. To take raw values from GUI elements and, using the definitions from
|
|
659
|
+
`FeatureMaster`, convert them into a single, ordered `numpy.ndarray`
|
|
660
|
+
that can be fed directly into a model for inference.
|
|
661
|
+
2. To take the results of a model's inference and update the
|
|
662
|
+
corresponding target fields in the GUI to display the prediction.
|
|
663
|
+
|
|
664
|
+
This handler ensures a clean separation of concerns, where the GUI is
|
|
665
|
+
only responsible for presentation, and the model sees correctly formatted numerical data.
|
|
666
|
+
"""
|
|
667
|
+
def __init__(self, feature_handler: FeatureMaster, model_expected_features: list[str]) -> None:
|
|
668
|
+
"""
|
|
669
|
+
Initializes the GUIHandler.
|
|
670
|
+
|
|
671
|
+
Args:
|
|
672
|
+
feature_handler (FeatureMaster):
|
|
673
|
+
An initialized instance of the `FeatureMaster` class. This object
|
|
674
|
+
contains all the necessary mappings and definitions for the model's
|
|
675
|
+
features and targets.
|
|
676
|
+
model_expected_features (list[str]):
|
|
677
|
+
A list of strings specifying the exact names of the features the
|
|
678
|
+
machine learning model expects in its input vector. The **order**
|
|
679
|
+
of features in this list is critical, as it dictates the final
|
|
680
|
+
column order of the output numpy array.
|
|
681
|
+
|
|
682
|
+
Raises:
|
|
683
|
+
TypeError: If `model_expected_features` is not a list or if any of its elements are not strings.
|
|
442
684
|
"""
|
|
443
|
-
|
|
685
|
+
if not isinstance(model_expected_features, list):
|
|
686
|
+
raise TypeError("Input 'model_expected_features' must be a list.")
|
|
687
|
+
if not all(isinstance(col, str) for col in model_expected_features):
|
|
688
|
+
raise TypeError("All elements in the 'model_expected_features' must be strings.")
|
|
444
689
|
|
|
445
|
-
|
|
690
|
+
# Model expected features
|
|
691
|
+
self.model_expected_features = tuple(model_expected_features)
|
|
692
|
+
# Feature master instance
|
|
693
|
+
self.master = feature_handler
|
|
694
|
+
|
|
695
|
+
def _process_continuous(self, gui_feature: str, chosen_value: Any) -> Tuple[str,float]:
|
|
696
|
+
"""
|
|
697
|
+
Maps GUI name to model expected name and casts the value to float.
|
|
446
698
|
"""
|
|
447
699
|
try:
|
|
448
|
-
|
|
700
|
+
model_name = self.master.continuous[gui_feature]
|
|
449
701
|
float_value = float(chosen_value)
|
|
450
702
|
except KeyError as e:
|
|
451
|
-
_LOGGER.error(f"No matching name for '{
|
|
703
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as continuous.")
|
|
452
704
|
raise e
|
|
453
705
|
except (ValueError, TypeError) as e2:
|
|
454
|
-
_LOGGER.error(f"Invalid number conversion for '{chosen_value}' of '{
|
|
706
|
+
_LOGGER.error(f"Invalid number conversion for '{chosen_value}' of '{gui_feature}'.")
|
|
455
707
|
raise e2
|
|
456
708
|
else:
|
|
457
|
-
return
|
|
458
|
-
|
|
459
|
-
def
|
|
709
|
+
return model_name, float_value
|
|
710
|
+
|
|
711
|
+
def _process_binary(self, gui_feature: str, chosen_value: str) -> Tuple[str,int]:
|
|
460
712
|
"""
|
|
461
|
-
|
|
713
|
+
Maps GUI name to model expected name and casts the value to binary (0,1).
|
|
714
|
+
"""
|
|
715
|
+
try:
|
|
716
|
+
model_name = self.master.binary[gui_feature]
|
|
717
|
+
binary_mapping_keys = self.master.binary_gui[gui_feature]
|
|
718
|
+
except KeyError as e:
|
|
719
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as binary.")
|
|
720
|
+
raise e
|
|
721
|
+
else:
|
|
722
|
+
mapping_dict = {
|
|
723
|
+
binary_mapping_keys[0]: 0,
|
|
724
|
+
binary_mapping_keys[1]: 1
|
|
725
|
+
}
|
|
726
|
+
result = mapping_dict[chosen_value]
|
|
727
|
+
return model_name, result
|
|
462
728
|
|
|
463
|
-
|
|
729
|
+
def _process_one_hot(self, gui_feature: str, chosen_value: str) -> Dict[str,int]:
|
|
464
730
|
"""
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
731
|
+
Maps GUI names to model expected names and casts values to one-hot encoding.
|
|
732
|
+
"""
|
|
733
|
+
try:
|
|
734
|
+
one_hot_mapping = self.master.one_hot[gui_feature]
|
|
735
|
+
except KeyError as e:
|
|
736
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as one-hot.")
|
|
737
|
+
raise e
|
|
738
|
+
else:
|
|
739
|
+
mapped_chosen_value = one_hot_mapping[chosen_value]
|
|
740
|
+
# base results mapped to 0
|
|
741
|
+
results = {model_key: 0 for model_key in one_hot_mapping.values()}
|
|
742
|
+
# update chosen value
|
|
743
|
+
results[mapped_chosen_value] = 1
|
|
744
|
+
return results
|
|
745
|
+
|
|
746
|
+
def _process_categorical(self, gui_feature: str, chosen_value: str) -> Tuple[str,int]:
|
|
747
|
+
"""
|
|
748
|
+
Maps GUI name to model expected name and casts the value to a categorical number.
|
|
749
|
+
"""
|
|
750
|
+
try:
|
|
751
|
+
categorical_tuple = self.master.categorical[gui_feature]
|
|
752
|
+
except KeyError as e:
|
|
753
|
+
_LOGGER.error(f"No matching name for '{gui_feature}' defined as categorical.")
|
|
754
|
+
raise e
|
|
755
|
+
else:
|
|
756
|
+
model_name = categorical_tuple[0]
|
|
757
|
+
categorical_mapping = categorical_tuple[1]
|
|
758
|
+
result = categorical_mapping[chosen_value]
|
|
759
|
+
return model_name, result
|
|
760
|
+
|
|
761
|
+
def update_target_fields(self, window: sg.Window, inference_results: Dict[str, Any]):
|
|
762
|
+
"""
|
|
763
|
+
Updates the GUI's target fields with inference results.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
window (sg.Window): The application's window object.
|
|
767
|
+
inference_results (dict): A dictionary where keys are target names (as used by the model) and values are the predicted results to update.
|
|
768
|
+
"""
|
|
769
|
+
# Target values to update
|
|
770
|
+
gui_targets_values = {gui_key: inference_results[model_key] for gui_key, model_key in self.master.targets.items()}
|
|
771
|
+
|
|
772
|
+
# Update window
|
|
773
|
+
for gui_key, result in gui_targets_values.items():
|
|
774
|
+
# Format numbers to 2 decimal places, leave other types as-is
|
|
775
|
+
display_value = f"{result:.2f}" if isinstance(result, (int, float)) else result
|
|
776
|
+
window[gui_key].update(display_value) # type: ignore
|
|
469
777
|
|
|
778
|
+
def _call_subprocess(self, window_values: Dict[str,Any], master_feature: Dict[str,str], processor: Callable) -> Dict[str, Union[float,int]]:
|
|
779
|
+
processed_features_subset: Dict[str, Union[float,int]] = dict()
|
|
780
|
+
|
|
781
|
+
for gui_name in master_feature.keys():
|
|
782
|
+
chosen_value = window_values.get(gui_name)
|
|
470
783
|
# value validation
|
|
471
784
|
if chosen_value is None or str(chosen_value) == '':
|
|
472
785
|
raise ValueError(f"GUI input '{gui_name}' is missing a value.")
|
|
786
|
+
# process value
|
|
787
|
+
raw_result = processor(gui_name, chosen_value)
|
|
788
|
+
if isinstance(raw_result, tuple):
|
|
789
|
+
model_name, result = raw_result
|
|
790
|
+
processed_features_subset[model_name] = result
|
|
791
|
+
elif isinstance(raw_result, dict):
|
|
792
|
+
processed_features_subset.update(raw_result)
|
|
793
|
+
else:
|
|
794
|
+
raise TypeError(f"Processor returned an unrecognized type: {type(raw_result)}")
|
|
795
|
+
|
|
796
|
+
return processed_features_subset
|
|
473
797
|
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
798
|
+
def process_features(self, window_values: Dict[str, Any]) -> np.ndarray:
|
|
799
|
+
"""
|
|
800
|
+
Translates GUI values to a model-expected input array, returning a 1D numpy array.
|
|
801
|
+
"""
|
|
802
|
+
# Stage 1: Process GUI inputs into a dictionary
|
|
803
|
+
processed_features: Dict[str, Union[float,int]] = {}
|
|
804
|
+
|
|
805
|
+
if self.master.has_continuous:
|
|
806
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
807
|
+
master_feature=self.master.continuous,
|
|
808
|
+
processor=self._process_continuous)
|
|
809
|
+
processed_features.update(processed_subset)
|
|
810
|
+
|
|
811
|
+
if self.master.has_binary:
|
|
812
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
813
|
+
master_feature=self.master.binary,
|
|
814
|
+
processor=self._process_binary)
|
|
815
|
+
processed_features.update(processed_subset)
|
|
816
|
+
|
|
817
|
+
if self.master.has_one_hot:
|
|
818
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
819
|
+
master_feature=self.master.one_hot,
|
|
820
|
+
processor=self._process_one_hot)
|
|
821
|
+
processed_features.update(processed_subset)
|
|
478
822
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
823
|
+
if self.master.has_categorical:
|
|
824
|
+
processed_subset = self._call_subprocess(window_values=window_values,
|
|
825
|
+
master_feature=self.master.categorical,
|
|
826
|
+
processor=self._process_categorical)
|
|
827
|
+
processed_features.update(processed_subset)
|
|
483
828
|
|
|
484
829
|
# Stage 2: Assemble the final vector using the model's required order
|
|
485
|
-
final_vector: List[float] =
|
|
830
|
+
final_vector: List[float] = list()
|
|
486
831
|
|
|
487
832
|
try:
|
|
488
|
-
for feature_name in self.
|
|
833
|
+
for feature_name in self.model_expected_features:
|
|
489
834
|
final_vector.append(processed_features[feature_name])
|
|
490
835
|
except KeyError as e:
|
|
491
|
-
raise RuntimeError(
|
|
492
|
-
|
|
493
|
-
f"the required model feature: '{e}'"
|
|
494
|
-
f"Check the gui_input_map and process_categorical logic."
|
|
495
|
-
)
|
|
496
|
-
|
|
836
|
+
raise RuntimeError(f"Configuration Error: Implemented methods failed to generate the required model feature: '{e}'")
|
|
837
|
+
|
|
497
838
|
return np.array(final_vector, dtype=np.float32)
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
def update_target_fields(window: sg.Window, results_dict: Dict[str, Any]):
|
|
501
|
-
"""
|
|
502
|
-
Updates the GUI's target fields with inference results.
|
|
503
|
-
|
|
504
|
-
Args:
|
|
505
|
-
window (sg.Window): The application's window object.
|
|
506
|
-
results_dict (dict): A dictionary where keys are target element-keys and values are the predicted results to update.
|
|
507
|
-
"""
|
|
508
|
-
for target_name, result in results_dict.items():
|
|
509
|
-
# Format numbers to 2 decimal places, leave other types as-is
|
|
510
|
-
display_value = f"{result:.2f}" if isinstance(result, (int, float)) else result
|
|
511
|
-
window[target_name].update(display_value) # type: ignore
|
|
512
|
-
|
|
839
|
+
|
|
513
840
|
|
|
514
841
|
def info():
|
|
515
842
|
_script_info(__all__)
|
ml_tools/ensemble_learning.py
CHANGED
|
@@ -973,9 +973,9 @@ class InferenceHandler:
|
|
|
973
973
|
verbose=self.verbose,
|
|
974
974
|
raise_on_error=True) # type: ignore
|
|
975
975
|
|
|
976
|
-
model: Any = full_object[
|
|
977
|
-
target_name: str = full_object[
|
|
978
|
-
feature_names_list: List[str] = full_object[
|
|
976
|
+
model: Any = full_object[ModelSaveKeys.MODEL]
|
|
977
|
+
target_name: str = full_object[ModelSaveKeys.TARGET]
|
|
978
|
+
feature_names_list: List[str] = full_object[ModelSaveKeys.FEATURES]
|
|
979
979
|
|
|
980
980
|
# Check that feature names match
|
|
981
981
|
if self._feature_names is None:
|
|
@@ -1026,7 +1026,8 @@ class InferenceHandler:
|
|
|
1026
1026
|
else: # Classification
|
|
1027
1027
|
label = model.predict(features)[0]
|
|
1028
1028
|
probabilities = model.predict_proba(features)[0]
|
|
1029
|
-
results[target_name] = {
|
|
1029
|
+
results[target_name] = {ModelSaveKeys.CLASSIFICATION_LABEL: label,
|
|
1030
|
+
ModelSaveKeys.CLASSIFICATION_PROBABILITIES: probabilities}
|
|
1030
1031
|
|
|
1031
1032
|
if self.verbose:
|
|
1032
1033
|
_LOGGER.info("✅ Inference process complete.")
|
ml_tools/keys.py
CHANGED
|
@@ -17,9 +17,12 @@ class LogKeys:
|
|
|
17
17
|
class ModelSaveKeys:
|
|
18
18
|
"""
|
|
19
19
|
Used internally for ensemble_learning module.
|
|
20
|
-
|
|
21
|
-
Keys used for serializing a trained model metadata.
|
|
22
20
|
"""
|
|
21
|
+
# Serializing a trained model metadata.
|
|
23
22
|
MODEL = "model"
|
|
24
23
|
FEATURES = "feature_names"
|
|
25
24
|
TARGET = "target_name"
|
|
25
|
+
|
|
26
|
+
# Classification keys
|
|
27
|
+
CLASSIFICATION_LABEL = "label"
|
|
28
|
+
CLASSIFICATION_PROBABILITIES = "probabilities"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|