{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# import sys\n#!pip install timm\n# print(\"卸载旧版 PyTorch / fastai ...\")\n# !pip uninstall -y torch torchvision torchaudio fastai fastbook\n\n# print(\"\\n安装 CUDA 12.1 兼容版 PyTorch ...\")\n# !pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu121\n\n# print(\"\\n安装新版 fastai ...\")\n# !pip install fastai\n\n# print(\"\\n=== 验证 GPU 可用性 ===\")\n# import torch\n# print(f\"CUDA available: {torch.cuda.is_available()}\")\n# if torch.cuda.is_available():\n#     print(f\"GPU: {torch.cuda.get_device_name(0)}\")\n#     print(f\"CUDA version: {torch.version.cuda}\")\n#     # 关键测试：复现原来的报错点\n#     t = torch.zeros(1, device=\"cuda\")\n#     t.float().div_(255.0)\n#     print(\"✅ 关键运算自检通过，CUDA 兼容正常！\")\n# else:\n#     print(\"⚠️ GPU 不可用 - 请在 Kaggle 右侧 Accelerator 下拉中选择 GPU (T4 或 P100)\")\n#     print(\"   然后重新运行本 Cell。\")\n\n# print(\"\\n✅ 修复完成，请执行 Runtime → Restart Runtime，然后从头运行全部 cell。\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-24T04:06:46.019604Z","iopub.execute_input":"2026-07-24T04:06:46.019859Z","iopub.status.idle":"2026-07-24T04:06:46.025384Z","shell.execute_reply.started":"2026-07-24T04:06:46.019832Z","shell.execute_reply":"2026-07-24T04:06:46.024524Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport random  # 【修复】之前报 ModuleNotFoundError 的地方\nimport warnings\nimport json\nfrom pathlib import Path\nfrom concurrent.futures import ThreadPoolExecutor\n\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport cv2  \nfrom sklearn.metrics import accuracy_score\n\n# 导入 timm 模型库与 FastAI 的进阶功能 (运行前请确保已 !pip install timm)\nimport timm \nfrom fastai.vision.all import *\nfrom fastai.callback.mixup import MixUp\nfrom fastai.losses import FocalLossFlat\nfrom tqdm.auto import tqdm\n\nwarnings.filterwarnings('ignore')\n\n# ============================================================\n# 1. 配置项与环境初始化\n# ============================================================\ndef seed_everything(seed=42):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n\nseed_everything(42)\n\nif torch.cuda.is_available():\n    print(f\"✅ GPU 就绪: {torch.cuda.get_device_name(0)} (CUDA {torch.version.cuda})\")\nelse:\n    print(\"⚠️ 未检测到 GPU\")\n\nclass Config:\n    SAMPLE_RATE = 16000\n    CLIP_SECONDS = 5.0\n    N_MELS = 128\n    IMAGE_SIZE = (224, 224)\n    N_FOLDS = 5\n\n    @staticmethod\n    def _find_csv_dir():\n        kaggle_input = Path(\"/kaggle/input\")\n        if not kaggle_input.exists(): return Path(\"../../data/data\")\n        direct = kaggle_input / \"fulldataset\" \n        if direct.exists(): return direct\n        for p in kaggle_input.glob(\"**/fulldataset\"): \n            if p.is_dir(): return p\n        return Path(\"../../data/data\")\n\n    CSV_DIR = _find_csv_dir.__func__()\n    TEST_CSV = \"03_test_holdout.csv\"             \n\n    @staticmethod\n    def fold_csvs(fold: int):\n        return (f\"cv_fold{fold}_train.csv\", f\"cv_fold{fold}_val.csv\")\n\n    @classmethod\n    def fold_dir(cls, fold: int):\n        return cls.OUT_DIR / f\"fold{fold}\"\n\n    KAGGLE_INPUT = Path(\"/kaggle/input\")\n\n    @staticmethod\n    def _default_out_dir():\n        if Path(\"/kaggle/working\").exists():\n            return Path(\"/kaggle/working/fastai\")\n        return Path(\"../outputs/fastai\")\n\n    OUT_DIR = _default_out_dir.__func__()\n    IMG_DIR = OUT_DIR / \"processed/fastai_spectrograms\" \n    LABEL_MAP_PATH = OUT_DIR / \"label_map.json\"\n\nConfig.IMG_DIR.mkdir(parents=True, exist_ok=True)\nConfig.OUT_DIR.mkdir(parents=True, exist_ok=True)\n\n# ============================================================\n# 2. 音频特征工程 (RMS能量检测极速版 + OpenCV + 频率截断)\n# ============================================================\n_global_audio_index = {}\nif Config.KAGGLE_INPUT.exists():\n    for audio_path in Config.KAGGLE_INPUT.rglob(\"*.ogg\"):\n        _global_audio_index[audio_path.name] = audio_path\n        relative_key = f\"{audio_path.parent.name}/{audio_path.name}\"\n        _global_audio_index[relative_key] = audio_path\n\ndef resolve_audio_path(row):\n    fname = str(row['filename'])\n    if fname in _global_audio_index: return _global_audio_index[fname]\n    just_name = Path(fname).name\n    if just_name in _global_audio_index: return _global_audio_index[just_name]\n    return None\n\ndef audio_to_spectrogram(row):\n    audio_path = resolve_audio_path(row)\n    if audio_path is None: return None\n        \n    safe_img_name = str(row['filename']).replace('/', '_') + \".png\"\n    save_path = Config.IMG_DIR / safe_img_name\n    \n    if save_path.exists(): return str(save_path)\n        \n    try:\n        y, sr = librosa.load(audio_path, sr=Config.SAMPLE_RATE, mono=True)\n        frame_length = int(Config.SAMPLE_RATE * Config.CLIP_SECONDS)\n        \n        # 基于 librosa 底层的极速 RMS 能量检测\n        if len(y) > frame_length:\n            hop_length = 512\n            rms = librosa.feature.rms(y=y, frame_length=1024, hop_length=hop_length)[0]\n            max_idx = np.argmax(rms)\n            \n            center_sample = max_idx * hop_length\n            start = max(0, min(center_sample - frame_length // 2, len(y) - frame_length))\n            y = y[start:start + frame_length]\n        else:\n            y = np.pad(y, (0, frame_length - len(y)), mode='constant')\n            \n        # 【优化 1】：Bandpass Filtering\n        mel = librosa.feature.melspectrogram(\n            y=y, sr=Config.SAMPLE_RATE, n_mels=Config.N_MELS, n_fft=1024, hop_length=512,\n            fmin=500,   # 强制屏蔽 500Hz 以下的低频风声/水流声\n            fmax=8000   # 强制屏蔽 8000Hz 以上的高频白噪\n        )\n        mel_db = librosa.power_to_db(mel, ref=np.max)\n        \n        img = (mel_db - mel_db.min()) / (mel_db.max() - mel_db.min() + 1e-8)\n        img = (img * 255).astype(np.uint8)\n        img = np.flipud(img)\n        img_color = cv2.applyColorMap(img, cv2.COLORMAP_VIRIDIS)\n        img_resized = cv2.resize(img_color, Config.IMAGE_SIZE)\n        \n        cv2.imwrite(str(save_path), img_resized)\n        return str(save_path)\n    except Exception as e:\n        print(f\"解析出错 {audio_path}: {e}\")\n        return None\n\ndef apply_multithread(df):\n    with ThreadPoolExecutor(max_workers=os.cpu_count() or 4) as executor:\n        paths = list(tqdm(executor.map(audio_to_spectrogram, df.to_dict('records')), total=len(df)))\n    df['spectrogram_path'] = paths\n    return df\n\n# ============================================================\n# 3. 5 折交叉验证主管道\n# ============================================================\ndef main_cv():\n    print(f\"\\n=== 启动 FastAI 5 折交叉验证 ===\")\n    \n    test_csv = Config.CSV_DIR / Config.TEST_CSV\n    df_test = pd.read_csv(test_csv)\n    print(\"正在预处理独立测试集 (Test Set)...\")\n    df_test = apply_multithread(df_test)\n    df_test = df_test.dropna(subset=['spectrogram_path']).reset_index(drop=True)\n    \n    clean_accs = []\n#for fold in range(1, Config.N_FOLDS + 1):\n    for fold in range(1, 2):\n        print(f\"\\n========== [Fold {fold}] 训练开始 ==========\")\n        fold_out = Config.fold_dir(fold)\n        fold_out.mkdir(parents=True, exist_ok=True)\n        \n        train_name, val_name = Config.fold_csvs(fold)\n        df_fold = pd.concat([\n            pd.read_csv(Config.CSV_DIR / train_name).assign(is_valid=False),\n            pd.read_csv(Config.CSV_DIR / val_name).assign(is_valid=True)\n        ], ignore_index=True)\n        \n        print(f\"  > 提取当前 Fold 图像特征 (带缓存)...\")\n        df_fold = apply_multithread(df_fold)\n        df_fold = df_fold.dropna(subset=['spectrogram_path']).reset_index(drop=True)\n            \n        all_labels = sorted(list(set(df_fold['primary_label'].unique()) | set(df_test['primary_label'].unique())))\n        \n        birds_db = DataBlock(\n            blocks=(ImageBlock, CategoryBlock(vocab=all_labels)),\n            get_x=ColReader('spectrogram_path'),\n            get_y=ColReader('primary_label'),\n            splitter=ColSplitter('is_valid'),\n            item_tfms=[], \n            batch_tfms=[\n                Normalize.from_stats(*imagenet_stats),\n                RandomErasing(p=0.4, sl=0.05, sh=0.15) \n            ]\n        )\n        \n        dls = birds_db.dataloaders(df_fold, bs=32, num_workers=4) \n        classes = list(dls.vocab)\n        \n        if fold == 1:\n            with open(Config.LABEL_MAP_PATH, \"w\") as f:\n                json.dump({name: idx for idx, name in enumerate(classes)}, f, indent=4)\n        \n        print(f\"  > 初始化 ConvNeXt-Small 与高级训练策略...\")\n        \n        # 【优化 2】：convnext_small\n        learn = vision_learner(\n            dls, \n            'convnext_small', \n            metrics=[accuracy, F1Score(average='macro')], \n            loss_func=FocalLossFlat(gamma=2.0), \n            cbs=[MixUp(alpha=0.4)],         \n            path=fold_out\n        ).to_fp16()\n        \n        print(f\"  > 开始微调: 3轮预热 + 12轮深层训练...\")\n        learn.fine_tune(epochs=12, base_lr=2e-3, freeze_epochs=3)\n        \n        learn.export(fold_out / \"fastai_bird_model.pkl\")\n        \n        print(f\"  > 正在对独立测试集进行预测...\")\n        test_dl = learn.dls.test_dl(df_test, with_labels=True)\n        preds, targets = learn.get_preds(dl=test_dl)\n        \n        y_prob, y_pred, y_true = preds.numpy(), preds.argmax(dim=1).numpy(), targets.numpy()\n        test_acc = accuracy_score(y_true, y_pred)\n        clean_accs.append(float(test_acc))\n        print(f\"  > [结果] Fold {fold} 测试集 Clean 准确率: {test_acc:.4f}\\n\")\n        \n        np.savez(\n            fold_out / \"test_predictions.npz\",\n            y_true=y_true, y_pred=y_pred, y_prob=y_prob, \n            classes=np.array(classes), test_filenames=df_test['filename'].values\n        )\n        \n        del learn, dls, birds_db\n        gc.collect()\n        torch.cuda.empty_cache()\n\n    print(\"\\n========== 交叉验证结束 ==========\")\n    clean_arr = np.array(clean_accs)\n    pd.DataFrame({\"fold\": range(1, Config.N_FOLDS + 1), \"clean_acc\": clean_arr}).to_csv(Config.OUT_DIR / \"cv_per_fold.csv\", index=False)\n    print(f\"[汇总] FastAI 终极版 Clean 准确率: {clean_arr.mean():.4f} ± {clean_arr.std(ddof=1):.4f}\")\n\nif __name__ == \"__main__\":\n    main_cv()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-24T04:06:46.07718Z","iopub.execute_input":"2026-07-24T04:06:46.078054Z"}},"outputs":[],"execution_count":null}]}