22
33import json
44import random
5- from typing import Callable , List , Tuple
5+ import sys
6+ from pathlib import Path
7+ from typing import Callable , List , Self , Tuple
68
79import numpy as np
810
1315class Network :
1416 def __init__ (
1517 self ,
18+ weights ,
19+ biases ,
1620 layer_sizes : List [int ],
1721 hidden_activation : str = "sigmoid" ,
1822 output_activation : str = "sigmoid" ,
1923 loss : str = "mse" ,
2024 optimizer : Optimizer | None = None ,
2125 class_weights : List [float ] | None = None ,
22- seed : int | None = None ,
2326 ):
2427 if len (layer_sizes ) < 2 :
2528 raise ValueError ("Need at least input and output layer" )
2629 if any (s <= 0 for s in layer_sizes ):
2730 raise ValueError ("All layer sizes must be > 0" )
2831
32+ self .biases = biases
33+ self .weights = weights
34+
2935 self .layer_sizes = layer_sizes
3036 self .hidden_activation = hidden_activation
3137 self .output_activation = output_activation
@@ -38,26 +44,35 @@ def __init__(
3844 if loss == "ce" and output_activation not in ("softmax" ,):
3945 raise ValueError ("Cross-entropy requires softmax output" )
4046
47+ @classmethod
48+ def random_net (
49+ cls , layer_sizes : List [int ], seed : int | None = None , ** kwargs
50+ ) -> Self :
4151 if seed is not None :
4252 random .seed (seed )
4353
44- self . weights : List [List [List [float ]]] = []
45- self . biases : List [List [float ]] = []
54+ weights : List [List [List [float ]]] = []
55+ biases : List [List [float ]] = []
4656
4757 for i in range (len (layer_sizes ) - 1 ):
4858 in_size = layer_sizes [i ]
4959 out_size = layer_sizes [i + 1 ]
5060
5161 scale = (2.0 / in_size ) ** 0.5
52- self . weights .append (
62+ weights .append (
5363 [
5464 [random .gauss (0 , scale ) for _ in range (in_size )]
5565 for _ in range (out_size )
5666 ]
5767 )
58- self .biases .append (
59- [random .uniform (- 0.1 , 0.1 ) for _ in range (out_size )]
60- )
68+ biases .append ([random .uniform (- 0.1 , 0.1 ) for _ in range (out_size )])
69+
70+ return cls (
71+ layer_sizes = layer_sizes ,
72+ weights = weights ,
73+ biases = biases ,
74+ ** kwargs ,
75+ )
6176
6277 def _compute_loss (self , output : List [float ], target : List [float ]) -> float :
6378 if self .loss_fn == "mse" :
@@ -336,43 +351,6 @@ def train_epoch(
336351 accuracy = total_correct / m if m else 0.0
337352 return avg_loss , accuracy
338353
339- def train (
340- self ,
341- dataset : List [Tuple [List [float ], List [float ]]],
342- epochs : int = 1000 ,
343- target_accuracy : float = 1.0 ,
344- batch_size : int = 32 ,
345- validation_data : List [Tuple [List [float ], List [float ]]] | None = None ,
346- verbose : bool = True ,
347- ) -> List [Tuple [int , float , float , float ]]:
348- history : List [Tuple [int , float , float , float ]] = []
349-
350- for epoch in range (1 , epochs + 1 ):
351- loss , acc = self .train_epoch (dataset , batch_size = batch_size )
352-
353- val_acc = 0.0
354- if validation_data :
355- val_acc = self .evaluate (validation_data )
356-
357- history .append ((epoch , loss , acc , val_acc ))
358-
359- if verbose :
360- val_str = (
361- f" val_acc={ val_acc * 100 :.1f} %" if validation_data else ""
362- )
363- print (
364- f"Epoch { epoch } : loss={ loss :.4f} train_acc={ acc * 100 :.1f} %{ val_str } "
365- )
366-
367- if acc >= target_accuracy :
368- if verbose :
369- print (
370- f"Reached target accuracy { target_accuracy * 100 :.1f} % at epoch { epoch } "
371- )
372- break
373-
374- return history
375-
376354 def to_dict (self ) -> dict :
377355 return {
378356 "version" : "1.0" ,
@@ -410,7 +388,9 @@ def from_dict(cls, data: dict):
410388 learning_rate = opt_data .get ("learning_rate" , 0.3 )
411389 )
412390
413- net = cls (
391+ return cls (
392+ weights = params ["weights" ],
393+ biases = params ["biases" ],
414394 layer_sizes = arch ["layer_sizes" ],
415395 hidden_activation = arch .get ("hidden_activation" , "sigmoid" ),
416396 output_activation = arch .get ("output_activation" , "sigmoid" ),
@@ -419,13 +399,27 @@ def from_dict(cls, data: dict):
419399 class_weights = hyper .get ("class_weights" ),
420400 )
421401
422- net .weights = params ["weights" ]
423- net .biases = params ["biases" ]
402+ @classmethod
403+ def load (cls , filepath : Path ) -> Self | None :
404+ try :
405+ print (f"Loading network from { filepath } ..." , file = sys .stderr )
406+ raw_config = filepath .read_text ()
407+
408+ except FileNotFoundError :
409+ print (
410+ f"Error: Network file '{ filepath } ' not found" , file = sys .stderr
411+ )
412+ return None
413+ except Exception as e :
414+ print (f"Error loading network: { e } " , file = sys .stderr )
415+ return None
424416
425- return net
417+ data = json .loads (raw_config )
418+ self = cls .from_dict (data )
426419
427- @classmethod
428- def load (cls , filepath : str ):
429- with open (filepath , "r" , encoding = "utf-8" ) as f :
430- data = json .load (f )
431- return cls .from_dict (data )
420+ if self is None :
421+ print ("Failed to load the network" , file = sys .stderr )
422+ return None
423+
424+ print (f"Loaded network: { self .layer_sizes } " , file = sys .stderr )
425+ return self
0 commit comments