@@ -101,6 +101,7 @@ def __init__(self):
101101 def forward (self , x ):
102102 x = torch .sin (x )
103103 return x
104+
104105class NN (torch .nn .Module ):
105106 def __init__ (self ,
106107 Num_Hidden_Layers : int = 3 ,
@@ -232,63 +233,105 @@ def forward(self, X: torch.Tensor) -> torch.Tensor:
232233 # Pass through the last layer (with no activation function) and return.
233234 return self .Layers [self .Num_Hidden_Layers ](X );
234235
235-
236236class ANNSmoother (AbstractSmoother ):
237237 def __init__ (self ):
238- pass
238+ super ().__init__ () # Optional depending on AbstractSmoother
239+ self .model = None
240+
241+ def __call__ (self , data , grid , epochs_max = 1000 , loss_mean = 1000 , loss_threshold = 1e-8 ,
242+ batch_frac = 0.5 , val_frac = 0.1 , learning_rate = 1e-3 , return_ann = False , device = 'cpu' ):
243+ if torch .cuda .is_available ():
244+ device = "cuda"
245+ # Convert to int if passed as float
246+ epochs_max = int (epochs_max )
239247
240- def __call__ (self , data , grid , epochs_max = 1e3 , loss_mean = 1000 , batch_frac = 0.5 ,
241- learining_rate = 1e-4 , return_ann : bool = False , device = 'cpu' ):
248+ # Infer input dimension
242249 dim = 1 if np .any ([s == 1 for s in data .shape ]) and data .ndim == 2 else data .ndim
243- model = baseline_ann (dim )
244- # model = NN(Num_Hidden_Layers=5, Neurons_Per_Layer=50, Input_Dim=dim, Activation_Function='Tanh')
245- grid_flattened = torch .from_numpy (np .array ([subgrid .reshape (- 1 ) for subgrid in grid ])).float ().T
246250
251+ # Initialize model
252+ # model = baseline_ann(dim).to(device)
253+ model = NN (Num_Hidden_Layers = 5 , Neurons_Per_Layer = 50 , Input_Dim = dim , Activation_Function = 'Sin' ).to (device )
254+ self .model = model
255+
256+ # Flatten grid and reshape field
257+ grid_flattened = torch .from_numpy (np .array ([subgrid .reshape (- 1 ) for subgrid in grid ])).float ().T .to (device )
258+ field_ = torch .from_numpy (data .reshape (- 1 , 1 )).float ().to (device )
247259 original_shape = data .shape
248260
249- field_ = torch .from_numpy (data .reshape (- 1 , 1 )).float ()
261+ # Train/val split
262+ N = grid_flattened .size (0 )
263+ val_size = int (N * val_frac )
264+ train_size = N - val_size
265+ indices = torch .randperm (N )
266+ train_idx , val_idx = indices [:train_size ], indices [train_size :]
267+
268+ train_x , train_y = grid_flattened [train_idx ], field_ [train_idx ]
269+ val_x , val_y = grid_flattened [val_idx ], field_ [val_idx ]
250270
251- # device = torch.device(device)
252- grid_flattened .to (device )
253- field_ .to (device )
254- optimizer = torch .optim .Adam (model .parameters (), lr = learining_rate )
271+ # Optimizer and scheduler
272+ optimizer = torch .optim .Adam (model .parameters (), lr = learning_rate )
273+ scheduler = torch .optim .lr_scheduler .StepLR (optimizer , step_size = epochs_max // 10 , gamma = 0.5 )
274+ # scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, 'min', factor=0.5)
275+ # scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs_max // 10)
255276
256- batch_size = int (data .size * batch_frac )
277+ # Loss function
278+ loss_fn = torch .nn .MSELoss ()
257279
258- t = 0
280+ # Batch size
281+ batch_size = max (1 , int (data .size * batch_frac ))
259282
260- min_loss = np .inf
261- while loss_mean > 1e-5 and t < epochs_max :
283+ # Training loop
284+ min_val_loss = np .inf
285+ best_model_state = None
262286
263- permutation = torch .randperm (grid_flattened .size ()[0 ])
287+ model .train ()
288+ for epoch in range (epochs_max ):
289+ permutation = torch .randperm (train_x .size (0 ))
290+ train_loss_list = []
264291
265- loss_list = []
292+ for i in range (0 , train_x .size (0 )- 1 , batch_size ):
293+ indices = permutation [i :i + batch_size ]
294+ batch_x = train_x [indices ]
295+ batch_y = train_y [indices ]
266296
267- for i in range (0 , grid_flattened .size ()[0 ], batch_size ):
268297 optimizer .zero_grad ()
298+ pred = model (batch_x )
299+ loss = loss_fn (pred , batch_y )
300+ # loss = torch.mean(torch.abs(batch_y - pred))
301+ loss .backward ()
302+ optimizer .step ()
303+ train_loss_list .append (loss .item ())
269304
270- indices = permutation [ i : i + batch_size ]
271- batch_x , batch_y = grid_flattened [ indices ], field_ [ indices ]
305+ train_loss = np . mean ( train_loss_list )
306+ scheduler . step ( train_loss )
272307
273- loss = torch .mean (torch .abs (batch_y - model (batch_x )))
308+ with torch .no_grad ():
309+ val_pred = model (val_x )
310+ val_loss = loss_fn (val_pred , val_y ).item ()
311+
312+ if epoch % 100 == 0 :
313+ print (f"Epoch { epoch :4d} | Loss: { val_loss :.6e} " )
314+
315+ if val_loss < min_val_loss :
316+ min_val_loss = val_loss
317+ best_model_state = model .state_dict ()
318+
319+ if val_loss <= loss_threshold :
320+ print (f"Early stopping at epoch { epoch } , loss = { val_loss :.4e} " )
321+ break
322+
323+ # Load best model and evaluate
324+ model .load_state_dict (best_model_state )
325+ model .eval ()
326+
327+ with torch .no_grad ():
328+ prediction = model (grid_flattened ).cpu ().numpy ().reshape (original_shape )
274329
275- loss .backward ()
276- optimizer .step ()
277- loss_list .append (loss .item ())
278- loss_mean = np .mean (loss_list )
279- if loss_mean < min_loss :
280- best_model = model
281- min_loss = loss_mean
282- # if global_var.verbose.show_ann_loss:
283- print ('Surface training t={}, loss={}' .format (t , loss_mean ))
284- t += 1
285-
286- data_approx = best_model (grid_flattened ).detach ().numpy ().reshape (original_shape )
287330 if return_ann :
288- warn ('Returning ANN from smoother. This should not occur anywhere, except selected experiments.' )
289- return data_approx , best_model
331+ warn ('Returning ANN from smoother. This should only happen in selected experiments.' )
332+ return prediction , model
290333 else :
291- return data_approx
334+ return prediction
292335
293336
294337class GaussianSmoother (AbstractSmoother ):
0 commit comments