{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":29193,"databundleVersionId":2318453,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 包导入和参数设置","metadata":{}},{"cell_type":"code","source":"import torch\nimport numpy as np\nimport pandas as pd\nfrom torch import nn\nimport torch.optim as optim\nimport torch.nn.functional as F\nimport torchvision.transforms as T\nfrom torch.utils.data import Dataset, DataLoader\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport matplotlib.image as mpimg\nimport os\nfrom PIL import Image\nimport torchvision.models as models\nfrom tqdm import tqdm \nfrom torch.utils.data import random_split\n\nbase_path = '/kaggle/input/classify-leaves'\nout_path = '/kaggle/working'","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:41.740860Z","iopub.execute_input":"2025-03-26T01:08:41.741227Z","iopub.status.idle":"2025-03-26T01:08:41.747021Z","shell.execute_reply.started":"2025-03-26T01:08:41.741198Z","shell.execute_reply":"2025-03-26T01:08:41.746192Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 数据查看","metadata":{}},{"cell_type":"code","source":"\ntrain_df= pd.read_csv(os.path.join(base_path,\"train.csv\"))\nval_df = pd.read_csv(os.path.join(base_path,\"test.csv\"))\n\n# 统计每个类别的数量\nlabel_counts = train_df['label'].value_counts()\n\nprint(train_df.shape,'\\n')\nprint(train_df.info(),'\\n')\nprint(train_df.describe(), \"\\n\")\nprint(train_df.head(),'\\n')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:41.748272Z","iopub.execute_input":"2025-03-26T01:08:41.748498Z","iopub.status.idle":"2025-03-26T01:08:41.813708Z","shell.execute_reply.started":"2025-03-26T01:08:41.748478Z","shell.execute_reply":"2025-03-26T01:08:41.812988Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 饼状图","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(8, 8)) \nplt.pie(\n    label_counts.values[:10], \n    labels=label_counts.index[:10], \n    autopct='%1.1f%%',  # 显示百分比\n    colors=plt.cm.Paired.colors,  # 配色\n    startangle=140, \n    wedgeprops={'edgecolor': 'black'}  # 添加边框\n)\n\n# 设置标题\nplt.title(\"leaves pie\")\n\n# 显示图像\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:41.815125Z","iopub.execute_input":"2025-03-26T01:08:41.815336Z","iopub.status.idle":"2025-03-26T01:08:41.989450Z","shell.execute_reply.started":"2025-03-26T01:08:41.815318Z","shell.execute_reply":"2025-03-26T01:08:41.988490Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 直方图","metadata":{}},{"cell_type":"code","source":"# 绘制直方图\nplt.figure(figsize=(8, 8)) \nsns.barplot(x=label_counts.index[:30], y=label_counts.values[:30], palette=\"viridis\")  # 只画前30个类别\nplt.xticks(rotation=90)  # 旋转x轴标签，防止重叠\nplt.xlabel(\"caterogies\")\nplt.ylabel(\"nums\")\nplt.title(\"leaves barplot\")\n\n# 显示图像\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:41.990742Z","iopub.execute_input":"2025-03-26T01:08:41.990976Z","iopub.status.idle":"2025-03-26T01:08:42.365091Z","shell.execute_reply.started":"2025-03-26T01:08:41.990957Z","shell.execute_reply":"2025-03-26T01:08:42.364147Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 预览图片","metadata":{}},{"cell_type":"code","source":"# 查看前20张图片\npreview_image_paths = train_df['image'][:20]\npreview_image_labels = train_df['label'][:20]\n# 创建子图\nfig, axes = plt.subplots(4,5, figsize=(12, 8))  # 一行多列\n\nfor i,(ax,img_path) in enumerate(zip(axes.flatten(),preview_image_paths)):\n    img = mpimg.imread(os.path.join(\"/kaggle/input/classify-leaves\", img_path))  # 读取图片\n    ax.imshow(img)\n    ax.axis(\"off\")\n    ax.set_title(preview_image_labels[i],y=-0.2)\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:42.365967Z","iopub.execute_input":"2025-03-26T01:08:42.366246Z","iopub.status.idle":"2025-03-26T01:08:43.020196Z","shell.execute_reply.started":"2025-03-26T01:08:42.366225Z","shell.execute_reply":"2025-03-26T01:08:43.019300Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 准备数据","metadata":{}},{"cell_type":"code","source":"# 获取所有唯一的类别（叶子种类）\nunique_labels = train_df[\"label\"].unique()\n\n# 创建 类别 → 索引 的映射\nlabel2idx = {label: idx for idx, label in enumerate(unique_labels)}\n\n# 创建反向映射（id → label）\nidx2label = {v: k for k, v in label2idx.items()}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:43.020958Z","iopub.execute_input":"2025-03-26T01:08:43.021228Z","iopub.status.idle":"2025-03-26T01:08:43.026591Z","shell.execute_reply.started":"2025-03-26T01:08:43.021206Z","shell.execute_reply":"2025-03-26T01:08:43.025759Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 自定义dataset","metadata":{}},{"cell_type":"code","source":"class LeaveDataset(Dataset):\n    def __init__(self, data_df, transform=None):\n        self.data = data_df\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(base_path, self.data.iloc[idx,0])\n        image = Image.open(img_path)\n\n        if self.transform:\n            image = self.transform(image)  # 应用转换\n        \n        label_name = self.data.iloc[idx,1]\n        label = label2idx[label_name]  # 转换为整数索引\n\n        return image, label  # 返回 (图片, 标签)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:43.027557Z","iopub.execute_input":"2025-03-26T01:08:43.027881Z","iopub.status.idle":"2025-03-26T01:08:43.043680Z","shell.execute_reply.started":"2025-03-26T01:08:43.027849Z","shell.execute_reply":"2025-03-26T01:08:43.042810Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 封装DataLoader","metadata":{}},{"cell_type":"code","source":"batch_size=128\n\n# 定义数据转换\ntransform = T.Compose([\n    # T.Resize((224, 224)),  # 调整图片大小\n    T.ToTensor(),  # 转为张量\n    # T.Normalize(mean=[0.5, 0.5, 0.5], std=[0.5, 0.5, 0.5])  # 归一化\n])\n\ntrain_dataset = LeaveDataset(train_df,transform)\n\n# 计算拆分大小\ntotal_size = len(train_dataset)\ntest_size = int(0.2 * total_size)  # 20% 作为测试集\ntrain_size = total_size - test_size  # 剩下的作为训练集\n\n# 随机拆分数据\ntrain_subset, test_subset = random_split(train_dataset, [train_size, test_size])\n\n# 重新创建 DataLoader\ntrain_loader = DataLoader(train_subset, batch_size=batch_size, shuffle=True, num_workers=4)\ntest_loader = DataLoader(test_subset, batch_size=batch_size, shuffle=False, num_workers=4)\n\nprint(\"总批次:\",len(train_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:43.044538Z","iopub.execute_input":"2025-03-26T01:08:43.044822Z","iopub.status.idle":"2025-03-26T01:08:43.062872Z","shell.execute_reply.started":"2025-03-26T01:08:43.044783Z","shell.execute_reply":"2025-03-26T01:08:43.062115Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 定义模型","metadata":{}},{"cell_type":"code","source":"# 加载 ResNet-18（可以换成 resnet34, resnet50, resnet101, resnet152）\nresnet = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)\n\n# 修改最终输出的类别\nnum_classes = len(label_counts)  \nresnet.fc = torch.nn.Linear(resnet.fc.in_features, num_classes)\n\n# 设备类型\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(device)\n\nresnet = resnet.to(device)\n\nresnet","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:43.064917Z","iopub.execute_input":"2025-03-26T01:08:43.065202Z","iopub.status.idle":"2025-03-26T01:08:43.310643Z","shell.execute_reply.started":"2025-03-26T01:08:43.065180Z","shell.execute_reply":"2025-03-26T01:08:43.309890Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 损失函数和优化器","metadata":{"execution":{"iopub.status.busy":"2025-03-25T06:36:33.498912Z","iopub.execute_input":"2025-03-25T06:36:33.499364Z","iopub.status.idle":"2025-03-25T06:36:33.504918Z","shell.execute_reply.started":"2025-03-25T06:36:33.499333Z","shell.execute_reply":"2025-03-25T06:36:33.503272Z"}}},{"cell_type":"code","source":"# 交叉熵损失（适用于分类问题）\ncriterion = nn.CrossEntropyLoss()\n\n# 学习率，动量\nlr,momentum = 0.01,0.9\n\n# 优化器\noptimizer = optim.SGD(resnet.parameters(), lr=lr, momentum=momentum)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:43.311777Z","iopub.execute_input":"2025-03-26T01:08:43.312025Z","iopub.status.idle":"2025-03-26T01:08:43.316746Z","shell.execute_reply.started":"2025-03-26T01:08:43.312005Z","shell.execute_reply":"2025-03-26T01:08:43.316127Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 训练模型","metadata":{}},{"cell_type":"code","source":"# 单次训练\ndef train():\n    resnet.train()\n\n    batch_nums = len(train_loader)  # 批次数\n    batch_size = train_loader.batch_size  # 批量大小\n    size = len(train_loader.dataset)  # 数据集大小\n    \n    train_loss,correct = 0.0, 0.0 # 统计损失和准确率\n\n    p = tqdm(train_loader, desc=\"Training\", unit=\"batch\")\n    \n    for X,y in p:\n        X,y = X.to(device),y.to(device)\n        pred = resnet(X)\n        loss = criterion(pred,y)\n        loss.backward()\n        optimizer.step()\n        optimizer.zero_grad()\n        \n        p.set_postfix(loss=f\"{loss.item():>8f}\")  # 显示损失值\n        \n        train_loss+=loss.item() # 累计每个批次的平均损失\n        correct += (pred.argmax(1) == y).sum().item() # 计算正确预测的数量\n\n    train_loss /= batch_nums\n    correct /= size\n    print(f\"Train Accuracy: {(100*correct):>0.2f}%, Train Avg loss: {train_loss:>8f}\")\n\n    return train_loss,correct\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:43.317598Z","iopub.execute_input":"2025-03-26T01:08:43.317848Z","iopub.status.idle":"2025-03-26T01:08:43.332478Z","shell.execute_reply.started":"2025-03-26T01:08:43.317817Z","shell.execute_reply":"2025-03-26T01:08:43.331713Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n\n# 验证\ndef test():\n    resnet.eval() # 评估模式\n    \n    batch_nums = len(test_loader)  # 批次数\n    batch_size = test_loader.batch_size  # 批量大小\n    size = len(test_loader.dataset)  # 数据集大小\n    \n    test_loss,correct = 0.0, 0.0 # 统计损失和准确率\n\n    with torch.no_grad():\n        \n        for X,y in test_loader:\n            X,y = X.to(device),y.to(device)\n            pred = resnet(X)\n            loss = criterion(pred,y)\n\n            test_loss+=loss.item() # 累计每个批次的平均损失\n            correct += (pred.argmax(1) == y).sum().item() # 计算正确预测的数量\n\n    test_loss /= batch_nums\n    correct /= size\n    print(f\"Test Accuracy: {(100*correct):>0.1f}%, Test Avg loss: {test_loss:>8f}\")\n    \n    return test_loss,correct\n            \n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:08:43.333373Z","iopub.execute_input":"2025-03-26T01:08:43.333665Z","iopub.status.idle":"2025-03-26T01:08:43.351576Z","shell.execute_reply.started":"2025-03-26T01:08:43.333635Z","shell.execute_reply":"2025-03-26T01:08:43.350763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 训练损失和准确率\ntrain_losses,train_accs = [],[]\n\n# 测试损失和准确率\ntest_losses ,test_accs= [],[]\n\nepochs = 20\n\nbest_acc = 0.0  # 记录最佳准确率\nsave_path = 'best_model.pth'  # 保存路径\n\nfor epoch in range(epochs):\n    print(f\"Epoch {epoch+1}\")\n    train_loss,train_acc = train()\n    train_losses.append(train_loss)\n    train_accs.append(train_acc)\n\n    test_loss,test_acc = test()\n    test_losses.append(test_loss)\n    test_accs.append(test_acc)\n\n    # 保存最好的模型\n    if test_acc > best_acc:\n        best_acc = test_acc\n        torch.save(resnet.state_dict(), save_path)  # 仅保存状态字典\n        print(f'New best model saved with accuracy: {best_acc:.4f}')\n\n    print(\"-\"*30)\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:15:37.137104Z","iopub.execute_input":"2025-03-26T01:15:37.137440Z","iopub.status.idle":"2025-03-26T01:17:06.463108Z","shell.execute_reply.started":"2025-03-26T01:15:37.137414Z","shell.execute_reply":"2025-03-26T01:17:06.461885Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 4))\nplt.plot(train_losses, \"r-\", label=\"train_losses\")\nplt.plot(train_accs, \"r--\", label=\"train_accs\")\nplt.plot(test_losses, \"b-\", label=\"test_losses\")\nplt.plot(test_accs, \"b--\", label=\"test_accs\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss/Accuracy\")\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:10:21.143081Z","iopub.execute_input":"2025-03-26T01:10:21.143383Z","iopub.status.idle":"2025-03-26T01:10:21.358917Z","shell.execute_reply.started":"2025-03-26T01:10:21.143357Z","shell.execute_reply":"2025-03-26T01:10:21.357895Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 加载最优模型","metadata":{}},{"cell_type":"code","source":"# 重新定义模型（确保架构一致）\nresnet = models.resnet18()\nresnet.fc = torch.nn.Linear(resnet.fc.in_features, num_classes)\n\n# 加载训练好的权重\nresnet.load_state_dict(torch.load(save_path,weights_only=True))\n\n# 切换到 eval 模式\nresnet.to(device)\nresnet.eval()\nprint(\"模型已加载并设置为评估模式！\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:14:58.521709Z","iopub.execute_input":"2025-03-26T01:14:58.522014Z","iopub.status.idle":"2025-03-26T01:14:58.757223Z","shell.execute_reply.started":"2025-03-26T01:14:58.521992Z","shell.execute_reply":"2025-03-26T01:14:58.756417Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 预测数据","metadata":{}},{"cell_type":"code","source":"resnet.eval()\n\n# 单张图片预测\nimg_val_path = os.path.join(base_path,val_df['image'][0])\nimage_val = Image.open(img_val_path)\nimage_val_tensor = transform(image_val).unsqueeze(0)  # 应用转换 升维\n\n# 预测\nwith torch.no_grad():\n    image_val_tensor = image_val_tensor.to(device)\n    output = resnet(image_val_tensor)\n    probabilities = F.softmax(output, dim=1)  # 转换为概率\n    pred_class = torch.argmax(probabilities).item()\n\nprint(torch.max(probabilities).item()*100,'%')\nprint(idx2label[pred_class])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:15:02.177330Z","iopub.execute_input":"2025-03-26T01:15:02.177617Z","iopub.status.idle":"2025-03-26T01:15:02.202467Z","shell.execute_reply.started":"2025-03-26T01:15:02.177595Z","shell.execute_reply":"2025-03-26T01:15:02.201813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 批量预测\n\nclass LeaveValDataset(Dataset):\n    def __init__(self, data_df, transform=None):\n        self.data = data_df\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(base_path, self.data.iloc[idx,0])\n        image = Image.open(img_path)\n\n        if self.transform:\n            image = self.transform(image)  # 应用转换\n        \n        return image\n\nval_dataset = LeaveValDataset(val_df,transform)\nval_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=4)\nprint(\"val batch num:\",len(val_loader))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:10:21.369430Z","iopub.status.idle":"2025-03-26T01:10:21.369879Z","shell.execute_reply":"2025-03-26T01:10:21.369681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_preds = []\n\nwith torch.no_grad():\n    for inputs in tqdm(val_loader):\n        inputs = inputs.to(device)\n        outputs = resnet(inputs)\n        probs = F.softmax(outputs, dim=1)\n        preds = torch.argmax(probs, dim=1)\n        \n        all_preds.extend(preds.cpu().numpy())\n\nprint(\"批量预测结果长度:\", len(all_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:10:21.370792Z","iopub.status.idle":"2025-03-26T01:10:21.371238Z","shell.execute_reply":"2025-03-26T01:10:21.371010Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pred_labels = [idx2label[pred_id] for pred_id in all_preds] # 转换成label\nval_df['label'] = pred_labels\nval_df.to_csv('submission.csv',index=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-03-26T01:10:21.372359Z","iopub.status.idle":"2025-03-26T01:10:21.372653Z","shell.execute_reply":"2025-03-26T01:10:21.372534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}