torchwires 0.0.2__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.
Files changed (38) hide show
  1. torchwires-0.0.2/LICENSE +21 -0
  2. torchwires-0.0.2/PKG-INFO +18 -0
  3. torchwires-0.0.2/README.md +1 -0
  4. torchwires-0.0.2/pyproject.toml +28 -0
  5. torchwires-0.0.2/setup.cfg +4 -0
  6. torchwires-0.0.2/src/torchwires/__init__.py +4 -0
  7. torchwires-0.0.2/src/torchwires/callbacks/autosave_callback.py +41 -0
  8. torchwires-0.0.2/src/torchwires/callbacks/base_callback.py +25 -0
  9. torchwires-0.0.2/src/torchwires/callbacks/callbacks_repo.py +50 -0
  10. torchwires-0.0.2/src/torchwires/callbacks/checkpoint_callback.py +52 -0
  11. torchwires-0.0.2/src/torchwires/callbacks/early_stopping_callback.py +75 -0
  12. torchwires-0.0.2/src/torchwires/common/functions/json_storage.py +33 -0
  13. torchwires-0.0.2/src/torchwires/common/logger/logger.py +7 -0
  14. torchwires-0.0.2/src/torchwires/constants/constants.py +11 -0
  15. torchwires-0.0.2/src/torchwires/dataloaders_repo/dataloaders_repo.py +28 -0
  16. torchwires-0.0.2/src/torchwires/display_widget/display_widget.py +93 -0
  17. torchwires-0.0.2/src/torchwires/history/history.py +115 -0
  18. torchwires-0.0.2/src/torchwires/models_repo/model_node.py +77 -0
  19. torchwires-0.0.2/src/torchwires/models_repo/models_repo.py +90 -0
  20. torchwires-0.0.2/src/torchwires/optimizers_repo/__init__.py +0 -0
  21. torchwires-0.0.2/src/torchwires/optimizers_repo/optimizer_node.py +89 -0
  22. torchwires-0.0.2/src/torchwires/optimizers_repo/optimizers_repo.py +85 -0
  23. torchwires-0.0.2/src/torchwires/repo/repo.py +183 -0
  24. torchwires-0.0.2/src/torchwires/state/base_state.py +17 -0
  25. torchwires-0.0.2/src/torchwires/state/batch_state.py +66 -0
  26. torchwires-0.0.2/src/torchwires/state/epoch_state.py +55 -0
  27. torchwires-0.0.2/src/torchwires/steps_repo/base_step.py +28 -0
  28. torchwires-0.0.2/src/torchwires/steps_repo/steps/forward_step.py +45 -0
  29. torchwires-0.0.2/src/torchwires/steps_repo/steps/loss_step.py +67 -0
  30. torchwires-0.0.2/src/torchwires/steps_repo/steps/metric_step.py +36 -0
  31. torchwires-0.0.2/src/torchwires/steps_repo/steps_repo.py +36 -0
  32. torchwires-0.0.2/src/torchwires/trainer/trainer.py +365 -0
  33. torchwires-0.0.2/src/torchwires/visualizer/visualizer.py +186 -0
  34. torchwires-0.0.2/src/torchwires.egg-info/PKG-INFO +18 -0
  35. torchwires-0.0.2/src/torchwires.egg-info/SOURCES.txt +36 -0
  36. torchwires-0.0.2/src/torchwires.egg-info/dependency_links.txt +1 -0
  37. torchwires-0.0.2/src/torchwires.egg-info/requires.txt +4 -0
  38. torchwires-0.0.2/src/torchwires.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Your Full Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: torchwires
3
+ Version: 0.0.2
4
+ Summary: A lightweight training framework for PyTorch
5
+ Author-email: Youssef Samy Youssef <yosefsamy019@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/YosefSamy019/torchwires
8
+ Project-URL: Repository, https://github.com/YosefSamy019/torchwires
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: torch
13
+ Requires-Dist: numpy
14
+ Requires-Dist: pandas
15
+ Requires-Dist: matplotlib
16
+ Dynamic: license-file
17
+
18
+ Soon.
@@ -0,0 +1 @@
1
+ Soon.
@@ -0,0 +1,28 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "torchwires"
7
+ version = "0.0.2"
8
+ description = "A lightweight training framework for PyTorch"
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ authors = [
12
+ { name = "Youssef Samy Youssef", email = "yosefsamy019@gmail.com" },
13
+ ]
14
+ license = "MIT"
15
+ license-files = ["LICENSE"]
16
+ dependencies = [
17
+ "torch",
18
+ "numpy",
19
+ "pandas",
20
+ "matplotlib",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/YosefSamy019/torchwires"
25
+ Repository = "https://github.com/YosefSamy019/torchwires"
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ from .repo.repo import Repo
2
+ from .callbacks.base_callback import BaseCallback
3
+ from .trainer.trainer import Trainer
4
+ from .visualizer.visualizer import Visualizer
@@ -0,0 +1,41 @@
1
+ from typing import Literal, Callable
2
+
3
+ from .base_callback import BaseCallback
4
+ from ..common.logger.logger import print_log
5
+ from ..state.epoch_state import EpochState
6
+
7
+
8
+ class AutoSaveCallback(BaseCallback):
9
+ def __init__(
10
+ self,
11
+ save_function: Callable[[str], None],
12
+ interval: int,
13
+ checkpoint_name: str,
14
+ concat_with_epoch_no: bool,
15
+ ):
16
+ super().__init__()
17
+ self._save_function = save_function
18
+ self._interval = interval
19
+ self._checkpoint_name = checkpoint_name
20
+ self._concat_with_epoch_no = concat_with_epoch_no
21
+
22
+ def on_epoch_end(self, epoch_state: EpochState):
23
+ cur_epoch = epoch_state.aggregate_over_batches(
24
+ split="train",
25
+ feature=epoch_state.KEY_EPOCH_NO,
26
+ func='max'
27
+ )
28
+
29
+ if cur_epoch % self._interval == 0:
30
+ if self._concat_with_epoch_no:
31
+ save_name = f"{self._checkpoint_name} epoch={cur_epoch}"
32
+ else:
33
+ save_name = f"{self._checkpoint_name}"
34
+
35
+ print_log(
36
+ title="Auto Save Checkpoint",
37
+ content=
38
+ f"save to checkpoint {save_name}",
39
+ )
40
+
41
+ self._save_function(save_name)
@@ -0,0 +1,25 @@
1
+ from abc import ABC
2
+
3
+ from ..state.epoch_state import EpochState
4
+
5
+
6
+ class BaseCallback(ABC):
7
+ def on_train_start(self):
8
+ pass
9
+
10
+ def on_train_end(self):
11
+ pass
12
+
13
+ def on_epoch_start(
14
+ self
15
+ ):
16
+ pass
17
+
18
+ def on_epoch_end(
19
+ self,
20
+ epoch_state: EpochState,
21
+ ):
22
+ pass
23
+
24
+ def should_stop_training(self):
25
+ return False
@@ -0,0 +1,50 @@
1
+ from typing import List
2
+
3
+ from .base_callback import BaseCallback
4
+ from ..state.epoch_state import EpochState
5
+
6
+
7
+ class CallbacksRepo:
8
+ def __init__(self):
9
+ self._callbacks_list: List[BaseCallback] = []
10
+
11
+ def register_callback(
12
+ self,
13
+ callback: BaseCallback
14
+ ):
15
+ self._callbacks_list.append(callback)
16
+
17
+ def register_callbacks(
18
+ self,
19
+ callbacks: List[BaseCallback]
20
+ ):
21
+ for callback in callbacks:
22
+ self.register_callback(callback)
23
+
24
+ def notify_train_start(self) -> None:
25
+ for callback in self._callbacks_list:
26
+ callback.on_train_start()
27
+
28
+ def notify_train_end(self) -> None:
29
+ for callback in self._callbacks_list:
30
+ callback.on_train_end()
31
+
32
+ def notify_epoch_start(
33
+ self
34
+ ) -> None:
35
+ for callback in self._callbacks_list:
36
+ callback.on_epoch_start()
37
+
38
+ def notify_epoch_end(
39
+ self,
40
+ epoch_state: EpochState
41
+ ) -> None:
42
+ for callback in self._callbacks_list:
43
+ callback.on_epoch_end(epoch_state=epoch_state)
44
+
45
+ def should_stop_training(self) -> bool:
46
+ for callback in self._callbacks_list:
47
+ if callback.should_stop_training():
48
+ return True
49
+
50
+ return False
@@ -0,0 +1,52 @@
1
+ from typing import Literal, Callable
2
+
3
+ from .base_callback import BaseCallback
4
+ from ..common.logger.logger import print_log
5
+ from ..state.epoch_state import EpochState
6
+
7
+
8
+ class CheckpointCallback(BaseCallback):
9
+ def __init__(
10
+ self,
11
+ save_function: Callable[[str], None],
12
+ split: Literal["train", "val"],
13
+ monitor: str,
14
+ mode: Literal["min", "max"],
15
+ ):
16
+ super().__init__()
17
+ self._save_function = save_function
18
+ self._split = split
19
+ self._monitor = monitor
20
+ self._mode = mode
21
+
22
+ self._checkpoint_name = f"best_checkpoint_{self._split}-{self._monitor}"
23
+
24
+ if mode == "min":
25
+ self._best_value = float("inf")
26
+ elif mode == "max":
27
+ self._best_value = float("inf") * -1
28
+ else:
29
+ raise ValueError(f"Invalid mode: {mode}")
30
+
31
+ def on_epoch_end(self, epoch_state: EpochState):
32
+ cur_value = epoch_state.aggregate_over_batches(feature=self._monitor, split=self._split, func='mean')
33
+
34
+ if self._mode == "min" and cur_value < self._best_value:
35
+ new_target_achieved = True
36
+ elif self._mode == "max" and cur_value > self._best_value:
37
+ new_target_achieved = True
38
+ else:
39
+ new_target_achieved = False
40
+
41
+ if new_target_achieved:
42
+ print_log(
43
+ title="Checkpoint taken",
44
+ content=
45
+ f"{self._split}-{self._monitor} improved from {self._best_value:0.6f} to {cur_value:0.6f}"
46
+ " | "
47
+ f"checkpoint name: {self._checkpoint_name}",
48
+ )
49
+
50
+ self._best_value = cur_value
51
+
52
+ self._save_function(self._checkpoint_name)
@@ -0,0 +1,75 @@
1
+ from typing import Literal
2
+
3
+ from .base_callback import BaseCallback
4
+ from ..common.logger.logger import print_log
5
+ from ..state.epoch_state import EpochState
6
+
7
+
8
+ class EarlyStoppingCallback(BaseCallback):
9
+ def __init__(
10
+ self,
11
+ split: Literal["train", "val"],
12
+ monitor: str,
13
+ mode: Literal["min", "max"],
14
+ patience: int,
15
+ ):
16
+ super().__init__()
17
+ self._split = split
18
+ self._monitor = monitor
19
+ self._mode = mode
20
+ self._patience = patience
21
+
22
+ if mode == "min":
23
+ self._best_value = float("inf")
24
+ elif mode == "max":
25
+ self._best_value = float("inf") * -1
26
+ else:
27
+ raise ValueError(f"Invalid mode: {mode}")
28
+
29
+ self._counter = 0
30
+
31
+ def on_train_start(self):
32
+ self._counter = 0
33
+
34
+ def on_epoch_end(self, epoch_state: EpochState):
35
+ cur_val = epoch_state.aggregate_over_batches(
36
+ feature=self._monitor,
37
+ split=self._split,
38
+ func='mean'
39
+ )
40
+
41
+ if cur_val is None:
42
+ print_log(
43
+ title="Early Stopping",
44
+ content=f"Couldn't find {self._monitor} feature in {self._split} split",
45
+ )
46
+ return
47
+
48
+ if self._mode == "min":
49
+ self._best_value = min(cur_val, self._best_value)
50
+ if cur_val > self._best_value:
51
+ self._counter += 1
52
+ else:
53
+ self._counter = 0
54
+
55
+ if self._mode == "max":
56
+ self._best_value = max(cur_val, self._best_value)
57
+ if cur_val < self._best_value:
58
+ self._counter += 1
59
+ else:
60
+ self._counter = 0
61
+
62
+ if self.should_stop_training():
63
+ print_log(
64
+ title="Early Stopping",
65
+ content=f'the training has been stopped',
66
+ )
67
+ else:
68
+ pass
69
+ # print_log(
70
+ # title="Early Stopping",
71
+ # content=f"counter={self._counter:d}, best={self._best_value:0.5f}",
72
+ # )
73
+
74
+ def should_stop_training(self) -> bool:
75
+ return self._counter >= self._patience
@@ -0,0 +1,33 @@
1
+ import json
2
+ import os
3
+
4
+
5
+ def check_json(*path_segments) -> bool:
6
+ load_path = os.path.join(*path_segments)
7
+
8
+ if not load_path.endswith(".json"):
9
+ load_path += ".json"
10
+
11
+ return os.path.exists(load_path)
12
+
13
+
14
+ def load_json(*path_segments) -> dict:
15
+ load_path = os.path.join(*path_segments)
16
+
17
+ if not load_path.endswith(".json"):
18
+ load_path += ".json"
19
+
20
+ with open(load_path, 'r') as f:
21
+ content = json.load(f)
22
+
23
+ return content
24
+
25
+
26
+ def save_json(content, *path_segments) -> None:
27
+ save_path = os.path.join(*path_segments)
28
+
29
+ if not save_path.endswith(".json"):
30
+ save_path += ".json"
31
+
32
+ with open(save_path, 'w') as f:
33
+ json.dump(content, f)
@@ -0,0 +1,7 @@
1
+ def print_log(
2
+ title: str,
3
+ content: str,
4
+ sep: str = ": ",
5
+ ):
6
+ print(f"\033[1m{title}\033[0m", end=sep)
7
+ print(content)
@@ -0,0 +1,11 @@
1
+ DEFAULT_CHECKPOINT_NAME = "last"
2
+
3
+ RAW_ANNOT_FOR_LOSS_NAME = ".raw"
4
+ WEIGHT_ANNOT_FOR_LOSS_NAME = ".weight"
5
+ EFF_ANNOT_FOR_LOSS_NAME = ".eff"
6
+
7
+ LOADER_TRAIN_TYPE = "train"
8
+ LOADER_VAL_TYPE = "val"
9
+ LOADER_TEST_TYPE = "test"
10
+
11
+ LR_ANNOT_FOR_OPTIMIZER_NAME = ".lr"
@@ -0,0 +1,28 @@
1
+ import torch.utils.data
2
+
3
+
4
+ class DataLoadersRepo:
5
+ def __init__(self) -> None:
6
+ self._train_loader: torch.utils.data.DataLoader | None = None
7
+ self._val_loader: torch.utils.data.DataLoader | None = None
8
+
9
+ self._loader_output_keys: list[str] = []
10
+
11
+ def attach_train_loader(
12
+ self, train_loader: torch.utils.data.DataLoader | None) -> None:
13
+ self._train_loader = train_loader
14
+
15
+ def attach_val_loader(self, val_loader: torch.utils.data.DataLoader | None) -> None:
16
+ self._val_loader = val_loader
17
+
18
+ def attach_loader_output_keys(self, loader_output_keys: list[str]) -> None:
19
+ self._loader_output_keys = loader_output_keys
20
+
21
+ def get_train_loader(self) -> torch.utils.data.DataLoader | None:
22
+ return self._train_loader
23
+
24
+ def get_val_loader(self) -> torch.utils.data.DataLoader | None:
25
+ return self._val_loader
26
+
27
+ def get_loader_output_keys(self) -> list[str]:
28
+ return self._loader_output_keys
@@ -0,0 +1,93 @@
1
+ import time
2
+ from pickle import NONE
3
+ from typing import List
4
+
5
+ from ..state.batch_state import BatchState
6
+ from ..state.epoch_state import EpochState
7
+
8
+
9
+ class DisplayWidget:
10
+ def __init__(
11
+ self,
12
+ tracked_features: List[str],
13
+ n_columns: int = 4,
14
+ columns_width: int = 35,
15
+ ):
16
+ self._tracked_features = tracked_features
17
+ self._n_columns = n_columns
18
+ self._columns_width = columns_width
19
+ self._max_length_ever = 0
20
+
21
+ def display_batch_state(
22
+ self,
23
+ split: str,
24
+ epoch_no: int,
25
+ max_epochs: int,
26
+ batch_no: int,
27
+ max_batch: int,
28
+ batch_state: BatchState,
29
+ ):
30
+ # time.sleep(0.1)
31
+
32
+ segments = [
33
+ "\r"
34
+ f"Epoch: {epoch_no}/{max_epochs} ({100 * epoch_no / max_epochs:5.1f} %)",
35
+ f"Batch: {batch_no}/{max_batch} ({100 * batch_no / max_batch:5.1f} %)",
36
+ f"Split: {split}",
37
+ " | ",
38
+ ]
39
+
40
+ for k, v in batch_state.get_dict().items():
41
+ if k not in self._tracked_features:
42
+ continue
43
+
44
+ if isinstance(v, float):
45
+ segments.append(f"{k}: {v:3.5f}")
46
+ else:
47
+ segments.append(f"{k}: {v}")
48
+
49
+ segments.append(f" - ")
50
+
51
+ line_str = ' '.join(segments[:-1])
52
+ self._max_length_ever = max(self._max_length_ever, len(line_str))
53
+ print(line_str, end=' ' * 15)
54
+
55
+ def display_epoch_state(
56
+ self,
57
+ epoch_no: int,
58
+ max_epochs: int,
59
+ epoch_state: EpochState,
60
+ ):
61
+ print(f"\rEpoch: {epoch_no}/{max_epochs}", end=' ' * self._max_length_ever)
62
+ print()
63
+
64
+ cell_i = 0
65
+
66
+ for feature in self._tracked_features:
67
+ for split in epoch_state.get_all_splits():
68
+ if feature in [BatchState.KEY_LOADER_TYPE, BatchState.KEY_EPOCH_NO, BatchState.KEY_BATCH_NO]:
69
+ continue
70
+
71
+ value = epoch_state.aggregate_over_batches(
72
+ feature=feature,
73
+ split=split,
74
+ func='mean',
75
+ )
76
+
77
+ if value is None:
78
+ val_str = f"{split}-{feature}: {None}"
79
+ else:
80
+ val_str = f"{split}-{feature}: {value:3.5f}"
81
+
82
+ val_str = val_str.ljust(self._columns_width)
83
+ print(val_str, end=' ')
84
+
85
+ if (cell_i + 1) % self._n_columns == 0:
86
+ print()
87
+ else:
88
+ print(' | ', end='')
89
+
90
+ cell_i += 1
91
+
92
+ def reset(self):
93
+ self._max_length_ever = 0
@@ -0,0 +1,115 @@
1
+ import json
2
+ import os
3
+ from typing import Any, Dict
4
+
5
+ import numpy as np
6
+ import torch
7
+ import pandas as pd
8
+
9
+ from ..common.logger.logger import print_log
10
+
11
+
12
+ class History:
13
+ def __init__(
14
+ self,
15
+ ):
16
+ self._tracked_features = []
17
+ self._content: list[dict[str, Any]] = []
18
+
19
+ def save(
20
+ self,
21
+ repo_name: str,
22
+ experiment: str,
23
+ ):
24
+ dir_path = os.path.join(repo_name, experiment)
25
+ cache_path = os.path.join(repo_name, experiment, "history.json")
26
+ csv_path = os.path.join(repo_name, experiment, "history.csv")
27
+ tracked_features_path = os.path.join(repo_name, experiment, "history_tracked.json")
28
+
29
+ os.makedirs(dir_path, exist_ok=True)
30
+
31
+ with open(cache_path, "w") as f:
32
+ f.write(
33
+ json.dumps(self._content)
34
+ )
35
+
36
+ pd.read_json(cache_path).to_csv(csv_path, index=False)
37
+
38
+ with open(tracked_features_path, "w") as f:
39
+ f.write(
40
+ json.dumps(self._tracked_features)
41
+ )
42
+
43
+ def load(
44
+ self,
45
+ repo_name: str,
46
+ experiment: str,
47
+ ):
48
+ cache_path = os.path.join(repo_name, experiment, "history.json")
49
+ tracked_features_path = os.path.join(repo_name, experiment, "history_tracked.json")
50
+
51
+ if os.path.exists(cache_path):
52
+ with open(cache_path, "r") as f:
53
+ self._content = json.loads(f.read())
54
+ print_log(
55
+ title="History loaded",
56
+ content=f"path={cache_path}",
57
+ )
58
+ else:
59
+ print_log(
60
+ title="No History cache found",
61
+ content=f"path={cache_path}",
62
+ )
63
+
64
+ if os.path.exists(tracked_features_path):
65
+ with open(tracked_features_path, "r") as f:
66
+ self._tracked_features = json.loads(f.read())
67
+ print_log(
68
+ title="History Features loaded",
69
+ content=f"path={tracked_features_path}",
70
+ )
71
+ else:
72
+ print_log(
73
+ title="No History Features found",
74
+ content=f"path={tracked_features_path}",
75
+ )
76
+
77
+ def track_feature(self, feature: str):
78
+ if feature not in self._tracked_features:
79
+ self._tracked_features.append(feature)
80
+
81
+ def track_features(self, features: list[str]):
82
+ for feature in features:
83
+ self.track_feature(feature)
84
+
85
+ def record(
86
+ self, state_dict: Dict[str, Any]
87
+ ) -> Dict[str, Any]:
88
+ new_line = {}
89
+
90
+ for feature in self._tracked_features:
91
+ val = state_dict.get(feature, None)
92
+
93
+ if isinstance(val, torch.Tensor):
94
+ val = val.item()
95
+
96
+ if isinstance(val, np.generic):
97
+ return val.item()
98
+
99
+ new_line[feature] = val
100
+
101
+ self._content.append(new_line)
102
+ return new_line
103
+
104
+ def get_feature_columns(self, feature: str) -> list:
105
+ li = []
106
+ for row in self._content:
107
+ li.append(row.get(feature))
108
+ return li
109
+
110
+ def get_tracked_features(self) -> list:
111
+ return self._tracked_features
112
+
113
+ def return_as_df(self) -> pd.DataFrame:
114
+ df = pd.DataFrame(self._content)
115
+ return df