{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","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"}],"dockerImageVersionId":30918,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# 导入必要的库\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport os\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.utils import class_weight\n\nimport tensorflow as tf\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import ModelCheckpoint, ReduceLROnPlateau, EarlyStopping\nfrom tensorflow.keras.utils import Sequence\n\n# 设置随机种子保证可重复性\nSEED = 42\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\n# 数据路径设置（Kaggle默认路径）\nBASE_PATH = \"/kaggle/input/aptos2019-blindness-detection\"\nTRAIN_CSV = os.path.join(BASE_PATH, \"train.csv\")\nTEST_CSV = os.path.join(BASE_PATH, \"test.csv\")\nTRAIN_IMG_PATH = os.path.join(BASE_PATH, \"train_images\")\nTEST_IMG_PATH = os.path.join(BASE_PATH, \"test_images\")\n\n# 读取数据\ntrain_df = pd.read_csv(TRAIN_CSV)\ntest_df = pd.read_csv(TEST_CSV)\n\n# 图像预处理函数（使用Ben Graham的预处理方法）\ndef preprocess_image(image_path, sigmaX=10):\n    image = cv2.imread(image_path)\n    if image is None:\n        raise ValueError(f\"无法读取图像: {image_path}\")\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    image = cv2.resize(image, (224, 224))\n    image = cv2.addWeighted(image, 4, cv2.GaussianBlur(image, (0,0), sigmaX), -4, 128)\n    return image\n\n# 加载并预处理训练图像\nX_train = []\ny_train = []\n\nfor i, row in train_df.iterrows():\n    img_path = os.path.join(TRAIN_IMG_PATH, row['id_code'] + '.png')  # 确认训练集是.png格式\n    img = preprocess_image(img_path)\n    X_train.append(img)\n    y_train.append(row['diagnosis'])\n\nX_train = np.array(X_train)\ny_train = np.array(y_train)\n\n# 划分训练集和验证集\nX_train, X_val, y_train, y_val = train_test_split(\n    X_train, y_train, \n    test_size=0.2, \n    random_state=SEED,\n    stratify=y_train\n)\n\n# 数据增强\ntrain_datagen = ImageDataGenerator(\n    rotation_range=20,\n    zoom_range=0.15,\n    width_shift_range=0.2,\n    height_shift_range=0.2,\n    shear_range=0.15,\n    horizontal_flip=True,\n    vertical_flip=True,\n    fill_mode=\"nearest\"\n)\n\nval_datagen = ImageDataGenerator()\n\n# 自定义生成器类\nclass CustomDataGenerator(Sequence):\n    def __init__(self, x, y, batch_size, augmenter=None, shuffle=True):\n        self.x = x\n        self.y = y\n        self.batch_size = batch_size\n        self.augmenter = augmenter\n        self.shuffle = shuffle\n        self.indexes = np.arange(len(x))\n        self.on_epoch_end()\n        super().__init__()\n\n    def __len__(self):\n        return int(np.ceil(len(self.x) / self.batch_size))\n\n    def __getitem__(self, index):\n        batch_indexes = self.indexes[index*self.batch_size : (index+1)*self.batch_size]\n        x_batch = self.x[batch_indexes]\n        y_batch = self.y[batch_indexes]\n        \n        if self.augmenter:\n            x_batch = np.array([self.augmenter.random_transform(img) for img in x_batch])\n            \n        return x_batch, y_batch\n\n    def on_epoch_end(self):\n        if self.shuffle:\n            np.random.shuffle(self.indexes)\n\n# 初始化生成器\ntrain_generator = CustomDataGenerator(\n    X_train, y_train,\n    batch_size=32,\n    augmenter=train_datagen,\n    shuffle=True\n)\n\nval_generator = CustomDataGenerator(\n    X_val, y_val,\n    batch_size=32,\n    augmenter=val_datagen,\n    shuffle=False\n)\n\n# 计算类别权重\nclass_weights = class_weight.compute_class_weight(\n    'balanced',\n    classes=np.unique(y_train),\n    y=y_train\n)\nclass_weights = dict(enumerate(class_weights))\n\n# 构建模型\ndef build_model():\n    base_model = EfficientNetB0(\n        include_top=False,\n        weights='imagenet',\n        input_shape=(224, 224, 3)\n    )\n    x = base_model.output\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.5)(x)\n    predictions = layers.Dense(5, activation='softmax')(x)\n    model = Model(inputs=base_model.input, outputs=predictions)\n    return model\n\nmodel = build_model()\n\n# 编译模型\nmodel.compile(\n    optimizer=Adam(learning_rate=3e-4),\n    loss='sparse_categorical_crossentropy',\n    metrics=['accuracy']\n)\n\n# 回调函数设置\ncheckpoint = ModelCheckpoint(\n    'best_model.keras',\n    monitor='val_loss',\n    verbose=1,\n    save_best_only=True,\n    mode='min'\n)\n\nreduce_lr = ReduceLROnPlateau(\n    monitor='val_loss',\n    factor=0.5,\n    patience=3,\n    min_lr=1e-6,\n    verbose=1\n)\n\nearly_stop = EarlyStopping(\n    monitor='val_loss',\n    patience=10,\n    verbose=1\n)\n\n# 训练参数\nEPOCHS = 20\n\n# 训练模型\nhistory = model.fit(\n    train_generator,\n    validation_data=val_generator,\n    epochs=EPOCHS,\n    class_weight=class_weights,\n    callbacks=[checkpoint, reduce_lr, early_stop]\n)\n\n# 绘制训练曲线\nplt.figure(figsize=(12, 4))\nplt.subplot(1, 2, 1)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.legend()\nplt.title('Loss Evolution')\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history['accuracy'], label='Train Accuracy')\nplt.plot(history.history['val_accuracy'], label='Validation Accuracy')\nplt.legend()\nplt.title('Accuracy Evolution')\nplt.show()\n\n# 加载最佳模型进行预测\nmodel = tf.keras.models.load_model('best_model.keras')  # 使用新格式加载\n\n# 预处理测试数据（关键修改：添加异常处理和多后缀支持）\nX_test = []\nfailed_ids = []\nvalid_extensions = ['.png', '.jpeg', '.jpg']  # 尝试多种图片后缀\n\nfor i, row in test_df.iterrows():\n    img_found = False\n    for ext in valid_extensions:\n        img_path = os.path.join(TEST_IMG_PATH, row['id_code'] + ext)\n        if os.path.exists(img_path):\n            try:\n                img = preprocess_image(img_path)\n                X_test.append(img)\n                img_found = True\n                break\n            except Exception as e:\n                print(f\"Error loading {img_path}: {str(e)}\")\n                failed_ids.append(row['id_code'])\n                img_found = False\n                break\n    if not img_found:\n        print(f\"警告: {row['id_code']} 没有找到有效图像文件，使用全黑占位\")\n        X_test.append(np.zeros((224, 224, 3)))  # 占位黑图\n\nX_test = np.array(X_test)\n\n# 生成预测结果\nprint(\"正在生成预测...\")\npreds = model.predict(X_test)\npred_classes = np.argmax(preds, axis=1)\n\n# 生成提交文件（关键修改：显式使用绝对路径）\nsubmission = pd.DataFrame({\n    'id_code': test_df['id_code'],\n    'diagnosis': pred_classes\n})\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)  # 绝对路径确保写入\n\n# 验证文件生成\nif os.path.exists('/kaggle/working/submission.csv'):\n    print(\"提交文件已成功生成！\")\n    print(\"文件路径: /kaggle/working/submission.csv\")\nelse:\n    print(\"错误: 提交文件生成失败！\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-17T15:09:34.226287Z","iopub.execute_input":"2025-03-17T15:09:34.226493Z","iopub.status.idle":"2025-03-17T15:28:29.018451Z","shell.execute_reply.started":"2025-03-17T15:09:34.226473Z","shell.execute_reply":"2025-03-17T15:28:29.017499Z"}},"outputs":[],"execution_count":null}]}