{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895},{"sourceType":"modelInstanceVersion","sourceId":773800,"databundleVersionId":15927866,"modelInstanceId":115450,"modelId":138703}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# SOLUCIÓN 1 - VGG16 + RandomForestRegressor\n# Incluye: Preprocesamiento, Aumento de Datos,\n#          Extracción de Características, Regresión,\n#          Validación y Calibración\n# NOTA: Esta version de Kaggle solo posee VGG con un vector de 512 en vez de los 4096 entonces la version local tiene un mejor valor \n# ============================================================\n\n# Para manipulación de datos\nimport os\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\n\n# Para métricas de validación\nfrom sklearn.metrics import mean_squared_error, r2_score\nfrom sklearn.model_selection import train_test_split\n\n# Para el modelo de aprendizaje automático\nfrom sklearn.ensemble import RandomForestRegressor\n\n# Para visión por computador\nimport tensorflow as tf\nfrom tensorflow.keras.applications.vgg16 import VGG16, preprocess_input\nfrom tensorflow.keras.preprocessing import image\n\n# Para el aumento de datos\nimport albumentations as A\n\n\n# ===============================\n# RUTAS\n# ===============================\n\nBASE_PATH   = \"/kaggle/input/competitions/csiro-biomass\"\nMODEL_PATH  = \"/kaggle/input/models/keras/vgg/keras/vgg_16_imagenet/3\"\n\nTRAIN_CSV_PATH = os.path.join(BASE_PATH, \"train.csv\")\nTEST_CSV_PATH  = os.path.join(BASE_PATH, \"test.csv\")\n\nTRAIN_IMG_DIR  = BASE_PATH\nTEST_IMG_DIR   = BASE_PATH\n\n\n# ===============================\n# CARGA DE DATOS\n# ===============================\n\ntrain_df = pd.read_csv(TRAIN_CSV_PATH)\ntest_df  = pd.read_csv(TEST_CSV_PATH)\n\n# Convertir de formato largo a ancho para tener una fila por imagen\n# Cada columna corresponde a una variable objetivo (target_name, image_path, \n# Dry_Clover_g, Dry_Dead_g, Dry_Green_g, Dry_Total_g, GDM_g)\ntrain_wide_df = train_df.pivot(\n    index=\"image_path\",\n    columns=\"target_name\",\n    values=\"target\"\n).reset_index()\n\nprint(train_wide_df)\n\n\n# ===============================\n# CONFIGURACIÓN GENERAL\n# ===============================\n\nIMG_SIZE    = 224       # Tamaño de entrada esperado por VGG16\nRANDOM_SEED = 999       # Semilla para reproducibilidad\nVAL_SIZE    = 0.2       # 20 % de los datos se reservan para validación\n\n\n# ===============================\n# MÓDULO DE AUMENTO DE DATOS\n# ===============================\n# Se definen transformaciones geométricas y de color sencillas que se\n# aplican aleatoriamente a cada imagen SOLO durante el entrenamiento.\n# El objetivo es aumentar artificialmente la variabilidad del conjunto\n# de entrenamiento para que el modelo generalice mejor.\n\ndef get_train_transforms():\n    \"\"\"\n    Devuelve un pipeline de albumentations con transformaciones simples:\n      - HorizontalFlip : volteo horizontal con probabilidad 0.5\n      - VerticalFlip   : volteo vertical con probabilidad 0.5\n      - RandomRotate90 : rotación aleatoria de 90° con probabilidad 0.5\n      - RandomBrightnessContrast : variación leve de brillo y contraste\n    \"\"\"\n    return A.Compose([\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.RandomRotate90(p=0.5),\n        A.RandomBrightnessContrast(\n            brightness_limit=0.2,\n            contrast_limit=0.2,\n            p=0.5\n        ),\n        A.Resize(IMG_SIZE, IMG_SIZE),\n    ], seed=RANDOM_SEED)\n\n\n# Instanciar el pipeline de aumento una sola vez\ntrain_transforms = get_train_transforms()\n\n\n# ===============================\n# CARGA DEL MODELO VGG16\n# ===============================\n\nfull_model = tf.keras.models.load_model(MODEL_PATH)\n\n# Tomar la salida de la última capa convolucional\nbackbone_output = full_model.get_layer(\"block5_pool\").output\n\n# Aplanar de (None, 7, 7, 512) → (None, 512) con GlobalAveragePooling\npooled_output = tf.keras.layers.GlobalAveragePooling2D()(backbone_output)\n\n# Se toma la penúltima capa como salida para obtener el vector de características\n# (se elimina la última capa de clasificación que no necesitamos)\nbase_model = tf.keras.Model(\n    inputs=full_model.input,\n    outputs=pooled_output\n)\nbase_model.trainable = False   # Los pesos de VGG16 NO se actualizan\n\nprint(f\"Modelo cargado. Forma del vector de características: {base_model.output_shape}\")\n\n\n# ===============================\n# FUNCIÓN DE EXTRACCIÓN DE CARACTERÍSTICAS\n# ===============================\n\ndef extract_features(img_path, augment=False):\n    \"\"\"\n    Carga una imagen desde disco, aplica (opcionalmente) aumento de datos\n    y extrae el vector de características con VGG16.\n\n    Parámetros\n    ----------\n    img_path : str\n        Ruta completa a la imagen.\n    augment : bool\n        Si es True, aplica las transformaciones de aumento antes\n        de pasar la imagen por el modelo.\n\n    Retorna\n    -------\n    numpy.ndarray\n        Vector 1-D con las características extraídas.\n    \"\"\"\n    # --- Preprocesamiento base ---\n    img = image.load_img(img_path, target_size=(IMG_SIZE, IMG_SIZE))\n    img_array = image.img_to_array(img).astype(np.uint8)   # albumentations espera uint8\n\n    # --- Aumento de datos (solo en entrenamiento) ---\n    if augment:\n        augmented = train_transforms(image=img_array)\n        img_array = augmented[\"image\"]\n\n    # Añadir dimensión de batch y aplicar preprocesamiento específico de VGG16\n    img_array = np.expand_dims(img_array.astype(np.float32), axis=0)\n    img_array = preprocess_input(img_array)\n\n    # Extraer características y aplanar a un vector 1-D\n    features = base_model.predict(img_array, verbose=0)\n    return features.flatten()\n\n\n# ===============================\n# DIVISIÓN ENTRENAMIENTO / VALIDACIÓN\n# ===============================\n# Se separa el 20 % de las imágenes como conjunto de validación.\n# Esto permite evaluar el rendimiento del modelo en datos no vistos\n# durante el entrenamiento, lo que da una estimación honesta del error.\n\ntrain_split_df, val_split_df = train_test_split(\n    train_wide_df,\n    test_size=VAL_SIZE,\n    random_state=RANDOM_SEED\n)\n\nprint(f\"Imágenes de entrenamiento : {len(train_split_df)}\")\nprint(f\"Imágenes de validación    : {len(val_split_df)}\")\n\n\n# ===============================\n# EXTRACCIÓN DE CARACTERÍSTICAS - ENTRENAMIENTO\n# ===============================\n# Se extrae el vector de características para cada imagen de entrenamiento.\n# El parámetro augment=True activa las transformaciones definidas anteriormente.\n\ntrain_features = []\nprint(\"Extrayendo características del conjunto de ENTRENAMIENTO...\")\n\nfor img_path in tqdm(train_split_df[\"image_path\"]):\n    full_path = os.path.join(TRAIN_IMG_DIR, img_path)\n    features  = extract_features(full_path, augment=True)\n    train_features.append(features)\n\ntrain_features = np.array(train_features)\nprint(\"Matriz de características de entrenamiento:\", train_features)\n\n\n# ===============================\n# EXTRACCIÓN DE CARACTERÍSTICAS - VALIDACIÓN\n# ===============================\n# Para validación NO se aplica aumento de datos: queremos evaluar el modelo\n# en imágenes tal cual llegan, sin modificaciones artificiales.\n\nval_features = []\nprint(\"Extrayendo características del conjunto de VALIDACIÓN...\")\n\nfor img_path in tqdm(val_split_df[\"image_path\"]):\n    full_path = os.path.join(TRAIN_IMG_DIR, img_path)\n    features  = extract_features(full_path, augment=False)\n    val_features.append(features)\n\nval_features = np.array(val_features)\nprint(\"Matriz de características de validación:\", val_features)\n\n\n# ===============================\n# ENTRENAMIENTO DEL MODELO\n# ===============================\n\nTARGET_COLS = train_wide_df.columns[1:]   # todas las columnas excepto image_path\n\nX_train = train_features\ny_train = train_split_df[TARGET_COLS].values\n\nX_val   = val_features\ny_val   = val_split_df[TARGET_COLS].values\n\nprint(f\"X_train: {X_train.shape}  |  y_train: {y_train.shape}\")\nprint(\"Entrenando RandomForestRegressor...\")\n\nmodel = RandomForestRegressor(\n    n_estimators=300,\n    random_state=RANDOM_SEED,\n    verbose=2,\n    n_jobs=-1\n)\n\nmodel.fit(X_train, y_train)\nprint(\"Entrenamiento completado.\")\n\n\n# ============================================================\n# MÓDULO DE VALIDACIÓN Y CALIBRACIÓN\n# ============================================================\n# En esta sección se evalúa el rendimiento del modelo comparando\n# las predicciones generadas contra los valores reales de biomasa\n# del conjunto de validación.\n#\n# Métricas utilizadas:\n#   - MSE  (Mean Squared Error / Error Cuadrático Medio):\n#       Promedio de los errores al cuadrado. Penaliza fuertemente\n#       los errores grandes. Unidad: (g/m²)²\n#\n#   - RMSE (Root Mean Squared Error / Raíz del ECM):\n#       Raíz cuadrada del MSE. Está en la misma unidad que la variable\n#       objetivo (g/m²), por lo que es más interpretable que el MSE.\n#\n#   - R²  (Coeficiente de Determinación):\n#       Indica qué proporción de la varianza de los datos es explicada\n#       por el modelo. Va de 0 a 1; un valor cercano a 1 indica un\n#       mejor ajuste.\n# ============================================================\n\nprint(\"\\n\" + \"=\" * 60)\nprint(\"MÓDULO DE VALIDACIÓN Y CALIBRACIÓN\")\nprint(\"=\" * 60)\n\n# Generar predicciones sobre el conjunto de validación\ny_pred = model.predict(X_val)\n\n# Calcular métricas para CADA variable objetivo por separado,\n# lo que permite identificar en cuál componente de biomasa el modelo\n# tiene mayor o menor dificultad.\nresults = []\n\nfor i, col in enumerate(TARGET_COLS):\n\n    # Valores reales y predichos para esta variable objetivo\n    y_true_col = y_val[:, i]\n    y_pred_col = y_pred[:, i]\n\n    # --- MSE: promedio de (real - predicho)² ---\n    mse  = mean_squared_error(y_true_col, y_pred_col)\n\n    # --- RMSE: raíz del MSE, misma unidad que la biomasa (g/m²) ---\n    rmse = np.sqrt(mse)\n\n    # --- R²: proporción de varianza explicada por el modelo ---\n    r2   = r2_score(y_true_col, y_pred_col)\n\n    results.append({\"Variable objetivo\": col, \"MSE\": mse, \"RMSE\": rmse, \"R²\": r2})\n\n    print(f\"  {col:<20}  MSE={mse:>10.4f}  RMSE={rmse:>8.4f} g/m²  R²={r2:>6.4f}\")\n\n# Mostrar también el promedio global para tener una visión general del modelo\nresults_df = pd.DataFrame(results)\nmean_mse  = results_df[\"MSE\"].mean()\nmean_rmse = results_df[\"RMSE\"].mean()\nmean_r2   = results_df[\"R²\"].mean()\n\nprint(\"-\" * 60)\nprint(f\"  {'PROMEDIO GLOBAL':<20}  MSE={mean_mse:>10.4f}  RMSE={mean_rmse:>8.4f} g/m²  R²={mean_r2:>6.4f}\")\nprint(\"=\" * 60 + \"\\n\")\n\n\n# =============================== TEST ===============================\n\n# ===============================\n# EXTRACCIÓN DE CARACTERÍSTICAS - TEST\n# ===============================\n# Al igual que en validación, NO se aplica aumento de datos en test.\n\ntest_features = []\nunique_image_paths = test_df[\"image_path\"].unique()\n\nprint(\"Extrayendo características del conjunto de TEST...\")\n\nfor img_path in tqdm(unique_image_paths):\n    full_path = os.path.join(TEST_IMG_DIR, img_path)\n    features  = extract_features(full_path, augment=False)\n    test_features.append(features)\n\ntest_features = np.array(test_features)\n\n\n# ===============================\n# PREDICCIÓN Y ENVÍO\n# ===============================\n\nprint(\"Generando predicciones finales...\")\n\ntest_predictions = model.predict(test_features)\n\n# Armar DataFrame ancho con las predicciones\npredictions_df = pd.DataFrame(test_predictions, columns=TARGET_COLS)\npredictions_df.insert(0, \"image_path\", unique_image_paths)\n\n# Convertir de formato ancho a largo (long format requerido para la entrega)\npredictions_long_df = predictions_df.melt(\n    id_vars=[\"image_path\"],\n    value_vars=TARGET_COLS,\n    var_name=\"target_name\",\n    value_name=\"target\"\n)\n\n# Unir con test_df para recuperar el sample_id original\nsubmission_df = pd.merge(\n    test_df[[\"sample_id\", \"image_path\", \"target_name\"]],\n    predictions_long_df,\n    on=[\"image_path\", \"target_name\"]\n)[[\"sample_id\", \"target\"]].dropna()\n\nsubmission_df.to_csv(\"submission.csv\", index=False)\nprint(\"Archivo de entrega creado: submission.csv\")\nprint(submission_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-25T00:47:44.839773Z","iopub.execute_input":"2026-04-25T00:47:44.840317Z","iopub.status.idle":"2026-04-25T00:50:45.064337Z","shell.execute_reply.started":"2026-04-25T00:47:44.840292Z","shell.execute_reply":"2026-04-25T00:50:45.063436Z"}},"outputs":[],"execution_count":null}]}