Skip to content

Commit 14e8cf9

Browse files
authored
Merge pull request #31 from Gromwud/main
Cumulative update
2 parents 76545ad + ac89e9e commit 14e8cf9

5 files changed

Lines changed: 100 additions & 49 deletions

File tree

epde/cache/cache.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -361,15 +361,15 @@ def add(self, label, tensor, normalized: bool = False, structural: bool = False,
361361

362362
def delete_entry(self, entry_label):
363363
print(f'Deleting {entry_label} from cache!')
364-
if entry_label not in self.memory_default.keys():
364+
if entry_label not in self.memory_default["numpy"].keys():
365365
raise ValueError('deleted element already not in memory')
366-
del self.memory_default[entry_label]
366+
del self.memory_default["numpy"][entry_label]
367367
try:
368-
del self.memory_structural[entry_label]
368+
del self.memory_structural["numpy"][entry_label]
369369
except KeyError:
370370
pass
371371
try:
372-
del self.memory_normalized[entry_label]
372+
del self.memory_normalized["numpy"][entry_label]
373373
except KeyError:
374374
pass
375375

epde/interface/interface.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,8 @@ def create_pool(self, data: Union[np.ndarray, list, tuple], variable_names=['u',
708708
for tf in additional_tokens])
709709
print(f'The cardinality of defined token pool is {self.pool.families_cardinality()}')
710710
print(f'Among them, the pool contains {self.pool.families_cardinality(meaningful_only=True)}')
711+
for family in self.pool.families:
712+
family.chech_constancy()
711713

712714
def save_derivatives(self, variable:str, deriv:np.ndarray):
713715
'''

epde/interface/token_family.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ def test_evaluator(self, deriv=False):
257257
self.test_evaluation = self._evaluator.apply(self.test_token)
258258
print('Test evaluation performed correctly')
259259

260-
def chech_constancy(self, test_function, **tfkwargs):
260+
def chech_constancy(self, **tfkwargs):
261261
'''
262262
Method to check, if any single simple token in the studied domain is constant, or close to it. The constant token is to be displayed and deleted from tokens and cache.
263263
@@ -270,16 +270,22 @@ def chech_constancy(self, test_function, **tfkwargs):
270270
assert self.params_set
271271
constant_tokens_labels = []
272272
for label in self.tokens:
273-
print(type(global_var.tensor_cache.memory[label + ' power 1']))
274-
constancy = test_function(global_var.tensor_cache.memory[label + ' power 1'], **tfkwargs)
273+
data_label = (label, (1.0,))
274+
data = global_var.tensor_cache.memory_default["numpy"].get(data_label)
275+
try:
276+
constancy = np.isclose(np.min(data), np.max(data))
277+
except TypeError:
278+
print(f"No {label} data in cache!")
279+
continue
275280
if constancy:
276281
constant_tokens_labels.append(label)
277282

278283
for label in constant_tokens_labels:
279284
print(f'Function {label} is assumed to be constant in the studied domain. \
280-
Removed from the equaton search.')
285+
Removed from the equaton search.')
286+
data_label = (label, (1.0,))
281287
self.tokens.remove(label)
282-
global_var.tensor_cache.delete_entry(label + ' power 1')
288+
global_var.tensor_cache.delete_entry(data_label)
283289

284290
def evaluate(self, token):
285291
"""

epde/operators/common/fitness.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def apply(self, objective: Equation, arguments: dict, force_out_of_place: bool =
201201
feature_window = features[start_idx:end_idx, :]
202202
estimator = LinearRegression(fit_intercept=False)
203203
estimator.fit(feature_window, target_window, sample_weight=self.g_fun_vals[start_idx:end_idx])
204-
valuable_weights = estimator.coef_[:-1]
204+
valuable_weights = estimator.coef_
205205
eq_window_weights.append(valuable_weights)
206206
eq_cv = np.array([np.abs(np.std(_) / np.mean(_)) for _ in zip(*eq_window_weights)])
207207
lr = eq_cv.mean()
@@ -235,7 +235,7 @@ def apply(self, objective: Equation, arguments: dict, force_out_of_place: bool =
235235
target_window = target_vals[:, start_idx:end_idx].reshape(-1)
236236
feature_window = features.reshape(*data_shape, -1)[:, start_idx:end_idx].reshape(-1, features.shape[-1])
237237
estimator.fit(feature_window, target_window, sample_weight=self.g_fun_vals.reshape(*data_shape, -1)[:, start_idx:end_idx].reshape(-1))
238-
valuable_weights = estimator.coef_[:-1]
238+
valuable_weights = estimator.coef_
239239
eq_window_weights.append(valuable_weights)
240240
eq_cv = np.array([np.abs(np.std(_) / np.mean(_)) for _ in zip(*eq_window_weights)])
241241
lr += eq_cv.mean()
@@ -275,7 +275,7 @@ def apply(self, objective: Equation, arguments: dict, force_out_of_place: bool =
275275
target_window = target_vals[:, :, start_idx:end_idx].reshape(-1)
276276
feature_window = features.reshape(*data_shape, -1)[:, :, start_idx:end_idx].reshape(-1, features.shape[-1])
277277
estimator.fit(feature_window, target_window, sample_weight=self.g_fun_vals.reshape(*data_shape, -1)[:, :, start_idx:end_idx].reshape(-1))
278-
valuable_weights = estimator.coef_[:-1]
278+
valuable_weights = estimator.coef_
279279
eq_window_weights.append(valuable_weights)
280280
eq_cv = np.array([np.abs(np.std(_) / np.mean(_)) for _ in zip(*eq_window_weights)])
281281
lr += eq_cv.mean()

epde/preprocessing/smoothers.py

Lines changed: 80 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ def __init__(self):
101101
def forward(self, x):
102102
x = torch.sin(x)
103103
return x
104+
104105
class 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-
236236
class 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

294337
class GaussianSmoother(AbstractSmoother):

0 commit comments

Comments
 (0)