{"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":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\n\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nfrom PIL import Image\nfrom pathlib import Path\n\n\nimport tensorflow as tf\nfrom tqdm.auto import tqdm\n\n\nsns.set_style(\"whitegrid\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:16.504111Z","iopub.execute_input":"2026-02-01T03:29:16.504801Z","iopub.status.idle":"2026-02-01T03:29:18.213693Z","shell.execute_reply.started":"2026-02-01T03:29:16.504773Z","shell.execute_reply":"2026-02-01T03:29:18.212924Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_PATH = \"/kaggle/input/csiro-biomass/\"\ntrain = BASE_PATH + \"train.csv\"\ntest  = BASE_PATH + \"test.csv\"\n\ndf_train = pd.read_csv(train)\ndf_test = pd.read_csv(test)\n\ndisplay(df_train)\ndisplay(df_test)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:18.214997Z","iopub.execute_input":"2026-02-01T03:29:18.215411Z","iopub.status.idle":"2026-02-01T03:29:18.268298Z","shell.execute_reply.started":"2026-02-01T03:29:18.215388Z","shell.execute_reply":"2026-02-01T03:29:18.267707Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(f\"Train shape: {df_train.shape} | Test shape: {df_test.shape}\")\nprint(f\"Unique images: {df_train['image_path'].nunique()}\")\nprint(f\"Target types: {df_train['target_name'].unique()}\")\nprint(f\"\\nTarget distribution:\")\nprint(df_train['target_name'].value_counts())\nprint(f\"\\nTarget statistics:\")\nprint(df_train.groupby('target_name')['target'].describe().round(2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:18.268996Z","iopub.execute_input":"2026-02-01T03:29:18.269237Z","iopub.status.idle":"2026-02-01T03:29:18.299678Z","shell.execute_reply.started":"2026-02-01T03:29:18.269205Z","shell.execute_reply":"2026-02-01T03:29:18.299161Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, axes = plt.subplots(2, 2, figsize=(14, 10))\n\nsns.countplot(data=df_train, x=\"target_name\", ax=axes[0,0])\naxes[0,0].set_title(\"Training Samples per Target Type\")\naxes[0,0].tick_params(axis='x', rotation=45)\n\nsns.histplot(df_train[\"Pre_GSHH_NDVI\"], bins=40, ax=axes[0,1], kde=True)\naxes[0,1].set_title(\"NDVI Distribution\")\n\nsns.histplot(df_train[\"Height_Ave_cm\"], bins=40, ax=axes[1,0], kde=True, color='orange')\naxes[1,0].set_title(\"Height Distribution\")\n\nfor target in df_train['target_name'].unique():\n    subset = df_train[df_train['target_name']==target]\n    axes[1,1].scatter(subset['Pre_GSHH_NDVI'], subset['target'], alpha=0.3, label=target, s=10)\naxes[1,1].set_xlabel(\"NDVI\")\naxes[1,1].set_ylabel(\"Target (g)\")\naxes[1,1].set_title(\"NDVI vs Biomass by Target Type\")\naxes[1,1].legend(fontsize=8)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:18.300552Z","iopub.execute_input":"2026-02-01T03:29:18.301249Z","iopub.status.idle":"2026-02-01T03:29:19.179191Z","shell.execute_reply.started":"2026-02-01T03:29:18.301223Z","shell.execute_reply":"2026-02-01T03:29:19.178414Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wide = df_train.pivot_table(index=\"sample_id\", columns=\"target_name\", values=\"target\", aggfunc=\"mean\")\n\nplt.figure(figsize=(8,6))\nsns.heatmap(wide.corr(), annot=True, fmt=\".2f\", cmap=\"coolwarm\", vmin=-1, vmax=1, center=0)\nplt.title(\"Correlation Between Biomass Target Types\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:19.181174Z","iopub.execute_input":"2026-02-01T03:29:19.181399Z","iopub.status.idle":"2026-02-01T03:29:19.385990Z","shell.execute_reply.started":"2026-02-01T03:29:19.181377Z","shell.execute_reply":"2026-02-01T03:29:19.385209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"sample_rows = df_train.sample(5, random_state=42)\n\nplt.figure(figsize=(20, 4))\nfor i, (idx, row) in enumerate(sample_rows.iterrows()):\n    full_path = os.path.join(BASE_PATH, row[\"image_path\"])\n    img = Image.open(full_path)\n    \n    plt.subplot(1, 5, i + 1)\n    plt.imshow(img)\n    plt.title(f\"NDVI: {row['Pre_GSHH_NDVI']:.2f}\\nHeight: {row['Height_Ave_cm']:.1f}cm\\n{row['target_name']}: {row['target']:.1f}g\", fontsize=9)\n    plt.axis(\"off\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:19.386808Z","iopub.execute_input":"2026-02-01T03:29:19.387082Z","iopub.status.idle":"2026-02-01T03:29:20.660035Z","shell.execute_reply.started":"2026-02-01T03:29:19.387059Z","shell.execute_reply":"2026-02-01T03:29:20.659030Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.preprocessing import StandardScaler\nimport pickle\n\nscalers = {}\ndf_train_scaled = df_train.copy()\n\nfor target_name in df_train[\"target_name\"].unique():\n    mask = df_train[\"target_name\"] == target_name\n    scaler = StandardScaler()\n    \n    df_train_scaled.loc[mask, \"target\"] = scaler.fit_transform(\n        df_train.loc[mask, \"target\"].values.reshape(-1, 1)\n    ).flatten()\n    \n    scalers[target_name] = scaler\n    print(f\"{target_name}: μ={scaler.mean_[0]:.2f}, σ={scaler.scale_[0]:.2f}\")\n\nwith open(\"target_scalers.pkl\", \"wb\") as f:\n    pickle.dump(scalers, f)\n\nprint(f\"\\nOriginal: [{df_train['target'].min():.1f}, {df_train['target'].max():.1f}] → Scaled: [{df_train_scaled['target'].min():.2f}, {df_train_scaled['target'].max():.2f}]\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:20.661109Z","iopub.execute_input":"2026-02-01T03:29:20.661411Z","iopub.status.idle":"2026-02-01T03:29:20.708257Z","shell.execute_reply.started":"2026-02-01T03:29:20.661387Z","shell.execute_reply":"2026-02-01T03:29:20.707582Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"IMG_SIZE = (224, 224)\n\ndef load_image(path, y):\n    x = tf.io.read_file(path)\n    x = tf.io.decode_jpeg(x, channels=3)\n    x = tf.image.resize(x, IMG_SIZE)\n    x = tf.image.convert_image_dtype(x, tf.float32)\n    x = (x - 0.5) * 2.0\n    return x, y\n\ndef augment(x, y):\n    x = tf.image.random_flip_left_right(x)\n    x = tf.image.random_flip_up_down(x)\n    x = tf.image.random_brightness(x, 0.2)\n    x = tf.image.random_contrast(x, 0.8, 1.2)\n    x = tf.image.random_saturation(x, 0.8, 1.2)\n    x = tf.clip_by_value(x, -1.0, 1.0)\n    return x, y\n\ndef make_dataset(df, shuffle=True, augment_data=False, batch_size=32):\n    paths = df[\"image_path\"].apply(lambda p: os.path.join(BASE_PATH, p)).values\n    targets = df[\"target\"].values.astype(\"float32\")\n    \n    ds = tf.data.Dataset.from_tensor_slices((paths, targets))\n    ds = ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    if augment_data:\n        ds = ds.map(augment, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    if shuffle:\n        ds = ds.shuffle(2048)\n    \n    ds = ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n    return ds\n\ndf_train_shuffled = df_train_scaled.sample(frac=1, random_state=42).reset_index(drop=True)\nsplit_idx = int(0.8 * len(df_train_shuffled))\ntrain_df, val_df = df_train_shuffled[:split_idx], df_train_shuffled[split_idx:]\n\ntrain_ds = make_dataset(train_df, shuffle=True, augment_data=True)\nval_ds = make_dataset(val_df, shuffle=False, augment_data=False)\n\nprint(f\"Train: {len(train_df)} samples | Val: {len(val_df)} samples\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:20.709134Z","iopub.execute_input":"2026-02-01T03:29:20.709663Z","iopub.status.idle":"2026-02-01T03:29:21.602543Z","shell.execute_reply.started":"2026-02-01T03:29:20.709637Z","shell.execute_reply":"2026-02-01T03:29:21.601680Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_dataset_with_metadata(df, shuffle=True, augment_data=False, batch_size=32):\n    paths = df[\"image_path\"].apply(lambda p: os.path.join(BASE_PATH, p)).values\n    targets = df[\"target\"].values.astype(\"float32\")\n    \n    ndvi = df[\"Pre_GSHH_NDVI\"].values.astype(\"float32\")\n    height = df[\"Height_Ave_cm\"].values.astype(\"float32\")\n    \n    ndvi = (ndvi - ndvi.min()) / (ndvi.max() - ndvi.min() + 1e-7)\n    height = (height - height.min()) / (height.max() - height.min() + 1e-7)\n    \n    metadata = np.stack([ndvi, height], axis=1)\n    \n    def load_image_with_meta(path, meta, y):\n        x = tf.io.read_file(path)\n        x = tf.io.decode_jpeg(x, channels=3)\n        x = tf.image.resize(x, IMG_SIZE)\n        x = tf.image.convert_image_dtype(x, tf.float32)\n        x = (x - 0.5) * 2.0\n        return (x, meta), y\n    \n    def augment_with_meta(inputs, y):\n        x, meta = inputs\n        x = tf.image.random_flip_left_right(x)\n        x = tf.image.random_flip_up_down(x)\n        x = tf.image.random_brightness(x, 0.2)\n        x = tf.image.random_contrast(x, 0.8, 1.2)\n        x = tf.image.random_saturation(x, 0.8, 1.2)\n        x = tf.clip_by_value(x, -1.0, 1.0)\n        return (x, meta), y\n    \n    ds = tf.data.Dataset.from_tensor_slices((paths, metadata, targets))\n    ds = ds.map(load_image_with_meta, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    if augment_data:\n        ds = ds.map(augment_with_meta, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    if shuffle:\n        ds = ds.shuffle(2048)\n    \n    return ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n\ntrain_ds_meta = make_dataset_with_metadata(train_df, shuffle=True, augment_data=True)\nval_ds_meta = make_dataset_with_metadata(val_df, shuffle=False, augment_data=False)\n\nprint(f\"Multi-input pipeline: {len(train_df)} train | {len(val_df)} val\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:21.603574Z","iopub.execute_input":"2026-02-01T03:29:21.603800Z","iopub.status.idle":"2026-02-01T03:29:21.751804Z","shell.execute_reply.started":"2026-02-01T03:29:21.603779Z","shell.execute_reply":"2026-02-01T03:29:21.751208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ResidualBlock(tf.keras.layers.Layer):\n    def __init__(self, filters, strides=1):\n        super().__init__()\n        self.conv1 = tf.keras.layers.Conv2D(filters, 3, strides, padding=\"same\", use_bias=False)\n        self.bn1 = tf.keras.layers.BatchNormalization()\n        self.conv2 = tf.keras.layers.Conv2D(filters, 3, padding=\"same\", use_bias=False)\n        self.bn2 = tf.keras.layers.BatchNormalization()\n        \n        self.shortcut = tf.keras.Sequential([\n            tf.keras.layers.Conv2D(filters, 1, strides, use_bias=False),\n            tf.keras.layers.BatchNormalization()\n        ]) if strides > 1 else lambda x: x\n        \n    def call(self, x, training=False):\n        if isinstance(self.shortcut, tf.keras.Sequential):\n            residual = self.shortcut(x, training=training)\n        else:\n            residual = self.shortcut(x)\n        x = tf.nn.relu(self.bn1(self.conv1(x), training=training))\n        x = self.bn2(self.conv2(x), training=training)\n        return tf.nn.relu(x + residual)\n\nclass SEBlock(tf.keras.layers.Layer):\n    def __init__(self, filters, ratio=16):\n        super().__init__()\n        self.gap = tf.keras.layers.GlobalAveragePooling2D()\n        self.fc1 = tf.keras.layers.Dense(filters // ratio, activation='relu')\n        self.fc2 = tf.keras.layers.Dense(filters, activation='sigmoid')\n        \n    def call(self, x):\n        se = self.gap(x)\n        se = self.fc2(self.fc1(se))\n        return x * tf.expand_dims(tf.expand_dims(se, 1), 1)\n\nclass MultiScaleCNN(tf.keras.Model):\n    def __init__(self):\n        super().__init__()\n        self.stem = tf.keras.Sequential([\n            tf.keras.layers.Conv2D(64, 7, 2, padding=\"same\", use_bias=False),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Activation('relu'),\n            tf.keras.layers.MaxPool2D(3, 2, padding=\"same\")\n        ])\n        \n        self.stage1 = tf.keras.Sequential([\n            ResidualBlock(64),\n            ResidualBlock(64),\n            SEBlock(64)\n        ])\n        \n        self.stage2 = tf.keras.Sequential([\n            ResidualBlock(128, strides=2),\n            ResidualBlock(128),\n            SEBlock(128)\n        ])\n        \n        self.stage3 = tf.keras.Sequential([\n            ResidualBlock(256, strides=2),\n            ResidualBlock(256),\n            SEBlock(256)\n        ])\n        \n        self.stage4 = tf.keras.Sequential([\n            ResidualBlock(512, strides=2),\n            ResidualBlock(512),\n            SEBlock(512)\n        ])\n        \n        self.gap = tf.keras.layers.GlobalAveragePooling2D()\n        self.gmp = tf.keras.layers.GlobalMaxPooling2D()\n        \n        self.meta_encoder = tf.keras.Sequential([\n            tf.keras.layers.Dense(64, activation='relu'),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.3),\n            tf.keras.layers.Dense(128, activation='relu'),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.3)\n        ])\n        \n        self.attention = tf.keras.Sequential([\n            tf.keras.layers.Dense(256, activation='tanh'),\n            tf.keras.layers.Dense(1, activation='sigmoid')\n        ])\n        \n        self.fusion = tf.keras.Sequential([\n            tf.keras.layers.Dense(512, activation='relu'),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.5),\n            tf.keras.layers.Dense(256, activation='relu'),\n            tf.keras.layers.BatchNormalization(),\n            tf.keras.layers.Dropout(0.4),\n            tf.keras.layers.Dense(128, activation='relu'),\n            tf.keras.layers.Dropout(0.3)\n        ])\n        \n        self.regressor = tf.keras.layers.Dense(1)\n    \n    def call(self, inputs, training=False):\n        image, metadata = inputs\n        \n        x = self.stem(image)\n        x = self.stage1(x, training=training)\n        x = self.stage2(x, training=training)\n        x = self.stage3(x, training=training)\n        x = self.stage4(x, training=training)\n        \n        gap_features = self.gap(x)\n        gmp_features = self.gmp(x)\n        img_features = tf.concat([gap_features, gmp_features], axis=-1)\n        \n        meta_features = self.meta_encoder(metadata, training=training)\n        \n        attention_weights = self.attention(tf.concat([img_features, meta_features], axis=-1))\n        img_features = img_features * attention_weights\n        meta_features = meta_features * (1 - attention_weights)\n        \n        combined = tf.concat([img_features, meta_features], axis=-1)\n        features = self.fusion(combined, training=training)\n        \n        return self.regressor(features)\n\nmodel_multi = MultiScaleCNN()\n\ndummy_img = tf.random.normal((1, 224, 224, 3))\ndummy_meta = tf.random.normal((1, 2))\n_ = model_multi((dummy_img, dummy_meta), training=False)\n\nprint(f\"Advanced ResNet-SE architecture initialized | Params: {model_multi.count_params():,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:29:21.752640Z","iopub.execute_input":"2026-02-01T03:29:21.752918Z","iopub.status.idle":"2026-02-01T03:29:25.783687Z","shell.execute_reply.started":"2026-02-01T03:29:21.752895Z","shell.execute_reply":"2026-02-01T03:29:25.783048Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"lr_schedule = tf.keras.optimizers.schedules.CosineDecayRestarts(\n    initial_learning_rate=1e-3,\n    first_decay_steps=len(train_ds_meta) * 5,\n    t_mul=2.0,\n    m_mul=0.9,\n    alpha=1e-5\n)\n\nmodel_multi.compile(\n    optimizer=tf.keras.optimizers.AdamW(learning_rate=lr_schedule, weight_decay=1e-4, clipnorm=1.0),\n    loss=tf.keras.losses.Huber(delta=1.0),\n    metrics=[\"mae\", tf.keras.metrics.RootMeanSquaredError()]\n)\n\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping('val_loss', patience=20, restore_best_weights=True, min_delta=0.005),\n    tf.keras.callbacks.ModelCheckpoint('best_model.keras', save_best_only=True, monitor='val_loss')\n]\n\nprint(\"Training model...\")\nhistory_multi = model_multi.fit(\n    train_ds_meta,\n    epochs=50,\n    validation_data=val_ds_meta,\n    callbacks=callbacks,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T03:56:52.506121Z","iopub.execute_input":"2026-02-01T03:56:52.506471Z","iopub.status.idle":"2026-02-01T04:19:07.676485Z","shell.execute_reply.started":"2026-02-01T03:56:52.506442Z","shell.execute_reply":"2026-02-01T04:19:07.675832Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"if 'target_name' in df_test.columns:\n    df_test_expanded = df_test.copy()\nelse:\n    unique_images = df_test.drop_duplicates(subset=['image_path']).copy()\n    target_names = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    \n    expanded_rows = []\n    for _, img_row in unique_images.iterrows():\n        for target_name in target_names:\n            row = img_row.copy()\n            row['target_name'] = target_name\n            img_id = img_row['sample_id'] if 'sample_id' in img_row else img_row['image_path'].split('/')[-1].split('.')[0]\n            row['sample_id'] = f\"{img_id}_{target_name}\"\n            expanded_rows.append(row)\n    \n    df_test_expanded = pd.DataFrame(expanded_rows).reset_index(drop=True)\n\nprint(f\"Test expanded: {df_test_expanded.shape} | {df_test_expanded['target_name'].value_counts().to_dict()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T04:29:19.429121Z","iopub.execute_input":"2026-02-01T04:29:19.429786Z","iopub.status.idle":"2026-02-01T04:29:19.437265Z","shell.execute_reply.started":"2026-02-01T04:29:19.429757Z","shell.execute_reply":"2026-02-01T04:29:19.436349Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_paths = df_test_expanded[\"image_path\"].apply(lambda p: os.path.join(BASE_PATH, p)).values\nhas_metadata = 'Pre_GSHH_NDVI' in df_test_expanded.columns and 'Height_Ave_cm' in df_test_expanded.columns\n\nif has_metadata:\n    ndvi_test = df_test_expanded[\"Pre_GSHH_NDVI\"].values.astype(\"float32\")\n    height_test = df_test_expanded[\"Height_Ave_cm\"].values.astype(\"float32\")\n    \n    train_ndvi, train_height = train_df[\"Pre_GSHH_NDVI\"].values, train_df[\"Height_Ave_cm\"].values\n    ndvi_test = (ndvi_test - train_ndvi.min()) / (train_ndvi.max() - train_ndvi.min() + 1e-7)\n    height_test = (height_test - train_height.min()) / (train_height.max() - train_height.min() + 1e-7)\nelse:\n    train_ndvi, train_height = train_df[\"Pre_GSHH_NDVI\"].values, train_df[\"Height_Ave_cm\"].values\n    ndvi_mean = (train_ndvi.mean() - train_ndvi.min()) / (train_ndvi.max() - train_ndvi.min())\n    height_mean = (train_height.mean() - train_height.min()) / (train_height.max() - train_height.min())\n    \n    ndvi_test = np.full(len(df_test_expanded), ndvi_mean, dtype=\"float32\")\n    height_test = np.full(len(df_test_expanded), height_mean, dtype=\"float32\")\n    print(f\"Warning: Using mean imputation for metadata\")\n\nmetadata_test = np.stack([ndvi_test, height_test], axis=1)\n\ndef load_test_image_with_meta(path, meta):\n    x = tf.io.read_file(path)\n    x = tf.io.decode_jpeg(x, channels=3)\n    x = tf.image.resize(x, IMG_SIZE)\n    x = tf.image.convert_image_dtype(x, tf.float32)\n    return ((x - 0.5) * 2.0, meta)\n\ntest_ds_meta = tf.data.Dataset.from_tensor_slices((test_paths, metadata_test))\ntest_ds_meta = test_ds_meta.map(load_test_image_with_meta, num_parallel_calls=tf.data.AUTOTUNE).batch(32).prefetch(tf.data.AUTOTUNE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T04:29:22.754619Z","iopub.execute_input":"2026-02-01T04:29:22.755204Z","iopub.status.idle":"2026-02-01T04:29:22.813550Z","shell.execute_reply.started":"2026-02-01T04:29:22.755170Z","shell.execute_reply":"2026-02-01T04:29:22.812823Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_multi_input(model, dataset):\n    predictions = []\n    for batch in dataset:\n        pred = model(batch, training=False)\n        predictions.append(pred.numpy())\n    return np.concatenate(predictions, axis=0)\n\npreds_multi = predict_multi_input(model_multi, test_ds_meta).squeeze()\n\nwith open(\"target_scalers.pkl\", \"rb\") as f:\n    scalers = pickle.load(f)\n\npreds_multi_original = np.zeros_like(preds_multi)\nfor i, (_, row) in enumerate(df_test_expanded.iterrows()):\n    preds_multi_original[i] = scalers[row[\"target_name\"]].inverse_transform([[preds_multi[i]]])[0, 0]\n\nsubmission_multi = pd.DataFrame({\n    \"sample_id\": df_test_expanded[\"sample_id\"],\n    \"target\": preds_multi_original.astype(\"float32\"),\n})\n\ntarget_counts = {t: submission_multi['sample_id'].str.contains(t).sum() \n                 for t in ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']}\n\nprint(f\"Submission: {len(submission_multi)} rows | {submission_multi['sample_id'].nunique()} unique\")\nprint(f\"Distribution: {target_counts}\")\n\nsubmission_multi.to_csv(\"submission_multi_input.csv\", index=False)\nprint(\"Submission saved!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T04:29:26.248206Z","iopub.execute_input":"2026-02-01T04:29:26.248496Z","iopub.status.idle":"2026-02-01T04:29:26.760489Z","shell.execute_reply.started":"2026-02-01T04:29:26.248472Z","shell.execute_reply":"2026-02-01T04:29:26.759777Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_with_tta(model, dataset, num_augmentations=5):\n    all_predictions = []\n    \n    for aug_idx in range(num_augmentations):\n        aug_preds = []\n        for batch in dataset:\n            img_batch, meta_batch = batch\n            \n            if aug_idx > 0:\n                img_batch = tf.image.random_flip_left_right(img_batch)\n                img_batch = tf.image.random_flip_up_down(img_batch)\n                img_batch = tf.image.random_brightness(img_batch, 0.1)\n                img_batch = tf.clip_by_value(img_batch, -1.0, 1.0)\n            \n            pred = model((img_batch, meta_batch), training=False)\n            aug_preds.append(pred.numpy())\n        \n        all_predictions.append(np.concatenate(aug_preds, axis=0))\n    \n    final_predictions = np.mean(all_predictions, axis=0)\n    return final_predictions\n\nprint(\"Test-Time Augmentation (TTA) function ready\")\nprint(\"TTA reduces prediction variance by averaging over multiple augmented views\")\nprint(\"Expected improvement: 2-5% better accuracy\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T04:29:31.661382Z","iopub.execute_input":"2026-02-01T04:29:31.661994Z","iopub.status.idle":"2026-02-01T04:29:31.668048Z","shell.execute_reply.started":"2026-02-01T04:29:31.661968Z","shell.execute_reply":"2026-02-01T04:29:31.667206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def ensemble_predict(model, dataset, use_tta=True):\n    print(\"Generating ensemble predictions...\")\n    \n    base_preds = predict_multi_input(model, dataset).squeeze()\n    \n    if use_tta:\n        print(\"Applying Test-Time Augmentation...\")\n        tta_preds = predict_with_tta(model, dataset, num_augmentations=3).squeeze()\n        final_preds = 0.6 * base_preds + 0.4 * tta_preds\n        print(\"Combined: 60% base + 40% TTA\")\n    else:\n        final_preds = base_preds\n    \n    return final_preds\n\nprint(\"Ensemble prediction function ready\")\nprint(\"Combines multiple strategies for robust predictions\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T04:29:36.117526Z","iopub.execute_input":"2026-02-01T04:29:36.118131Z","iopub.status.idle":"2026-02-01T04:29:36.123629Z","shell.execute_reply.started":"2026-02-01T04:29:36.118104Z","shell.execute_reply":"2026-02-01T04:29:36.122691Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preds_enhanced = ensemble_predict(model_multi, test_ds_meta, use_tta=True)\n\nwith open(\"target_scalers.pkl\", \"rb\") as f:\n    scalers = pickle.load(f)\n\npreds_enhanced_original = np.zeros_like(preds_enhanced)\nfor i, (_, row) in enumerate(df_test_expanded.iterrows()):\n    preds_enhanced_original[i] = scalers[row[\"target_name\"]].inverse_transform([[preds_enhanced[i]]])[0, 0]\n\nsubmission_enhanced = pd.DataFrame({\n    \"sample_id\": df_test_expanded[\"sample_id\"],\n    \"target\": preds_enhanced_original.astype(\"float32\"),\n})\n\ntarget_counts = {t: submission_enhanced['sample_id'].str.contains(t).sum() \n                 for t in ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']}\n\n\nsubmission_enhanced.to_csv(\"submission.csv\", index=False)\nprint(f\"\\n✓ Saved: submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T04:29:53.105738Z","iopub.execute_input":"2026-02-01T04:29:53.106498Z","iopub.status.idle":"2026-02-01T04:29:53.902707Z","shell.execute_reply.started":"2026-02-01T04:29:53.106468Z","shell.execute_reply":"2026-02-01T04:29:53.901949Z"}},"outputs":[],"execution_count":null}]}