Source code for HeteroSymNN.Core.Nets.neural_nets

from __future__ import annotations
from typing import Optional,Literal,Union,Any
import warnings
import numpy as np

from ...Backend import hardware as HW
from ...types import NodeConfig,LayerValues,LayerConstructionConfig,FlexibleNodeConfig,BackendArray,ConstantToUpdate
from .layers import Layer
from .. import losses as lossC, optimizers as OptiC, initializers as InitC

[docs] class ConfigurableNN: """ Base class for creating a dense neural network with customizable architecture, activation functions, and training parameters. Parameters ---------- nodes_structure : list[int] List with the number of nodes per layer including input and output layers. detailed_activations : list[list[:obj:`~HeteroSymNN.types.NodeConfig`]] List of lists containing the activation configuration for each node in each layer. initial_values : Optional[list[:obj:`~HeteroSymNN.types.LayerValues`]], optional Optional list of initial values for each layer. If not provided, weights and biases will be initialized using the specified initializer., by default None initializer : Optional[:obj:`~HeteroSymNN.Core.Nets.initializers.Initializer`], optional Initializer to use for weights and biases if initial_values is not provided. Most pass an instance of :obj:`~HeteroSymNN.Core.Nets.initializers.Initializer` and the default uses :obj:`~HeteroSymNN.Core.Nets.initializers.HeNormal`, value by default is None. learning_rate : float, optional Learning rate for the network. In the case that a custom optimizer is provided with its own learning rate this value will be overwritten., by default 0.001 batch_size : int, optional Batch size to use during training, by default 32 if training_mode is "mini-batch", 1 if "stochastic" and size of the dataset if "batch". training_mode : Literal["batch", "mini-batch", "stochastic"], optional Training mode to use during training. Options are "batch", "mini-batch", and "stochastic". By default "mini-batch". learning_mode : str, optional Learning mode of the network. Currently only "Static" is supported., by default "Static" loss_function : :obj:`~HeteroSymNN.Core.Nets.losses.Loss`, optional Loss function to use during training. Must be an instance of :obj:`~HeteroSymNN.Core.Nets.losses.Loss`. If not provided, ::obj:`~HeteroSymNN.Core.Nets.losses.MSELoss` will be used., value by default is None. optimizer : Optional[:obj:`~HeteroSymNN.Core.Nets.optimizers.Optimizer`], optional Optimizer to use for updating the network parameters. Must be an instance of :obj:`~HeteroSymNN.Core.Nets.optimizers.Optimizer`. If not provided, :obj:`~HeteroSymNN.Core.Nets.optimizers.AdamOptimizer` will be used.,value by default is None. num_treaning_iter : int, optional Number of Epochs to use during training, by default 1000 Attributes ---------- num_treaning_iterations : int, read-write Number of training iterations (epochs) for the network. learning_mode : str, read-write Learning mode of the network. Currently only "Static" is supported. training_mode : Literal["batch", "mini-batch", "stochastic"], read-write Training mode to use during training. When seting it to "mini-batch" from "stochastic" or "batch" the batch size that will be used is the one stored in the attribute batch_size. batch_size : int, read-write Batch size to use during training. histogram_losses : list[float], read-only List of loss values recorded at each epoch during training. num_complited_train_iterations : int, read-only Number of completed training steps. num_completed_epochs : int, read-only Number of completed training epochs. Examples -------- >>> from HeteroSymNN.Core.Nets.neural_nets import ConfigurableNN >>> CNN = ConfigurableNN( ... nodes_structure=[3, 5, 2], ... detailed_activations=[ ... [("relu", {}), ("relu", {}), ("relu", {}), ("relu", {}), ("relu", {})], ... [("sigmoid", {}), ("sigmoid", {})] ... ], ... learning_rate=0.01, ... batch_size=16, ... training_mode="mini-batch" ... ) >>> from HeteroSymNN.Core.Nets import losses as lossC, optimizers as OptiC, initializers as InitC >>> custom_loss = lossC.CrossEntropyLoss() >>> custom_optimizer = OptiC.SGDOptimizer(learning_rate=0.01) >>> custom_initializer = InitC.XavierUniform() >>> CNN = ConfigurableNN( ... nodes_structure=[4, 6, 3], ... detailed_activations=[ ... [("tanh", {}), ("tanh", {}), ("tanh", {}), ("tanh", {}), ("tanh", {}), ("tanh", {})], ... [("softmax", {}), ("softmax", {}), ("softmax", {})] ... ], ... initializer=custom_initializer, ... loss_function=custom_loss, ... optimizer=custom_optimizer, ... training_mode="batch" ... ) """ def __init__(self, nodes_structure: list[int], detailed_activations: list[list[NodeConfig]],initial_values: Optional[list[LayerValues]] = None,initializer: Optional[InitC.Initializer] = None, learning_rate: float = 0.001, batch_size: int = 32, training_mode: Literal["batch", "mini-batch", "stochastic"] = "mini-batch", learning_mode: str = "Static", loss_function: Optional[lossC.Loss] = None, optimizer: Optional[OptiC.Optimizer] = None, num_treaning_iter: int = 1000): self._CALCULATION_MANAGER = HW.be self._ASNUMPY = HW.asnumpy self.num_complited_train_iterations = 0 self.num_completed_epochs = 0 if len(nodes_structure) < 2: raise ValueError("nodes_structure debe tener al menos 2 elementos (entrada y salida).") num_layers = len(nodes_structure) - 1 if (len(detailed_activations) != num_layers): raise ValueError(f"La estructura de nodos define {num_layers} capas (conexiones), pero 'detailed_activations' tiene {len(detailed_activations)} elementos.") if ((initial_values is not None) and (len(initial_values) != num_layers)): raise ValueError(f"Se proporcionaron valores iniciales, pero su longitud ({len(initial_values)}) no coincide con el número de capas ({num_layers}).") self._LEARNING_RATE = learning_rate self._LEAR_MODE = learning_mode self.training_mode =training_mode self._BATCH_SIZE = batch_size self._GPU_ID = 0 self._DEFAULT_FLOAT_TYPE = self._CALCULATION_MANAGER.float32 self._CURRENT_DEVICE = "CPU" self._COMPUTATIONAL_METHOD = HW.DEFAULT_COMPUTE_METHOD self.num_treaning_iterations = num_treaning_iter self._NODE_STRUCTURE = nodes_structure if (initializer is None): self._INITIALIZER = InitC.HeNormal() else: self._INITIALIZER = initializer if (loss_function == None): self._LOSS_FUNCTION:lossC.Loss = lossC.MSELoss(self._COMPUTATIONAL_METHOD,self._GPU_ID) else: self._LOSS_FUNCTION = loss_function temp_result = self._LOSS_FUNCTION._change_COMPUTATIONAL_METHOD(self._COMPUTATIONAL_METHOD,self._GPU_ID) if (temp_result != self._COMPUTATIONAL_METHOD): raise RuntimeError(f"Funcion de perdia no pudo sincronizar su metodo al de la red.Metodo de la red:{self._COMPUTATIONAL_METHOD}. Metodo de la funcion de perdida: {temp_result}") if (optimizer != None): self._UPDATE_METHOD = optimizer if (self._UPDATE_METHOD.learning_rate != None): self._UPDATE_METHOD.learning_rate = self._LEARNING_RATE else: self._LEARNING_RATE = self._UPDATE_METHOD.learning_rate else: self._UPDATE_METHOD:OptiC.Optimizer = OptiC.AdamOptimizer(self._LEARNING_RATE,self._COMPUTATIONAL_METHOD.split("_")[0],device_id=self._GPU_ID) if self.training_mode == "stochastic": self._BATCH_SIZE = 1 self._LAYERS: list[Layer] = [] for i in range(num_layers): num_inputs = nodes_structure[i] num_nodes = nodes_structure[i+1] node_configs = detailed_activations[i] if (len(node_configs) != num_nodes): raise ValueError(f"Error en Capa {i+1}: Se definieron {num_nodes} nodos en 'nodes_structure', pero hay {len(node_configs)} configuraciones de activación.") # Determinar valores iniciales (Pasados o Generados) if (initial_values is not None): current_vals = initial_values[i] else: current_vals = self._INITIALIZER.generate(num_inputs, num_nodes) # Crear la capa layer_config: LayerConstructionConfig = (node_configs, current_vals) self._LAYERS.append(Layer(num_inputs, layer_config, self._BATCH_SIZE, self._GPU_ID)) self.histogram_losses = [] @property def LAYERS(self)->list[Layer]: """ Property to get the layers of the network. Read-only. Returns ------- list[:obj:`~HeteroSymNN.Core.Nets.layers.Layer`] """ return self._LAYERS @property def NODE_STRUCTURE(self)->list[int]: """ Property to get the number of nodes per layer of the network. Read-only. Returns ------- list[int] """ return self._NODE_STRUCTURE @property def GPU_ID(self)->int: """ Property to get the current GPU ID being used by the network. Read-only. For setting a new GPU ID, use the :obj:`~set_gpu_id` method. Returns ------- int """ return self._GPU_ID @property def BATCH_SIZE(self)->int: """ Property to get the current batch size being used by the network in case of mini-batch training. Read-only. Returns ------- int """ return self._BATCH_SIZE @property def CURRENT_DEVICE(self)->Literal["CPU","GPU"]: """ Property to get the current device where the network parameters are located. Read-only. For changing location of the network parameters use the :obj:`~change_device` method. Returns ------- Literal["CPU","GPU"] """ return self._CURRENT_DEVICE @property def COMPUTATIONAL_METHOD(self)->Literal["GPU_CUDA","CPU_JIT","CPU_PYTHON"]: """ Property to get the current computational method being used by the network. Read-only. To change the computational method use the :obj:`~_change_COMPUTATIONAL_METHOD` method. Returns ------- Literal["GPU_CUDA","CPU_JIT","CPU_PYTHON"] """ return self._COMPUTATIONAL_METHOD @property def OPTIMIZER(self)->OptiC.Optimizer: """ Property to get the current optimizer being used by the network. Read-only. Returns ------- :obj:`~HeteroSymNN.Core.Nets.optimizers.Optimizer` """ return self._UPDATE_METHOD @property def LOSS_FUNCTION(self)->lossC.Loss: """ Property to get the current loss function being used by the network. Read-only. Returns ------- :obj:`~HeteroSymNN.Core.Nets.losses.Loss` """ return self._LOSS_FUNCTION @property def INICIALIZER(self)->InitC.Initializer: """ Property to get the current initializer being used by the network. Read-only. Returns ------- :obj:`~HeteroSymNN.Core.Nets.initializers.Initializer` """ return self._INITIALIZER @property def learning_rate(self)->float: """ Property to get the current learning rate of the network. Read-write. Returns ------- float """ return self._LEARNING_RATE @learning_rate.setter def learning_rate(self,new_learning_rate:float): self._LEARNING_RATE = new_learning_rate self._UPDATE_METHOD.learning_rate = new_learning_rate
[docs] def set_gpu_id(self,new_id:int)->None: """ Method to set a new GPU ID for the network. This will change the device of the network parameters if currently on GPU. Parameters ---------- new_id : int New GPU ID to set. Raises ------ ValueError If the Id for the new GPU is greater than the number of available GPUs. """ if (self._GPU_ID != new_id): if (new_id >= HW.NUM_GPUS): raise ValueError(f"ID de GPU {new_id} no es válido. GPUs disponibles: {HW.NUM_GPUS}") self.change_device("CPU") self._GPU_ID = new_id self._LOSS_FUNCTION.set_gpu_id(self._GPU_ID) self._UPDATE_METHOD.set_gpu_id(self._GPU_ID) for layer in self._LAYERS: layer.set_gpu_id(new_id)
def _change_COMPUTATIONAL_METHOD(self,new_method:Literal["GPU_CUDA","CPU_JIT","CPU_PYTHON"],gpu_id:int = None)->None: """ Change the computational method used by the network. This will also change the device of the network parameters if needed. Parameters ---------- new_method : Literal["GPU_CUDA","CPU_JIT","CPU_PYTHON"] New computational method to set. gpu_id : int, optional GPU ID to use if the new method is "GPU_CUDA". If not provided, the current GPU ID of the network will be used., by default None Raises ------ ValueError If the new method is not one of "GPU_CUDA", "CPU_JIT", or "CPU_PYTHON". RuntimeError If trying to set "GPU_CUDA" without a valid GPU or "CPU_JIT" without a valid C++ compiler when strict warnings mode is enabled. If not enabled, it will fallback to the next available method and throw a warning. RuntimeError In the case the loss function or any of the layers could not change to the new computational method will show a runtime error. .. Warning:: When changing computational methods it will force a kernel recompilation for all layers and reallocation of all parameters. Depending on the size of the network this could take a while. """ if not(new_method in ["GPU_CUDA","CPU_JIT","CPU_PYTHON"]): raise ValueError("Se intento cambiar a un metodo computacional que no es GPU_CUDA, CPU_JIT o CPU_PYTHON") if (gpu_id == None): gpu_id = self._GPU_ID if ((new_method == "GPU_CUDA") and not(HW.GPU_ENABLED)): if (HW.WARNINGS_STRICT_MODE): raise RuntimeError("Se intento cambiar al metodo de GPU_CUDA cuando no se tiene una gpu valida.") else: warnings.warn("Se intento cambiar al metodo de GPU_CUDA cuando no se tiene una gpu valida."+"Intantando con el metodo CPU_JIT") new_method = "CPU_JIT" if ((new_method == "CPU_JIT")and not(HW.CPP_JIT_ENABLED)): if (HW.WARNINGS_STRICT_MODE): raise RuntimeError("Se intento cambiar al metodo de CPU_JIT cuando no se tiene un compilador de c++ valido.") else: warnings.warn("Se intento cambiar al metodo de GPU_CUDA cuando no se tiene un compilador de c++ valido."+"Cambiando al metodo CPU_PYTHON") new_method = "CPU_PYTHON" if(new_method != self._COMPUTATIONAL_METHOD): self._to("CPU") self._COMPUTATIONAL_METHOD = new_method self._GPU_ID = gpu_id if ("CPU" in new_method): self._CALCULATION_MANAGER = np self._ASNUMPY = np.array elif ("GPU" in new_method): self._CALCULATION_MANAGER = HW.cp self._ASNUMPY = HW.cp.array temp_result = self._LOSS_FUNCTION._change_COMPUTATIONAL_METHOD(self._COMPUTATIONAL_METHOD,self._GPU_ID) if (temp_result != self._COMPUTATIONAL_METHOD): raise RuntimeError(f"Funcion de perdia no pudo sincronizar su metodo al de la red.Metodo de la red:{self._COMPUTATIONAL_METHOD}. Metodo de la funcion de perdida: {temp_result}") self._UPDATE_METHOD._change_COMPUTATIONAL_DEVICE(self._COMPUTATIONAL_METHOD.split("_")[0]) laye_calc_method = [] for layer in self._LAYERS: laye_calc_method.append(layer._change_COMPUTATIONAL_METHOD(new_method,gpu_id)) expected = [new_method]*len(self._LAYERS) if (laye_calc_method != expected): raise RuntimeError(f"Cambio de metodo no se pudo realizar. Uno o mas capas no estan en el nuevo metodo. Configuracion nueva de las capas: {laye_calc_method}")
[docs] def change_device(self, device:Literal["CPU","GPU"])->None: """ Change location of the network parameters to the specified device. Parameters ---------- device : Literal["CPU","GPU"] Device to move the network parameters to. """ if not(device in ["CPU","GPU"]): raise ValueError("Se paso como device algo que no es GPU o CPU.") if ((device == "GPU") and not(HW.GPU_ENABLED)): if (HW.WARNINGS_STRICT_MODE): raise RuntimeError("Se intento cambiar al dispositivo GPU cuando no se tiene una gpu valida.") else: warnings.warn("Se intento cambiar al dispositivo GPU cuando no se tiene una gpu valida."+"Cambiando a CPU") device = "CPU" if ((device == "GPU")and("CPU" in self._COMPUTATIONAL_METHOD)): if (HW.WARNINGS_STRICT_MODE): raise RuntimeError("Se intento cambiar al dispositivo GPU cuando se tiene definido la CPU como dispositivo computacional") else: warnings.warn("Se intento cambiar al dispositivo GPU cuando se tiene definido la CPU como dispositivo computacional."+"Ignorando peticion por seguridad.") device = "CPU" if(device != self._CURRENT_DEVICE): self._CURRENT_DEVICE = device self._to(device)
def _to(self, device:Literal["CPU","GPU"]): """ Internal method to move the network parameters to the specified device. """ self._UPDATE_METHOD._to_device(device) for layer in self._LAYERS: layer._to(device) def _forward(self,input_values:BackendArray)->BackendArray: """ Internal method to perform a forward pass through the network. Parameters ---------- input_values : :obj:`~HeteroSymNN.types.BackendArray` Input values for the network. Returns ------- :obj:`~HeteroSymNN.types.BackendArray` Output values from the network. """ current_a = input_values for layer in self._LAYERS: current_a = layer.forward(current_a) return current_a
[docs] def backward(self,error_values:BackendArray)->BackendArray: """ Internal method to perform a backward pass through the network. Parameters ---------- error_values : :obj:`~HeteroSymNN.types.BackendArray` Error values to propagate back through the network. Returns ------- :obj:`~HeteroSymNN.types.BackendArray` Error values propagated back to the input layer. """ self.change_device(self._COMPUTATIONAL_METHOD.split("_")[0]) next_layer_error_sum =self._CALCULATION_MANAGER.array(error_values, dtype=self._CALCULATION_MANAGER.float32) for layer in reversed(self._LAYERS): next_layer_error_sum = layer.backward(next_layer_error_sum)
[docs] def train_step(self, x_input: BackendArray, y_target: BackendArray)->float: """ Perform a single training step (forward pass, loss computation, backward pass, and parameter update). Parameters ---------- x_input : :obj:`~HeteroSymNN.types.BackendArray` Input values to train the network on. y_target : :obj:`~HeteroSymNN.types.BackendArray` Target output values for the network. Returns ------- float Computed loss for the training step. """ self.change_device(self._COMPUTATIONAL_METHOD.split("_")[0]) self.num_complited_train_iterations += 1 y_pred = self._forward(x_input) loss = self._LOSS_FUNCTION.forward(y_pred, y_target) error_to_propagate = self._LOSS_FUNCTION.backward(y_pred, y_target) self.backward(error_to_propagate) self.update_params(x_input) return loss
[docs] def update_params(self, inputs:BackendArray)->None: """ Update the network parameters using the optimizer. Parameters ---------- inputs : :obj:`~HeteroSymNN.types.BackendArray` Input values used for the parameter update. """ self.change_device(self._COMPUTATIONAL_METHOD.split("_")[0]) self._UPDATE_METHOD.step(self._LAYERS,inputs)
[docs] def train(self,training_inputs: list[list[float]], training_targets: list[list[float]],num_iterations = None, training_mode: Literal["batch", "mini-batch", "stochastic"] = None, batch_size: int = None)->list[float]: """ Train the neural network using the provided training data. Parameters ---------- training_inputs : list[list[float]] List of input samples for training. Shape should be (num_samples, num_features). training_targets : list[list[float]] List of target output samples for training. Shape should be (num_samples, num_outputs). num_iterations : int, optional Number of training iterations (epochs) to perform. If not provided, uses the value of the attribute :obj:`~num_treaning_iterations`., by default None training_mode : Literal["batch", "mini-batch", "stochastic"], optional Training mode to use during training. Options are "batch", "mini-batch", and "stochastic". If not provided, uses the current value of the attribute :obj:`~training_mode`., by default None batch_size : int, optional Batch size to use during training if training_mode is "mini-batch". If not provided, uses the current value of the attribute :obj:`~batch_size`., by default None Returns ------- list[float] List of loss values recorded at each epoch during training. """ self.change_device(self._COMPUTATIONAL_METHOD.split("_")[0]) train_data = self._CALCULATION_MANAGER.array(training_inputs,dtype=np.float32).T train_targets = self._CALCULATION_MANAGER.array(training_targets,dtype=np.float32).T mode = self.training_mode if training_mode is None else training_mode b_size = self._BATCH_SIZE if batch_size is None else batch_size if (num_iterations == None): num_iterations = self.num_treaning_iterations self.training_mode = mode if (b_size != self._BATCH_SIZE): self._BATCH_SIZE = b_size for layer in self._LAYERS: layer.batch_size_change(b_size) if mode == "stochastic": b_size = 1 elif mode == "batch": b_size = len(training_inputs) num_samples = train_data.shape[1] for _ in range(num_iterations): self.num_completed_epochs += 1 iter_loss = self._CALCULATION_MANAGER.array(0.0, dtype=self._DEFAULT_FLOAT_TYPE) indices = self._CALCULATION_MANAGER.random.permutation(num_samples) for start_idx in range(0, num_samples, b_size): end_idx = min(start_idx + b_size, num_samples) batch_indices = indices[start_idx:end_idx] x = train_data[:, batch_indices] y = train_targets[:, batch_indices] loss = self.train_step(x, y) iter_loss += loss * (end_idx - start_idx) avg_loss = self._ASNUMPY(iter_loss) / num_samples self.histogram_losses.append(avg_loss) return self.histogram_losses
[docs] def predict(self,input_values:Union[list,list[list]],to_cpu:bool = True)->Union[np.ndarray,BackendArray]: """ Make predictions using the neural network. Parameters ---------- input_values : list or list[list] Input values for making predictions. In case of multiple samples, shape should be (num_samples, num_features) or (num_features, num_samples). to_cpu : bool, optional Whether to return the predictions as a NumPy array on the CPU. If False, returns in the current backend array format., by default True Returns ------- np.ndarray or :obj:`~HeteroSymNN.types.BackendArray` Predicted output values. """ self.change_device(self._COMPUTATIONAL_METHOD.split("_")[0]) if not isinstance(input_values, (np.ndarray, self._CALCULATION_MANAGER.ndarray)): current_a = self._CALCULATION_MANAGER.array(input_values, dtype=self._DEFAULT_FLOAT_TYPE) else: current_a = self._CALCULATION_MANAGER.asarray(input_values, dtype=self._DEFAULT_FLOAT_TYPE) if current_a.ndim == 1: current_a = current_a.reshape(-1, 1) if current_a.shape[0] == self._LAYERS[0].num_inputs: pass elif current_a.shape[1] == self._LAYERS[0].num_inputs: current_a = current_a.T else: raise ValueError( f"La forma de los datos de entrada {current_a.shape} es incorrecta. " f"La Capa 0 esperaba {self._LAYERS[0].num_inputs} características (features), " f"pero ninguna dimensión ({current_a.shape[0]} o {current_a.shape[1]}) coincidió." ) prediction = self._forward(current_a) if(to_cpu): return self._ASNUMPY(prediction).T return prediction
[docs] def get_parameters(self)->dict[Union[str,int],dict[str,np.ndarray]]: """ Get the parameters (weights and biases) of the network. Returns ------- dict[Union[str,int],dict[str,np.ndarray]] Dictionary containing the parameters of each layer. """ self.change_device("CPU") return {f'layer_{i}': layer.get_parameters() for i, layer in enumerate(self._LAYERS)}
[docs] def set_parameters(self, params:dict[Union[str,int],dict[str,np.ndarray]])->None: """ Set the parameters (weights and biases) of the network. Parameters ---------- params : dict[Union[str,int],dict[str,np.ndarray]] Dictionary containing the parameters for each layer. """ self.change_device("CPU") for key in params: layer_params = {} layer_params.update({"weights":params[key]["weights"].copy().T}) layer_params.update({"biases":params[key]["biases"].copy().reshape(-1,1)}) if (type(key) != int): index = int(key.split("_")[-1]) self._LAYERS[index].set_parameters(layer_params)
[docs] def change_constants(self,new_constants:dict[int,Union[list[ConstantToUpdate],ConstantToUpdate]])->None: """ Change the constants in the activation functions of the network layers. Parameters ---------- new_constants : dict[int,Union[list[:obj:`~HeteroSymNN.types.ConstantToUpdate`], :obj:`~HeteroSymNN.types.ConstantToUpdate`]] Dictionary mapping layer indices to new constant values for the activation functions.""" for num_layer in new_constants.keys(): self._LAYERS[num_layer].change_constant(new_constants[num_layer])
[docs] def get_config(self)->dict[str,Any]: """ Get the configuration of the neural network. Returns ------- dict[str,Any] Dictionary containing the configuration of the network. Parameters include: * **"nodes_structure"** (*list[int]*): List of number of nodes per layer. * **"detailed_activations"** (*list[list[NodeConfig]*): Activation configuration for each node. * **"learning_rate"** (*float*): Learning rate of the network. * **"learning_mode"** (*str*): Learning mode of the network. * **"training_mode"** (*str*): Training mode ("batch", "mini-batch", "stochastic"). * **"batch_size"** (*int*): Batch size used during training. * **"initializer_config"** (*dict[str, Any]*): Configuration of the initializer. * **"optimizer_config"** (*dict[str, Any]*): Configuration of the optimizer. * **"loss_config"** (*dict[str, Any]*): Configuration of the loss function. * **"num_treaning_iterations"** (*int*): Number of training iterations (epochs). """ self.change_device("CPU") node_configs = [] for layer in self._LAYERS: node_configs.append(layer.recunstruct_layer_config()) config = { 'nodes_structure': self._NODE_STRUCTURE, 'detailed_activations': node_configs, 'learning_rate': self._LEARNING_RATE, 'learning_mode': self._LEAR_MODE, 'training_mode': self.training_mode, 'batch_size': self._BATCH_SIZE } config.update({'initializer_config': self._INITIALIZER.get_config()}) config['optimizer_config'] = self._UPDATE_METHOD.get_config() config["loss_config"] = self._LOSS_FUNCTION.get_config() config['num_treaning_iterations'] = self.num_treaning_iterations return config
[docs] class FlexibleNN(ConfigurableNN): """ Intermediate class for creating dense neural networks with flexible activation configurations. Child class of :obj:`~HeteroSymNN.Core.Nets.neural_nets.ConfigurableNN`. Parameters ---------- nodes_structure : list[int] List containing the number of nodes in each layer including input and output layers. activation_config : list[:obj:`~HeteroSymNN.types.FlexibleNodeConfig`] List containing the activation configuration for each layer. Each element can be a string (activation name) or a tuple (activation name, parameters dictionary). initial_values : Optional[list[:obj:`~HeteroSymNN.types.LayerValues`]], optional List of initial values for weights and biases for each layer. If not provided, weights and biases will be initialized using the specified initializer., by default None initializer : Optional[:obj:`~HeteroSymNN.Core.Nets.initializers.Initializer`], optional Initializer to use for initializing weights and biases. If not provided, :obj:`~HeteroSymNN.Core.Nets.initializers.HeNormal` will be used., by default None learning_rate : float, optional Learning rate for the network., by default 0.001 learning_mode : str, optional Learning mode of the network. Currently only "Static" is supported., by default "Static" training_mode : Literal["batch", "mini-batch", "stochastic"], optional Training mode to use during training. In case of "batch" or "stochastic" the batch size attribute will be ignored., by default "stochastic" batch_size : int, optional Batch size to use during training. In the case of using "stochastic" or "batch" training mode this attribute will be ignored and in training time the batch size will be set to 1 or to the full dataset size respectively., by default 32 loss_function : :obj:`~HeteroSymNN.Core.Nets.losses.Loss`, optional Loss function to use for training. If not provided, :obj:`~HeteroSymNN.Core.Nets.losses.MSELoss` will be used., by default None optimizer : :obj:`~HeteroSymNN.Core.Nets.optimizers.Optimizer`, optional Optimizer to use for training. If not provided, :obj:`~HeteroSymNN.Core.Nets.optimizers.AdamOptimizer` will be used., by default None num_treaning_iter : int, optional Number of training iterations (epochs)., by default 1000 Attributes ---------- num_treaning_iterations : int, read-write Number of training iterations (epochs) for the network. learning_mode : str, read-write Learning mode of the network. Currently only "Static" is supported. training_mode : Literal["batch", "mini-batch", "stochastic"], read-write Training mode to use during training. When seting it to "mini-batch" from "stochastic" or "batch" the batch size that will be used is the one stored in the attribute batch_size. batch_size : int, read-write Batch size to use during training. histogram_losses : list[float], read-only List of loss values recorded at each epoch during training. num_complited_train_iterations : int, read-only Number of completed training steps. num_completed_epochs : int, read-only Number of completed training epochs. Examples -------- >>> from HeteroSymNN.Core.Nets.neural_nets import FlexibleNN >>> FNN = FlexibleNN( ... nodes_structure=[3, 5, 2], ... activation_config=[ ... "relu", ... ("sigmoid", {}) ... ], ... learning_rate=0.01, ... batch_size=16, ... training_mode="mini-batch" ... ) """ def __init__(self, nodes_structure: list[int], activation_config: list[FlexibleNodeConfig],initial_values: Optional[list[LayerValues]] = None,initializer: Optional[InitC.Initializer] = None, learning_rate: float = 0.001, learning_mode: str = "Static", training_mode: Literal["batch", "mini-batch", "stochastic"] = "stochastic", batch_size: int = 32, loss_function: Optional[lossC.Loss] = None, optimizer: Optional[OptiC.Optimizer] = None, num_treaning_iter: int = 1000): num_layers = len(nodes_structure) - 1 if not isinstance(activation_config, list): raise ValueError(f"activation_config debe ser una lista con un elemento por capa. Se recibió: {type(activation_config)}") if len(activation_config) != num_layers: raise ValueError(f"La lista de activaciones tiene {len(activation_config)} elementos, pero hay {num_layers} capas en nodes_structure.") detailed_activations = self._expand_to_detailed(num_layers, nodes_structure[1:], activation_config) super().__init__( nodes_structure=nodes_structure, detailed_activations=detailed_activations, initial_values=initial_values, initializer=initializer, learning_rate=learning_rate, learning_mode=learning_mode, training_mode=training_mode, batch_size=batch_size, loss_function=loss_function, optimizer=optimizer, num_treaning_iter=num_treaning_iter ) def _process_node_config(self, config_item: FlexibleNodeConfig) -> NodeConfig: """ Internal method to process a flexible node configuration into a strict node configuration. Parameters ---------- config_item : :obj:`~HeteroSymNN.types.FlexibleNodeConfig` Flexible node configuration (string or tuple). Returns ------- :obj:`~HeteroSymNN.types.NodeConfig` Strict node configuration (tuple).""" if (isinstance(config_item, str)): return (config_item, {}) elif ((isinstance(config_item, tuple)) and (len(config_item) == 2)): return config_item else: raise ValueError( f"Formato inválido para la activación: {config_item}.\n"+ f"Se esperaba 'str' o 'tuple[str, dict[str, float]]'.\n"+ "Ejemplos: 'relu', ('mish', {'beta': 1.0})" ) def _expand_to_detailed(self, num_layers: int, nodes_per_layer: list[int], layer_configs: list[FlexibleNodeConfig]) -> list[list[NodeConfig]]: """ Internal method to expand flexible layer configurations into detailed node configurations for each layer. Child class of :obj:`~HeteroSymNN.Core.Nets.neural_nets.FlexibleNN`. Parameters ---------- num_layers : int Number of layers in the network. nodes_per_layer : list[int] List containing the number of nodes in each layer. layer_configs : list[:obj:`~HeteroSymNN.types.FlexibleNodeConfig`] List containing the flexible activation configuration for each layer. Returns ------- list[list[:obj:`~HeteroSymNN.types.NodeConfig`]] Detailed activation configuration for each node in each layer. """ final_config = [] for i in range(num_layers): layer_conf_raw = layer_configs[i] num_nodes = nodes_per_layer[i] node_conf = self._process_node_config(layer_conf_raw) final_config.append([node_conf] * num_nodes) return final_config
[docs] class SimpleNN(FlexibleNN): """ High-level class for creating dense neural networks with uniform activation functions across hidden layers. Parameters ---------- nodes_structure : list[int] List containing the number of nodes in each layer including input and output layers. activation : :obj:`~HeteroSymNN.types.FlexibleNodeConfig`, optional Activation function configuration for hidden layers. Can be a string (activation name) or a tuple (activation name, parameters dictionary)., by default "relu output_activation : :obj:`~HeteroSymNN.types.FlexibleNodeConfig`, optional Activation function configuration for the output layer. Can be a string (activation name) or a tuple (activation name, parameters dictionary)., by default "num initializer : Optional[:obj:`~HeteroSymNN.Core.Nets.initializers.Initializer`], optional Initializer to use for initializing weights and biases. If not provided, :obj:`~HeteroSymNN.Core.Nets.initializers.HeNormal` will be used., by default None learning_rate : float, optional Learning rate for the network., by default 0.001 learning_mode : str, optional Learning mode of the network. Currently only "Static" is supported., by default "Static" training_mode : Literal["batch", "mini-batch", "stochastic"], optional Training mode to use during training. In case of "batch" or "stochastic" the batch size attribute will be ignored., by default "stochastic" batch_size : int, optional Batch size to use during training. In the case of using "stochastic" or "batch" training mode this attribute will be ignored and in training time the batch size will be set to 1 or to the full dataset size respectively., by default 32 loss_function : :obj:`~HeteroSymNN.Core.Nets.losses.Loss`, optional Loss function to use for training. If not provided, :obj:`~HeteroSymNN.Core.Nets.losses.MSELoss` will be used., by default None optimizer : :obj:`~HeteroSymNN.Core.Nets.optimizers.Optimizer`, optional Optimizer to use for training. If not provided, :obj:`~HeteroSymNN.Core.Nets.optimizers.AdamOptimizer` will be used., by default None num_treaning_iter : int, optional Number of training iterations (epochs)., by default 1000 Attributes ---------- num_treaning_iterations : int, read-write Number of training iterations (epochs) for the network. learning_mode : str, read-write Learning mode of the network. Currently only "Static" is supported. training_mode : Literal["batch", "mini-batch", "stochastic"], read-write Training mode to use during training. When seting it to "mini-batch" from "stochastic" or "batch" the batch size that will be used is the one stored in the attribute batch_size. batch_size : int, read-write Batch size to use during training. histogram_losses : list[float], read-only List of loss values recorded at each epoch during training. num_complited_train_iterations : int, read-only Number of completed training steps. num_completed_epochs : int, read-only Number of completed training epochs. Examples -------- >>> from HeteroSymNN.Core.Nets.neural_nets import SimpleNN >>> SNN = SimpleNN( ... nodes_structure=[4, 8, 3], ... activation="tanh", ... output_activation="softmax", ... learning_rate=0.01, ... batch_size=64, ... training_mode="mini-batch" ... ) """ def __init__(self, nodes_structure: list[int], activation: FlexibleNodeConfig = "relu", output_activation: FlexibleNodeConfig = "num",initializer: Optional[InitC.Initializer] = None, learning_rate: float = 0.001, learning_mode: str = "Static",training_mode: Literal["batch", "mini-batch", "stochastic"] = "stochastic", batch_size: int = 32, loss_function: Optional[lossC.Loss] = None, optimizer: Optional[OptiC.Optimizer] = None, num_treaning_iter: int = 1000): if len(nodes_structure) < 2: raise ValueError("nodes_structure debe tener al menos 2 elementos (entrada y salida)") num_hidden_layers = len(nodes_structure) - 2 activations_list = [activation] * num_hidden_layers activations_list.append(output_activation) super().__init__( nodes_structure=nodes_structure, activation_config=activations_list, initializer=initializer, learning_rate=learning_rate, learning_mode=learning_mode, training_mode=training_mode, batch_size=batch_size, loss_function=loss_function, optimizer=optimizer, num_treaning_iter=num_treaning_iter )