{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":113983,"databundleVersionId":13594875,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 1.配置环境并准备数据集\n1. 请确保在右侧**Input**中已经添加本次作业所需要的数据集**深度学习与地理应用-2025秋-作业2-遥感图像分类**（默认会自动添加，如未，则搜索添加即可）\n2. 若以上方法无法添加数据，请手动上传所需数据（可在作业首页下载数据）\n3. 若要使用GPU或TPU，需要手动在右侧**Session options**中指定**ACCELERATOR**，Kaggle为免费用户每周提供30个小时的GPU和20个小时的TPU免费时长，已经足够\n4. 若要托管代码，需要在右侧**Schedule a notebook to run**中保存或者直接点击右上角**Save Version**按钮，Kaggle会后台托管代码自动运行\n5. 详细介绍和其余操作参见课程资源网站","metadata":{}},{"cell_type":"code","source":"# 定义每个地物所对应的类别标签\nlabels = {\"airport\":0, \"bridge\":1, \"church\":2, \"commercial\":3, \"mountain\":4, \"parking\":5, \"pond\":6, \"resort\":7, \"river\":8, \"storagetanks\":9}","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch                                        # pytorch，目前主流的深度学习框架\nfrom torch.utils import data                        # 从torch.utils导入data模块，用来构建pytorch中的数据结构--->dataloader\nfrom torch import nn                                # 从torch导入nn模块，用来提供基本的神经网络接口，如全连接层nn.Linear()\nimport numpy as np                                  # 导入numpy，并将其重命名为np，numpy是一个高效的用来处理矩阵的库\nfrom tqdm import tqdm                               # tqdm是一个可视化代码进程的模块\nimport skimage.io                                   # skimage.io 可以用来读取图像\nimport cv2                                          # 用来处理图像的库\nimport os                                           # os库用来处理磁盘读写过程\nimport csv                                          # 读写csv文件的库\nimport torchvision.transforms as transforms      \nimport torch.nn.functional as F\nimport matplotlib.pyplot as plt\nfrom glob import glob\nimport time\n\n# 为了处理大批量数据，在目前主流的深度学习框架下，数据都是以矩阵的形式表示的\n# 而对于CPU来讲，其对于矩阵数据处理效率欠佳，而GPU(显卡）则擅长处理矩阵，因此深度学习模型都会部署到GPU上\n# 这段代码的意思是，判断目前的设备中是否有安装CUDA（GPU加速模块）。\n# 如果有的话，我们就让模型部署到GPU上，如果没有，我们继续用CPU（速度会慢一点）\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ndevice","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 可视化我们用于训练的遥感影像\nroot = \"/kaggle/input/dl-geo-2025fall-homework2\"\nimage1, image2 = skimage.io.imread(\"/kaggle/input/dl-geo-2025fall-homework2/train/airport_153.jpg\"), skimage.io.imread(\"/kaggle/input/dl-geo-2025fall-homework2/train/bridge_221.jpg\")\nfig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2)\nax1.imshow(image1)\nax2.imshow(image2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2.构建数据流","metadata":{}},{"cell_type":"code","source":"# training 时做 data augmentation\ntrain_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.RandomHorizontalFlip(), # 图像上下翻转\n    transforms.RandomRotation(15), # 图像旋转\n    transforms.ToTensor(), # 将图像转成 Tensor，并把数值 normalize 到 [0,1] (data normalization)\n])\n# testing 时不需要做 data augmentation\ntest_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.ToTensor(),\n])\nclass RsDataset(data.Dataset):\n    def __init__(self, mode):\n        self.mode = mode\n        self.data_tokens = glob(os.path.join(root, f\"{mode}/*.jpg\"))\n    def __getitem__(self, index):\n        image_path = self.data_tokens[index]\n        image = skimage.io.imread(image_path)\n        image_name = os.path.basename(image_path)\n        if self.mode == \"test\":\n          image = test_transform(image)\n          return image,image_name\n\n        elif self.mode == \"val\":\n          image = test_transform(image)\n          label_tag = image_name.split(\"_\")[0]\n          label = labels[label_tag]\n          label = torch.tensor(label)\n          return image,label\n        else:\n          image = train_transform(image)\n          label_tag = image_name.split(\"_\")[0]\n          label = labels[label_tag]\n          label = torch.tensor(label)\n          return image,label\n    def __len__(self):\n        return len(self.data_tokens)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3.构建模型","metadata":{}},{"cell_type":"code","source":"class Classifier(nn.Module):\n    def __init__(self):\n        super(Classifier, self).__init__()\n        # torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride, padding)\n        # torch.nn.MaxPool2d(kernel_size, stride, padding)\n        # input 维度 [3, 600, 600]\n        self.cnn = nn.Sequential(\n            nn.Conv2d(3, 64, 3, 1, 1),  # [64, 600, 600]\n            nn.BatchNorm2d(64),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2, 0),      # [64, 300, 300]\n\n            nn.Conv2d(64, 128, 3, 1, 1), # [128, 300, 300]\n            nn.BatchNorm2d(128),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2, 0),      # [128, 150, 150]\n\n            nn.Conv2d(128, 256, 3, 1, 1), # [256, 150, 150]\n            nn.BatchNorm2d(256),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2, 0),      # [256, 75, 75]\n\n            nn.Conv2d(256, 512, 3, 1, 1), # [512, 75, 75]\n            nn.BatchNorm2d(512),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2, 0),       # [512, 37, 37]\n\n            nn.Conv2d(512, 512, 3, 1, 1), # [512, 37, 37]\n            nn.BatchNorm2d(512),\n            nn.ReLU(),\n            nn.MaxPool2d(2, 2, 0),       # [512, 18, 18]\n        )\n        self.avg_pool = nn.AdaptiveAvgPool2d((4,4)) # [512, 4, 4]\n        self.fc = nn.Sequential(\n            nn.Linear(512*4*4, 1024),\n            nn.ReLU(),\n            nn.Linear(1024, 512),\n            nn.ReLU(),\n            nn.Linear(512, 10)\n        )\n\n    def forward(self, x):\n        out = self.cnn(x)\n        out = self.avg_pool(out)\n        out = out.view(out.size()[0], -1)\n        return self.fc(out)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4.模型训练和评估","metadata":{}},{"cell_type":"code","source":"model = Classifier().cuda()\nloss = nn.CrossEntropyLoss()\nlearning_rate = 0.001\nnum_epoch = 10\nbatch_size = 16\noptimizer = torch.optim.SGD(model.parameters(), lr=learning_rate) # optimizer 使用 SGD","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_set = RsDataset(\"train\")\nval_set = RsDataset(\"val\")\ntest_set = RsDataset(\"test\")\ntrain_loader = data.DataLoader(train_set,batch_size = batch_size, num_workers = 1)\nval_loader = data.DataLoader(val_set, batch_size = batch_size, num_workers = 1)\ntest_loader = data.DataLoader(test_set, batch_size = 1, shuffle=False, num_workers = 1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.makedirs('/kaggle/working/models', exist_ok=True)\n\nloss_log = {\"train_loss\": [], \"val_loss\": []}\nacc_log = {\"train_acc\": [], \"val_acc\": []}\nbest_acc = 0.0\nfor epoch in range(num_epoch):\n    epoch_start_time = time.time()\n    train_acc = 0.0\n    train_loss = 0.0\n    val_acc = 0.0\n    val_loss = 0.0\n    best_acc = 0.0\n    model.train()\n    for i, data in enumerate(train_loader):\n        optimizer.zero_grad()\n        train_pred = model(data[0].cuda())\n        batch_loss = loss(train_pred, data[1].cuda())\n        batch_loss.backward() # 梯度回传\n        optimizer.step()\n        train_acc += np.sum(np.argmax(train_pred.cpu().data.numpy(), axis=1) == data[1].numpy()) # 输出结果是对每一个类别的预测概率，因此需要获取概率最大的类别，然后再与标签计算acc\n        train_loss += batch_loss.item()\n\n    model.eval()\n    with torch.no_grad():\n        for i, data in enumerate(val_loader):\n            val_pred = model(data[0].cuda())\n            batch_loss = loss(val_pred, data[1].cuda())\n\n            val_acc += np.sum(np.argmax(val_pred.cpu().data.numpy(), axis=1) == data[1].numpy())\n            val_loss += batch_loss.item()\n\n        print('[%03d/%03d] %2.2f sec(s) Train Acc: %3.6f Loss: %3.6f | Val Acc: %3.6f loss: %3.6f' % \\\n            (epoch + 1, num_epoch, time.time()-epoch_start_time, \\\n            train_acc/train_set.__len__(), train_loss/train_set.__len__(), val_acc/val_set.__len__(), val_loss/val_set.__len__()))\n    if (val_acc/val_set.__len__())>best_acc:\n        best_acc = (val_acc/val_set.__len__())\n        best_state_dict = model.state_dict()\n    loss_log[\"train_loss\"].append(train_loss/train_set.__len__())\n    loss_log[\"val_loss\"].append(val_loss/val_set.__len__())\n    acc_log[\"train_acc\"].append(train_acc/train_set.__len__())\n    acc_log[\"val_acc\"].append(val_acc/val_set.__len__())\n    if best_state_dict is not None:\n        torch.save(best_state_dict, \"models/model\" + \".ckpt\")\n        print(f\"epoch {epoch + 1}, best model saved. (accuracy={(val_acc/val_set.__len__()):.4f})\")\n        best_state_dict = None","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt # 用来绘制曲线\nfrom matplotlib.pyplot import figure\n\ndef plot_learning_curve(loss_record, title='',y_max=1):\n  total_steps = len(loss_record[f'train_{title}'])\n  x_1 = range(total_steps)\n  x_2 = x_1[::len(loss_record[f'train_{title}']) // len(loss_record[f'val_{title}'])]\n  figure(figsize=(6, 4))\n  plt.plot(x_1, loss_record[f'train_{title}'], c='tab:red', label='train')\n  plt.plot(x_2, loss_record[f'val_{title}'], c='tab:cyan', label='val')\n  plt.ylim(0.0, y_max)\n  plt.xlabel('Training steps')\n  plt.title('Learning curve of {}'.format(title))\n  plt.legend()\n  plt.show()\n\nplot_learning_curve(loss_log,\"loss\",1) # loss的变化曲线\nplot_learning_curve(acc_log,\"acc\",1) # acc的变化曲线","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5.使用训练好的模型进行测试","metadata":{}},{"cell_type":"code","source":"checkPoint=torch.load(\"/kaggle/working/models/model.ckpt\")\nmodel.load_state_dict(checkPoint)\nmodel.eval()\nheader = [\"ID\", \"label\"]\nwith open(\"pred.csv\", \"w\", newline=\"\") as f:\n    writer = csv.writer(f)\n    writer.writerow(header)\n    for i, (x,image_name) in enumerate(test_loader):\n        with torch.no_grad():\n            test_pred = model(x.cuda())\n            test_label = np.argmax(test_pred.cpu().data.numpy(), axis=1)\n            in_data = [image_name[0], test_label[0]]\n            writer.writerow(in_data)\n    print(\"成功得到了测试集的预测结果!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}