{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport zipfile\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nfrom sklearn.model_selection import train_test_split\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\nif torch.cuda.is_available():\n    print(f\"GPU count: {torch.cuda.device_count()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:36:20.977461Z","iopub.execute_input":"2025-12-18T11:36:20.977730Z","iopub.status.idle":"2025-12-18T11:36:32.072932Z","shell.execute_reply.started":"2025-12-18T11:36:20.977700Z","shell.execute_reply":"2025-12-18T11:36:32.071917Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 解压训练集\nif not os.path.exists('/kaggle/working/train'):\n    with zipfile.ZipFile('/kaggle/input/dogs-vs-cats/train.zip', 'r') as z:\n        z.extractall('/kaggle/working')\n\nbase_dir = '/kaggle/working/train'\nfilenames = os.listdir(base_dir)\nfilepaths = [os.path.join(base_dir, f) for f in filenames]\ncategories = ['Dog' if f.split('.')[0] == 'dog' else 'Cat' for f in filenames]\nlabels = [1 if c == 'Dog' else 0 for c in categories]\n\ndf_eda = pd.DataFrame({'filename': filenames, 'label': categories})\nprint(df_eda.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:36:34.867885Z","iopub.execute_input":"2025-12-18T11:36:34.868212Z","iopub.status.idle":"2025-12-18T11:36:41.308418Z","shell.execute_reply.started":"2025-12-18T11:36:34.868185Z","shell.execute_reply":"2025-12-18T11:36:41.307462Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(6, 4))\ndf_eda['label'].value_counts().plot.bar(color=['skyblue', 'salmon'])\nplt.title('Class Distribution')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:36:45.372422Z","iopub.execute_input":"2025-12-18T11:36:45.373425Z","iopub.status.idle":"2025-12-18T11:36:45.845123Z","shell.execute_reply.started":"2025-12-18T11:36:45.373379Z","shell.execute_reply":"2025-12-18T11:36:45.844430Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 6))\nsample_idx = random.sample(range(len(filepaths)), 8)\nfor i, idx in enumerate(sample_idx):\n    img = mpimg.imread(filepaths[idx])\n    plt.subplot(2, 4, i+1)\n    plt.imshow(img)\n    plt.title(categories[idx])\n    plt.axis('off')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:36:58.800553Z","iopub.execute_input":"2025-12-18T11:36:58.800895Z","iopub.status.idle":"2025-12-18T11:36:59.531224Z","shell.execute_reply.started":"2025-12-18T11:36:58.800867Z","shell.execute_reply":"2025-12-18T11:36:59.530437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_paths, val_paths, train_labels, val_labels = train_test_split(\n    filepaths, labels, test_size=0.15, random_state=42\n)\n\nIMAGE_SIZE = 128\nBATCH_SIZE = 64\n\ntrain_transform = transforms.Compose([\n    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(brightness=0.1, contrast=0.1),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((IMAGE_SIZE, IMAGE_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\nclass CatDogDataset(Dataset):\n    def __init__(self, paths, labels, transform=None):\n        self.paths = paths\n        self.labels = labels\n        self.transform = transform\n    \n    def __len__(self):\n        return len(self.paths)\n    \n    def __getitem__(self, idx):\n        img = Image.open(self.paths[idx]).convert('RGB')\n        if self.transform:\n            img = self.transform(img)\n        label = torch.tensor(self.labels[idx], dtype=torch.float32)\n        return img, label\n\ntrain_dataset = CatDogDataset(train_paths, train_labels, train_transform)\nval_dataset = CatDogDataset(val_paths, val_labels, val_transform)\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2, pin_memory=True)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n\nprint(f\"训练集: {len(train_dataset)}, 验证集: {len(val_dataset)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:37:15.628947Z","iopub.execute_input":"2025-12-18T11:37:15.629761Z","iopub.status.idle":"2025-12-18T11:37:15.650228Z","shell.execute_reply.started":"2025-12-18T11:37:15.629727Z","shell.execute_reply":"2025-12-18T11:37:15.649424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ConvBlock(nn.Module):\n    def __init__(self, in_channels, out_channels):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, 3, padding=1),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, 2)\n        )\n    def forward(self, x):\n        return self.block(x)\n\nclass CatDogCNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.features = nn.Sequential(\n            ConvBlock(3, 32),    # 128 -> 64\n            ConvBlock(32, 64),   # 64 -> 32\n            ConvBlock(64, 128),  # 32 -> 16\n            ConvBlock(128, 256), # 16 -> 8\n            ConvBlock(256, 512), # 8 -> 4\n        )\n        self.classifier = nn.Sequential(\n            nn.AdaptiveAvgPool2d(1),\n            nn.Flatten(),\n            nn.Linear(512, 256),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.5),\n            nn.Linear(256, 1)\n        )\n    \n    def forward(self, x):\n        x = self.features(x)\n        x = self.classifier(x)\n        return x.squeeze(1)\n\nmodel = CatDogCNN().to(device)\n\n# 如果有多卡，使用 DataParallel\nif torch.cuda.device_count() > 1:\n    model = nn.DataParallel(model)\n    print(f\"Using {torch.cuda.device_count()} GPUs!\")\n\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:37:23.703718Z","iopub.execute_input":"2025-12-18T11:37:23.704313Z","iopub.status.idle":"2025-12-18T11:37:23.921743Z","shell.execute_reply.started":"2025-12-18T11:37:23.704281Z","shell.execute_reply":"2025-12-18T11:37:23.920892Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.BCEWithLogitsLoss()\noptimizer = optim.Adam(model.parameters(), lr=1e-4)\n\nEPOCHS = 30\nCLIP_NORM = 1.0  # 梯度剪裁阈值\n\nhistory = {'train_loss': [], 'train_acc': [], 'val_loss': [], 'val_acc': []}\n\nfor epoch in range(EPOCHS):\n    # --- Training ---\n    model.train()\n    train_loss, train_correct = 0.0, 0\n    for images, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Train]\"):\n        images, labels = images.to(device), labels.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), CLIP_NORM)\n        \n        optimizer.step()\n        \n        train_loss += loss.item() * images.size(0)\n        preds = (torch.sigmoid(outputs) > 0.5).float()\n        train_correct += (preds == labels).sum().item()\n    \n    train_loss /= len(train_loader.dataset)\n    train_acc = train_correct / len(train_loader.dataset)\n\n    model.eval()\n    val_loss, val_correct = 0.0, 0\n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item() * images.size(0)\n            preds = (torch.sigmoid(outputs) > 0.5).float()\n            val_correct += (preds == labels).sum().item()\n    \n    val_loss /= len(val_loader.dataset)\n    val_acc = val_correct / len(val_loader.dataset)\n    \n    history['train_loss'].append(train_loss)\n    history['train_acc'].append(train_acc)\n    history['val_loss'].append(val_loss)\n    history['val_acc'].append(val_acc)\n    \n    print(f\"Epoch {epoch+1}: Train Loss={train_loss:.4f}, Train Acc={train_acc:.4f}, Val Loss={val_loss:.4f}, Val Acc={val_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:37:35.666776Z","iopub.execute_input":"2025-12-18T11:37:35.667295Z","iopub.status.idle":"2025-12-18T11:49:24.448717Z","shell.execute_reply.started":"2025-12-18T11:37:35.667269Z","shell.execute_reply":"2025-12-18T11:49:24.447801Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 4))\nplt.subplot(1, 2, 1)\nplt.plot(history['train_acc'], label='Train Acc')\nplt.plot(history['val_acc'], label='Val Acc')\nplt.title('Accuracy')\nplt.legend()\n\nplt.subplot(1, 2, 2)\nplt.plot(history['train_loss'], label='Train Loss')\nplt.plot(history['val_loss'], label='Val Loss')\nplt.title('Loss')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-18T11:49:46.176732Z","iopub.execute_input":"2025-12-18T11:49:46.177627Z","iopub.status.idle":"2025-12-18T11:49:46.450393Z","shell.execute_reply.started":"2025-12-18T11:49:46.177588Z","shell.execute_reply":"2025-12-18T11:49:46.449803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}