{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":102335,"databundleVersionId":12518947,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-08-10T22:46:51.883247Z","iopub.execute_input":"2025-08-10T22:46:51.883534Z","iopub.status.idle":"2025-08-10T22:46:54.242346Z","shell.execute_reply.started":"2025-08-10T22:46:51.883497Z","shell.execute_reply":"2025-08-10T22:46:54.241507Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Import necessary libraries\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport numpy as np\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.metrics import accuracy_score, classification_report, confusion_matrix, ConfusionMatrixDisplay\nimport matplotlib.pyplot as plt\n\n# --- 1. Mock Data Generation (for demonstration) ---\n# In a real scenario, you would load data from a file (e.g., CSV).\ndef generate_mock_data(num_samples=1000, sequence_length=50, num_sensors=5):\n    \"\"\"\n    Generates a mock dataset of time-series sensor data.\n    Simulates two distinct behaviors (labels 0 and 1).\n    \"\"\"\n    X = np.random.randn(num_samples, sequence_length, num_sensors)\n    y = np.zeros(num_samples, dtype=int)\n\n    # Introduce a pattern for a specific behavior (e.g., label 1)\n    for i in range(num_samples // 2, num_samples):\n        # Add a sine wave pattern to a few sensors for behavior 1\n        t = np.linspace(0, 2 * np.pi, sequence_length)\n        pattern = np.sin(t) * 2 + np.cos(t * 3)\n        # Add the pattern with some additional random noise to make the problem harder\n        X[i, :, 0] += pattern + np.random.randn(sequence_length) * 0.5\n        y[i] = 1\n\n    return X, y\n\n# --- 2. PyTorch Dataset and DataLoader ---\nclass SensorDataset(Dataset):\n    \"\"\"\n    Custom PyTorch Dataset for our sensor data.\n    \"\"\"\n    def __init__(self, features, labels):\n        # Convert numpy arrays to PyTorch tensors\n        self.features = torch.tensor(features, dtype=torch.float32)\n        self.labels = torch.tensor(labels, dtype=torch.long)\n\n    def __len__(self):\n        return len(self.labels)\n\n    def __getitem__(self, idx):\n        return self.features[idx], self.labels[idx]\n\n# --- 3. Neural Network Model Definition ---\nclass BehaviorDetector(nn.Module):\n    \"\"\"\n    A simple neural network for classifying time-series sensor data.\n    This uses a combination of a simple Recurrent Neural Network (RNN) layer\n    to process the sequence and a fully-connected layer for classification.\n    \"\"\"\n    def __init__(self, input_size, hidden_size, num_layers, num_classes, dropout_rate=0.5):\n        super().__init__()\n        self.hidden_size = hidden_size\n        self.num_layers = num_layers\n        \n        # RNN layer to process the time-series data\n        # batch_first=True means the input tensor has shape (batch, sequence, features)\n        self.rnn = nn.RNN(input_size, hidden_size, num_layers, batch_first=True)\n        \n        # Dropout layer to help prevent overfitting\n        self.dropout = nn.Dropout(dropout_rate)\n        \n        # Fully-connected layer for final classification\n        self.fc = nn.Linear(hidden_size, num_classes)\n\n    def forward(self, x):\n        # Initialize hidden state with zeros\n        h0 = torch.zeros(self.num_layers, x.size(0), self.hidden_size).to(x.device)\n        \n        # Pass the input through the RNN layer\n        out, _ = self.rnn(x, h0)\n        \n        # Apply dropout to the output of the last time step before the classifier\n        out = self.dropout(out[:, -1, :])\n        \n        # Get the output of the last time step for classification\n        out = self.fc(out)\n        return out\n\n# --- 4. Main Execution Block ---\nif __name__ == \"__main__\":\n    print(\"Generating mock sensor data...\")\n    X, y = generate_mock_data()\n    \n    # --- Data Visualization: Plotting mock data for different behaviors ---\n    # This helps us understand what the model needs to learn.\n    plt.figure(figsize=(12, 6))\n    \n    # Set the number of samples to visualize\n    num_samples_to_show = 5\n    \n    # Find indices of a few samples for each behavior\n    behavior_0_idx = np.where(y == 0)[0][:num_samples_to_show]\n    behavior_1_idx = np.where(y == 1)[0][:num_samples_to_show]\n\n    # Plot the first sensor's data for Behavior 0 samples\n    for i, idx in enumerate(behavior_0_idx):\n        plt.plot(X[idx, :, 0], label=f'Behavior 0 Sample {i+1}', linestyle='--')\n    \n    # Plot the first sensor's data for Behavior 1 samples\n    for i, idx in enumerate(behavior_1_idx):\n        plt.plot(X[idx, :, 0], label=f'Behavior 1 Sample {i+1}', linestyle='-')\n    \n    plt.title('Sample Sensor Data for Two Different Behaviors')\n    plt.xlabel('Time Step')\n    plt.ylabel('Sensor Value')\n    plt.legend()\n    plt.grid(True)\n    plt.show()\n\n    # Split data into training, validation, and testing sets\n    # We now split into 3 sets to monitor validation loss for early stopping.\n    X_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.4, random_state=42, stratify=y)\n    X_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp)\n    \n    # Scale the features\n    scaler = StandardScaler()\n    \n    X_train_reshaped = X_train.reshape(-1, X_train.shape[-1])\n    X_val_reshaped = X_val.reshape(-1, X_val.shape[-1])\n    X_test_reshaped = X_test.reshape(-1, X_test.shape[-1])\n    \n    X_train_scaled = scaler.fit_transform(X_train_reshaped).reshape(X_train.shape)\n    X_val_scaled = scaler.transform(X_val_reshaped).reshape(X_val.shape)\n    X_test_scaled = scaler.transform(X_test_reshaped).reshape(X_test.shape)\n    \n    # Create datasets and dataloaders\n    train_dataset = SensorDataset(X_train_scaled, y_train)\n    val_dataset = SensorDataset(X_val_scaled, y_val)\n    test_dataset = SensorDataset(X_test_scaled, y_test)\n\n    train_loader = DataLoader(dataset=train_dataset, batch_size=32, shuffle=True)\n    val_loader = DataLoader(dataset=val_dataset, batch_size=32, shuffle=False)\n    test_loader = DataLoader(dataset=test_dataset, batch_size=32, shuffle=False)\n\n    # Hyperparameters\n    input_size = X.shape[2]\n    hidden_size = 64\n    num_layers = 2\n    num_classes = 2 # Behavior 0 or Behavior 1\n    learning_rate = 0.001\n    num_epochs = 50 # Increased epochs to demonstrate early stopping\n    patience = 5 # Number of epochs to wait for improvement before stopping\n    \n    # Initialize a list to store loss for plotting\n    loss_history = []\n    val_loss_history = []\n    best_val_loss = float('inf')\n    epochs_no_improve = 0\n\n    # Initialize model, loss function, and optimizer\n    model = BehaviorDetector(input_size, hidden_size, num_layers, num_classes)\n    criterion = nn.CrossEntropyLoss()\n    optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\n\n    print(\"\\nStarting model training...\")\n    # Training loop with Early Stopping\n    for epoch in range(num_epochs):\n        model.train()\n        for i, (features, labels) in enumerate(train_loader):\n            # Forward pass\n            outputs = model(features)\n            loss = criterion(outputs, labels)\n            \n            # Backward and optimize\n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n\n        # Evaluate on the validation set after each epoch\n        model.eval()\n        with torch.no_grad():\n            val_loss = 0.0\n            for features, labels in val_loader:\n                outputs = model(features)\n                val_loss += criterion(outputs, labels).item()\n            val_loss /= len(val_loader)\n            \n        # Store the final losses for the epoch for visualization\n        loss_history.append(loss.item())\n        val_loss_history.append(val_loss)\n        \n        print(f'Epoch [{epoch+1}/{num_epochs}], Training Loss: {loss.item():.4f}, Validation Loss: {val_loss:.4f}')\n\n        # Early stopping logic\n        if val_loss < best_val_loss:\n            best_val_loss = val_loss\n            epochs_no_improve = 0\n            # You can save the best model here: torch.save(model.state_dict(), 'best_model.pth')\n        else:\n            epochs_no_improve += 1\n            if epochs_no_improve >= patience:\n                print(f\"Early stopping triggered after {patience} epochs without improvement.\")\n                break\n\n    print(\"\\nTraining complete. Evaluating model on test data...\")\n    # Evaluation loop\n    model.eval()\n    with torch.no_grad():\n        y_true = []\n        y_pred = []\n        for features, labels in test_loader:\n            outputs = model(features)\n            _, predicted = torch.max(outputs.data, 1)\n            \n            y_true.extend(labels.tolist())\n            y_pred.extend(predicted.tolist())\n\n    # Calculate and print final accuracy and classification report\n    final_accuracy = accuracy_score(y_true, y_pred)\n    print(f\"\\nFinal Test Accuracy: {final_accuracy:.4f}\")\n    print(\"\\nClassification Report:\")\n    print(classification_report(y_true, y_pred))\n\n    # --- Data Visualization: Plotting training and validation loss ---\n    plt.figure(figsize=(10, 6))\n    plt.plot(loss_history, marker='o', label='Training Loss')\n    plt.plot(val_loss_history, marker='o', label='Validation Loss')\n    plt.title('Training and Validation Loss over Epochs')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.grid(True)\n    plt.show()\n\n    # --- Data Visualization: Plotting the Confusion Matrix ---\n    # This shows the breakdown of correct and incorrect predictions for each class.\n    cm = confusion_matrix(y_true, y_pred)\n    disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=['Behavior 0', 'Behavior 1'])\n    disp.plot(cmap=plt.cm.Blues)\n    plt.title('Confusion Matrix')\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T22:46:54.243783Z","iopub.execute_input":"2025-08-10T22:46:54.244262Z","iopub.status.idle":"2025-08-10T22:47:07.452231Z","shell.execute_reply.started":"2025-08-10T22:46:54.244237Z","shell.execute_reply":"2025-08-10T22:47:07.451199Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}