{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3362,"databundleVersionId":31148,"sourceType":"competition"}],"dockerImageVersionId":25160,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 【Pytorch】猫狗大战Densenet-121（教程版）\n# 注释就是教程，多看注释！","metadata":{}},{"cell_type":"code","source":"import numpy as np  # 提供高性能的多维数组运算，常用于线性代数、数组操作等数值计算。\nimport pandas as pd  # 强大的数据处理库，适用于CSV等结构化数据的读写和操作，例如 pd.read_csv 用于读取CSV文件。\nimport os  # 提供与操作系统交互的功能，如文件和目录的操作（创建、删除、遍历等）。\nimport torch  # PyTorch框架的核心库，主要用于构建和训练深度学习模型。\nimport torch.nn as nn  # PyTorch中的神经网络模块，封装了构建神经网络的常用层和函数。\nimport cv2  # OpenCV库，用于计算机视觉任务，如图像处理、特征提取和视频操作。\nimport matplotlib.pyplot as plt  # 一个绘图库，适合生成静态、交互式和动态的图表，常用于数据可视化。\nimport torchvision  # PyTorch的计算机视觉库，包含预训练模型、数据集和相关工具。\nfrom torch.utils.data import Dataset, DataLoader, ConcatDataset  # 数据加载和管理模块，Dataset定义数据逻辑，DataLoader处理批量数据加载，ConcatDataset将多个数据集合并。\nfrom torchvision import transforms  # 图像预处理和增强的工具箱，如随机裁剪、归一化等。\nimport copy  # 提供浅拷贝和深拷贝功能，便于管理复杂对象的复制操作。\nimport tqdm  # 一个快速、可视化的进度条工具，适用于长时间运行的循环任务。\nfrom PIL import Image  # Python Imaging Library（Pillow）的核心模块，用于图像的打开、处理和保存。\n\n%matplotlib inline \n# Jupyter Notebook中的魔术命令，用于直接在单元格中显示Matplotlib绘制的图像。\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:32.960216Z","iopub.execute_input":"2024-12-11T15:58:32.960475Z","iopub.status.idle":"2024-12-11T15:58:33.221880Z","shell.execute_reply.started":"2024-12-11T15:58:32.960436Z","shell.execute_reply":"2024-12-11T15:58:33.221298Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 加载数据集\n> 复制我这个笔记本的时候，Kaggle已经帮你弄好了，无脑导入运行代码就行","metadata":{}},{"cell_type":"code","source":"import zipfile\nimport os\n\n# 定义压缩包路径\ntrain_zip_path = '/kaggle/input/train.zip'\ntest_zip_path = '/kaggle/input/test1.zip'\n\n# 定义解压目标路径\ntrain_extract_path = '/kaggle/working/train'\ntest_extract_path = '/kaggle/working/test1'\n\n# 解压训练集文件\nwith zipfile.ZipFile(train_zip_path, 'r') as train_zip:\n    train_zip.extractall(train_extract_path)  # 将内容解压到目标文件夹\n    print(\"训练集解压完成！\")\n\n# 解压测试集文件\nwith zipfile.ZipFile(test_zip_path, 'r') as test_zip:\n    test_zip.extractall(test_extract_path)\n    print(\"测试集解压完成！\")\n","metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:33.222823Z","iopub.execute_input":"2024-12-11T15:58:33.223021Z","iopub.status.idle":"2024-12-11T15:58:43.558701Z","shell.execute_reply.started":"2024-12-11T15:58:33.222987Z","shell.execute_reply":"2024-12-11T15:58:43.557888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 定义数据目录路径\ntrain_dir = '/kaggle/working/train/train'  # 解压后的训练集文件夹\ntest_dir = '/kaggle/working/test1/test1'   # 解压后的测试集文件夹\n\n# 获取文件列表\ntrain_files = os.listdir(train_dir)\ntest_files = os.listdir(test_dir)\n\n# 输出文件信息\nprint(f\"训练集文件数量: {len(train_files)}\")\nprint(f\"测试集文件数量: {len(test_files)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:43.559679Z","iopub.execute_input":"2024-12-11T15:58:43.559979Z","iopub.status.idle":"2024-12-11T15:58:43.584740Z","shell.execute_reply.started":"2024-12-11T15:58:43.559914Z","shell.execute_reply":"2024-12-11T15:58:43.583879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 自定义数据集类，继承自PyTorch的Dataset类\nclass CatDogDataset(Dataset):\n    \"\"\"\n    用于处理猫狗图片的自定义数据集类\n    \"\"\"\n    def __init__(self, file_list, dir, mode='train', transform=None):\n        \"\"\"\n        初始化函数\n        参数:\n        - file_list: 文件名列表，用于指定数据集中的图片文件\n        - dir: 数据目录，存储图片的文件夹路径\n        - mode: 数据集模式（'train'或其他），决定是否附加标签\n        - transform: 数据预处理和增强的变换操作\n        \"\"\"\n        self.file_list = file_list  # 保存文件列表\n        self.dir = dir  # 保存图片存放的目录\n        self.mode = mode  # 数据集模式\n        self.transform = transform  # 数据变换\n\n        # 如果是训练模式，需要根据文件名自动生成标签\n        if self.mode == 'train':\n            # 假设文件名中包含'dog'或'cat'，以此区分标签\n            if 'dog' in self.file_list[0]:\n                self.label = 1  # 狗的标签设为1\n            else:\n                self.label = 0  # 猫的标签设为0\n\n    def __len__(self):\n        \"\"\"\n        返回数据集的长度，即文件列表的长度\n        \"\"\"\n        return len(self.file_list)\n\n    def __getitem__(self, idx):\n        \"\"\"\n        根据索引获取单个数据项\n        参数:\n        - idx: 索引值\n        返回:\n        - 训练模式下：返回图片数据（float32格式）和标签\n        - 非训练模式下：返回图片数据和文件名\n        \"\"\"\n        # 打开图片文件\n        img = Image.open(os.path.join(self.dir, self.file_list[idx]))\n\n        # 如果定义了数据变换操作，则应用这些变换\n        if self.transform:\n            img = self.transform(img)\n\n        # 根据模式返回不同内容\n        if self.mode == 'train':\n            # 转换为NumPy数组并返回图片和标签\n            img = img.numpy()\n            return img.astype('float32'), self.label\n        else:\n            # 转换为NumPy数组并返回图片和文件名\n            img = img.numpy()\n            return img.astype('float32'), self.file_list[idx]\n\n# 定义数据增强和预处理的变换操作\ndata_transform = transforms.Compose([\n    transforms.Resize(256),  # 将图片调整为256x256\n    transforms.ColorJitter(),  # 随机改变亮度、对比度和饱和度\n    transforms.RandomCrop(224),  # 随机裁剪224x224区域\n    transforms.RandomHorizontalFlip(),  # 随机水平翻转\n    transforms.Resize(128),  # 最终调整为128x128大小\n    transforms.ToTensor()  # 转换为张量，并将像素值归一化为[0, 1]\n])\n\n# 将训练集中的猫和狗文件分别分离\ncat_files = [tf for tf in train_files if 'cat' in tf]  # 筛选猫的文件名\ndog_files = [tf for tf in train_files if 'dog' in tf]  # 筛选狗的文件名\n\n# 创建猫和狗数据集实例\ncats = CatDogDataset(cat_files, train_dir, transform=data_transform)\ndogs = CatDogDataset(dog_files, train_dir, transform=data_transform)\n\n# 将猫和狗的数据集合并为一个数据集\ncatdogs = ConcatDataset([cats, dogs])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:43.585866Z","iopub.execute_input":"2024-12-11T15:58:43.586094Z","iopub.status.idle":"2024-12-11T15:58:44.220549Z","shell.execute_reply.started":"2024-12-11T15:58:43.586043Z","shell.execute_reply":"2024-12-11T15:58:44.219862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 从PyTorch的DataLoader类创建数据加载器\n# DataLoader 是 PyTorch 中用来将数据集分批加载到模型的工具，提供了高效的数据加载方式\ndataloader = DataLoader(\n    catdogs,            # 数据集，这里是合并后的猫狗数据集 (ConcatDataset)\n    batch_size=32,      # 每批次加载的数据量，设置为32，即每次训练传入32张图片\n    shuffle=True,       # 是否在每个 epoch 开始时打乱数据集，设置为 True，可以增加模型的泛化能力\n    num_workers=4       # 加载数据时使用的工作线程数，设置为4，用于加速数据加载过程\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:44.221618Z","iopub.execute_input":"2024-12-11T15:58:44.221843Z","iopub.status.idle":"2024-12-11T15:58:44.589538Z","shell.execute_reply.started":"2024-12-11T15:58:44.221793Z","shell.execute_reply":"2024-12-11T15:58:44.588753Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 从 DataLoader 中获取一个批次的数据\n# `iter(dataloader)` 将 DataLoader 转换为迭代器\n# `.next()` 返回迭代器的下一个批次，包含样本（samples）和标签（labels）\nsamples, labels = iter(dataloader).next()\n\n# 创建一个图像显示窗口，设置图像的大小\n# `plt.figure` 用于创建一个新的图形窗口，`figsize` 设置窗口的宽度和高度\nplt.figure(figsize=(16, 24))\n\n# 将样本中的前24张图片组合成一个网格（grid）进行展示\n# `torchvision.utils.make_grid` 会将多张图像拼接成一个大图像网格\n# `samples[:24]` 表示取批次中的前24张图片进行处理\ngrid_imgs = torchvision.utils.make_grid(samples[:24])\n\n# 将图片从 PyTorch 张量格式转换为 NumPy 数组格式，方便后续处理\n# `grid_imgs.numpy()` 将图像张量转为 NumPy 数组，方便使用 NumPy 和 Matplotlib 操作\nnp_grid_imgs = grid_imgs.numpy()\n\n# 图像通道转换（从 PyTorch 格式到 Matplotlib 格式）\n# 在 PyTorch 的张量中，图像格式为 (C, H, W)，即通道、宽度和高度\n# 但是在 Matplotlib 中，图像格式为 (H, W, C)，即高度、宽度和通道\n# 使用 `np.transpose` 方法将轴从 (C, H, W) 转换为 (H, W, C)\nplt.imshow(np.transpose(np_grid_imgs, (1, 2, 0)))\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:44.591414Z","iopub.execute_input":"2024-12-11T15:58:44.591712Z","iopub.status.idle":"2024-12-11T15:58:46.383045Z","shell.execute_reply.started":"2024-12-11T15:58:44.591659Z","shell.execute_reply":"2024-12-11T15:58:46.382221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\ndevice = 'cuda' \n# ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠＠ＩＩＩＩ\n# ＩＩＩ＠＠＠＠＠＠＠＠＠＠ＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠＠ＩＩＩＩ\n# ＩＩＩ＠ＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩ＠＠＠＠＠＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠＠＠＠＠＠＠＠＠＠ＩＩＩＩＩ＠＠＠＠＠＠Ｉ＠＠ＩＩＩＩ\n# ＩＩＩ＠＠＠＠＠＠＠＠＠＠ＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩ＠＠＠＠＠＠Ｉ\n# ＩＩＩ＠ＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩ＠＠ＩＩ＠Ｉ\n# ＩＩＩ＠＠＠＠＠＠＠＠＠＠ＩＩＩ＠＠＠＠＠＠＠＠＠＠＠＠＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠＠＠＠＠＠＠＠＠＠ＩＩＩＩ＠＠＠＠＠＠＠Ｉ＠＠ＩＩ＠Ｉ\n# ＩＩＩ＠ＩＩ＠ＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩ＠＠ＩＩ＠Ｉ\n# ＩＩＩ＠＠Ｉ＠ＩＩ＠Ｉ＠＠ＩＩＩＩＩＩＩＩＩ＠＠＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠Ｉ＠＠＠＠＠＠＠＠＠ＩＩＩＩＩ＠＠＠＠ＩＩ＠＠ＩＩ＠Ｉ\n# ＩＩＩ＠＠Ｉ＠ＩＩ＠Ｉ＠＠ＩＩＩＩＩＩＩＩＩ＠＠＠＠＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠Ｉ＠ＩＩＩＩＩＩ＠＠ＩＩＩＩＩ＠＠Ｉ＠ＩＩ＠＠ＩＩ＠Ｉ\n# ＩＩＩ＠＠Ｉ＠ＩＩ＠Ｉ＠＠ＩＩＩＩＩＩＩＩＩ＠ＩＩ＠＠＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠＠Ｉ＠ＩＩＩＩＩＩＩ＠ＩＩＩＩＩ＠＠Ｉ＠＠Ｉ＠＠ＩＩ＠Ｉ\n# ＩＩＩＩ＠＠＠ＩＩ＠＠＠ＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩ＠ＩＩＩＩ＠＠＠ＩＩＩＩＩＩＩＩＩＩＩ＠＠Ｉ＠ＩＩＩＩＩＩＩ＠ＩＩＩＩ＠＠＠＠＠＠＠＠ＩＩＩ＠Ｉ\n# ＩＩＩＩ＠Ｉ＠ＩＩ＠Ｉ＠ＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩ＠＠＠ＩＩＩＩＩＩＩＩＩＩＩ＠＠Ｉ＠ＩＩＩＩＩＩ＠＠ＩＩＩＩ＠＠＠＠＠＠＠＠＠＠＠＠Ｉ\n# ＩＩ＠＠＠＠＠＠＠＠＠＠＠＠＠ＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩ＠＠ＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩ＠＠＠＠＠＠＠＠＠ＩＩＩＩＩＩＩＩＩ＠＠Ｉ＠＠＠＠Ｉ\n# ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ＠ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ\n# ＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩＩ\n\nmodel = torchvision.models.densenet121(pretrained=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:46.384320Z","iopub.execute_input":"2024-12-11T15:58:46.384554Z","iopub.status.idle":"2024-12-11T15:58:46.670979Z","shell.execute_reply.started":"2024-12-11T15:58:46.384505Z","shell.execute_reply":"2024-12-11T15:58:46.670189Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## `model = torchvision.models.densenet121(pretrained=True)`\n\n### 代码功能概述：\n\n这一行代码的作用是加载一个预训练好的 **DenseNet121** 模型，用于深度学习任务（例如图像分类、特征提取等）。\n\n它是通过 `torchvision.models` 提供的高效工具来实现的。\n\n---\n\n### 逐步解析：\n\n#### 1. `torchvision.models.densenet121`\n\n这是 PyTorch 中 `torchvision` 库提供的 **DenseNet121** 模型结构构造函数。\n\n- **DenseNet** 的简介：\n  - DenseNet（Densely Connected Convolutional Networks）是一种卷积神经网络架构，其核心理念是将每一层的输出直接连接到后续所有层。\n  - 它通过 \"密集连接\"（Dense Connections）减少了特征冗余，同时提高了梯度流的效率。\n  - **DenseNet121** 是 DenseNet 系列的一个具体变体，121 表示该网络包含的总层数。\n\n- **网络结构的特点**：\n  - **密集连接**：每层的输出特征图被传递给后续所有层，形成一个特征图的“累积”。\n  - **高效参数利用**：相比 ResNet，DenseNet 使用更少的参数实现了类似甚至更好的性能。\n  - **传输信息和梯度**：密集连接使得梯度在网络中更容易传递，有助于缓解梯度消失问题。\n\n#### 2. `pretrained=True`\n这一参数表示是否加载一个在大型数据集（如 ImageNet）上预训练过的权重。\n\n- **True（加载预训练模型）**：\n  - 模型的所有参数已基于 ImageNet 数据集训练完成。\n  - 可用于特征提取或迁移学习，显著节省时间和计算资源。\n  - 通常会冻结前几层的参数（不参与训练），保留模型已有的视觉特征提取能力。\n  \n- **False（不加载预训练模型）**：\n  - 初始化一个随机权重的 DenseNet121 模型。\n  - 用于从零开始在自定义数据集上训练。\n\n#### 3. 返回值 `model`\n`model` 是一个 PyTorch 中的 `torch.nn.Module` 实例，表示一个完整的 DenseNet121 神经网络模型。\n\n---\n\n### DenseNet121 的结构细节：\n- **输入**：\n  - 接受 3 通道的彩色图像（如 RGB 图像）。\n  - 默认输入尺寸为 `224x224`。\n  \n- **核心组件**：\n  - **卷积层和批量归一化**：用于特征提取和数据标准化。\n  - **密集块（Dense Blocks）**：每个密集块内部层层连接，累积所有特征图。\n  - **过渡层（Transition Layers）**：减少特征图的大小和数量，以控制模型复杂度。\n  - **全连接层（Fully Connected Layer）**：最后一层用于分类，输出为 1000 个类别（ImageNet 分类任务）。\n\n---\n\n### 迁移学习中的用法：\n加载预训练模型后，可以对其进行以下调整：\n\n1. **特征提取**：\n   - 冻结模型的卷积层，仅训练最后的分类层。\n   - 适合小型数据集，因为大部分特征已经由预训练模型学习。\n\n2. **微调**：\n   - 解冻部分或全部卷积层，对整个模型进行微调。\n   - 适合与原始数据集差异较大的任务，但需要更多计算资源。\n\n---\n\n> **关键点**：  \n> - DenseNet121 是一个强大的卷积神经网络，适合迁移学习和图像分类任务。  \n> - 预训练参数可以大幅减少训练时间，并提高模型的初始性能。\n","metadata":{}},{"cell_type":"code","source":"# 获取 DenseNet121 模型分类器的输入特征数量\n# model.classifier 是 DenseNet121 原始分类器模块\n# .in_features 获取分类器输入层的特征数量（通常是 DenseNet 的最后一个卷积层的输出特征数）\nnum_ftrs = model.classifier.in_features\n\n# 替换 DenseNet121 原始的分类器模块为自定义的全连接层序列\n# 该部分用于适配特定任务（如将 ImageNet 的 1000 类分类改为自定义的 2 类分类）\nmodel.classifier = nn.Sequential(\n    nn.Linear(num_ftrs, 500),  # 第一个全连接层，将特征数量从 num_ftrs 降维到 500\n    nn.Linear(500, 2)         # 第二个全连接层，将 500 个特征进一步降维到 2（对应二分类任务）\n)\n\n# 将模型发送到指定设备（CPU 或 GPU）\n# device 是先前定义的设备变量，通常为 torch.device(\"cuda\") 或 torch.device(\"cpu\")\nmodel = model.to(device)\n\n# 定义损失函数，使用交叉熵损失（CrossEntropyLoss）\n# 交叉熵损失是分类任务中常用的损失函数，适用于目标标签为 one-hot 或整数形式的多分类任务\ncriterion = nn.CrossEntropyLoss()\n\n# 定义优化器，使用 Adam 优化器\n# Adam 是一种自适应学习率优化算法，具有较快的收敛速度\n# model.parameters() 将模型的所有可训练参数传递给优化器\n# lr=0.002 设置学习率（learning rate）\n# amsgrad=True 启用改进的 AMSGrad 算法，提供更稳定的优化过程\noptimizer = torch.optim.Adam(model.parameters(), lr=0.002, amsgrad=True)\n\n# 定义学习率调度器（Scheduler）\n# 使用 MultiStepLR，在指定的训练步数（milestones）时调整学习率\n# milestones=[500, 1000, 1500] 表示在第 500、1000 和 1500 步时分别调整学习率\n# gamma=0.5 表示学习率在每个里程碑时减半\nscheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[500, 1000, 1500], gamma=0.5)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-11T15:58:46.672141Z","iopub.execute_input":"2024-12-11T15:58:46.672339Z","iopub.status.idle":"2024-12-11T15:58:48.613411Z","shell.execute_reply.started":"2024-12-11T15:58:46.672299Z","shell.execute_reply":"2024-12-11T15:58:48.612533Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 定义训练的总轮数\n# 每轮训练会遍历整个数据集一次\nepochs = 1\n\n# 定义全局迭代计数器（iteration），用于统计训练的进度\nitr = 1\n\n# 定义打印间隔，每隔 p_itr 次迭代打印一次训练损失和准确率\np_itr = 50\n\n# 将模型切换到训练模式\n# 这会启用 Dropout 和 BatchNorm 等训练相关的特性\nmodel.train()\n\n# 初始化总损失，用于统计一个打印间隔内的损失\ntotal_loss = 0\n\n# 初始化空列表，用于记录每次打印时的平均损失\nloss_list = []\n\n# 初始化空列表，用于记录每次打印时的准确率\nacc_list = []\n\n# 开始训练过程\n# 外层循环表示训练轮次（epoch）\nfor epoch in range(epochs):\n    # 内层循环遍历数据加载器（dataloader），逐批次加载数据\n    for samples, labels in dataloader:\n        # 将样本和标签移动到指定设备（GPU 或 CPU）\n        samples, labels = samples.to(device), labels.to(device)\n\n        # 优化器梯度清零，避免累积上次计算的梯度\n        optimizer.zero_grad()\n\n        # 前向传播，将输入数据传入模型，获得预测结果\n        output = model(samples)\n\n        # 计算当前批次的损失\n        loss = criterion(output, labels)\n\n        # 反向传播，计算损失对模型参数的梯度\n        loss.backward()\n\n        # 使用优化器更新模型参数\n        optimizer.step()\n\n        # 累加损失，用于计算平均损失\n        total_loss += loss.item()\n\n        # 调度器更新学习率，根据设定的计划调整优化器的学习率\n        scheduler.step()\n\n        # 每隔 p_itr 次迭代打印一次训练损失和准确率\n        if itr % p_itr == 0:\n            # 使用 argmax 获取模型输出中预测的类别\n            pred = torch.argmax(output, dim=1)\n\n            # 比较预测类别与真实标签，计算准确率\n            correct = pred.eq(labels)  # 返回布尔值张量\n            acc = torch.mean(correct.float())  # 转换为浮点数并计算均值\n\n            # 打印当前轮次、迭代次数、平均损失和准确率\n            print('[Epoch {}/{}] Iteration {} -> Train Loss: {:.4f}, Accuracy: {:.3f}'.format(\n                epoch + 1, epochs, itr, total_loss / p_itr, acc))\n\n            # 将当前的平均损失和准确率存入对应的列表\n            loss_list.append(total_loss / p_itr)\n            acc_list.append(acc)\n\n            # 重置 total_loss，为下一个打印间隔重新累加损失\n            total_loss = 0\n\n        # 迭代计数器加一\n        itr += 1\n\n# 绘制训练损失和准确率的曲线\n# 使用 Matplotlib 绘制两个指标的变化趋势\nplt.plot(loss_list, label='loss')  # 绘制损失曲线\nplt.plot(acc_list, label='accuracy')  # 绘制准确率曲线\nplt.legend()  # 显示图例\nplt.title('training loss and accuracy')  # 设置图标题\nplt.show()  # 显示图像\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2024-12-11T16:00:23.675184Z","iopub.execute_input":"2024-12-11T16:00:23.675448Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 定义模型权重文件的保存路径\n# 'ckpt_densenet121_catdog.pth' 是保存文件的名称，通常使用 `.pth` 作为 PyTorch 模型文件的后缀\nfilename_pth = 'ckpt_densenet121_catdog.pth'\n\n# 保存当前模型的参数（权重）\n# torch.save() 是 PyTorch 用于保存模型或参数的方法\n# model.state_dict() 返回模型的所有参数，以字典形式存储\n# 参数保存到指定路径 filename_pth 中\ntorch.save(model.state_dict(), filename_pth)\n\n# 定义测试数据的预处理和增强变换\n# 这些变换将应用于测试集中的每张图片\ntest_transform = transforms.Compose([\n    transforms.Resize((128, 128)),  # 将图片大小调整为 128x128\n    transforms.ToTensor()          # 将图片转为 PyTorch 张量，并归一化为 [0, 1]\n])\n\n# 创建测试数据集\n# CatDogDataset 是自定义的数据集类\n# test_files 是测试集的文件名列表，test_dir 是存储测试图片的目录\n# mode='test' 表示这是测试模式，transform=test_transform 应用前面定义的预处理变换\ntestset = CatDogDataset(test_files, test_dir, mode='test', transform=test_transform)\n\n# 创建测试数据加载器（DataLoader）\n# testset 是前面定义的测试数据集\ntestloader = DataLoader(testset, batch_size=32, shuffle=False, num_workers=4)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 将模型设置为评估模式\n# model.eval() 禁用 Dropout 和 BatchNorm 等训练特性，确保推理时的行为一致\nmodel.eval()\n\n# 初始化文件名列表，用于保存测试图片的文件名\nfn_list = []\n\n# 初始化预测标签列表，用于保存模型对每张测试图片的预测结果\npred_list = []\n\n# 遍历测试数据加载器（testloader）\n# x 是测试数据，fn 是对应的文件名\nfor x, fn in testloader:\n    # 禁用梯度计算，加快推理速度并节省显存\n    with torch.no_grad():\n        # 将测试数据移动到指定设备（CPU 或 GPU）\n        x = x.to(device)\n\n        # 前向传播，通过模型生成预测输出\n        output = model(x)\n\n        # 获取预测类别，argmax 返回每个样本预测结果的最大值索引（即类别标签）\n        pred = torch.argmax(output, dim=1)\n\n        # 将文件名去掉后缀 \".jpg\" 或 \".png\" 等，仅保留文件名主体，存入 fn_list\n        fn_list += [n[:-4] for n in fn]\n\n        # 将预测的类别标签转为 Python 标量（从张量形式转化）并存入 pred_list\n        pred_list += [p.item() for p in pred]\n\n# 创建一个 Pandas DataFrame，存储文件名和对应的预测标签\n# {\"id\": fn_list, \"label\": pred_list} 将文件名和标签组织为两列\nsubmission = pd.DataFrame({\"id\": fn_list, \"label\": pred_list})\n\n# 将 DataFrame 保存为 CSV 文件\n# 文件名为 'preds_densenet121.csv'，index=False 表示不输出索引列\nsubmission.to_csv('preds_densenet121.csv', index=False)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 从测试数据加载器中获取一个批次的样本（不需要标签，忽略 _）\n# iter(testloader) 将 DataLoader 转为迭代器，.next() 获取第一个批次\nsamples, _ = iter(testloader).next()\n\n# 将样本移动到指定设备（GPU 或 CPU），便于模型推理\nsamples = samples.to(device)\n\n# 创建一个图形窗口，用于展示样本图片和预测结果\n# figsize=(24, 16) 设置图形的宽和高，单位为英寸\nfig = plt.figure(figsize=(24, 16))\n\n# 调整子图之间的间距，避免重叠\nfig.tight_layout()\n\n# 对前24张图片进行模型推理\n# 通过模型前向传播，获取每张图片的预测输出\noutput = model(samples[:24])\n\n# 获取预测类别\n# torch.argmax 返回每张图片在类别维度上的最大值索引（即预测的类别标签）\npred = torch.argmax(output, dim=1)\n\n# 将预测结果从张量形式转为 Python 标量列表，便于后续处理\npred = [p.item() for p in pred]\n\n# 定义类别映射字典，将类别索引转换为类别名称\nad = {0: 'cat', 1: 'dog'}\n\n# 遍历前24张图片以及对应的预测结果\nfor num, sample in enumerate(samples[:24]):\n    # 创建子图，布局为4行6列，第 num+1 个位置\n    plt.subplot(4, 6, num + 1)\n\n    # 设置子图标题为预测类别名称\n    plt.title(ad[pred[num]])\n\n    # 隐藏坐标轴，突出显示图像内容\n    plt.axis('off')\n\n    # 将图片从张量转换为 NumPy 数组，便于使用 Matplotlib 显示\n    # 张量格式为 (C, H, W)，需要转为 NumPy 格式\n    sample = sample.cpu().numpy()\n\n    # 转换通道顺序，将 (C, H, W) 调整为 (H, W, C)，符合 Matplotlib 的显示需求\n    plt.imshow(np.transpose(sample, (1, 2, 0)))\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}