{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895},{"sourceType":"modelInstanceVersion","sourceId":675996,"databundleVersionId":14832178,"modelInstanceId":512495,"modelId":527135},{"sourceType":"modelInstanceVersion","sourceId":674853,"databundleVersionId":14812680,"modelInstanceId":511543,"modelId":526212}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# --- Libraries\n\nimport os\nimport logging\n\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' \n\nimport random\nimport pandas as pd\nimport numpy as np\nimport tensorflow as tf\n\ntf.get_logger().setLevel(logging.ERROR)\n\nimport warnings\n\nwarnings.filterwarnings('ignore')\n\nimport gc\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.model_selection import KFold\nfrom tensorflow.keras import mixed_precision","metadata":{"_uuid":"567e08cb-1bd3-47a5-b662-0217f9c47665","_cell_guid":"f0b56776-6221-4087-817f-2b11c868c7f6","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2026-03-20T19:13:18.114186Z","iopub.execute_input":"2026-03-20T19:13:18.114724Z","iopub.status.idle":"2026-03-20T19:13:35.379852Z","shell.execute_reply.started":"2026-03-20T19:13:18.114700Z","shell.execute_reply":"2026-03-20T19:13:35.378994Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Configuration \n\nINPUT_ROOT = '/kaggle/input/csiro-biomass'\nTRAIN_CSV  = os.path.join(INPUT_ROOT, 'train.csv')\nTEST_CSV   = os.path.join(INPUT_ROOT, 'test.csv')\n\nIMG_SIZE   = 512\n\nBATCH_SIZE_PER_REPLICA = 4\n\n# 2 Gpus\nSTRATEGY = tf.distribute.MirroredStrategy()\nGLOBAL_BATCH_SIZE = BATCH_SIZE_PER_REPLICA * STRATEGY.num_replicas_in_sync\n\nprint(STRATEGY.num_replicas_in_sync)\nprint(GLOBAL_BATCH_SIZE)\n\n# Mixed precision\nmixed_precision.set_global_policy('mixed_float16')\nprint(mixed_precision.global_policy())","metadata":{"_uuid":"e2c33715-4ef0-4de4-a82f-edb9de2a4602","_cell_guid":"d874f7f4-6c7a-4b9e-9d86-90d661e0ba3a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2026-03-20T19:13:35.381133Z","iopub.execute_input":"2026-03-20T19:13:35.381960Z","iopub.status.idle":"2026-03-20T19:13:36.603288Z","shell.execute_reply.started":"2026-03-20T19:13:35.381939Z","shell.execute_reply":"2026-03-20T19:13:36.602335Z"},"scrolled":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Data prep\nraw_train_df = pd.read_csv(TRAIN_CSV)\n\n# Pivot long to wide\ntrain_df = raw_train_df.pivot(index='image_path', columns='target_name', values='target').reset_index()\n\n# Paths (add the complete position of each image)\ntrain_df['full_path'] = train_df['image_path'].apply(lambda x: os.path.join(INPUT_ROOT, x))","metadata":{"_uuid":"5760e64a-25f4-4613-9134-2fd38601bd15","_cell_guid":"e1482750-2b6e-4330-b263-c0cfaf128de1","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:36.604190Z","iopub.execute_input":"2026-03-20T19:13:36.604949Z","iopub.status.idle":"2026-03-20T19:13:36.659756Z","shell.execute_reply.started":"2026-03-20T19:13:36.604928Z","shell.execute_reply":"2026-03-20T19:13:36.658985Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"raw_train_df","metadata":{"_uuid":"53f12253-8f95-4de6-83ae-80bf243146a6","_cell_guid":"c09d8586-23be-45d4-812e-c2eb0984e806","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:36.661523Z","iopub.execute_input":"2026-03-20T19:13:36.661771Z","iopub.status.idle":"2026-03-20T19:13:36.682519Z","shell.execute_reply.started":"2026-03-20T19:13:36.661754Z","shell.execute_reply":"2026-03-20T19:13:36.681942Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df","metadata":{"_uuid":"b6315de6-4543-4b51-8c5d-26c289d365ca","_cell_guid":"e3c2548a-3210-40eb-b9c4-c2545fe0f799","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:36.683171Z","iopub.execute_input":"2026-03-20T19:13:36.683372Z","iopub.status.idle":"2026-03-20T19:13:36.694941Z","shell.execute_reply.started":"2026-03-20T19:13:36.683357Z","shell.execute_reply":"2026-03-20T19:13:36.694214Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Scale Targets\n\n# Scale Targets \ntarget_cols = ['Dry_Total_g', 'GDM_g', 'Dry_Green_g']   # Only these 3 bcs they should be the easist to predic (the others with math)\nMAX_VAL = train_df[target_cols].max().max()             # Max value for scaling\nTARGET_SCALER = MAX_VAL * 1.1\n\n# Arrays\nX_paths = train_df['full_path'].values                  \nY_targets = train_df[target_cols].values.astype('float32') / TARGET_SCALER   # Scaling","metadata":{"_uuid":"b2606cca-a488-4dbe-9a45-942d6108174a","_cell_guid":"b96bc6e1-d049-4132-a081-d7ca2e62883f","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:36.695893Z","iopub.execute_input":"2026-03-20T19:13:36.696216Z","iopub.status.idle":"2026-03-20T19:13:36.706446Z","shell.execute_reply.started":"2026-03-20T19:13:36.696197Z","shell.execute_reply":"2026-03-20T19:13:36.705639Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(X_paths[:5])\nprint(\"\\n\")\nprint(Y_targets[:5])","metadata":{"_uuid":"d2388002-9734-45cd-857f-366d661445fc","_cell_guid":"da55bcbc-c5c4-4f96-be4f-fec3a4a55e93","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:36.707381Z","iopub.execute_input":"2026-03-20T19:13:36.707653Z","iopub.status.idle":"2026-03-20T19:13:36.717938Z","shell.execute_reply.started":"2026-03-20T19:13:36.707631Z","shell.execute_reply":"2026-03-20T19:13:36.717256Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Load & pre-process\n\n@tf.function\ndef load_image(path, label):\n    # Load\n    img = tf.io.read_file(path)\n    img = tf.io.decode_jpeg(img, channels = 3)\n    # Resize\n    img = tf.image.resize(img,\n                          [IMG_SIZE, IMG_SIZE],\n                          preserve_aspect_ratio = False,\n                          antialias = True\n                         )\n    # Normalize\n    img = tf.cast(img, tf.float32) / 255.0\n    return img, label\n\n@tf.function\ndef augment_geometric(img, label):\n    # Random Flips\n    img = tf.image.random_flip_left_right(img)\n    img = tf.image.random_flip_up_down(img)\n\n    # Random Rotate\n    k = tf.random.uniform([], minval=0, maxval=4, dtype=tf.int32)\n    img = tf.image.rot90(img, k)\n\n    return img, label\n\n# MixUp\nMIXUP_LAYER = keras.layers.MixUp(alpha=0.178, dtype='float32')\n\n@tf.function\ndef apply_mixup(images, labels):\n    images = tf.cast(images, tf.float32)\n    labels = tf.cast(labels, tf.float32)\n    outputs = MIXUP_LAYER({\"images\": images, \"labels\": labels})\n    return outputs[\"images\"], outputs[\"labels\"]","metadata":{"_uuid":"2e3f43f6-0601-49cd-a234-f86daee60424","_cell_guid":"57e6f418-5c10-4f5a-8dce-bf23a5b6c540","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:36.718615Z","iopub.execute_input":"2026-03-20T19:13:36.719043Z","iopub.status.idle":"2026-03-20T19:13:36.740690Z","shell.execute_reply.started":"2026-03-20T19:13:36.718989Z","shell.execute_reply":"2026-03-20T19:13:36.739713Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model \n* Backbone: ConvNeXt-Base with Squeeze-and-Excitation attention\n* Pooling: Learnable GeM Pooling (initial p=3.0)\n* Head: Multi-branch regression structure with three dedicated paths for Total, GDM and Green biomass.\n* Regularization: Combined Gaussian Noise, Dropout (0.3) and Swish activations","metadata":{"_uuid":"7aa2d003-e570-4fa6-b68c-3761399f3f9b","_cell_guid":"9310ce5c-b14e-412d-a2db-c074ab64a120","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Generalized mean pooling (GeM)\n\n@tf.keras.utils.register_keras_serializable()\nclass GeMPooling2D(layers.Layer):\n    def __init__(self, p_init=3.0, **kwargs):\n        super().__init__(dtype='float32', **kwargs)\n        self.p_init = p_init\n\n    def build(self, input_shape):\n        self.p = self.add_weight(\n            name=\"p_param\",\n            shape=(1,),\n            initializer=keras.initializers.Constant(self.p_init),\n            trainable=True\n        )\n        super().build(input_shape)\n\n    def call(self, inputs):\n        # (1/N * sum(x^p))^(1/p)\n        x = tf.cast(inputs, tf.float32)\n        x = tf.maximum(inputs, 1e-6)\n        x = tf.pow(x, self.p)\n        x = tf.reduce_mean(x, axis=[1, 2], keepdims=False)\n        x = tf.pow(x, 1.0 / self.p)\n        return tf.cast(x, mixed_precision.global_policy().compute_dtype)\n\n    # save/load the p_init value\n    def get_config(self):\n        config = super().get_config()\n        config.update({\"p_init\": self.p_init})\n        return config","metadata":{"_uuid":"6bc75b48-34a1-4e7e-8c0a-31d065af0373","_cell_guid":"8c7a7830-cf6c-47c0-989a-1e5b5ecc7fa4","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2026-03-20T19:13:36.741606Z","iopub.execute_input":"2026-03-20T19:13:36.741847Z","iopub.status.idle":"2026-03-20T19:13:36.747559Z","shell.execute_reply.started":"2026-03-20T19:13:36.741824Z","shell.execute_reply":"2026-03-20T19:13:36.746785Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Attention block\n\ndef attention_block(inputs, ratio=4):\n    \"\"\"\n    Squeeze-and-Excitation Block\n    \"\"\"\n    # Number of features\n    channels = inputs.shape[-1]\n    # Squeeze: global Average Pooling to get a summary vector (Squeeze feature from (H, W, C) to (1, 1, C))\n    x = layers.GlobalAveragePooling2D(keepdims=True)(inputs)\n    # Excitation: bottleneck part, reduce param to learn (weights)\n    x = layers.Dense(channels // ratio, activation='swish')(x)\n    # Restoration: restore to the original dimension and weights can be put into the original feature map\n    x = layers.Dense(channels, activation='sigmoid')(x)\n    # Scale: multiply the original inputs by the calculated weights\n    return layers.Multiply()([inputs, x])","metadata":{"_uuid":"023c230a-f661-4806-8af0-2ec90870ca95","_cell_guid":"d9d9cbca-ba2a-4b8b-b88c-1792dba4a6a5","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2026-03-20T19:13:36.749671Z","iopub.execute_input":"2026-03-20T19:13:36.749840Z","iopub.status.idle":"2026-03-20T19:13:36.760623Z","shell.execute_reply.started":"2026-03-20T19:13:36.749827Z","shell.execute_reply":"2026-03-20T19:13:36.759967Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Model (+ Data Aug)\n\nweights_path = '/kaggle/input/convnext-base/tensorflow2/default/1/convnext_base_notop.h5'\n\ndef build_model():\n    inputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n\n    # Backbone\n    base = keras.applications.ConvNeXtBase(\n        include_top=False,\n        weights=None,\n        input_shape=(IMG_SIZE, IMG_SIZE, 3),\n        name = \"convnext_backbone\"\n    )\n\n    base.load_weights(weights_path)\n    base.trainable = False\n\n    x = base(inputs)\n    x = attention_block(x)\n\n    x = GeMPooling2D(p_init=3)(x)\n\n    # Gaussian Noise to regularize\n    x = layers.GaussianNoise(0.125)(x)\n\n    # Dense layer + Dropout\n    x = layers.Dense(384, activation='swish')(x)\n    x = layers.Dropout(0.3)(x)\n    \n    # Common dense layer \n    shared = layers.Dense(128, activation='swish')(x)\n\n    # # Multi-Head Outputs\n    # Head 1: Dry_Total_g\n    h1 = layers.Dense(64, activation='swish')(shared)\n    out1 = layers.Dense(1, activation='linear', name='total_head', dtype='float32')(h1)\n    # Head 2: GDM_g\n    h2 = layers.Dense(64, activation='swish')(shared)\n    out2 = layers.Dense(1, activation='linear', name='gdm_head', dtype='float32')(h2)\n    # Head 3: Dry_Green_g\n    h3 = layers.Dense(64, activation='swish')(shared)\n    out3 = layers.Dense(1, activation='linear', name='green_head', dtype='float32')(h3)\n\n    # Concatenate\n    outputs = layers.Concatenate(axis=-1)([out1, out2, out3])\n\n    return keras.Model(inputs, outputs)\n\nmodel = build_model()\n\nmodel.summary()","metadata":{"_uuid":"8a87a99f-1a58-4a17-b8c6-c2d9833fb16d","_cell_guid":"5559b081-695f-4242-9361-3e3ae00b3421","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:36.761245Z","iopub.execute_input":"2026-03-20T19:13:36.761477Z","iopub.status.idle":"2026-03-20T19:13:44.544335Z","shell.execute_reply.started":"2026-03-20T19:13:36.761456Z","shell.execute_reply":"2026-03-20T19:13:44.543737Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Test data\n\nraw_test_df = pd.read_csv(TEST_CSV)\nunique_test_df = raw_test_df.drop_duplicates(subset=['image_path'])\ntest_paths = [os.path.join(INPUT_ROOT, p) for p in unique_test_df['image_path'].values]\n\n@tf.function\ndef load_test_image(path):\n    img = tf.io.read_file(path)\n    img = tf.io.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, [IMG_SIZE, IMG_SIZE])\n    img = tf.cast(img, tf.float32) / 255.0 \n    return img\n\ntest_ds = tf.data.Dataset.from_tensor_slices(test_paths)\ntest_ds = test_ds.map(load_test_image, num_parallel_calls=tf.data.AUTOTUNE).batch(GLOBAL_BATCH_SIZE)\n\n\n# Test-Time Augmentation (TTA)\ndef predict_with_tta(model, dataset):\n    \"\"\"\n    Data aug on test to improve results by average the results\n    \"\"\"\n    p1 = model.predict(dataset, verbose=0)\n    p2 = model.predict(dataset.map(lambda x: tf.image.flip_left_right(x)), verbose=0)  \n    p3 = model.predict(dataset.map(lambda x: tf.image.flip_up_down(x)), verbose=0)\n    return (p1 + p2 + p3) / 3.0","metadata":{"_uuid":"57b5a247-626e-4ec9-a76f-eaa205a4b3f4","_cell_guid":"796935aa-5454-4506-b117-41d65979ddd4","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:44.545044Z","iopub.execute_input":"2026-03-20T19:13:44.545236Z","iopub.status.idle":"2026-03-20T19:13:44.817896Z","shell.execute_reply.started":"2026-03-20T19:13:44.545221Z","shell.execute_reply":"2026-03-20T19:13:44.816783Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- K-fold Training Loop\n\nN_FOLDS = 5\n\nkf = KFold(n_splits = N_FOLDS, shuffle = True, random_state = 42)\n\nfold_predictions = [] # Store predictions each fold\n\nfor fold, (train_idx, val_idx) in enumerate(kf.split(X_paths, Y_targets)):\n    print(f\"\\n{'='*15} FOLD {fold+1}/{N_FOLDS} {'='*15}\")\n    \n    # Split Data\n    X_train, Y_train = X_paths[train_idx], Y_targets[train_idx]\n    X_val, Y_val     = X_paths[val_idx],   Y_targets[val_idx]\n\n    # Train dataset\n    train_ds = tf.data.Dataset.from_tensor_slices((X_train, Y_train))\n    train_ds = train_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)         # Load (deterministic)\n    train_ds = train_ds.cache()                                                      # Store images in ram\n    train_ds = train_ds.shuffle(len(X_train), seed=42)                               # Shuffle\n    train_ds = train_ds.map(augment_geometric, num_parallel_calls=tf.data.AUTOTUNE)  # Geometric Aug \n    train_ds = train_ds.batch(GLOBAL_BATCH_SIZE)                                     # Batch\n    train_ds = train_ds.map(apply_mixup, num_parallel_calls=tf.data.AUTOTUNE)        # MixUp\n    train_ds = train_ds.prefetch(tf.data.AUTOTUNE)\n\n    # Val dataset\n    val_ds = tf.data.Dataset.from_tensor_slices((X_val, Y_val))\n    val_ds = val_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\n    val_ds = val_ds.cache()\n    val_ds = val_ds.batch(GLOBAL_BATCH_SIZE)\n    val_ds = val_ds.prefetch(tf.data.AUTOTUNE)\n\n    \n    keras.backend.clear_session()\n\n    # Train Head\n\n    print(f\"\\n--- Fold {fold+1}: Training Head ---\")\n    \n    with STRATEGY.scope():\n        \n        model = build_model()\n        \n        optimizer_1 = tf.keras.optimizers.AdamW(\n        learning_rate = 5e-4, \n        weight_decay = 1e-4\n        )\n       \n        model.compile(optimizer = optimizer_1,\n                      loss=keras.losses.Huber(delta=0.347),\n                      metrics = ['mae']\n                     )\n    \n        early_stop = keras.callbacks.EarlyStopping(monitor = 'val_loss',\n                                           patience = 7,\n                                           restore_best_weights = True\n                                          )\n        \n        reduce_plateau = keras.callbacks.ReduceLROnPlateau(monitor = \"val_loss\",\n                                                           factor = 0.1,\n                                                           patience = 2,\n                                                           verbose = 0                                                        \n        )\n        \n    \n    model.fit(train_ds,\n              validation_data = val_ds,\n              epochs = 20,\n              callbacks = [early_stop, reduce_plateau],\n              verbose = 1\n             )\n\n    # Fine Tune\n\n    print(f\"\\n--- Fold {fold+1}: Fine Tuning ---\")\n    \n    model.get_layer(\"convnext_backbone\").trainable = True\n    \n    FINE_TUNE_BATCH_SIZE = GLOBAL_BATCH_SIZE // 2\n\n    # Train\n    ft_train_ds = tf.data.Dataset.from_tensor_slices((X_train, Y_train))\n    ft_train_ds = ft_train_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE).cache()\n    ft_train_ds = ft_train_ds.shuffle(len(X_train), seed=42)\n    ft_train_ds = ft_train_ds.map(augment_geometric, num_parallel_calls=tf.data.AUTOTUNE)\n    ft_train_ds = ft_train_ds.batch(FINE_TUNE_BATCH_SIZE) \n    ft_train_ds = ft_train_ds.map(apply_mixup, num_parallel_calls=tf.data.AUTOTUNE).prefetch(tf.data.AUTOTUNE)\n\n    # Val\n    ft_val_ds = tf.data.Dataset.from_tensor_slices((X_val, Y_val))\n    ft_val_ds = ft_val_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE).cache()\n    ft_val_ds = ft_val_ds.batch(FINE_TUNE_BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n            \n    with STRATEGY.scope():\n\n        # Cosine schedule -> Optuna LR & Cosine Decay\n        lr_schedule_2 = keras.optimizers.schedules.CosineDecay(\n            initial_learning_rate = 1.63e-05,\n            decay_steps = 1519,\n            alpha = 0.01\n        )\n        \n        optimizer_2 = tf.keras.optimizers.AdamW(\n            learning_rate = lr_schedule_2, \n            weight_decay = 0.00188\n        )\n    \n        \n        model.compile(optimizer = optimizer_2,\n                      loss=keras.losses.Huber(delta=0.347),\n                      metrics = ['mae']\n                     )\n    \n    ckpt_path = f\"model_fold_{fold}.keras\"\n    ckpt = keras.callbacks.ModelCheckpoint(ckpt_path,              # Save Best Model Checkpoint\n                                           save_best_only = True,\n                                           monitor = 'val_loss',\n                                           verbose = 0\n                                          )  \n    \n    history = model.fit(ft_train_ds,\n                        validation_data = ft_val_ds,\n                        epochs = 30,\n                        callbacks = [ckpt, early_stop],\n                        verbose = 1\n                       )\n\n\n    # Memory clean up\n    del model\n    keras.backend.clear_session()\n    gc.collect()\n    \n    \n    # Predict on test data\n    print(f\"Predicting Fold {fold+1}\")\n    with STRATEGY.scope():\n        \n        best_model = keras.models.load_model(\n            ckpt_path,          # Load best weights\n            custom_objects={\"GeMPooling2D\": GeMPooling2D}\n        ) \n        \n    preds = predict_with_tta(best_model, test_ds)\n    fold_predictions.append(preds)\n    \n    # Cleanup\n    del best_model, train_ds, val_ds\n    import gc; gc.collect()","metadata":{"_uuid":"c99b6b52-205e-4cd7-a6a8-ffb3c80b527f","_cell_guid":"253a6ab0-7396-41a9-8637-ceadc8f8464d","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-03-20T19:13:44.818965Z","iopub.execute_input":"2026-03-20T19:13:44.819595Z","execution_failed":"2026-03-20T19:17:53.816Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Ensamble avereging\n\n# Average the predictions\navg_preds_scaled = np.mean(fold_predictions, axis=0)\n\n# Clip & Convert to Grams (denormalize)\npreds_3_grams = np.clip(avg_preds_scaled, 0, None) * TARGET_SCALER","metadata":{"_uuid":"c385e2dc-a496-434d-920c-d12043bc5ba5","_cell_guid":"efe7281b-9556-490d-bb05-6a9e6a917a8d","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"execution_failed":"2026-03-20T19:17:53.816Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Physics & Submission \nfinal_preds_5 = []        # there are 5 things to predict\n\nfor row in preds_3_grams:\n    total = row[0]        # Dry_Total_g  (total biomass)\n    gdm   = row[1]        # GDM_g        (green dry matter)\n    green = row[2]        # Dry_Green_g  (dry green biomass)\n    \n    # Physics Constraints\n\n    if gdm > total:       # gdm must be <= than total biomass\n        gdm = total       \n\n    if green > gdm:       # dry green must be <= than gdm \n        green = gdm\n        \n    dead = total - gdm              # dry dead = total biomass - green dry matter\n    clover = gdm - green            # dry clover = green dry matter - dry green biomass\n    \n    final_preds_5.append([green, dead, clover, gdm, total])\n\n# Map to Submission\npred_map = {path: preds for path, preds in zip(unique_test_df['image_path'], final_preds_5)}\ntarget_indices = {'Dry_Green_g': 0, 'Dry_Dead_g': 1, 'Dry_Clover_g': 2, 'GDM_g': 3, 'Dry_Total_g': 4}\n\nsubmission_rows = []\nfor _, row in raw_test_df.iterrows():\n    img_name = row['image_path']\n    t_name = row['target_name']\n    val = pred_map[img_name][target_indices[t_name]]\n    submission_rows.append({'sample_id': row['sample_id'], 'target': float(val)})\n\nsub_df = pd.DataFrame(submission_rows)\nsub_df.to_csv('submission.csv', index=False)\nprint(sub_df.head())","metadata":{"_uuid":"1d4bac97-8ee9-4854-b2d3-74d008211341","_cell_guid":"93c2fdf8-4a17-4720-8a9e-dad204abbdfe","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"execution_failed":"2026-03-20T19:17:53.816Z"}},"outputs":[],"execution_count":null}]}