{"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","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\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:46:43.013933Z","iopub.execute_input":"2026-02-22T14:46:43.014527Z","iopub.status.idle":"2026-02-22T14:46:43.022774Z","shell.execute_reply.started":"2026-02-22T14:46:43.014491Z","shell.execute_reply":"2026-02-22T14:46:43.02183Z"}},"outputs":[],"execution_count":null},{"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 = \"zarifmahir21/bengali-speaker-diarization_v2\"  # 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:46:43.026418Z","iopub.execute_input":"2026-02-22T14:46:43.026728Z","iopub.status.idle":"2026-02-22T14:46:43.056038Z","shell.execute_reply.started":"2026-02-22T14:46:43.026701Z","shell.execute_reply":"2026-02-22T14:46:43.055211Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 3. Data Preparation Functions","metadata":{}},{"cell_type":"code","source":"def convert_timestamp_to_seconds(timestamp_str):\n    \"\"\"\n    Convert HH:MM:SS or MM:SS format to seconds.\n    \"\"\"\n    if isinstance(timestamp_str, (int, float)):\n        return float(timestamp_str)\n    \n    timestamp_str = str(timestamp_str).strip()\n    \n    if ':' in timestamp_str:\n        parts = timestamp_str.split(':')\n        if len(parts) == 3:  # HH:MM:SS\n            h, m, s = parts\n            return int(h) * 3600 + int(m) * 60 + float(s)\n        elif len(parts) == 2:  # MM:SS\n            m, s = parts\n            return int(m) * 60 + float(s)\n    \n    return float(timestamp_str)\n\n\ndef load_csv_annotation(csv_path):\n    \"\"\"\n    Load annotation from CSV file.\n    Expected columns: start_time, end_time, speaker_id\n    \"\"\"\n    try:\n        df = pd.read_csv(csv_path)\n        \n        # Normalize column names\n        df.columns = df.columns.str.lower().str.strip()\n        \n        # Create annotation\n        annotation = Annotation()\n        \n        for _, row in df.iterrows():\n            # Handle different column names\n            start_col = [c for c in df.columns if 'start' in c][0]\n            end_col = [c for c in df.columns if 'end' in c][0]\n            speaker_col = [c for c in df.columns if 'speaker' in c or 'id' in c][0]\n            \n            start = convert_timestamp_to_seconds(row[start_col])\n            end = convert_timestamp_to_seconds(row[end_col])\n            speaker_id = row[speaker_col]\n            \n            # Skip invalid segments\n            if pd.isna(start) or pd.isna(end) or pd.isna(speaker_id):\n                continue\n            if start >= end:\n                continue\n            \n            # Format speaker ID\n            speaker = f\"speaker_{int(speaker_id):02d}\"\n            \n            # Add segment\n            annotation[Segment(start, end)] = speaker\n        \n        return annotation\n    \n    except Exception as e:\n        print(f\"Error loading {csv_path}: {e}\")\n        return None\n\ndef 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        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:46:43.057229Z","iopub.execute_input":"2026-02-22T14:46:43.057491Z","iopub.status.idle":"2026-02-22T14:46:43.07287Z","shell.execute_reply.started":"2026-02-22T14:46:43.057469Z","shell.execute_reply":"2026-02-22T14:46:43.072162Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 4. Create PyAnnote Database","metadata":{}},{"cell_type":"code","source":"def create_rttm_file(annotation, uri, output_path):\n    \"\"\"\n    Create RTTM file from annotation.\n    RTTM format: SPEAKER <uri> 1 <start> <duration> <NA> <NA> <speaker> <NA> <NA>\n    \"\"\"\n    with open(output_path, 'w') as f:\n        for segment, _, speaker in annotation.itertracks(yield_label=True):\n            line = f\"SPEAKER {uri} 1 {segment.start:.3f} {segment.duration:.3f} <NA> <NA> {speaker} <NA> <NA>\\n\"\n            f.write(line)\n\n\ndef create_uem_file(uri, duration, output_path):\n    \"\"\"\n    Create UEM file (defines evaluation boundaries).\n    UEM format: <uri> NA <start> <end>\n    \"\"\"\n    with open(output_path, 'w') as f:\n        f.write(f\"{uri} NA 0.000 {duration:.3f}\\n\")\n\n\ndef prepare_database():\n    \"\"\"\n    Prepare PyAnnote database structure with train/validation splits.\n    \"\"\"\n    print(\"\\n\" + \"=\"*60)\n    print(\"PREPARING DATABASE\")\n    print(\"=\"*60)\n    \n    # Get all audio files\n    audio_files = sorted(Path(TRAIN_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 {TRAIN_AUDIO_DIR}\")\n\n\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\n    # Load annotations\n    data = []\n    for audio_file in tqdm(audio_files, desc=\"Loading data\"):\n        # audio_file is like: .../temp_outputs/htdemucs/train_001/vocals.wav\n        \n        \n        # 1. Get the parent folder name (e.g., 'train_001') instead of .stem ('vocals')\n        uri = audio_file.parent.name \n        # uri = audio_file.stem\n        print(uri)\n        # 2. Find corresponding annotation (changed .csv to .txt based on your description)\n        ann_file = Path(TRAIN_ANNOTATION_DIR) / f\"{uri}.csv\"\n        print(ann_file)\n        \n        if not ann_file.exists():\n            print(f\"Warning: No annotation for {uri}\")\n            continue\n        \n        # Load annotation\n        annotation = load_csv_annotation(str(ann_file))\n        if annotation is None or len(annotation) == 0:\n            print(f\"Warning: Empty annotation for {uri}\")\n            continue\n        \n        # Get audio duration\n        waveform, sr = torchaudio.load(str(audio_file))\n        duration = waveform.shape[1] / sr\n        \n        data.append({\n            'uri': uri,\n            'audio_path': str(audio_file),\n            'annotation': annotation,\n            'duration': duration\n        })\n    \n    print(f\"Loaded {len(data)} valid samples\")\n    \n    # Split into train/val\n    train_data, val_data = train_test_split(\n        data, \n        test_size=VAL_SPLIT, \n        random_state=42\n    )\n    \n    print(f\"Train: {len(train_data)} samples\")\n    print(f\"Validation: {len(val_data)} samples\")\n    \n    # Create database structure\n    lists_dir = Path(DATABASE_DIR) / \"lists\"\n    annotations_dir = Path(DATABASE_DIR) / \"annotations\"\n    lists_dir.mkdir(parents=True, exist_ok=True)\n    annotations_dir.mkdir(parents=True, exist_ok=True)\n    \n    def create_subset_files(subset_data, subset_name):\n        \"\"\"Create files for a subset (train or development).\"\"\"\n        lst_path = lists_dir / f\"{subset_name}.lst\"\n        rttm_path = annotations_dir / f\"{subset_name}.rttm\"\n        uem_path = annotations_dir / f\"{subset_name}.uem\"\n        \n        # Create .lst file\n        with open(lst_path, 'w') as f:\n            for item in subset_data:\n                f.write(f\"{item['uri']}\\n\")\n        \n        # Create .rttm and .uem files\n        with open(rttm_path, 'w') as rttm_f, open(uem_path, 'w') as uem_f:\n            for item in subset_data:\n                uri = item['uri']\n                annotation = item['annotation']\n                duration = item['duration']\n                \n                # Write RTTM\n                for segment, _, speaker in annotation.itertracks(yield_label=True):\n                    line = f\"SPEAKER {uri} 1 {segment.start:.3f} {segment.duration:.3f} <NA> <NA> {speaker} <NA> <NA>\\n\"\n                    rttm_f.write(line)\n                \n                # Write UEM\n                uem_f.write(f\"{uri} NA 0.000 {duration:.3f}\\n\")\n    \n    create_subset_files(train_data, \"train\")\n    create_subset_files(val_data, \"development\")\n    \n    # Create database.yml\n    database_yml = {\n        \"Databases\": {\n            \"Custom\": f\"{TRAIN_AUDIO_DIR}/{{uri}}.wav\"\n        },\n        \"Protocols\": {\n            \"Custom\": {\n                \"SpeakerDiarization\": {\n                    \"Training\": {\n                        \"train\": {\n                            \"uri\": f\"lists/train.lst\",\n                            \"annotation\": f\"annotations/train.rttm\",\n                            \"annotated\": f\"annotations/train.uem\"\n                        },\n                        \"development\": {\n                            \"uri\": f\"lists/development.lst\",\n                            \"annotation\": f\"annotations/development.rttm\",\n                            \"annotated\": f\"annotations/development.uem\"\n                        }\n                    }\n                }\n            }\n        }\n    }\n    \n    database_yml_path = Path(DATABASE_DIR) / \"database.yml\"\n    with open(database_yml_path, 'w') as f:\n        yaml.dump(database_yml, f, default_flow_style=False)\n    \n    print(f\"\\n✓ Database created at {DATABASE_DIR}\")\n    print(f\"✓ Database config: {database_yml_path}\")\n    \n    return train_data, val_data, str(database_yml_path)\n\n# Create the database\ntrain_data, val_data, database_yml_path = prepare_database()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T14:46:44.534693Z","iopub.execute_input":"2026-02-22T14:46:44.535275Z","iopub.status.idle":"2026-02-22T15:12:07.87496Z","shell.execute_reply.started":"2026-02-22T14:46:44.535248Z","shell.execute_reply":"2026-02-22T15:12:07.873999Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Load Protocol","metadata":{}},{"cell_type":"code","source":"# Load database\nregistry.load_database(database_yml_path)\n\n# Get protocol\nprotocol = get_protocol(\n    \"Custom.SpeakerDiarization.Training\",\n    preprocessors={\"audio\": FileFinder()}\n)\n\nprint(\"✓ Protocol loaded successfully\")\n\n# Verify protocol\ntrain_files = list(protocol.train())\ndev_files = list(protocol.development())\n\nprint(f\"\\nProtocol verification:\")\nprint(f\"  Train files: {len(train_files)}\")\nprint(f\"  Development files: {len(dev_files)}\")\n\nif len(train_files) > 0:\n    sample = train_files[0]\n    print(f\"\\nSample file:\")\n    print(f\"  URI: {sample['uri']}\")\n    print(f\"  Audio: {sample['audio']}\")\n    print(f\"  Segments: {len(sample['annotation'])}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:12:07.876762Z","iopub.execute_input":"2026-02-22T15:12:07.87714Z","iopub.status.idle":"2026-02-22T15:12:08.084449Z","shell.execute_reply.started":"2026-02-22T15:12:07.877114Z","shell.execute_reply":"2026-02-22T15:12:08.083673Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Setup Training Task","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch import nn\nfrom torch_audiomentations import Compose, Gain\n\nclass ManualExternal(nn.Module):\n    def __init__(self, augmentation):\n        super().__init__()\n        self.augmentation = augmentation\n\n    def forward(self, samples, sample_rate, targets=None, **kwargs):\n        # 1. Apply the audio augmentation\n        augmented_samples = self.augmentation(samples, sample_rate=sample_rate)\n        \n        # 2. Create a container that holds both samples and targets\n        # pyannote's collate_fn will look for both .samples and .targets\n        class AugmentedBundle:\n            def __init__(self, s, t):\n                self.samples = s\n                self.targets = t\n        \n        return AugmentedBundle(augmented_samples, targets)\n    \n    def __call__(self, samples, sample_rate, targets=None, **kwargs):\n        return self.forward(samples, sample_rate, targets=targets, **kwargs)\n\n# --- Re-initialize everything ---\nraw_augmentation = Compose([\n    Gain(min_gain_in_db=-6.0, max_gain_in_db=6.0, p=0.4),\n])\n\naugmentation = ManualExternal(raw_augmentation)\n\n# Ensure the Task is updated with this new augmentation instance\ntask = SpeakerDiarization(\n    protocol, \n    duration=DURATION, \n    batch_size=BATCH_SIZE,\n    num_workers=NUM_WORKERS,\n    augmentation=augmentation \n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:12:08.085368Z","iopub.execute_input":"2026-02-22T15:12:08.08564Z","iopub.status.idle":"2026-02-22T15:12:08.280181Z","shell.execute_reply.started":"2026-02-22T15:12:08.08562Z","shell.execute_reply":"2026-02-22T15:12:08.279425Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Initialize Model","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-22T15:12:08.281926Z","iopub.execute_input":"2026-02-22T15:12:08.282212Z","iopub.status.idle":"2026-02-22T15:12:08.287841Z","shell.execute_reply.started":"2026-02-22T15:12:08.282191Z","shell.execute_reply":"2026-02-22T15:12:08.286959Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Option 1: Train from scratch\nPRETRAINED_MODEL = \"ishmamzarif/bengali-diarization_aug_v1\"\nmodel = Model.from_pretrained(\n    PRETRAINED_MODEL,\n    use_auth_token=HF_TOKEN,\n    strict=False  # Allow fine-tuning with different number of speakers\n)\n\nmodel.task = task\n# Option 2: Start from a checkpoint (uncomment if resuming)\n# model = Model.from_pretrained(\n#     \"path/to/checkpoint.ckpt\",\n#     strict=False\n# )\n\nprint(\"✓ Model initialized\")\nprint(f\"  Base model: {PRETRAINED_MODEL}\")\nprint(f\"  Parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:12:08.288857Z","iopub.execute_input":"2026-02-22T15:12:08.289132Z","iopub.status.idle":"2026-02-22T15:12:09.460476Z","shell.execute_reply.started":"2026-02-22T15:12:08.289104Z","shell.execute_reply":"2026-02-22T15:12:09.459678Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Setup Training","metadata":{}},{"cell_type":"code","source":"# Callbacks\ncheckpoint_callback = ModelCheckpoint(\n    dirpath=CHECKPOINT_DIR,\n    filename=\"diarization-{epoch:02d}-{val_loss:.4f}\",\n    monitor=\"loss/val\",\n    mode=\"min\",\n    save_top_k=3,\n    save_last=True,\n)\n\nearly_stopping = EarlyStopping(\n    monitor=\"loss/val\",\n    patience=PATIENCE,\n    min_delta=MIN_DELTA,\n    mode=\"min\",\n)\n\n# Logger\nlogger = TensorBoardLogger(\n    save_dir=LOGS_DIR,\n    name=\"diarization_training\"\n)\n\n# Trainer\ntrainer = pl.Trainer(\n    accelerator=\"auto\",\n    devices=1,\n    max_epochs=MAX_EPOCHS,\n    callbacks=[checkpoint_callback, early_stopping],\n    logger=logger,\n    gradient_clip_val=1.0,\n    log_every_n_steps=10,\n)\n\nprint(\"✓ Training setup complete\")\nprint(f\"  Max epochs: {MAX_EPOCHS}\")\nprint(f\"  Early stopping patience: {PATIENCE}\")\nprint(f\"  Checkpoints: {CHECKPOINT_DIR}\")\nprint(f\"  Logs: {LOGS_DIR}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:12:09.461366Z","iopub.execute_input":"2026-02-22T15:12:09.461657Z","iopub.status.idle":"2026-02-22T15:12:09.542096Z","shell.execute_reply.started":"2026-02-22T15:12:09.461624Z","shell.execute_reply":"2026-02-22T15:12:09.541514Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Train Model","metadata":{}},{"cell_type":"code","source":"print(\"\\n\" + \"=\"*60)\nprint(\"STARTING TRAINING\")\nprint(\"=\"*60)\n\n# Train\ntrainer.fit(model)\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"TRAINING COMPLETE\")\nprint(\"=\"*60)\nprint(f\"Best checkpoint: {checkpoint_callback.best_model_path}\")\n# print(f\"Best validation loss: {checkpoint_callback.best_model_score:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:12:09.543013Z","iopub.execute_input":"2026-02-22T15:12:09.543302Z","iopub.status.idle":"2026-02-22T15:17:00.896678Z","shell.execute_reply.started":"2026-02-22T15:12:09.543279Z","shell.execute_reply":"2026-02-22T15:17:00.895823Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 11. Save Final Model","metadata":{}},{"cell_type":"code","source":"# Save model\nfinal_model_path = Path(MODEL_DIR) / \"final_model\"\nimport shutil\nimport os\n\n# 1. Get the path to the best checkpoint from your trainer/callback\n# (This variable usually exists if you just finished training)\nbest_ckpt_path = checkpoint_callback.best_model_path\n\n# 2. Define where you want to save your final model\n# Pyannote models MUST end in .ckpt to be loaded correctly later\nfinal_path = \"work/models/final_model.ckpt\"\n\n# Ensure directory exists\nos.makedirs(os.path.dirname(final_path), exist_ok=True)\n\n# 3. Copy the file\nprint(f\"Copying best model from: {best_ckpt_path}\")\nshutil.copy(best_ckpt_path, final_path)\n\nprint(f\"✓ Model successfully saved to: {final_path}\")\n\n\n# Save pipeline configuration\npipeline_config = {\n    \"segmentation_model\": str(final_path),\n    \"embedding_model\": PRETRAINED_EMBEDDING,\n    \"parameters\": {\n    \"segmentation\": {\n        \"threshold\": 0.5,\n        \"min_duration_off\": 0.0\n    },\n    \"clustering\": {\n        \"method\": \"centroid\",\n        \"threshold\": 0.7,\n        \"min_cluster_size\": 15\n    }\n}\n}\n\nconfig_path = Path(MODEL_DIR) / \"pipeline_config.json\"\nwith open(config_path, 'w') as f:\n    json.dump(pipeline_config, f, indent=2)\n\nprint(f\"✓ Pipeline config saved to {config_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:17:00.898029Z","iopub.execute_input":"2026-02-22T15:17:00.898285Z","iopub.status.idle":"2026-02-22T15:17:00.920199Z","shell.execute_reply.started":"2026-02-22T15:17:00.89826Z","shell.execute_reply":"2026-02-22T15:17:00.919034Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install -q huggingface_hub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:20:51.431495Z","iopub.execute_input":"2026-02-22T15:20:51.43224Z","iopub.status.idle":"2026-02-22T15:20:55.360099Z","shell.execute_reply.started":"2026-02-22T15:20:51.432209Z","shell.execute_reply":"2026-02-22T15:20:55.359127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# UPLOAD TRAINED MODEL TO HUGGING FACE HUB\n# ============================================================\n# Add this as a new cell AFTER training completes successfully\n\n# Install required package\n\n\nimport os\nimport json\nfrom pathlib import Path\nfrom huggingface_hub import create_repo, upload_folder\nfrom pyannote.audio import Model\n\nprint(\"=\" * 70)\nprint(\"UPLOADING MODEL TO HUGGING FACE HUB\")\nprint(\"=\" * 70)\n\n# ============================================================\n# CONFIGURATION\n# ============================================================\n\n# Your Hugging Face credentials\nHF_USERNAME = \"ishmamzarif\"  # ← CHANGE THIS to your HF username\nMODEL_NAME = \"bengali-diarization_aug_v1\"  # ← CHANGE THIS to your desired model name\nHF_TOKEN = \"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\"\n# Full repository name\nREPO_ID = f\"{HF_USERNAME}/{MODEL_NAME}\"\n\n# Your HF token (already defined in your notebook)\n# HF_TOKEN is already set above\n\nprint(f\"\\nRepository ID: {REPO_ID}\")\nprint(f\"Token available: {'✓' if HF_TOKEN else '✗'}\")\n\n# ============================================================\n# STEP 1: PREPARE MODEL FILES\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 1: Preparing Model Files\")\nprint(\"=\" * 70)\n\n# Create a directory for HF upload\nHF_MODEL_DIR = \"./hf_upload\"\nos.makedirs(HF_MODEL_DIR, exist_ok=True)\n\n# Copy the trained model checkpoint\nimport shutil\n\nsource_model = \"/kaggle/working/work/models/final_model.ckpt\"\ndest_model = os.path.join(HF_MODEL_DIR, \"pytorch_model.bin\")\n\nif os.path.exists(source_model):\n    print(f\"\\n✓ Copying model from: {source_model}\")\n    shutil.copy(source_model, dest_model)\n    print(f\"✓ Model copied to: {dest_model}\")\nelse:\n    print(f\"✗ Error: Model not found at {source_model}\")\n    raise FileNotFoundError(f\"Model checkpoint not found at {source_model}\")\n\n# ============================================================\n# STEP 2: CREATE MODEL CARD (README.md)\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 2: Creating Model Card\")\nprint(\"=\" * 70)\n\n# Get training metrics if available\ntry:\n    training_der = f\"{der*100:.2f}%\" if 'der' in locals() else \"Not computed\"\n    num_train_files = len(train_data) if 'train_data' in locals() else \"Unknown\"\n    num_val_files = len(val_data) if 'val_data' in locals() else \"Unknown\"\n    num_epochs = trainer.current_epoch if 'trainer' in locals() else \"Unknown\"\nexcept:\n    training_der = \"Not computed\"\n    num_train_files = \"Unknown\"\n    num_val_files = \"Unknown\"\n    num_epochs = \"Unknown\"\n\n# Create comprehensive model card\nmodel_card = f\"\"\"---\nlanguage:\n- bn\ntags:\n- speaker-diarization\n- pyannote\n- pyannote-audio\n- audio\n- voice\n- speech\n- bengali\nlicense: mit\ndatasets:\n- custom\nmetrics:\n- der\nmodel-index:\n- name: {MODEL_NAME}\n  results:\n  - task:\n      type: speaker-diarization\n      name: Speaker Diarization\n    metrics:\n    - type: der\n      value: {training_der}\n      name: Diarization Error Rate\n---\n\n# {MODEL_NAME}\n\nThis is a fine-tuned speaker diarization model based on pyannote.audio, specifically trained on Bengali audio data.\n\n\"\"\"\n\n# Save model card\nreadme_path = os.path.join(HF_MODEL_DIR, \"README.md\")\nwith open(readme_path, \"w\", encoding=\"utf-8\") as f:\n    f.write(model_card)\n\nprint(f\"✓ Model card created: {readme_path}\")\n\n# ============================================================\n# STEP 3: CREATE CONFIG FILES\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 3: Creating Configuration Files\")\nprint(\"=\" * 70)\n\n# Create config.yaml (pyannote format)\nconfig_yaml = \"\"\"\n# Model configuration for pyannote.audio\ntask:\n  name: SpeakerDiarization\n  \narchitecture:\n  name: PyanNet\n  \nspecifications:\n  duration: 5.0\n  sample_rate: 16000\n  \ntraining:\n  batch_size: 32\n  learning_rate: 0.0001\n  max_epochs: 20\n\"\"\"\n\nconfig_yaml_path = os.path.join(HF_MODEL_DIR, \"config.yaml\")\nwith open(config_yaml_path, \"w\") as f:\n    f.write(config_yaml)\n\nprint(f\"✓ Config YAML created: {config_yaml_path}\")\n\n# Create pipeline config\npipeline_config = {\n    \"model_type\": \"speaker-diarization\",\n    \"pyannote_version\": \"3.3.2\",\n    \"embedding_model\": \"pyannote/wespeaker-voxceleb-resnet34-LM\",\n    \"optimal_parameters\": {\n        \"segmentation\": {\n            \"threshold\": 0.5,\n            \"min_duration_off\": 0.0\n        },\n        \"clustering\": {\n            \"method\": \"centroid\",\n            \"threshold\": 0.7,\n            \"min_cluster_size\": 12\n        }\n    }\n}\n\npipeline_config_path = os.path.join(HF_MODEL_DIR, \"pipeline_config.json\")\nwith open(pipeline_config_path, \"w\") as f:\n    json.dump(pipeline_config, f, indent=2)\n\nprint(f\"✓ Pipeline config created: {pipeline_config_path}\")\n\n# ============================================================\n# STEP 4: CREATE USAGE EXAMPLE\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 4: Creating Usage Example\")\nprint(\"=\" * 70)\n\nusage_example = f\"\"\"# Example Usage: {MODEL_NAME}\n\nThis example shows how to use the model for speaker diarization.\n\n\"\"\"\n\nexample_path = os.path.join(HF_MODEL_DIR, \"USAGE.md\")\nwith open(example_path, \"w\") as f:\n    f.write(usage_example)\n\nprint(f\"✓ Usage example created: {example_path}\")\n\n# ============================================================\n# STEP 5: UPLOAD TO HUGGING FACE HUB\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 5: Uploading to Hugging Face Hub\")\nprint(\"=\" * 70)\n\n# Verify token\nif not HF_TOKEN or HF_TOKEN == \"your_token_here\":\n    print(\"\\n✗ Error: HF_TOKEN not set!\")\n    print(\"Please set your Hugging Face token in the configuration section above.\")\n    raise ValueError(\"HF_TOKEN not configured\")\n\n# Verify username\nif HF_USERNAME == \"your-username\":\n    print(\"\\n✗ Error: HF_USERNAME not set!\")\n    print(\"Please change HF_USERNAME to your actual Hugging Face username.\")\n    raise ValueError(\"HF_USERNAME not configured\")\n\n# # Initialize API\n# api = HfApi()\n\ntry:\n    # Create repository\n    print(f\"\\nCreating repository: {REPO_ID}\")\n    create_repo(\n        repo_id=REPO_ID,\n        token=HF_TOKEN,\n        private=False,  # Set to True if you want a private repo\n        exist_ok=True,  # Don't fail if repo already exists\n        repo_type=\"model\"\n    )\n    print(f\"✓ Repository created/verified: https://huggingface.co/{REPO_ID}\")\n    \n    # Upload files\n    print(f\"\\nUploading model files...\")\n    upload_folder(\n        folder_path=HF_MODEL_DIR,\n        repo_id=REPO_ID,\n        token=HF_TOKEN,\n        commit_message=\"Upload fine-tuned Bengali speaker diarization model\",\n    )\n    \n    print(f\"\\n{'='*70}\")\n    print(\"✓ UPLOAD SUCCESSFUL!\")\n    print(f\"{'='*70}\")\n    print(f\"\\n🎉 Your model is now available at:\")\n    print(f\"   https://huggingface.co/{REPO_ID}\")\n    print(f\"\\n📖 To use your model:\")\n    print(f\"\"\"\nfrom pyannote.audio import Model\nmodel = Model.from_pretrained(\"{REPO_ID}\")\n    \"\"\")\n    \nexcept Exception as e:\n    print(f\"\\n✗ Error during upload: {e}\")\n    print(\"\\nTroubleshooting:\")\n    print(\"1. Verify your HF_TOKEN is correct\")\n    print(\"2. Verify your HF_USERNAME is correct\")\n    print(\"3. Check your internet connection\")\n    print(\"4. Ensure you have write permissions\")\n    raise\n\n# ============================================================\n# STEP 6: CREATE DOWNLOAD INSTRUCTIONS\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"STEP 6: Generating Download Instructions\")\nprint(\"=\" * 70)\n\ndownload_instructions = f\"\"\"# How to Download and Use Your Model\n\n## From Python\n\n```python\nfrom pyannote.audio import Model\nfrom pyannote.audio.pipelines import SpeakerDiarization\n\n# Download and load model (automatic caching)\nmodel = Model.from_pretrained(\"{REPO_ID}\")\n```\n\"\"\"\n\ndownload_path = os.path.join(HF_MODEL_DIR, \"DOWNLOAD.md\")\nwith open(download_path, \"w\") as f:\n    f.write(download_instructions)\n\nprint(f\"✓ Download instructions created: {download_path}\")\n\n# ============================================================\n# SUMMARY\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 70)\nprint(\"UPLOAD SUMMARY\")\nprint(\"=\" * 70)\n\nprint(f\"\"\"\n✓ Model repository: {REPO_ID}\n✓ URL: https://huggingface.co/{REPO_ID}\n\nFiles uploaded:\n  - pytorch_model.bin (trained model)\n  - README.md (model card)\n  - config.yaml (model config)\n  - pipeline_config.json (pipeline parameters)\n  - USAGE.md (usage examples)\n  - DOWNLOAD.md (download instructions)\n\nTo use your model anywhere:\n  \n  from pyannote.audio import Model\n  model = Model.from_pretrained(\"{REPO_ID}\")\n\nShare with others:\n  https://huggingface.co/{REPO_ID}\n\"\"\")\n\nprint(\"=\" * 70)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-22T15:21:01.144526Z","iopub.execute_input":"2026-02-22T15:21:01.14547Z","iopub.status.idle":"2026-02-22T15:21:01.183125Z","shell.execute_reply.started":"2026-02-22T15:21:01.145436Z","shell.execute_reply":"2026-02-22T15:21:01.182264Z"}},"outputs":[],"execution_count":null}]}