{"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":"gpu","dataSources":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# ViT","metadata":{}},{"cell_type":"markdown","source":"# ConvNeXt\n# ConvNeXt-Tiny Optimization Strategy: Phase \n\n* without metadata(NDVI, Height etc)\n* composite loss mae:rmse = 65:35\n* moduler architecture\n","metadata":{}},{"cell_type":"markdown","source":"## Configuration\n* unfreeze layer - 15\n* LR - 1e-4\n* WD - 1e-2\n* Loss - 0.65","metadata":{}},{"cell_type":"markdown","source":"## Main","metadata":{}},{"cell_type":"markdown","source":"### Libraries","metadata":{}},{"cell_type":"code","source":"!pip install tensorflow==2.15.0 tensorflow-addons vit-keras","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:23:41.376351Z","iopub.execute_input":"2026-03-03T16:23:41.376578Z","iopub.status.idle":"2026-03-03T16:23:48.058329Z","shell.execute_reply.started":"2026-03-03T16:23:41.376554Z","shell.execute_reply":"2026-03-03T16:23:48.057463Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### imports","metadata":{}},{"cell_type":"code","source":"import os\nimport time\nimport math\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nimport cv2\nimport random\nfrom tensorflow.keras import layers, models\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import StandardScaler, OneHotEncoder\nfrom sklearn.metrics import r2_score\nfrom sklearn.model_selection import KFold\nfrom IPython.display import FileLink, display","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:23:48.059960Z","iopub.execute_input":"2026-03-03T16:23:48.060256Z","iopub.status.idle":"2026-03-03T16:24:09.517157Z","shell.execute_reply.started":"2026-03-03T16:23:48.060226Z","shell.execute_reply":"2026-03-03T16:24:09.516360Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# static seed\nSEED = 42\ntf.random.set_seed(SEED)\nnp.random.seed(SEED)\nos.environ['PYTHONHASHSEED'] = str(SEED)\n\ndef set_deterministic_environment(seed):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    os.environ['TF_CUDNN_DETERMINISTIC'] = '1'\n    random.seed(seed)\n    np.random.seed(seed)\n    tf.keras.utils.set_random_seed(seed)\n    tf.config.experimental.enable_op_determinism()\n    print(f\"✅ Environment locked to Seed: {seed}\")\n\nset_deterministic_environment(SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:09.518083Z","iopub.execute_input":"2026-03-03T16:24:09.518552Z","iopub.status.idle":"2026-03-03T16:24:09.524087Z","shell.execute_reply.started":"2026-03-03T16:24:09.518524Z","shell.execute_reply":"2026-03-03T16:24:09.523395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"PATH = '/kaggle/input/csiro-biomass'\nIMG_DIR = PATH \nIMG_SIZE = 224\nBATCH_SIZE = 16\nEPOCHS = 500\nK = 5\nUNFREEZE_LAYERS = 15\nLEARNING_RATE = 1e-4\nWEIGHT_DECAY = 1e-2\nDROP_OUT = 0.3\nALPHA = 0.65\nLR_SCHEDULER_MONITOR = \"val_loss\"\nLR_SCHEDULER_PATIENCE = 10\nLR_SCHEDULER_FACTOR = 0.1\nEARLY_STOP_MONITOR = \"val_loss\"\nEARLY_STOP_PATIENCE = 15\n\nFT_LEARNING_RATE = 1e-5\nFT_WEIGHT_DECAY = 1e-4\nFT_EPOCHS = 100\nFT_ALPHA = 0.65\nFT_LR_SCHEDULER_MONITOR = \"val_loss\"\nFT_LR_SCHEDULER_PATIENCE = 10\nFT_LR_SCHEDULER_FACTOR = 0.6\nFT_EARLY_STOP_MONITOR = \"val_loss\"\nFT_EARLY_STOP_PATIENCE = 5\n\nTRAIN_TARGETS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g']\nFINAL_TARGETS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:09.524882Z","iopub.execute_input":"2026-03-03T16:24:09.525117Z","iopub.status.idle":"2026-03-03T16:24:09.551632Z","shell.execute_reply.started":"2026-03-03T16:24:09.525101Z","shell.execute_reply":"2026-03-03T16:24:09.551075Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### composite loss function","metadata":{}},{"cell_type":"code","source":"def stabilized_mae_mse_loss(alpha=0.65, target_weights=None, epsilon=1e-6):\n    \"\"\"\n    Composite loss:\n    alpha * MAE + (1 - alpha) * RMSE\n    Optional per-target weighting\n    \"\"\"\n    if target_weights is not None:\n        # Ensure weights are a float32 tensor for multiplication\n        target_weights = tf.constant(target_weights, dtype=tf.float32)\n\n    def loss(y_true, y_pred):\n        error = y_true - y_pred\n        \n        # Calculate raw MAE and RMSE per element\n        mae = tf.abs(error)\n        rmse = tf.sqrt(tf.square(error) + epsilon)\n\n        # Apply weights: error (batch, 3) * weights (3,)\n        if target_weights is not None:\n            mae = mae * target_weights\n            rmse = rmse * target_weights\n\n        # Average first across targets (axis=-1), then across batch\n        return alpha * tf.reduce_mean(mae) + (1 - alpha) * tf.reduce_mean(rmse)\n\n    return loss","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:09.552377Z","iopub.execute_input":"2026-03-03T16:24:09.552617Z","iopub.status.idle":"2026-03-03T16:24:09.565864Z","shell.execute_reply.started":"2026-03-03T16:24:09.552600Z","shell.execute_reply":"2026-03-03T16:24:09.565314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def log_transform_targets(df, target_cols):\n    df = df.copy()\n    df[target_cols] = np.log1p(df[target_cols])\n    return df\n\n\ndef inverse_log_transform_targets(arr):\n    \"\"\"\n    Use this ONLY for evaluation / reporting\n    \"\"\"\n    return np.expm1(arr)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:09.567801Z","iopub.execute_input":"2026-03-03T16:24:09.568003Z","iopub.status.idle":"2026-03-03T16:24:09.588885Z","shell.execute_reply.started":"2026-03-03T16:24:09.567988Z","shell.execute_reply":"2026-03-03T16:24:09.588070Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### data loader","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(os.path.join(PATH, 'train.csv'))\ndf_pivot = df.pivot_table(\n    index=['image_path'], \n    columns='target_name', values='target').reset_index().fillna(0)\n\ndf_final = log_transform_targets(df_pivot, TRAIN_TARGETS)\n\ntrainval_df, test_df = train_test_split(df_final, test_size=0.20, random_state=SEED)\ntrain_df, val_df = train_test_split(trainval_df, test_size=0.20, random_state=SEED)\n\n# train_order_file = 'training_order_fixed.csv'\n# train_df[['image_path']].to_csv(train_order_file, index=False)\n# display(FileLink(train_order_file))\n\n# ====================================================\n# DATA PIPELINE\n# ====================================================\ndef preprocess_fn(img_path, label):\n    file_path = tf.strings.join([IMG_DIR, '/', img_path])\n    img = tf.io.read_file(file_path)\n    img = tf.image.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    \n    return img, label\n\ndef create_ds(df, shuffle=True):\n    ds = tf.data.Dataset.from_tensor_slices((\n        df[\"image_path\"].values.astype(str),\n        df[TRAIN_TARGETS].values.astype(np.float32)\n    ))\n    \n    if shuffle: \n        ds = ds.shuffle(len(df), seed=SEED, reshuffle_each_iteration=True)\n    \n    return ds.map(preprocess_fn, num_parallel_calls=tf.data.AUTOTUNE).batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\ntrain_ds, val_ds, test_ds = create_ds(train_df), create_ds(val_df, False), create_ds(test_df, False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:09.589613Z","iopub.execute_input":"2026-03-03T16:24:09.589872Z","iopub.status.idle":"2026-03-03T16:24:12.650947Z","shell.execute_reply.started":"2026-03-03T16:24:09.589854Z","shell.execute_reply":"2026-03-03T16:24:12.650340Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### main tracker","metadata":{}},{"cell_type":"code","source":"def mae_per_target(y_true, y_pred):\n    return np.mean(np.abs(y_true - y_pred), axis=0).tolist()\n\ndef mse_per_target(y_true, y_pred):\n    return np.mean((y_true - y_pred)**2, axis=0).tolist()\n\ndef rmse_per_target(y_true, y_pred):\n    return np.sqrt(np.mean((y_true - y_pred)**2, axis=0)).tolist()\n\ndef r2_per_target(y_true, y_pred):\n    return r2_score(y_true, y_pred, multioutput='raw_values').tolist()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:12.651903Z","iopub.execute_input":"2026-03-03T16:24:12.652240Z","iopub.status.idle":"2026-03-03T16:24:12.657715Z","shell.execute_reply.started":"2026-03-03T16:24:12.652209Z","shell.execute_reply":"2026-03-03T16:24:12.656975Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### score calculator","metadata":{}},{"cell_type":"code","source":"def final_score_calculator(y_true, y_pred, ft = \"\"):\n    \"\"\"\n    Computes the weighted R2 over all (image, target) pairs combined.\n    Expects y_true and y_pred to be shape (N, 5) in order:\n    [Green, Dead, Clover, GDM, Total]\n    \"\"\"\n    # 1. Define weights per target type\n    # Weights: Green (0.1), Dead (0.1), Clover (0.1), GDM (0.2), Total (0.5)\n    weights_per_target = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n    \n    # 2. Flatten the arrays to treat all (image, target) pairs together\n    y_true_flat = y_true.flatten()\n    y_pred_flat = y_pred.flatten()\n    \n    # 3. Create a matching weights vector\n    # Repeat the weights for every row (image) in the dataset\n    w_flat = np.tile(weights_per_target, len(y_true))\n    \n    # 4. Calculate weighted mean of true values (y_bar_w)\n    # Formula: sum(w_j * y_j) / sum(w_j)\n    y_bar_w = np.sum(w_flat * y_true_flat) / np.sum(w_flat)\n    \n    # 5. Residual Sum of Squares (SS_res)\n    # Formula: sum(w_j * (y_j - y_hat_j)^2)\n    ss_res = np.sum(w_flat * (y_true_flat - y_pred_flat)**2)\n    \n    # 6. Total Sum of Squares (SS_tot)\n    # Formula: sum(w_j * (y_j - y_bar_w)^2)\n    ss_tot = np.sum(w_flat * (y_true_flat - y_bar_w)**2)\n    \n    # 7. Final R2 calculation\n    # Formula: 1 - (SS_res / SS_tot)\n    r2_weighted = 1 - (ss_res / ss_tot)\n\n    print(\"#\" * 20)\n    print(f'# R2 Result {ft} : {r2_weighted}')\n    print(\"#\" * 20)\n    \n    return r2_weighted","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:12.658378Z","iopub.execute_input":"2026-03-03T16:24:12.658557Z","iopub.status.idle":"2026-03-03T16:24:12.673426Z","shell.execute_reply.started":"2026-03-03T16:24:12.658541Z","shell.execute_reply":"2026-03-03T16:24:12.672726Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### visualizer","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 7. METRIC CURVE DASHBOARD\n# ====================================================\ndef visualizer(model, history, dataset, df, name=\"\"):\n    # 1. Metric Plotting\n    plt.figure(figsize=(18, 5))\n    # Filter only metrics that exist in history (Keras sometimes changes names)\n    metrics_to_plot = [m for m in ['loss', 'mae', 'mse', 'rmse'] if m in history.history]\n    \n    for i, metric in enumerate(metrics_to_plot):\n        plt.subplot(1, len(metrics_to_plot), i+1)\n        plt.plot(history.history[metric], label=f'Train {metric}')\n        plt.plot(history.history[f'val_{metric}'], label=f'Val {metric}')\n        plt.title(metric.upper())\n        plt.xlabel('Epoch'); plt.legend(); plt.grid(True)\n    plt.tight_layout(); plt.show()\n    \n    # 2. Predictions\n    y_pred_log = model.predict(dataset)\n    y_true_log = np.concatenate([y for x, y in dataset], axis=0)\n\n    \n    # Inverse log transform and clip negatives\n    y_pred_raw = np.maximum(np.expm1(y_pred_log), 0.0)\n    y_true_raw = np.expm1(y_true_log)\n    \n    # 3. Reconstruction (5 columns)\n    def reconstruct_full(data):\n        g, d, c = data[:, 0], data[:, 1], data[:, 2]\n        # Columns: Green, Dead, Clover, GDM (G+D), Total (G+D+C)\n        return np.column_stack([g, d, c, (g + d), (g + d + c)])\n    \n    y_pred = reconstruct_full(y_pred_raw)\n    y_true = reconstruct_full(y_true_raw)\n\n    # files name\n    target_metrics = f\"target_metrics_{name}.csv\"\n    detailed_error = f\"detailed_errors_{name}.csv\"\n    \n    # FIX: Define the 5 names to match the 5 columns above\n    REPORT_NAMES = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    \n    # 4. Error Export\n    error_export = pd.DataFrame({'image_path': df['image_path'].values})\n    for i, target in enumerate(REPORT_NAMES):\n        error_export[f'true_{target}'] = y_true[:, i]\n        error_export[f'pred_{target}'] = y_pred[:, i]\n        error_export[f'error_{target}'] = np.abs(y_true[:, i] - y_pred[:, i])\n    error_export.to_csv(detailed_error, index=False)\n    \n    # 5. Metrics DataFrame (Now all arrays have length 5)\n    metrics_df = pd.DataFrame({\n        'Target': REPORT_NAMES, \n        'MAE': mae_per_target(y_true, y_pred), \n        'MSE': mse_per_target(y_true, y_pred),\n        'RMSE': rmse_per_target(y_true, y_pred),\n        'R2': r2_per_target(y_true, y_pred)\n    })\n    metrics_df.to_csv(target_metrics, index=False)\n    \n    print(\"\\n--- FINAL TEST SUMMARY ---\")\n    print(metrics_df.to_string(index=False))\n    display(FileLink(detailed_error))\n    display(FileLink(target_metrics))\n\n    return final_score_calculator(y_true, y_pred, name)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:12.674208Z","iopub.execute_input":"2026-03-03T16:24:12.674963Z","iopub.status.idle":"2026-03-03T16:24:12.692320Z","shell.execute_reply.started":"2026-03-03T16:24:12.674945Z","shell.execute_reply":"2026-03-03T16:24:12.691574Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### shap","metadata":{}},{"cell_type":"markdown","source":"### lime","metadata":{}},{"cell_type":"markdown","source":"### main model","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 5. MODEL ARCHITECTURE\n# ====================================================\ndef build_model():\n    img_in = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3), name=\"image_input\")\n    \n    # 1. DATA AUGMENTATION PIPELINE\n    aug = layers.RandomFlip(\"horizontal_and_vertical\")(img_in) \n    aug = layers.RandomRotation(0.2, fill_mode='reflect')(aug)  \n    aug = layers.RandomTranslation(0.1, 0.1, fill_mode='reflect')(aug)\n    aug = layers.RandomZoom(0.2)(aug)\n    \n    # Color/Brightness\n    aug = layers.RandomBrightness(factor=0.2)(aug)   \n    aug = layers.RandomContrast(factor=0.2)(aug)     \n    \n    # Custom Lambda Layers\n    aug = layers.Lambda(lambda x: tf.image.random_hue(x, 0.02))(aug)\n    aug = layers.Lambda(lambda x: tf.image.random_saturation(x, 0.7, 1.3))(aug)\n    \n    # Noise for Regularization\n    aug = layers.GaussianNoise(0.3)(aug) \n    \n    # 2. BASE MODEL\n    # Ensure the indentation here matches the start of the function!\n    base_model = tf.keras.applications.ConvNeXtTiny(\n        include_top=False, \n        weights='imagenet', \n        input_tensor=aug, \n        pooling='avg'\n    )\n    \n    # 3. FINE-TUNING STRATEGY\n    base_model.trainable = True\n    # Freeze all layers except the last N\n    for layer in base_model.layers[:-UNFREEZE_LAYERS]:\n        layer.trainable = False\n        \n    # 4. TOP HEAD\n    vis_out = base_model.output\n    x = layers.Dense(128, activation='silu')(vis_out)\n    x = layers.Dropout(DROP_OUT)(x) \n    \n    outputs = layers.Dense(\n        len(TRAIN_TARGETS), \n        activation=\"linear\",\n        name=\"biomass_output\"\n    )(x)\n    \n    return models.Model(inputs=[img_in], outputs=outputs)\n\n# Instantiate and Compile\nmodel = build_model()\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.AdamW(\n        learning_rate=LEARNING_RATE, \n        weight_decay=WEIGHT_DECAY\n    ),\n    loss=stabilized_mae_mse_loss(alpha=ALPHA),\n    metrics=[\n        tf.keras.metrics.MeanAbsoluteError(name=\"mae\"),\n        tf.keras.metrics.MeanSquaredError(name=\"mse\"),\n        tf.keras.metrics.RootMeanSquaredError(name=\"rmse\")\n    ]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:12.693162Z","iopub.execute_input":"2026-03-03T16:24:12.693428Z","iopub.status.idle":"2026-03-03T16:24:18.887404Z","shell.execute_reply.started":"2026-03-03T16:24:12.693405Z","shell.execute_reply":"2026-03-03T16:24:18.886591Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### K FOLD","metadata":{}},{"cell_type":"code","source":"# kf = KFold(n_splits=K, shuffle=True, random_state=SEED)\n\n# # To store results for all folds\n# all_fold_metrics = {\n#     'mae': [],\n#     'mse': [],\n#     'rmse': []\n# }\n\n# # 2. Loop through each fold\n# for fold, (train_idx, val_idx) in enumerate(kf.split(df_final)):\n#     print(f\"\\n--- TRAINING FOLD {fold + 1}/{K} ---\")\n    \n#     # Create fold-specific DataFrames\n#     train_df = df_final.iloc[train_idx]\n#     val_df = df_final.iloc[val_idx]\n    \n#     # Create tf.data.Datasets using your create_ds function\n#     train_ds = create_ds(train_df, shuffle=True)\n#     val_ds = create_ds(val_df, shuffle=False)\n    \n#     # Re-initialize a fresh model for every fold!\n#     model = build_model()\n#     model.compile(\n#         optimizer=tf.keras.optimizers.AdamW(\n#             learning_rate=LEARNING_RATE, \n#             weight_decay=WEIGHT_DECAY\n#         ),\n#         loss=stabilized_mae_mse_loss(\n#             alpha=ALPHA,\n#         ),\n#         metrics=[\n#             tf.keras.metrics.MeanAbsoluteError(name=\"mae\"),\n#             tf.keras.metrics.MeanSquaredError(name=\"mse\"),\n#             tf.keras.metrics.RootMeanSquaredError(name=\"rmse\")\n#         ]\n#     )\n\n#     callbacks = [\n#         tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True),\n#         tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.6, patience=10, verbose=1),\n#     ]\n    \n#     # Train\n#     history = model.fit(train_ds, validation_data=val_ds, epochs=EPOCHS, callbacks=callbacks)\n\n#     # visualizer(model, history)\n#     visualizer(\n#         model,\n#         history,\n#         val_ds,\n#         val_df,\n#         name=f\"fold_{fold+1}\"\n#     )\n    \n#     # Evaluate the fold\n#     results = model.evaluate(val_ds, return_dict=True)\n    \n#     # Store each metric (ensure the keys match your model.compile names)\n#     all_fold_metrics['mae'].append(results['mae'])\n#     all_fold_metrics['mse'].append(results['mse'])\n#     all_fold_metrics['rmse'].append(results['rmse'])\n    \n#     print(f\"Fold {fold+1} Results: MAE: {results['mae']:.4f}, MSE: {results['mse']:.4f}, RMSE: {results['rmse']:.4f}\")\n\n\n# # --- FINAL AGGREGATED SUMMARY ---\n# print(\"\\n\" + \"=\"*30)\n# print(\"FINAL CROSS-VALIDATION SUMMARY\")\n# print(\"=\"*30)\n# for metric, values in all_fold_metrics.items():\n#     mean_val = np.mean(values)\n#     std_val = np.std(values)\n#     print(f\"Average {metric.upper()}: {mean_val:.4f} (+/- {std_val:.4f})\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:18.888254Z","iopub.execute_input":"2026-03-03T16:24:18.888552Z","iopub.status.idle":"2026-03-03T16:24:18.894038Z","shell.execute_reply.started":"2026-03-03T16:24:18.888518Z","shell.execute_reply":"2026-03-03T16:24:18.893247Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### main model train","metadata":{}},{"cell_type":"code","source":"# ====================================================\n# 6. TRAINING\n# ====================================================\nprint(\"\\n--- Training Stability-Optimized ConvNext ---\")\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(monitor=EARLY_STOP_MONITOR, patience=EARLY_STOP_PATIENCE, restore_best_weights=True),\n    tf.keras.callbacks.ReduceLROnPlateau(monitor=LR_SCHEDULER_MONITOR, factor=LR_SCHEDULER_FACTOR, patience=LR_SCHEDULER_PATIENCE, verbose=1)\n]\n\nhistory = model.fit(train_ds, validation_data=val_ds, epochs=EPOCHS, callbacks=callbacks)\n\nvisualizer(model, history, test_ds, test_df, name=\"main\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:18.895022Z","iopub.execute_input":"2026-03-03T16:24:18.895397Z","iopub.status.idle":"2026-03-03T16:24:54.192256Z","shell.execute_reply.started":"2026-03-03T16:24:18.895375Z","shell.execute_reply":"2026-03-03T16:24:54.191706Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # ====================================================\n# # 1. TOTAL DETERMINISM SETUP\n# # ====================================================\n# def set_all_seeds(seed):\n#     os.environ['PYTHONHASHSEED'] = str(seed)\n#     os.environ['TF_CUDNN_DETERMINISTIC'] = '1'  # Operational for some TF versions\n#     random.seed(seed)\n#     np.random.seed(seed)\n#     tf.keras.utils.set_random_seed(seed)\n#     tf.config.experimental.enable_op_determinism() # The \"Magic\" switch\n\n# # ====================================================\n# # 2. UPDATED DATA PIPELINE\n# # ====================================================\n# def preprocess_fn(img_path, label):\n#     file_path = tf.strings.join([IMG_DIR, '/', img_path])\n#     img = tf.io.read_file(file_path)\n#     img = tf.image.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, label\n\n# def create_deterministic_ds(df, seed_val, is_training=True):\n#     ds = tf.data.Dataset.from_tensor_slices((\n#         df[\"image_path\"].values.astype(str),\n#         df[TRAIN_TARGETS].values.astype(np.float32)\n#     ))\n    \n#     if is_training:\n#         # Crucial: seed here matches the global loop seed\n#         ds = ds.shuffle(len(df), seed=seed_val, reshuffle_each_iteration=True)\n    \n#     # We keep AUTOTUNE, but enable_op_determinism() handles the ordering\n#     ds = ds.map(preprocess_fn, num_parallel_calls=tf.data.AUTOTUNE)\n#     return ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\n# # ====================================================\n# # 3. OPTIMIZATION LOOP\n# # ====================================================\n# seeds = [42, 123, 500, 789, 1024, 2048, 5555, 7777, 8888, 9999]\n# best_r2 = -float('inf')\n# best_seed = None\n\n# for attempt, seed in enumerate(seeds, 1):\n#     print(f\"\\n{'='*30}\\nATTEMPT {attempt}: Testing Seed {seed}\\n{'='*30}\")\n    \n#     # --- Step A: Reset everything to this specific seed ---\n#     set_all_seeds(seed)\n#     tf.keras.backend.clear_session()\n    \n#     # --- Step B: Prepare Data ---\n#     train_ds_fixed = create_deterministic_ds(train_df, seed, is_training=True)\n#     val_ds_fixed = create_deterministic_ds(val_df, seed, is_training=False)\n#     test_ds_fixed = create_deterministic_ds(test_df, seed, is_training=False)\n    \n#     # --- Step C: Build Model (Weights now initialized by seed) ---\n#     model = build_model() \n#     model.compile(\n#         optimizer=tf.keras.optimizers.AdamW(learning_rate=LEARNING_RATE, weight_decay=WEIGHT_DECAY),\n#         loss=stabilized_mae_mse_loss(alpha=ALPHA),\n#         metrics=[tf.keras.metrics.MeanAbsoluteError(name=\"mae\"), \"mse\"]\n#     )\n\n#     callbacks = [\n#         tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=15, restore_best_weights=True),\n#         tf.keras.callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.6, patience=10, verbose=0)\n#     ]\n\n#     # --- Step D: Train ---\n#     history = model.fit(\n#         train_ds_fixed, \n#         validation_data=val_ds_fixed, \n#         epochs=EPOCHS, \n#         callbacks=callbacks,\n#         verbose=1\n#     )\n    \n#     # --- Step E: Evaluate ---\n#     # (Assuming your visualizer saves metrics to a CSV)\n#     attempt_name = f\"seed_{seed}\"\n#     current_r2 = visualizer(model, history, test_ds_fixed, test_df, name=attempt_name)\n    \n#     # metrics_df = pd.read_csv(f\"target_metrics_{attempt_name}.csv\")\n#     # current_r2 = metrics_df.loc[metrics_df['Target'] == 'Dry_Total_g', 'R2'].values[0]\n    \n#     print(f\"Seed {seed} -> R2: {current_r2:.4f}\")\n    \n#     # --- Step F: Save Best ---\n#     if current_r2 > best_r2:\n#         best_r2 = current_r2\n#         best_seed = seed\n#         model.save('best_model.keras')\n#         print(f\"⭐ New Best Found! R2: {best_r2:.4f}\")\n\n#     # Cleanup memory\n#     del model\n#     import gc\n#     gc.collect()\n\n# print(f\"\\n✅ Optimization Complete. Best Seed: {best_seed} with R2: {best_r2:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:54.193021Z","iopub.execute_input":"2026-03-03T16:24:54.193327Z","iopub.status.idle":"2026-03-03T16:24:54.198058Z","shell.execute_reply.started":"2026-03-03T16:24:54.193310Z","shell.execute_reply":"2026-03-03T16:24:54.197397Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### finetune model","metadata":{}},{"cell_type":"code","source":"# # ===============================\n# # 6. FINE-TUNE LAST BLOCK - FIXED\n# # ===============================\n\n# # 1. Start by setting the \"fine-tune\" toggle\n# # We want to unfreeze everything from stage_3 onwards\n# fine_tune_at_layer = \"stage_3_block_0\" \n# keep_frozen = True\n\n# print(f\"Searching for {fine_tune_at_layer} to begin unfreezing...\")\n\n# for layer in model.layers:\n#     # If we hit the target layer, stop freezing everything from here onwards\n#     if fine_tune_at_layer in layer.name:\n#         keep_frozen = False\n    \n#     if keep_frozen:\n#         layer.trainable = False\n#     else:\n#         # This will unfreeze the stage 3 blocks AND your custom Dense/Output layers\n#         layer.trainable = True\n\n# # 2. Verify\n# trainable_layers = [l.name for l in model.layers if l.trainable]\n# print(f\"\\nTotal trainable layers: {len(trainable_layers)}\")\n# print(\"Last 5 trainable layers:\", trainable_layers[-5:])\n\n# # 3. Recompile (Important: always recompile after changing 'trainable' flags)\n# model.compile(\n#     optimizer=tf.keras.optimizers.AdamW(\n#         learning_rate=5e-5, # Low LR for fine-tuning\n#         weight_decay=WEIGHT_DECAY\n#     ),\n#     loss=stabilized_mae_mse_loss(alpha=ALPHA),\n#     metrics=[\n#         tf.keras.metrics.MeanAbsoluteError(name=\"mae\"),\n#         tf.keras.metrics.MeanSquaredError(name=\"mse\"),\n#         tf.keras.metrics.RootMeanSquaredError(name=\"rmse\")\n#     ]\n# )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:54.198782Z","iopub.execute_input":"2026-03-03T16:24:54.199161Z","iopub.status.idle":"2026-03-03T16:24:54.442897Z","shell.execute_reply.started":"2026-03-03T16:24:54.199135Z","shell.execute_reply":"2026-03-03T16:24:54.442098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ====================================================\n# 6. PRECISION FINE-TUNE (STAGE 3 ONLY)\n# ====================================================\n\n# 1. First, freeze EVERY layer in the entire model\nmodel.trainable = True \nfor layer in model.layers:\n    layer.trainable = False\n\n# 2. Specifically unfreeze only Stage 3 and the Head\n# We use a simple name check on the full model list\nprint(\"--- Confirmed Unfrozen Layers ---\")\nfor layer in model.layers:\n    # Logic: Unfreeze if it's Stage 3 OR if it's our custom top layers\n    if \"stage_3\" in layer.name or any(x in layer.name for x in [\"dense\", \"biomass\", \"dropout\", \"pooling\"]):\n        layer.trainable = True\n        print(f\"✅ {layer.name}\")\n\n# 3. Recompile with the proper Learning Rate\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.AdamW(\n        learning_rate=FT_LEARNING_RATE, \n        weight_decay=FT_WEIGHT_DECAY\n    ),\n    loss=stabilized_mae_mse_loss(alpha=FT_ALPHA),\n    metrics=[\n        tf.keras.metrics.MeanAbsoluteError(name=\"mae\"),\n        tf.keras.metrics.MeanSquaredError(name=\"mse\"),\n        tf.keras.metrics.RootMeanSquaredError(name=\"rmse\")\n    ]\n)\n\nprint(\"\\nSuccess: Only Stage 3 and the Prediction Head are now trainable.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:28:51.139867Z","iopub.execute_input":"2026-03-03T16:28:51.140600Z","iopub.status.idle":"2026-03-03T16:28:51.153447Z","shell.execute_reply.started":"2026-03-03T16:28:51.140578Z","shell.execute_reply":"2026-03-03T16:28:51.152790Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### finetune model train","metadata":{}},{"cell_type":"code","source":"# 6.5 Fine-tune\nlr_scheduler = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor=FT_LR_SCHEDULER_MONITOR,\n    factor=FT_LR_SCHEDULER_FACTOR,\n    patience=FT_LR_SCHEDULER_PATIENCE,\n    verbose=1\n)\n\nearly_stop = tf.keras.callbacks.EarlyStopping(\n    monitor=FT_EARLY_STOP_MONITOR, \n    patience=FT_EARLY_STOP_PATIENCE, \n    restore_best_weights=True\n)\n\nhistoryft = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=FT_EPOCHS,\n    callbacks=[early_stop, lr_scheduler]\n)\n\nvisualizer(model, history, test_ds, test_df, name=\"ft\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-03T16:24:54.741811Z","iopub.status.idle":"2026-03-03T16:24:54.742043Z","shell.execute_reply.started":"2026-03-03T16:24:54.741926Z","shell.execute_reply":"2026-03-03T16:24:54.741937Z"}},"outputs":[],"execution_count":null}]}