{"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":"none","dataSources":[{"sourceId":29193,"databundleVersionId":2318453,"sourceType":"competition"}],"dockerImageVersionId":30918,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\nfrom torch import nn\nimport pandas as pd\nimport numpy as np\nfrom torch.utils.data import Dataset\nfrom torch.utils.data import DataLoader\nfrom torchvision import transforms\nimport os\nfrom PIL import Image\nfrom torch.utils.tensorboard import SummaryWriter\nfrom tqdm import tqdm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:20:44.705579Z","iopub.execute_input":"2025-02-21T08:20:44.705843Z","iopub.status.idle":"2025-02-21T08:21:05.973101Z","shell.execute_reply.started":"2025-02-21T08:20:44.705812Z","shell.execute_reply":"2025-02-21T08:21:05.971871Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Mydata(Dataset):\n    def __init__(self, img_path, csv_path, mode, transform, val_size=0.2):\n        self.img_path = img_path\n        self.transform = transform\n        self.mode = mode\n        #读取图像和标签\n        df = pd.read_csv(csv_path)\n        if self.mode == 'test':\n            df['label'] = 0\n        #绑定图像和标签，方便之后打乱\n        sample = list(zip(df['image'], df['label']))\n        #打乱顺序（如果为测试集则不打乱）\n        if self.mode != 'test':\n            np.random.shuffle(sample)\n        #获取训练集长度\n        train_len = int((1 -val_size) * len(sample))\n        train_data = sample[:train_len]\n        val_data = sample[train_len:]\n        if self.mode == 'train':\n            self.data = train_data\n        if self.mode == 'val':\n            self.data = val_data\n        if self.mode == 'test':\n            self.data = sample\n        self.classes = sorted(set(df['label']))\n        self.class_to_idx = {cls: idx for idx, cls in enumerate(self.classes)}\n\n    def __getitem__(self, idx):\n        img_name, label_class = self.data[idx]\n        img_item = os.path.join(self.img_path, img_name)\n        img = Image.open(img_item)\n        if self.transform:\n            img = self.transform(img)\n        if self.mode == 'test':\n            return img\n        else :\n            label = self.class_to_idx[label_class]\n            return img, label\n\n    def __len__(self):\n        return len(self.data)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T08:21:05.97761Z","iopub.execute_input":"2025-02-21T08:21:05.97793Z","iopub.status.idle":"2025-02-21T08:21:05.986534Z","shell.execute_reply.started":"2025-02-21T08:21:05.977905Z","shell.execute_reply":"2025-02-21T08:21:05.985214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = Mydata( '/kaggle/input/classify-leaves/', '/kaggle/input/classify-leaves/train.csv', mode='train', transform=transforms.Compose([transforms.RandomHorizontalFlip(p=0.5),\n                                                                                                          transforms.ToTensor()]))\nval_dataset = Mydata( '/kaggle/input/classify-leaves/', '/kaggle/input/classify-leaves/train.csv', mode='val', transform=transforms.ToTensor())\ntest_dataset = Mydata( '/kaggle/input/classify-leaves/', '/kaggle/input/classify-leaves/test.csv', mode='test', transform=transforms.ToTensor())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T09:10:11.582998Z","iopub.execute_input":"2025-02-21T09:10:11.583397Z","iopub.status.idle":"2025-02-21T09:10:11.664936Z","shell.execute_reply.started":"2025-02-21T09:10:11.583364Z","shell.execute_reply":"2025-02-21T09:10:11.663783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"len(train_dataset), len(val_dataset), len(test_dataset)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T09:10:13.945659Z","iopub.execute_input":"2025-02-21T09:10:13.946Z","iopub.status.idle":"2025-02-21T09:10:13.954823Z","shell.execute_reply.started":"2025-02-21T09:10:13.945977Z","shell.execute_reply":"2025-02-21T09:10:13.953349Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img, target = train_dataset[1]\nimg, target, train_dataset.classes[target]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T09:10:16.735998Z","iopub.execute_input":"2025-02-21T09:10:16.736346Z","iopub.status.idle":"2025-02-21T09:10:16.922903Z","shell.execute_reply.started":"2025-02-21T09:10:16.736314Z","shell.execute_reply":"2025-02-21T09:10:16.921604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset[1]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T09:10:30.669168Z","iopub.execute_input":"2025-02-21T09:10:30.669497Z","iopub.status.idle":"2025-02-21T09:10:30.695836Z","shell.execute_reply.started":"2025-02-21T09:10:30.669472Z","shell.execute_reply":"2025-02-21T09:10:30.694398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Residual(nn.Module):  \n    def __init__(self, input_channels, num_channels, use_1x1conv=False,\n                 strides=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(input_channels, num_channels, kernel_size=3,\n                               padding=1, stride=strides)\n        self.conv2 = nn.Conv2d(num_channels, num_channels, kernel_size=3,\n                               padding=1)\n        if use_1x1conv:\n            self.conv3 = nn.Conv2d(input_channels, num_channels,\n                                   kernel_size=1, stride=strides)\n        else:\n            self.conv3 = None\n        self.bn1 = nn.BatchNorm2d(num_channels)\n        self.bn2 = nn.BatchNorm2d(num_channels)\n        self.relu = nn.ReLU(inplace=True)\n\n    def forward(self, X):\n        Y = self.relu(self.bn1(self.conv1(X)))\n        Y = self.bn2(self.conv2(Y))\n        if self.conv3:\n            X = self.conv3(X)\n        Y = X + Y\n        return self.relu(Y)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T09:10:39.813138Z","iopub.execute_input":"2025-02-21T09:10:39.81347Z","iopub.status.idle":"2025-02-21T09:10:39.819616Z","shell.execute_reply.started":"2025-02-21T09:10:39.813446Z","shell.execute_reply":"2025-02-21T09:10:39.818682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"b1 = nn.Sequential(nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3),\n                   nn.BatchNorm2d(64), nn.ReLU(),\n                   nn.MaxPool2d(kernel_size=3, stride=2, padding=1))\n\ndef resnet_block(input_channels, num_channels, num_residuals,\n                 first_block=False):\n    blk = []\n    for i in range(num_residuals):\n        if i == 0 and not first_block:\n            blk.append(\n                Residual(input_channels, num_channels, use_1x1conv=True,\n                         strides=2))\n        else:\n            blk.append(Residual(num_channels, num_channels))\n    return blk\n\nb2 = nn.Sequential(*resnet_block(64, 64, 2, first_block=True))\nb3 = nn.Sequential(*resnet_block(64, 128, 2))\nb4 = nn.Sequential(*resnet_block(128, 256, 2))\nb5 = nn.Sequential(*resnet_block(256, 512, 2))\n\nnet = nn.Sequential(b1, b2, b3, b4, b5, nn.AdaptiveAvgPool2d((1, 1)),\n                    nn.Flatten(), nn.Linear(512, 176))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-02-21T09:10:46.142675Z","iopub.execute_input":"2025-02-21T09:10:46.143025Z","iopub.status.idle":"2025-02-21T09:10:46.245231Z","shell.execute_reply.started":"2025-02-21T09:10:46.143001Z","shell.execute_reply":"2025-02-21T09:10:46.243654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def model(net, train_dataset, val_dataset, num_epochs, lr, batch_size, weight_decay):\n    #定义损失函数\n    loss = nn.CrossEntropyLoss()\n    #定义优化器\n    optimizer = torch.optim.Adam(net.parameters(), lr=lr, weight_decay=weight_decay)\n    #定义dataloader\n    train_iter = DataLoader(train_dataset, batch_size=batch_size)\n    val_iter = DataLoader(val_dataset, batch_size=batch_size)\n    #记录日志，为之后画图做准备\n    writer = SummaryWriter(f'logs_{torch.rand(1).item():.3f}')\n    print(f'tensorboard port=logs_{torch.rand(1).item():.3f}')\n    #使用gpu\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f'使用{device}进行训练')\n    #把net移动到gpu上\n    net.to(device)\n    # 检查模型是否在GPU上\n    print(f\"模型在设备上: {next(net.parameters()).device}\")\n    best_acc = 0\n    for epoch in range(num_epochs):\n        #训练\n        net.train()\n        train_loss = []\n        train_acc = []\n        for X, y in tqdm(train_iter):\n            #把数据移动到gpu上\n            X = X.to(device, dtype=torch.float32)\n            y = y.to(device)\n            #梯度清零\n            optimizer.zero_grad()\n            #前向传播\n            y_hat = net(X)\n            #计算损失\n            l = loss(y_hat, y)\n            #反向传播\n            l.backward()\n            #更新参数\n            optimizer.step()\n            #把每个batch的损失添加到列表中\n            train_loss.append(l.item())\n            #计算每个batch的准确率\n            acc = (y_hat.argmax(axis=1) == y).float().mean()\n            #或者这样算准确率\n            # correct = (y_hat.argmax(axis=1) == y).sum()\n            # acc = correct / len(y)\n            #把每个batch的准确率添加到列表中\n            train_acc.append(acc)\n        #计算一个epoch上的损失和平均值，一个epoch上的损失和平均值等于对每个batch上的损失和平均值做平均\n        #因为这里的loss和acc不是张量了，所以不能用torch的方法.mean()，手动算平均值\n        total_train_loss = sum(train_loss) / len(train_loss)\n        total_train_acc = sum(train_acc) / len(train_acc)\n        print(f'epoch：{epoch + 1} / {num_epochs}  训练集loss：{total_train_loss:.5f}  训练集准确率：{total_train_acc:.5f}')\n        writer.add_scalar('train_loss', total_train_loss, epoch)\n\n        #测试\n        net.eval()\n        val_loss = []\n        val_acc = []\n        with torch.no_grad():\n            for X, y in tqdm(val_iter):\n                X = X.to(device, dtype=torch.float32)\n                y = y.to(device)\n                y_hat = net(X)\n                l = loss(y_hat, y)\n                val_loss.append(l.item())\n                acc = (y_hat.argmax(axis=1) == y).float().mean()\n                val_acc.append(acc)\n        total_val_loss = sum(val_loss) / len(val_loss)\n        total_val_acc = sum(val_acc) / len(val_acc)\n        print(f'epoch：{epoch + 1} / {num_epochs}  验证集loss：{total_val_loss:.5f}  验证集准确率：{total_val_acc:.5f}')\n        writer.add_scalar('val_loss', total_val_loss, epoch)\n        writer.add_scalar('val_accuracy', total_val_acc, epoch)\n        #保存模型\n        if total_val_acc > best_acc:\n            best_acc = total_val_acc\n            torch.save(net.state_dict(), '/kaggle/input/classify-leaves/model.pth')\n            print(f'成功保存模型，精度为：{best_acc:.5f}')\n    writer.close","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lr, num_epochs, batch_size, weight_decay = 0.0001, 20, 16, 0.001\nmodel(net, train_dataset, val_dataset, num_epochs, lr, batch_size, weight_decay)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#预测\ntest_iter = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\nnet.load_state_dict(torch.load('/kaggle/input/classify-leaves/model.pth'))\nnet.to(device)\n#输出类别对标签字典\nidx_to_class = {v: k for k, v in train_dataset.class_to_idx.items()}\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\nnet.eval()\nlabels = []\noutputs = []\nwith torch.no_grad():\n    for X in tqdm(test_iter):\n        X = X.to(device)\n        output = net(X)\n        #求出预测类别，把它移到cpu上，把数据类型从tensor转成numpy再变成列表\n        output = output.argmax(axis=1).cpu().numpy().tolist()\n        #append是一个列表一个列表加，extend是把列表解包后加\n        outputs.extend(output)\nfor output in outputs:\n    #把每个类别都转成标签\n    label = idx_to_class[output]\n    labels.append(label)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#保存结果\ntest_data = pd.read_csv('/kaggle/input/classify-leaves/test.csv')\ntest_data['label'] = pd.Series(labels)\ntest_data.to_csv('/kaggle/input/classify-leaves/submission.csv', index=False)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}