{"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.12.12"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceType":"competition","sourceId":129271,"databundleVersionId":15506307}],"dockerImageVersionId":31287,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## 1. Installation and Dependencies","metadata":{}},{"cell_type":"markdown","source":"# PLEASE RESTRART THE SESSION AFTER INSTALLING THE DEPENDIES TO AVOID IMPORT ERRORS","metadata":{}},{"cell_type":"markdown","source":"# Using P100 gpu","metadata":{}},{"cell_type":"code","source":"# Install required packages\n# CRITICAL: Install in this exact order with these exact versions\n\n# Step 1: Core dependencies first\n!pip install -q torch==2.5.1 torchvision==0.20.1 torchaudio==2.5.1 --index-url https://download.pytorch.org/whl/cu118\n\n# Step 2: PyAnnote ecosystem (latest compatible versions)\n!pip install -q pyannote.core==5.0.0\n!pip install -q pyannote.database==5.1.0\n!pip install -q pyannote.metrics==3.2.1\n!pip install -q pyannote.audio==3.3.2\n\n# Step 3: Lightning and other dependencies\n!pip install -q pytorch-lightning==2.4.0\n!pip install -q lightning==2.4.0\n!pip install -q tensorboard\n\n# Step 4: Audio processing\n!pip install -q pydub librosa soundfile\n!pip install torch-audiomentations==0.11.0\n\n# Step 5: Utilities\n!pip install -q pandas tqdm pyyaml\n\n!pip install \"numpy<2.0\"\n!pip install git+https://github.com/MahmoudAshraf97/demucs.git\n\nprint(\"All packages installed successfully!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 2. Imports and Configuration","metadata":{}},{"cell_type":"code","source":"import os\nimport json\nimport yaml\nimport pandas as pd\nimport numpy as np\nimport shutil\nfrom pathlib import Path\nfrom typing import Dict, List\nimport warnings\nwarnings.filterwarnings('ignore')\nprint(np.__version__)\n\n# PyTorch\nimport torch\nimport pytorch_lightning as pl\nfrom pytorch_lightning.callbacks import ModelCheckpoint, EarlyStopping\nfrom pytorch_lightning.loggers import TensorBoardLogger\n\n# PyAnnote\nfrom pyannote.audio import Model, Inference, Pipeline\nfrom pyannote.audio.tasks import SpeakerDiarization\nfrom pyannote.database import FileFinder, get_protocol, registry\nfrom pyannote.core import Annotation, Segment, Timeline\nfrom pyannote.metrics.diarization import DiarizationErrorRate\n\n# Audio\nfrom pydub import AudioSegment\nimport torchaudio\nfrom torch_audiomentations import Compose, Gain, AddBackgroundNoise\n\n# Utils\nfrom tqdm.auto import tqdm\nfrom sklearn.model_selection import train_test_split\n\n# uncomment ffor a100\ntorch.set_float32_matmul_precision('high')\ntorch.backends.cudnn.benchmark = True\nimport pytorch_lightning as pl\n\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(0)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T14:31:33.155164Z","iopub.execute_input":"2026-02-22T14:31:33.155501Z","iopub.status.idle":"2026-02-22T14:31:45.957404Z","shell.execute_reply.started":"2026-02-22T14:31:33.155472Z","shell.execute_reply":"2026-02-22T14:31:45.956773Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. Directories","metadata":{}},{"cell_type":"code","source":"# ==================== CONFIGURATION ====================\n\n# Hugging Face Token (required for pre-trained models)\nHF_TOKEN = \"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\"  # Replace with your token\n\n# Paths - UPDATE THESE FOR YOUR ENVIRONMENT\nTRAIN_AUDIO_DIR = \"/kaggle/input/competitions/dl-sprint-4-0-bengali-speaker-diarization-challenge/diarization/diarization/train/audio\"  # Directory with .wav files\nTRAIN_ANNOTATION_DIR = \"/kaggle/input/competitions/dl-sprint-4-0-bengali-speaker-diarization-challenge/diarization/diarization/train/annotation\"  # Directory with .csv/.rttm files\nTEST_AUDIO_DIR = \"/kaggle/input/competitions/dl-sprint-4-0-bengali-speaker-diarization-challenge/diarization/diarization/test/audio\"  # Test audio files\n\n# Output paths\nWORK_DIR = \"./work\"\nDATABASE_DIR = f\"{WORK_DIR}/database\"\nMODEL_DIR = f\"{WORK_DIR}/models\"\nCHECKPOINT_DIR = f\"{WORK_DIR}/checkpoints\"\nLOGS_DIR = f\"{WORK_DIR}/logs\"\nSUBMISSION_PATH = f\"{WORK_DIR}/submission.csv\"\n\n# Create directories\nfor d in [WORK_DIR, DATABASE_DIR, MODEL_DIR, CHECKPOINT_DIR, LOGS_DIR]:\n    os.makedirs(d, exist_ok=True)\n\n# ==================== AUDIO SETTINGS ====================\nSAMPLE_RATE = 16000  # Required by pyannote\nMONO = True\n\n# ==================== TRAINING SETTINGS ====================\n# Model configuration\nPRETRAINED_MODEL = \"ishmamzarif/bengali-diarization_aug_v1\"  # Best segmentation model\nPRETRAINED_EMBEDDING = \"pyannote/wespeaker-voxceleb-resnet34-LM\"  # Best embedding\n\n# Training hyperparameters\nBATCH_SIZE = 32  # Adjust based on GPU memory\nNUM_WORKERS = 4\nMAX_EPOCHS = 20\nLEARNING_RATE = 5e-5\nDURATION = 5.0  # seconds per training chunk\nMAX_SPEAKERS_PER_CHUNK = 3  # Maximum speakers in a chunk\n\n# Early stopping\nPATIENCE = 5\nMIN_DELTA = 0.001\n\n# Train/val split\nVAL_SPLIT = 0.20\n\n# Device\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(f\"✓ Configuration loaded\")\nprint(f\"✓ Device: {DEVICE}\")\nprint(f\"✓ Batch size: {BATCH_SIZE}\")\nprint(f\"✓ Max epochs: {MAX_EPOCHS}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T14:31:45.958771Z","iopub.execute_input":"2026-02-22T14:31:45.959499Z","iopub.status.idle":"2026-02-22T14:31:45.967534Z","shell.execute_reply.started":"2026-02-22T14:31:45.959471Z","shell.execute_reply":"2026-02-22T14:31:45.966719Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. Preprocessing audio","metadata":{}},{"cell_type":"code","source":"def preprocess_audio(input_path, output_path='./preprocessed'):\n    \"\"\"\n    Preprocess audio to 16kHz mono WAV.\n    \"\"\"\n    if output_path is None:\n        output_path = input_path\n    \n    try:\n        return_code = os.system(\n            f'python -m demucs.separate -n htdemucs --two-stems=vocals \"{input_path}\" -o \"temp_outputs\" --device \"{DEVICE}\"'\n        )\n    \n        if return_code != 0:\n            logging.warning(\"Source splitting failed, using original audio file.\")\n            vocal_target = input_path\n        else:\n            vocal_target = os.path.join(\n                \"temp_outputs\",\n                \"htdemucs\",\n                os.path.splitext(os.path.basename(input_path))[0],\n                \"vocals.wav\",\n            )\n\n            non_vocal_target = os.path.join(\n                \"/kaggle/working/temp_outputs\",\n                \"htdemucs\",\n                os.path.splitext(os.path.basename(input_path))[0],\n                \"no_vocals.wav\",\n            )\n            \n            # Use os.remove() to delete a file\n            os.remove(non_vocal_target)\n        audio = AudioSegment.from_wav(vocal_target)\n        \n        # Convert to mono\n        if audio.channels > 1:\n            audio = audio.set_channels(1)\n        \n        # Resample to 16kHz\n        if audio.frame_rate != SAMPLE_RATE:\n            audio = audio.set_frame_rate(SAMPLE_RATE)\n        \n        # Export\n        audio.export(output_path, format=\"wav\")\n        return output_path\n    \n    except Exception as e:\n        print(f\"Error preprocessing {input_path}: {e}\")\n        return None\n\nprint(\"✓ Data preparation functions loaded\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T14:31:45.968489Z","iopub.execute_input":"2026-02-22T14:31:45.968748Z","iopub.status.idle":"2026-02-22T14:31:45.986895Z","shell.execute_reply.started":"2026-02-22T14:31:45.968724Z","shell.execute_reply":"2026-02-22T14:31:45.986158Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference","metadata":{}},{"cell_type":"code","source":"import huggingface_hub\nimport pyannote.audio.core.model\n\n# 1. Save the original Hugging Face download function\noriginal_download = huggingface_hub.hf_hub_download\n\ndef patched_download(*args, **kwargs):\n    if 'use_auth_token' in kwargs:\n        kwargs['token'] = kwargs.pop('use_auth_token')\n    return original_download(*args, **kwargs)\n\n# 3. Overwrite the function inside pyannote with our patched version\npyannote.audio.core.model.hf_hub_download = patched_download\nprint(\" PyAnnote Monkey Patch Applied!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T14:31:45.988514Z","iopub.execute_input":"2026-02-22T14:31:45.988811Z","iopub.status.idle":"2026-02-22T14:31:46.002334Z","shell.execute_reply.started":"2026-02-22T14:31:45.988787Z","shell.execute_reply":"2026-02-22T14:31:46.001765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# OPTIMIZED INFERENCE CODE - FAST & GPU-ACCELERATED\n\nimport torch\nfrom pathlib import Path\nimport json\nfrom tqdm.auto import tqdm\nimport pandas as pd\n\ndef load_trained_pipeline_FAST(model_dir, device):\n    \"\"\"\n    Load trained pipeline with GPU optimization.\n    This version is 5-10x faster than the original.\n    \"\"\"\n    print(\"Loading model for GPU inference...\")\n    \n    # Load config\n    # config_path = Path(model_dir) / \"pipeline_config.json\"\n    # with open(config_path) as f:\n    #     config = json.load(f)\n    \n    # # Load the PRETRAINED model (not checkpoint) for faster loading\n    # model_path = Path(model_dir) / \"final_model.ckpt\"\n    \n    # if not model_path.exists():\n    #     # Fallback to checkpoint\n    #     print(\"⚠ Pretrained model not found, using checkpoint (slower)...\")\n    #     from pyannote.audio import Model\n    #     segmentation_model = Model.from_pretrained(\n    #         checkpoint_callback.best_model_path\n    #     )\n    # else:\n    from pyannote.audio import Model\n    segmentation_model = Model.from_pretrained(PRETRAINED_MODEL)\n    \n    # Move model to GPU immediately\n    segmentation_model = segmentation_model.to(device)\n    segmentation_model.eval()  # Set to evaluation mode\n    \n    print(f\"✓ Model loaded on {device}\")\n    \n    # Create pipeline with GPU support\n    from pyannote.audio.pipelines import SpeakerDiarization\n    \n    pipeline = SpeakerDiarization(\n        segmentation=segmentation_model,\n        embedding=PRETRAINED_EMBEDDING,\n        embedding_exclude_overlap=True,\n        use_auth_token=HF_TOKEN,  # CRITICAL: Must include auth token\n    )\n    \n    # Move entire pipeline to GPU\n    pipeline.to(device)\n    \n    # Instantiate with optimized parameters\n    optimized_params = {\n        \"segmentation\": {\n            \"threshold\": 0.5,\n            \"min_duration_off\": 0.0,  # No minimum gap\n        },\n        \"clustering\": {\n            \"method\": \"centroid\",\n            \"min_cluster_size\": 12,  # Minimum samples per cluster\n            \"threshold\": 0.7,  # Clustering threshold\n        },\n    }\n    \n    try:\n        pipeline.instantiate(optimized_params)\n        print(\"✓ Pipeline instantiated with optimized parameters\")\n    except Exception as e:\n        print(f\"Warning: {e}\")\n        # Fallback\n        pipeline.instantiate({\n            \"clustering\": {\n                \"method\": \"centroid\",\n                \"min_cluster_size\": 12,\n                \"threshold\": 0.7,\n            }\n        })\n        print(\"✓ Pipeline instantiated with fallback parameters\")\n    \n    return pipeline\n\n\ndef predict_test_set_FAST(pipeline, test_audio_dir, output_path, device):\n    \"\"\"\n    Fast GPU-accelerated inference with optimizations.\n    \"\"\"\n    audio_files = sorted(Path(test_audio_dir).glob(\"*.wav\"))\n    print(f\"Found {len(audio_files)} audio files\")\n    if len(audio_files) == 0:\n        raise ValueError(f\"No audio files found in {test_audio_dir}\")\n        \n    for audio_file in tqdm(audio_files, desc=\"Preprocessing data\"):\n        preprocess_audio(str(audio_file))\n    \n    \n    # 1. Define the base path\n    base_path = Path(\"/kaggle/working/temp_outputs/htdemucs\")\n    \n    # 2. Get the folders (assuming htdemucs created a folder per song)\n    au = sorted(base_path.glob(\"*\"))\n    \n    # 3. Change {} to [] because you are appending to a list\n    audio_files = [] \n    \n    for folder in au:\n        # 4. Construct the path directly using pathlib's '/' operator\n        # 'folder' is already a Path object, so we look for vocals.wav inside it\n        vocal_file = folder / \"vocals.wav\"\n        \n        # Optional: Check if the file actually exists before adding\n        if vocal_file.exists():\n            audio_files.append(vocal_file)\n    \n    print(f\"Found {len(audio_files)} audio files\")\n\n    # 5. Fix the quote syntax inside the f-string\n    if len(audio_files) == 0:\n        raise ValueError(f\"No audio files found in '{base_path}'\")\n\n\n    results = []\n    \n    # Use torch.no_grad() for faster inference\n    with torch.no_grad():\n        for audio_file in tqdm(audio_files, desc=\"🚀 Fast GPU Inference\"):\n            uri = audio_file.parent.name\n            \n            try:\n                # Run diarization on GPU\n                diarization = pipeline(str(audio_file))\n                \n                # Convert to submission format\n                segments = []\n                speaker_mapping = {}\n                next_id = 1\n                \n                for segment, _, speaker in diarization.itertracks(yield_label=True):\n                    # Map speakers to sequential IDs\n                    if speaker not in speaker_mapping:\n                        speaker_mapping[speaker] = next_id\n                        next_id += 1\n                    \n                    segments.append({\n                        \"start_time\": round(segment.start, 2),\n                        \"end_time\": round(segment.end, 2),\n                        \"speaker_id\": speaker_mapping[speaker]\n                    })\n                \n                results.append({\n                    \"filename\": uri,\n                    \"diarization\": json.dumps(segments)\n                })\n                \n            except Exception as e:\n                print(f\"\\n⚠ Error processing {uri}: {e}\")\n                # Add empty result to maintain file count\n                results.append({\n                    \"filename\": uri,\n                    \"diarization\": json.dumps([])\n                })\n    \n    # Create submission\n    df = pd.DataFrame(results)\n    # df.to_csv(output_path, index=False)\n    \n    return df\n\n\n# ============================================================\n# RUN OPTIMIZED INFERENCE\n# ============================================================\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"FAST GPU INFERENCE\")\nprint(\"=\"*60)\n\n# Ensure CUDA is available\nif not torch.cuda.is_available():\n    print(\"⚠ WARNING: CUDA not available, using CPU (will be slow)\")\n    DEVICE = torch.device(\"cpu\")\nelse:\n    DEVICE = torch.device(\"cuda\")\n    print(f\"✓ Using GPU: {torch.cuda.get_device_name(0)}\")\n    # Enable cuDNN autotuner for additional speed\n    torch.backends.cudnn.benchmark = True\n\n# Load pipeline with GPU optimization\npipeline = load_trained_pipeline_FAST(MODEL_DIR, DEVICE)\n\n# Run fast inference\nprint(\"\\n Running FAST inference on test set...\")\nprint(f\"Device: {DEVICE}\")\nprint(f\"Number of test files: {len(list(Path(TEST_AUDIO_DIR).glob('*.wav')))}\")\n\nsubmission_df = predict_test_set_FAST(pipeline, TEST_AUDIO_DIR, SUBMISSION_PATH, DEVICE)\n\n# print(f\"\\n✓ Submission created: {SUBMISSION_PATH}\")\nprint(f\"✓ Total files processed: {len(submission_df)}\")\nprint(\"\\nSubmission preview:\")\nprint(submission_df.head())\n\n# Clean up GPU memory\nif torch.cuda.is_available():\n    torch.cuda.empty_cache()\n    print(\"\\n✓ GPU memory cleared\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T14:31:46.003631Z","iopub.execute_input":"2026-02-22T14:31:46.003954Z","iopub.status.idle":"2026-02-22T15:50:19.23659Z","shell.execute_reply.started":"2026-02-22T14:31:46.003929Z","shell.execute_reply":"2026-02-22T15:50:19.235961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import json\nimport pandas as pd\n\ndef format_speaker_id(json_str):\n    \"\"\"\n    Parses a JSON string, updates speaker_id to 'SPEAKER_X' format,\n    and returns the modified JSON string.\n    \"\"\"\n    try:\n        # Parse the JSON string into a list of dictionaries\n        data = json.loads(json_str)\n        \n        if isinstance(data, list):\n            for entry in data:\n                # Check if speaker_id exists and update it\n                if 'speaker_id' in entry:\n                    entry['speaker_id'] = f\"SPEAKER_{entry['speaker_id']}\"\n            \n            # Convert the list back to a JSON string\n            return json.dumps(data)\n        return json_str\n    except Exception as e:\n        # In case of error (e.g. malformed JSON), return original string\n        return json_str\n\n# 1. Load the CSV file\n# df = pd.read_csv('/kaggle/working/work/submission.csv')\n\n# 2. Apply the formatting function to the 'diarization' column\nsubmission_df['diarization'] = submission_df['diarization'].apply(format_speaker_id)\n\n# 3. Save the modified DataFrame to a new CSV file\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"File processed and saved as 'submission.csv'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:50:19.237549Z","iopub.execute_input":"2026-02-22T15:50:19.237753Z","iopub.status.idle":"2026-02-22T15:50:19.273623Z","shell.execute_reply.started":"2026-02-22T15:50:19.23773Z","shell.execute_reply":"2026-02-22T15:50:19.272942Z"}},"outputs":[],"execution_count":null}]}