{"metadata":{"kernelspec":{"display_name":"Python 3","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.11.13"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":96164,"databundleVersionId":12993472,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":6749.135215,"end_time":"2025-07-21T12:55:40.405899","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2025-07-21T11:03:11.270684","version":"2.6.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"00b3a949","cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","execution":{"iopub.status.busy":"2025-07-23T06:30:34.111659Z","iopub.execute_input":"2025-07-23T06:30:34.112007Z"},"papermill":{"duration":2.203991,"end_time":"2025-07-21T11:03:18.919017","exception":false,"start_time":"2025-07-21T11:03:16.715026","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"63b802ac","cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, TensorDataset\nimport torch.nn.functional as F\nfrom sklearn.preprocessing import StandardScaler, RobustScaler\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom scipy.stats import pearsonr\nimport gc\nimport warnings\nwarnings.filterwarnings('ignore')","metadata":{"papermill":{"duration":9.84988,"end_time":"2025-07-21T11:03:28.774249","exception":false,"start_time":"2025-07-21T11:03:18.924369","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"ed69cb7d","cell_type":"code","source":"np.random.seed(42)\ntorch.manual_seed(42)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed(42)\n\nprint(\"=== GPU-Optimized LSTM for Crypto Price Prediction ===\")\nprint(f\"PyTorch version: {torch.__version__}\")\nprint(f\"CUDA available: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    print(f\"GPU: {torch.cuda.get_device_name()}\")\n    print(f\"GPU Memory: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\")","metadata":{"papermill":{"duration":5.504146,"end_time":"2025-07-21T11:03:34.287112","exception":false,"start_time":"2025-07-21T11:03:28.782966","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"61f0d9b5","cell_type":"code","source":"import gc\ndef clear_memory():\n        gc.collect()\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n            torch.cuda.synchronize()","metadata":{"papermill":{"duration":0.013902,"end_time":"2025-07-21T11:03:34.307342","exception":false,"start_time":"2025-07-21T11:03:34.29344","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"id":"abe8eda3","cell_type":"markdown","source":"# ============================================================================\n# 1. DATA LOADING AND INITIAL EXPLORATION\n# ============================================================================","metadata":{"papermill":{"duration":0.004966,"end_time":"2025-07-21T11:03:34.317879","exception":false,"start_time":"2025-07-21T11:03:34.312913","status":"completed"},"tags":[]}},{"id":"f41b8140-db24-43a5-8d32-f47a66cf0a23","cell_type":"code","source":"class MemoryEfficientDataLoader:\n    \"\"\"Memory-efficient data loading with chunked processing\"\"\"\n    \n    def __init__(self, chunk_size=10000):\n        self.chunk_size = chunk_size\n        self.scaler = StandardScaler()\n        \n    def load_and_preprocess_data(self):\n        \"\"\"Load data with memory optimization\"\"\"\n        print(\"\\n1. Loading data with memory optimization...\")\n        \n        # Load data\n        train_df = pd.read_parquet('/kaggle/input/drw-crypto-market-prediction/train.parquet')\n        test_df = pd.read_parquet('/kaggle/input/drw-crypto-market-prediction/test.parquet')\n\n        start_date = \"2023-11-01 00:00:00\"\n        end_date = \"2024-02-29 23:59:00\"\n\n        # Filter the DataFrame and update train_df with the subset\n        train_df = train_df.loc[start_date:end_date]\n        \n        print(f\"Training data shape: {train_df.shape}\")\n        print(f\"Test data shape: {test_df.shape}\")\n        \n        # Get feature columns\n        feature_cols = [col for col in train_df.columns if col not in ['timestamp', 'label']]\n        print(f\"Number of features: {len(feature_cols)}\")\n        \n        # Extract features and target\n        X = train_df[feature_cols].values.astype(np.float32)  # Use float32 for memory efficiency\n        y = train_df['label'].values.astype(np.float32)\n        X_test = test_df[feature_cols].values.astype(np.float32)\n        \n        # Handle missing values efficiently\n        X = np.nan_to_num(X, nan=0.0, posinf=0.0, neginf=0.0)\n        X_test = np.nan_to_num(X_test, nan=0.0, posinf=0.0, neginf=0.0)\n        \n        # Clean up memory\n        del train_df, test_df\n        gc.collect()\n        \n        return X, y, X_test, feature_cols\n    \n    def create_sequences_efficient(self, data, target, seq_length=60, stride=1):\n        \"\"\"Create sequences with memory optimization\"\"\"\n        print(f\"Creating sequences with length {seq_length}, stride {stride}...\")\n        \n        sequences = []\n        targets = []\n        \n        # Process in chunks to avoid memory overflow\n        for start_idx in range(0, len(data) - seq_length, self.chunk_size):\n            end_idx = min(start_idx + self.chunk_size, len(data) - seq_length)\n            \n            chunk_sequences = []\n            chunk_targets = []\n            \n            for i in range(start_idx, end_idx, stride):\n                seq = data[i:i+seq_length]\n                tgt = target[i+seq_length]\n                chunk_sequences.append(seq)\n                chunk_targets.append(tgt)\n            \n            if chunk_sequences:\n                sequences.extend(chunk_sequences)\n                targets.extend(chunk_targets)\n            \n            # Clear chunk data to free memory\n            del chunk_sequences, chunk_targets\n            \n            if (start_idx // self.chunk_size + 1) % 5 == 0:\n                print(f\"Processed {start_idx + self.chunk_size} samples...\")\n                gc.collect()\n        \n        sequences = np.array(sequences, dtype=np.float32)\n        targets = np.array(targets, dtype=np.float32)\n        \n        print(f\"Created {len(sequences)} sequences with shape {sequences.shape}\")\n        return sequences, targets","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"92751e73","cell_type":"markdown","source":"# ============================================================================\n# 4. MODEL DEFINITIONS\n# ============================================================================","metadata":{"papermill":{"duration":0.004603,"end_time":"2025-07-21T11:03:34.513237","exception":false,"start_time":"2025-07-21T11:03:34.508634","status":"completed"},"tags":[]}},{"id":"8aadb10c-6995-4b4f-8de0-e34dc117c26c","cell_type":"code","source":"class AdvancedLSTM(nn.Module):\n    \"\"\"Advanced LSTM with attention mechanism and regularization\"\"\"\n    \n    def __init__(self, input_size, hidden_size=128, num_layers=3, dropout=0.3, \n                 use_attention=True, use_residual=True):\n        super(AdvancedLSTM, self).__init__()\n        \n        self.input_size = input_size\n        self.hidden_size = hidden_size\n        self.num_layers = num_layers\n        self.use_attention = use_attention\n        self.use_residual = use_residual\n        \n        # Input projection to reduce dimensionality\n        self.input_projection = nn.Linear(input_size, hidden_size)\n        self.input_dropout = nn.Dropout(dropout)\n        \n        # LSTM layers with batch normalization\n        self.lstm = nn.LSTM(\n            hidden_size, hidden_size, num_layers,\n            batch_first=True, dropout=dropout if num_layers > 1 else 0,\n            bidirectional=False\n        )\n        \n        # Batch normalization for LSTM output\n        self.lstm_bn = nn.BatchNorm1d(hidden_size)\n        \n        # Attention mechanism\n        if self.use_attention:\n            self.attention = nn.MultiheadAttention(\n                embed_dim=hidden_size,\n                num_heads=8,\n                dropout=dropout,\n                batch_first=True\n            )\n            self.attention_norm = nn.LayerNorm(hidden_size)\n        \n        # Output layers with residual connections\n        self.fc_layers = nn.ModuleList([\n            nn.Linear(hidden_size, hidden_size // 2),\n            nn.Linear(hidden_size // 2, hidden_size // 4),\n            nn.Linear(hidden_size // 4, 1)\n        ])\n        \n        self.dropout_layers = nn.ModuleList([\n            nn.Dropout(dropout),\n            nn.Dropout(dropout * 0.5),\n            nn.Dropout(dropout * 0.25)\n        ])\n        \n        self.batch_norms = nn.ModuleList([\n            nn.BatchNorm1d(hidden_size // 2),\n            nn.BatchNorm1d(hidden_size // 4)\n        ])\n        \n        # Initialize weights\n        self._init_weights()\n    \n    def _init_weights(self):\n        \"\"\"Initialize weights using Xavier initialization\"\"\"\n        for name, param in self.named_parameters():\n            if 'weight_ih' in name:\n                nn.init.xavier_uniform_(param.data)\n            elif 'weight_hh' in name:\n                nn.init.orthogonal_(param.data)\n            elif 'bias' in name:\n                param.data.fill_(0)\n    \n    def forward(self, x):\n        batch_size, seq_len, _ = x.shape\n        \n        # Input projection\n        x = self.input_projection(x)\n        x = self.input_dropout(x)\n        \n        # LSTM forward pass\n        lstm_out, (hidden, cell) = self.lstm(x)\n        \n        # Apply batch normalization to the last timestep\n        last_output = lstm_out[:, -1, :]  # Shape: (batch_size, hidden_size)\n        last_output = self.lstm_bn(last_output)\n        \n        # Attention mechanism\n        if self.use_attention:\n            # Self-attention on LSTM outputs\n            attn_out, _ = self.attention(lstm_out, lstm_out, lstm_out)\n            attn_out = self.attention_norm(attn_out + lstm_out)  # Residual connection\n            \n            # Global average pooling with attention weights\n            attention_weights = torch.softmax(\n                torch.sum(attn_out * lstm_out, dim=-1), dim=1\n            ).unsqueeze(-1)\n            attended_output = torch.sum(attn_out * attention_weights, dim=1)\n            \n            # Combine LSTM output and attended output\n            combined_output = last_output + attended_output\n        else:\n            combined_output = last_output\n        \n        # Forward through FC layers with residual connections\n        x = combined_output\n        residual = x\n        \n        for i, (fc, dropout, bn) in enumerate(zip(self.fc_layers[:-1], \n                                                 self.dropout_layers[:-1], \n                                                 self.batch_norms)):\n            x = fc(x)\n            x = bn(x)\n            x = F.relu(x)\n            x = dropout(x)\n            \n            # Residual connection for compatible dimensions\n            if self.use_residual and i == 0 and x.shape[-1] == residual.shape[-1]:\n                x = x + residual\n        \n        # Final output layer\n        x = self.dropout_layers[-1](x)\n        x = self.fc_layers[-1](x)\n        \n        return x.squeeze(-1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"9c73a7f2-37b0-4e0c-9ed0-98c32a1c08da","cell_type":"markdown","source":"# ============================================================================\n# 3. MODEL TRAINING\n# ============================================================================","metadata":{}},{"id":"518f7342-8df8-4018-a99e-bcc92a1a3939","cell_type":"code","source":"class GPUTrainer:\n    \"\"\"GPU-optimized trainer with memory management\"\"\"\n    \n    def __init__(self, model, device, learning_rate=0.001, weight_decay=1e-5):\n        self.model = model.to(device)\n        self.device = device\n        self.criterion = nn.MSELoss()\n        self.optimizer = optim.AdamW(\n            model.parameters(), \n            lr=learning_rate, \n            weight_decay=weight_decay,\n            eps=1e-8\n        )\n        self.scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n            self.optimizer, mode='min', factor=0.5, patience=5, verbose=True\n        )\n        \n        # For tracking metrics\n        self.train_losses = []\n        self.val_losses = []\n        self.val_correlations = []\n        \n    def train_epoch(self, dataloader):\n        \"\"\"Train for one epoch\"\"\"\n        self.model.train()\n        total_loss = 0\n        num_batches = 0\n        \n        for batch_idx, (batch_x, batch_y) in enumerate(dataloader):\n            # Move data to GPU\n            batch_x = batch_x.to(self.device, non_blocking=True)\n            batch_y = batch_y.to(self.device, non_blocking=True)\n            \n            # Forward pass\n            self.optimizer.zero_grad()\n            outputs = self.model(batch_x)\n            loss = self.criterion(outputs, batch_y)\n            \n            # Backward pass\n            loss.backward()\n            \n            # Gradient clipping to prevent exploding gradients\n            torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=1.0)\n            \n            self.optimizer.step()\n            \n            total_loss += loss.item()\n            num_batches += 1\n            \n            # Clear cache every 50 batches to prevent memory overflow\n            if batch_idx % 50 == 0:\n                torch.cuda.empty_cache()\n                if batch_idx % 100 == 0:\n                    print(f\"Batch {batch_idx}/{len(dataloader)}, Loss: {loss.item():.6f}\")\n        \n        return total_loss / num_batches\n    \n    def validate(self, dataloader):\n        \"\"\"Validate the model\"\"\"\n        self.model.eval()\n        total_loss = 0\n        predictions = []\n        actuals = []\n        \n        with torch.no_grad():\n            for batch_x, batch_y in dataloader:\n                batch_x = batch_x.to(self.device, non_blocking=True)\n                batch_y = batch_y.to(self.device, non_blocking=True)\n                \n                outputs = self.model(batch_x)\n                loss = self.criterion(outputs, batch_y)\n                \n                total_loss += loss.item()\n                predictions.extend(outputs.cpu().numpy())\n                actuals.extend(batch_y.cpu().numpy())\n        \n        # Calculate correlation\n        correlation = pearsonr(actuals, predictions)[0] if len(predictions) > 1 else 0\n        avg_loss = total_loss / len(dataloader)\n        \n        return avg_loss, correlation, predictions, actuals\n    \n    def train(self, train_loader, val_loader, epochs=100, early_stopping_patience=10):\n        \"\"\"Full training loop with early stopping\"\"\"\n        print(f\"\\nStarting training for {epochs} epochs...\")\n        \n        best_val_loss = float('inf')\n        patience_counter = 0\n        \n        for epoch in range(epochs):\n            print(f\"\\nEpoch {epoch + 1}/{epochs}\")\n            \n            # Training\n            train_loss = self.train_epoch(train_loader)\n            self.train_losses.append(train_loss)\n            \n            # Validation\n            val_loss, val_corr, _, _ = self.validate(val_loader)\n            self.val_losses.append(val_loss)\n            self.val_correlations.append(val_corr)\n            \n            # Learning rate scheduling\n            self.scheduler.step(val_loss)\n            \n            print(f\"Train Loss: {train_loss:.6f}, Val Loss: {val_loss:.6f}, Val Corr: {val_corr:.4f}\")\n            \n            # Early stopping\n            if val_loss < best_val_loss:\n                best_val_loss = val_loss\n                patience_counter = 0\n                # Save best model\n                torch.save(self.model.state_dict(), 'best_lstm_model.pth')\n            else:\n                patience_counter += 1\n                if patience_counter >= early_stopping_patience:\n                    print(f\"Early stopping after {epoch + 1} epochs\")\n                    break\n            \n            # Clear cache\n            torch.cuda.empty_cache()\n        \n        # Load best model\n        self.model.load_state_dict(torch.load('best_lstm_model.pth'))\n        print(\"Training completed!\")\n        \n        return self.train_losses, self.val_losses, self.val_correlations","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"dd397898","cell_type":"markdown","source":"# ============================================================================\n# 3. MAIN EXECUTION PIPELINE\n# ============================================================================","metadata":{"papermill":{"duration":0.00458,"end_time":"2025-07-21T11:03:34.464204","exception":false,"start_time":"2025-07-21T11:03:34.459624","status":"completed"},"tags":[]}},{"id":"874b8665-06f2-4c04-95ff-ee027813ef9e","cell_type":"code","source":"def main():\n    \"\"\"Main execution pipeline optimized for GPU\"\"\"\n    \n    # Check GPU availability\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"Using device: {device}\")\n    \n    # Memory-efficient data loading\n    data_loader = MemoryEfficientDataLoader(chunk_size=5000)\n    X, y, X_test, feature_cols = data_loader.load_and_preprocess_data()\n    \n    print(f\"Feature matrix shape: {X.shape}\")\n    print(f\"Target vector shape: {y.shape}\")\n    print(f\"Test matrix shape: {X_test.shape}\")\n    \n    # Feature scaling\n    print(\"\\nScaling features...\")\n    scaler = RobustScaler()\n    X_scaled = scaler.fit_transform(X).astype(np.float32)\n    X_test_scaled = scaler.transform(X_test).astype(np.float32)\n    \n    # Create sequences for LSTM\n    seq_length = 60  # Use 60 timesteps for sequence\n    stride = 2  # Use stride of 2 to reduce memory usage\n    \n    X_sequences, y_sequences = data_loader.create_sequences_efficient(\n        X_scaled, y, seq_length=seq_length, stride=stride\n    )\n    \n    print(f\"Sequence shape: {X_sequences.shape}\")\n    print(f\"Target shape: {y_sequences.shape}\")\n    \n    # Split data for training and validation\n    train_size = int(0.8 * len(X_sequences))\n    X_train = X_sequences[:train_size]\n    y_train = y_sequences[:train_size]\n    X_val = X_sequences[train_size:]\n    y_val = y_sequences[train_size:]\n    \n    print(f\"Training sequences: {X_train.shape}\")\n    print(f\"Validation sequences: {X_val.shape}\")\n    \n    # Clear intermediate variables to free memory\n    del X_sequences, y_sequences, X_scaled\n    gc.collect()\n    \n    # Create data loaders with optimal batch size for GPU\n    batch_size = 32  # Optimized for Tesla T4\n    \n    train_dataset = TensorDataset(\n        torch.FloatTensor(X_train), \n        torch.FloatTensor(y_train)\n    )\n    val_dataset = TensorDataset(\n        torch.FloatTensor(X_val), \n        torch.FloatTensor(y_val)\n    )\n    \n    train_loader = DataLoader(\n        train_dataset, \n        batch_size=batch_size, \n        shuffle=True, \n        num_workers=2,\n        pin_memory=True,\n        drop_last=True\n    )\n    val_loader = DataLoader(\n        val_dataset, \n        batch_size=batch_size, \n        shuffle=False, \n        num_workers=2,\n        pin_memory=True,\n        drop_last=False\n    )\n    \n    # Initialize model\n    input_size = X_train.shape[2]  # Number of features\n    model = AdvancedLSTM(\n        input_size=input_size,\n        hidden_size=128,\n        num_layers=3,\n        dropout=0.3,\n        use_attention=True,\n        use_residual=True\n    )\n    \n    print(f\"\\nModel architecture:\")\n    print(f\"Input size: {input_size}\")\n    print(f\"Total parameters: {sum(p.numel() for p in model.parameters()):,}\")\n    print(f\"Trainable parameters: {sum(p.numel() for p in model.parameters() if p.requires_grad):,}\")\n    \n    # Initialize trainer\n    trainer = GPUTrainer(model, device, learning_rate=0.001, weight_decay=1e-5)\n    \n    # Train model\n    train_losses, val_losses, val_correlations = trainer.train(\n        train_loader, val_loader, epochs=100, early_stopping_patience=15\n    )\n    \n    # Generate predictions for test set\n    print(\"\\nGenerating test predictions...\")\n    \n    # Create sequences for test data\n    X_test_sequences = []\n    for i in range(seq_length, len(X_test_scaled)):\n        X_test_sequences.append(X_test_scaled[i-seq_length:i])\n    \n    X_test_sequences = np.array(X_test_sequences, dtype=np.float32)\n    \n    # Predict in batches to avoid memory issues\n    test_predictions = []\n    model.eval()\n    \n    with torch.no_grad():\n        for i in range(0, len(X_test_sequences), batch_size):\n            batch_end = min(i + batch_size, len(X_test_sequences))\n            batch_data = torch.FloatTensor(X_test_sequences[i:batch_end]).to(device)\n            \n            batch_pred = model(batch_data)\n            test_predictions.extend(batch_pred.cpu().numpy())\n            \n            if i % (batch_size * 10) == 0:\n                print(f\"Predicted {i + len(batch_pred)} / {len(X_test_sequences)} samples\")\n    \n    # Handle the first seq_length predictions (use last prediction)\n    full_predictions = np.full(len(X_test_scaled), test_predictions[-1] if test_predictions else 0.0)\n    full_predictions[seq_length:seq_length + len(test_predictions)] = test_predictions\n    \n    # Create submission file\n    submission = pd.DataFrame({\n        'ID': range(len(full_predictions)),\n        'label': full_predictions\n    })\n    \n    submission.to_csv('/kaggle/working/lstm_submission.csv', index=False)\n    \n    print(f\"\\nSubmission created: lstm_submission.csv\")\n    print(f\"Prediction statistics:\")\n    print(f\"Mean: {np.mean(full_predictions):.6f}\")\n    print(f\"Std: {np.std(full_predictions):.6f}\")\n    print(f\"Min: {np.min(full_predictions):.6f}\")\n    print(f\"Max: {np.max(full_predictions):.6f}\")\n    \n    # Plot training history\n    plt.figure(figsize=(15, 5))\n    \n    plt.subplot(1, 3, 1)\n    plt.plot(train_losses, label='Train Loss')\n    plt.plot(val_losses, label='Val Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.title('Training History - Loss')\n    plt.legend()\n    \n    plt.subplot(1, 3, 2)\n    plt.plot(val_correlations, label='Val Correlation')\n    plt.xlabel('Epoch')\n    plt.ylabel('Correlation')\n    plt.title('Validation Correlation')\n    plt.legend()\n    \n    plt.subplot(1, 3, 3)\n    plt.hist(full_predictions, bins=50, alpha=0.7)\n    plt.xlabel('Prediction Value')\n    plt.ylabel('Frequency')\n    plt.title('Prediction Distribution')\n    \n    plt.tight_layout()\n    plt.savefig('training_analysis.png', dpi=300, bbox_inches='tight')\n    plt.show()\n    \n    print(\"\\nTraining completed successfully!\")\n    print(f\"Best validation correlation: {max(val_correlations):.4f}\")\n    \n    return model, submission, (train_losses, val_losses, val_correlations)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"98c6c663-cdf1-4f00-a18a-733c04bced1e","cell_type":"code","source":"if __name__ == \"__main__\":\n    # Set memory optimization for PyTorch\n    torch.backends.cudnn.benchmark = True\n    torch.backends.cudnn.deterministic = False\n    \n    # Run main pipeline\n    model, submission, training_history = main()\n    \n    print(\"\\n\" + \"=\"*60)\n    print(\"GPU-Optimized LSTM Training Completed Successfully!\")\n    print(\"=\"*60)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}