11import numpy as np
22import pickle
3- from .layer import Layer
4- from .activations import Sigmoid
3+ from .layer import Layer , Dropout
4+ from .activations import Sigmoid
55from .losses import MSE
6+ from .optimizers import SGD
67
78class NeuralNetwork :
8- def __init__ (self , loss_function = MSE ()):
9+ def __init__ (self , loss_function = MSE (), optimizer = None ):
910 self .layers = []
10- self .loss_list = []
1111 self .loss_function = loss_function
12+ # Por defecto usamos SGD si no se pasa uno
13+ self .optimizer = optimizer if optimizer else SGD (learning_rate = 0.01 )
1214
1315 def add_layer (self , num_neurons , input_size = None , activation = None ):
1416 if activation is None :
@@ -19,59 +21,65 @@ def add_layer(self, num_neurons, input_size=None, activation=None):
1921 raise ValueError ("Debes definir input_size para la primera capa." )
2022 self .layers .append (Layer (num_neurons , input_size , activation ))
2123 else :
22- previous_output_size = len (self .layers [- 1 ].neurons )
23- self .layers .append (Layer (num_neurons , previous_output_size , activation ))
24-
25- def forward (self , inputs ):
24+ # Busca la última capa que tenga neuronas (ignora Dropout para contar outputs)
25+ last_layer_size = 0
26+ for layer in reversed (self .layers ):
27+ if hasattr (layer , 'n_neurons' ) and layer .n_neurons > 0 :
28+ last_layer_size = layer .n_neurons
29+ break
30+ self .layers .append (Layer (num_neurons , last_layer_size , activation ))
31+
32+ def add_dropout (self , rate ):
33+ self .layers .append (Dropout (rate ))
34+
35+ def forward (self , inputs , training = True ):
2636 for layer in self .layers :
27- inputs = layer .forward (inputs )
37+ inputs = layer .forward (inputs , training = training )
2838 return inputs
2939
30- def backward (self , loss_gradient , learning_rate ):
40+ def backward (self , loss_gradient ):
41+ # Propagar el error hacia atrás
3142 for layer in reversed (self .layers ):
32- loss_gradient = layer .backward (loss_gradient , learning_rate )
43+ loss_gradient = layer .backward (loss_gradient )
3344
34- def train (self , x , y , epochs = 1000 , learning_rate = 0.1 ):
35- indices = np .arange (len (x ))
36-
45+ def train (self , x , y , epochs = 1000 , batch_size = 32 ):
3746 for epoch in range (epochs ):
38- epoch_loss = 0
47+ # 1. Shuffle (Barajado)
48+ permutation = np .random .permutation (x .shape [0 ])
49+ x_shuffled = x [permutation ]
50+ y_shuffled = y [permutation ]
3951
40- # Barajado de datos (Shuffle)
41- np .random .shuffle (indices )
52+ epoch_loss = 0
4253
43- for i in indices :
44- # 1. Forward
45- output = self .forward (x [i ])
54+ # 2. Mini-Batch Gradient Descent
55+ for i in range (0 , len (x ), batch_size ):
56+ # Crear batch
57+ x_batch = x_shuffled [i :i + batch_size ]
58+ y_batch = y_shuffled [i :i + batch_size ]
4659
47- # 2. Calcular Loss
48- epoch_loss + = self .loss_function . calculate ( output , y [ i ] )
60+ # Forward
61+ output = self .forward ( x_batch , training = True )
4962
50- # 3. Calcular gradiente
51- loss_gradient = self .loss_function .derivative (output , y [i ])
63+ # Calcular Loss y Gradiente de Loss
64+ loss = self .loss_function .calculate (output , y_batch )
65+ epoch_loss += loss
66+ grad = self .loss_function .derivative (output , y_batch )
5267
53- # 4. Backward
54- self .backward (loss_gradient , learning_rate )
55-
56- # --- ESTAS LÍNEAS FALTABAN ---
57- # Calcular promedio del error en este epoch
58- epoch_loss /= len (x )
59- self .loss_list .append (epoch_loss )
68+ # Backward (Calcula gradientes dW, db)
69+ self .backward (grad )
70+
71+ # Optimizer Step (Actualiza pesos)
72+ self .optimizer .update (self .layers )
6073
61- # Imprimir progreso cada 100 épocas
6274 if epoch % 100 == 0 :
63- print (f"Epoch: { epoch } , Loss: { epoch_loss :.6f} " )
75+ print (f"Epoch: { epoch } , Loss promedio : { epoch_loss / ( len ( x ) / batch_size ) :.6f} " )
6476
6577 def predict (self , x ):
66- predictions = []
67- for i in range (len (x )):
68- predictions .append (self .forward (x [i ]))
69- return np .array (predictions )
78+ return self .forward (x , training = False )
7079
7180 def save_model (self , filename ):
7281 with open (filename , 'wb' ) as file :
7382 pickle .dump (self , file )
74- print (f"Modelo guardado en { filename } " )
7583
7684 @staticmethod
7785 def load_model (filename ):
0 commit comments