{"cells": [{"cell_type": "markdown", "metadata": {}, "source": "## Config"}, {"cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": "import os"}, {"cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": "# \u73af\u5883\u8bbe\u7f6e\n\nTRAIN = True  # submission\u65f6\u53ea\u7528\u8dd1\u63a8\u7406\uff0c\u8bbe\u4e3a False\n\nDEBUG = os.environ.get('KAGGLE_KERNEL_RUN_TYPE', '') == 'Interactive'  # \u4ea4\u4e92\u5f0f\u73af\u5883\u4e0b\u4f1a\u5c11\u8dd1\u4e00\u4e9bepoch\uff0c\u7528\u4e8e\u5feb\u901f\u8dd1\u901a\u6d41\u7a0b\uff0c\u65b9\u4fbf\u8c03\u8bd5\nLOCAL = os.environ.get('KAGGLE_KERNEL_RUN_TYPE', '') == ''  # \u7528\u4e8e\u5728\u672c\u5730\u5f00\u53d1\uff0c\u89c9\u5f97\u4ee3\u7801ok\u540e\u901a\u8fc7 `kaggle k push` \u547d\u4ee4\u63d0\u4ea4\u5230 kaggle \u5e73\u53f0\n\nif LOCAL:\n    os.environ['HF_ENDPOINT'] = 'https://hf-mirror.com'"}, {"cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": "# \u8bad\u7ec3\u8bbe\u7f6e\n\n## CV\nCV_STRATEGY = 'groupby_Sampling_Date'  # groupby_Sampling_Date\nNFOLD = 5\nKFOLD_SEED = 42\n\n## Model\nMODEL_NAME = 'efficientnet_b2'\n\n## Training Hyper Params\nLR = 1e-2"}, {"cell_type": "markdown", "metadata": {"execution": {"iopub.execute_input": "2025-11-21T05:07:31.762158Z", "iopub.status.busy": "2025-11-21T05:07:31.760963Z", "iopub.status.idle": "2025-11-21T05:07:31.766349Z", "shell.execute_reply": "2025-11-21T05:07:31.765388Z", "shell.execute_reply.started": "2025-11-21T05:07:31.762125Z"}}, "source": "## Load Data"}, {"cell_type": "markdown", "metadata": {}, "source": "### \u6570\u636e\u89e3\u8bfb\n\n#### \u5e72\u7269\u8d28\uff08Dry Matter\uff09\n\u7267\u8349\u542b\u6709\u5927\u91cf\u6c34\u5206\uff0c\u5176\u542b\u6c34\u91cf\u4f1a\u56e0\u5929\u6c14\u3001\u65f6\u95f4\u3001\u751f\u957f\u9636\u6bb5\u7b49\u56e0\u7d20\u800c\u5267\u70c8\u53d8\u5316\u3002\u4e3a\u4e86\u5f97\u5230\u4e00\u4e2a\u7a33\u5b9a\u3001\u53ef\u6bd4\u8f83\u7684\u8861\u91cf\u6807\u51c6\u6765\u8bc4\u4f30\u7267\u8349\u7684\u771f\u5b9e\u8425\u517b\u4ef7\u503c\u548c\u6570\u91cf\uff0c\u519c\u5b66\u5bb6\u548c\u519c\u6c11\u4f1a\u5148\u5c06\u7267\u8349\u6837\u672c\u70d8\u5e72\uff0c\u53bb\u9664\u6240\u6709\u6c34\u5206\uff0c\u7136\u540e\u79f0\u91cf\u5269\u4f59\u90e8\u5206\u7684\u91cd\u91cf\u3002\u8fd9\u4e2a\u91cd\u91cf\u5c31\u662f\u201c\u5e72\u7269\u8d28\u201d\u6216\u201c\u5e72\u71e5\u751f\u7269\u91cf\u201d\u3002\u6bd4\u8d5b\u4e2d\u7684\u6240\u6709\u6307\u6807\u90fd\u662f\u57fa\u4e8e\u8fd9\u4e2a\u201c\u5e72\u201d\u91cd\u6765\u8ba1\u7b97\u7684\u3002\n\n#### 1. Dry green vegetation (excluding clover) / \u5e72\u71e5\u7684\u7eff\u8272\u690d\u88ab\uff08\u4e0d\u5305\u62ec\u4e09\u53f6\u8349\uff09\n\n*   **\u8fd9\u662f\u4ec0\u4e48\uff1f**\n    \u8fd9\u4e3b\u8981\u662f\u6307\u7267\u573a\u4e2d\u6b63\u5728\u751f\u957f\u7684\u3001\u7eff\u8272\u7684\u79be\u672c\u79d1\u7267\u8349\uff08\u5982\u9ed1\u9ea6\u8349\u3001\u7f8a\u8305\u7b49\uff09\u7684\u5e72\u91cd\u3002\u5b83\u4ee3\u8868\u4e86\u7267\u573a\u4e2d\u4e3b\u8981\u7684\u3001\u6709\u6d3b\u529b\u7684\u975e\u8c46\u79d1\u690d\u7269\u90e8\u5206\u3002\n*   **\u4e3a\u4ec0\u4e48\u91cd\u8981\uff1f**\n    \u8fd9\u662f\u7272\u755c\uff08\u5982\u725b\u3001\u7f8a\uff09**\u6700\u4e3b\u8981\u7684\u80fd\u91cf\u6765\u6e90**\u3002\u8fd9\u4e9b\u7eff\u8272\u7684\u8349\u5bcc\u542b\u78b3\u6c34\u5316\u5408\u7269\uff0c\u4e3a\u52a8\u7269\u63d0\u4f9b\u4e86\u65e5\u5e38\u6d3b\u52a8\u548c\u751f\u957f\u6240\u9700\u7684\u57fa\u672c\u80fd\u91cf\u3002\u5b83\u7684\u6570\u91cf\u76f4\u63a5\u51b3\u5b9a\u4e86\u7267\u573a\u80fd\u517b\u6d3b\u591a\u5c11\u52a8\u7269\u3002\u5c06\u5176\u4e0e\u4e09\u53f6\u8349\u5206\u5f00\u6d4b\u91cf\uff0c\u662f\u56e0\u4e3a\u5b83\u4eec\u7684\u8425\u517b\u6210\u5206\uff08\u7279\u522b\u662f\u86cb\u767d\u8d28\u542b\u91cf\uff09\u6709\u663e\u8457\u5dee\u5f02\u3002\n\n#### 2. Dry dead material / \u5e72\u71e5\u7684\u67af\u6b7b\u7269\u8d28\n\n*   **\u8fd9\u662f\u4ec0\u4e48\uff1f**\n    \u8fd9\u662f\u6307\u7267\u573a\u4e2d\u5df2\u7ecf\u67af\u9ec4\u3001\u6b7b\u4ea1\u7684\u690d\u7269\u90e8\u5206\u7684\u5e72\u91cd\u3002\u5b83\u53ef\u80fd\u662f\u524d\u4e00\u4e2a\u5b63\u8282\u7559\u4e0b\u7684\u8001\u8349\uff0c\u6216\u662f\u56e0\u5e72\u65f1\u3001\u8fc7\u5ea6\u6210\u719f\u800c\u6b7b\u4ea1\u7684\u690d\u7269\u3002\n*   **\u4e3a\u4ec0\u4e48\u91cd\u8981\uff1f**\n    \u8fd9\u90e8\u5206\u662f**\u4f4e\u8d28\u91cf\u7684\u9972\u6599**\u3002\u5b83\u7684\u8425\u517b\u4ef7\u503c\u975e\u5e38\u4f4e\uff08\u86cb\u767d\u8d28\u548c\u80fd\u91cf\u542b\u91cf\u90fd\u5f88\u4f4e\uff09\uff0c\u6d88\u5316\u7387\u5dee\uff0c\u800c\u4e14\u53e3\u611f\u4e0d\u597d\uff0c\u7272\u755c\u901a\u5e38\u4f1a\u5c3d\u91cf\u907f\u514d\u91c7\u98df\u3002\n    *   **\u6307\u793a\u4f5c\u7528**\uff1a\u5982\u679c\u8fd9\u90e8\u5206\u5360\u6bd4\u8f83\u9ad8\uff0c\u8bf4\u660e\u7267\u573a\u7ba1\u7406\u53ef\u80fd\u5b58\u5728\u95ee\u9898\uff08\u4f8b\u5982\uff0c\u653e\u7267\u4e0d\u53ca\u65f6\u5bfc\u81f4\u7267\u8349\u957f\u8001\u3001\u67af\u6b7b\uff09\uff0c\u6216\u8005\u7267\u573a\u5065\u5eb7\u72b6\u51b5\u4e0d\u4f73\u3002\u5b83\u4f1a\u201c\u7a00\u91ca\u201d\u4f18\u8d28\u9972\u6599\u7684\u6bd4\u4f8b\uff0c\u964d\u4f4e\u7272\u755c\u7684\u91c7\u98df\u6548\u7387\u3002\n    *   **\u751f\u6001\u5f71\u54cd**\uff1a\u8fc7\u591a\u7684\u67af\u6b7b\u7269\u8d28\u4f1a\u8986\u76d6\u5728\u5730\u9762\uff0c\u963b\u788d\u9633\u5149\u7167\u5c04\uff0c\u6291\u5236\u65b0\u8349\u7684\u751f\u957f\u3002\n\n#### 3. Dry clover biomass / \u5e72\u71e5\u7684\u4e09\u53f6\u8349\u751f\u7269\u91cf\n\n*   **\u8fd9\u662f\u4ec0\u4e48\uff1f**\n    \u8fd9\u662f\u6307\u7267\u573a\u4e2d\u6240\u6709\u4e09\u53f6\u8349\uff08Clover\uff09\u6216\u5176\u4ed6\u8c46\u79d1\u690d\u7269\uff08\u5982\u82dc\u84ff\uff09\u7684\u5e72\u91cd\u3002\n*   **\u4e3a\u4ec0\u4e48\u91cd\u8981\uff1f**\n    \u4e09\u53f6\u8349\u662f\u7267\u573a\u4e2d\u7684\u201c**\u8d85\u7ea7\u98df\u7269**\u201d\u3002\n    *   **\u9ad8\u86cb\u767d\u8d28**\uff1a\u4e0e\u79be\u672c\u79d1\u7267\u8349\u76f8\u6bd4\uff0c\u4e09\u53f6\u8349\u7684\u86cb\u767d\u8d28\u542b\u91cf\u8981\u9ad8\u5f97\u591a\u3002\u86cb\u767d\u8d28\u662f\u52a8\u7269\u589e\u91cd\u3001\u4ea7\u5976\u548c\u7ef4\u6301\u5065\u5eb7\u7684\u5173\u952e\u3002\u56e0\u6b64\uff0c\u4e09\u53f6\u8349\u7684\u542b\u91cf\u76f4\u63a5\u5173\u7cfb\u5230\u9972\u6599\u7684\u201c\u8d28\u91cf\u201d\u3002\n    *   **\u5929\u7136\u80a5\u6599**\uff1a\u4e09\u53f6\u8349\u5177\u6709\u201c\u56fa\u6c2e\u201d\u80fd\u529b\uff0c\u80fd\u5c06\u7a7a\u6c14\u4e2d\u7684\u6c2e\u6c14\u8f6c\u5316\u4e3a\u571f\u58e4\u4e2d\u7684\u6c2e\u80a5\uff0c\u4e3a\u5468\u56f4\u7684\u79be\u672c\u79d1\u7267\u8349\u63d0\u4f9b\u5929\u7136\u517b\u5206\uff0c\u4ece\u800c\u63d0\u9ad8\u6574\u4e2a\u7267\u573a\u7684\u751f\u4ea7\u529b\u5e76\u51cf\u5c11\u5bf9\u5316\u80a5\u7684\u4f9d\u8d56\u3002\n\n#### 4. Green dry matter (GDM) / \u7eff\u8272\u5e72\u7269\u8d28\n\n*   **\u8fd9\u662f\u4ec0\u4e48\uff1f**\n    \u8fd9\u662f **(\u5e72\u71e5\u7684\u7eff\u8272\u690d\u88ab) + (\u5e72\u71e5\u7684\u4e09\u53f6\u8349\u751f\u7269\u91cf)** \u7684\u603b\u548c\u3002\u7b80\u5355\u6765\u8bf4\uff0c\u5b83\u4ee3\u8868\u4e86\u7267\u573a\u4e2d\u6240\u6709**\u6709\u751f\u547d\u7684\u3001\u7eff\u8272\u7684\u690d\u7269**\u7684\u603b\u5e72\u91cd\u3002\n*   **\u4e3a\u4ec0\u4e48\u91cd\u8981\uff1f**\n    \u8fd9\u662f\u8bc4\u4f30\u7267\u573a**\u5f53\u524d\u53ef\u7528\u4f18\u8d28\u9972\u6599\u603b\u91cf**\u7684\u6838\u5fc3\u6307\u6807\u3002\u5f53\u519c\u6c11\u51b3\u5b9a\u4e00\u4e2a\u7267\u533a\uff08paddock\uff09\u53ef\u4ee5\u653e\u517b\u591a\u5c11\u5934\u725b\u3001\u80fd\u653e\u517b\u591a\u5c11\u5929\u65f6\uff0c\u4ed6\u4eec\u6700\u5173\u5fc3\u7684\u5c31\u662fGDM\u3002\u5b83\u76f4\u63a5\u53cd\u6620\u4e86\u7267\u573a\u7684\u201c\u627f\u8f7d\u80fd\u529b\u201d\uff08Carrying Capacity\uff09\u3002\u8fd9\u4e2a\u6570\u503c\u8d8a\u9ad8\uff0c\u610f\u5473\u7740\u53ef\u4f9b\u7272\u755c\u91c7\u98df\u7684\u4f18\u8d28\u9972\u6599\u8d8a\u591a\u3002\n\n#### 5. Total dry biomass / \u603b\u5e72\u71e5\u751f\u7269\u91cf\n\n*   **\u8fd9\u662f\u4ec0\u4e48\uff1f**\n    \u8fd9\u662f\u7267\u573a\u4e2d\u6240\u6709\u5730\u4e0a\u90e8\u5206\u751f\u7269\u91cf\u7684\u603b\u548c\uff0c\u5373 **(\u7eff\u8272\u690d\u88ab) + (\u4e09\u53f6\u8349) + (\u67af\u6b7b\u7269\u8d28)** \u7684\u603b\u5e72\u91cd\u3002\n*   **\u4e3a\u4ec0\u4e48\u91cd\u8981\uff1f**\n    \u8fd9\u4e2a\u6307\u6807\u53cd\u6620\u4e86\u7267\u573a\u4e0a**\u6240\u6709\u690d\u7269\u7269\u8d28\u7684\u603b\u91cf**\u3002\u901a\u8fc7\u6bd4\u8f83\u201c\u603b\u5e72\u71e5\u751f\u7269\u91cf\u201d\u548c\u201c\u7eff\u8272\u5e72\u7269\u8d28\uff08GDM\uff09\u201d\uff0c\u519c\u6c11\u53ef\u4ee5\u5feb\u901f\u4e86\u89e3\u7267\u573a\u7684\u5065\u5eb7\u72b6\u51b5\u3002\n    *   **\u5065\u5eb7\u6307\u6807**\uff1a\u5982\u679c\u201c\u603b\u5e72\u71e5\u751f\u7269\u91cf\u201d\u5f88\u9ad8\uff0c\u4f46\u201c\u7eff\u8272\u5e72\u7269\u8d28\u201d\u5360\u6bd4\u5f88\u4f4e\uff0c\u8bf4\u660e\u7267\u573a\u91cc\u5806\u79ef\u4e86\u5927\u91cf\u65e0\u7528\u7684\u67af\u6b7b\u7269\u8d28\uff0c\u9700\u8981\u8fdb\u884c\u7ba1\u7406\uff08\u5982\u901a\u8fc7\u77ed\u671f\u91cd\u5ea6\u653e\u7267\u6e05\u7406\uff0c\u6216\u7528\u673a\u68b0\u5272\u9664\uff09\u3002\n    *   **\u957f\u671f\u89c4\u5212**\uff1a\u8fd9\u4e2a\u6570\u636e\u6709\u52a9\u4e8e\u4e86\u89e3\u7267\u573a\u7684\u5b63\u8282\u6027\u751f\u957f\u5468\u671f\u548c\u6574\u4f53\u751f\u4ea7\u529b\u3002"}, {"cell_type": "code", "execution_count": 4, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:24.197503Z", "iopub.status.busy": "2025-11-26T05:13:24.197001Z", "iopub.status.idle": "2025-11-26T05:13:24.888291Z", "shell.execute_reply": "2025-11-26T05:13:24.887702Z", "shell.execute_reply.started": "2025-11-26T05:13:24.19748Z"}}, "outputs": [], "source": "from PIL import Image\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\n%matplotlib inline\n\nfrom sklearn.model_selection import KFold, GroupKFold, StratifiedGroupKFold\nfrom tqdm.auto import tqdm\ntqdm.pandas()"}, {"cell_type": "code", "execution_count": 5, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:24.889917Z", "iopub.status.busy": "2025-11-26T05:13:24.889209Z", "iopub.status.idle": "2025-11-26T05:13:24.893298Z", "shell.execute_reply": "2025-11-26T05:13:24.892718Z", "shell.execute_reply.started": "2025-11-26T05:13:24.889897Z"}}, "outputs": [], "source": "DATA_ROOT = '../input/' if LOCAL else '/kaggle/input/csiro-biomass/'"}, {"cell_type": "code", "execution_count": 6, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:27.347148Z", "iopub.status.busy": "2025-11-26T05:13:27.346841Z", "iopub.status.idle": "2025-11-26T05:13:27.386982Z", "shell.execute_reply": "2025-11-26T05:13:27.386384Z", "shell.execute_reply.started": "2025-11-26T05:13:27.347127Z"}}, "outputs": [], "source": "train_df = pd.read_csv(f'{DATA_ROOT}/train.csv')\ntrain_df.head()"}, {"cell_type": "code", "execution_count": 7, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:27.631939Z", "iopub.status.busy": "2025-11-26T05:13:27.631688Z", "iopub.status.idle": "2025-11-26T05:13:27.641364Z", "shell.execute_reply": "2025-11-26T05:13:27.64078Z", "shell.execute_reply.started": "2025-11-26T05:13:27.631919Z"}}, "outputs": [], "source": "train_df[['sample_id_prefix', 'sample_id_suffix']] = train_df.sample_id.str.split('__', expand=True)"}, {"cell_type": "code", "execution_count": 8, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:28.169403Z", "iopub.status.busy": "2025-11-26T05:13:28.168916Z", "iopub.status.idle": "2025-11-26T05:13:28.174843Z", "shell.execute_reply": "2025-11-26T05:13:28.174144Z", "shell.execute_reply.started": "2025-11-26T05:13:28.169378Z"}}, "outputs": [], "source": "(train_df.sample_id_suffix == train_df.target_name).all()"}, {"cell_type": "code", "execution_count": 9, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:29.483361Z", "iopub.status.busy": "2025-11-26T05:13:29.483071Z", "iopub.status.idle": "2025-11-26T05:13:51.252318Z", "shell.execute_reply": "2025-11-26T05:13:51.251717Z", "shell.execute_reply.started": "2025-11-26T05:13:29.483336Z"}}, "outputs": [], "source": "cols = ['sample_id_prefix', 'image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\nagg_train_df = train_df.groupby(cols).apply(lambda df: df.set_index('target_name').target)\nagg_train_df.reset_index(inplace=True)\nagg_train_df.columns.name = None\n\nagg_train_df['image'] = agg_train_df.image_path.progress_apply(\n    lambda path: Image.open(DATA_ROOT + path).convert('RGB')\n)\n\nagg_train_df.head()"}, {"cell_type": "code", "execution_count": 10, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:51.253733Z", "iopub.status.busy": "2025-11-26T05:13:51.253494Z", "iopub.status.idle": "2025-11-26T05:13:51.261665Z", "shell.execute_reply": "2025-11-26T05:13:51.261045Z", "shell.execute_reply.started": "2025-11-26T05:13:51.253715Z"}}, "outputs": [], "source": "agg_train_df['image_size'] = agg_train_df.image.apply(lambda x: x.size)\nagg_train_df['image_size'].value_counts()"}, {"cell_type": "code", "execution_count": 11, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:51.262599Z", "iopub.status.busy": "2025-11-26T05:13:51.262345Z", "iopub.status.idle": "2025-11-26T05:13:51.292319Z", "shell.execute_reply": "2025-11-26T05:13:51.29177Z", "shell.execute_reply.started": "2025-11-26T05:13:51.262578Z"}}, "outputs": [], "source": "np.isclose(\n    agg_train_df[['Dry_Green_g', 'Dry_Clover_g']].sum(axis=1),\n    agg_train_df['GDM_g'],\n    atol=1e-04\n).mean()"}, {"cell_type": "code", "execution_count": 12, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:51.294185Z", "iopub.status.busy": "2025-11-26T05:13:51.293933Z", "iopub.status.idle": "2025-11-26T05:13:51.30743Z", "shell.execute_reply": "2025-11-26T05:13:51.306726Z", "shell.execute_reply.started": "2025-11-26T05:13:51.29417Z"}}, "outputs": [], "source": "np.isclose(\n    agg_train_df[['GDM_g', 'Dry_Dead_g']].sum(axis=1),\n    agg_train_df['Dry_Total_g'],\n    atol=1e-04\n).mean()"}, {"cell_type": "code", "execution_count": 13, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:51.30832Z", "iopub.status.busy": "2025-11-26T05:13:51.308155Z", "iopub.status.idle": "2025-11-26T05:13:51.888601Z", "shell.execute_reply": "2025-11-26T05:13:51.887775Z", "shell.execute_reply.started": "2025-11-26T05:13:51.308306Z"}}, "outputs": [], "source": "plt.figure(figsize=(16, 4))\nplt.subplot(1, 3, 1)\nagg_train_df.Dry_Green_g.plot(kind='hist')\n_ = plt.title('Dry_Green_g')\n\nplt.subplot(1, 3, 2)\nagg_train_df.Dry_Clover_g.plot(kind='hist')\n_ = plt.title('Dry_Clover_g')\n\nplt.subplot(1, 3, 3)\nagg_train_df.Dry_Dead_g.plot(kind='hist')\n_ = plt.title('Dry_Dead_g')"}, {"cell_type": "markdown", "metadata": {}, "source": "## CV Strategy"}, {"cell_type": "markdown", "metadata": {}, "source": "It appears there is a significant gap between the local CV score and the public LB score. We need to figure out the distributional differences between the test set and training set in order to select an appropriate CV strategy. Here are some discussions regarding the distribution of the test set.\n\n1. [Is there data drift between the training data and the test data?](https://www.kaggle.com/competitions/csiro-biomass/discussion/613724)\n\n    From **Competition Host**:\n> The data split between the public and private sets is not completely random.\n> \n> We used data from various seasons and states throughout the year as the training set. For validation and testing, we included some data from overlapping time periods and locations, **while also incorporating data from non-overlapping time periods to evaluate the model's generalization ability**.\n\n2. [State and Species](https://www.kaggle.com/competitions/csiro-biomass/discussion/615003)\n\n    From **Competition Host**:\n> Hi there, images in the testing set contain the same State and Species\n\n3. [Are There Any New Species in the Test Dataset?](https://www.kaggle.com/competitions/csiro-biomass/discussion/614083)\n    From **Competition Host**:\n> Great question. The direct answer is that all the dominant species in the test set are the same as those in the training set.\n> \n> Please note that the \"Species\" column refers to the visually annotated dominant species in the images and does not include every species present (which would be practically impossible). Additionally, the \"Species\" column is only provided for the training set and is not available for the testing set (in case you didn't know it).\n\nIt appears the distribution differences between the test and training sets are **primarily reflected in the date**. Therefore, we could attempt using `Sampling_Date` as groups for K-fold cross-validation."}, {"cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [], "source": "agg_train_df['Sampling_Date_Month'] = agg_train_df.Sampling_Date.apply(lambda x: x.split('/')[1].strip())"}, {"cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": "agg_train_df = agg_train_df.sort_index().sample(frac=1.0, random_state=31).copy()  # shuffle"}, {"cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [], "source": "agg_train_df['idx'] = agg_train_df.index"}, {"cell_type": "code", "execution_count": 17, "metadata": {"scrolled": true}, "outputs": [], "source": "half_num = agg_train_df.shape[0] // 6\nhalf_num"}, {"cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [], "source": "head_df = agg_train_df.iloc[:half_num].reset_index(drop=True)\ntail_df = agg_train_df.iloc[half_num:].reset_index(drop=True)\nhead_df.shape[0], tail_df.shape[0], len(set(head_df.idx) | set(tail_df.idx))"}, {"cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [], "source": "agg_train_df['fold'] = None"}, {"cell_type": "code", "execution_count": 20, "metadata": {"scrolled": true}, "outputs": [], "source": "kfold = KFold(n_splits=NFOLD, shuffle=True, random_state=KFOLD_SEED)\nfor i, (trn_idx, val_idx) in enumerate(kfold.split(head_df.index, y=head_df.State, groups=head_df.Sampling_Date)):\n    ori_val_idx = head_df.loc[val_idx, 'idx']\n    agg_train_df.loc[ori_val_idx, 'fold'] = i"}, {"cell_type": "code", "execution_count": 22, "metadata": {"scrolled": true}, "outputs": [], "source": "kfold = StratifiedGroupKFold(n_splits=NFOLD, shuffle=True, random_state=KFOLD_SEED)\nfor i, (trn_idx, val_idx) in enumerate(kfold.split(tail_df.index, y=tail_df.State, groups=tail_df.Sampling_Date)):\n    ori_val_idx = tail_df.loc[val_idx, 'idx']\n    agg_train_df.loc[ori_val_idx, 'fold'] = i"}, {"cell_type": "code", "execution_count": 23, "metadata": {}, "outputs": [], "source": "agg_train_df.sort_index(inplace=True)"}, {"cell_type": "code", "execution_count": 24, "metadata": {}, "outputs": [], "source": "agg_train_df.fold.value_counts(dropna=False).sort_index()"}, {"cell_type": "code", "execution_count": 25, "metadata": {}, "outputs": [], "source": "for i in range(NFOLD):\n    trn_df = agg_train_df[agg_train_df.fold != i]\n    val_df = agg_train_df[agg_train_df.fold == i]\n    \n    flag = val_df.Sampling_Date.isin(trn_df.Sampling_Date)\n    print(f'trn({trn_df.shape[0]}) -> val({val_df.shape[0]}): {flag.mean()}')"}, {"cell_type": "markdown", "metadata": {}, "source": "## DataLoader"}, {"cell_type": "code", "execution_count": 26, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:51.899647Z", "iopub.status.busy": "2025-11-26T05:13:51.899382Z", "iopub.status.idle": "2025-11-26T05:13:58.321093Z", "shell.execute_reply": "2025-11-26T05:13:58.320344Z", "shell.execute_reply.started": "2025-11-26T05:13:51.899623Z"}}, "outputs": [], "source": "from torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms"}, {"cell_type": "code", "execution_count": 27, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:58.322393Z", "iopub.status.busy": "2025-11-26T05:13:58.321963Z", "iopub.status.idle": "2025-11-26T05:13:58.329794Z", "shell.execute_reply": "2025-11-26T05:13:58.329054Z", "shell.execute_reply.started": "2025-11-26T05:13:58.322368Z"}}, "outputs": [], "source": "class RegressionDataset(Dataset):\n    def __init__(self, data, transform=None, vertical_split=True):\n        self.data = data\n        self.transform = transform\n        self.vertical_split = vertical_split\n\n    def __len__(self):\n        return self.data.shape[0]\n\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        image = item.image\n        targets = [item['Dry_Green_g'], item['Dry_Clover_g'], item['Dry_Dead_g']]\n        \n        if self.vertical_split:\n            # \u5782\u76f4\u5747\u5206\u6210\u5de6\u53f3\u4e24\u5f20\u56fe\u7247\n            width, height = image.size\n            mid_point = width // 2\n            left_image = image.crop((0, 0, mid_point, height))\n            right_image = image.crop((mid_point, 0, width, height))\n            \n            if self.transform:\n                left_image = self.transform(left_image)\n                right_image = self.transform(right_image)\n            \n            return left_image, right_image, targets\n        \n        else:\n            if self.transform:\n                image = self.transform(image)\n\n            return image, targets\n\n\ndef create_dataloader(data, target_image_size=(256, 256), batch_size=32, shuffle=True, aug=True, tta_transform=None):    \n    if aug:\n        transform = transforms.Compose([\n            transforms.Resize(target_image_size),\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.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n        ])\n    \n    else:\n        if tta_transform:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n                tta_transform,\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n        else:\n            transform = transforms.Compose([\n                transforms.Resize(target_image_size),\n\n                transforms.ToTensor(),\n                transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n            ])\n\n    dataset = RegressionDataset(data, transform=transform)\n    print('dataset size:', len(dataset))\n    dataloader = DataLoader(dataset, batch_size=batch_size, shuffle=shuffle, num_workers=4)\n    return dataloader"}, {"cell_type": "code", "execution_count": 28, "metadata": {}, "outputs": [], "source": "def get_tta_dataloaders(data, target_image_size, batch_size):\n    res = []\n    for transform in [None, transforms.RandomHorizontalFlip(p=1.0), transforms.RandomVerticalFlip(p=1.0), transforms.RandomRotation([90, 90])]:\n        res.append(\n            create_dataloader(data, target_image_size, batch_size, shuffle=False, aug=False, tta_transform=transform)\n        )\n    return res"}, {"cell_type": "markdown", "metadata": {}, "source": "## Model"}, {"cell_type": "code", "execution_count": 29, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:13:58.330975Z", "iopub.status.busy": "2025-11-26T05:13:58.330716Z", "iopub.status.idle": "2025-11-26T05:14:01.753392Z", "shell.execute_reply": "2025-11-26T05:14:01.752597Z", "shell.execute_reply.started": "2025-11-26T05:13:58.330953Z"}}, "outputs": [], "source": "import timm\nimport torch\nimport torch.nn as nn"}, {"cell_type": "code", "execution_count": 30, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:01.760763Z", "iopub.status.busy": "2025-11-26T05:14:01.760506Z", "iopub.status.idle": "2025-11-26T05:14:03.816872Z", "shell.execute_reply": "2025-11-26T05:14:03.816344Z", "shell.execute_reply.started": "2025-11-26T05:14:01.760741Z"}}, "outputs": [], "source": "model = timm.create_model(MODEL_NAME, pretrained=False, num_classes=3)"}, {"cell_type": "code", "execution_count": 31, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:03.817519Z", "iopub.status.busy": "2025-11-26T05:14:03.817348Z", "iopub.status.idle": "2025-11-26T05:14:03.822464Z", "shell.execute_reply": "2025-11-26T05:14:03.821865Z", "shell.execute_reply.started": "2025-11-26T05:14:03.817503Z"}}, "outputs": [], "source": "model.pretrained_cfg"}, {"cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [], "source": "TARGET_IMAGE_SIZE = model.pretrained_cfg['input_size'][1:]"}, {"cell_type": "code", "execution_count": 33, "metadata": {}, "outputs": [], "source": "class FiLM(nn.Module):\n    def __init__(self, feat_dim):\n        super().__init__()\n        hidden = max(32, feat_dim // 2)\n        self.mlp = nn.Sequential(\n            nn.Linear(feat_dim, hidden), nn.ReLU(inplace=True), nn.Linear(hidden, feat_dim * 2)\n        )\n\n    def forward(self, context):\n        gamma_beta = self.mlp(context)\n        return torch.chunk(gamma_beta, 2, dim=1)"}, {"cell_type": "code", "execution_count": 34, "metadata": {}, "outputs": [], "source": "class MultiTargetRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0, freeze_backbone=False):\n        super().__init__()\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=0, global_pool='avg')\n        \n        self.film = FiLM(self.backbone.num_features)\n        \n        self.dropout = nn.Dropout(dropout)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.backbone.num_features * 2, 8),\n                nn.ReLU(inplace=True),\n                nn.Dropout(dropout),\n                nn.Linear(8, 1),\n            )\n\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        \n        self.softplus = nn.Softplus(beta=1.0)\n    \n        if freeze_backbone:\n            for p in self.backbone.parameters():\n                p.requires_grad = False\n    \n    \n    def forward(self, left_img, right_img):\n        left_feat = self.backbone(left_img)\n        right_feat = self.backbone(right_img)\n        \n        context = (left_feat + right_feat) / 2\n        gamma, beta = self.film(context)\n        \n        left_feat_modulated = left_feat * (1 + gamma) + beta\n        right_feat_modulated = right_feat * (1 + gamma) + beta\n        \n        combined = torch.cat([left_feat_modulated, right_feat_modulated], dim=1)\n        \n        green = self.softplus(self.head_green(combined))    # Bx1\n        clover = self.softplus(self.head_clover(combined))  # Bx1\n        dead = self.softplus(self.head_dead(combined))   # Bx1\n    \n        logits = torch.cat([green, clover, dead], dim=1)  # Bx3 \n\n        return logits"}, {"cell_type": "markdown", "metadata": {}, "source": "## Train"}, {"cell_type": "code", "execution_count": 35, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:03.82358Z", "iopub.status.busy": "2025-11-26T05:14:03.823383Z", "iopub.status.idle": "2025-11-26T05:14:03.844755Z", "shell.execute_reply": "2025-11-26T05:14:03.844054Z", "shell.execute_reply.started": "2025-11-26T05:14:03.823562Z"}}, "outputs": [], "source": "import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim import AdamW\nfrom transformers import get_cosine_schedule_with_warmup"}, {"cell_type": "code", "execution_count": 36, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:03.861197Z", "iopub.status.busy": "2025-11-26T05:14:03.86099Z", "iopub.status.idle": "2025-11-26T05:14:03.875253Z", "shell.execute_reply": "2025-11-26T05:14:03.874606Z", "shell.execute_reply.started": "2025-11-26T05:14:03.861173Z"}}, "outputs": [], "source": "# ======== Weighted R\u00b2 ========\ndef weighted_r2_score(y_true: np.ndarray, y_pred: np.ndarray):\n    \"\"\"\n    y_true, y_pred: shape (N, 5): Green/Clover/Dead/GDM/Total\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"}, {"cell_type": "code", "execution_count": 37, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:03.876167Z", "iopub.status.busy": "2025-11-26T05:14:03.875973Z", "iopub.status.idle": "2025-11-26T05:14:03.893652Z", "shell.execute_reply": "2025-11-26T05:14:03.892829Z", "shell.execute_reply.started": "2025-11-26T05:14:03.876144Z"}}, "outputs": [], "source": "def calc_metric(outputs, targets):\n    '''\n        outputs/targets: shape (N, 3): 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"}, {"cell_type": "code", "execution_count": 38, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:03.894694Z", "iopub.status.busy": "2025-11-26T05:14:03.894431Z", "iopub.status.idle": "2025-11-26T05:14:03.908794Z", "shell.execute_reply": "2025-11-26T05:14:03.908076Z", "shell.execute_reply.started": "2025-11-26T05:14:03.894667Z"}}, "outputs": [], "source": "def train_epoch(model, dataloader, criterion, optimizer, scheduler, device):\n    model.train()\n    total_loss = 0\n\n    for left_images, right_images, targets in dataloader:\n        left_images = left_images.to(device)\n        right_images = right_images.to(device)\n        targets = torch.stack(targets).T.float().to(device)\n\n        optimizer.zero_grad()\n        outputs = model(left_images, right_images)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        scheduler.step()\n\n        total_loss += loss.item()\n\n    return total_loss / len(dataloader)\n\n\ndef validate(model, dataloader, criterion, device):\n    model.eval()\n    total_loss = 0\n    all_outputs = []\n    all_targets = []\n\n    with torch.no_grad():\n        for left_images, right_images, targets in dataloader:\n            left_images = left_images.to(device)\n            right_images = right_images.to(device)\n            targets = torch.stack(targets).T.float().to(device)\n\n            outputs = model(left_images, right_images)\n            loss = criterion(outputs, targets)\n            total_loss += loss.item()\n\n            all_outputs.append(outputs.detach().cpu())\n            all_targets.append(targets.detach().cpu())\n\n    outputs = torch.cat(all_outputs).numpy()\n    targets = torch.cat(all_targets).numpy()\n    \n    return total_loss / len(dataloader), outputs, targets\n    \n    # weighted_r2, r2_scores = calc_metric(outputs, targets)\n    # return total_loss / len(dataloader), weighted_r2, r2_scores\n\n\ndef tta_validate(model, dataloaders, criterion, device):\n    if not isinstance(dataloaders, list):\n        dataloaders = [dataloaders]\n    \n    all_loss = []\n    all_outputs = []\n    all_targets = []\n    for dataloader in dataloaders:\n        loss, outputs, targets = validate(model, dataloader, criterion, device)\n        all_loss.append(loss)\n        all_outputs.append(outputs)\n        all_targets.append(targets)\n    \n    avg_loss = np.mean(all_loss)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    avg_targets = np.mean(all_targets, axis=0)\n    \n    weighted_r2, r2_scores = calc_metric(avg_outputs, avg_targets)\n    \n    return avg_loss, weighted_r2, r2_scores"}, {"cell_type": "code", "execution_count": 39, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:03.909865Z", "iopub.status.busy": "2025-11-26T05:14:03.909607Z", "iopub.status.idle": "2025-11-26T05:14:03.928121Z", "shell.execute_reply": "2025-11-26T05:14:03.927467Z", "shell.execute_reply.started": "2025-11-26T05:14:03.909844Z"}}, "outputs": [], "source": "def train_fold(data, fold, batch_size=32, continue_training=False):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    # Hyperparameters\n    # batch_size = 8\n    lr = LR\n    patience = 10\n    num_epochs = 5 if DEBUG else 100\n    warmup_ratio = 0.05\n\n    # data\n    train_loader = create_dataloader(data[data.fold != fold], TARGET_IMAGE_SIZE, batch_size, shuffle=True, aug=True)\n    val_loader = create_dataloader(data[data.fold == fold], TARGET_IMAGE_SIZE, batch_size, shuffle=False, aug=False)\n    # val_loaders = get_tta_dataloaders(data[data.fold == fold], TARGET_IMAGE_SIZE, batch_size)\n\n    # model, loss, optimizer\n    # model = timm.create_model(MODEL_NAME, pretrained=True, num_classes=3)\n    if continue_training:\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3, freeze_backbone=False)\n        model_file = f'{OUTPUT_DIR}/best_mode_fold{fold}.pth'\n        model.load_state_dict(torch.load(model_file))\n        \n    else:\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=True, num_classes=3, freeze_backbone=True)\n        \n    model.to(device)\n\n    criterion = nn.SmoothL1Loss()  # nn.MSELoss()\n    optimizer = AdamW(model.parameters(), lr=lr / 500 if continue_training else lr)\n\n    num_training_steps = num_epochs * len(train_loader)\n    warmup_steps = int(warmup_ratio * num_training_steps)\n    \n    # scheduler = optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='max', factor=0.5, patience=patience//2)\n    scheduler = get_cosine_schedule_with_warmup(optimizer, num_warmup_steps=warmup_steps, num_training_steps=num_training_steps)\n\n    # Training loop\n    history = []\n    best_score = -float('inf')\n    # best_loss = float('inf')\n    epochs_without_improvement = 0\n    \n    for epoch in range(num_epochs):\n        train_loss = train_epoch(model, train_loader, criterion, optimizer, scheduler, device)\n        # val_loss, weighted_r2, r2_scores = validate(model, val_loader, criterion, device)\n        val_loss, weighted_r2, r2_scores = tta_validate(model, val_loader, criterion, device)\n\n        if epoch % 10 == 0:\n            print(f\"Epoch [{epoch}/{num_epochs}]: Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}, weighted_r2: {weighted_r2:.4f}, lr: {optimizer.param_groups[0]['lr']}\")\n        \n        history.append({\n            'train_loss': train_loss,\n            'val_loss': val_loss,\n            'lr': optimizer.param_groups[0]['lr'],\n            'weighted_r2': weighted_r2,\n            'r2_scores': r2_scores,\n        })\n        \n        # \u65e9\u505c\n        if weighted_r2 > best_score:\n        # if val_loss < best_loss:\n            best_loss = val_loss\n            best_score = weighted_r2\n            epochs_without_improvement = 0\n            # \u4fdd\u5b58\u6700\u4f73\u6a21\u578b\n            torch.save(model.state_dict(), f'{OUTPUT_DIR}/best_mode_fold{fold}.pth')\n        \n        else:\n            epochs_without_improvement += 1\n        \n        if epochs_without_improvement >= patience:\n            print(f\"\u65e9\u505c: epoch={epoch}, {patience} \u4e2a epoch \u65e0\u6539\u5584\")\n            break\n\n    print(f\"\\nTraining completed. Best weighted_r2: {best_score:.4f}\")\n    \n    return history, best_score"}, {"cell_type": "code", "execution_count": 40, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:03.845773Z", "iopub.status.busy": "2025-11-26T05:14:03.845478Z", "iopub.status.idle": "2025-11-26T05:14:03.860248Z", "shell.execute_reply": "2025-11-26T05:14:03.859597Z", "shell.execute_reply.started": "2025-11-26T05:14:03.845756Z"}}, "outputs": [], "source": "OUTPUT_DIR = 'trained_models/'\nif not os.path.exists(OUTPUT_DIR):\n    os.mkdir(OUTPUT_DIR)"}, {"cell_type": "markdown", "metadata": {}, "source": "### Stage 1"}, {"cell_type": "code", "execution_count": 41, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:09.475734Z", "iopub.status.busy": "2025-11-26T05:14:09.47535Z"}, "scrolled": true}, "outputs": [], "source": "if TRAIN:\n    all_best_score = []\n\n    for i in range(2 if DEBUG else NFOLD):\n        print(f'### fold={i}')\n        history, best_score = train_fold(agg_train_df, fold=i, batch_size=32, continue_training=False)\n        all_best_score.append(best_score)\n        history = pd.DataFrame(history)\n\n        history.to_json(\n            f'{OUTPUT_DIR}/history_fold{i}.jsonl',\n            orient='records',\n            lines=True,\n            force_ascii=False,\n        )\n\n        # plot\n        plt.figure(figsize=(16, 4))\n\n        plt.subplot(1, 3, 1)\n        plt.title('LR')\n        plt.plot(history.lr)\n\n        plt.subplot(1, 3, 2)\n        plt.title('Loss')\n        plt.plot(history.train_loss, label='train')\n        plt.plot(history.val_loss, label='val')\n        plt.legend()\n\n        plt.subplot(1, 3, 3)\n        plt.title('weighted_r2')\n        plt.plot(history.weighted_r2)\n        plt.show()\n    \n    print('Avg CV:', np.mean(all_best_score))"}, {"cell_type": "markdown", "metadata": {}, "source": "### Stage2"}, {"cell_type": "code", "execution_count": 42, "metadata": {"execution": {"iopub.execute_input": "2025-11-26T05:14:09.475734Z", "iopub.status.busy": "2025-11-26T05:14:09.47535Z"}, "scrolled": true}, "outputs": [], "source": "if TRAIN:\n    all_best_score = []\n\n    for i in range(2 if DEBUG else NFOLD):\n        print(f'### fold={i}')\n        history, best_score = train_fold(agg_train_df, fold=i, batch_size=8, continue_training=True)\n        all_best_score.append(best_score)\n        history = pd.DataFrame(history)\n\n        history.to_json(\n            f'{OUTPUT_DIR}/history_fold{i}.jsonl',\n            orient='records',\n            lines=True,\n            force_ascii=False,\n        )\n\n        # plot\n        plt.figure(figsize=(16, 4))\n\n        plt.subplot(1, 3, 1)\n        plt.title('LR')\n        plt.plot(history.lr)\n\n        plt.subplot(1, 3, 2)\n        plt.title('Loss')\n        plt.plot(history.train_loss, label='train')\n        plt.plot(history.val_loss, label='val')\n        plt.legend()\n\n        plt.subplot(1, 3, 3)\n        plt.title('weighted_r2')\n        plt.plot(history.weighted_r2)\n        plt.show()\n    \n    print('Avg CV:', np.mean(all_best_score))"}, {"cell_type": "markdown", "metadata": {}, "source": "## Inference"}, {"cell_type": "code", "execution_count": 45, "metadata": {}, "outputs": [], "source": "import os\nfrom pathlib import Path"}, {"cell_type": "code", "execution_count": 46, "metadata": {}, "outputs": [], "source": "def get_lastest_saved_models():\n    model_root = '/kaggle/input/csiro-simple-output/pytorch/default/'\n    \n    latest = 1\n    for version in os.listdir(model_root):\n        try:\n            version = int(version)\n        except:\n            continue\n\n        if version > latest:\n            latest = version\n\n    return f'{model_root}/{latest}/trained_models/'"}, {"cell_type": "code", "execution_count": 47, "metadata": {}, "outputs": [], "source": "SAVED_MODELS = './trained_models/' if TRAIN else get_lastest_saved_models()"}, {"cell_type": "code", "execution_count": 50, "metadata": {}, "outputs": [], "source": "def predict(model, dataloader, device):\n    model.to(device)\n    model.eval()\n\n    all_outputs = []\n    with torch.no_grad():\n        for left_images, right_images, targets in dataloader:\n            left_images = left_images.to(device)\n            right_images = right_images.to(device)\n    \n            outputs = model(left_images, right_images)\n            all_outputs.append(outputs.detach().cpu())\n    \n    outputs = torch.cat(all_outputs).numpy()\n    return outputs"}, {"cell_type": "code", "execution_count": 51, "metadata": {}, "outputs": [], "source": "def tta_predict(model, dataloaders, device):\n    all_outputs = []\n    for dataloader in dataloaders:\n        outputs = predict(model, dataloader, device)\n        all_outputs.append(outputs)\n    avg_outputs = np.mean(all_outputs, axis=0)\n    return avg_outputs"}, {"cell_type": "code", "execution_count": 57, "metadata": {}, "outputs": [], "source": "def kfold_predict(dataloaders):\n    device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')\n    \n    all_preds = []\n    for model_file in Path(SAVED_MODELS).glob('*.pth'):\n        # model = timm.create_model(MODEL_NAME, pretrained=False, num_classes=3)\n        fold_idx = int(model_file.name.split('.')[0].split('fold')[1])\n        if fold_idx >= NFOLD: continue\n        print(model_file.name)\n        model = MultiTargetRegressor(MODEL_NAME, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file))\n\n        preds = tta_predict(model, dataloaders, device)\n        all_preds.append(preds)\n\n    avg_preds = np.mean(all_preds, axis=0)\n    return avg_preds"}, {"cell_type": "code", "execution_count": 53, "metadata": {}, "outputs": [], "source": "test_df = pd.read_csv(DATA_ROOT + 'test.csv')\n\ntest_df['target'] = 0.0\ntest_df[['sample_id_prefix', 'sample_id_suffix']] = test_df.sample_id.str.split('__', expand=True)"}, {"cell_type": "code", "execution_count": 54, "metadata": {}, "outputs": [], "source": "cols = ['sample_id_prefix', 'image_path']\nagg_test_df = test_df.groupby(cols).apply(lambda df: df.set_index('target_name').target)\nagg_test_df.reset_index(inplace=True)\nagg_test_df.columns.name = None\n\nagg_test_df['image'] = agg_test_df.image_path.progress_apply(\n    lambda path: Image.open(DATA_ROOT + path).convert('RGB')\n)\n\nagg_test_df.head()"}, {"cell_type": "code", "execution_count": 55, "metadata": {}, "outputs": [], "source": "test_loader = get_tta_dataloaders(agg_test_df, TARGET_IMAGE_SIZE, 64)"}, {"cell_type": "code", "execution_count": 58, "metadata": {}, "outputs": [], "source": "preds = kfold_predict(test_loader)"}, {"cell_type": "code", "execution_count": 59, "metadata": {}, "outputs": [], "source": "agg_test_df[['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']] = preds\nagg_test_df['GDM_g'] = agg_test_df.Dry_Green_g + agg_test_df.Dry_Clover_g\nagg_test_df['Dry_Total_g'] = agg_test_df.GDM_g + agg_test_df.Dry_Dead_g"}, {"cell_type": "code", "execution_count": 60, "metadata": {}, "outputs": [], "source": "agg_test_df.head()"}, {"cell_type": "code", "execution_count": 61, "metadata": {}, "outputs": [], "source": "cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nsub_df = agg_test_df.set_index('sample_id_prefix')[cols].stack()\nsub_df = sub_df.reset_index()\nsub_df.columns = ['sample_id_prefix', 'target_name', 'target']\n\nsub_df['sample_id'] = sub_df.sample_id_prefix + '__' + sub_df.target_name"}, {"cell_type": "code", "execution_count": 62, "metadata": {}, "outputs": [], "source": "cols = ['sample_id', 'target']\nsub_df[cols].to_csv('submission.csv', index=False)"}, {"cell_type": "code", "execution_count": 63, "metadata": {}, "outputs": [], "source": "!head submission.csv"}, {"cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": ""}], "metadata": {"kaggle": {"accelerator": "gpu", "dataSources": [{"databundleVersionId": 14254895, "sourceId": 112509, "sourceType": "competition"}], "dockerImageVersionId": 31193, "isGpuEnabled": true, "isInternetEnabled": true, "language": "python", "sourceType": "notebook"}, "kernelspec": {"display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3"}, "language_info": {"codemirror_mode": {"name": "ipython", "version": 3}, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.15"}}, "nbformat": 4, "nbformat_minor": 4}