Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion executables/bestest_hydronic_heat_pump/P_hp.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"""

# Setup up logger for saving
Logger.setup_logger(folder_name='P_hp', override=True)
Logger.setup_logger(folder_name='P_hp', override=True, print_level='warning')

# File path to data
file_path = r"data/bestest_hydronic_heat_pump/pid_data.csv"
Expand Down
12 changes: 5 additions & 7 deletions physXAI/evaluation/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import numpy as np
from sklearn.metrics import mean_squared_error, r2_score
from physXAI.preprocessing.training_data import TrainingData, TrainingDataMultiStep, TrainingDataGeneric
from physXAI.utils.logging import Logger


class Metrics:
Expand All @@ -10,8 +11,6 @@ class Metrics:
for training, validation, and test datasets.
"""

print_evaluate = True

def __init__(self, td: TrainingDataGeneric):
"""
Initializes the Metrics object by calculating metrics for train, validation (if available),
Expand Down Expand Up @@ -52,10 +51,9 @@ def evaluate(y_true: np.ndarray, y_pred: np.ndarray, label: str = '') -> dict[st
kpis['RMSE' + ' ' + label] = rmse
kpis['R2' + ' ' + label] = r2

if Metrics.print_evaluate:
# print(f"{label} MSE: {mse:.2f}")
print(f"{label} RMSE: {rmse:.2f}")
print(f"{label} R2: {r2:.2f}")
Logger.print(f"{label} MSE: {mse:.2f}", 'debug')
Logger.print(f"{label} RMSE: {rmse:.2f}", 'info')
Logger.print(f"{label} R2: {r2:.2f}", 'info')

return kpis

Expand Down Expand Up @@ -130,7 +128,7 @@ def evaluate(y_true: np.ndarray, y_pred: np.ndarray, label: str = '', **kwargs)
for loss in kwargs['pinn_losses']:
val = float(loss(y_true, y_pred))
kpis[loss.__name__ + ' ' + label] = val
print(f"{loss.__name__ + ' ' + label}: {val:.2f}")
Logger.print(f"{loss.__name__ + ' ' + label}: {val:.2f}", 'info')

return kpis

Expand Down
17 changes: 8 additions & 9 deletions physXAI/feature_selection/recursive_feature_elimination.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ def search_best_features(runs: dict, multi_step: bool, use_multi_step_error: boo
except ValueError:
max_features = np.inf

print('Selected features:')
Logger.print('Selected features:', 'info')
if max_features == np.inf:
inputs = sorted_kpis[min_index]['inputs']
else:
inputs = sorted_kpis[max_features]['inputs']
print(inputs)
Logger.print(inputs, 'info')
return inputs


Expand All @@ -53,12 +53,11 @@ def recursive_feature_elimination(file_path: str, preprocessing: PreprocessingDa
if fixed_inputs is None:
fixed_inputs = list()

print('Feature Selection')
Metrics.print_evaluate = False

if Logger._logger is None:
Logger.setup_logger()

Logger.print('Feature Selection', 'info')

org_inputs = preprocessing.inputs
inputs = preprocessing.inputs
input_length = len(inputs)
Expand All @@ -83,8 +82,8 @@ def recursive_feature_elimination(file_path: str, preprocessing: PreprocessingDa

# Recursive feature elimination
for j in range(input_length - 1, 0, -1):
print(f'Features {j + 1}')
print(inputs)
Logger.print(f'Features {j + 1}', 'info')
Logger.print(inputs, 'info')

# Reduced input features
new_inputs = list()
Expand Down Expand Up @@ -137,8 +136,8 @@ def recursive_feature_elimination(file_path: str, preprocessing: PreprocessingDa
key_filter = int(min(kpis, key=kpis.get))
inputs = new_inputs[key_filter]
runs[j] = run
print(f'Features {1}')
print(inputs)
Logger.print(f'Features {1}', 'info')
Logger.print(inputs, 'info')

preprocessing.inputs = org_inputs

Expand Down
16 changes: 9 additions & 7 deletions physXAI/models/ann/ann_design.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def fit_model(self, model, td: TrainingDataGeneric):
callbacks = list()
if self.early_stopping_epochs is not None:
es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs,
restore_best_weights=True, verbose=1)
restore_best_weights=True, verbose=Logger.verbosity_int())
callbacks.append(es)

# Fit model, track training time
Expand Down Expand Up @@ -111,14 +111,16 @@ def fit_model(self, model, td: TrainingDataGeneric):
training_history = model.fit(train_ds,
validation_data=val_ds,
epochs=self.epochs,
callbacks=callbacks)
callbacks=callbacks,
verbose=Logger.verbosity())
stop_time = time.perf_counter()

# Add metrics to training data
td.add_training_time(stop_time - start_time)
td.add_training_record(training_history)

model.summary()
if Logger.check_print_level('info'):
model.summary()

def plot(self, td: TrainingDataGeneric):
"""
Expand Down Expand Up @@ -587,10 +589,10 @@ def evaluate(self, model, td: TrainingDataGeneric):
td (TrainingData): The training data
"""

y_pred_train = model.predict(td.X_train_single)
y_pred_test = model.predict(td.X_test_single)
y_pred_train = model.predict(td.X_train_single, verbose=Logger.verbosity())
y_pred_test = model.predict(td.X_test_single, verbose=Logger.verbosity())
if td.X_val is not None:
y_pred_val = model.predict(td.X_val_single)
y_pred_val = model.predict(td.X_val_single, verbose=Logger.verbosity())
else:
y_pred_val = None
td.add_predictions(y_pred_train, y_pred_val, y_pred_test)
Expand Down Expand Up @@ -703,7 +705,7 @@ def fit_model(self, model, td: TrainingDataMultiStep):
callbacks = list()
if self.early_stopping_epochs is not None:
es = keras.callbacks.EarlyStopping(monitor='val_loss', mode='min', patience=self.early_stopping_epochs,
restore_best_weights=True, verbose=1)
restore_best_weights=True, verbose=Logger.verbosity_int())
callbacks.append(es)

# Fit model, track training time
Expand Down
4 changes: 0 additions & 4 deletions physXAI/models/ann/model_construction/ann_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,6 @@ def ClassicalANNConstruction(config: dict, td: TrainingDataGeneric):
if config['rescale_output']:
model.add(keras.layers.Rescaling(scale=rescale_sigma, offset=rescale_mean))

model.summary()

return model


Expand Down Expand Up @@ -182,6 +180,4 @@ def CMNNModelConstruction(config: dict, td: TrainingDataGeneric):

model = keras.models.Model(inputs=input_layer, outputs=x)

model.summary()

return model
5 changes: 2 additions & 3 deletions physXAI/models/ann/model_construction/rbf_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from physXAI.preprocessing.training_data import TrainingDataGeneric
from physXAI.models.ann.configs.ann_model_configs import RBFConstruction_config
from physXAI.models.ann.keras_models.keras_models import RBFLayer
from physXAI.utils.logging import Logger


def gamma_init(centers, overlap=0.5) -> float:
Expand All @@ -26,7 +27,7 @@ def gamma_init(centers, overlap=0.5) -> float:
return 1.0 # Fallback

gamma = -np.log(overlap) / avg_dist_sq
# print(f"Calculated Gamma: {gamma}")
Logger.print(f"Calculated Gamma: {gamma}", 'info')
return gamma


Expand Down Expand Up @@ -111,6 +112,4 @@ def RBFModelConstruction(config: dict, td: TrainingDataGeneric):

model = keras.Model(inputs=input_layer, outputs=x)

model.summary()

return model
2 changes: 0 additions & 2 deletions physXAI/models/ann/model_construction/residual_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,4 @@ def LinResidualANNConstruction(config: dict, td: TrainingDataGeneric, lin_model:
lin.set_weights([lin_model.coef_.reshape(-1, 1), np.array(lin_model.intercept_)])
lin.trainable = False

model.summary()

return model
23 changes: 15 additions & 8 deletions physXAI/models/models.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import os
import time
from abc import ABC, abstractmethod
from typing import Type
Expand All @@ -10,6 +11,9 @@
from physXAI.evaluation.metrics import Metrics, MetricsMultiStep
from physXAI.plotting.plotting import (plot_prediction_correlation, plot_metrics_table, subplots,
plot_predictions, plot_multi_rmse)
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '1'
import keras
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '0'


MODEL_CLASS_REGISTRY: dict[str, Type['AbstractModel']] = dict()
Expand Down Expand Up @@ -199,7 +203,7 @@ def register_model(cls): # pragma: no cover
The class is registered using its __name__.
"""
if cls.__name__ in MODEL_CLASS_REGISTRY:
print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.")
Logger.print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.", 'warning')
MODEL_CLASS_REGISTRY[cls.__name__] = cls
return cls # Decorators must return the class (or a replacement)

Expand All @@ -224,11 +228,14 @@ def evaluate(model, td: TrainingDataGeneric):
model: The trained model instance.
td (TrainingDataGeneric): The TrainingData object containing datasets and for storing results.
"""
kwargs = {}
if isinstance(model, keras.Model):
kwargs['verbose'] = Logger.verbosity()

y_pred_train = model.predict(td.X_train_single)
y_pred_test = model.predict(td.X_test_single)
y_pred_train = model.predict(td.X_train_single, **kwargs)
y_pred_test = model.predict(td.X_test_single, **kwargs)
if td.X_val_single is not None:
y_pred_val = model.predict(td.X_val_single)
y_pred_val = model.predict(td.X_val_single, **kwargs)
else:
y_pred_val = None
td.add_predictions(y_pred_train, y_pred_val, y_pred_test)
Expand Down Expand Up @@ -307,7 +314,7 @@ def _evaluate_multi_inner_loop(model, X: np.ndarray, y: np.ndarray, X_columns: l
current_val = X[:, 0, index].reshape(-1, 1)
current_true_val = current_val.copy()
for t in range(X.shape[1]):
pred = model.predict(X[:, t, :], verbose=0)
pred = model.predict(X[:, t, :], verbose=Logger.verbosity())
if delta_prediction:
current_val += pred
current_true_val += y[:, t, 0].reshape(-1, 1)
Expand Down Expand Up @@ -463,12 +470,12 @@ def evaluate(model, td: TrainingDataMultiStep):
td (TrainingDataMultistep): The TrainingDataMultiStep object containing datasets and for storing results.
"""

y_pred_train = model.predict(td.X_train)
y_pred_train = model.predict(td.X_train, verbose=Logger.verbosity())
if td.X_val is not None:
y_pred_val = model.predict(td.X_val)
y_pred_val = model.predict(td.X_val, verbose=Logger.verbosity())
else:
y_pred_val = None
y_pred_test = model.predict(td.X_test)
y_pred_test = model.predict(td.X_test, verbose=Logger.verbosity())
td.add_predictions(y_pred_train, y_pred_val, y_pred_test)

metrics = MetricsMultiStep(td)
Expand Down
3 changes: 2 additions & 1 deletion physXAI/preprocessing/constructed.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from abc import ABC, abstractmethod
from physXAI.utils.logging import Logger
from typing import Type, Union
import numpy as np
from pandas import DataFrame, Series
Expand Down Expand Up @@ -135,7 +136,7 @@ def register_feature(cls):
The class is registered using its __name__.
"""
if cls.__name__ in CONSTRUCTED_CLASS_REGISTRY: # pragma: no cover
print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.") # pragma: no cover
Logger.print(f"Warning: Class '{cls.__name__}' is already registered. Overwriting.", 'warning') # pragma: no cover
CONSTRUCTED_CLASS_REGISTRY[cls.__name__] = cls
return cls # Decorators must return the class (or a replacement)

Expand Down
49 changes: 42 additions & 7 deletions physXAI/utils/logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,9 @@
import os
import shutil
from datetime import datetime
from typing import Union
import git
from physXAI.preprocessing.constructed import FeatureConstruction
import pickle
from pathlib import Path

from physXAI.preprocessing.training_data import TrainingDataMultiStep


def get_parent_working_directory() -> str:
Expand All @@ -28,10 +25,10 @@ def get_parent_working_directory() -> str:
git_root = repo.working_tree_dir
return git_root
except git.InvalidGitRepositoryError: # pragma: no cover
print(f"Error: Cannot find git root directory.") # pragma: no cover
Logger.print(f"Error: Cannot find git root directory.", 'error') # pragma: no cover
return '' # pragma: no cover
except Exception as e: # pragma: no cover
print(f"Error: An unexpected error occurred when searching for parent directory: {e}") # pragma: no cover
Logger.print(f"Error: An unexpected error occurred when searching for parent directory: {e}", 'error') # pragma: no cover
return '' # pragma: no cover


Expand Down Expand Up @@ -110,9 +107,40 @@ class Logger:
save_name_model: str = 'model'
save_name_model_online_learning: str = 'model_ol'

print_level: str = 'info' # options: 'debug', 'info', 'warning', 'error'
_print_levels = ['debug', 'info', 'warning', 'error']

_logger = None
_override = False

@staticmethod
def print(message: str, print_level: str = 'info'):
if Logger.check_print_level(print_level):
print(message)

@staticmethod
def check_print_level(print_level: str) -> bool:
if str(print_level).lower() not in Logger._print_levels:
raise ValueError(f"Invalid print level: {str(print_level).lower()}. Valid options are: {Logger._print_levels}")
if Logger._print_levels.index(str(print_level).lower()) >= Logger._print_levels.index(Logger.print_level):
return True
else:
return False

@staticmethod
def verbosity() -> Union[int, str]:
if Logger._print_levels.index(Logger.print_level) >= Logger._print_levels.index('warning'):
return 0
else:
return "auto"

@staticmethod
def verbosity_int() -> int:
if Logger._print_levels.index(Logger.print_level) >= Logger._print_levels.index('warning'):
return 0
else:
return 1

@staticmethod
def override_question(path: str): # pragma: no cover
if os.path.exists(path) and not Logger._override:
Expand All @@ -138,7 +166,7 @@ def already_exists_question(path: str): # pragma: no cover
raise e

@staticmethod
def setup_logger(folder_name: str = None, override: bool = False, base_path: str = None):
def setup_logger(folder_name: str = None, override: bool = False, base_path: str = None, print_level: str = None):
if base_path is None:
base_path = Logger.base_path
if folder_name is None:
Expand All @@ -153,6 +181,11 @@ def setup_logger(folder_name: str = None, override: bool = False, base_path: str

Logger._logger = path
Logger._override = override

if print_level is not None:
if str(print_level).lower() not in Logger._print_levels:
raise ValueError(f"Invalid print level: {str(print_level).lower()}. Valid options are: {Logger._print_levels}")
Logger.print_level = str(print_level).lower()

@staticmethod
def log_setup(preprocessing=None, model=None, save_name_preprocessing=None, save_name_model=None,
Expand All @@ -173,6 +206,7 @@ def log_setup(preprocessing=None, model=None, save_name_preprocessing=None, save
with open(path, "w") as f:
json.dump(preprocessing_dict, f, indent=4)

from physXAI.preprocessing.constructed import FeatureConstruction
constructed_config = FeatureConstruction.get_config()
if len(constructed_config) > 0:
if save_name_constructed is None:
Expand Down Expand Up @@ -221,6 +255,7 @@ def save_training_data(training_data, path: str = None):
with open(p, "w") as f:
json.dump(td_dict, f, indent=4)

from physXAI.preprocessing.training_data import TrainingDataMultiStep
if isinstance(training_data, TrainingDataMultiStep):
training_data = copy.copy(training_data)
training_data.train_ds = None
Expand Down