Alexander Benz
Alexander Benz

Reputation: 11

MLP with data and physics loss

I have the following problem: Training an MLP on 4 inputs while also having an estimation from a physical model (with some error). I now want to compute a combined loss from the physics and data loss and use this for training the MLP (1 output as a regression value). I'm using tensorflow and I'm having some errors in seperating the targets and providing them to the model. I appreciate any suggestions. Thank you! This is my model and code so far:

import tensorflow as tf
import pandas as pd
import numpy as np
from sklearn.preprocessing import StandardScaler
from tensorflow.keras.models import Sequential
from tensorflow.keras import regularizers
from tensorflow.keras.layers import Dense
from tensorflow.keras.optimizers import SGD
from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
import joblib
import tensorflow.keras.backend as K

# Load data
train_csv_path = "/home/qiki1832/PycharmProjects/datasets_TABULA/GroundTruth_train20250219.csv"
val_csv_path = "/home/qiki1832/PycharmProjects/datasets_TABULA/GroundTruth_val20250218.csv"

# Read and clean data
df_train = pd.read_csv(train_csv_path, delimiter=",").dropna()
df_val = pd.read_csv(val_csv_path, delimiter=",").dropna()

# Features and targets
features = ['T_se', 'T_si', 'q_si', 'd']
targets = ['R']  # Only R_true is used for actual training
pred_phys = ['R_phys,i']  # R_phys,i is used for physics loss

X_train = df_train[features].values
y_train = df_train[targets].values
X_val = df_val[features].values
y_val = df_val[targets].values

# Normalize inputs
scaler_X = StandardScaler()
X_train_scaled = scaler_X.fit_transform(X_train)
X_val_scaled = scaler_X.transform(X_val)

# Normalize targets
scaler_y = StandardScaler()
y_train_scaled = scaler_y.fit_transform(y_train)
y_val_scaled = scaler_y.transform(y_val)

# Normalize the physics targets (R_phys) using the same scaler
scaler_phys = StandardScaler()
y_train_scaled_R_phys = scaler_phys.fit_transform(df_train[pred_phys].values)
y_val_scaled_R_phys = scaler_phys.transform(df_val[pred_phys].values)

# Save scalers for later use
joblib.dump(scaler_X, "/home/qiki1832/PycharmProjects/PINN/PhysicsRegularisation/MLP_01/transient_scaler_X.pkl")
joblib.dump(scaler_y, "/home/qiki1832/PycharmProjects/PINN/PhysicsRegularisation/MLP_01/transient_scaler_y.pkl")
joblib.dump(scaler_phys, "/home/qiki1832/PycharmProjects/PINN/PhysicsRegularisation/MLP_01/transient_scaler_phys.pkl")

# Convert to tensors
X_train_tensor = tf.convert_to_tensor(X_train_scaled, dtype=tf.float32)
X_val_tensor = tf.convert_to_tensor(X_val_scaled, dtype=tf.float32)

# Stack both targets together for proper loss computation
y_train_tensor = tf.convert_to_tensor(np.hstack([y_train_scaled, y_train_scaled_R_phys]), dtype=tf.float32)
y_val_tensor = tf.convert_to_tensor(np.hstack([y_val_scaled, y_val_scaled_R_phys]), dtype=tf.float32)

print('Training data shape:', X_train_tensor.shape, y_train_tensor.shape)
print('Validation data shape:', X_val_tensor.shape, y_val_tensor.shape)

# Define MLP model
def create_model():
    model = Sequential([
        Dense(4, activation="relu", input_shape=(X_train_tensor.shape[1],), kernel_regularizer=regularizers.l2(1e-3)),
        Dense(1, activation="linear")  # Single output: R_pred
    ])
    return model

# Create and compile model
model = create_model()
optimizer = SGD(learning_rate=0.001, momentum=0.8)

# Define hybrid loss function
def hybrid_loss(y_true, y_pred):
    R_true = y_true[:, 0:1]  # Extract R_true from y_true
    R_phys = y_true[:, 1:2]  # Extract R_phys from y_true

    # Compute MSE for data loss (R_pred vs R_true)
    L_data = tf.reduce_mean(tf.square(y_pred - R_true))  # MSE Loss (Data)

    # Convert R_phys to numpy array for physics loss calculation
    R_phys_numpy = R_phys.numpy() if tf.executing_eagerly() else tf.make_ndarray(R_phys)

    # Normalize R_phys for physics loss
    R_phys_normalized = scaler_phys.transform(R_phys_numpy)

    # Convert normalized R_phys back to tensor
    R_phys_normalized_tensor = tf.convert_to_tensor(R_phys_normalized, dtype=tf.float32)

    # Compute MSE for physics loss (R_pred vs normalized R_phys)
    L_physics = tf.reduce_mean(tf.square(y_pred - R_phys_normalized_tensor))  # MSE Loss (Physics)

    # Weighted combination of data loss and physics loss
    physics_weight = 0.05
    data_weight = 0.95
    L_total = data_weight * L_data + physics_weight * L_physics
    return L_total

# Custom Model Checkpoint callback
class CustomModelCheckpoint(tf.keras.callbacks.Callback):
    def __init__(self, filepath, save_freq=1):
        self.filepath = filepath
        self.save_freq = save_freq

    def on_epoch_end(self, epoch, logs=None):
        if epoch == 0 or (epoch + 1) % self.save_freq == 0:
            print(f"\nSaving model at epoch {epoch + 1}...")
            self.model.save(self.filepath.format(epoch=epoch + 1))

checkpoint_callback = CustomModelCheckpoint("/home/qiki1832/PycharmProjects/PINN/PhysicsRegularisation/MLP_01/model_epoch_{epoch}.h5", save_freq=1)

# Custom callback to track and save metrics
class MetricsLogger(tf.keras.callbacks.Callback):
    def __init__(self, X_train, y_train, X_test, y_test, filepath):
        self.X_train = X_train
        self.y_train = y_train
        self.X_test = X_test
        self.y_test = y_test
        self.filepath = filepath
        self.metrics_data = []

    def on_epoch_end(self, epoch, logs=None):
        y_train_pred = self.model.predict(self.X_train, verbose=0)
        y_test_pred = self.model.predict(self.X_test, verbose=0)

        train_mae = mean_absolute_error(self.y_train, y_train_pred)
        train_mse = mean_squared_error(self.y_train, y_train_pred)
        train_rmse = np.sqrt(train_mse)
        train_r2 = r2_score(self.y_train, y_train_pred)

        test_mae = mean_absolute_error(self.y_test, y_test_pred)
        test_mse = mean_squared_error(self.y_test, y_test_pred)
        test_rmse = np.sqrt(test_mse)
        test_r2 = r2_score(self.y_test, y_test_pred)

        # Get current learning rate
        current_lr = float(self.model.optimizer.lr.numpy()) if tf.executing_eagerly() else self.model.optimizer.lr

        # Store metrics
        self.metrics_data.append([epoch + 1, current_lr, train_mae, train_mse, train_rmse, train_r2,
                                  test_mae, test_mse, test_rmse, test_r2])

        # Save to CSV at every epoch
        metrics_df = pd.DataFrame(self.metrics_data, columns=[
            "Epoch", "Learning Rate",
            "Train MAE", "Train MSE", "Train RMSE", "Train R²",
            "Val MAE", "Val MSE", "Val RMSE", "Val R2"
        ])
        metrics_df.to_csv(self.filepath, index=False)

# Define log file path
metrics_csv_path = "/home/qiki1832/PycharmProjects/PINN/PhysicsRegularisation/MLP_01/training_epoch_metrics.csv"

# Initialize custom callback
metrics_logger = MetricsLogger(X_train_tensor, y_train_tensor, X_val_tensor, y_val_tensor, metrics_csv_path)

# Compile model
model.compile(optimizer=optimizer, loss=hybrid_loss, metrics=['mae'])

# Train the model
num_epochs = 50
batch_size = 256

model.fit(X_train_tensor, y_train_tensor,  # Now y_train_tensor includes both R_true and R_phys
          validation_data=(X_val_tensor, y_val_tensor),  # Pass both targets for validation
          epochs=num_epochs, batch_size=batch_size, verbose=1,
          callbacks=[checkpoint_callback, metrics_logger])

I tried several things: seperating the target files to have an easier normalization or a combined target file, but I encounter always errors when trying to read it.

Upvotes: 1

Views: 22

Answers (0)

Related Questions