{"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":"gpu","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31041,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, glob\nimport numpy as np\nimport tensorflow as tf\nfrom sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\nfrom keras.layers import TFSMLayer\nfrom keras.layers import TFSMLayer, Input, Dropout, Dense\nfrom tensorflow.keras import layers, models, regularizers, callbacks\nfrom keras.models import Model\nfrom tensorflow import keras\n\nimport zipfile\nimport os\n\nprint(\"✅ \")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-06-06T01:53:02.389826Z","iopub.execute_input":"2025-06-06T01:53:02.390072Z","iopub.status.idle":"2025-06-06T01:53:16.383007Z","shell.execute_reply.started":"2025-06-06T01:53:02.390027Z","shell.execute_reply":"2025-06-06T01:53:16.382245Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 预处理\n\n# 定义解压目标目录（可自定义）\ntrain_zip_path = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/train.zip'\ntest_zip_path = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/test.zip'\n\nextract_dir = '/kaggle/working/'\n\n# 解压 train.zip\nwith zipfile.ZipFile(train_zip_path, 'r') as zip_ref:\n    zip_ref.extractall(extract_dir)\n\n# 解压 test.zip\nwith zipfile.ZipFile(test_zip_path, 'r') as zip_ref:\n    zip_ref.extractall(extract_dir)\n\nprint(\"✅ \")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T01:53:16.384715Z","iopub.execute_input":"2025-06-06T01:53:16.385166Z","iopub.status.idle":"2025-06-06T01:53:31.018445Z","shell.execute_reply.started":"2025-06-06T01:53:16.385148Z","shell.execute_reply":"2025-06-06T01:53:31.017792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 1. 参数设置\n# ===============================\nDATA_DIR = \"/kaggle/working/train\"\nIMG_SIZE = 256\nBATCH_SIZE = 64\nSEED = 42\n\nprint(\"✅ \")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T01:53:31.019138Z","iopub.execute_input":"2025-06-06T01:53:31.019401Z","iopub.status.idle":"2025-06-06T01:53:31.023866Z","shell.execute_reply.started":"2025-06-06T01:53:31.019378Z","shell.execute_reply":"2025-06-06T01:53:31.023175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 2. 数据准备\n# ===============================\n# 获取图像路径与标签（dog=1，cat=0）\nall_images = glob.glob(os.path.join(DATA_DIR, \"*.jpg\"))\nlabels = [1 if \"dog\" in os.path.basename(p) else 0 for p in all_images]\n\n# 划分训练/验证集（stratify 保持标签分布）\ntrain_paths, val_paths, train_labels, val_labels = train_test_split(\n    all_images, labels, test_size=0.15, stratify=labels, random_state=SEED\n)\n\n# 图像解码与预处理函数\ndef decode_img(path, label):\n    img = tf.io.read_file(path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = img / 255.0\n    return img, label\n\n# 构建 tf.data.Dataset\ndef build_dataset(paths, labels, is_train=True):\n    ds = tf.data.Dataset.from_tensor_slices((paths, labels))\n    ds = ds.map(decode_img, num_parallel_calls=tf.data.AUTOTUNE)\n    if is_train:\n        ds = ds.shuffle(1024)\n    return ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\ntrain_ds = build_dataset(train_paths, train_labels)\nval_ds = build_dataset(val_paths, val_labels, is_train=False)\n\nprint(\"✅ \")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T01:53:31.024672Z","iopub.execute_input":"2025-06-06T01:53:31.024975Z","iopub.status.idle":"2025-06-06T01:53:32.549981Z","shell.execute_reply.started":"2025-06-06T01:53:31.024927Z","shell.execute_reply":"2025-06-06T01:53:32.549188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 3. 构建模型\n# ===============================\n\nfrom tensorflow.keras.applications import MobileNetV3Large, EfficientNetB0, \\\nResNet50V2, MobileNetV3Small, NASNetMobile\nfrom tensorflow.keras import layers, models, regularizers\nimport tensorflow as tf\n\ninputs = tf.keras.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n\n\nbase_model = NASNetMobile(\n    input_shape=(IMG_SIZE, IMG_SIZE, 3),\n    include_top=False,\n    weights=\"imagenet\"\n)\n\nbase_model.trainable = False\nfor layer in base_model.layers[:-30]:\n    layer.trainable = False\n\nx = base_model(inputs, training=False)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dense(16, activation='relu', kernel_regularizer=regularizers.l2(1e-4))(x)\nx = layers.Dropout(0.2)(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\n\nmodel = models.Model(inputs, outputs)\n\n# 编译模型\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-5),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\nmodel.summary()\n\nprint(\"✅ \")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-06T01:53:32.550712Z","iopub.execute_input":"2025-06-06T01:53:32.550915Z","execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===============================\n# 4. 模型训练\n# ===============================\n\n\n# ✅ 设置 EarlyStopping 回调（监控验证集 loss）\nearly_stop = callbacks.EarlyStopping(\n    monitor='val_loss',\n    patience=1,\n    restore_best_weights=True\n)\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    # epochs=20,\n    epochs=50,\n    callbacks=[early_stop]\n)\n\nprint(\"✅ \")\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'], label=\"Train Acc\")\nplt.plot(history.history['val_accuracy'], label=\"Val Acc\")\nplt.title(\"Accuracy\")\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history['loss'], label=\"Train Loss\")\nplt.plot(history.history['val_loss'], label=\"Val Loss\")\nplt.title(\"Loss\")\nplt.legend()\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 保存为 keras 原生格式（推荐）\nmodel.save('/kaggle/working/NASNetMobile.keras')\n\nprint(\"✅ \")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm.notebook import tqdm\nimport math\n\n# ===============================\n# 5. 生成预测文件\n# ===============================\n\n# test_dir = \"/kaggle/working/test\"\n\n# MODEL_SAVE_PATH = '/kaggle/working/dense32_mbt2.keras'\n# model = tf.keras.models.load_model(MODEL_SAVE_PATH)\n\n# 测试数据\ntest_dir = \"/kaggle/working/test\"\ntest_paths = sorted(glob.glob(os.path.join(test_dir, \"*.jpg\")), key=lambda x: int(os.path.basename(x).split('.')[0]))\n\n# 构建测试集\ndef build_test_ds(paths):\n    ds = tf.data.Dataset.from_tensor_slices(paths)\n    ds = ds.map(lambda p: decode_img(p, 0)[0], num_parallel_calls=tf.data.AUTOTUNE)\n    return ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\ntest_ds = build_test_ds(test_paths)\n\n# 模型预测\npreds = model.predict(test_ds)\npreds = preds.ravel()\npreds = preds.clip(min=0.005, max=0.995)   # 裁剪一下两头\n\nprint(\"✅ \")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"预测最大值：{preds.max():.4f}\")\nprint(f\"预测最小值：{preds.min():.4f}\")\nprint(f\"预测均值：{preds.mean():.4f}\")\nplt.hist(preds, bins=100)\nplt.title(\"Test Prediction Distribution\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 写入提交文件\nimport pandas as pd\nsubmission = pd.DataFrame({\n    \"id\": [int(os.path.basename(p).split('.')[0]) for p in test_paths],\n    \"label\": preds\n})\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"✅ \")\n","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 删除工作区文件,不然比赛提交会耗时保存\n\nimport os\nimport shutil\n\ndef delete_folder_contents(folder_path):\n    \"\"\"删除文件夹及其所有内容\"\"\"\n    try:\n        # 删除文件夹内容\n        for filename in os.listdir(folder_path):\n            file_path = os.path.join(folder_path, filename)\n            try:\n                if os.path.isfile(file_path) or os.path.islink(file_path):\n                    os.unlink(file_path)\n                elif os.path.isdir(file_path):\n                    shutil.rmtree(file_path)\n            except Exception as e:\n                print(f'删除 {file_path} 失败。原因: {e}')\n        \n        # 删除空文件夹\n        os.rmdir(folder_path)\n        print(f\"文件夹 '{folder_path}' 已成功删除。\")\n    except FileNotFoundError:\n        print(f\"文件夹 '{folder_path}' 不存在。\")\n    except PermissionError:\n        print(f\"没有权限删除文件夹 '{folder_path}'。\")\n    except OSError as e:\n        print(f\"删除文件夹时出错: {e}\")\n\n# 要删除的文件夹列表\nfolders_to_delete = ['/kaggle/working/train', '/kaggle/working/test']\n\n# 删除每个文件夹\nfor folder in folders_to_delete:\n    delete_folder_contents(folder)\n\nprint(\"✅ \")","metadata":{"trusted":true,"execution":{"execution_failed":"2025-06-06T01:53:40.663Z"}},"outputs":[],"execution_count":null}]}