Skip to content

Commit

Permalink
In progress: Refactor all decay classes (arith, geom, etc.)
Browse files Browse the repository at this point in the history
- Add type hints for all methods/functions
- Refactor file and method/function docstrings
- General code style improvements
  • Loading branch information
knakamura13 committed Aug 4, 2024
1 parent 8651387 commit 3cc64b8
Show file tree
Hide file tree
Showing 17 changed files with 118 additions and 102 deletions.
2 changes: 1 addition & 1 deletion mlrose_hiive/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .algorithms.rhc import random_hill_climb
from .algorithms.gd import gradient_descent
from .algorithms.mimic import mimic
from .algorithms.decay import GeomDecay, ArithDecay, ExpDecay, CustomSchedule
from .algorithms.decay import GeomDecay, ArithmeticDecay, ExpDecay, CustomSchedule
from .algorithms.crossovers import OnePointCrossover, UniformCrossover, TSPCrossover
from .algorithms.mutators import ChangeOneMutator, DiscreteMutator, SwapMutator, ShiftOneMutator

Expand Down
2 changes: 1 addition & 1 deletion mlrose_hiive/algorithms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@
from .mimic import mimic

from .crossovers import UniformCrossover, TSPCrossover, OnePointCrossover
from .decay import ArithDecay, CustomSchedule, ExpDecay, GeomDecay
from .decay import ArithmeticDecay, CustomSchedule, ExpDecay, GeomDecay
from .mutators import ChangeOneMutator, DiscreteMutator, ShiftOneMutator, SwapMutator
6 changes: 3 additions & 3 deletions mlrose_hiive/algorithms/decay/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# Author: Genevieve Hayes
# License: BSD 3 clause

from .arith_decay import ArithDecay
from .geom_decay import GeomDecay
from .exp_decay import ExpDecay
from .arith_decay import ArithmeticDecay
from .custom_schedule import CustomSchedule
from .exp_decay import ExpDecay
from .geom_decay import GeomDecay
151 changes: 81 additions & 70 deletions mlrose_hiive/algorithms/decay/arith_decay.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,118 @@
"""Classes for defining decay schedules for simulated annealing."""


# Author: Genevieve Hayes
# Authors: Genevieve Hayes (modified by Andrew Rollings, Kyle Nakamura)
# License: BSD 3 clause


class ArithDecay:
class ArithmeticDecay:
"""
Schedule for arithmetically decaying the simulated
annealing temperature parameter T according to the formula:
Schedule for arithmetically decaying the temperature parameter T in a
simulated annealing process, calculated using the formula:
.. math::
T(t) = \\max(T_{0} - rt, T_{min})
where:
* :math:`T_{0}` is the initial temperature (at time t = 0);
* :math:`r` is the rate of arithmetic decay; and
* :math:`T_{min}` is the minimum temperature value.
where :math:`T_{0}` is the initial temperature at time `t = 0`, `r` is the decay rate, and :math:`T_{min}`
is the minimum temperature.
Parameters
----------
init_temp: float, default: 1.0
Initial value of temperature parameter T. Must be greater than 0.
decay: float, default: 0.0001
Temperature decay parameter, r. Must be greater than 0.
min_temp: float, default: 0.001
Minimum value of temperature parameter. Must be greater than 0.
Example
-------
.. highlight:: python
.. code-block:: python
>>> import mlrose_hiive
>>> schedule = mlrose_hiive.ArithDecay(init_temp=10, decay=0.95, min_temp=1)
>>> schedule.evaluate(5)
5.25
initial_temperature : float
Initial value of the temperature parameter T. Must be greater than 0.
decay_rate : float
Temperature decay parameter. Must be a positive value and less than or equal to 1.
minimum_temperature : float
Minimum allowable value of the temperature parameter. Must be positive and less than `initial_temperature`.
Attributes
----------
initial_temperature : float
Stores the initial temperature.
decay_rate : float
Stores the rate of temperature decay.
minimum_temperature : float
Stores the minimum temperature.
Examples
--------
>>> schedule = ArithmeticDecay(initial_temperature=10, decay_rate=0.95, minimum_temperature=1)
>>> schedule.evaluate(5)
5.25
"""

def __init__(self, init_temp=1.0, decay=0.0001, min_temp=0.001):
def __init__(self, initial_temperature: float = 1.0, decay_rate: float = 0.0001, minimum_temperature: float = 0.001) -> None:
self.initial_temperature: float = initial_temperature
self.decay_rate: float = decay_rate
self.minimum_temperature: float = minimum_temperature

self.init_temp = init_temp
self.decay = decay
self.min_temp = min_temp
if self.initial_temperature <= 0:
raise ValueError("Initial temperature must be greater than 0.")
if not (0 < self.decay_rate <= 1):
raise ValueError("Decay rate must be greater than 0 and less than or equal to 1.")
if not (0 < self.minimum_temperature < self.initial_temperature):
raise ValueError("Minimum temperature must be greater than 0 and less than initial temperature.")

if self.init_temp <= 0:
raise Exception("""init_temp must be greater than 0.""")
def __str__(self) -> str:
return (f'ArithmeticDecay(initial_temperature={self.initial_temperature}, '
f'decay_rate={self.decay_rate}, '
f'minimum_temperature={self.minimum_temperature})')

if (self.decay <= 0) or (self.decay > 1):
raise Exception("""decay must be greater than 0.""")
def __repr__(self) -> str:
return self.__str__()

if self.min_temp < 0:
raise Exception("""min_temp must be greater than 0.""")
elif self.min_temp > self.init_temp:
raise Exception("""init_temp must be greater than min_temp.""")
def __eq__(self, other: object) -> bool:
if not isinstance(other, ArithmeticDecay):
return False
return (self.initial_temperature == other.initial_temperature and
self.decay_rate == other.decay_rate and
self.minimum_temperature == other.minimum_temperature)

def evaluate(self, t):
"""Evaluate the temperature parameter at time t.
def evaluate(self, time: int) -> float:
"""
Calculate and return the temperature parameter at the given time.
Parameters
----------
t: int
Time at which the temperature paramter T is evaluated.
time : int
The time at which to evaluate the temperature parameter.
Returns
-------
temp: float
Temperature parameter at time t.
float
The temperature parameter at the given time, respecting the minimum temperature.
"""
temperature = max(self.initial_temperature - (self.decay_rate * time), self.minimum_temperature)
return temperature

temp = self.init_temp - (self.decay * t)
def get_info(self, time: int = None, prefix: str = '') -> dict:
"""
Generate a dictionary containing the decay schedule's settings and optionally its current value.
if temp < self.min_temp:
temp = self.min_temp
Parameters
----------
time : int, optional
The time at which to evaluate the current temperature value.
If provided, the current value is included in the returned dictionary.
prefix : str, optional
A prefix to prepend to each key in the dictionary, useful for integrating this info into larger structured data.
return temp
Returns
-------
dict
A dictionary with keys reflecting the decay schedule's parameters and optionally the current temperature.
"""
info_prefix = f'{prefix}schedule_' if prefix else 'schedule_'

def get_info__(self, t=None, prefix=''):
prefix = f'_{prefix}__schedule_' if len(prefix) > 0 else 'schedule_'
info = {
f'{prefix}type': 'arithmetic',
f'{prefix}init_temp': self.init_temp,
f'{prefix}decay': self.decay,
f'{prefix}min_temp': self.min_temp,
f'{info_prefix}type': 'arithmetic',
f'{info_prefix}initial_temperature': self.initial_temperature,
f'{info_prefix}decay_rate': self.decay_rate,
f'{info_prefix}minimum_temperature': self.minimum_temperature,
}
if t is not None:
info[f'{prefix}current_value'] = self.evaluate(t)
return info

def __str__(self):
return str(self.init_temp)
if time is not None:
info[f'{info_prefix}current_value'] = self.evaluate(time)

def __repr__(self):
return f'{self.__class__.__name__}(init_temp={self.init_temp}, ' \
f'decay={self.decay}, min_temp={self.min_temp})'

def __eq__(self, other):
try:
return (self.__class__.__name__ == other.__class__.__name__
and self.init_temp == other.init_temp
and self.decay == other.decay
and self.min_temp == other.min_temp)
except AttributeError:
return False
return info
16 changes: 8 additions & 8 deletions mlrose_hiive/algorithms/ga.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _get_hamming_distance_float(population: np.ndarray, p1: np.ndarray) -> np.nd


def _genetic_alg_select_parents(pop_size: int, problem: Any,
get_hamming_distance_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None,
get_hamming_distance_func: Callable[[np.ndarray, np.ndarray], np.ndarray],
hamming_factor: float = 0.0) -> tuple[np.ndarray, np.ndarray]:
"""
Select parents for the next generation in the genetic algorithm.
Expand All @@ -58,7 +58,7 @@ def _genetic_alg_select_parents(pop_size: int, problem: Any,
Size of the population.
problem : optimization object
The optimization problem instance.
get_hamming_distance_func : Callable[[np.ndarray, np.ndarray], np.ndarray] | None
get_hamming_distance_func : Callable[[np.ndarray, np.ndarray], np.ndarray]
Function to calculate Hamming distance.
hamming_factor : float, default: 0.0
Factor to account for Hamming distance in parent selection.
Expand Down Expand Up @@ -107,7 +107,7 @@ def genetic_alg(problem: Any,
state_fitness_callback: Callable[..., Any] = None,
callback_user_info: Any = None,
hamming_factor: float = 0.0,
hamming_decay_factor: float = None) -> tuple[np.ndarray, float, np.ndarray | None]:
hamming_decay_factor: float = None) -> tuple[np.ndarray, float, np.ndarray]:
"""
Use a standard genetic algorithm to find the optimum for a given optimization problem.
Expand Down Expand Up @@ -137,17 +137,17 @@ def genetic_alg(problem: Any,
Boolean to keep fitness values for a curve.
If :code:`False`, then no curve is stored.
If :code:`True`, then a history of fitness values is provided as a third return value.
random_state : int | None, default: None
random_state : int, default: None
If random_state is a positive integer, random_state is the seed used by np.random.seed(); otherwise, the random seed is not set.
state_fitness_callback : Callable[..., Any] | None, default: None
state_fitness_callback : Callable[..., Any], default: None
If specified, this callback will be invoked once per iteration.
Parameters are (iteration, max attempts reached?, current best state, current best fit, user callback data).
Return true to continue iterating, or false to stop.
callback_user_info : Any, default: None
User data passed as last parameter of callback.
hamming_factor : float, default: 0.0
Factor to account for Hamming distance in parent selection.
hamming_decay_factor : float | None, default: None
hamming_decay_factor : float, default: None
Decay factor for the hamming_factor over iterations.
Returns
Expand All @@ -156,7 +156,7 @@ def genetic_alg(problem: Any,
Numpy array containing state that optimizes the fitness function.
best_fitness : float
Value of fitness function at best state.
fitness_curve : np.ndarray | None
fitness_curve : np.ndarray
Numpy array of arrays containing the fitness of the entire population at every iteration.
Only returned if input argument :code:`curve` is :code:`True`.
Expand Down Expand Up @@ -194,7 +194,7 @@ def genetic_alg(problem: Any,
fitness_evaluations=problem.fitness_evaluations,
user_data=callback_user_info)

get_hamming_distance_func: Callable[[np.ndarray, np.ndarray], np.ndarray] | None = None
get_hamming_distance_func: Callable[[np.ndarray, np.ndarray], np.ndarray] = None
if hamming_factor > 0:
g1 = problem.get_population()[0][0]
if isinstance(g1, float) or g1.dtype == 'float64':
Expand Down
2 changes: 1 addition & 1 deletion mlrose_hiive/algorithms/gd.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def gradient_descent(problem: Any,
curve: bool = False,
random_state: int = None,
state_fitness_callback: Callable = None,
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray | None]:
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray]:
"""Use gradient_descent to find the optimal neural network weights.
Parameters
Expand Down
16 changes: 8 additions & 8 deletions mlrose_hiive/algorithms/hc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
def hill_climb(problem: Any,
max_iters: int = np.inf,
restarts: int = 0,
init_state: np.ndarray | None = None,
init_state: np.ndarray = None,
curve: bool = False,
random_state: int | None = None,
random_state: int = None,
state_fitness_callback: Callable = None,
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray | None]:
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray]:
"""Use standard hill climbing to find the optimum for a given optimization problem.
Parameters
Expand All @@ -28,22 +28,22 @@ def hill_climb(problem: Any,
Maximum number of iterations of the algorithm for each restart.
restarts: int, default: 0
Number of random restarts.
init_state: np.ndarray | None, default: None
init_state: np.ndarray, default: None
1-D Numpy array containing starting state for algorithm.
If None, then a random state is used.
curve: bool, default: False
Boolean to keep fitness values for a curve.
If False, then no curve is stored.
If True, then a history of fitness values is provided as a
third return value.
random_state: int | None, default: None
random_state: int, default: None
If random_state is a positive integer, random_state is the seed used
by np.random.seed(); otherwise, the random seed is not set.
state_fitness_callback: callable | None, default: None
state_fitness_callback: callable, default: None
If specified, this callback will be invoked once per iteration.
Parameters are (iteration, current best state, current best fit, user callback data).
Return true to continue iterating, or false to stop.
callback_user_info: any | None, default: None
callback_user_info: any, default: None
User data passed as last parameter of callback.
Returns
Expand All @@ -52,7 +52,7 @@ def hill_climb(problem: Any,
Numpy array containing state that optimizes the fitness function.
best_fitness: float
Value of fitness function at best state.
fitness_curve: np.ndarray | None
fitness_curve: np.ndarray
Numpy array containing the fitness at every iteration.
Only returned if input argument curve is True.
Expand Down
4 changes: 2 additions & 2 deletions mlrose_hiive/algorithms/mimic.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ def mimic(problem: Any,
noise: float = 0.0,
max_iters: int = np.inf,
curve: bool = False,
random_state: int | None = None,
random_state: int = None,
state_fitness_callback: Callable = None,
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray | None]:
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray]:
"""Use MIMIC to find the optimum for a given optimization problem.
Note
Expand Down
2 changes: 1 addition & 1 deletion mlrose_hiive/algorithms/rhc.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def random_hill_climb(problem: Any,
curve: bool = False,
random_state: int = None,
state_fitness_callback: Callable = None,
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray | None]:
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray]:
"""Use randomized hill climbing to find the optimum for a given optimization problem.
Parameters
Expand Down
6 changes: 3 additions & 3 deletions mlrose_hiive/algorithms/sa.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ def simulated_annealing(problem: Any,
schedule: Any = GeomDecay(),
max_attempts: int = 10,
max_iters: int = np.inf,
init_state: np.ndarray | None = None,
init_state: np.ndarray = None,
curve: bool = False,
random_state: int | None = None,
random_state: int = None,
state_fitness_callback: Callable = None,
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray | None]:
callback_user_info: Any = None) -> tuple[np.ndarray, float, np.ndarray]:
"""Use simulated annealing to find the optimum for a given optimization problem.
Parameters
Expand Down
1 change: 1 addition & 0 deletions mlrose_hiive/fitness/continuous_peaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class ContinuousPeaks:
as:
.. math::
Fitness(x, T) = \\max(max\\_run(0, x), max\\_run(1, x)) + R(x, T)
where:
Expand Down
1 change: 1 addition & 0 deletions mlrose_hiive/fitness/four_peaks.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class FourPeaks(DiscretePeaksBase):
fitness of an n-dimensional state vector :math:`x`, given parameter T, as:
.. math::
Fitness(x, T) = \\max(tail(0, x), head(1, x)) + R(x, T)
where:
Expand Down
Loading

0 comments on commit 3cc64b8

Please sign in to comment.