{"slug": "inside-the-lstm-an-xai-field-guide-to-weather-prediction", "title": "Inside the LSTM: An XAI Field Guide to Weather Prediction", "summary": "A developer demonstrates how to train an LSTM on daily temperature data and apply three explainability methods—permutation importance, SHAP, and Integrated Gradients—to interpret the model's predictions. The tutorial covers data preparation, model architecture with stacked LSTM layers and dropout, and early stopping, then evaluates which lag days the model relies on most.", "body_md": "LSTMs are still the go-to architecture for a lot of time series work, but they're annoying to trust. You get a number out the other end and no real sense of why the model landed there. This tutorial walks through training an LSTM on daily temperature data, then pulling it apart with three explainability methods: permutation importance, SHAP, and Integrated Gradients.\n\n**Who this is for:** people who already know some Keras and want to add interpretability to a forecasting model, not a from-scratch intro to neural nets.\n\nLSTMs want a 3D tensor — `(samples, timesteps, features)`\n\n— so before anything else we need to turn a flat column of temperatures into overlapping 7-day windows, each one paired with the value on day 8.\n\n``` python\nimport numpy as np\nimport pandas as pd\nfrom sklearn.preprocessing import MinMaxScaler\n\n# 1. Load data\ndf = pd.read_csv(\"weather_data.csv\")\ndata = df['Temperature'].values.reshape(-1, 1)\n\n# 2. Scale the data for stable neural network training\nscaler = MinMaxScaler(feature_range=(0, 1))\nscaled_data = scaler.fit_transform(data)\n\n# 3. Create sequences: 7 days of lag to predict the 8th day\nX, y = [], []\nfor i in range(7, len(scaled_data)):\n    X.append(scaled_data[i-7:i])\n    y.append(scaled_data[i])\n\nX, y = np.array(X), np.array(y)\nprint(f\"Input shape: {X.shape}\") # Output: (Samples, 7, 1)\n```\n\nScaling matters more than it sounds like it should — LSTMs trained on unscaled temperature values are prone to exploding gradients, and training just falls apart. The windowing step is really the whole trick here: every prediction only ever sees the past seven days, nothing more.\n\nTwo stacked LSTM layers, dropout after each one, early stopping so we don't have to babysit the epoch count.\n\n``` python\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import LSTM, Dense, Dropout, Input\nfrom tensorflow.keras.callbacks import EarlyStopping\n\n# 1. Build the network\nmodel = Sequential([\n    Input(shape=(7, 1)),\n    LSTM(units=100, activation='relu', return_sequences=True),\n    Dropout(0.2),\n    LSTM(units=100, activation='relu'),\n    Dropout(0.2),\n    Dense(units=1)\n])\n\nmodel.compile(optimizer='adam', loss='mse')\n\n# 2. Configure Early Stopping\nearly_stopping = EarlyStopping(\n    monitor='val_loss', \n    patience=10, \n    restore_best_weights=True\n)\n\n# 3. Train the model\nhistory = model.fit(\n    X, y, \n    epochs=50, \n    batch_size=32, \n    validation_split=0.2, \n    callbacks=[early_stopping]\n)\n```\n\nOne detail worth flagging: `return_sequences=True`\n\non the first LSTM layer is required, not optional, if you're stacking a second one — it passes the full sequence of hidden states forward instead of just the final one. `patience=10`\n\ngives the model some room to wander before we give up on it, and `restore_best_weights=True`\n\nmeans we walk away with the checkpoint from its best epoch, not whatever it happened to end on.\n\nOnce training is done, the obvious question is: does the model lean on yesterday's temperature, or does it weight the whole week roughly evenly? Three different methods, three different angles on that question.\n\nThe idea is blunt but effective — shuffle one lag day across the whole dataset, see how much worse the predictions get, repeat for each of the 7 days.\n\n``` python\nfrom sklearn.metrics import mean_squared_error\n\nbase_preds = model.predict(X, verbose=0)\nbase_error = mean_squared_error(y, base_preds)\n\nfeature_importance = []\nfor i in range(7):\n    X_permuted = X.copy()\n    np.random.shuffle(X_permuted[:, i, 0]) # Shuffle a specific lag day\n\n    permuted_preds = model.predict(X_permuted, verbose=0)\n    permuted_error = mean_squared_error(y, permuted_preds)\n\n    feature_importance.append(permuted_error - base_error)\n```\n\nWhichever day causes the biggest jump in error when scrambled is the one the model depends on most. In practice, for a weather series, that's usually the most recent day or two — but it's worth checking rather than assuming.\n\nPermutation importance tells you what matters on average across the dataset. SHAP tells you what mattered for one specific prediction, which is a lot more useful when you're trying to explain a single forecast to someone. `KernelExplainer`\n\nworks reasonably well for LSTMs, though be warned it's slow — it's model-agnostic and treats the network as a black box, so it has to make a lot of prediction calls.\n\n``` python\nimport shap\n\n# 1. Reshape data for KernelExplainer (requires 2D input instead of 3D)\nbackground_data = X[:100].reshape(-1, 7)\ninstance_to_explain = X[0:1].reshape(-1, 7)\n\n# 2. Wrapper to handle 2D to 3D reshaping during SHAP calculation\ndef model_predict_wrapper(input_2d):\n    return model.predict(input_2d.reshape(-1, 7, 1), verbose=0)\n\n# 3. Compute SHAP Values\nexplainer = shap.KernelExplainer(model_predict_wrapper, background_data)\nshap_values = explainer.shap_values(instance_to_explain)\n\n# 4. Visualize\nshap.initjs()\nshap.force_plot(\n    explainer.expected_value[0], \n    shap_values[0].flatten(), \n    feature_names=[f'Day {i+1}' for i in range(7)],\n    features=instance_to_explain[0,:]\n)\n```\n\nThe force plot shows, day by day, whether each lag pushed that particular forecast above or below the baseline average. It's the kind of chart you can actually put in front of a non-technical stakeholder.\n\nThis one's gradient-based rather than perturbation-based, and it plays nicely with TensorFlow since it just needs access to the gradient tape. You interpolate a path from a baseline (usually all zeros) to the real input, and integrate the gradients along that path.\n\n``` python\nimport tensorflow as tf\n\ndef integrated_gradients(model, input_tensor, baseline=None, steps=50):\n    if baseline is None:\n        baseline = tf.zeros_like(input_tensor)\n\n    # 1. Interpolate from baseline to actual input\n    alphas = tf.linspace(0.0, 1.0, steps + 1)\n    interpolated_inputs = [baseline + alpha * (input_tensor - baseline) for alpha in alphas]\n    interpolated_inputs = tf.concat(interpolated_inputs, axis=0)\n\n    # 2. Calculate Gradients\n    @tf.function\n    def call_model(inputs):\n        return model(inputs, training=False)\n\n    with tf.GradientTape() as tape:\n        tape.watch(interpolated_inputs)\n        predictions = call_model(interpolated_inputs)\n\n    grads = tape.gradient(predictions, interpolated_inputs)\n\n    # 3. Approximate the integral\n    avg_grads = (grads[:-1] + grads[1:]) / 2.0\n    return tf.reduce_mean(avg_grads, axis=0) * (input_tensor - baseline)\n\nsample = tf.convert_to_tensor(X[0:1], dtype=tf.float32)\nig_attributions = integrated_gradients(model, sample)\n```\n\nThe `steps`\n\nparameter is a tradeoff — more steps gives a smoother, more accurate integral approximation, at the cost of more forward passes. 50 is a reasonable default; you rarely need to go much higher for a 7-day window.\n\nNone of these three methods is strictly \"correct\" — they answer slightly different questions. Permutation importance gives you the global picture, SHAP gives you a per-prediction breakdown that's easy to explain to someone else, and Integrated Gradients gives you a gradient-based view that's cheap to compute since it doesn't need to touch the model as a black box. Running all three and comparing where they agree (and where they don't) tends to be more informative than picking just one.", "url": "https://wpnews.pro/news/inside-the-lstm-an-xai-field-guide-to-weather-prediction", "canonical_source": "https://dev.to/meftamila/inside-the-lstm-an-xai-field-guide-to-weather-prediction-4d2p", "published_at": "2026-07-25 06:07:10+00:00", "updated_at": "2026-07-25 06:33:23.676149+00:00", "lang": "en", "topics": ["machine-learning", "neural-networks", "developer-tools"], "entities": ["Keras", "TensorFlow", "scikit-learn", "SHAP", "Integrated Gradients"], "alternates": {"html": "https://wpnews.pro/news/inside-the-lstm-an-xai-field-guide-to-weather-prediction", "markdown": "https://wpnews.pro/news/inside-the-lstm-an-xai-field-guide-to-weather-prediction.md", "text": "https://wpnews.pro/news/inside-the-lstm-an-xai-field-guide-to-weather-prediction.txt", "jsonld": "https://wpnews.pro/news/inside-the-lstm-an-xai-field-guide-to-weather-prediction.jsonld"}}