{"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":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\nimport shutil\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:36:06.750031Z","iopub.execute_input":"2025-04-10T17:36:06.750344Z","iopub.status.idle":"2025-04-10T17:36:06.757295Z","shell.execute_reply.started":"2025-04-10T17:36:06.750321Z","shell.execute_reply":"2025-04-10T17:36:06.756622Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 解压训练集和测试集\n!unzip -q /kaggle/input/dogs-vs-cats/train.zip -d /kaggle/working/\n!unzip -q /kaggle/input/dogs-vs-cats/test1.zip -d /kaggle/working/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:32:11.485127Z","iopub.execute_input":"2025-04-10T17:32:11.485527Z","iopub.status.idle":"2025-04-10T17:32:27.950502Z","shell.execute_reply.started":"2025-04-10T17:32:11.4855Z","shell.execute_reply":"2025-04-10T17:32:27.949329Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\n# 检查训练集目录\nprint(\"训练集文件示例：\", os.listdir(\"/kaggle/working/train\")[:5])\n\n# 检查测试集目录\nprint(\"测试集文件示例：\", os.listdir(\"/kaggle/working/test1\")[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:37:55.263364Z","iopub.execute_input":"2025-04-10T17:37:55.264026Z","iopub.status.idle":"2025-04-10T17:37:55.287227Z","shell.execute_reply.started":"2025-04-10T17:37:55.263999Z","shell.execute_reply":"2025-04-10T17:37:55.286396Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 按文件名移动文件到对应目录\nos.makedirs(\"/kaggle/working/train/cats\", exist_ok=True)\nos.makedirs(\"/kaggle/working/train/dogs\", exist_ok=True)\n# 遍历源目录中的文件（排除目录）\nfor filename in os.listdir('/kaggle/working/train'):\n    src = os.path.join('/kaggle/working/train', filename)\n    \n    # 条件1：仅处理文件（跳过目录）\n    if not os.path.isfile(src):\n        continue\n    \n    # 条件2：仅处理图片文件（按扩展名过滤）\n    if not filename.lower().endswith(('.jpg', '.jpeg', '.png', '.bmp')):\n        continue\n    \n    # 按文件名分类\n    if 'cat' in filename.lower():\n        dst = os.path.join('/kaggle/working/train/cats', filename)\n        shutil.move(src, dst)\n    elif 'dog' in filename.lower():\n        dst = os.path.join('/kaggle/working/train/dogs', filename)\n        shutil.move(src, dst)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:43:36.921881Z","iopub.execute_input":"2025-04-10T17:43:36.923011Z","iopub.status.idle":"2025-04-10T17:43:37.59156Z","shell.execute_reply.started":"2025-04-10T17:43:36.922976Z","shell.execute_reply":"2025-04-10T17:43:37.590864Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 导入必要库\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torchvision import transforms, models, datasets\nfrom torch.utils.data import DataLoader, random_split\nimport matplotlib.pyplot as plt\nfrom PIL import Image\nimport numpy as np\nfrom sklearn.metrics import confusion_matrix\nimport seaborn as sns","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:43:40.893702Z","iopub.execute_input":"2025-04-10T17:43:40.894352Z","iopub.status.idle":"2025-04-10T17:43:40.899069Z","shell.execute_reply.started":"2025-04-10T17:43:40.894327Z","shell.execute_reply":"2025-04-10T17:43:40.898286Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 设置随机种子保证可复现\ntorch.manual_seed(42)\nnp.random.seed(42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:17:25.916199Z","iopub.execute_input":"2025-04-10T17:17:25.916757Z","iopub.status.idle":"2025-04-10T17:17:25.926561Z","shell.execute_reply.started":"2025-04-10T17:17:25.916706Z","shell.execute_reply":"2025-04-10T17:17:25.925701Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 数据预处理与加载\n# ----------------------\n# Kaggle数据集路径配置\ndata_dir = '/kaggle/working/train/'  # 原始数据路径\nbatch_size = 4  # 根据GPU内存调整\n\n# 数据增强与归一化\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),        # ResNet的标准输入尺寸\n    transforms.RandomHorizontalFlip(),    # 随机水平翻转\n    transforms.RandomRotation(15),        # 随机旋转\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],      # ImageNet统计值\n        std=[0.229, 0.224, 0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:44:09.544343Z","iopub.execute_input":"2025-04-10T17:44:09.545207Z","iopub.status.idle":"2025-04-10T17:44:09.550621Z","shell.execute_reply.started":"2025-04-10T17:44:09.54518Z","shell.execute_reply":"2025-04-10T17:44:09.549731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 创建数据集\nfull_dataset = datasets.ImageFolder(\n    root=data_dir,\n    transform=transform\n)\n\n# 划分训练集和验证集（80%训练，20%验证）\ntrain_size = int(0.8 * len(full_dataset))\nval_size = len(full_dataset) - train_size\ntrain_dataset, val_dataset = random_split(full_dataset, [train_size, val_size])\n\n# 创建数据加载器\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=batch_size,\n    shuffle=True,\n    num_workers=2\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=batch_size,\n    shuffle=False,\n    num_workers=2\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:44:28.752759Z","iopub.execute_input":"2025-04-10T17:44:28.753627Z","iopub.status.idle":"2025-04-10T17:44:28.818768Z","shell.execute_reply.started":"2025-04-10T17:44:28.753597Z","shell.execute_reply":"2025-04-10T17:44:28.817857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 模型定义（使用预训练ResNet）\n# ----------------------\n# 加载预训练ResNet18\nmodel = models.resnet18(weights='IMAGENET1K_V1')\n\n# 冻结所有卷积层参数（迁移学习常用技巧）\nfor param in model.parameters():\n    param.requires_grad = False\n\n# 替换最后一层全连接层（适配二分类任务）\nnum_features = model.fc.in_features\nmodel.fc = nn.Sequential(\n    nn.Linear(num_features, 256),\n    nn.ReLU(),\n    nn.Dropout(0.5),\n    nn.Linear(256, 2)  # 二分类输出\n)\n\n# 将模型移动到GPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:44:23.775418Z","iopub.execute_input":"2025-04-10T17:44:23.775802Z","iopub.status.idle":"2025-04-10T17:44:24.039301Z","shell.execute_reply.started":"2025-04-10T17:44:23.775775Z","shell.execute_reply":"2025-04-10T17:44:24.038445Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 训练配置\n# ----------------------\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.fc.parameters(), lr=0.001)  # 只训练最后一层\nepochs = 5  # 完整训练可设置为10-15\n\n# 记录训练过程\ntrain_losses = []\nval_losses = []\naccuracies = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:44:31.807951Z","iopub.execute_input":"2025-04-10T17:44:31.80879Z","iopub.status.idle":"2025-04-10T17:44:31.814147Z","shell.execute_reply.started":"2025-04-10T17:44:31.808759Z","shell.execute_reply":"2025-04-10T17:44:31.813483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 训练循环\n# ----------------------\nfor epoch in range(epochs):\n    # 训练阶段\n    model.train()\n    running_loss = 0.0\n    for images, labels in train_loader:\n        images = images.to(device)\n        labels = labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item() * images.size(0)\n    \n    # 计算训练损失\n    epoch_loss = running_loss / len(train_loader.dataset)\n    train_losses.append(epoch_loss)\n    \n    # 验证阶段\n    model.eval()\n    val_loss = 0.0\n    correct = 0\n    total = 0\n    \n    with torch.no_grad():\n        for images, labels in val_loader:\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item() * images.size(0)\n            \n            _, predicted = torch.max(outputs.data, 1)\n            total += labels.size(0)\n            correct += (predicted == labels).sum().item()\n    \n    # 计算验证指标\n    val_loss = val_loss / len(val_loader.dataset)\n    val_losses.append(val_loss)\n    accuracy = 100 * correct / total\n    accuracies.append(accuracy)\n    \n    # 打印进度\n    print(f\"Epoch {epoch+1}/{epochs}\")\n    print(f\"Train Loss: {epoch_loss:.4f} | Val Loss: {val_loss:.4f} | Accuracy: {accuracy:.2f}%\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:44:34.181469Z","iopub.execute_input":"2025-04-10T17:44:34.181799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 结果可视化\n# ----------------------\n# 绘制训练曲线\nplt.figure(figsize=(12, 4))\nplt.subplot(1, 2, 1)\nplt.plot(train_losses, label='Train Loss')\nplt.plot(val_losses, label='Val Loss')\nplt.legend()\nplt.title('Loss Curve')\n\nplt.subplot(1, 2, 2)\nplt.plot(accuracies, label='Accuracy')\nplt.legend()\nplt.title('Accuracy Curve')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:21:27.677335Z","iopub.execute_input":"2025-04-10T17:21:27.678261Z","iopub.status.idle":"2025-04-10T17:21:28.078542Z","shell.execute_reply.started":"2025-04-10T17:21:27.678225Z","shell.execute_reply":"2025-04-10T17:21:28.077645Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 模型保存\n# ----------------------\ntorch.save(model.state_dict(), 'resnet_dogcat.pth')","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ----------------------\n# 测试推理（示例）\n# ----------------------\ndef predict_image(img_path):\n    # 加载图像\n    img = Image.open(img_path).convert('RGB')\n    \n    # 预处理\n    transform = transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize(\n            mean=[0.485, 0.456, 0.406],\n            std=[0.229, 0.224, 0.225])\n    ])\n    \n    img_tensor = transform(img).unsqueeze(0).to(device)\n    \n    # 预测\n    model.eval()\n    with torch.no_grad():\n        output = model(img_tensor)\n        _, predicted = torch.max(output, 1)\n    \n    class_names = ['cat', 'dog']\n    return class_names[predicted.item()]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:21:39.924042Z","iopub.execute_input":"2025-04-10T17:21:39.924639Z","iopub.status.idle":"2025-04-10T17:21:39.930358Z","shell.execute_reply.started":"2025-04-10T17:21:39.924596Z","shell.execute_reply":"2025-04-10T17:21:39.929594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 递归列出所有子目录\nfor root, dirs, files in os.walk('/kaggle/input/dog-vs-cat/'):\n    print(f\"目录：{root}\")\n    print(f\"包含文件：{files[:5]}\")  # 显示前5个文件","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:27:54.798843Z","iopub.execute_input":"2025-04-10T17:27:54.799184Z","iopub.status.idle":"2025-04-10T17:27:54.806144Z","shell.execute_reply.started":"2025-04-10T17:27:54.799159Z","shell.execute_reply":"2025-04-10T17:27:54.80517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import random\n\n# 获取测试集所有图片路径\ntest_dir = '/kaggle/input/dog-vs-cat/'\nall_test_images = [os.path.join(test_dir, f) for f in os.listdir(test_dir) if f.endswith('.jpg')]\n\n# 随机选择一张图片\nrandom_image = random.choice(all_test_images)\nprint(f\"随机测试图片：{random_image}\")\nprint(f\"预测结果：{predict_image(random_image)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-04-10T17:26:26.832319Z","iopub.execute_input":"2025-04-10T17:26:26.833246Z","iopub.status.idle":"2025-04-10T17:26:26.84941Z","shell.execute_reply.started":"2025-04-10T17:26:26.833219Z","shell.execute_reply":"2025-04-10T17:26:26.848303Z"}},"outputs":[],"execution_count":null}]}