libinephany 0.16.3__py3-none-any.whl → 0.16.4__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.
@@ -64,20 +64,8 @@ def get_exponential_weighted_average(values: list[int | float]) -> float:
64
64
  :param values: List of values to average via EWA.
65
65
  :return: EWA of the given values.
66
66
  """
67
-
68
- # Check for NaN and infinite values in input
69
- valid_values = [float(val) for val in values if not math.isnan(float(val)) and not math.isinf(float(val))]
70
-
71
- if not valid_values:
72
- raise ValueError("Cannot compute exponential weighted average on empty list")
73
-
74
- if len(valid_values) == 1:
75
- return valid_values[0]
76
-
77
- exp_weighted_average = pd.Series(valid_values).ewm(alpha=0.1).mean().iloc[-1]
67
+ exp_weighted_average = pd.Series(values).ewm(alpha=0.1).mean().iloc[-1]
78
68
  assert isinstance(exp_weighted_average, float)
79
- assert not math.isnan(exp_weighted_average)
80
- assert not math.isinf(exp_weighted_average)
81
69
  return exp_weighted_average
82
70
 
83
71
 
@@ -395,7 +383,7 @@ def _weighted_interval_expectation(
395
383
  interval_gradient = (end_value - start_value) / (end_time_point - start_time_point)
396
384
  start_exp_time = math.exp(log_decay_factor * start_time_point)
397
385
  end_exp_time = math.exp(log_decay_factor * end_time_point)
398
- return (1 / log_decay_factor) * (end_value * end_exp_time - start_value * start_exp_time) + (
386
+ return (1 / log_decay_factor) * (end_value * end_exp_time - start_value * start_exp_time) - (
399
387
  1 / log_decay_factor**2
400
388
  ) * interval_gradient * (end_exp_time - start_exp_time)
401
389
 
@@ -0,0 +1,58 @@
1
+ # ======================================================================================================================
2
+ #
3
+ # GLOBAL OBSERVERS PACKAGE
4
+ #
5
+ # This package contains all global observer classes used for collecting observations
6
+ # across the entire training process, not specific to individual agents.
7
+ #
8
+ # ======================================================================================================================
9
+
10
+
11
+ from .gradient_observers import GlobalFirstOrderGradients, GlobalSecondOrderGradients
12
+ from .hyperparameter_observers import InitialHyperparameters, ModelFamilyOneHot, OptimizerTypeOneHot
13
+ from .loss_observers import (
14
+ LHOPTLossRatio,
15
+ LHOPTTrainingLoss,
16
+ LHOPTValidationLoss,
17
+ LossRatio,
18
+ PercentileOfLossAtEachCheckpoint,
19
+ TrainingLoss,
20
+ TrainingScore,
21
+ ValidationLoss,
22
+ ValidationScore,
23
+ )
24
+ from .model_observers import (
25
+ GlobalActivations,
26
+ GlobalLAMBTrustRatio,
27
+ GlobalParameters,
28
+ GlobalParameterUpdates,
29
+ NumberOfLayers,
30
+ NumberOfParameters,
31
+ )
32
+ from .progress_observers import EpochsCompleted, ProgressAtEachCheckpoint, TrainingProgress
33
+
34
+ __all__ = [
35
+ InitialHyperparameters.__name__,
36
+ OptimizerTypeOneHot.__name__,
37
+ ModelFamilyOneHot.__name__,
38
+ TrainingLoss.__name__,
39
+ ValidationLoss.__name__,
40
+ LossRatio.__name__,
41
+ TrainingScore.__name__,
42
+ ValidationScore.__name__,
43
+ GlobalFirstOrderGradients.__name__,
44
+ GlobalSecondOrderGradients.__name__,
45
+ GlobalActivations.__name__,
46
+ GlobalParameterUpdates.__name__,
47
+ GlobalParameters.__name__,
48
+ GlobalLAMBTrustRatio.__name__,
49
+ NumberOfParameters.__name__,
50
+ NumberOfLayers.__name__,
51
+ TrainingProgress.__name__,
52
+ EpochsCompleted.__name__,
53
+ ProgressAtEachCheckpoint.__name__,
54
+ LHOPTTrainingLoss.__name__,
55
+ LHOPTValidationLoss.__name__,
56
+ LHOPTLossRatio.__name__,
57
+ PercentileOfLossAtEachCheckpoint.__name__,
58
+ ]
@@ -0,0 +1,183 @@
1
+ # ======================================================================================================================
2
+ #
3
+ # BASE CLASSES
4
+ #
5
+ # ======================================================================================================================
6
+
7
+ from abc import ABC, abstractmethod
8
+ from typing import Any
9
+
10
+ from libinephany.observations.observation_utils import StatisticStorageTypes, compute_cdf_feature
11
+ from libinephany.observations.observers.base_observers import GlobalObserver
12
+ from libinephany.observations.observers.global_observers.constants import LHOPT_CONSTANTS
13
+ from libinephany.pydantic_models.schemas.observation_models import ObservationInputs
14
+ from libinephany.pydantic_models.schemas.tensor_statistics import TensorStatistics
15
+ from libinephany.pydantic_models.states.hyperparameter_states import HyperparameterStates
16
+
17
+
18
+ class LHOPTOuterStepBaseObserver(GlobalObserver, ABC):
19
+ """
20
+ Base class for LHOPT outer step observers to eliminate duplicate code.
21
+ """
22
+
23
+ def __init__(
24
+ self,
25
+ decay_factor: float = LHOPT_CONSTANTS["DEFAULT_DECAY_FACTOR"],
26
+ time_window: int = LHOPT_CONSTANTS["DEFAULT_TIME_WINDOW"],
27
+ **kwargs,
28
+ ) -> None:
29
+ """
30
+ :param decay_factor: Decay factor for CDF calculation in [1, 2.5, 5, 10, 20]
31
+ :param time_window: Number of time steps to consider for CDF calculation
32
+ :param kwargs: Other observation keyword arguments.
33
+ """
34
+ super().__init__(**kwargs)
35
+ self.decay_factor = max(0.0, decay_factor)
36
+ self.time_window = max(1, time_window)
37
+
38
+ # Store time series data for CDF calculation
39
+ self._time_series: list[tuple[float, float]] = [] # (time, value) pairs
40
+ self._current_time: float = 0.0
41
+
42
+ @property
43
+ def can_standardize(self) -> bool:
44
+ """
45
+ This observer has its own CDF calculation, no need to standardize.
46
+ :return: Whether the observation can be standardized.
47
+ """
48
+ return False
49
+
50
+ def _get_observation_format(self) -> StatisticStorageTypes:
51
+ """
52
+ :return: Format the observation returns data in. Must be one of the StatisticStorageTypes
53
+ enumeration class.
54
+ """
55
+ return StatisticStorageTypes.VECTOR
56
+
57
+ def _compute_cdf_feature(self, value: float) -> float:
58
+ """
59
+ Compute CDF feature for the given value.
60
+ training loss will be added to the time series after this call.
61
+ :param value: The value to compute CDF feature for
62
+ :return: CDF feature value
63
+ """
64
+ return compute_cdf_feature(value, self._time_series, self.decay_factor, self._current_time, self.time_window)
65
+
66
+ def _update_time(self) -> None:
67
+ """Update the current time counter."""
68
+ self._current_time += 1.0
69
+
70
+ def get_required_trackers(self) -> dict[str, dict[str, Any] | None]:
71
+ """
72
+ :return: Dictionary mapping statistic tracker class names to kwargs for the class or None if no kwargs are
73
+ needed.
74
+ """
75
+ return {}
76
+
77
+ def reset(self) -> None:
78
+ """Reset the observer by clearing the time series."""
79
+ self._time_series = []
80
+ self._current_time = 0.0
81
+
82
+ @abstractmethod
83
+ def _observe(
84
+ self,
85
+ observation_inputs: ObservationInputs,
86
+ hyperparameter_states: HyperparameterStates,
87
+ tracked_statistics: dict[str, dict[str, float | TensorStatistics]],
88
+ action_taken: float | int | None,
89
+ ) -> float | int | list[int | float] | TensorStatistics:
90
+ """
91
+ :param observation_inputs: Observation input metrics not calculated with statistic trackers.
92
+ :param hyperparameter_states: HyperparameterStates that manages the hyperparameters.
93
+ :param tracked_statistics: Dictionary mapping statistic tracker class names to dictionaries mapping module
94
+ :param action_taken: Action taken by the agent this class instance is assigned to.
95
+ """
96
+ raise NotImplementedError
97
+
98
+
99
+ class LHOPTCheckpointBaseObserver(GlobalObserver, ABC):
100
+ """
101
+ Base class for checkpoint-based observers to eliminate duplicate code.
102
+ """
103
+
104
+ def __init__(self, checkpoint_interval: int = LHOPT_CONSTANTS["DEFAULT_CHECKPOINT_INTERVAL"], **kwargs) -> None:
105
+ """
106
+ :param checkpoint_interval: How often to create checkpoints (in outer model steps).
107
+ :param kwargs: Miscellaneous keyword arguments.
108
+ """
109
+ super().__init__(**kwargs)
110
+ self.checkpoint_interval = checkpoint_interval
111
+ self._history: list[float] = []
112
+ self.last_value: float | None = None
113
+
114
+ @property
115
+ def can_standardize(self) -> bool:
116
+ """
117
+ This observer has its own CDF calculation, no need to standardize.
118
+ :return: Whether the observation can be standardized.
119
+ """
120
+ return False
121
+
122
+ def _get_observation_format(self) -> StatisticStorageTypes:
123
+ """
124
+ :return: Format the observation returns data in.
125
+ """
126
+ return StatisticStorageTypes.FLOAT
127
+
128
+ def _update_history(self, value: float) -> None:
129
+ """
130
+ Update the history with a new value and maintain sliding window.
131
+
132
+ :param value: The new value to add to history
133
+ """
134
+ self._history.append(value)
135
+
136
+ # Keep only the last checkpoint_interval values for sliding window
137
+ if len(self._history) > self.checkpoint_interval:
138
+ self._history = self._history[-self.checkpoint_interval :]
139
+
140
+ def _should_create_checkpoint(self) -> bool:
141
+ """
142
+ Check if we should create a checkpoint.
143
+
144
+ :return: True if checkpoint should be created, False otherwise
145
+ """
146
+ return len(self._history) >= self.checkpoint_interval
147
+
148
+ def _cold_start(self, value: float) -> None:
149
+ """
150
+ Handle cold start by setting the last value if not already set.
151
+
152
+ :param value: The value to set as last value if cold start
153
+ """
154
+ if self.last_value is None:
155
+ self.last_value = value
156
+
157
+ def get_required_trackers(self) -> dict[str, dict[str, Any] | None]:
158
+ """
159
+ :return: Dictionary mapping statistic tracker class names to kwargs for the class or None if no kwargs are
160
+ needed.
161
+ """
162
+ return {}
163
+
164
+ def reset(self) -> None:
165
+ """Reset the observer by clearing history."""
166
+ self._history = []
167
+ self.last_value = None
168
+
169
+ @abstractmethod
170
+ def _observe(
171
+ self,
172
+ observation_inputs: ObservationInputs,
173
+ hyperparameter_states: HyperparameterStates,
174
+ tracked_statistics: dict[str, dict[str, float | TensorStatistics]],
175
+ action_taken: float | int | None,
176
+ ) -> float | int | list[int | float] | TensorStatistics:
177
+ """
178
+ :param observation_inputs: Observation input metrics not calculated with statistic trackers.
179
+ :param hyperparameter_states: HyperparameterStates that manages the hyperparameters.
180
+ :param tracked_statistics: Dictionary mapping statistic tracker class names to dictionaries mapping module
181
+ :param action_taken: Action taken by the agent this class instance is assigned to.
182
+ """
183
+ raise NotImplementedError
@@ -0,0 +1,33 @@
1
+ # ======================================================================================================================
2
+ #
3
+ # CONSTANTS
4
+ #
5
+ # ======================================================================================================================
6
+
7
+ from typing import TypedDict
8
+
9
+
10
+ class LHOPTConstants(TypedDict):
11
+ IS_NAN: float
12
+ NOT_NAN: float
13
+ IS_INF: float
14
+ NOT_INF: float
15
+ TANH_BOUND: float
16
+ DEFAULT_DECAY_FACTOR: float
17
+ DEFAULT_TIME_WINDOW: int
18
+ DEFAULT_CHECKPOINT_INTERVAL: int
19
+ DEFAULT_PERCENTILE: float
20
+
21
+
22
+ # Create the constants instance
23
+ LHOPT_CONSTANTS: LHOPTConstants = LHOPTConstants(
24
+ IS_NAN=1.0,
25
+ NOT_NAN=0.0,
26
+ IS_INF=1.0,
27
+ NOT_INF=0.0,
28
+ TANH_BOUND=10.0,
29
+ DEFAULT_DECAY_FACTOR=1.25,
30
+ DEFAULT_TIME_WINDOW=32,
31
+ DEFAULT_CHECKPOINT_INTERVAL=100,
32
+ DEFAULT_PERCENTILE=0.6,
33
+ )
@@ -0,0 +1,112 @@
1
+ # ======================================================================================================================
2
+ #
3
+ # GRADIENT OBSERVERS
4
+ #
5
+ # ======================================================================================================================
6
+
7
+ from typing import Any
8
+
9
+ from libinephany.observations import observation_utils, statistic_trackers
10
+ from libinephany.observations.observation_utils import StatisticStorageTypes
11
+ from libinephany.observations.observers.base_observers import GlobalObserver
12
+ from libinephany.pydantic_models.schemas.observation_models import ObservationInputs
13
+ from libinephany.pydantic_models.schemas.tensor_statistics import TensorStatistics
14
+ from libinephany.pydantic_models.states.hyperparameter_states import HyperparameterStates
15
+
16
+
17
+ class GlobalFirstOrderGradients(GlobalObserver):
18
+
19
+ def _get_observation_format(self) -> StatisticStorageTypes:
20
+ """
21
+ :return: Format the observation returns data in. Must be one of the enum attributes in the StatisticStorageTypes
22
+ enumeration class.
23
+ """
24
+
25
+ return StatisticStorageTypes.TENSOR_STATISTICS
26
+
27
+ def _observe(
28
+ self,
29
+ observation_inputs: ObservationInputs,
30
+ hyperparameter_states: HyperparameterStates,
31
+ tracked_statistics: dict[str, dict[str, float | TensorStatistics]],
32
+ action_taken: float | int | None,
33
+ ) -> float | int | list[int | float] | TensorStatistics:
34
+ """
35
+ :param observation_inputs: Observation input metrics not calculated with statistic trackers.
36
+ :param hyperparameter_states: HyperparameterStates that manages the hyperparameters.
37
+ :param tracked_statistics: Dictionary mapping statistic tracker class names to dictionaries mapping module
38
+ names to floats or TensorStatistic models.
39
+ :param action_taken: Action taken by the agent this class instance is assigned to.
40
+ :return: Single float/int, list of floats/ints or TensorStatistics model to add to the observation vector.
41
+ """
42
+
43
+ statistics = tracked_statistics[statistic_trackers.FirstOrderGradients.__name__]
44
+
45
+ return observation_utils.average_tensor_statistics(tensor_statistics=list(statistics.values())) # type: ignore
46
+
47
+ def get_required_trackers(self) -> dict[str, dict[str, Any] | None]:
48
+ """
49
+ :return: Dictionary mapping statistic tracker class names to kwargs for the class or None if no kwargs are
50
+ needed.
51
+ """
52
+
53
+ return {statistic_trackers.FirstOrderGradients.__name__: dict(skip_statistics=self.skip_statistics)}
54
+
55
+
56
+ class GlobalSecondOrderGradients(GlobalObserver):
57
+
58
+ def __init__(
59
+ self,
60
+ *,
61
+ compute_hessian_diagonal: bool = False,
62
+ **kwargs,
63
+ ) -> None:
64
+ """
65
+ :param compute_hessian_diagonal: Whether to compute the Hessian diagonal to determine second order gradients
66
+ or use the squared first order gradients as approximations in the same way Adam does.
67
+ :param kwargs: Miscellaneous keyword arguments.
68
+ """
69
+
70
+ super().__init__(**kwargs)
71
+
72
+ self.compute_hessian_diagonal = compute_hessian_diagonal
73
+
74
+ def _get_observation_format(self) -> StatisticStorageTypes:
75
+ """
76
+ :return: Format the observation returns data in. Must be one of the enum attributes in the StatisticStorageTypes
77
+ enumeration class.
78
+ """
79
+
80
+ return StatisticStorageTypes.TENSOR_STATISTICS
81
+
82
+ def _observe(
83
+ self,
84
+ observation_inputs: ObservationInputs,
85
+ hyperparameter_states: HyperparameterStates,
86
+ tracked_statistics: dict[str, dict[str, float | TensorStatistics]],
87
+ action_taken: float | int | None,
88
+ ) -> float | int | list[int | float] | TensorStatistics:
89
+ """
90
+ :param observation_inputs: Observation input metrics not calculated with statistic trackers.
91
+ :param hyperparameter_states: HyperparameterStates that manages the hyperparameters.
92
+ :param tracked_statistics: Dictionary mapping statistic tracker class names to dictionaries mapping module
93
+ names to floats or TensorStatistic models.
94
+ :param action_taken: Action taken by the agent this class instance is assigned to.
95
+ :return: Single float/int, list of floats/ints or TensorStatistics model to add to the observation vector.
96
+ """
97
+
98
+ statistics = tracked_statistics[statistic_trackers.SecondOrderGradients.__name__]
99
+
100
+ return observation_utils.average_tensor_statistics(tensor_statistics=list(statistics.values())) # type: ignore
101
+
102
+ def get_required_trackers(self) -> dict[str, dict[str, Any] | None]:
103
+ """
104
+ :return: Dictionary mapping statistic tracker class names to kwargs for the class or None if no kwargs are
105
+ needed.
106
+ """
107
+
108
+ return {
109
+ statistic_trackers.SecondOrderGradients.__name__: dict(
110
+ skip_statistics=self.skip_statistics, compute_hessian_diagonal=self.compute_hessian_diagonal
111
+ )
112
+ }