Skip to content

Commit bba577c

Browse files
authored
Merge pull request #35 from Gromwud/main
added minmax normalization for factors
2 parents 099f77d + 276a005 commit bba577c

2 files changed

Lines changed: 88 additions & 55 deletions

File tree

epde/structure/main_structures.py

Lines changed: 57 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
from epde.structure.encoding import Chromosome
3434
from epde.structure.factor import Factor
3535
from epde.structure.structure_template import ComplexStructure, check_uniqueness
36-
from epde.supplementary import filter_powers, normalize_ts, population_sort, flatten, rts, exp_form
36+
from epde.supplementary import filter_powers, normalize_ts, population_sort, flatten, rts, exp_form, minmax_normalize
3737

3838

3939
class Term(ComplexStructure):
@@ -71,23 +71,23 @@ def __init__(self, pool, passed_term=None, mandatory_family=None, max_factors_in
7171
self.use_cache()
7272
# key - state of normalization, value - if the variable is saved in cache
7373
self.reset_saved_state()
74-
74+
7575
def manual_reconst(self, attribute:str, value, except_attrs:dict):
76-
from epde.loader import attrs_from_dict, get_typespec_attrs
76+
from epde.loader import attrs_from_dict, get_typespec_attrs
7777
supported_attrs = ['structure']
7878
if attribute not in supported_attrs:
7979
raise ValueError(f'Attribute {attribute} is not supported by manual_reconst method.')
80-
80+
8181
if attribute == supported_attrs[0]:
8282
# Validate correctness of a term definition
8383
self.structure = []
8484
for factor_elem in value:
8585
factor = Factor.__new__(Factor)
86-
86+
8787
attrs_from_dict(factor, factor_elem, except_attrs)
8888
factor.evaluator = self.pool
8989
self.structure.append(factor)
90-
90+
9191
@property
9292
def cache_label(self):
9393
if len(self.structure) > 1:
@@ -171,7 +171,7 @@ def update_token_status(token_status, changes):
171171

172172
self.descr_variable_marker = mandatory_family if mandatory_family is not None else False
173173

174-
if not mandatory_family:
174+
if not mandatory_family:
175175
occupied_by_factor, factor = self.pool.create(label=None, create_meaningful=True,
176176
token_status=self.occupied_tokens_labels,
177177
create_derivs=create_derivs, **kwargs)
@@ -216,13 +216,15 @@ def evaluate(self, structural, grids=None):
216216
value = super().evaluate(structural)
217217
if normalize:
218218
if np.ndim(value) != 1:
219-
if len(self.structure) > 1:
220-
value = np.ones_like(value)
221-
for factor in self.structure:
222-
temp = factor.evaluate()
223-
value *= normalize_ts(temp)
224-
else:
225-
value = normalize_ts(value)
219+
value = np.ones_like(value)
220+
for factor in self.structure:
221+
temp = factor.evaluate()
222+
# value *= normalize_ts(temp)
223+
value *= minmax_normalize(temp)
224+
# value *= factor.evaluate(structural)
225+
# else:
226+
# # value = normalize_ts(value)
227+
# value = minmax_normalize(value)
226228
else:
227229
if np.std(value) != 0:
228230
value = (value - np.mean(value)) / np.std(value)
@@ -239,7 +241,7 @@ def evaluate(self, structural, grids=None):
239241
def filter_tokens_by_right_part(self, reference_target, equation, equation_position):
240242
warnings.warn(message='Tokens can no longer be set as right-part-unique',
241243
category=DeprecationWarning)
242-
taken_tokens = [factor.label for factor in reference_target.structure
244+
taken_tokens = [factor.label for factor in reference_target.structure
243245
if factor.status['unique_for_right_part']]
244246
meaningful_taken = any([factor.status['meaningful'] for factor in reference_target.structure
245247
if factor.status['unique_for_right_part']])
@@ -355,10 +357,10 @@ def __deepcopy__(self, memo=None):
355357
class Equation(ComplexStructure):
356358
__slots__ = ['_history', 'structure', 'interelement_operator', 'n_immutable', 'pool',
357359
# '_target', '_features', 'saved', 'saved_as','max_factors_in_term', 'operator',
358-
'target_idx', 'right_part_selected', '_weights_final', 'weights_final_evald',
360+
'target_idx', 'right_part_selected', '_weights_final', 'weights_final_evald',
359361
'_weights_internal', 'weights_internal_evald', 'fitness_calculated', 'stability_calculated', 'aic_calculated', 'solver_form_defined',
360362
'_fitness_value', '_coefficients_stability', '_aic', 'metaparameters', 'main_var_to_explain'] # , '_solver_form'
361-
363+
362364

363365
def __init__(self, pool: TFPool, basic_structure: Union[list, tuple, set], var_to_explain: str = None,
364366
metaparameters: dict = {'sparsity': {'optimizable': True, 'value': 1.},
@@ -383,7 +385,7 @@ def __init__(self, pool: TFPool, basic_structure: Union[list, tuple, set], var_t
383385
matrix, composed of terms, not included in target, value columns, designated as features for application in sparse regression;
384386
385387
fitness_value : float \r\n
386-
Inverse value of squared error for the selected target 2function and features and discovered weights;
388+
Inverse value of squared error for the selected target 2function and features and discovered weights;
387389
388390
estimator : sklearn estimator of selected type \r\n
389391
@@ -431,29 +433,29 @@ def __init__(self, pool: TFPool, basic_structure: Union[list, tuple, set], var_t
431433
if check_uniqueness(new_term, self.structure):
432434
force_var_to_explain = False
433435
break
434-
436+
435437
self.structure.append(new_term)
436438

437439
for idx, _ in enumerate(self.structure):
438440
self.structure[idx].use_cache()
439441
# self.coefficients_stability = np.inf
440-
442+
441443
def manual_reconst(self, attribute:str, value, except_attrs:dict):
442-
from epde.loader import attrs_from_dict, get_typespec_attrs
444+
from epde.loader import attrs_from_dict, get_typespec_attrs
443445
supported_attrs = ['structure']
444446
if attribute not in supported_attrs:
445447
raise ValueError(f'Attribute {attribute} is not supported by manual_reconst method.')
446-
448+
447449
if attribute == supported_attrs[0]:
448450
# Validate correctness of a term definition
449451
self.structure = []
450452
for term_elem in value:
451453
term = Term.__new__(Term)
452454
# except_attr, _ = get_typespec_attrs(term)
453-
455+
454456
attrs_from_dict(term, term_elem, except_attrs)
455457
self.structure.append(term)
456-
458+
457459
def reset_explaining_term(self, term_idx=0):
458460
for idx, term in enumerate(self.structure):
459461
if idx == term_idx:
@@ -533,21 +535,21 @@ def reconstruct_by_right_part(self, right_part_idx):
533535

534536
def evaluate(self, normalize=True, return_val=False, grids=None):
535537
target = self.structure[self.target_idx].evaluate(normalize, grids=grids)
536-
538+
537539
# Place for improvent: introduce shifted_idx where necessary
538540
def shifted_idx(idx):
539541
if idx < self.target_idx:
540-
return idx
542+
return idx
541543
elif idx > self.target_idx:
542544
return idx - 1
543545
else:
544546
return -1
545-
547+
546548
if normalize:
547549
feature_indexes = list(range(len(self.structure)))
548550
feature_indexes.remove(self.target_idx)
549551
else:
550-
feature_indexes = [idx for idx in range(len(self.structure))
552+
feature_indexes = [idx for idx in range(len(self.structure))
551553
if self.weights_internal[shifted_idx(idx)] != 0 and idx != self.target_idx]
552554
if len(feature_indexes) > 0:
553555
for feat_idx in range(len(feature_indexes)):
@@ -564,15 +566,15 @@ def shifted_idx(idx):
564566
temp_feats = np.transpose(temp_feats)
565567
else:
566568
features = None
567-
569+
568570
if return_val:
569571
self.prev_normalized = normalize
570572
if normalize:
571573
elem1 = np.expand_dims(target, axis=1)
572574
value = np.add(elem1, - reduce(lambda x, y: np.add(x, y), [np.multiply(self.weights_internal[idx_full], temp_feats[:, idx_sparse])
573575
for idx_sparse, idx_full in enumerate(feature_indexes)]))
574576
# for feature_idx, weight in np.ndenumerate(self.weights_internal)]))
575-
else:
577+
else:
576578
elem1 = np.expand_dims(target, axis=1)
577579
if features is not None:
578580
features_val = reduce(lambda x, y: np.add(x, y), [np.multiply(self.weights_final[idx_full], temp_feats[:, idx_sparse])
@@ -723,7 +725,7 @@ def text_form(self):
723725
self.structure[term_idx].name + ' + '
724726
form += 'k_' + str(len(self.structure)) + ' = 0'
725727
return form
726-
728+
727729
@property
728730
def latex_form(self):
729731
form = self.structure[self.target_idx].latex_form + r' = '
@@ -736,13 +738,13 @@ def latex_form(self):
736738
mnt, exp = exp_form(self.weights_final[idx_corrected], digits_rounding_max)
737739
exp_str = r'\cdot 10^{{{0}}} '.format(str(exp)) if exp != 0 else ''
738740
form += str(mnt) + exp_str + term.latex_form + r' + '
739-
741+
740742
mnt, exp = exp_form(self.weights_final[-1], digits_rounding_max)
741743
exp_str = r'\cdot 10^{{{0}}} '.format(str(exp)) if exp != 0 else ''
742-
744+
743745
form += str(mnt) + exp_str
744746
return form
745-
747+
746748
@property
747749
def state(self):
748750
return self.text_form
@@ -786,26 +788,26 @@ def count_order(obj, deriv_ax):
786788
if np.max(max_orders) > 4:
787789
raise NotImplementedError('The current implementation allows does not allow higher orders of equation, than 2.')
788790
return max_orders
789-
791+
790792
def boundary_conditions(self, max_deriv_orders=(1,), main_var_key=('u', (1.0,)), full_domain: bool = False,
791793
grids : list = None):
792794
required_bc_ord = max_deriv_orders # We assume, that the maximum order of the equation here is 2
793795
if global_var.grid_cache is None:
794796
raise NameError('Grid cache has not been initialized yet.')
795-
797+
796798
bconds = []
797799
hardcoded_bc_relative_locations = {0: (), 1: (0,), 2: (0, 1),
798800
3: (0., 0.5, 1.), 4: (0., 1/3., 2/3., 1.)}
799-
801+
800802
if full_domain:
801803
grid_cache = global_var.initial_data_cache
802804
tensor_cache = global_var.initial_data_cache
803805
else:
804806
grid_cache = global_var.grid_cache
805807
tensor_cache = global_var.tensor_cache
806-
808+
807809
tensor_shape = grid_cache.get('0').shape
808-
810+
809811
def get_boundary_ind(tensor_shape, axis, rel_loc):
810812
return tuple(np.meshgrid(*[np.arange(shape) if dim_idx != axis else min(int(rel_loc * shape), shape-1)
811813
for dim_idx, shape in enumerate(tensor_shape)], indexing='ij'))
@@ -818,12 +820,12 @@ def get_boundary_ind(tensor_shape, axis, rel_loc):
818820
if coords.ndim > 2:
819821
coords = coords.squeeze()
820822
vals = np.expand_dims(tensor_cache.get(main_var_key)[indexes], axis=0).T
821-
823+
822824
coords = torch.from_numpy(coords).type(torch.FloatTensor)
823-
825+
824826
vals = torch.from_numpy(vals).type(torch.FloatTensor)
825-
bconds.append([coords, vals, 'dirichlet'])
826-
827+
bconds.append([coords, vals, 'dirichlet'])
828+
827829
return bconds
828830

829831
def clear_after_solver(self):
@@ -876,13 +878,13 @@ def __init__(self, pool: TFPool, metaparameters: dict):
876878
self.moeadd_set = False
877879

878880
self.vars_to_describe = [token_family.variable for token_family in self.tokens_for_eq.families]
879-
881+
880882
def manual_reconst(self, attribute:str, value, except_attrs:dict):
881883
from epde.loader import attrs_from_dict, get_typespec_attrs
882884
supported_attrs = ['vals']
883885
if attribute not in supported_attrs:
884886
raise ValueError(f'Attribute {attribute} is not supported by manual_reconst method.')
885-
887+
886888
if attribute == supported_attrs[0]:
887889
# Validate correctness of a term definition
888890
equations = {}
@@ -892,7 +894,7 @@ def manual_reconst(self, attribute:str, value, except_attrs:dict):
892894
equations[self.vars_to_describe[idx]] = eq
893895
self.vals = Chromosome(equations, {key: val for key, val in self.metaparameters.items()
894896
if val['optimizable']})
895-
897+
896898
def use_default_multiobjective_function(self, use_pic: bool = False):
897899
if use_pic:
898900
self.use_pic_multiobjective_function()
@@ -935,35 +937,35 @@ def set_objective_functions(self, obj_funs):
935937
Parameters:
936938
-----------
937939
obj_funs - callable or list of callables;
938-
function/functions to evaluate quality metrics of system of equations. Can return a single
939-
metric (for example, quality of the process modelling with specific system), or
940+
function/functions to evaluate quality metrics of system of equations. Can return a single
941+
metric (for example, quality of the process modelling with specific system), or
940942
a list of metrics (for example, number of terms for each equation in the system).
941-
The function results will be flattened after their application.
943+
The function results will be flattened after their application.
942944
943945
'''
944946
assert callable(obj_funs) or all([callable(fun) for fun in obj_funs])
945947
self.obj_funs = obj_funs
946948

947949
def matches_complexitiy(self, complexity : Union[int, list]):
948-
if isinstance(complexity, (int, float)):
950+
if isinstance(complexity, (int, float)):
949951
complexity = [complexity,]
950-
952+
951953
if not isinstance(complexity, list) or len(self.vars_to_describe) != len(complexity):
952954
raise ValueError('Incorrect list of complexities passed.')
953955
adj_complexity = copy.copy(complexity)
954956
for idx, compl in enumerate(adj_complexity):
955957
if compl is None:
956958
adj_complexity[idx] = self.obj_fun[-len(complexity) + idx]
957-
959+
958960
return list(self.obj_fun[-len(adj_complexity):]) == adj_complexity
959961

960962
def create(self, passed_equations: list = None):
961963
if passed_equations is None:
962964
structure = {}
963-
965+
964966
token_selection = self.tokens_supp
965967
current_tokens_pool = token_selection + self.tokens_for_eq
966-
968+
967969
for eq_idx, variable in enumerate(self.vars_to_describe):
968970
structure[variable] = Equation(current_tokens_pool, basic_structure=[],
969971
var_to_explain=variable,
@@ -988,7 +990,7 @@ def equation_opt_iteration(population, evol_operator, population_size, iter_inde
988990
gc.collect()
989991
population = evol_operator.apply(population, unexplained_vars)
990992
return population
991-
993+
992994
@property
993995
def obj_fun(self):
994996
return np.array(flatten([func(self) for func in self.obj_funs]))

epde/supplementary.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,3 +334,34 @@ def normalize_ts(Input):
334334
else:
335335
matrix[i] = 1
336336
return matrix
337+
338+
def minmax_normalize(matrix):
339+
"""
340+
Apply min-max normalization to a matrix.
341+
For 1D arrays: returns as-is
342+
For 2D+ arrays: normalizes each row to [0, 1] range
343+
"""
344+
matrix = np.copy(matrix)
345+
346+
if np.ndim(matrix) == 0:
347+
raise ValueError('Incorrect input to the normalization: the data has 0 dimensions')
348+
elif np.ndim(matrix) == 1:
349+
return matrix
350+
else:
351+
domain_min = np.min(matrix)
352+
domain_max = np.max(matrix)
353+
domain_mean = np.mean(matrix)
354+
if domain_max != domain_min:
355+
matrix = (matrix - domain_mean - domain_min) / (domain_max - domain_min)
356+
# for i in np.arange(matrix.shape[0]):
357+
# row_min = np.min(matrix[i])
358+
# row_max = np.max(matrix[i])
359+
#
360+
# # Only normalize if the row has variation
361+
# if domain_max != domain_min:
362+
# matrix[i] = (matrix[i] - domain_mean - domain_min) / (domain_max - domain_min)
363+
# else:
364+
# # If all values are the same, set to 0.5 or keep original (0.5 is midpoint)
365+
# matrix[i] = 0.5
366+
367+
return matrix

0 commit comments

Comments
 (0)