{"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":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14156308,"sourceType":"datasetVersion","datasetId":8715355,"isSourceIdPinned":true}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CSIRO - Image2Biomass Prediction\n### Predict biomass using the provided pasture images\n\nCompetition: https://www.kaggle.com/competitions/csiro-biomass/overview \n\n**Notebook Links**\n- Data Preprocessing and Image Augmentation: https://www.kaggle.com/code/aliciarsimmons/as-csiro-image2biomass-data-preprocessing \n- Submission: https://www.kaggle.com/code/aliciarsimmons/as-csiro-image2biomass-submission-v02 ","metadata":{}},{"cell_type":"markdown","source":"# Import Packages","metadata":{}},{"cell_type":"code","source":"# General Packages\nimport pandas as pd\nimport numpy as np\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error, mean_absolute_error\nimport matplotlib.pyplot as plt\nimport joblib\n\n\n# TF/Keras\nimport tensorflow as tf\nfrom tensorflow.keras.applications import ResNet50, InceptionV3, VGG16, EfficientNetB0\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-14T19:05:15.878206Z","iopub.execute_input":"2025-12-14T19:05:15.878810Z","iopub.status.idle":"2025-12-14T19:05:15.883193Z","shell.execute_reply.started":"2025-12-14T19:05:15.878773Z","shell.execute_reply":"2025-12-14T19:05:15.882563Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Import Data","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/csiro-image2biomass-augmented-images-v01/train_aug.csv')\ntargets = train['target_name'].unique()\nprint(f'Targets: {targets}')\ntrain.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.672387Z","iopub.execute_input":"2025-12-14T18:24:35.672920Z","iopub.status.idle":"2025-12-14T18:24:35.728238Z","shell.execute_reply.started":"2025-12-14T18:24:35.672900Z","shell.execute_reply":"2025-12-14T18:24:35.727681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.isnull().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.728945Z","iopub.execute_input":"2025-12-14T18:24:35.729242Z","iopub.status.idle":"2025-12-14T18:24:35.737911Z","shell.execute_reply.started":"2025-12-14T18:24:35.729224Z","shell.execute_reply":"2025-12-14T18:24:35.737248Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Preprocess Data","metadata":{}},{"cell_type":"code","source":"train_df = train[['image_path', 'img_type', 'aug_func', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']].drop_duplicates()\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.739526Z","iopub.execute_input":"2025-12-14T18:24:35.739766Z","iopub.status.idle":"2025-12-14T18:24:35.766120Z","shell.execute_reply.started":"2025-12-14T18:24:35.739750Z","shell.execute_reply":"2025-12-14T18:24:35.765543Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for target in targets:\n    target_df = train[train['target_name']==target][['image_path','target']]\n    train_df = pd.merge(train_df,target_df, on='image_path', how='left')\n    train_df.rename(columns={'target':target}, inplace=True)\n\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.766764Z","iopub.execute_input":"2025-12-14T18:24:35.767048Z","iopub.status.idle":"2025-12-14T18:24:35.800316Z","shell.execute_reply.started":"2025-12-14T18:24:35.767024Z","shell.execute_reply":"2025-12-14T18:24:35.799733Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"There are perfect relationships between the 5 targets, such that they can all be calculated using only 3 component values. The relationships are as follows: \n\nDry_Total_g = Dry_Clover_g + Dry_Dead_g + Dry_Green_g\n\nGDM_g = Dry_Clover_g + Dry_Green_g \n\nDry_Total_g = GDM_g + Dry_Dead_g","metadata":{}},{"cell_type":"code","source":"# only predict component values\ntargets2 = ['Dry_Clover_g','Dry_Dead_g','Dry_Green_g']","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.800898Z","iopub.execute_input":"2025-12-14T18:24:35.801104Z","iopub.status.idle":"2025-12-14T18:24:35.804498Z","shell.execute_reply.started":"2025-12-14T18:24:35.801080Z","shell.execute_reply":"2025-12-14T18:24:35.803827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 80/20 split to data, stratify on type of augmentation\ntrain_data, valid_data = train_test_split(train_df, test_size=0.2, random_state=0, stratify = train_df['aug_func'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.805132Z","iopub.execute_input":"2025-12-14T18:24:35.805290Z","iopub.status.idle":"2025-12-14T18:24:35.819100Z","shell.execute_reply.started":"2025-12-14T18:24:35.805276Z","shell.execute_reply":"2025-12-14T18:24:35.818508Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_datagen = ImageDataGenerator(rescale=1./255)\nvalid_datagen = ImageDataGenerator(rescale=1./255)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.819880Z","iopub.execute_input":"2025-12-14T18:24:35.820146Z","iopub.status.idle":"2025-12-14T18:24:35.824056Z","shell.execute_reply.started":"2025-12-14T18:24:35.820128Z","shell.execute_reply":"2025-12-14T18:24:35.823465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 64\n\ntrain_loader1 = train_datagen.flow_from_dataframe(\n    dataframe = train_data,\n    directory = '/kaggle/input/csiro-image2biomass-augmented-images-v01',\n    x_col = 'image_path',\n    y_col = targets2,\n    batch_size = BATCH_SIZE,\n    seed = 1,\n    shuffle = True,\n    class_mode = 'raw',\n    target_size = (224,224)\n)\n\nvalid_loader1 = valid_datagen.flow_from_dataframe(\n    dataframe = valid_data,\n    directory = '/kaggle/input/csiro-image2biomass-augmented-images-v01',\n    x_col = 'image_path',\n    y_col = targets2,\n    batch_size = BATCH_SIZE,\n    seed = 1,\n    shuffle = True,\n    class_mode = 'raw',\n    target_size = (224,224)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:35.824654Z","iopub.execute_input":"2025-12-14T18:24:35.824877Z","iopub.status.idle":"2025-12-14T18:24:37.361665Z","shell.execute_reply.started":"2025-12-14T18:24:35.824856Z","shell.execute_reply":"2025-12-14T18:24:37.360968Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TR_STEPS1 = len(train_loader1)\nVA_STEPS1 = len(valid_loader1)\n\nprint(TR_STEPS1)\nprint(VA_STEPS1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:37.364456Z","iopub.execute_input":"2025-12-14T18:24:37.364658Z","iopub.status.idle":"2025-12-14T18:24:37.368729Z","shell.execute_reply.started":"2025-12-14T18:24:37.364643Z","shell.execute_reply":"2025-12-14T18:24:37.368021Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load Pretrained Model","metadata":{}},{"cell_type":"code","source":"# 1. Load and configure the pre-trained base model \nbase_model = EfficientNetB0(weights='imagenet', include_top=False, input_shape=(224, 224, 3))\n\n# Freeze the base model layers\nbase_model.trainable = False\n\n# Ensure all of the base model layers are frozen\nfor layer in base_model.layers:\n    # Ensure all layers in the base are marked non-trainable\n    layer.trainable = False \n    \n    # Specifically ensure BatchNormalization layers are in inference mode\n    if isinstance(layer, tf.keras.layers.BatchNormalization):\n        layer._per_input_v2 = True # Helps force inference mode compatibility\n        layer.trainable = False\n\n# 2. Add new layers for regression\nx = base_model.output\n# Add a global average pooling layer to flatten the feature maps\nx = GlobalAveragePooling2D()(x)\n# Add a dense layer with 3 output neurons for the component targets (Dry_Clover_g,Dry_Dead_g,Dry_Green_g)\npredictions = Dense(3, activation='linear')(x)\n\n# 3. Create the new model and compile it\nmodel = Model(inputs=base_model.input, outputs=predictions)\n\n# Define the loss function and optimizer for regression\nopt = tf.keras.optimizers.Adam(0.001)\nmodel.compile(optimizer=opt,\n              loss='mse',\n              metrics=['mae', 'root_mean_squared_error'])\n\n# View Model Structure\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:37.369472Z","iopub.execute_input":"2025-12-14T18:24:37.369797Z","iopub.status.idle":"2025-12-14T18:24:41.224703Z","shell.execute_reply.started":"2025-12-14T18:24:37.369763Z","shell.execute_reply":"2025-12-14T18:24:41.224176Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Train the Added Layers","metadata":{}},{"cell_type":"code","source":"%%time\n\nearly_stopping1 = tf.keras.callbacks.EarlyStopping(\n    monitor='val_loss',  \n    patience=3,         \n    verbose=1,\n    restore_best_weights=True\n)\n\nh1 = model.fit(\n    x = train_loader1, \n    steps_per_epoch = TR_STEPS1, \n    epochs = 30,\n    validation_data = valid_loader1, \n    validation_steps = VA_STEPS1, \n    verbose = 1,\n    callbacks = [early_stopping1]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:24:41.225407Z","iopub.execute_input":"2025-12-14T18:24:41.225656Z","iopub.status.idle":"2025-12-14T18:44:33.569437Z","shell.execute_reply.started":"2025-12-14T18:24:41.225633Z","shell.execute_reply":"2025-12-14T18:44:33.568821Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history1 = h1.history\n\nepoch_range = range(1, len(history1['loss'])+1)\n\nplt.figure(figsize=[14,14])\nplt.subplot(3,1,1)\nplt.plot(epoch_range, history1['loss'], label='Training')\nplt.plot(epoch_range, history1['val_loss'], label='Validation')\nplt.xlabel('Epoch'); plt.ylabel('MSE'); plt.title('Mean Squared Error')\nplt.legend()\nplt.subplot(3,1,2)\nplt.plot(epoch_range, history1['root_mean_squared_error'], label='Training')\nplt.plot(epoch_range, history1['val_root_mean_squared_error'], label='Validation')\nplt.xlabel('Epoch'); plt.ylabel('RMSE'); plt.title('Root Mean Squared Error')\nplt.legend()\nplt.tight_layout()\nplt.subplot(3,1,3)\nplt.plot(epoch_range, history1['mae'], label='Training')\nplt.plot(epoch_range, history1['val_mae'], label='Validation')\nplt.xlabel('Epoch'); plt.ylabel('MAE'); plt.title('Mean Absolute Error')\nplt.legend()\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:33.570394Z","iopub.execute_input":"2025-12-14T18:44:33.570578Z","iopub.status.idle":"2025-12-14T18:44:34.490138Z","shell.execute_reply.started":"2025-12-14T18:44:33.570563Z","shell.execute_reply":"2025-12-14T18:44:34.489462Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train Base Model Layers","metadata":{}},{"cell_type":"code","source":"# Determine where each block of the base model starts\n\n# Define a function to get the starting indices of the model blocks\ndef get_efficientnetb0_block_indices(model):\n    \"\"\"\n    Analyzes a Keras EfficientNetB0 model and returns the starting layer index\n    for each major convolutional block/stage (block1 through block7).\n    \n    Note: 'block8' is typically the final Conv2D layer before the GlobalAveragePooling.\n    We identify the start of the MBConv sequence (block1-block7).\n    \"\"\"\n    block_indices = {}\n    \n    # EfficientNetB0 uses 8 main stages (excluding the initial Stem Conv)\n    # The layers are typically named 'blockX_Y_conv' or similar, where X is 1-7.\n    # The final Conv is named 'top_conv'.\n    \n    # We look for the start of the MBConv blocks\n    block_names = [f'block{i}' for i in range(1, 8)]\n    \n    for i, layer in enumerate(model.layers):\n        layer_name = layer.name\n        \n        for block_key in block_names:\n            # Check if the block name is in the layer name AND\n            # if we have NOT already recorded its starting index.\n            if block_key in layer_name and block_key not in block_indices:\n                # This is the first layer of the new block\n                block_indices[block_key] = i\n                break # Move to the next layer in the model\n                \n    # Add the final convolutional layer (often named 'top_conv')\n    # This is effectively the final block of feature extraction before pooling/head\n    for i, layer in enumerate(model.layers):\n        if 'top_conv' in layer.name:\n            block_indices['top_conv'] = i\n            break\n                \n    return block_indices\n\n# Use the UDF on the base model\nindices = get_efficientnetb0_block_indices(base_model)\n\nprint(\"EfficientNetB0 Convolutional Block Start Indices\")\nprint(\"-\" * 40)\nprint(f\"Total layers in model: {len(base_model.layers)}\")\nprint(\"-\" * 40)\n\n# Print the results in a readable format\nfor block, index in indices.items():\n    print(f\"| {block.upper()} Start Index: {index}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:34.490890Z","iopub.execute_input":"2025-12-14T18:44:34.491145Z","iopub.status.idle":"2025-12-14T18:44:34.498623Z","shell.execute_reply.started":"2025-12-14T18:44:34.491127Z","shell.execute_reply":"2025-12-14T18:44:34.497834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# restore weights from first section of training if necessary\nmodel=h1.model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:34.499479Z","iopub.execute_input":"2025-12-14T18:44:34.499749Z","iopub.status.idle":"2025-12-14T18:44:34.512632Z","shell.execute_reply.started":"2025-12-14T18:44:34.499733Z","shell.execute_reply":"2025-12-14T18:44:34.511848Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Unfreeze the base model completely first\nbase_model.trainable = True\n\n# Start Training at Block 6\nFINE_TUNE_START_INDEX = 163\n\n# Freeze the early layers of the base model\nfor layer in base_model.layers[:FINE_TUNE_START_INDEX]:\n    layer.trainable = False\n\nprint(f\"Base model unfreezing completed. {len(base_model.layers) - FINE_TUNE_START_INDEX} layers are now trainable.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:34.513398Z","iopub.execute_input":"2025-12-14T18:44:34.513631Z","iopub.status.idle":"2025-12-14T18:44:34.529996Z","shell.execute_reply.started":"2025-12-14T18:44:34.513614Z","shell.execute_reply":"2025-12-14T18:44:34.529224Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create new data objects with a smaller batch size\n\nBATCH_SIZE = 8\n\ntrain_loader2 = train_datagen.flow_from_dataframe(\n    dataframe = train_data,\n    directory = '/kaggle/input/csiro-image2biomass-augmented-images-v01',\n    x_col = 'image_path',\n    y_col = targets2,\n    batch_size = BATCH_SIZE,\n    seed = 1,\n    shuffle = True,\n    class_mode = 'raw',\n    target_size = (224,224)\n)\n\nvalid_loader2 = valid_datagen.flow_from_dataframe(\n    dataframe = valid_data,\n    directory = '/kaggle/input/csiro-image2biomass-augmented-images-v01',\n    x_col = 'image_path',\n    y_col = targets2,\n    batch_size = BATCH_SIZE,\n    seed = 1,\n    shuffle = True,\n    class_mode = 'raw',\n    target_size = (224,224)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:34.530751Z","iopub.execute_input":"2025-12-14T18:44:34.531053Z","iopub.status.idle":"2025-12-14T18:44:34.562472Z","shell.execute_reply.started":"2025-12-14T18:44:34.531027Z","shell.execute_reply":"2025-12-14T18:44:34.561932Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TR_STEPS2 = len(train_loader2)\nVA_STEPS2 = len(valid_loader2)\n\nprint(TR_STEPS2)\nprint(VA_STEPS2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:34.563257Z","iopub.execute_input":"2025-12-14T18:44:34.563546Z","iopub.status.idle":"2025-12-14T18:44:34.567528Z","shell.execute_reply.started":"2025-12-14T18:44:34.563521Z","shell.execute_reply":"2025-12-14T18:44:34.566913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a new, tiny optimizer instance\nopt_fine_tune = tf.keras.optimizers.Adam(1e-5) # Use small learning rate\n\n# Recompile the final model object\nmodel.compile(\n    optimizer=opt_fine_tune,\n    loss='mse',\n    metrics=['mae', 'root_mean_squared_error']\n)\n\nprint(\"Model recompiled successfully with a tiny learning rate for fine-tuning.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:34.568332Z","iopub.execute_input":"2025-12-14T18:44:34.568658Z","iopub.status.idle":"2025-12-14T18:44:34.596320Z","shell.execute_reply.started":"2025-12-14T18:44:34.568636Z","shell.execute_reply":"2025-12-14T18:44:34.595772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"%%time\n\nearly_stopping2 = tf.keras.callbacks.EarlyStopping(\n    monitor='val_loss',  \n    patience=5,         \n    verbose=1,\n    restore_best_weights=True\n)\n\nh2 = model.fit(\n    x = train_loader2, \n    steps_per_epoch = TR_STEPS2, \n    epochs = 50,\n    validation_data = valid_loader2, \n    validation_steps = VA_STEPS2, \n    verbose = 1,\n    callbacks = [early_stopping2]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T18:44:34.597247Z","iopub.execute_input":"2025-12-14T18:44:34.597466Z","iopub.status.idle":"2025-12-14T19:04:15.453344Z","shell.execute_reply.started":"2025-12-14T18:44:34.597451Z","shell.execute_reply":"2025-12-14T19:04:15.452541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"history2 = h2.history\n\nepoch_range = range(1, len(history2['loss'])+1)\n\nplt.figure(figsize=[14,14])\nplt.subplot(3,1,1)\nplt.plot(epoch_range, history2['loss'], label='Training')\nplt.plot(epoch_range, history2['val_loss'], label='Validation')\nplt.xlabel('Epoch'); plt.ylabel('MSE'); plt.title('Mean Squared Error')\nplt.legend()\nplt.subplot(3,1,2)\nplt.plot(epoch_range, history2['root_mean_squared_error'], label='Training')\nplt.plot(epoch_range, history2['val_root_mean_squared_error'], label='Validation')\nplt.xlabel('Epoch'); plt.ylabel('RMSE'); plt.title('Root Mean Squared Error')\nplt.legend()\nplt.tight_layout()\nplt.subplot(3,1,3)\nplt.plot(epoch_range, history2['mae'], label='Training')\nplt.plot(epoch_range, history2['val_mae'], label='Validation')\nplt.xlabel('Epoch'); plt.ylabel('MAE'); plt.title('Mean Absolute Error')\nplt.legend()\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T19:04:15.454445Z","iopub.execute_input":"2025-12-14T19:04:15.455081Z","iopub.status.idle":"2025-12-14T19:04:16.068283Z","shell.execute_reply.started":"2025-12-14T19:04:15.455053Z","shell.execute_reply":"2025-12-14T19:04:16.067655Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluate Results","metadata":{}},{"cell_type":"markdown","source":"### Compare Predictions to True Values and Calculate Evaluation Metrics","metadata":{}},{"cell_type":"code","source":"true_values1 = valid_loader1.labels\n\nmodel1=h1.model\npred_values1 = model1.predict(valid_loader1, steps=VA_STEPS1)\n\nprint(f'Avg. True Value: {np.average(true_values1,axis=0)}')\nprint(f'Avg. Pred Value: {np.average(pred_values1,axis=0)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T19:04:28.464596Z","iopub.execute_input":"2025-12-14T19:04:28.464959Z","iopub.status.idle":"2025-12-14T19:04:48.157842Z","shell.execute_reply.started":"2025-12-14T19:04:28.464936Z","shell.execute_reply":"2025-12-14T19:04:48.157148Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"true_values2 = valid_loader2.labels\n\nmodel2=h2.model\npred_values2 = model2.predict(valid_loader2, steps=VA_STEPS2)\n\nprint(f'Avg. True Value: {np.average(true_values2,axis=0)}')\nprint(f'Avg. Pred Value: {np.average(pred_values2,axis=0)}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T19:04:48.159023Z","iopub.execute_input":"2025-12-14T19:04:48.159248Z","iopub.status.idle":"2025-12-14T19:05:01.774550Z","shell.execute_reply.started":"2025-12-14T19:04:48.159231Z","shell.execute_reply":"2025-12-14T19:05:01.773989Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculate RMSE for Both Models\n\nval_mse1 = mean_squared_error(true_values1, pred_values1)\nval_rmse1 = np.sqrt(val_mse1)\nprint(f'Model 1 RMSE = {val_rmse1:.2f}')\n\nval_mse2 = mean_squared_error(true_values2, pred_values2)\nval_rmse2 = np.sqrt(val_mse2)\nprint(f'Model 2 RMSE = {val_rmse2:.2f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T19:05:01.775313Z","iopub.execute_input":"2025-12-14T19:05:01.775580Z","iopub.status.idle":"2025-12-14T19:05:01.781745Z","shell.execute_reply.started":"2025-12-14T19:05:01.775557Z","shell.execute_reply":"2025-12-14T19:05:01.781104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Calculate MAE for Both Models\n\nval_mae1 = mean_absolute_error(true_values1, pred_values1)\nprint(f'Model 1 MAE = {val_mae1:.2f}')\n\nval_mae2 = mean_absolute_error(true_values2, pred_values2)\nprint(f'Model 2 MAE = {val_mae2:.2f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T19:05:24.961022Z","iopub.execute_input":"2025-12-14T19:05:24.961661Z","iopub.status.idle":"2025-12-14T19:05:24.967356Z","shell.execute_reply.started":"2025-12-14T19:05:24.961634Z","shell.execute_reply":"2025-12-14T19:05:24.966649Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot True vs. Predicted values for each target in both models\n\n# Model 1\nplt.figure(figsize=(10,5))\nplt.suptitle('Model 1')\nfor i in range(3):\n    t = targets2[i]\n    true = true_values1[:,i]\n    pred = pred_values1[:,i]\n\n    plt.subplot(2,2,i+1)\n    plt.title(t)\n    plt.scatter(true, pred)\nplt.tight_layout()\nplt.show()\n\n# Model 2\nplt.figure(figsize=(10,5))\nplt.suptitle('Model 2')\nfor i in range(3):\n    t = targets2[i]\n    true = true_values2[:,i]\n    pred = pred_values2[:,i]\n\n    plt.subplot(2,2,i+1)\n    plt.title(t)\n    plt.scatter(true, pred)\nplt.tight_layout()\nplt.show()\n    ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-14T19:12:57.017495Z","iopub.execute_input":"2025-12-14T19:12:57.017760Z","iopub.status.idle":"2025-12-14T19:12:57.864901Z","shell.execute_reply.started":"2025-12-14T19:12:57.017740Z","shell.execute_reply":"2025-12-14T19:12:57.864245Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Export the Best Model","metadata":{}},{"cell_type":"code","source":"# use RMSE to determine the better model\nbest_rmse = min(val_rmse1, val_rmse2)\n\n# export the better model\nif best_rmse == val_rmse1:\n    joblib.dump(model1, 'model_all.joblib')\nelse:\n    joblib.dump(model2, 'model_all.joblib')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T01:10:28.860810Z","iopub.execute_input":"2025-12-13T01:10:28.861173Z","iopub.status.idle":"2025-12-13T01:10:29.603032Z","shell.execute_reply.started":"2025-12-13T01:10:28.861147Z","shell.execute_reply":"2025-12-13T01:10:29.602367Z"}},"outputs":[],"execution_count":null}]}