{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":31254,"databundleVersionId":3103714,"sourceType":"competition"}],"dockerImageVersionId":30839,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\n\n# List all files under the input directory, but ignore image files\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        if filename.endswith('.csv'):\n            print(os.path.join(dirname, filename))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:29:39.044710Z","iopub.execute_input":"2025-02-16T03:29:39.045054Z","iopub.status.idle":"2025-02-16T03:34:03.612822Z","shell.execute_reply.started":"2025-02-16T03:29:39.045022Z","shell.execute_reply":"2025-02-16T03:34:03.612000Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 1. Environment Setup: Essential Libraries and Data Inspection\n## \"Importing foundational packages and examining available dataset files\"\n\n* Load numpy, pandas, PyTorch, and multiprocessing tools\n\n* List CSV files in the H&M dataset directory\n\n* Identify core data files: transactions, articles, customers, and sample submission","metadata":{}},{"cell_type":"code","source":"import time\nimport numpy as np\nimport pandas as pd\nimport torch\nfrom torch import nn\nfrom torch.utils.data import Dataset, DataLoader\n\nimport multiprocessing as mp\nfrom multiprocessing import Pool\nfrom functools import partial\nimport pandas as pd\nfrom pathlib import Path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:03.613602Z","iopub.execute_input":"2025-02-16T03:34:03.613998Z","iopub.status.idle":"2025-02-16T03:34:05.102032Z","shell.execute_reply.started":"2025-02-16T03:34:03.613969Z","shell.execute_reply":"2025-02-16T03:34:05.100906Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Define the dataset path\nbase_path = '../input/h-and-m-personalized-fashion-recommendations/'\ntransactions_path = f'{base_path}transactions_train.csv'\ncsv_sub = f'{base_path}sample_submission.csv'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.102835Z","iopub.execute_input":"2025-02-16T03:34:05.103279Z","iopub.status.idle":"2025-02-16T03:34:05.107578Z","shell.execute_reply.started":"2025-02-16T03:34:05.103250Z","shell.execute_reply":"2025-02-16T03:34:05.106430Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2. Data Filtering: Focusing on Recent Active Shoppers\n##  \"Isolating post-January 2020 transactions from frequent purchasers (40+ items)\"\n\n* Load 2.2M+ transactions from CSV\n\n* Filter to keep only transactions since 2020-03-02\n\n* Retain customers with ≥40 purchases to focus on engaged users","metadata":{}},{"cell_type":"code","source":"# Load and Clean Data\ndef load_and_filter_data(transactions_path, start_date=\"2020-01-01\", min_purchases=40):\n    \"\"\"\n    Load transaction data, filter users with at least `min_purchases` and start_date, \n    and sort the data by customer_id and transaction date.\n    \"\"\"\n    # Load data\n    transactions = pd.read_csv(\n        transactions_path,\n        dtype={\"article_id\": str, \"customer_id\": str},\n        parse_dates=[\"t_dat\"]\n    )\n    \n    # Filter transactions based on start date\n    transactions = transactions[transactions[\"t_dat\"] >= pd.Timestamp(start_date)]\n    \n    # Filter users with at least `min_purchases`\n    user_purchase_counts = transactions.groupby(\"customer_id\").size()\n    filtered_users = user_purchase_counts[user_purchase_counts >= min_purchases].index\n    filtered_data = transactions[transactions[\"customer_id\"].isin(filtered_users)]\n    \n    # Sort data by customer_id and transaction date\n    filtered_data = filtered_data.sort_values(by=[\"customer_id\", \"t_dat\"])\n    \n    return filtered_data\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.108646Z","iopub.execute_input":"2025-02-16T03:34:05.109005Z","iopub.status.idle":"2025-02-16T03:34:05.127840Z","shell.execute_reply.started":"2025-02-16T03:34:05.108937Z","shell.execute_reply":"2025-02-16T03:34:05.126570Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. Sequence Preparation: Building User Purchase Timelines\n## \"Creating chronological item sequences for each customer's purchase history\"\n\n* Group transactions by customer_id\n\n* Sort purchases chronologically per user\n\n* Convert histories to lists of article_id sequences\n\n* Remove users with insufficient purchase history","metadata":{}},{"cell_type":"code","source":"# Group and Preprocess Purchase Histories\ndef preprocess_purchase_histories(filtered_data):\n    \"\"\"\n    Group purchase histories by user and prepare sequences.\n    \"\"\"\n    # Group article_ids by customer_id (sorted by t_dat)\n    user_histories = filtered_data.groupby(\"customer_id\")[\"article_id\"].apply(list).tolist()\n    \n    # Ensure no empty histories\n    user_histories = [history for history in user_histories if len(history) > 1]\n    \n    return user_histories","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.129166Z","iopub.execute_input":"2025-02-16T03:34:05.129605Z","iopub.status.idle":"2025-02-16T03:34:05.146035Z","shell.execute_reply.started":"2025-02-16T03:34:05.129520Z","shell.execute_reply":"2025-02-16T03:34:05.145127Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. Sliding Window Processing: Creating Training Examples\n## \"Generating input-target pairs using 5-item context windows\"\n\n* Create overlapping sequences of length 5\n\n* Each input sequence predicts the immediately following item\n\n* Generate 2M+ training examples from user histories","metadata":{}},{"cell_type":"code","source":"# Generate Sequences\ndef generate_sequences(histories, sequence_length):\n    \"\"\"\n    Generate sliding window sequences from purchase histories.\n    \"\"\"\n    input_sequences, target_items = [], []\n    for history in histories:\n        if len(history) > sequence_length:  # Ensure the sequence can be formed\n            for i in range(len(history) - sequence_length):\n                input_sequences.append(history[i:i + sequence_length])\n                target_items.append(history[i + sequence_length])\n    return input_sequences, target_items","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.148449Z","iopub.execute_input":"2025-02-16T03:34:05.148799Z","iopub.status.idle":"2025-02-16T03:34:05.167575Z","shell.execute_reply.started":"2025-02-16T03:34:05.148765Z","shell.execute_reply":"2025-02-16T03:34:05.166660Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5. Item Encoding: Mapping Articles to Learnable Embeddings\n## \"Converting string IDs to numerical indices for model processing\"\n\n* Create unique article_id vocabulary (44,028 items)\n\n* Build bi-directional mapping dictionaries (ID ↔ index)\n\n* Convert all sequences and targets to encoded indices","metadata":{}},{"cell_type":"code","source":"# Encode Items to Indices\ndef encode_items(input_sequences, target_items, unique_items):\n    \"\"\"\n    Convert article_ids to indices using a mapping.\n    \"\"\"\n    item_to_idx = {item: idx for idx, item in enumerate(unique_items)}\n    idx_to_item = {idx: item for item, idx in item_to_idx.items()}\n    \n    # Convert sequences and targets to indices\n    input_sequences = [\n        [item_to_idx[item] for item in seq if item in item_to_idx]\n        for seq in input_sequences\n    ]\n    target_items = [item_to_idx[item] for item in target_items if item in item_to_idx]\n    \n    return input_sequences, target_items, item_to_idx, idx_to_item","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.168726Z","iopub.execute_input":"2025-02-16T03:34:05.169042Z","iopub.status.idle":"2025-02-16T03:34:05.185294Z","shell.execute_reply.started":"2025-02-16T03:34:05.169011Z","shell.execute_reply":"2025-02-16T03:34:05.184214Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6. Dataset Splitting: Separating Training and Validation Sets\n## \"80-20 split with randomization for model evaluation\"\n\n* Shuffle dataset indices\n\n* Allocate 20% of examples to validation set\n\n* Final counts: 1.63M training, 408K validation sequences","metadata":{}},{"cell_type":"code","source":"# Split Data\ndef split_data(input_sequences, target_items, validation_ratio=0.2):\n    \"\"\"\n    Split sequences and target items into training and validation sets.\n    \"\"\"\n    dataset_size = len(input_sequences)\n    validation_size = int(dataset_size * validation_ratio)\n\n    indices = np.arange(dataset_size)\n    np.random.shuffle(indices)  # Shuffle indices to ensure randomness\n\n    val_indices = indices[:validation_size]\n    train_indices = indices[validation_size:]\n\n    train_sequences = [input_sequences[i] for i in train_indices]\n    train_targets = [target_items[i] for i in train_indices]\n    val_sequences = [input_sequences[i] for i in val_indices]\n    val_targets = [target_items[i] for i in val_indices]\n\n    return train_sequences, train_targets, val_sequences, val_targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.186144Z","iopub.execute_input":"2025-02-16T03:34:05.186397Z","iopub.status.idle":"2025-02-16T03:34:05.202799Z","shell.execute_reply.started":"2025-02-16T03:34:05.186370Z","shell.execute_reply":"2025-02-16T03:34:05.201852Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create PyTorch Dataset\nclass PurchaseDataset(Dataset):\n    def __init__(self, sequences, targets):\n        self.sequences = sequences\n        self.targets = targets\n\n    def __len__(self):\n        return len(self.sequences)\n\n    def __getitem__(self, idx):\n        return {\n            'sequence': torch.tensor(self.sequences[idx], dtype=torch.long),\n            'target': torch.tensor(self.targets[idx], dtype=torch.long)\n        }\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.203603Z","iopub.execute_input":"2025-02-16T03:34:05.203811Z","iopub.status.idle":"2025-02-16T03:34:05.227220Z","shell.execute_reply.started":"2025-02-16T03:34:05.203790Z","shell.execute_reply":"2025-02-16T03:34:05.226128Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 7. Neural Architecture: LSTM-Based Recommendation Model\n## \"Embedding-LSTM-FC structure with 50D embeddings and 100D hidden states\"\n### Key components:\n\n* Embedding layer: Learns item representations\n\n* LSTM layer: Captures temporal patterns\n\n* Linear layer: Predicts next-item probabilities","metadata":{}},{"cell_type":"code","source":"# Define the Model\nclass LSTMRecommendationModel(nn.Module):\n    def __init__(self, num_items, embedding_dim, hidden_dim):\n        super(LSTMRecommendationModel, self).__init__()\n        self.embedding = nn.Embedding(num_items, embedding_dim)  # Embedding layer\n        self.lstm = nn.LSTM(embedding_dim, hidden_dim, batch_first=True)  # LSTM layer\n        self.fc = nn.Linear(hidden_dim, num_items)  # Fully connected layer to predict items\n\n    def forward(self, x):\n        embeddings = self.embedding(x)  # Convert items to embeddings\n        lstm_out, _ = self.lstm(embeddings)  # Process through LSTM\n        output = self.fc(lstm_out[:, -1, :])  # Use the last LSTM output for prediction\n        return output","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.227965Z","iopub.execute_input":"2025-02-16T03:34:05.228169Z","iopub.status.idle":"2025-02-16T03:34:05.247139Z","shell.execute_reply.started":"2025-02-16T03:34:05.228149Z","shell.execute_reply":"2025-02-16T03:34:05.246272Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 8. Training Configuration: Optimization Setup\n## \"Adam optimizer with gradient clipping and learning rate scheduling\"\n\n* Cross-entropy loss for multi-class classification\n\n* Initial learning rate: 0.005\n\n* Batch size: 128 sequences\n\n* Gradient clipping at 1.0 norm\n\n* LR reduction on validation plateau\n\n","metadata":{}},{"cell_type":"code","source":"# Predict Function\ndef predict_next_item(model, sequence, idx_to_item):\n    \"\"\"\n    Given a sequence of items, predict the next item.\n    \"\"\"\n    model.eval()\n    with torch.no_grad():\n        sequence = torch.tensor([sequence], dtype=torch.long)  # Add batch dimension\n        output = model(sequence)  # Get predictions\n        predicted_idx = output.argmax(dim=1).item()  # Index with highest probability\n        return idx_to_item[predicted_idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:34:05.247658Z","iopub.execute_input":"2025-02-16T03:34:05.247846Z","iopub.status.idle":"2025-02-16T03:34:05.267279Z","shell.execute_reply.started":"2025-02-16T03:34:05.247827Z","shell.execute_reply":"2025-02-16T03:34:05.266380Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 9. Model Training: Iterative Learning Process\n## \"10-epoch training with early stopping monitoring\"\n\n* Track batch-wise and epoch-level losses\n\n* Validation check after each epoch\n\n* Early stopping threshold: 0.5 validation loss\n\n* Progress reporting every 100 batches","metadata":{}},{"cell_type":"code","source":"# Paths and hyperparameters\ntransactions_path = f'{base_path}transactions_train.csv'\nsequence_length = 5\nbatch_size = 128\nembedding_dim = 50\nhidden_dim = 100\nepochs = 10\nvalidation_ratio = 0.2\nearly_stop_threshold = 0.5  # Stop training if validation loss drops below this value\nlearning_rate = 0.005\n\nprint(\"Step 1: Loading and preprocessing data...\")\n# Load and preprocess data\nfiltered_data = load_and_filter_data(\n    transactions_path,\n    start_date=\"2020-07-02\", \n    min_purchases=40\n)\nprint(f\"Filtered data contains {len(filtered_data)} transactions from {filtered_data['customer_id'].nunique()} users.\")\n\nuser_histories = preprocess_purchase_histories(filtered_data)\nprint(f\"Generated purchase histories for {len(user_histories)} users.\")\n\nprint(\"Step 2: Generating sequences...\")\ninput_sequences, target_items = generate_sequences(user_histories, sequence_length)\nprint(f\"Generated {len(input_sequences)} input sequences.\")\n\nprint(\"Step 3: Encoding items...\")\nunique_items = set(filtered_data[\"article_id\"])\ninput_sequences, target_items, item_to_idx, idx_to_item = encode_items(\n    input_sequences, target_items, unique_items\n)\nprint(f\"Encoded {len(unique_items)} unique items.\")\n\nprint(\"Step 4: Splitting data into training and validation sets...\")\ntrain_sequences, train_targets, val_sequences, val_targets = split_data(\n    input_sequences, target_items, validation_ratio\n)\nprint(f\"Training set: {len(train_sequences)} sequences, Validation set: {len(val_sequences)} sequences.\")\n\nprint(\"Step 5: Creating datasets and dataloaders...\")\ntrain_dataset = PurchaseDataset(train_sequences, train_targets)\nval_dataset = PurchaseDataset(val_sequences, val_targets)\n\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\nval_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)\n\nprint(\"Step 6: Initializing model, criterion, and optimizer...\")\nmodel = LSTMRecommendationModel(len(unique_items), embedding_dim, hidden_dim)\ncriterion = nn.CrossEntropyLoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, mode=\"min\", factor=0.5, patience=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:39:59.714850Z","iopub.execute_input":"2025-02-16T03:39:59.715169Z","iopub.status.idle":"2025-02-16T03:41:48.805966Z","shell.execute_reply.started":"2025-02-16T03:39:59.715144Z","shell.execute_reply":"2025-02-16T03:41:48.804677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Step 7: Starting training...\")\ntrain_losses = []\nval_losses = []\n\nfor epoch in range(epochs):\n    print(f\"\\nEpoch {epoch + 1}/{epochs}\")\n    model.train()\n    total_loss = 0\n\n    for batch_idx, batch in enumerate(train_loader):\n        batch_inputs = batch['sequence']\n        batch_targets = batch['target']\n\n        optimizer.zero_grad()\n        outputs = model(batch_inputs)\n        loss = criterion(outputs, batch_targets)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)  # Gradient clipping\n        optimizer.step()\n\n        total_loss += loss.item()\n\n        # Print progress for every 100 batches\n        if (batch_idx + 1) % 100 == 0:\n            print(f\"  Batch {batch_idx + 1}/{len(train_loader)}, Loss: {loss.item():.4f}\")\n\n    avg_train_loss = total_loss / len(train_loader)\n    train_losses.append(avg_train_loss)\n    print(f\"Epoch {epoch + 1} Training Loss: {avg_train_loss:.4f}\")\n\n    # Validation phase\n    model.eval()\n    val_loss = 0\n    with torch.no_grad():\n        for batch in val_loader:\n            batch_inputs = batch['sequence']\n            batch_targets = batch['target']\n\n            outputs = model(batch_inputs)\n            loss = criterion(outputs, batch_targets)\n            val_loss += loss.item()\n\n    avg_val_loss = val_loss / len(val_loader)\n    val_losses.append(avg_val_loss)\n    print(f\"Epoch {epoch + 1} Validation Loss: {avg_val_loss:.4f}\")\n\n    # Reduce learning rate if no improvement\n    scheduler.step(avg_val_loss)\n\n    # Early stopping\n    if avg_val_loss < early_stop_threshold:\n        print(f\"Early stopping triggered. Validation loss {avg_val_loss:.4f} is below the threshold {early_stop_threshold}.\")\n        break\n\nprint(\"Training complete.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T03:47:55.344358Z","iopub.execute_input":"2025-02-16T03:47:55.344745Z","iopub.status.idle":"2025-02-16T03:58:38.497866Z","shell.execute_reply.started":"2025-02-16T03:47:55.344708Z","shell.execute_reply":"2025-02-16T03:58:38.497139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Plot training and validation loss\nplt.figure(figsize=(10, 6))\nplt.plot(train_losses, label='Training Loss', color='blue')\nplt.plot(val_losses, label='Validation Loss', color='orange')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.title('Training and Validation Loss Curve')\nplt.legend()\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T04:01:05.192767Z","iopub.execute_input":"2025-02-16T04:01:05.193089Z","iopub.status.idle":"2025-02-16T04:01:05.466918Z","shell.execute_reply.started":"2025-02-16T04:01:05.193067Z","shell.execute_reply":"2025-02-16T04:01:05.466005Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 10. Recommendation Generation: Personalized Top-K Predictions\n## \"Leveraging trained LSTM to suggest the next likely purchases\"\n\nInput: User's recent purchase sequence (last 5 items)\n\nProcess: Model predicts item probabilities, selects top-k highest scores\n\nOutput: List of recommended article IDs tailored to user history\n\nThis function encapsulates the end goal—using the trained model to generate actionable recommendations. It ensures the input sequence is trimmed to the model's expected length, computes predictions, and maps indices back to meaningful article IDs for interpretability.","metadata":{}},{"cell_type":"code","source":"def recommend_items(model, user_sequence, idx_to_item, top_k=5):\n    \"\"\"\n    Recommend top-k items based on the user's purchase sequence.\n    \n    Parameters:\n    - model: Trained LSTM model\n    - user_sequence: List of item indices representing the user's recent history\n    - idx_to_item: Dictionary mapping item indices back to article IDs\n    - top_k: Number of recommendations to generate\n    \n    Returns:\n    - List of recommended article IDs\n    \"\"\"\n    model.eval()\n    with torch.no_grad():\n        # Convert the user sequence to a tensor and ensure correct length\n        input_seq = torch.tensor([user_sequence[-sequence_length:]], dtype=torch.long)\n        \n        # Get the model's predictions\n        output = model(input_seq)\n        \n        # Get the top-k predictions\n        top_indices = torch.topk(output, top_k).indices.squeeze().tolist()\n        \n        # Convert indices back to article IDs\n        recommended_items = [idx_to_item[idx] for idx in top_indices]\n    \n    return recommended_items\n\n# Example usage\nuser_history = train_sequences[0]  # Take one user's purchase history as an example\nrecommended_items = recommend_items(model, user_history, idx_to_item, top_k=5)\n\nprint(\"Recommended items:\", recommended_items)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-16T04:04:04.218085Z","iopub.execute_input":"2025-02-16T04:04:04.218437Z","iopub.status.idle":"2025-02-16T04:04:04.235774Z","shell.execute_reply.started":"2025-02-16T04:04:04.218407Z","shell.execute_reply":"2025-02-16T04:04:04.234737Z"}},"outputs":[],"execution_count":null}]}