{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"},{"sourceId":12722239,"sourceType":"datasetVersion","datasetId":8041124},{"sourceId":12725723,"sourceType":"datasetVersion","datasetId":8043467},{"sourceId":12749005,"sourceType":"datasetVersion","datasetId":8059130},{"sourceId":12779032,"sourceType":"datasetVersion","datasetId":8078999},{"sourceId":12779055,"sourceType":"datasetVersion","datasetId":8079015},{"sourceId":12779099,"sourceType":"datasetVersion","datasetId":8079047},{"sourceId":256281420,"sourceType":"kernelVersion"}],"dockerImageVersionId":31090,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import cv2\nimport numpy as np\nimport os\nimport pandas as pd\nimport random\nimport timm\nimport torch\nimport torch.nn as nn\nfrom albumentations import Compose, Normalize, Resize\nfrom albumentations.pytorch import ToTensorV2\nfrom torch.utils.data import DataLoader, Dataset\nfrom tqdm import tqdm\n\n# ==============================================================================\n# 0. 全局设置\n# ==============================================================================\ndef set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed_all(seed)\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n\nset_seed(42)\n\n# ==============================================================================\n# 1. 预处理函数 (用于实时处理原始测试图片)\n# ==============================================================================\ndef circle_crop(image):\n    gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)\n    _, thresh = cv2.threshold(gray, 10, 255, cv2.THRESH_BINARY)\n    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)\n    if not contours:\n        return image\n    cnt = max(contours, key=cv2.contourArea)\n    x, y, w, h = cv2.boundingRect(cnt)\n    cropped_image = image[y:y + h, x:x + w]\n    return cropped_image\n\ndef apply_ben_graham_preprocessing(image, sigmaX=30):\n    blurred_image = cv2.GaussianBlur(image, (0, 0), sigmaX)\n    processed_image = cv2.addWeighted(image, 4, blurred_image, -4, 128)\n    return processed_image\n\n# ==============================================================================\n# 2. PyTorch 模型与数据类\n# ==============================================================================\n# 这部分与您之前的训练脚本保持一致，确保模型结构相同\ndef replace_batchnorm_with_groupnorm(module, num_groups=32):\n    for name, child in module.named_children():\n        if isinstance(child, nn.BatchNorm2d):\n            num_channels = child.num_features\n            if num_channels % num_groups == 0:\n                setattr(module, name, nn.GroupNorm(num_groups=num_groups, num_channels=num_channels))\n        else:\n            replace_batchnorm_with_groupnorm(child, num_groups)\n\nclass EfficientNetModel(nn.Module):\n    def __init__(self, model_name, pretrained=False):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=pretrained)\n        replace_batchnorm_with_groupnorm(self.model)\n        in_features = self.model.classifier.in_features\n        self.model.classifier = nn.Sequential(\n            nn.Dropout(p=0.5),\n            nn.Linear(in_features, 1)\n        )\n    def forward(self, x):\n        return self.model(x)\n\nclass PseudoLabelDataset(Dataset):\n    def __init__(self, img_paths, image_size):\n        self.img_paths = img_paths\n        # 定义包含完整预处理流程的变换\n        self.transform = Compose([\n            Resize(image_size, image_size),\n            Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n            ToTensorV2(),\n        ])\n\n    def __len__(self):\n        return len(self.img_paths)\n\n    def __getitem__(self, idx):\n        img_path = self.img_paths[idx]\n        try:\n            # 实时进行预处理\n            img = cv2.imread(img_path)\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            img = circle_crop(img)\n            img = apply_ben_graham_preprocessing(img)\n            \n            if self.transform:\n                img = self.transform(image=img)[\"image\"]\n            return img\n        except Exception as e:\n            print(f\"警告: 处理图片 {img_path} 时出错: {e}, 将加载下一张\")\n            return self.__getitem__((idx + 1) % len(self))\n\n# ==============================================================================\n# 3. 预测函数\n# ==============================================================================\ndef generate_predictions(model, loader, device):\n    model.eval()\n    all_preds = []\n    with torch.no_grad():\n        for images in tqdm(loader, desc=\"正在生成预测结果\"):\n            images = images.to(device)\n            outputs = model(images).squeeze()\n            preds_np = outputs.cpu().numpy()\n            all_preds.extend(np.atleast_1d(preds_np))\n    return np.array(all_preds)\n\n# ==============================================================================\n# 4. 主执行逻辑\n# ==============================================================================\nif __name__ == \"__main__\":\n    # --- 步骤 1: 定义路径和参数 ---\n    print(\"--- 步骤 1: 初始化路径和参数 ---\")\n    \n    MODEL_NAME = 'efficientnet_b4'\n    IMAGE_SIZE = 256\n    BATCH_SIZE = 64 # 预测时可以适当增大Batch Size\n    \n    # --- 路径定义 ---\n    # 您在第一阶段训练好的权重文件路径\n    WEIGHTS_PATH = '/kaggle/input/efficientnetb4-newdataset/best_model_kappa_256.pth'\n    \n    # APTOS 2019 竞赛的原始数据路径 (用于读取测试图片)\n    APTOS_DATA_DIR = '/kaggle/input/aptos2019-blindness-detection'\n    TEST_CSV_PATH = os.path.join(APTOS_DATA_DIR, 'test.csv')\n    ORIGINAL_TEST_IMAGES_DIR = os.path.join(APTOS_DATA_DIR, 'test_images')\n    \n    # 输出的伪标签文件路径\n    OUTPUT_CSV_PATH = '/kaggle/working/pseudo_labels.csv'\n\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f\"使用的设备: {device}\")\n    \n    # --- 步骤 2: 初始化模型并加载权重 ---\n    print(\"\\n--- 步骤 2: 加载模型权重 ---\")\n    model = EfficientNetModel(MODEL_NAME, pretrained=False).to(device)\n    \n    if os.path.exists(WEIGHTS_PATH):\n        print(f\"正在从路径加载权重: {WEIGHTS_PATH}\")\n        model.load_state_dict(torch.load(WEIGHTS_PATH))\n    else:\n        print(f\"错误: 权重文件未找到: {WEIGHTS_PATH}\")\n        exit() # 如果找不到权重，则停止脚本\n\n    # --- 步骤 3: 准备测试数据集 ---\n    print(\"\\n--- 步骤 3: 准备测试数据 ---\")\n    test_df = pd.read_csv(TEST_CSV_PATH)\n    # 构建每张原始测试图片的完整路径列表\n    test_image_paths = [os.path.join(ORIGINAL_TEST_IMAGES_DIR, f\"{img_id}.png\") for img_id in test_df['id_code']]\n    \n    dataset = PseudoLabelDataset(test_image_paths, IMAGE_SIZE)\n    loader = DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\n    print(f\"已为 {len(dataset)} 张测试图片创建数据加载器。\")\n\n    # --- 步骤 4: 生成、处理并保存伪标签 ---\n    print(\"\\n--- 步骤 4: 开始生成伪标签 ---\")\n    \n    # 生成连续值的预测\n    raw_predictions = generate_predictions(model, loader, device)\n    \n    # 将连续值转换为整数伪标签 (0-4)\n    pseudo_labels = np.clip(np.round(raw_predictions).astype(int), 0, 4)\n    \n    # 创建新的DataFrame并保存\n    pseudo_df = pd.DataFrame({'id_code': test_df['id_code'], 'diagnosis': pseudo_labels})\n    pseudo_df.to_csv(OUTPUT_CSV_PATH, index=False)\n    \n    print(f\"\\n伪标签生成完毕！\")\n    print(f\"结果已保存至: {OUTPUT_CSV_PATH}\")\n    print(\"现在您可以进入第三阶段，使用这个文件进行最终微调。\")\n\n    print(\"\\n--- 脚本执行结束 ---\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-10T15:22:44.252016Z","iopub.execute_input":"2025-08-10T15:22:44.252338Z"}},"outputs":[],"execution_count":null}]}