训练时反归一化数据:TensorFlow/Keras CNN损失值还原求助
Hey there! Let's work through this problem together. First off, I notice you're using the same MinMaxScaler for both your input X and output Y—that's a small but important mistake! Input and output data almost always have different value ranges and distributions, so you should use separate scalers for each. Let's fix that first, then dive into getting your original-scale loss.
Step 1: Fix Your Data Normalization
First, let's split the scalers for inputs and outputs to avoid issues later:
from sklearn.preprocessing import MinMaxScaler # Create separate scalers for input and output scaler_X = MinMaxScaler() scaler_Y = MinMaxScaler() # Normalize input and output data X_scaled = scaler_X.fit_transform(X) Y_scaled = scaler_Y.fit_transform(Y) # Keep a copy of your original Y data (unscaled) for later loss calculations Y_original = Y.copy()
Step 2: Three Ways to Get Original-Scale Loss
Now, let's cover three practical methods to compute loss on the original data scale, depending on your needs.
Method 1: Use a Callback to Calculate Original Loss After Each Epoch
This is great if you still want to train on normalized data (which helps model stability) but want to track the original-scale loss during training. We'll create a custom callback that runs after each epoch to predict, inverse-transform, and compute the original loss:
import tensorflow as tf from tensorflow.keras.callbacks import Callback class OriginalScaleLossCallback(Callback): def __init__(self, val_X_scaled, val_Y_original, scaler_Y): super().__init__() self.val_X = val_X_scaled # Normalized validation input self.val_Y_true = val_Y_original # Unscaled validation labels self.scaler = scaler_Y def on_epoch_end(self, epoch, logs=None): # Predict normalized outputs from the model val_Y_pred_scaled = self.model.predict(self.val_X, verbose=0) # Inverse-transform predictions back to original scale val_Y_pred_original = self.scaler.inverse_transform(val_Y_pred_scaled) # Calculate original-scale MSE (or your preferred loss) original_mse = tf.keras.losses.MeanSquaredError()(self.val_Y_true, val_Y_pred_original).numpy() # Add the original loss to logs so it shows up in training output logs['original_mse'] = original_mse print(f"\nEpoch {epoch+1} - Original-Scale MSE: {original_mse:.4f}") # Initialize the callback (make sure you have validation data ready) original_loss_callback = OriginalScaleLossCallback( val_X_scaled=X_val_scaled, # Your normalized validation X val_Y_original=Y_val_original, # Your unscaled validation Y scaler_Y=scaler_Y ) # Train with the callback model.fit( X_scaled, Y_scaled, epochs=50, validation_data=(X_val_scaled, Y_val_scaled), callbacks=[original_loss_callback] )
Method 2: Custom Loss Function That Inverse-Transforms Internally
If you want the model to optimize directly for the original-scale loss, you can create a custom loss function that converts the normalized predictions and labels back to the original scale before calculating loss.
Note: To make this compatible with model saving/loading, we'll extract the scaler's parameters (instead of passing the scaler object directly, which can't be serialized):
# Extract scaler parameters for output data y_min = tf.constant(scaler_Y.data_min_, dtype=tf.float32) y_scale = tf.constant(scaler_Y.scale_, dtype=tf.float32) def original_scale_mse(y_true_scaled, y_pred_scaled): # Manually inverse-transform: original_value = scaled_value * scale + min y_true_original = y_true_scaled * y_scale + y_min y_pred_original = y_pred_scaled * y_scale + y_min # Calculate MSE on original scale return tf.keras.losses.MSE(y_true_original, y_pred_original) # Compile your model with this custom loss model.compile(optimizer='adam', loss=original_scale_mse) # Train as usual (still using normalized X and Y) model.fit(X_scaled, Y_scaled, epochs=50, validation_data=(X_val_scaled, Y_val_scaled))
Method 3: Add an Inverse-Transform Layer to Your Model
If you want the model to directly output predictions in the original scale, you can add a Lambda layer to the end of your model that handles the inverse transformation. Then you can train using the original (unscaled) labels directly:
# Build your base CNN model (outputs normalized values) base_cnn = tf.keras.Sequential([ tf.keras.layers.Conv2D(32, (3,3), activation='relu', input_shape=X_scaled.shape[1:]), tf.keras.layers.MaxPooling2D((2,2)), # Add your other CNN/Dense layers here... tf.keras.layers.Dense(3) # Outputs 3 normalized values ]) # Create the inverse-transform layer def inverse_transform(y_scaled): return y_scaled * y_scale + y_min # Attach the inverse-transform layer to the base model original_output = tf.keras.layers.Lambda(inverse_transform)(base_cnn.output) full_model = tf.keras.Model(inputs=base_cnn.input, outputs=original_output) # Compile with standard loss (since we're using original labels now) full_model.compile(optimizer='adam', loss='mse') # Train using normalized X and original (unscaled) Y full_model.fit(X_scaled, Y_original, epochs=50, validation_data=(X_val_scaled, Y_val_original))
Key Notes
- Always use separate scalers for inputs and outputs—sharing them will lead to incorrect inverse transformations and messed-up loss calculations.
- If you plan to save your model, avoid using loss functions that depend on external scaler objects (use Method 2's parameter-based approach or Method 3 instead).
内容的提问来源于stack exchange,提问作者Murilo Souza

