{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","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":30673,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"### **题目:使用预训练的DenseNet121模型实现猫狗图像分类**\n\n**要求:**\n1. 使用PyTorch框架加载预训练的DenseNet121模型。\n2. 使用迁移学习的方法,冻结DenseNet121的特征提取层,并替换最后的全连接层以适应猫狗分类任务。\n3. 对猫狗图像数据集进行数据预处理和数据扩充。\n4. 将数据集划分为训练集和验证集,并使用DataLoader进行数据加载。\n5. 定义损失函数和优化器,对模型进行训练和验证。\n6. 使用训练好的模型对测试集进行预测,并计算分类准确率。\n7. 对模型进行微调,尝试解冻部分卷积层并进行训练,观察性能变化。\n8. 使用数据可视化工具(如Matplotlib)对训练过程进行可视化,包括训练和验证的损失曲线、准确率曲线等。\n9. 对模型的性能进行评估,分析错误分类的样本,并给出可能的改进方向。\n\n**数据集:**\n- 使用Kaggle上的猫狗图像数据集:https://www.kaggle.com/c/dogs-vs-cats/data\n- 数据集包含猫和狗两个类别的图像,训练集包含25,000张图像,测试集包含12,500张图像。\n- 数据已经帮你们加载到环境中，只需要按代码解压即可。\n\n**提示:**\n- 可以参考PyTorch官方教程中的迁移学习示例:https://pytorch.org/tutorials/beginner/transfer_learning_tutorial.html\n- 对于数据预处理和数据扩充,可以使用torchvision.transforms模块提供的各种转换函数。\n- 在训练过程中,可以使用学习率调度器(如StepLR或ReduceLROnPlateau)来动态调整学习率。\n- 对于模型微调,可以尝试不同的微调策略,如解冻不同数量的卷积层,或者使用不同的学习率。\n- 使用混淆矩阵和分类报告等工具对模型的性能进行详细评估。\n\n**评估标准:**\n- 在测试集上的分类准确率达到90%以上。\n- 代码实现清晰、规范,并包含必要的注释说明。\n- 提供完整的实验报告,包括实验流程、结果分析和可视化等。","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport torch\nimport torch.nn as nn\nimport cv2\nimport matplotlib.pyplot as plt\nimport torchvision\nfrom torch.utils.data import Dataset, DataLoader, ConcatDataset\nfrom torchvision import transforms\nimport copy\nimport tqdm\nfrom PIL import Image\n\n%matplotlib inline","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:02:44.171113Z","iopub.execute_input":"2024-04-11T02:02:44.171432Z","iopub.status.idle":"2024-04-11T02:02:51.612221Z","shell.execute_reply.started":"2024-04-11T02:02:44.171408Z","shell.execute_reply":"2024-04-11T02:02:51.611197Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:02:51.613889Z","iopub.execute_input":"2024-04-11T02:02:51.614392Z","iopub.status.idle":"2024-04-11T02:02:51.621353Z","shell.execute_reply.started":"2024-04-11T02:02:51.614364Z","shell.execute_reply":"2024-04-11T02:02:51.620480Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"! unzip /kaggle/input/dogs-vs-cats/train.zip\n! unzip /kaggle/input/dogs-vs-cats/test1.zip","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:02:51.622371Z","iopub.execute_input":"2024-04-11T02:02:51.622638Z","iopub.status.idle":"2024-04-11T02:03:10.819272Z","shell.execute_reply.started":"2024-04-11T02:02:51.622615Z","shell.execute_reply":"2024-04-11T02:03:10.818144Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_dir = '/kaggle/working/train'\ntest_dir = '/kaggle/working/train'\ntrain_files = os.listdir(train_dir)\ntest_files = os.listdir(test_dir)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:03:10.821414Z","iopub.execute_input":"2024-04-11T02:03:10.821793Z","iopub.status.idle":"2024-04-11T02:03:10.857029Z","shell.execute_reply.started":"2024-04-11T02:03:10.821762Z","shell.execute_reply":"2024-04-11T02:03:10.856279Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 数据的均值和标准差","metadata":{}},{"cell_type":"code","source":"dog_vs_cat_mean = (0.49446443 ,0.4565001,  0.41672936)\ndog_vs_cat_std = (0.21931112 ,0.21457843, 0.21322015)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:03:17.584383Z","iopub.execute_input":"2024-04-11T02:03:17.585123Z","iopub.status.idle":"2024-04-11T02:03:17.590818Z","shell.execute_reply.started":"2024-04-11T02:03:17.585085Z","shell.execute_reply":"2024-04-11T02:03:17.589078Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 定义数据处理函数：\n\n使用torchvision.transforms进行图像增强：https://0809zheng.github.io/2021/11/30/transforms.html","metadata":{}},{"cell_type":"code","source":"# dog 标签映射为1 ，cat 标签映射为0\nclass CatDogDataset(Dataset):\n    def __init__(self, file_list, dir, mode='train', transform = None):\n        self.file_list = file_list\n        self.dir = dir\n        self.mode= mode\n        self.transform = transform\n        if self.mode == 'train':\n            if 'dog' in self.file_list[0]:\n                self.label = 1\n            else:\n                self.label = 0\n            \n    def __len__(self):\n        return len(self.file_list)\n    \n    def __getitem__(self, idx):\n        img = Image.open(os.path.join(self.dir, self.file_list[idx]))\n        if self.transform:\n            img = self.transform(img)\n        if self.mode == 'train':\n            img = img.numpy()\n            return img.astype('float32'), self.label\n        else:\n            img = img.numpy()\n            return img.astype('float32'), self.file_list[idx]\n        \n        \n\ndata_transform = transforms.Compose([\n    transforms.Resize([256,256]),\n    transforms.RandomHorizontalFlip(0.5),\n    transforms.ToTensor(),\n    transforms.Normalize(dog_vs_cat_mean, dog_vs_cat_std),\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\ncats = CatDogDataset(cat_files, train_dir, transform = data_transform)\ndogs = CatDogDataset(dog_files, train_dir, transform = data_transform)\n\ncatdogs = ConcatDataset([cats, dogs])","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:07:39.572120Z","iopub.execute_input":"2024-04-11T02:07:39.572520Z","iopub.status.idle":"2024-04-11T02:07:39.592484Z","shell.execute_reply.started":"2024-04-11T02:07:39.572485Z","shell.execute_reply":"2024-04-11T02:07:39.591448Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 定义数据迭代器：","metadata":{}},{"cell_type":"code","source":"dataloader = DataLoader(catdogs, batch_size = 64, shuffle=True, num_workers=4) ## 把数据装入迭代器，","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:07:46.067764Z","iopub.execute_input":"2024-04-11T02:07:46.068609Z","iopub.status.idle":"2024-04-11T02:07:46.073366Z","shell.execute_reply.started":"2024-04-11T02:07:46.068578Z","shell.execute_reply":"2024-04-11T02:07:46.072392Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 开始迭代数据集，可视化前25条数据","metadata":{}},{"cell_type":"code","source":"for ddd in dataloader:\n    break","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:07:47.985606Z","iopub.execute_input":"2024-04-11T02:07:47.985959Z","iopub.status.idle":"2024-04-11T02:07:49.049969Z","shell.execute_reply.started":"2024-04-11T02:07:47.985933Z","shell.execute_reply":"2024-04-11T02:07:49.048755Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"samples, labels = ddd\nplt.figure(figsize=(16,24))\ngrid_imgs = torchvision.utils.make_grid(samples[:24])\nnp_grid_imgs = grid_imgs.numpy()\n# in tensor, image is (batch, width, height), so you have to transpose it to (width, height, batch) in numpy to show it.\nplt.imshow(np.transpose(np_grid_imgs, (1,2,0)))","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:07:54.875510Z","iopub.execute_input":"2024-04-11T02:07:54.875879Z","iopub.status.idle":"2024-04-11T02:07:55.869247Z","shell.execute_reply.started":"2024-04-11T02:07:54.875851Z","shell.execute_reply":"2024-04-11T02:07:55.868359Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 设置训练硬件为GPU，以及加载预训练模型 DenseNet121","metadata":{}},{"cell_type":"code","source":"# device = 'cuda'   ## 制定设备\ndevice=torch.device(\"cuda\"if torch.cuda.is_available() else \"cpu\")\nmodel = torchvision.models.densenet121() ## 加载预训练模型 densenet121\nmodel","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:10:15.847738Z","iopub.execute_input":"2024-04-11T02:10:15.848092Z","iopub.status.idle":"2024-04-11T02:10:16.048679Z","shell.execute_reply.started":"2024-04-11T02:10:15.848067Z","shell.execute_reply":"2024-04-11T02:10:16.047731Z"},"collapsed":true,"jupyter":{"outputs_hidden":true},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:10:35.188593Z","iopub.execute_input":"2024-04-11T02:10:35.188949Z","iopub.status.idle":"2024-04-11T02:10:35.195335Z","shell.execute_reply.started":"2024-04-11T02:10:35.188924Z","shell.execute_reply":"2024-04-11T02:10:35.194205Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 冻结模型的预训练层：","metadata":{}},{"cell_type":"code","source":"model","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:12:51.432965Z","iopub.execute_input":"2024-04-11T02:12:51.433340Z","iopub.status.idle":"2024-04-11T02:12:51.447583Z","shell.execute_reply.started":"2024-04-11T02:12:51.433311Z","shell.execute_reply":"2024-04-11T02:12:51.446527Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"## 获取vgg16的 features 层\nds121 = model.features\n# 将vgg16的features 层参数冻结，不对其进行更新\nfor param in ds121.parameters():\n    param.requires_grad_(False)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:12:33.189652Z","iopub.execute_input":"2024-04-11T02:12:33.190783Z","iopub.status.idle":"2024-04-11T02:12:33.197911Z","shell.execute_reply.started":"2024-04-11T02:12:33.190745Z","shell.execute_reply":"2024-04-11T02:12:33.196875Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 修改模型的全连接层","metadata":{}},{"cell_type":"code","source":"num_ftrs = model.classifier.in_features\nmodel.classifier = nn.Sequential(\n    nn.Linear(num_ftrs, 500),\n    nn.Linear(500, 2)\n)\n\n\nmodel = model.to(device)\n","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:14:14.237588Z","iopub.execute_input":"2024-04-11T02:14:14.238262Z","iopub.status.idle":"2024-04-11T02:14:14.446911Z","shell.execute_reply.started":"2024-04-11T02:14:14.238228Z","shell.execute_reply":"2024-04-11T02:14:14.445881Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 设置优化器和损失函数：","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss()  ### 设置损失函数\noptimizer = torch.optim.Adam(model.parameters(), lr=0.003, amsgrad=True)\n","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:17:32.446407Z","iopub.execute_input":"2024-04-11T02:17:32.447066Z","iopub.status.idle":"2024-04-11T02:17:32.454624Z","shell.execute_reply.started":"2024-04-11T02:17:32.447033Z","shell.execute_reply":"2024-04-11T02:17:32.453684Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 设置动态学习率：\n\n    `torch.optim.lr_scheduler.MultiStepLR` 这个学习率调度器。\n\n    在训练神经网络时,学习率(Learning Rate)是一个重要的超参数。合适的学习率能够帮助模型更快更好地收敛。但是如果学习率固定不变,可能会影响模型的训练效果。因此,我们常常需要在训练过程中动态调整学习率。\n\n    `MultiStepLR` 就是一种常用的学习率调度方法。它的作用是:在设定的若干个 epoch 时刻,降低学习率。\n\n    具体来说,`MultiStepLR` 接收以下参数:\n\n    - `optimizer`: 所使用的优化器,如 SGD、Adam 等\n    - `milestones`: 表示学习率下降时的 epoch 索引数组,如 [50,100,150] \n    - `gamma`: 学习率下降的乘法因子,默认为 0.1\n\n    `MultiStepLR` 的工作流程如下:\n\n    1. 最初学习率为 `optimizer` 中设置的初始学习率 \n    2. 当 epoch 达到 `milestones` 中的值时,学习率乘以 `gamma` 系数进行下调\n    3. 重复步骤2,直到训练结束\n\n    举个例子,假设:\n    - 初始学习率为 0.1\n    - milestones = [30,80]  \n    - gamma = 0.1\n\n    则学习率的变化为:\n    - epoch 0~29: 学习率 0.1\n    - epoch 30~79: 学习率 0.01  (0.1*0.1)\n    - epoch 80~结束: 学习率0.001 (0.01*0.1)\n\n    这样,模型在一开始可以用较大的学习率快速收敛,而后在接近最优解时以较小的学习率微调。`MultiStepLR` 提供了一种简单而有效的学习率动态调度策略。\n\n    当然,除了 `MultiStepLR`,PyTorch 还提供了其他的学习率调度方法,如 `StepLR`、`ExponentialLR`、`CosineAnnealingLR` 等,可以根据实际需求选择。","metadata":{}},{"cell_type":"code","source":"scheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[500,1000,1500], gamma=0.5)","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:17:36.201731Z","iopub.execute_input":"2024-04-11T02:17:36.202468Z","iopub.status.idle":"2024-04-11T02:17:36.206936Z","shell.execute_reply.started":"2024-04-11T02:17:36.202439Z","shell.execute_reply":"2024-04-11T02:17:36.206000Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 定义模型训练相关内容：","metadata":{}},{"cell_type":"code","source":"epochs =  3 #制定迭代的次数\nitr = 1\np_itr = 200\n\nmodel.train()    ### 开启模型的训练模型\n\ntotal_loss = 0\nloss_list = []\nacc_list = []\n\nfor epoch in range(epochs):\n    for samples, labels in dataloader:\n        samples, labels = samples.to(device),labels.to(device)    ### 把数据加载对应的device中\n        optimizer.zero_grad()### 将梯度置为0\n        output = model(samples)### 开始前向传播\n        loss = criterion(output, labels)### 开始计算损失\n        loss.backward() ### 开始反向传播\n        optimizer.step()### 开始更新参数                            \n        total_loss += loss.item()\n        scheduler.step()\n        \n        if itr%p_itr == 0:\n            pred = torch.argmax(output, dim=1)\n            correct = pred.eq(labels)\n            acc = torch.mean(correct.float())\n            print('[Epoch {}/{}] Iteration {} -> Train Loss: {:.4f}, Accuracy: {:.3f}'.format(epoch+1, epochs, itr, total_loss/p_itr, acc))\n            loss_list.append(total_loss/p_itr)\n            acc_list.append(acc)\n            total_loss = 0\n            \n        itr += 1\n\nplt.plot(loss_list.to(\"cpu\"), label='loss')\nplt.plot(acc_list.to(\"cpu\"), label='accuracy')\nplt.legend()\nplt.title('training loss and accuracy')\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2024-04-11T02:25:51.357016Z","iopub.execute_input":"2024-04-11T02:25:51.357391Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 保存模型","metadata":{}},{"cell_type":"code","source":"filename_pth = 'ckpt_densenet121_catdog_loss_{:.4f}_acc_{:.4f}.pth'.format(total_loss/p_itr,acc)\ntorch.save(xxxxxx, filename_pth)   ### 保存模型","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 处理测试数据","metadata":{}},{"cell_type":"code","source":"test_transform = transforms.Compose([\n    transforms.Resize(xxxx),\n    xxxxxxx ## 转成张量\n])\n\ntestset = CatDogDataset(test_files, test_dir, mode='test', transform = test_transform) ### 把数据传入到处理函数中\ntestloader = DataLoader(xxxxx, batch_size = xxxx, shuffle=False, num_workers=4)        ### 设置迭代器","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"#### 开始获取图片进行预测","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef visualize_predictions(model, dataloader, device, class_names, num_images=5):\n    model.eval()  # 将模型设置为评估模式\n    images_so_far = 0\n    fig = plt.figure(figsize=(10, 10))\n\n    with torch.no_grad():\n        for inputs, _ in dataloader:\n            inputs = inputs.to(device)\n            outputs = model(inputs)\n            _, preds = torch.max(outputs, 1)\n\n            for j in range(inputs.size(0)):\n                images_so_far += 1\n                ax = plt.subplot(num_images//2, 2, images_so_far)\n                ax.axis('off')\n                ax.set_title(f'Predicted: {class_names[preds[j]]}')\n                imshow(inputs.cpu().data[j])\n\n                if images_so_far == num_images:\n                    return\n\n# 显示图片的函数\ndef imshow(img):\n    img = img / 2 + 0.5  # 反归一化\n    npimg = img.numpy()\n    plt.imshow(np.transpose(npimg, (1, 2, 0)))\n    plt.show()\n\nclass_names=[]\n    \n# 调用可视化预测函数\nvisualize_predictions(model, test_loader, device, classes, num_images=6)","metadata":{},"execution_count":null,"outputs":[]}]}