{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":91844,"databundleVersionId":11361821,"sourceType":"competition"},{"sourceId":11679882,"sourceType":"datasetVersion","datasetId":7330611}],"dockerImageVersionId":31012,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\n\nbase_path = '/kaggle/input/modeling'  # thư mục mặc định chứa các file/dataset bạn đã thêm vào\nfor root, dirs, files in os.walk(base_path):\n    if 'ModelTuanTran1 ' in dirs:\n        print(\"Đường dẫn tới modeling:\", os.path.join(root, 'ModelTuanTran1'))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-06T08:27:51.332049Z","iopub.execute_input":"2025-05-06T08:27:51.332421Z","iopub.status.idle":"2025-05-06T08:27:51.345768Z","shell.execute_reply.started":"2025-05-06T08:27:51.332398Z","shell.execute_reply":"2025-05-06T08:27:51.344546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\npaths = '/kaggle/input/modeling/ModelTuanTran1 /ResNet50_best.weights.h5'\n\nif os.path.exists(paths):\n      print(f\"{paths} ✅ tồn tại.\")\nelse:\n      print(f\"{paths} ❌ không tồn tại.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T08:27:51.346933Z","iopub.execute_input":"2025-05-06T08:27:51.347314Z","iopub.status.idle":"2025-05-06T08:27:51.360531Z","shell.execute_reply.started":"2025-05-06T08:27:51.347283Z","shell.execute_reply":"2025-05-06T08:27:51.359431Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport time\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport cv2\nfrom tqdm import tqdm\nimport tensorflow as tf\nimport keras\nfrom keras.layers import Input, GlobalAveragePooling2D, Dense\nfrom keras.models import Model\nfrom pathlib import Path\n\n# Cấu hình CFG\nclass CFG:\n    # Mel-spectrogram & Audio Params\n    N_FFT = 2048\n    HOP_LENGTH = 512\n    N_MELS = 256\n    FMIN = 20\n    FMAX = 16000\n    TARGET_SHAPE = (256, 256)\n    FS = 32000\n    WINDOW_SIZE = 5\n    POWER = 2.0\n    NORM = 'slaney'\n    PAD_MODE = 'reflect'\n    IN_CHANNELS = 3\n\n    # Model\n    model_path_efficient = '/kaggle/input/modeling/ModelTuanTran1 /EfficientNetV2S_best.weights.h5'\n    model_path_resnet = '/kaggle/input/modeling/ModelTuanTran1 /ResNet50_best.weights.h5'\n    model_name_efficient = 'EfficientNetV2S'\n    model_name_resnet = 'ResNet50'\n    \n    batch_size = 16\n    use_tta = False\n    threshold = 0.5\n\n    # Datasets\n    test_soundscapes = '/kaggle/input/birdclef-2025/test_soundscapes'\n    submission_csv = '/kaggle/input/birdclef-2025/sample_submission.csv'\n    taxonomy_csv = '/kaggle/input/birdclef-2025/taxonomy.csv'\n\n    # Utility\n    debug = False\n    debug_count = 3\n\ncfg = CFG()\nprint(f\"Loading taxonomy data...\")\ntaxonomy_df = pd.read_csv(cfg.taxonomy_csv)\nspecies_ids = taxonomy_df['primary_label'].tolist()\nnum_classes = len(species_ids)\nprint(f\"Number of classes: {num_classes}\")\n\n# Hàm tạo model giống cách huấn luyện\ndef create_model(preset, num_classes, cfg):\n    inp = Input(shape=(cfg.TARGET_SHAPE[0], cfg.TARGET_SHAPE[1], cfg.IN_CHANNELS))\n    if preset == 'EfficientNetV2S':\n        backbone = keras.applications.EfficientNetV2S(\n            include_top=False,\n            weights=None,  # Sẽ load trọng số sau\n            input_shape=(cfg.TARGET_SHAPE[0], cfg.TARGET_SHAPE[1], cfg.IN_CHANNELS)\n        )\n    elif preset == 'ResNet50':\n        backbone = keras.applications.ResNet50(\n            include_top=False,\n            weights=None,  # Sẽ load trọng số sau\n            input_shape=(cfg.TARGET_SHAPE[0], cfg.TARGET_SHAPE[1], cfg.IN_CHANNELS)\n        )\n    else:\n        raise ValueError(f\"Unsupported preset: {preset}\")\n\n    x = backbone(inp)\n    x = GlobalAveragePooling2D()(x)\n    out = Dense(num_classes, activation='sigmoid', name='classifier')(x)\n\n    model = Model(inputs=inp, outputs=out)\n    return model\n\n# Load cả hai mô hình và trọng số\ndef load_models(cfg, num_classes):\n    models = {}\n    \n    # Load EfficientNetV2S\n    print(f\"Loading EfficientNetV2S from: {cfg.model_path_efficient}\")\n    if not os.path.exists(cfg.model_path_efficient):\n        raise FileNotFoundError(f\"EfficientNetV2S weights not found at {cfg.model_path_efficient}\")\n    model_efficient = create_model(cfg.model_name_efficient, num_classes, cfg)\n    model_efficient.load_weights(cfg.model_path_efficient)\n    models['efficient'] = model_efficient\n    \n    # Load ResNet50\n    print(f\"Loading ResNet50 from: {cfg.model_path_resnet}\")\n    if not os.path.exists(cfg.model_path_resnet):\n        raise FileNotFoundError(f\"ResNet50 weights not found at {cfg.model_path_resnet}\")\n    model_resnet = create_model(cfg.model_name_resnet, num_classes, cfg)\n    model_resnet.load_weights(cfg.model_path_resnet)\n    models['resnet'] = model_resnet\n    \n    return models\n\n# Xử lý mel-spectrogram\ndef audio2melspec(audio_data, cfg, tta_variant=None):\n    if np.isnan(audio_data).any():\n        mean_signal = np.nanmean(audio_data)\n        audio_data = np.nan_to_num(audio_data, nan=mean_signal)\n\n    # TTA: Chỉ dùng biến thể 'noise'\n    if tta_variant == 'noise':\n        noise = np.random.normal(0, 0.01, audio_data.shape)\n        audio_data = audio_data + noise\n        \n    mel_spec = librosa.feature.melspectrogram(\n        y=audio_data,\n        sr=cfg.FS,\n        n_fft=cfg.N_FFT,\n        hop_length=cfg.HOP_LENGTH,\n        n_mels=cfg.N_MELS,\n        fmin=cfg.FMIN,\n        fmax=cfg.FMAX,\n        power=cfg.POWER,\n        pad_mode=cfg.PAD_MODE,\n        norm=cfg.NORM,\n        htk=True,\n        center=True,\n    )\n\n    mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)\n    mel_spec_norm = (mel_spec_db - mel_spec_db.min()) / (mel_spec_db.max() - mel_spec_db.min() + 1e-8)\n    return mel_spec_norm\n\ndef process_audio_segment(audio_data, cfg, tta_variant=None):\n    # Padding nếu đoạn âm thanh ngắn hơn 5 giây\n    window_samples = int(cfg.FS * cfg.WINDOW_SIZE)\n    if len(audio_data) < window_samples:\n        audio_data = np.pad(\n            audio_data,\n            (0, window_samples - len(audio_data)),\n            mode='constant'\n        )\n    \n    mel_spec = audio2melspec(audio_data, cfg, tta_variant)\n    \n    if mel_spec.shape != cfg.TARGET_SHAPE:\n        mel_spec = cv2.resize(mel_spec, cfg.TARGET_SHAPE, interpolation=cv2.INTER_LINEAR)\n    \n    mel_spec = np.stack([mel_spec] * 3, axis=-1)\n    return mel_spec.astype(np.float32)\n\ndef predict_on_spectrogram(audio_path, models, cfg, species_ids):\n    predictions = []\n    row_ids = []\n    soundscape_id = os.path.splitext(os.path.basename(audio_path))[0]\n    \n    try:\n        print(f\"Processing {soundscape_id}\")\n        audio_data, _ = librosa.load(audio_path, sr=cfg.FS)\n        \n        total_segments = int(len(audio_data) / (cfg.FS * cfg.WINDOW_SIZE))\n        \n        for segment_idx in range(total_segments):\n            start_sample = segment_idx * cfg.FS * cfg.WINDOW_SIZE\n            end_sample = start_sample + cfg.FS * cfg.WINDOW_SIZE\n            segment_audio = audio_data[start_sample:end_sample]\n            \n            end_time_sec = (segment_idx + 1) * cfg.WINDOW_SIZE\n            row_id = f\"{soundscape_id}_{end_time_sec}\"\n            row_ids.append(row_id)\n\n            tta_variants = None if not cfg.use_tta else 'noise'\n            \n            # Dự đoán trên toàn bộ segment \n            mel_spec = process_audio_segment(segment_audio, cfg, tta_variants)\n            mel_spec = np.expand_dims(mel_spec, axis=0)\n            \n            # EfficientNetV2S\n            preds_efficient = models['efficient'].predict(mel_spec, verbose=0).squeeze()\n            \n            # # ResNet50\n            # preds_resnet = models['resnet'].predict(mel_spec, verbose=0).squeeze()\n            \n            # # # Kết hợp dự đoán của EfficientNetV2S và ResNet50 với trọng số 0.6/0.4\n            # segment_preds = 0.65 * preds_efficient + 0.35 * preds_resnet\n            segment_preds = preds_efficient\n            \n            predictions.append(segment_preds)\n            \n    except Exception as e:\n        print(f\"Error processing {audio_path}: {e}\")\n    \n    return row_ids, predictions\n\ndef run_inference(cfg, models, species_ids):\n    test_files = list(Path(cfg.test_soundscapes).glob('*.ogg'))\n    \n    if cfg.debug:\n        print(f\"Debug mode enabled, using only {cfg.debug_count} files\")\n        test_files = test_files[:cfg.debug_count]\n    \n    print(f\"Found {len(test_files)} test soundscapes\")\n\n    all_row_ids = []\n    all_predictions = []\n\n    for audio_path in tqdm(test_files):\n        row_ids, predictions = predict_on_spectrogram(str(audio_path), models, cfg, species_ids)\n        all_row_ids.extend(row_ids)\n        all_predictions.extend(predictions)\n    \n    return all_row_ids, all_predictions\n\ndef create_submission(row_ids, predictions, species_ids, cfg):\n    print(\"Creating submission dataframe...\")\n    submission_dict = {'row_id': row_ids}\n    \n    for i, species in enumerate(species_ids):\n        submission_dict[species] = [pred[i] for pred in predictions]\n\n    submission_df = pd.DataFrame(submission_dict)\n    submission_df.set_index('row_id', inplace=True)\n    sample_sub = pd.read_csv(cfg.submission_csv, index_col='row_id')\n\n    missing_cols = set(sample_sub.columns) - set(submission_df.columns)\n    if missing_cols:\n        print(f\"Warning: Missing {len(missing_cols)} species columns in submission\")\n        for col in missing_cols:\n            submission_df[col] = 0.0\n\n    submission_df = submission_df[sample_sub.columns]\n    submission_df = submission_df.reset_index()\n    \n    return submission_df\n\ndef main():\n    start_time = time.time()\n    print(\"Starting BirdCLEF-2025 inference...\")\n    print(f\"TTA enabled: {cfg.use_tta}\")\n\n    try:\n        models = load_models(cfg, num_classes)\n    except Exception as e:\n        print(f\"Failed to load model: {e}\")\n        return\n    \n    print(\"Model loaded successfully\")\n\n    row_ids, predictions = run_inference(cfg, models, species_ids)\n    submission_df = create_submission(row_ids, predictions, species_ids, cfg)\n    submission_path = 'submission.csv'\n    submission_df.to_csv(submission_path, index=False)\n    print(f\"Submission saved to {submission_path}\")\n    \n    # Post-processing\n    sub = pd.read_csv('submission.csv')\n    cols = sub.columns[1:]\n    groups = sub['row_id'].str.rsplit('_', n=1).str[0]\n    groups = groups.values\n    for group in np.unique(groups):\n        sub_group = sub[group == groups]\n        predictions = sub_group[cols].values\n        new_predictions = predictions.copy()\n        for i in range(1, predictions.shape[0]-1):\n            new_predictions[i] = (predictions[i-1] * 0.2) + (predictions[i] * 0.6) + (predictions[i+1] * 0.2)\n        new_predictions[0] = (predictions[0] * 0.9) + (predictions[1] * 0.1)\n        new_predictions[-1] = (predictions[-1] * 0.9) + (predictions[-2] * 0.1)\n        sub_group[cols] = new_predictions\n        sub[group == groups] = sub_group\n    sub.to_csv(\"submission.csv\", index=False)\n    \n    end_time = time.time()\n    print(f\"Inference completed in {(end_time - start_time)/60:.2f} minutes\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T08:27:51.361557Z","iopub.execute_input":"2025-05-06T08:27:51.361835Z","iopub.status.idle":"2025-05-06T08:27:56.982664Z","shell.execute_reply.started":"2025-05-06T08:27:51.361814Z","shell.execute_reply":"2025-05-06T08:27:56.981621Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\npd.read_csv('/kaggle/input/birdclef-2025/sample_submission.csv').head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T08:27:56.983726Z","iopub.execute_input":"2025-05-06T08:27:56.984009Z","iopub.status.idle":"2025-05-06T08:27:57.008894Z","shell.execute_reply.started":"2025-05-06T08:27:56.983987Z","shell.execute_reply":"2025-05-06T08:27:57.008041Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\npd.read_csv('submission.csv').head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-06T08:27:57.009867Z","iopub.execute_input":"2025-05-06T08:27:57.010209Z","iopub.status.idle":"2025-05-06T08:27:57.040417Z","shell.execute_reply.started":"2025-05-06T08:27:57.010184Z","shell.execute_reply":"2025-05-06T08:27:57.039688Z"}},"outputs":[],"execution_count":null}]}