{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.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":112509,"databundleVersionId":14254895,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":14407211,"sourceType":"datasetVersion","datasetId":9201397},{"sourceId":4533,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":3325,"modelId":986},{"sourceId":397680,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":149492,"modelId":172002}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"#起手式\nimport numpy as np \nimport math\nimport os\nimport random\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport numpy as np\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision\nimport timm\nfrom torchvision import transforms\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import CosineAnnealingLR, LinearLR\nfrom torch.optim.lr_scheduler import SequentialLR\n\nfrom torch.utils.data import DataLoader\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler\n\nfrom PIL import Image\n\nimport warnings\nwarnings.filterwarnings(\"ignore\") # 忽略所有警告\n\n#随机种子\ndef set_seed(seed):\n    seed = int(seed)\n    if seed < 0 or seed > (2**32 - 1):\n        raise ValueError(\"Seed must be between 0 and 2**32 - 1\")\n    else:\n        random.seed(seed)\n        np.random.seed(seed)\n        torch.manual_seed(seed)\n        torch.cuda.manual_seed(seed)\n        torch.backends.cudnn.deterministic = True\nset_seed(16)\n\n#当前设备\ndevice = 'cuda' if torch.cuda.is_available() else 'cpu'\nprint(f'使用{device}训练')\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:01:09.038281Z","iopub.execute_input":"2026-01-08T13:01:09.039072Z","iopub.status.idle":"2026-01-08T13:01:10.496920Z","shell.execute_reply.started":"2026-01-08T13:01:09.039012Z","shell.execute_reply":"2026-01-08T13:01:10.495975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#数据增强\ntransform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.5),\n    transforms.RandomApply([transforms.RandomRotation([90, 90])], p=0.5),\n    transforms.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05),\n    transforms.Normalize(mean=(0.485,0.456,0.406),\n                         std=(0.229,0.224,0.225)),\n])\n#原图测试\ntransform_test = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=(0.485,0.456,0.406),\n                         std=(0.229,0.224,0.225)),\n])\n#数据集\nclass Dataset():\n    def __init__(self, df, transform,transform_test, scaler_target,transform_type):\n        self.samples = list(df.itertuples(index=False))\n        self.image_dir = \"/kaggle/input/csiro-biomass\"\n        self.targets = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n        self.transform = transform\n        self.transform_test = transform_test\n        self.scaler_target = scaler_target\n        self.transform_type = transform_type\n    def __len__(self):\n        return len(self.samples)\n\n    def __getitem__(self, idx):\n        row = self.samples[idx]\n        \n        img_path = os.path.join(self.image_dir, row.image_path)\n        image = Image.open(img_path).convert(\"RGB\")\n        if self.transform_type == 'train':\n            image = self.transform(image)\n        if self.transform_type == 'test':\n            image = self.transform_test(image)\n\n        target = torch.tensor([getattr(row, t) for t in self.targets], dtype=torch.float32)\n        target = torch.tensor(self.scaler_target.transform([target.numpy()])[0], dtype=torch.float32)\n\n        return image, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:47.906852Z","iopub.execute_input":"2026-01-07T16:08:47.907082Z","iopub.status.idle":"2026-01-07T16:08:47.923919Z","shell.execute_reply.started":"2026-01-07T16:08:47.907064Z","shell.execute_reply":"2026-01-07T16:08:47.923210Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#读取文件 并统计图片对应标签值\ndf = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")\ndf = df[[\"image_path\", \"target_name\", \"target\"]]\ndf = df.pivot_table(index=[\"image_path\"],columns='target_name',values=\"target\").reset_index()\nprint('文件读取成功',df.head(5))\n\n#转换结构 列为target_name 值为target\n\n#df_train, df_test = train_test_split(df, test_size=0.2, random_state=42, shuffle=True)\n\n# 目标值缩放 避免梯度爆炸\nscaler_target = StandardScaler()\ntargets=['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nscaler_target.fit(df[targets].values)\n\nplt.figure(figsize=(16, 4))\nplt.subplot(1, 3, 1)\ndf.Dry_Green_g.plot(kind='hist')\nplt.title('Dry_Green_g')\n\nplt.subplot(1, 3, 2)\ndf.Dry_Clover_g.plot(kind='hist')\nplt.title('Dry_Clover_g')\n\nplt.subplot(1, 3, 3)\ndf.Dry_Dead_g.plot(kind='hist')\nplt.title('Dry_Dead_g')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:47.925656Z","iopub.execute_input":"2026-01-07T16:08:47.925972Z","iopub.status.idle":"2026-01-07T16:08:48.396282Z","shell.execute_reply.started":"2026-01-07T16:08:47.925944Z","shell.execute_reply":"2026-01-07T16:08:48.395605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#模型\nclass ViTBase16(nn.Module):\n    def __init__(self):\n\n        super(ViTBase16, self).__init__()\n\n        # # 加载权重\n        self.model = timm.create_model('vit_base_patch16_224', pretrained=False)\n        self.model.head = nn.Linear(self.model.head.in_features, 5)\n        state_dict = torch.load('/kaggle/input/vit-transformer-16/vit_base_patch16_224')\n        \n        state_dict = {k.replace('model.', ''): v for k, v in state_dict.items()}\n        self.model.load_state_dict(state_dict)\n        \n\n    def forward(self, x):\n        x = self.model(x)\n        return x\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:48.397973Z","iopub.execute_input":"2026-01-07T16:08:48.398196Z","iopub.status.idle":"2026-01-07T16:08:48.403705Z","shell.execute_reply.started":"2026-01-07T16:08:48.398177Z","shell.execute_reply":"2026-01-07T16:08:48.402877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#模型评估\ndef calc_metric(outputs, targets):\n    #只评估: Green/Clover/Dead\n    \n    y_true = np.column_stack((\n        targets,\n        targets[:, :2].sum(axis=1),\n        targets.sum(axis=1),\n    ))\n    \n    y_pred = np.column_stack((\n        outputs,\n        outputs[:, :2].sum(axis=1),\n        outputs.sum(axis=1),\n    ))\n    \n    weighted_r2, r2_scores = weighted_r2_score(y_true, y_pred)\n    return weighted_r2, r2_scores\n    \n# ======== Weighted R² ========\ndef weighted_r2_score(y_true: np.ndarray, y_pred: np.ndarray):\n    \n    weights = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n    r2_scores = []\n    for i in range(5):\n        y_t = y_true[:, i]\n        y_p = y_pred[:, i]\n        ss_res = np.sum((y_t - y_p) ** 2)\n        ss_tot = np.sum((y_t - np.mean(y_t)) ** 2)\n        r2 = 1 - ss_res / ss_tot if ss_tot > 0 else 0.0\n        r2_scores.append(r2)\n    r2_scores = np.array(r2_scores)\n    weighted_r2 = np.sum(r2_scores * weights) / np.sum(weights)\n    return weighted_r2, r2_scores","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:48.404613Z","iopub.execute_input":"2026-01-07T16:08:48.404807Z","iopub.status.idle":"2026-01-07T16:08:48.415535Z","shell.execute_reply.started":"2026-01-07T16:08:48.404789Z","shell.execute_reply":"2026-01-07T16:08:48.414853Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class Full():\n    def __init__(self,df,scaler_target,loss_fn,optimizer):\n        #super().__init__()\n        self.df = df\n        self.scaler_target = scaler_target\n        #self.model=model\n        # self.loader=loader\n        self.loss_fn = loss_fn\n        self.optimizer = optimizer\n        \n    def train(self,model,train_dataloader):\n        #混合精度\n        from torch.amp import autocast, GradScaler \n        scaler = GradScaler()\n        #print(\"启用混合精度训练\")\n        model.train()\n        loss_sum = 0.0\n    \n        for x , Y in train_dataloader:\n            x , Y= x.to(device) , Y.to(device)\n    \n            self.optimizer.zero_grad()\n            with autocast(device):\n                pred = model(x)\n                loss = self.loss_fn(pred, Y)\n                #print(loss)\n            scaler.scale(loss).backward() \n            scaler.step(self.optimizer)\n            scaler.update()\n            \n            loss_sum += loss.item()\n\n        return loss_sum / len(train_dataloader)\n        \n    def test(self,model,test_dataloader):\n        model.eval()\n        loss_sum = 0.0\n        all_pred=[]\n        all_target=[]\n        with torch.no_grad():\n            for x , Y in test_dataloader:\n                x , Y= x.to(device),Y.to(device)\n                \n                pred = model(x)\n       \n                loss = self.loss_fn(pred, Y)\n                loss_sum += loss.item()\n\n                all_pred.append(pred.detach().cpu())\n                all_target.append(Y.detach().cpu())\n        #评估\n        outputs = torch.cat(all_pred).numpy()\n        targets = torch.cat(all_target).numpy()         \n        \n        weighted_r2, r2_scores=calc_metric(outputs, targets)\n    \n        return loss_sum / len(test_dataloader),weighted_r2\n        \n    def res(self,best_model,flod):\n        #获得结果\n        df_test = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\n        test_path = df_test[\"image_path\"].values.tolist()\n        test_target = df_test[\"target_name\"].values.tolist()\n        \n        res = []\n \n        # 定义3种TTA变换\n        tta_transforms = [\n            transforms.RandomHorizontalFlip(p=1.0),  # 强制水平翻转\n            transforms.RandomVerticalFlip(p=1.0),    # 强制垂直翻转\n            transforms.RandomRotation([90, 90])      # 旋转90度\n        ]\n        # 创建对应的transform组合\n        tta_transform_list = []\n        for tta_t in tta_transforms:\n            # 带增强的transform\n            transform_combo = transforms.Compose([\n                transforms.Resize((224, 224)),\n                tta_t,  # 添加TTA变换\n                transforms.ToTensor(),\n                transforms.Normalize(mean=(0.485,0.456,0.406),\n                                     std=(0.229,0.224,0.225)),\n            ])\n            tta_transform_list.append(transform_combo)\n   \n        with torch.no_grad():\n            for img_path,target in zip(test_path,test_target):\n                img_path = os.path.join('/kaggle/input/csiro-biomass', img_path)\n                image = Image.open(img_path).convert(\"RGB\")\n           \n                all_preds=[]\n                #原图\n                raw_image = transform_test(image).to(device).unsqueeze(0)\n                pred = best_model(raw_image)\n                all_preds.append(pred.detach().cpu())\n\n                #TTA测试取平均\n                for transform_combo in tta_transform_list:\n                    tta_image = transform_combo(image).to(device).unsqueeze(0)\n                    pred = best_model(tta_image)\n                    all_preds.append(pred.detach().cpu())\n                    \n                #对4种预测结果取平均\n                preds = torch.mean(torch.stack(all_preds), dim=0)\n\n                preds = self.scaler_target.inverse_transform(preds.detach().cpu())\n                preds=preds.reshape(-1)\n\n                if target == 'Dry_Clover_g':\n                    res.append(preds[0])\n                elif target == 'Dry_Dead_g':\n                    res.append(preds[1])\n                elif target == 'Dry_Green_g':\n                    res.append(preds[2])\n                elif target == 'Dry_Total_g':\n                    res.append(preds[3])\n                else: #GDM_g\n                    res.append(preds[4])\n       \n            #输出\n            submission = pd.DataFrame({'sample_id':df_test['sample_id'],'target':res})\n            submission.to_csv(f'submission-{flod}.csv', index = False)\n            \n            torch.save(best_model.state_dict(), f'VIT—CSIRO-{flod}')\n            print(f\"保存模型：VIT—CSIRO-{flod}\")\n            print(submission)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:48.416587Z","iopub.execute_input":"2026-01-07T16:08:48.416803Z","iopub.status.idle":"2026-01-07T16:08:48.436396Z","shell.execute_reply.started":"2026-01-07T16:08:48.416784Z","shell.execute_reply":"2026-01-07T16:08:48.435707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def dataloader(flod):\n    batch_size = 64\n    \n    train_dataset = Dataset(df[df['fold']!=flod], transform, transform_test,scaler_target,'train')\n    test_dataset = Dataset(df[df['fold']==flod], transform,transform_test, scaler_target,'test')\n    \n    train_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True,num_workers=2)\n    test_dataloader = DataLoader(test_dataset,batch_size=batch_size,num_workers=2)\n    \n    return train_dataloader,test_dataloader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:48.437125Z","iopub.execute_input":"2026-01-07T16:08:48.437383Z","iopub.status.idle":"2026-01-07T16:08:48.451038Z","shell.execute_reply.started":"2026-01-07T16:08:48.437357Z","shell.execute_reply":"2026-01-07T16:08:48.450378Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_flod(df,scaler_target,train_dataloader,test_dataloader,flod):\n    #释放内存\n    import gc\n    \n    torch.cuda.empty_cache()\n    gc.collect()\n    \n    #定义模型\n    model=ViTBase16()\n    \n    #多gpu运行\n    model = model.to(device)\n    if torch.cuda.device_count() > 1:\n        print(f\"使用 {torch.cuda.device_count()} 个GPU\")\n        model = torch.nn.DataParallel(model)\n    else:\n        model.to(device)\n    \n    #损失函数\n    # loss_fn = nn.MSELoss()\n    loss_fn=nn.SmoothL1Loss()\n    epoch = 100\n\n     #耐心\n    n= 100\n    print(f\"早停耐心：{n}  | 学习率耐心: {0}\")\n\n    optimizer = optim.AdamW(model.parameters(),\n                          lr=1e-3,              \n                          weight_decay=5e-4\n                         )\n       \n    # scheduler = optim.lr_scheduler.ReduceLROnPlateau(\n    #     optimizer, mode='max', factor=0.5, patience=3\n    # )\n    \n    #余弦退火\n    scheduler = CosineAnnealingLR(optimizer,\n                                  T_max=epoch//4,\n                                  eta_min=1e-10\n                                 )\n    \n    best_model = model #最优模型\n    \n    train_loss_list=[]\n    test_loss_list=[]\n    lr_list=[]\n    w_r2_list=[]\n\n    min_R2 = -1*np.inf\n    \n    #训练\n    full=Full(df,scaler_target,loss_fn,optimizer)\n    \n    for i in range(epoch):\n        train_loss=full.train(model,train_dataloader)\n        test_loss,weighted_r2=full.test(model,test_dataloader)\n        \n        # scheduler.step(weighted_r2)\n        scheduler.step()  #余弦退火\n        \n        train_loss_list.append(train_loss)\n        test_loss_list.append(test_loss)\n        w_r2_list.append(weighted_r2)\n        lr_list.append(scheduler.get_last_lr()[0])\n        \n        if (i+1)%10==0:\n            print(f\"第{i+1}/{epoch}迭代 | 训练损失:{train_loss} | 测试损失值:{test_loss} | weighted_r2:{weighted_r2}\")\n            print(f'lr: {scheduler.get_last_lr()[0]:6f}')\n        \n        if min_R2 <= weighted_r2:\n            min_R2 = weighted_r2\n            best_model=model    #最优模型\n            # n=10    #恢复耐心\n              \n        #减少耐心\n        # else:n-=1\n        # if n<=0:\n        #     print(f'超过{{n}}epoch没有改善，早停')\n        #     break\n    #最优结果\n    full.res(best_model,flod)\n    \n    #图片\n    plt.figure(figsize=(20,15))\n    \n    plt.subplot(2,2,1)\n    plt.plot(train_loss_list,color='red')#,label='train_loss'\n    plt.plot(test_loss_list,color='green')#,label='test_loss'\n    plt.title(\"loss\")\n    \n    plt.subplot(2,2,2)\n    plt.plot(lr_list)\n    plt.title(\"lr\")\n    \n    plt.subplot(2,2,3)\n    plt.plot(w_r2_list)\n    plt.title(weighted_r2)\n\n    plt.show()\n\n    print(f'{'-'*20}第{flod+1}折结束{'-'*20}')    \n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:48.452010Z","iopub.execute_input":"2026-01-07T16:08:48.452302Z","iopub.status.idle":"2026-01-07T16:08:48.468166Z","shell.execute_reply.started":"2026-01-07T16:08:48.452275Z","shell.execute_reply":"2026-01-07T16:08:48.467550Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#按采样时间分组的五折交叉验证\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nCV_STRATEGY = 'random'\nNFOLD = 5\n\nKFoldClass = StratifiedGroupKFold if CV_STRATEGY == 'groupby_Sampling_Date' else KFold\nkfold = KFoldClass(n_splits=NFOLD, shuffle=True, random_state=42)\n\n# kfold = KFold(n_splits=NFOLD, shuffle=True, random_state=42)\n\ndf['fold'] = None\nfor i, (trn_idx, val_idx) in enumerate(kfold.split(df.index)):\n    df.loc[val_idx, 'fold'] = i\n    \n#抽样结果统计\n\nprint(\"\\n-----------------------------测试集合-----------------------------------\\n\")\nfor i in range(NFOLD):\n    \n    plt.figure(figsize=(16, 4))\n    plt.subplot(1, 3, 1)\n    df[df['fold']==i].Dry_Green_g.plot(kind='hist')\n    plt.title('Dry_Green_g')\n    \n    plt.subplot(1, 3, 2)\n    df[df['fold']==i].Dry_Clover_g.plot(kind='hist')\n    plt.title('Dry_Clover_g')\n    \n    plt.subplot(1, 3, 3)\n    df[df['fold']==i].Dry_Dead_g.plot(kind='hist')\n    plt.title('Dry_Dead_g')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:48.469896Z","iopub.execute_input":"2026-01-07T16:08:48.470118Z","iopub.status.idle":"2026-01-07T16:08:49.298767Z","shell.execute_reply.started":"2026-01-07T16:08:48.470100Z","shell.execute_reply":"2026-01-07T16:08:49.298049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"\\n-----------------------------训练集合-----------------------------------\\n\")\nfor i in range(NFOLD):\n    \n    plt.figure(figsize=(16, 4))\n    plt.subplot(1, 3, 1)\n    df[df['fold']!=i].Dry_Green_g.plot(kind='hist')\n    plt.title('Dry_Green_g')\n    \n    plt.subplot(1, 3, 2)\n    df[df['fold']!=i].Dry_Clover_g.plot(kind='hist')\n    plt.title('Dry_Clover_g')\n    \n    plt.subplot(1, 3, 3)\n    df[df['fold']!=i].Dry_Dead_g.plot(kind='hist')\n    plt.title('Dry_Dead_g')\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:49.299705Z","iopub.execute_input":"2026-01-07T16:08:49.300070Z","iopub.status.idle":"2026-01-07T16:08:50.109451Z","shell.execute_reply.started":"2026-01-07T16:08:49.300048Z","shell.execute_reply":"2026-01-07T16:08:50.108726Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nfor i in range(NFOLD):\n\n    train_dataloader,test_dataloader = dataloader(flod=i)\n    train_flod(df,scaler_target,train_dataloader,test_dataloader,i)\n\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:08:50.110376Z","iopub.execute_input":"2026-01-07T16:08:50.110688Z","iopub.status.idle":"2026-01-07T16:09:55.669528Z","shell.execute_reply.started":"2026-01-07T16:08:50.110657Z","shell.execute_reply":"2026-01-07T16:09:55.668876Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#平均五折最优训练结果输出\np = pd.read_csv(f\"/kaggle/working/submission-{0}.csv\")\nvalue = p.target\nprint(p)\nfor i in range(1,NFOLD):\n    p = pd.read_csv(f\"/kaggle/working/submission-{i}.csv\")\n    print(p)\n    value+=p.target\n\nprint(value,'\\n')\n\nres_cvs=pd.DataFrame({\n    'sample_id' : p['sample_id'],\n    'target' : value/NFOLD\n})\nres_cvs.to_csv(f'submission.csv', index = False)\nres_cvs","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T16:09:55.670987Z","iopub.execute_input":"2026-01-07T16:09:55.671247Z","iopub.status.idle":"2026-01-07T16:09:55.689014Z","shell.execute_reply.started":"2026-01-07T16:09:55.671223Z","shell.execute_reply":"2026-01-07T16:09:55.688293Z"}},"outputs":[],"execution_count":null}]}