diff --git a/bilstm_sequential.ipynb b/bilstm_sequential.ipynb
new file mode 100644
index 0000000..c00a06c
--- /dev/null
+++ b/bilstm_sequential.ipynb
@@ -0,0 +1,906 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "21ce2b41",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "\n",
+ "from sklearn.model_selection import train_test_split\n",
+ "from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score\n",
+ "\n",
+ "import tensorflow as tf\n",
+ "from tensorflow.keras.models import Sequential\n",
+ "from tensorflow.keras.layers import (\n",
+ " LSTM,\n",
+ " Bidirectional,\n",
+ " Dense,\n",
+ " Dropout,\n",
+ " Masking\n",
+ ")\n",
+ "\n",
+ "from tensorflow.keras.callbacks import (\n",
+ " EarlyStopping,\n",
+ " ReduceLROnPlateau\n",
+ ")\n",
+ "\n",
+ "import matplotlib.pyplot as plt"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "7b5e2a73",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " match_id | \n",
+ " delivery_number | \n",
+ " legal_ball | \n",
+ " over_ball | \n",
+ " score | \n",
+ " wickets | \n",
+ " target_score | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 0 | \n",
+ " 1001349 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 0.1 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 168 | \n",
+ "
\n",
+ " \n",
+ " | 1 | \n",
+ " 1001349 | \n",
+ " 2 | \n",
+ " 2 | \n",
+ " 0.2 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 168 | \n",
+ "
\n",
+ " \n",
+ " | 2 | \n",
+ " 1001349 | \n",
+ " 3 | \n",
+ " 3 | \n",
+ " 0.3 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ " 168 | \n",
+ "
\n",
+ " \n",
+ " | 3 | \n",
+ " 1001349 | \n",
+ " 4 | \n",
+ " 4 | \n",
+ " 0.4 | \n",
+ " 3 | \n",
+ " 0 | \n",
+ " 168 | \n",
+ "
\n",
+ " \n",
+ " | 4 | \n",
+ " 1001349 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " 0.5 | \n",
+ " 3 | \n",
+ " 0 | \n",
+ " 168 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " match_id delivery_number legal_ball over_ball score wickets \\\n",
+ "0 1001349 1 1 0.1 0 0 \n",
+ "1 1001349 2 2 0.2 0 0 \n",
+ "2 1001349 3 3 0.3 1 0 \n",
+ "3 1001349 4 4 0.4 3 0 \n",
+ "4 1001349 5 5 0.5 3 0 \n",
+ "\n",
+ " target_score \n",
+ "0 168 \n",
+ "1 168 \n",
+ "2 168 \n",
+ "3 168 \n",
+ "4 168 "
+ ]
+ },
+ "execution_count": 2,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df = pd.read_csv(\n",
+ " \"data/run_prediction_dataset_raw.csv\"\n",
+ ")\n",
+ "\n",
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "4b258bec",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " match_id | \n",
+ " delivery_number | \n",
+ " legal_ball | \n",
+ " over_ball | \n",
+ " score | \n",
+ " wickets | \n",
+ " target_score | \n",
+ " runs_this_ball | \n",
+ " previous_score | \n",
+ " last_6_runs | \n",
+ " last_12_runs | \n",
+ " boundaries | \n",
+ " last_12_boundaries | \n",
+ " dots | \n",
+ " last_12_dots | \n",
+ " balls_remaining | \n",
+ " run_rate | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " | 514915 | \n",
+ " 211028 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ " 0.1 | \n",
+ " 0 | \n",
+ " 0 | \n",
+ " 179 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " 1 | \n",
+ " 0.0 | \n",
+ " 119 | \n",
+ " 0.0 | \n",
+ "
\n",
+ " \n",
+ " | 514916 | \n",
+ " 211028 | \n",
+ " 2 | \n",
+ " 2 | \n",
+ " 0.2 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ " 179 | \n",
+ " 1.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " 118 | \n",
+ " 3.0 | \n",
+ "
\n",
+ " \n",
+ " | 514917 | \n",
+ " 211028 | \n",
+ " 3 | \n",
+ " 3 | \n",
+ " 0.3 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ " 179 | \n",
+ " 0.0 | \n",
+ " 1.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " 1 | \n",
+ " 0.0 | \n",
+ " 117 | \n",
+ " 2.0 | \n",
+ "
\n",
+ " \n",
+ " | 514918 | \n",
+ " 211028 | \n",
+ " 4 | \n",
+ " 4 | \n",
+ " 0.4 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ " 179 | \n",
+ " 0.0 | \n",
+ " 1.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " 1 | \n",
+ " 0.0 | \n",
+ " 116 | \n",
+ " 1.5 | \n",
+ "
\n",
+ " \n",
+ " | 514919 | \n",
+ " 211028 | \n",
+ " 5 | \n",
+ " 5 | \n",
+ " 0.5 | \n",
+ " 1 | \n",
+ " 0 | \n",
+ " 179 | \n",
+ " 0.0 | \n",
+ " 1.0 | \n",
+ " 0.0 | \n",
+ " 0.0 | \n",
+ " 0 | \n",
+ " 0.0 | \n",
+ " 1 | \n",
+ " 0.0 | \n",
+ " 115 | \n",
+ " 1.2 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " match_id delivery_number legal_ball over_ball score wickets \\\n",
+ "514915 211028 1 1 0.1 0 0 \n",
+ "514916 211028 2 2 0.2 1 0 \n",
+ "514917 211028 3 3 0.3 1 0 \n",
+ "514918 211028 4 4 0.4 1 0 \n",
+ "514919 211028 5 5 0.5 1 0 \n",
+ "\n",
+ " target_score runs_this_ball previous_score last_6_runs \\\n",
+ "514915 179 0.0 0.0 0.0 \n",
+ "514916 179 1.0 0.0 0.0 \n",
+ "514917 179 0.0 1.0 0.0 \n",
+ "514918 179 0.0 1.0 0.0 \n",
+ "514919 179 0.0 1.0 0.0 \n",
+ "\n",
+ " last_12_runs boundaries last_12_boundaries dots last_12_dots \\\n",
+ "514915 0.0 0 0.0 1 0.0 \n",
+ "514916 0.0 0 0.0 0 0.0 \n",
+ "514917 0.0 0 0.0 1 0.0 \n",
+ "514918 0.0 0 0.0 1 0.0 \n",
+ "514919 0.0 0 0.0 1 0.0 \n",
+ "\n",
+ " balls_remaining run_rate \n",
+ "514915 119 0.0 \n",
+ "514916 118 3.0 \n",
+ "514917 117 2.0 \n",
+ "514918 116 1.5 \n",
+ "514919 115 1.2 "
+ ]
+ },
+ "execution_count": 3,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "df = df.sort_values(\n",
+ " [\n",
+ " \"match_id\",\n",
+ " \"delivery_number\"\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "\n",
+ "# runs scored this ball\n",
+ "df[\"runs_this_ball\"] = (\n",
+ " df.groupby(\"match_id\")[\"score\"]\n",
+ " .diff()\n",
+ " .fillna(\n",
+ " df[\"score\"]\n",
+ " )\n",
+ ")\n",
+ "\n",
+ "\n",
+ "# previous score\n",
+ "df[\"previous_score\"] = (\n",
+ " df[\"score\"]\n",
+ " -\n",
+ " df[\"runs_this_ball\"]\n",
+ ")\n",
+ "\n",
+ "\n",
+ "# rolling scoring form\n",
+ "\n",
+ "df[\"last_6_runs\"] = (\n",
+ " df.groupby(\"match_id\")\n",
+ " [\"runs_this_ball\"]\n",
+ " .rolling(6)\n",
+ " .sum()\n",
+ " .reset_index(level=0,drop=True)\n",
+ " .fillna(0)\n",
+ ")\n",
+ "\n",
+ "\n",
+ "df[\"last_12_runs\"] = (\n",
+ " df.groupby(\"match_id\")\n",
+ " [\"runs_this_ball\"]\n",
+ " .rolling(12)\n",
+ " .sum()\n",
+ " .reset_index(level=0,drop=True)\n",
+ " .fillna(0)\n",
+ ")\n",
+ "\n",
+ "\n",
+ "df[\"boundaries\"] = (\n",
+ " df[\"runs_this_ball\"] >= 4\n",
+ ").astype(int)\n",
+ "\n",
+ "\n",
+ "df[\"last_12_boundaries\"] = (\n",
+ " df.groupby(\"match_id\")\n",
+ " [\"boundaries\"]\n",
+ " .rolling(12)\n",
+ " .sum()\n",
+ " .reset_index(level=0,drop=True)\n",
+ " .fillna(0)\n",
+ ")\n",
+ "\n",
+ "\n",
+ "df[\"dots\"] = (\n",
+ " df[\"runs_this_ball\"] == 0\n",
+ ").astype(int)\n",
+ "\n",
+ "\n",
+ "df[\"last_12_dots\"] = (\n",
+ " df.groupby(\"match_id\")\n",
+ " [\"dots\"]\n",
+ " .rolling(12)\n",
+ " .sum()\n",
+ " .reset_index(level=0,drop=True)\n",
+ " .fillna(0)\n",
+ ")\n",
+ "\n",
+ "# Existing features above...\n",
+ "\n",
+ "\n",
+ "# New features\n",
+ "df[\"balls_remaining\"] = 120 - df[\"legal_ball\"]\n",
+ "\n",
+ "df[\"run_rate\"] = (\n",
+ " df[\"score\"] / (df[\"legal_ball\"] / 6)\n",
+ ").replace(\n",
+ " [float(\"inf\"), -float(\"inf\")],\n",
+ " 0\n",
+ ").fillna(0)\n",
+ "\n",
+ "df.head()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "1fc0e0b9",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "(440081, 17)\n"
+ ]
+ }
+ ],
+ "source": [
+ "df = df[\n",
+ " df[\"legal_ball\"] >= 30\n",
+ "]\n",
+ "\n",
+ "\n",
+ "print(\n",
+ " df.shape\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 5,
+ "id": "370d0cfb",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "FEATURES = [\n",
+ " \"score\",\n",
+ " \"wickets\",\n",
+ " \"legal_ball\",\n",
+ " \"runs_this_ball\",\n",
+ " \"last_6_runs\",\n",
+ " \"last_12_runs\",\n",
+ " \"last_12_boundaries\",\n",
+ " \"last_12_dots\",\n",
+ " \"run_rate\",\n",
+ " \"balls_remaining\"\n",
+ "]\n",
+ "\n",
+ "\n"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 6,
+ "id": "d43f7fc0",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Training matches: 3686\n",
+ "Testing matches : 922\n",
+ "Training rows: 352215\n",
+ "Testing rows : 87866\n"
+ ]
+ }
+ ],
+ "source": [
+ "from sklearn.model_selection import train_test_split\n",
+ "\n",
+ "\n",
+ "# ==========================================\n",
+ "# Split by MATCH, not by sequence\n",
+ "# ==========================================\n",
+ "\n",
+ "all_matches = df[\"match_id\"].unique()\n",
+ "\n",
+ "\n",
+ "train_matches, test_matches = train_test_split(\n",
+ " all_matches,\n",
+ " test_size=0.2,\n",
+ " random_state=42\n",
+ ")\n",
+ "\n",
+ "\n",
+ "train_df = df[\n",
+ " df[\"match_id\"].isin(train_matches)\n",
+ "].copy()\n",
+ "\n",
+ "\n",
+ "test_df = df[\n",
+ " df[\"match_id\"].isin(test_matches)\n",
+ "].copy()\n",
+ "\n",
+ "\n",
+ "print(\"Training matches:\", len(train_matches))\n",
+ "print(\"Testing matches :\", len(test_matches))\n",
+ "\n",
+ "print(\"Training rows:\", len(train_df))\n",
+ "print(\"Testing rows :\", len(test_df))"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 7,
+ "id": "55e46374",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "0\n"
+ ]
+ }
+ ],
+ "source": [
+ "print(\n",
+ " len(\n",
+ " set(train_matches)\n",
+ " &\n",
+ " set(test_matches)\n",
+ " )\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 8,
+ "id": "66f4ae4e",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Train sequences: 241635\n",
+ "Test sequences: 60206\n"
+ ]
+ }
+ ],
+ "source": [
+ "def create_sequences(data):\n",
+ "\n",
+ " X = []\n",
+ " y = []\n",
+ "\n",
+ " for match_id, innings in data.groupby(\"match_id\"):\n",
+ "\n",
+ " innings = innings.sort_values(\n",
+ " \"legal_ball\"\n",
+ " )\n",
+ "\n",
+ " sequence = innings[FEATURES].values\n",
+ "\n",
+ " target = innings[\"target_score\"].iloc[0]\n",
+ "\n",
+ "\n",
+ " for i in range(30, len(sequence)):\n",
+ "\n",
+ " X.append(\n",
+ " sequence[:i+1]\n",
+ " )\n",
+ "\n",
+ " y.append(\n",
+ " target\n",
+ " )\n",
+ "\n",
+ " return X, y\n",
+ "\n",
+ "\n",
+ "\n",
+ "X_train_sequences, y_train_targets = create_sequences(train_df)\n",
+ "\n",
+ "X_test_sequences, y_test_targets = create_sequences(test_df)\n",
+ "\n",
+ "\n",
+ "print(\n",
+ " \"Train sequences:\",\n",
+ " len(X_train_sequences)\n",
+ ")\n",
+ "\n",
+ "print(\n",
+ " \"Test sequences:\",\n",
+ " len(X_test_sequences)\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 9,
+ "id": "1aa07812",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Train: (241635, 120, 10) (241635,)\n",
+ "Test : (60206, 120, 10) (60206,)\n"
+ ]
+ }
+ ],
+ "source": [
+ "from tensorflow.keras.preprocessing.sequence import pad_sequences\n",
+ "import numpy as np\n",
+ "\n",
+ "\n",
+ "# X_train_sequences and X_test_sequences\n",
+ "# should already be created from separate match splits\n",
+ "\n",
+ "X_train = pad_sequences(\n",
+ " X_train_sequences,\n",
+ " maxlen=120,\n",
+ " dtype=\"float32\",\n",
+ " padding=\"pre\"\n",
+ ")\n",
+ "\n",
+ "X_test = pad_sequences(\n",
+ " X_test_sequences,\n",
+ " maxlen=120,\n",
+ " dtype=\"float32\",\n",
+ " padding=\"pre\"\n",
+ ")\n",
+ "\n",
+ "\n",
+ "y_train = np.array(y_train_targets)\n",
+ "y_test = np.array(y_test_targets)\n",
+ "\n",
+ "\n",
+ "print(\"Train:\", X_train.shape, y_train.shape)\n",
+ "print(\"Test :\", X_test.shape, y_test.shape)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 10,
+ "id": "e470e33f",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stderr",
+ "output_type": "stream",
+ "text": [
+ "/Users/keshav/code/cricket-dls/.venv/lib/python3.12/site-packages/keras/src/layers/core/masking.py:48: UserWarning: Do not pass an `input_shape`/`input_dim` argument to a layer. When using Sequential models, prefer using an `Input(shape)` object as the first layer in the model instead.\n",
+ " super().__init__(**kwargs)\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ "Model: \"sequential\"\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1mModel: \"sequential\"\u001b[0m\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+ "┃ Layer (type) ┃ Output Shape ┃ Param # ┃\n",
+ "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+ "│ masking (Masking) │ (None, 120, 10) │ 0 │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ bidirectional (Bidirectional) │ (None, 64) │ 11,008 │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dropout (Dropout) │ (None, 64) │ 0 │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense (Dense) │ (None, 32) │ 2,080 │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense_1 (Dense) │ (None, 1) │ 33 │\n",
+ "└─────────────────────────────────┴────────────────────────┴───────────────┘\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓\n",
+ "┃\u001b[1m \u001b[0m\u001b[1mLayer (type) \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1mOutput Shape \u001b[0m\u001b[1m \u001b[0m┃\u001b[1m \u001b[0m\u001b[1m Param #\u001b[0m\u001b[1m \u001b[0m┃\n",
+ "┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩\n",
+ "│ masking (\u001b[38;5;33mMasking\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m120\u001b[0m, \u001b[38;5;34m10\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ bidirectional (\u001b[38;5;33mBidirectional\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m) │ \u001b[38;5;34m11,008\u001b[0m │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dropout (\u001b[38;5;33mDropout\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m64\u001b[0m) │ \u001b[38;5;34m0\u001b[0m │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m32\u001b[0m) │ \u001b[38;5;34m2,080\u001b[0m │\n",
+ "├─────────────────────────────────┼────────────────────────┼───────────────┤\n",
+ "│ dense_1 (\u001b[38;5;33mDense\u001b[0m) │ (\u001b[38;5;45mNone\u001b[0m, \u001b[38;5;34m1\u001b[0m) │ \u001b[38;5;34m33\u001b[0m │\n",
+ "└─────────────────────────────────┴────────────────────────┴───────────────┘\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Total params: 13,121 (51.25 KB)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Total params: \u001b[0m\u001b[38;5;34m13,121\u001b[0m (51.25 KB)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Trainable params: 13,121 (51.25 KB)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Trainable params: \u001b[0m\u001b[38;5;34m13,121\u001b[0m (51.25 KB)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ " Non-trainable params: 0 (0.00 B)\n",
+ "
\n"
+ ],
+ "text/plain": [
+ "\u001b[1m Non-trainable params: \u001b[0m\u001b[38;5;34m0\u001b[0m (0.00 B)\n"
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "model = Sequential([\n",
+ "\n",
+ " Masking(\n",
+ " mask_value=0.,\n",
+ " input_shape=(120,len(FEATURES))\n",
+ " ),\n",
+ "\n",
+ " Bidirectional(\n",
+ " LSTM(32)\n",
+ " ),\n",
+ "\n",
+ " Dropout(0.25),\n",
+ "\n",
+ " Dense(\n",
+ " 32,\n",
+ " activation=\"relu\"\n",
+ " ),\n",
+ "\n",
+ " Dense(1)\n",
+ "\n",
+ "])\n",
+ "\n",
+ "model.compile(\n",
+ " optimizer=tf.keras.optimizers.Adam(\n",
+ " learning_rate=0.001\n",
+ " ),\n",
+ "\n",
+ " loss=\"mse\",\n",
+ "\n",
+ " metrics=[\n",
+ " \"mae\"\n",
+ " ]\n",
+ ")\n",
+ "\n",
+ "\n",
+ "model.summary()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "495daca8",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Epoch 1/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m32s\u001b[0m 254ms/step - loss: 19128.5391 - mae: 131.3299 - val_loss: 12421.8320 - val_mae: 103.6737 - learning_rate: 0.0010\n",
+ "Epoch 2/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m38s\u001b[0m 321ms/step - loss: 6210.3130 - mae: 65.5333 - val_loss: 1969.1512 - val_mae: 33.6969 - learning_rate: 0.0010\n",
+ "Epoch 3/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m38s\u001b[0m 321ms/step - loss: 1224.2498 - mae: 24.7959 - val_loss: 525.3225 - val_mae: 15.5029 - learning_rate: 0.0010\n",
+ "Epoch 4/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m38s\u001b[0m 322ms/step - loss: 610.5830 - mae: 17.7209 - val_loss: 320.0849 - val_mae: 12.2769 - learning_rate: 0.0010\n",
+ "Epoch 5/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m38s\u001b[0m 322ms/step - loss: 483.1981 - mae: 16.2305 - val_loss: 249.6277 - val_mae: 11.1048 - learning_rate: 0.0010\n",
+ "Epoch 6/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m41s\u001b[0m 345ms/step - loss: 434.6412 - mae: 15.6104 - val_loss: 226.9275 - val_mae: 10.6671 - learning_rate: 0.0010\n",
+ "Epoch 7/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m40s\u001b[0m 341ms/step - loss: 414.1877 - mae: 15.3427 - val_loss: 211.9790 - val_mae: 10.3182 - learning_rate: 0.0010\n",
+ "Epoch 8/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m39s\u001b[0m 331ms/step - loss: 395.6093 - mae: 15.0844 - val_loss: 207.4465 - val_mae: 10.3751 - learning_rate: 0.0010\n",
+ "Epoch 9/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m44s\u001b[0m 376ms/step - loss: 385.9764 - mae: 14.9372 - val_loss: 198.0012 - val_mae: 10.2073 - learning_rate: 0.0010\n",
+ "Epoch 10/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m47s\u001b[0m 395ms/step - loss: 374.2676 - mae: 14.7318 - val_loss: 194.2088 - val_mae: 10.1279 - learning_rate: 0.0010\n",
+ "Epoch 11/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m46s\u001b[0m 392ms/step - loss: 367.1200 - mae: 14.5911 - val_loss: 188.0659 - val_mae: 9.9135 - learning_rate: 0.0010\n",
+ "Epoch 12/100\n",
+ "\u001b[1m118/118\u001b[0m \u001b[32m━━━━━━━━━━━━━━━━━━━━\u001b[0m\u001b[37m\u001b[0m \u001b[1m49s\u001b[0m 412ms/step - loss: 362.3481 - mae: 14.5265 - val_loss: 188.1493 - val_mae: 9.9198 - learning_rate: 0.0010\n",
+ "Epoch 13/100\n",
+ "\u001b[1m 27/118\u001b[0m \u001b[32m━━━━\u001b[0m\u001b[37m━━━━━━━━━━━━━━━━\u001b[0m \u001b[1m41s\u001b[0m 451ms/step - loss: 357.6455 - mae: 14.4617"
+ ]
+ }
+ ],
+ "source": [
+ "callbacks = [\n",
+ "\n",
+ " EarlyStopping(\n",
+ " patience=10,\n",
+ " restore_best_weights=True\n",
+ " ),\n",
+ "\n",
+ " ReduceLROnPlateau(\n",
+ " patience=5,\n",
+ " factor=0.5\n",
+ " )\n",
+ "\n",
+ "]\n",
+ "\n",
+ "\n",
+ "history = model.fit(\n",
+ "\n",
+ " X_train,\n",
+ " y_train,\n",
+ "\n",
+ " validation_data=(\n",
+ " X_test,\n",
+ " y_test\n",
+ " ),\n",
+ "\n",
+ " epochs=100,\n",
+ "\n",
+ " batch_size=2048,\n",
+ "\n",
+ " callbacks=callbacks,\n",
+ "\n",
+ " verbose=1\n",
+ ")"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": ".venv (3.14.6)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.13"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}