Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import numpy as np | |
| import tensorflow as tf | |
| from tensorflow.keras.models import load_model | |
| # Assuming TKAN and TKAT are available after installing the respective packages | |
| from tkan import TKAN | |
| # If TKAT is from a different library, import it similarly | |
| try: | |
| from tkat import TKAT | |
| except ImportError: | |
| print("TKAT library not found. If your model uses TKAT, make sure the library is installed.") | |
| TKAT = None # Set to None if TKAT is not available | |
| from tensorflow.keras.utils import custom_object_scope | |
| import pickle # Used for saving/loading the scaler | |
| # --- Your MinMaxScaler Class (Copied from Notebook) --- | |
| class MinMaxScaler: | |
| def __init__(self, feature_axis=None, minmax_range=(0, 1)): | |
| """ | |
| Initialize the MinMaxScaler. | |
| Args: | |
| feature_axis (int, optional): The axis that represents the feature dimension if applicable. | |
| Use only for 3D data to specify which axis is the feature axis. | |
| Default is None, automatically managed based on data dimensions. | |
| """ | |
| self.feature_axis = feature_axis | |
| self.min_ = None | |
| self.max_ = None | |
| self.scale_ = None | |
| self.minmax_range = minmax_range # Default range for scaling (min, max) | |
| def fit(self, X): | |
| """ | |
| Fit the scaler to the data based on its dimensionality. | |
| Args: | |
| X (np.array): The data to fit the scaler on. | |
| """ | |
| if X.ndim == 3 and self.feature_axis is not None: # 3D data | |
| axis = tuple(i for i in range(X.ndim) if i != self.feature_axis) | |
| self.min_ = np.min(X, axis=axis) | |
| self.max_ = np.max(X, axis=axis) | |
| elif X.ndim == 2: # 2D data | |
| self.min_ = np.min(X, axis=0) | |
| self.max_ = np.max(X, axis=0) | |
| elif X.ndim == 1: # 1D data | |
| self.min_ = np.min(X) | |
| self.max_ = np.max(X) | |
| else: | |
| raise ValueError("Data must be 1D, 2D, or 3D.") | |
| self.scale_ = self.max_ - self.min_ | |
| return self | |
| def transform(self, X): | |
| """ | |
| Transform the data using the fitted scaler. | |
| Args: | |
| X (np.array): The data to transform. | |
| Returns: | |
| np.array: The scaled data. | |
| """ | |
| X_scaled = (X - self.min_) / self.scale_ | |
| X_scaled = X_scaled * (self.minmax_range[1] - self.minmax_range[0]) + self.minmax_range[0] | |
| return X_scaled | |
| def fit_transform(self, X): | |
| """ | |
| Fit to data, then transform it. | |
| Args: | |
| X (np.array): The data to fit and transform. | |
| Returns: | |
| np.array: The scaled data. | |
| """ | |
| return self.fit(X).transform(X) | |
| def inverse_transform(self, X_scaled): | |
| """ | |
| Inverse transform the scaled data to original data. | |
| Args: | |
| X_scaled (np.array): The scaled data to inverse transform. | |
| Returns: | |
| np.array: The original data scale. | |
| """ | |
| X = (X_scaled - self.minmax_range[0]) / (self.minmax_range[1] - self.minmax_range[0]) | |
| X = X * self.scale_ + self.min_ | |
| return X | |
| # --- End of MinMaxScaler Class --- | |
| # --- Configuration --- | |
| MODEL_PATH = "best_model_TKAN_nahead_1 (2).keras" | |
| INPUT_SCALER_PATH = "input_scaler.pkl" # You need to save your X_scaler to this file | |
| # TARGET_SCALER_PATH = "target_scaler.pkl" # You might also need this if you predict scaled target | |
| SEQUENCE_LENGTH = 24 # Matches the notebook | |
| NUM_INPUT_FEATURES = 5 # ['calculated_aqi', 'temp', 'pm25', 'pm10', 'co'] | |
| N_AHEAD = 1 # Matches the notebook | |
| # --- Load Model and Scalers --- | |
| custom_objects = {"TKAN": TKAN} | |
| if TKAT is not None: | |
| custom_objects["TKAT"] = TKAT | |
| # Also add your custom MinMaxScaler to custom_objects for loading the scaler object | |
| custom_objects["MinMaxScaler"] = MinMaxScaler | |
| model = None | |
| input_scaler = None | |
| # target_scaler = None # Load if needed | |
| try: | |
| with custom_object_scope(custom_objects): | |
| model = load_model(MODEL_PATH) | |
| print("Model loaded successfully!") | |
| model.summary() | |
| except Exception as e: | |
| print(f"Error loading model from {MODEL_PATH}: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| import sys | |
| sys.exit("Failed to load the model. Exiting.") | |
| try: | |
| # When loading the scaler, you need custom_objects if it's your custom class | |
| with custom_object_scope(custom_objects): | |
| with open(INPUT_SCALER_PATH, 'rb') as f: | |
| input_scaler = pickle.load(f) | |
| print(f"Input scaler loaded successfully from {INPUT_SCALER_PATH}") | |
| # If you also scaled your target variable and need to inverse transform the prediction, | |
| # load the target scaler here as well. | |
| # with custom_object_scope(custom_objects): | |
| # with open(TARGET_SCALER_PATH, 'rb') as f: | |
| # target_scaler = pickle.load(f) | |
| # print(f"Target scaler loaded successfully from {TARGET_SCALER_PATH}") | |
| except FileNotFoundError as e: | |
| print(f"Error loading scaler: {e}. Make sure your scaler files are in the correct path.") | |
| import sys | |
| sys.exit("Failed to load scaler(s). Exiting.") | |
| except Exception as e: | |
| print(f"Error loading scaler: {e}") | |
| import traceback | |
| traceback.print_exc() | |
| import sys | |
| sys.exit("Failed to load scaler(s). Exiting.") | |
| # --- Data Preparation (get_latest_data_sequence needs implementation) --- | |
| def get_latest_data_sequence(sequence_length, num_features): | |
| """ | |
| Retrieves the latest sequence of data for the required features. | |
| This function needs to be implemented based on your data source in the | |
| deployment environment. It should return a numpy array with shape | |
| (sequence_length, num_features). | |
| Args: | |
| sequence_length (int): The length of the historical sequence required. | |
| num_features (int): The number of features in each time step. | |
| Returns: | |
| np.ndarray: A numpy array containing the historical data sequence. | |
| Shape: (sequence_length, num_features) | |
| """ | |
| print("WARNING: Using dummy data sequence. Implement get_latest_data_sequence.") | |
| # --- REPLACE THIS WITH YOUR ACTUAL DATA RETRIEVAL LOGIC --- | |
| # Example: Load from a database, fetch from an API, read from a file. | |
| # The data should be in the correct order (oldest to newest time step). | |
| # The columns should be in the order ['calculated_aqi', 'temp', 'pm25', 'pm10', 'co']. | |
| # For now, returning a placeholder with the correct shape. | |
| dummy_data = np.zeros((sequence_length, num_features)) | |
| # Populate dummy_data with some values for testing if you can load historical data | |
| # For example, load a small sample of your training data's X_test_unscaled | |
| # and use it here temporarily to verify the scaling and prediction pipeline. | |
| return dummy_data | |
| # --- END OF PLACEHOLDER --- | |
| # --- Define Predict Function --- | |
| def predict(): # Modify inputs as needed based on how you get data | |
| """ | |
| Retrieves the latest data sequence, preprocesses it, and makes a prediction. | |
| The Gradio interface will need to trigger this function. | |
| The input parameters to this function might change depending on how you | |
| provide the necessary historical data sequence via the Gradio interface. | |
| """ | |
| if model is None or input_scaler is None: | |
| return "Model or scaler not loaded. Check logs." | |
| # 1. Get the latest historical data sequence | |
| latest_data_sequence = get_latest_data_sequence(SEQUENCE_LENGTH, NUM_INPUT_FEATURES) | |
| # Ensure the retrieved data has the correct shape | |
| if latest_data_sequence.shape != (SEQUENCE_LENGTH, NUM_INPUT_FEATURES): | |
| return f"Error: Retrieved data has incorrect shape {latest_data_sequence.shape}. Expected ({SEQUENCE_LENGTH}, {NUM_INPUT_FEATURES})." | |
| # 2. Scale the data sequence using the loaded input scaler | |
| # Your MinMaxScaler from the notebook had feature_axis=2 for 3D data (samples, sequence, features). | |
| # So, for a single sequence (2D array), you'll likely need to add a batch dimension (1) before scaling. | |
| latest_data_sequence_with_batch = latest_data_sequence[np.newaxis, :, :] | |
| scaled_input_data = input_scaler.transform(latest_data_sequence_with_batch) | |
| # 3. Perform prediction | |
| # The model expects input shape (batch_size, sequence_length, num_features) | |
| output = model.predict(scaled_input_data) | |
| # 4. Process the output | |
| # The output shape is (batch_size, n_ahead). Since n_ahead=1, shape is (batch_size, 1). | |
| predicted_scaled_value = output[0][0] # Get the first prediction for the first sample | |
| # 5. Inverse transform the prediction if the target was scaled | |
| # If you scaled the target variable (calculated_aqi) before training, | |
| # you need to inverse transform the prediction back to the original scale. | |
| # This requires saving and loading the target_scaler as well and using it here. | |
| # Example if you need to inverse transform the target: | |
| # if target_scaler is not None: | |
| # predicted_original_scale = target_scaler.inverse_transform(np.array([[predicted_scaled_value]]))[0][0] | |
| # else: | |
| # predicted_original_scale = predicted_scaled_value | |
| # predicted_value = predicted_original_scale | |
| # For now, assuming the model outputs directly in the desired scale or | |
| # you handle inverse transformation elsewhere if needed. | |
| predicted_value = predicted_scaled_value # Adjust this if inverse transformation is needed | |
| return float(predicted_value) | |
| # --- Gradio Interface --- | |
| # The Gradio interface needs to allow the user to provide the necessary | |
| # historical data for the `predict` function. The current inputs (pm25, pm10, co, temp) | |
| # are NOT used by the predict function as written, since predict calls `get_latest_data_sequence`. | |
| # You need to decide how the user will provide the historical data. | |
| # Option 1: No direct inputs to `predict` function, it fetches data internally. | |
| # In this case, the Gradio interface might just have a button to trigger the prediction. | |
| interface = gr.Interface( | |
| fn=predict, | |
| inputs=None, # `predict` function doesn't take direct inputs from Gradio | |
| outputs=gr.Number(label=f"Predicted AQI (Next {N_AHEAD} Hour(s))") | |
| ) | |
| # Option 2: Modify `predict` to accept some parameters that help retrieve the data. | |
| # For example, if you pass the current time, and `get_latest_data_sequence` uses that | |
| # to find the last 24 hours ending at that time. | |
| # Option 3: Design a more complex Gradio interface to input the full sequence. | |
| # This is more complex but directly aligns with the model input. | |
| # Choose the Gradio interface definition that matches how your `predict` function | |
| # will receive the data it needs. Option 1 is shown below. | |
| # --- Launch Gradio Interface --- | |
| if __name__ == "__main__": | |
| interface.launch() |