{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":45867,"databundleVersionId":6924515,"sourceType":"competition"},{"sourceId":6774400,"sourceType":"datasetVersion","datasetId":3895136},{"sourceId":6774553,"sourceType":"datasetVersion","datasetId":3898019},{"sourceId":6874344,"sourceType":"datasetVersion","datasetId":3950227},{"sourceId":10098897,"sourceType":"datasetVersion","datasetId":6228476},{"sourceId":10099422,"sourceType":"datasetVersion","datasetId":6228871},{"sourceId":10100127,"sourceType":"datasetVersion","datasetId":6229444},{"sourceId":10100617,"sourceType":"datasetVersion","datasetId":6229816},{"sourceId":10100646,"sourceType":"datasetVersion","datasetId":6229838},{"sourceId":10100693,"sourceType":"datasetVersion","datasetId":6229870},{"sourceId":10150460,"sourceType":"datasetVersion","datasetId":6266319},{"sourceId":10161128,"sourceType":"datasetVersion","datasetId":6274424},{"sourceId":10207611,"sourceType":"datasetVersion","datasetId":6308447},{"sourceId":10210996,"sourceType":"datasetVersion","datasetId":6310951},{"sourceId":10215556,"sourceType":"datasetVersion","datasetId":6314297},{"sourceId":10220907,"sourceType":"datasetVersion","datasetId":6318387},{"sourceId":3729,"sourceType":"modelInstanceVersion","modelInstanceId":2656,"modelId":312}],"dockerImageVersionId":30805,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true},"papermill":{"default_parameters":{},"duration":144.597815,"end_time":"2024-01-02T18:27:49.10704","environment_variables":{},"exception":null,"input_path":"__notebook__.ipynb","output_path":"__notebook__.ipynb","parameters":{},"start_time":"2024-01-02T18:25:24.509225","version":"2.4.0"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\n# 离线安装pyvips\nos.system(\"yes | dpkg -i --force-depends /kaggle/input/pyvips-python-and-deb-package-gpu/linux_packages/archives/*.deb 2>&1 >/dev/null\")\nos.system(\"pip install pyvips -f /kaggle/input/pyvips-python-and-deb-package-gpu/python_packages/ --no-index\")","metadata":{"_kg_hide-output":true,"papermill":{"duration":59.488106,"end_time":"2024-01-02T18:26:27.381336","exception":false,"start_time":"2024-01-02T18:25:27.89323","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nos.environ['VIPS_CONCURRENCY'] = '4'\nos.environ['VIPS_DISC_THRESHOLD'] = '15gb'\nimport gc\nimport cv2\nimport math\nimport copy\nimport time\nimport random\nimport glob\nfrom PIL import Image\nfrom matplotlib import pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch.nn.functional as F\nfrom torch.optim import lr_scheduler\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.cuda import amp\nimport torchvision\nimport joblib\nfrom tqdm import tqdm\nfrom collections import defaultdict\nfrom sklearn.preprocessing import LabelEncoder\nfrom sklearn.model_selection import StratifiedKFold\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom colorama import Fore, Back, Style\nimport os\nimport pyvips\nimport numpy as np\nimport random\nfrom PIL import Image\nimport scipy\nimport shutil\nfrom torch.utils.data import DataLoader\n\nb_ = Fore.BLUE\nsr_ = Style.RESET_ALL\nimport warnings\nwarnings.filterwarnings(\"ignore\")\nos.environ['CUDA_LAUNCH_BLOCKING'] = \"1\"\n\n# 定义路径\nDEBUG = False\nTMP_DIR = \"/tmp/test_tiles\"\nROOT_DIR = '/kaggle/input/UBC-OCEAN'\nif DEBUG:\n    CSV_PATH = f'{ROOT_DIR}/train.csv'\n    IMG_DIR = f'{ROOT_DIR}/train_images'\nelse:\n    CSV_PATH = f'{ROOT_DIR}/test.csv'\n    IMG_DIR = f'{ROOT_DIR}/test_images'","metadata":{"papermill":{"duration":8.356181,"end_time":"2024-01-02T18:26:35.747097","exception":false,"start_time":"2024-01-02T18:26:27.390916","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def seed_everything(seed=42):\n    # 设置随机种子，确保实验可复现性\n    random.seed(seed)  # 设置Python内置随机库的随机种子\n    os.environ['PYTHONHASHSEED'] = str(seed)  # 设置环境变量PYTHONHASHSEED，用于控制哈希随机性\n    np.random.seed(seed)  # 设置NumPy的随机种子\n    torch.manual_seed(seed)  # 设置PyTorch的随机种子\n    torch.cuda.manual_seed(seed)  # 设置PyTorch在CUDA上的随机种子\n    torch.backends.cudnn.deterministic = True  # 确保卷积算法的确定性\n    torch.backends.cudnn.benchmark = False  # 关闭cuDNN自动优化，保持计算一致性\n    \n\n# Label Encoding\nlabel_to_id = {\n    \"CC\": 0,\n    \"EC\": 1,\n    \"HGSC\": 2,\n    \"LGSC\": 3,\n    \"MC\": 4,\n    \"Other\": 5,\n}\n\nid_to_label = {v:k for k,v in label_to_id.items()}\nid_to_label","metadata":{"papermill":{"duration":0.020633,"end_time":"2024-01-02T18:26:35.777205","exception":false,"start_time":"2024-01-02T18:26:35.756572","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CFG:\n    seed = 42\n    drop_rate=0.0\n    drop_path_rate=0.0\n    drop_rate_last = 0.0\n    device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n    \n    # df\n    sep_thr = 32060160 # 区分wsi和tma，这个值根据训练集中最小的wsi面积除以2 得到\n    \n    # crop\n    crop_random = True  # 是否随机裁剪\n    crop_max_samples = 90  # 最大裁剪样本数\n    \n    wsi_crop_size = 384 * 2  # WSI（Whole Slide Image）裁剪大小\n    wsi_crop_scale = 0.5  # WSI裁剪时的缩放比例\n    wsi_crop_black_thr = 0.01  # WSI裁剪时黑色区域的阈值\n    wsi_crop_white_thr = None  # WSI裁剪时白色区域的阈值\n    \n    tma_crop_size = wsi_crop_size * 2  # TMA（Tissue MicroArray）裁剪大小\n    tma_crop_scale = wsi_crop_scale / 2  # TMA裁剪时的缩放比例\n    tma_crop_black_thr = None  # TMA裁剪时黑色区域的阈值\n    tma_crop_white_thr = 0.6  # TMA裁剪时白色区域的阈值\n    \n    # dataset\n    img_size = 384  # 图像大小\n    sample_num = 16  # 样本数\n    shuffe_tiles = True  # 是否打乱图像块\n\n    # 模型配置\n    num_classes = 5  # 类别数\n    \n\nseed_everything(CFG.seed)\n","metadata":{"papermill":{"duration":0.094316,"end_time":"2024-01-02T18:26:35.8808","exception":false,"start_time":"2024-01-02T18:26:35.786484","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MODEL_ARCHS = []\nMODEL_WEIGHTS = []\n# tf_efficientnetv2_s_in21ft1k # tf_efficientnet_b0_ns  eca_nfnet_l0 convnext_nano\n\n\nMODEL_ARCHS += [\n    # \"tf_efficientnet_b0_ns\",\n    # \"tf_efficientnet_b0_ns\",\n    # \"tf_efficientnet_b0_ns\",\n    # \"tf_efficientnet_b0_ns\",\n    # \"tf_efficientnet_b0_ns\",\n    \n    # \"eca_nfnet_l0\",\n    # \"eca_nfnet_l0\",\n    # \"eca_nfnet_l0\",\n    # \"eca_nfnet_l0\",\n    # \"eca_nfnet_l0\",\n        \n    \"tf_efficientnetv2_s_in21ft1k\",\n    \"tf_efficientnetv2_s_in21ft1k\",\n    # \"tf_efficientnetv2_s_in21ft1k\",\n    # \"tf_efficientnetv2_s_in21ft1k\",\n    # \"tf_efficientnetv2_s_in21ft1k\",\n    \n    # \"swin_base_patch4_window12_384_in22k\",\n    # \"swin_base_patch4_window12_384_in22k\",\n    # \"swin_base_patch4_window12_384_in22k\",\n    # \"swin_base_patch4_window12_384_in22k\",\n    # \"swin_base_patch4_window12_384_in22k\",\n]\n\nMODEL_WEIGHTS += [ \n    '/kaggle/input/v2s-last/12041953mil_tf_efficientnetv2_s.in21k_ft_in1k_F0_ep32_Re0.8450_TMARe1.0000_Acc0.8037_Loss0.2897.pth',\n    '/kaggle/input/v2s-last/12042109mil_tf_efficientnetv2_s.in21k_ft_in1k_F2_ep39_Re0.8686_TMARe1.0000_Acc0.8426_Loss0.4150.pth',\n]\n\nassert len(MODEL_ARCHS) == len(MODEL_WEIGHTS), \"发现 MODEL_ARCHS 和 MODEL_WEIGHTS 长度不一致，请检查两者长度，并确保一致\"\n\nfor model_arch, model_weight in zip(MODEL_ARCHS, MODEL_WEIGHTS):\n    print(f\"{model_arch}: {model_weight}\")","metadata":{"papermill":{"duration":0.022034,"end_time":"2024-01-02T18:26:35.938484","exception":false,"start_time":"2024-01-02T18:26:35.91645","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"tma_image_ids = [91, 4134, 8280, 9200, 13568, 17637, 21020, 29084, 31594, 35565, 36302, 36583, 36783, 37385, 40864, 41368, 41586, 42857, 44603, 47035, 48734, 50932, 53655, 57696, 61797]\n\ndf = pd.read_csv(CSV_PATH)\ndf[\"area\"] = df[\"image_width\"] * df[\"image_height\"]\ndf[\"is_tma\"] = df[\"area\"] < CFG.sep_thr # 根据面积判断是否为TMA\ndf['label'] = \"Other\"\n\n# 划分wsi数据和tma数据\nwsi_df = df[~df[\"is_tma\"]].reset_index(drop=True)\ntma_df = df[df[\"is_tma\"]].reset_index(drop=True)\n\ndf","metadata":{"papermill":{"duration":0.058879,"end_time":"2024-01-02T18:26:36.010556","exception":false,"start_time":"2024-01-02T18:26:35.951677","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def extract_image_tiles(\n    p_img, folder, \n    size, scale, black_thr, white_thr,\n    inds = None\n) -> list:\n    \"\"\"\n    将大图像切分为较小的平铺图像并保存。\n    1. 加载大图。\n    2. 创建小图的索引列表 [(y, y+h, x, x+w),...]\n    3. 遍历索引列表。\n        3.1 根据索引裁剪图像。\n        3.2 计算黑色面积,如果超过阈值,则跳过该tile。\n        3.3 如果tile的形状不等于(h, w), 一般是因为裁到边缘了。如果太小了则跳过, 否则用0来填充空白区域。\n        3.4 调整tile的大小并保存。\n    \"\"\"\n    # 加载图像\n    im = pyvips.Image.new_from_file(p_img)\n    # 设置平铺图像的宽度和高度\n    w = h = size\n    if not inds:\n        # 创建索引列表,用于分割图像\n        inds = [(y, y + h, x, x + w)\n                for y in range(0, im.height, h)\n                for x in range(0, im.width, w)]\n    \n    if CFG.crop_random:\n        random.shuffle(inds)\n    \n    files, idxs, k = [], [], 0\n    for idx in inds:\n        y, y_, x, x_ = idx\n        # 裁剪图像\n        tile = im.crop(x, y, min(w, im.width - x), min(h, im.height - y)).numpy()[..., :3]\n        tile_area = tile.shape[0] * tile.shape[1]\n        if black_thr is not None:\n            # 检测黑色背景\n            black_bg = np.sum(tile, axis=2) <= 8\n            black_area = np.sum(black_bg)\n            black_ratio = black_area / tile_area\n            if black_ratio >= black_thr:\n                continue\n\n        if white_thr is not None:\n            # 检测白色背景\n            white_bg = np.sum(tile, axis=2) >= 720\n            white_area = np.sum(white_bg)\n            white_ratio = white_area / tile_area\n            if white_ratio >= white_thr:\n                continue\n\n        # 如果平铺的形状与预期不符,调整形状\n        edge_ratio = 0.0\n        if tile.shape[:2] != (h, w):\n            # 计算tile的面积, 如果太小了则跳过\n            edge_ratio = 1-(tile_area/(h*w))\n            if black_thr is not None:\n                if edge_ratio+black_ratio >= black_thr:\n                    continue\n            if white_thr is not None:\n                if edge_ratio+white_ratio >= white_thr:\n                    continue\n\n            # 用0填充空白区域\n            tile_ = tile\n            tile_size = (h, w) if tile.ndim == 2 else (h, w, tile.shape[2]) # 获得tile的shape\n            tile = np.zeros(tile_size, dtype=tile.dtype)\n            tile[:tile_.shape[0], :tile_.shape[1], ...] = tile_\n\n        # 调整图像大小并保存\n        new_size = int(size * scale), int(size * scale)    \n        \n        # 创建文件路径\n        p_img = f\"{folder}/{k:05}_{int(x_ / w)}-{int(y_ / h)}.png\"\n\n        Image.fromarray(tile).resize(new_size, Image.LANCZOS).save(p_img)\n        # 添加文件路径和索引到列表\n        files.append(p_img)\n        idxs.append(idx)\n        k += 1\n        if len(files) >= CFG.crop_max_samples:\n            break\n    return files, idxs\n\n\ndef extract_prune_tiles(\n    path_img, folder, \n    size, scale, black_thr, white_thr,\n) -> str:\n    print(f\"processing: {path_img}\")\n    name, _ = os.path.splitext(os.path.basename(path_img))\n    folder = os.path.join(folder, name)\n    os.makedirs(folder, exist_ok=True)\n    # 提取tiles\n    tiles = extract_image_tiles(\n        path_img, folder, \n        size=size, scale=scale, black_thr=black_thr, white_thr=white_thr,\n    )\n    return folder","metadata":{"papermill":{"duration":0.029318,"end_time":"2024-01-02T18:26:36.051132","exception":false,"start_time":"2024-01-02T18:26:36.021814","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset","metadata":{"papermill":{"duration":0.009786,"end_time":"2024-01-02T18:26:36.070203","exception":false,"start_time":"2024-01-02T18:26:36.060417","status":"completed"},"tags":[]}},{"cell_type":"code","source":"data_transforms = {\n    \"valid\": A.Compose([\n        A.Resize(CFG.img_size, CFG.img_size),\n        A.Normalize(\n                mean=[0.485, 0.456, 0.406], \n                std=[0.229, 0.224, 0.225], \n                max_pixel_value=255.0, \n                p=1.0\n            ),\n        ToTensorV2()], p=1.)\n}","metadata":{"papermill":{"duration":0.017068,"end_time":"2024-01-02T18:26:36.096897","exception":false,"start_time":"2024-01-02T18:26:36.079829","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model","metadata":{"papermill":{"duration":0.009111,"end_time":"2024-01-02T18:26:36.115423","exception":false,"start_time":"2024-01-02T18:26:36.106312","status":"completed"},"tags":[]}},{"cell_type":"code","source":"class Attention(nn.Module):\n    def __init__(self, feature_dim, step_dim, bias=True, **kwargs):\n        super(Attention, self).__init__(**kwargs)\n        # 初始化Attention层的参数\n        self.supports_masking = True  # 支持masking\n\n        self.bias = bias  # 是否使用偏置项\n        self.feature_dim = feature_dim  # 特征维度\n        self.step_dim = step_dim  # 步长维度\n        self.features_dim = 0  # 初始化特征维度\n\n        # 初始化权重，并使用xavier_uniform方法进行初始化\n        weight = torch.zeros(feature_dim, 1)\n        nn.init.xavier_uniform_(weight)\n        self.weight = nn.Parameter(weight)  # 定义权重为模型参数\n\n        if bias:\n            # 如果使用偏置项，初始化偏置参数\n            self.b = nn.Parameter(torch.zeros(step_dim))\n\n    def forward(self, x, mask=None):\n        # 前向传播方法\n        feature_dim = self.feature_dim\n        step_dim = x.shape[1]  # 获取输入x的步长维度\n\n        # 计算注意力分数\n        eij = torch.mm(\n            x.contiguous().view(-1, feature_dim),\n            self.weight\n        ).view(-1, step_dim)\n\n        if self.bias:\n            # 如果使用偏置，加上偏置项\n            eij = eij + self.b\n\n        eij = torch.tanh(eij)  # 使用tanh激活函数\n        a = torch.exp(eij)  # 计算指数，得到注意力权重\n\n        if mask is not None:\n            # 如果提供了mask，应用mask\n            a = a * mask\n\n        # 归一化注意力权重，并防止除零错误\n        a = a / torch.sum(a, 1, keepdim=True) + 1e-10\n\n        # 计算加权平均\n        weighted_input = x * torch.unsqueeze(a, -1)\n        return torch.sum(weighted_input, 1)","metadata":{"papermill":{"duration":0.02098,"end_time":"2024-01-02T18:26:36.145805","exception":false,"start_time":"2024-01-02T18:26:36.124825","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class UBCModel(nn.Module):\n    def __init__(self, model_name, num_classes, pretrained=False, checkpoint_path=None):\n        \"\"\"\n        UBCModel的初始化函数。\n        参数:\n            model_name: 模型名称，决定了使用哪种预训练模型。\n            num_classes: 分类任务中的类别数。\n            pretrained: 是否使用预训练的权重。\n            checkpoint_path: 预训练模型的权重文件路径，如果有的话。\n        \"\"\"\n        super(UBCModel, self).__init__()\n        self.model_name = model_name  # 存储模型名称\n        self.model = timm.create_model(\n            model_name, \n            num_classes=num_classes,\n            pretrained=pretrained, \n            drop_rate=CFG.drop_rate,  # dropout率\n            drop_path_rate=CFG.drop_path_rate,  # drop path率，通常用于训练中提高正则化\n        )\n        \n        in_features = self.model.get_classifier().in_features  # 获取模型分类器部分的输入特征数量\n\n        # 创建层级结构\n        self.norm = nn.LayerNorm(in_features)  # 层归一化\n        self.att = Attention(in_features, CFG.sample_num, bias=False)  # 注意力机制模块\n        self.fc = nn.Sequential(nn.Linear(in_features, num_classes))  # 全连接层\n\n    def forward(self, images):\n        \"\"\"\n        UBCModel的前向传播函数。\n        参数:\n            images: 输入的图像数据。\n        返回:\n            模型输出。\n        \"\"\"\n        # 获取图像的维度：批次大小、样本数、通道数、高度、宽度\n        bs, samp_num, c, h, w = images.shape \n        \n        # 重塑图像维度以适应模型的输入要求\n        images = images.view(bs*samp_num, c, h, w)\n        \n        # 通过模型提取特征\n        features = self.model.forward_features(images)\n        \n        # 根据模型名称处理特征形状\n        if self.model_name.startswith(\"swin\"):\n            feature_bs, feature_h, feature_w, feature_c = features.shape\n        elif self.model_name.startswith(\"vit\"):\n            feature_bs, feature_seqlen, feature_c = features.shape\n        else:\n            feature_bs, feature_c, feature_h, feature_w = features.shape\n\n        # 重新整理特征维度，应用层归一化和注意力机制\n        features = features.contiguous().view(bs, -1, feature_c)\n        features = self.norm(features)\n        features = self.att(features)\n        \n        # 使用全连接层生成最终输出\n        output = self.fc(features)\n\n        return output","metadata":{"papermill":{"duration":0.026182,"end_time":"2024-01-02T18:26:36.181378","exception":false,"start_time":"2024-01-02T18:26:36.155196","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference","metadata":{"papermill":{"duration":0.009405,"end_time":"2024-01-02T18:26:36.200509","exception":false,"start_time":"2024-01-02T18:26:36.191104","status":"completed"},"tags":[]}},{"cell_type":"code","source":"model_list = []\n# 加载所有模型\nfor model_arch, model_weight in zip(MODEL_ARCHS, MODEL_WEIGHTS):\n    print(f\"{model_arch}: {model_weight}\")\n    model = UBCModel(model_arch, CFG.num_classes, pretrained=False)\n    model.load_state_dict(torch.load(model_weight))\n    model.to(CFG.device)\n    model_list.append(model)","metadata":{"papermill":{"duration":37.804759,"end_time":"2024-01-02T18:27:14.014955","exception":false,"start_time":"2024-01-02T18:26:36.210196","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = []  # 初始化一个空列表用于存放最终的提交结果\ntransforms = data_transforms[\"valid\"]  # 获取数据的验证集变换方式\nno_tiles_cnt = 0  # 用于统计没有瓦片切割的样本数量\n\n# 遍历数据框中的每一行\nfor _, row in df.iterrows():\n    row = dict(row)  # 将行数据转换为字典格式\n    is_tma = row[\"is_tma\"]  # 获取当前行是否为TMA图像的标志\n\n    # 根据图像类型（WSI或TMA）选择不同的参数来执行瓦片提取和裁剪\n    if not is_tma:\n        # 如果是WSI图像\n        folder_tiles = extract_prune_tiles(\n            f\"{IMG_DIR}/{str(row['image_id'])}.png\",\n            TMP_DIR, \n            size=CFG.wsi_crop_size, \n            scale=CFG.wsi_crop_scale,\n            black_thr=CFG.wsi_crop_black_thr,\n            white_thr=CFG.wsi_crop_white_thr,\n        )\n    else:\n        # 如果是TMA图像\n        folder_tiles = extract_prune_tiles(\n            f\"{IMG_DIR}/{str(row['image_id'])}.png\",\n            TMP_DIR, \n            size=CFG.tma_crop_size, \n            scale=CFG.tma_crop_scale,\n            black_thr=CFG.tma_crop_black_thr,\n            white_thr=CFG.tma_crop_white_thr,\n        )\n    \n    # 读取裁剪后的瓦片文件路径\n    tiles_path_list = glob.glob(os.path.join(folder_tiles, \"*.png\"))\n    tiles_path_num = len(tiles_path_list)\n    \n    # 如果没有裁剪出瓦片，则记录并继续下一个样本的处理\n    if not tiles_path_num:\n        no_tiles_cnt += 1 \n        print(f\"seem no tiles were cut for `{folder_tiles}`\")\n        submission.append(row)\n        os.system(f\"rm -rf {folder_tiles}\")\n        continue\n    \n    # 根据配置决定是否随机抽取瓦片\n    if CFG.shuffe_tiles:\n        samp_tiles_path = random.sample(tiles_path_list, min(tiles_path_num, CFG.sample_num))\n    else:\n        samp_tiles_path = tiles_path_list[:CFG.sample_num]\n\n    imgs = []\n    # 对选中的瓦片进行读取和预处理\n    for tile_path in samp_tiles_path:\n        img = cv2.imread(tile_path)[..., :3]  # 读取图像\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)  # 将图像从BGR转换为RGB格式\n\n        img = transforms(image=img)[\"image\"]  # 应用数据变换\n        imgs.append(img)\n    imgs = torch.stack(imgs)  # 将图像列表转换为张量\n\n    # 如果选取的瓦片数量小于配置的样本数，则使用零填充\n    n = imgs.shape[0]\n    if n < CFG.sample_num:\n        samples_to_add = CFG.sample_num - n\n        zeros = torch.zeros(samples_to_add, 3, CFG.img_size, CFG.img_size)\n        half = samples_to_add // 2\n        imgs = torch.cat((zeros[:half], imgs, zeros[half:]), dim=0)\n        assert imgs.shape == (CFG.sample_num, 3, CFG.img_size, CFG.img_size)\n\n    with torch.no_grad():\n        # 预测部分\n        images = imgs.to(CFG.device, dtype=torch.float)  \n        images = images.unsqueeze(0)\n        model_pred = []\n        for model in model_list:\n            model.eval()  # 设置模型为评估模式\n            outputs = model(images)\n            outputs = scipy.special.softmax(outputs.detach().cpu().numpy(), axis=1)\n            model_pred.append(outputs)\n        preds = np.mean(model_pred, axis=0)  # 计算模型预测结果的平均值\n\n    lb = np.argmax(preds)  # 获取预测结果中最大概率的索引\n    row['label'] = id_to_label[lb]  # 将索引转换为标签\n\n    submission.append(row)  # 将结果添加到提交列表\n    os.system(f\"rm -rf {folder_tiles}\")  # 删除处理过的瓦片文件夹\n\ndf_sub = pd.DataFrame(submission)  # 将提交列表转换为数据框\ndf_sub","metadata":{"papermill":{"duration":31.641733,"end_time":"2024-01-02T18:27:45.703565","exception":false,"start_time":"2024-01-02T18:27:14.061832","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 保存提交文件\ndf_sub[[\"image_id\", \"label\"]].to_csv(\"submission.csv\", index=False)\ndf_sub[[\"image_id\", \"label\"]].head()","metadata":{"papermill":{"duration":0.031114,"end_time":"2024-01-02T18:27:45.746044","exception":false,"start_time":"2024-01-02T18:27:45.71493","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null}]}