{"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":113950,"databundleVersionId":13591777,"sourceType":"competition"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 1.配置环境并准备数据集","metadata":{}},{"cell_type":"code","source":"!pip install imagecodecs-lite","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.functional as TF      # 用于进行图像变换的库\nimport torch.nn.functional as F\nimport matplotlib.pyplot as plt\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-homework1\"\nimage = skimage.io.imread(\"/kaggle/input/dl-geo-2025fall-homework1/image/train/22678915_15_1_4.tiff\")\nlabel = skimage.io.imread(\"/kaggle/input/dl-geo-2025fall-homework1/label/train/22678915_15_1_4.tif\")\nfig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2)\nax1.imshow(image)\nax2.imshow(label)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 2.构建数据流","metadata":{}},{"cell_type":"code","source":"def makeDataset(mode):\n    all_tokens = []\n\n    assert mode in ['train', 'val', 'test']\n    image_path = os.path.join(root, \"image\", mode)\n\n    if mode == \"test\":\n        img_tokens = os.listdir(image_path)\n        for img_token in img_tokens:\n            all_tokens.append(os.path.join(image_path, img_token))\n\n        print(f' 在{mode}阶段，我们有{len(all_tokens)}个样本')\n        return all_tokens\n    else:\n        label_path = os.path.join(root, \"label\", mode)\n\n        img_tokens = os.listdir(image_path)\n        img_tokens.sort()\n\n        label_tokens = os.listdir(label_path)\n        label_tokens.sort()\n\n        assert len(img_tokens) == len(label_tokens), \"The number of images is inconsistent with the number of labels\"\n\n        for img_token, label_token in zip(img_tokens, label_tokens):\n            token = (os.path.join(image_path, img_token), os.path.join(label_path, label_token))\n            all_tokens.append(token)\n\n        print(f' 在{mode}阶段，我们有{len(all_tokens)}个样本')\n        return all_tokens\n\n\nclass BuildingDataset(data.Dataset):\n    def __init__(self, mode):\n        self.mode = mode\n        self.data_tokens = makeDataset(mode)\n        assert len(self.data_tokens), \"the data is empty!Please check the root\"\n\n    def __getitem__(self, index):\n        token = self.data_tokens[index]\n        if self.mode == \"test\":\n            image_path = token\n            image = skimage.io.imread(image_path)\n            image = TF.to_pil_image(image)\n            image_name = os.path.splitext(os.path.basename(image_path))[0]\n            image = TF.to_tensor(image)\n            image = TF.normalize(image, mean=[0.33, 0.34, 0.30], std=[0.17, 0.165, 0.17])\n            return image, image_name\n\n        else:\n            image_path, label_path = token\n            image, label = skimage.io.imread(image_path), skimage.io.imread(label_path)\n            image = TF.to_pil_image(image)\n            label = TF.to_pil_image(label)\n            image_name = os.path.splitext(os.path.basename(image_path))[0]\n            if self.mode in [\"val\"]:\n                image = TF.to_tensor(image)\n                label = TF.to_tensor(label)\n                image = TF.normalize(image, mean=[0.33, 0.34, 0.30], std=[0.17, 0.165, 0.17]) # 图像标准化\n            else:\n                \"\"\"\n                TODO：一般来讲，增加数据多样性会提高模型的泛化性能，这一操作叫做即数据增强(data augumentation)。常用的图像增强的方法有：color jittering, roate, vflip和hflip等。\n                请查阅如何用TF库实现数据增强，并在此处进行修改，来增加数据的多样性\n                \"\"\"\n                image = TF.to_tensor(image)\n                label = TF.to_tensor(label)\n                image = TF.normalize(image, mean=[0.33, 0.34, 0.30], std=[0.17, 0.165, 0.17])\n            label = label[0, :, :]\n            label = torch.unsqueeze(label, 0)\n            return image, label\n\n    def __len__(self):\n        return len(self.data_tokens)\n    \n    @staticmethod\n    def get_boundary(mask, thicky=8):\n        # thicky即轮廓的\"厚度\"\n        # 该函数可以用来提取建筑物的轮廓\n        # 将轮廓信息，做为辅助的监督信息会提升模型对建筑物预测的精度，一种简单的设计是这样↓，即我们让模型有两个预测分支，一个是建筑物足迹掩膜（我们所需要的），另一个是建筑物边缘掩膜，并对这两个结果同时计算损失并反向传播\n        #               -----> building prediction <-> building ground truth\n        # image->model->\n        #               -----> boundary prediction <-> boundary ground truth\n        tmp = mask.data.numpy().astype('uint8')\n        tmp = tmp[0]  # 从 [1,w,h] to [w,h]\n        contour, _, = cv2.findContours(tmp, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)\n        boundary = np.zeros_like(tmp)\n        boundary = cv2.drawContours(boundary, contour, -1, 1, thicky)\n        boundary = boundary.astype(np.float)\n        boundary = TF.to_tensor(boundary)\n        return boundary","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3.构建模型","metadata":{}},{"cell_type":"code","source":"class MyCNN(nn.Module):\n    \"\"\"\n    这是一个修改过的FCN模型, FCN(Fully Convolutional Networks)是利用卷积神经网络实现语义分割的开山之作。\n    在使用这个模型的时候，请思考：\n    （1）什么是语义分割？它的特点是什么？\n    （2）FCN为什么这样设计？这样设计的目的是什么？\n     \"\"\"\n    def __init__(self):\n        \n        super().__init__()\n        # 第一层卷积\n        self.layer1 = nn.Sequential(\n            nn.Conv2d(3, 48, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n          nn.Conv2d(48, 48, (3, 3), padding=1),\n          nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/2\n        )\n\n        # 第二层卷积\n        self.layer2 = nn.Sequential(\n            nn.Conv2d(48, 128, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(128, 128, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/4\n        )\n\n        # 第三层卷积\n        self.layer3 = nn.Sequential(\n            nn.Conv2d(128, 192, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(192, 192, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/8\n        )\n\n        # 第四层卷积\n        self.layer4 = nn.Sequential(\n            nn.Conv2d(192, 256, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(256, 256, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/16\n        )\n\n        # 第五层卷积\n        self.layer5 = nn.Sequential(\n            nn.Conv2d(256, 512, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(512, 512, (3, 3), padding=1),\n            nn.ReLU(inplace=True),\n            nn.MaxPool2d(2, stride=2, ceil_mode=True)  # Downsampling 1/32\n        )\n\n        self.score_1 = nn.Conv2d(512, 128, (1, 1))\n        self.score_2 = nn.Conv2d(256, 128, (1, 1))\n        self.score_3 = nn.Conv2d(192, 128, (1, 1))\n        self.score_4 = nn.Conv2d(128, 128, (1, 1))\n\n        self.final_layer = nn.Conv2d(128, 1, (1, 1))\n      \n    def forward(self,x):\n        _,_,h,w = x.shape\n        # 输入的图像为x，通过下面的五个layer的操作，我们发现特征的尺寸在逐渐变小，为什么我们要这么做，这样做的目的是什么？\n        # (提示：从物体尺度和模型计算量角度思考）\n        x1 = self.layer1(x)         # (B,C,H/2,W/2)\n        x2 = self.layer2(x1)        # (B,C,H/4,W/4)\n        x3 = self.layer3(x2)        # (B,C,H/8,W/8)\n        x4 = self.layer4(x3)        # (B,C,H/16,W/16)\n        x5 = self.layer5(x4)        # (B,C,H/32,W/32)\n\n        # 上述操作让我们的特征图尺寸逐渐变小，我们为了得到原始分辨率的预测，在这里我们逐步恢复图像分辨率\n        x5 = self.score_1(x5)       # 在这里，我们在恢复特征图分辨率之前，先用一个卷积进行特征提取，目的有\n        #1）保证不同尺度特征图通道数相同 2）将不同尺度特征图映射到相同的特征空间，使得相加有意义。\n        # 代码默认的方法是双线性插值上采样，你有更好的办法吗，如反卷积等？这些方法会提高模型的精度吗？\n\n        x5 = F.interpolate(input=x5, size=(int(h/16), int(w/16)), mode='bilinear')\n        x4 = self.score_2(x4)\n        x4 += x5\n\n        x4 = F.interpolate(input=x4, size=(int(h/8), int(w/8)), mode='bilinear')\n        x3 = self.score_3(x3)\n        x3 +=x4\n\n        x3 = F.interpolate(input=x3, size=(int(h/4), int(w/4)), mode='bilinear')\n        x2 = self.score_4(x2)\n        x2 += x3\n\n        final_score = self.final_layer(x2)\n        final = F.interpolate(input=final_score, size=(h, w), mode='bilinear')\n\n        return final\n\n# TODO：事实上，语义分割领域目前有非常多的网络可供选择，你会使用它们，并将它们应用到建筑物足迹提取上来吗？","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4.设置评价标准和后处理操作","metadata":{}},{"cell_type":"code","source":"class Evaluator(object):\n    \"\"\"\n    Code is modified based on  https://github.com/jfzhang95/pytorch-deeplab-xception/blob/master/utils/metrics.py#L33\n    \"\"\"\n\n    def __init__(self, num_class):\n        self.num_class = num_class\n        self.confusion_matrix = np.zeros((self.num_class,) * 2)\n\n        self.TP = 0\n        self.FN = 0\n        self.FP = 0\n        self.TN = 0\n\n    def Pixel_Accuracy(self):\n        Acc = np.diag(self.confusion_matrix).sum() / self.confusion_matrix.sum()  # (TP+TN)/(ALL)\n        return Acc\n\n    def Pixel_Accuracy_Class(self):\n        Acc = np.diag(self.confusion_matrix) / self.confusion_matrix.sum(axis=1)\n        Acc = np.nanmean(Acc)\n        return Acc\n\n    def Mean_Intersection_over_Union(self):\n        MIoU = np.diag(self.confusion_matrix) / (\n                np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -\n                np.diag(self.confusion_matrix))\n\n        MIoU = np.nanmean(MIoU)\n\n        return MIoU\n\n    def Frequency_Weighted_Intersection_over_Union(self):\n        freq = np.sum(self.confusion_matrix, axis=1) / np.sum(self.confusion_matrix)\n        iu = np.diag(self.confusion_matrix) / (\n                np.sum(self.confusion_matrix, axis=1) + np.sum(self.confusion_matrix, axis=0) -\n                np.diag(self.confusion_matrix))\n\n        FWIoU = (freq[freq > 0] * iu[freq > 0]).sum()\n        return FWIoU\n\n    def _generate_matrix(self, gt_image, pre_image):\n        mask = (gt_image >= 0) & (gt_image < self.num_class)\n        label = self.num_class * gt_image[mask].astype('int') + pre_image[mask]\n        count = np.bincount(label, minlength=self.num_class ** 2)\n        confusion_matrix = count.reshape(self.num_class, self.num_class)\n        return confusion_matrix\n\n    def add_batch(self, gt_image, pre_image):\n        assert gt_image.shape == pre_image.shape, f\"gt shape is {gt_image.shape}, while pre shape is {pre_image.shape}\"\n        # gt_image = gt_image.astype(np.int8)\n        # pre_image = pre_image.astype(np.int8)\n        self.confusion_matrix += self._generate_matrix(gt_image, pre_image)\n        self.TP = self.confusion_matrix[1, 1]\n        self.FN = self.confusion_matrix[1, 0]\n        self.FP = self.confusion_matrix[0, 1]\n        self.TN = self.confusion_matrix[0, 0]\n\n    def F1(self):\n        return (2 * self.TP) / (2 * self.TP + self.FP + self.FN)\n\n    def Precision(self):\n        return self.TP / (self.TP + self.FP)\n\n    def Recall(self):\n        return self.TP / (self.TP + self.FN)\n\n    def OA(self):\n        return (self.TP + self.TN) / (self.TP + self.FP + self.TN + self.FN)\n\n    def IoU(self):\n        \"\"\"只算前景(1)类的IoU\"\"\"\n        return self.TP / (self.TP + self.FP + self.FN)\n\n    def reset(self):\n        self.confusion_matrix = np.zeros((self.num_class,) * 2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def rle_encoding(img):\n    # 游程长度编码程序\n    pixels = img.T.flatten()\n    pixels = np.concatenate([[0], pixels, [0]])\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    return ' '.join(str(x) for x in runs)\n\n# 模型的输出是建筑物概率值(0-1)，我们需要二值化输出来得到最终的预测结果\ndef process_output(model_out,threshold=0.5):\n    model_out = torch.sigmoid(model_out) # 利用sigmoid 将预测结果转换为概率\n    out_new = model_out.detach().cpu().numpy()\n    out_new = np.where(out_new > threshold, 1, 0)\n    return out_new\n\ndef process_testOutput(model_out, image_name, threshold=0.5):\n    model_out = torch.sigmoid(model_out) # 利用sigmoid 将预测结果转换为概率\n    out_new = model_out.detach().cpu().numpy()\n    out_new = np.where(out_new > threshold, 1, 0)\n    out_new = out_new[0][0]\n    out_new = out_new.astype(np.uint8)\n    # 在这里，我们用两种方式处理预测结果\n    # (1) 保存成二值图，方便我们进行视觉观察\n    skimage.io.imsave(f\"pred/{image_name[0]}.png\", out_new)\n    # (2) 利用游程长度编码进行压缩，存储为csv格式，用于最终Kaggle平台上的提交\n    pre_encoding = rle_encoding(out_new)\n    return pre_encoding","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5.编写训练、验证和测试等Pipeline代码","metadata":{}},{"cell_type":"code","source":"def train(train_dataset, val_dataset, config, model, evaluator):\n    \"\"\"训练代码\"\"\"\n    n_epochs = config['n_epochs'] # 训练次数\n    optimizer = getattr(torch.optim, config['optimizer'])(model.parameters(), **config['optim_hparas']) # 优化器\n    model.train() # 设置模型为训练模式（梯度可以回传）\n    pbar = tqdm(total=n_epochs, desc=\"Train Mode\", unit=\"epoch\")\n    loss = nn.BCEWithLogitsLoss() # 损失函数为二分类交叉熵\n    loss_log = {\"train_loss\": [], \"val_loss\": []}\n    iou_log =  {\"train_iou\": [], \"val_iou\": []}\n    f1_log = {\"train_f1\": [], \"val_f1\": []}\n    oa_log = {\"train_oa\": [], \"val_oa\": []}\n    best_accuracy = 0.0\n    best_state_dict = None\n    for epoch in range(n_epochs):\n        pbar.update()\n        epoch_loss = 0.0\n        for i,(x, label) in enumerate(train_dataset):\n            optimizer.zero_grad() # 将优化器里储存的梯度清零\n            x, label = x.to(device).float(), label.to(device).float()\n            pred = model(x)\n            ce_loss = loss(pred, label) # 计算损失\n            batch_loss = ce_loss.detach().cpu().item()\n            ce_loss.backward() # 损失回传\n            optimizer.step() # 梯度更新\n            loss_log[\"train_loss\"].append(ce_loss.detach().cpu().item())\n            epoch_loss += ce_loss.detach().cpu().item()\n            pred_processed = process_output(pred)\n            evaluator.add_batch(label.detach().cpu().numpy(), pred_processed)\n            pbar.set_postfix(\n                batch=f\"{i}/{len(train_dataset)}\",\n                batch_loss=f\"{batch_loss:.4f}\",\n                epoch=epoch+1\n            )\n        IoU = evaluator.IoU()\n        OA = evaluator.OA()\n        F1 = evaluator.F1()\n        f1_log[\"train_f1\"].append(F1)\n        oa_log[\"train_oa\"].append(OA)\n        iou_log[\"train_iou\"].append(IoU)\n        pbar.set_postfix(\n            epoch_sum_loss=f\"{epoch_loss:.4f}\",\n            epoch_IoU=f\"{IoU:.4f}\",\n            epoch_OA=f\"{OA:.4f}\",\n            epoch_F1=f\"{F1:.4f}\",\n            epoch=epoch + 1\n        )\n        evaluator.reset()\n        pbar.close()\n        val_loss, val_IoU, val_OA, val_F1 = val(val_dataset, model, evaluator)\n        loss_log[\"val_loss\"].append(val_loss)\n        f1_log[\"val_f1\"].append(val_F1)\n        oa_log[\"val_oa\"].append(val_OA)\n        iou_log[\"val_iou\"].append(val_IoU)\n        if val_IoU>best_accuracy:\n            best_accuracy = val_IoU\n            best_state_dict = model.state_dict()\n        pbar = tqdm(total=n_epochs, desc=\"Train Mode\", unit=\"step\", initial=epoch + 1)  # 重启 pbar\n        if best_state_dict is not None:\n            torch.save(best_state_dict, config[\"save_path\"] + \".ckpt\")\n            pbar.write(f\"epoch {epoch + 1}, best model saved. (accuracy={best_accuracy:.4f})\")\n            best_state_dict = None\n    return loss_log, iou_log,oa_log,f1_log,best_accuracy\n\ndef val(dataloader, model, evaluator):\n    \"\"\"验证代码\"\"\"\n    model.eval()\n    pbar = tqdm(total=len(dataloader.dataset), ncols=0, desc=\"Valid Mode\", unit=\" uttr\")\n    loss = nn.BCEWithLogitsLoss() # 损失函数为二分类交叉熵\n    total_loss = 0.0\n    for i, (x,label) in enumerate(dataloader):\n        with torch.no_grad():\n            x, label = x.to(device).float(), label.to(device).float()\n            pred = model(x)\n            pred_processed = process_output(pred)\n            ce_loss = loss(pred,label)\n            total_loss += ce_loss.detach().cpu().item()\n            evaluator.add_batch(label.detach().cpu().numpy(), pred_processed)\n        pbar.update(dataloader.batch_size)\n    IoU = evaluator.IoU()\n    OA = evaluator.OA()\n    F1 = evaluator.F1()\n    pbar.set_postfix(\n        loss=f\"{total_loss :.4f}\",\n        IoU=f\"{IoU :.4f}\",\n        OA=f\"{OA :.4f}\",\n        F1=f\"{F1 :.4f}\",\n    )\n    evaluator.reset()\n    pbar.close()\n    model.train()\n\n    return total_loss / len(dataloader), IoU, OA, F1\n\ndef test(dataloader, model):\n    \"\"\"测试代码\"\"\"\n    model.eval()\n    pbar = tqdm(total=len(dataloader.dataset), ncols=0, desc=\"Valid Mode\", unit=\" uttr\")\n    header = [\"ImageId\", \"EncodedPixels\"]\n    with open(\"pred.csv\", \"w\", newline=\"\") as f:\n        writer = csv.writer(f)\n        writer.writerow(header)\n        for i, (x,image_name) in enumerate(dataloader):\n            with torch.no_grad():\n                x = x.to(device).float()\n                pred = model(x)\n                pre_encoding = process_testOutput(pred,image_name)\n                in_data = [image_name[0], pre_encoding]\n                writer.writerow(in_data)\n            pbar.update(1)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6.模型训练和评估","metadata":{}},{"cell_type":"code","source":"# 超参数的设置\nconfig = {\n    'n_epochs': 100,                                 # 训练次数\n    'batch_size': 8,                                 # mini-batch 的大小\n    'optimizer': 'SGD',                              # 优化算法 (optimizer in torch.optim)\n    'optim_hparas': {                                # 优化器的超参数(取决于你用了什么优化算法)\n        'lr': 0.001,\n        'momentum':0.9                               # SGD的学习率  # SGD的动量\n    },\n    'save_path': '/kaggle/working/models/model.pth'  # 存储路径\n}\nos.makedirs(name='/kaggle/working/models', exist_ok=True)\n# 构建dataset\ntrain_set = BuildingDataset(\"train\")\nval_set = BuildingDataset(\"val\")\ntest_set = BuildingDataset(\"test\")\n\ntrain_loader = data.DataLoader(train_set,batch_size = config[\"batch_size\"], num_workers = 1)\nval_loader = data.DataLoader(val_set,batch_size = config[\"batch_size\"], num_workers = 1)\ntest_loader = data.DataLoader(test_set,batch_size = 1, shuffle=False, num_workers = 1)\n\n# 实例化模型\nmodel = MyCNN().to(device)\nevaluator = Evaluator(num_class=2)\n# 开始训练与验证\nresult = train(train_loader, val_loader, config, model,evaluator)","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.ylabel('BCE loss')\n    plt.title('Learning curve of {}'.format(title))\n    plt.legend()\n    plt.show()\n\nplot_learning_curve(result[0],\"loss\",1) # loss的变化曲线\nplot_learning_curve(result[1],\"iou\",1) # iou的变化曲线\nplot_learning_curve(result[2],\"oa\",1) # oa的变化曲线\nplot_learning_curve(result[3],\"f1\",1) # f1的变化曲线","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6.使用训练好的模型进行测试","metadata":{}},{"cell_type":"code","source":"os.makedirs(\"/kaggle/working/pred\", exist_ok=True)\ncheckPoint=torch.load(\"/kaggle/working/models/model.pth.ckpt\")\nmodel.load_state_dict(checkPoint)\ntest_loader = data.DataLoader(test_set, batch_size = 1, shuffle=True, num_workers = 1)\ntest(test_loader, model) # 下面会提示，UserWarning: *** is a low contrast image，不用管它\n# 执行完成后，可以从左侧下载预测结果(pre.csv)，并将其提交到Kaggle上，Kaggle会反馈给你测试集的评分","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}