{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":25954,"databundleVersionId":2091745,"sourceType":"competition"},{"sourceId":387269,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":319284,"modelId":339845}],"dockerImageVersionId":31012,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# 1. Procesamiento de audios y generación de imágenes RGB\n\n\nimport os\nimport numpy as np\nimport librosa\nimport cv2\nfrom tqdm import tqdm\nfrom sklearn.preprocessing import LabelBinarizer\n\n# Especies objetivo\nespecies_objetivo = [\n    'bkcchi', 'grycat', 'blujay', 'bobfly1', 'hofwoo1', 'sonspa', 'amegfi', 'grekis', 'rewbla',\n    'rucwar', 'brnjay', 'chswar', 'swaspa', 'norcar', 'haiwoo', 'reevir1', 'obnthr1', 'rubwre1',\n    'orcpar', 'runwre1', 'comgra', 'rtlhum', 'sthwoo1', 'plawre1', 'yebsap', 'ovenbi1', 'orfpar',\n    'norwat', 'comyel', 'belkin1', 'bucmot2', 'dowwoo', 'eastow', 'eawpew', 'grhcha1',\n    'amecro', 'amerob', 'balori', 'bucmot2', 'cangoo', 'clcrob', 'crfpar', 'norfli', 'rebwoo',\n    'whcpar', 'woothr', 'yehcar1', 'melbla1', 'gockin', 'nocall'  # incluimos nocall\n]\n\nclases = sorted(especies_objetivo)\nclass_to_idx = {c: i for i, c in enumerate(clases)}\n\n# Parámetros\nSPEC_SHAPE = (224, 224) # Nuevo tamaño de espectograma\nSAMPLE_RATE = 32000\nSIGNAL_LENGTH = 5  # segundos\n\ndef mono_to_color(X, eps=1e-6):\n    mean = X.mean()\n    std = X.std()\n    X = (X - mean) / (std + eps)\n    _min, _max = X.min(), X.max()\n    if (_max - _min) > eps:\n        V = np.clip(X, _min, _max)\n        V = 255 * (V - _min) / (_max - _min)\n        V = V.astype(np.uint8)\n    else:\n        V = np.zeros_like(X, dtype=np.uint8)\n    return np.stack([V, V, V], axis=-1)\n\ndef audio_to_rgb_melspec(file_path):\n    wav, _ = librosa.load(file_path, sr=SAMPLE_RATE, duration=SIGNAL_LENGTH, mono=True)\n    if len(wav) < SAMPLE_RATE * SIGNAL_LENGTH:\n        wav = np.pad(wav, (0, SAMPLE_RATE * SIGNAL_LENGTH - len(wav)))\n\n    melspec = librosa.feature.melspectrogram(\n        y=wav,\n        sr=SAMPLE_RATE,\n        n_mels=SPEC_SHAPE[0],\n        fmin=500,\n        fmax=12500\n    )\n    melspec = librosa.power_to_db(melspec).astype(np.float32)\n    melspec = cv2.resize(melspec, SPEC_SHAPE)\n    return mono_to_color(melspec)\n\n# Carga de datos\nX, y = [], []\nbase_path = \"/kaggle/input/birdclef-2021/train_short_audio\"\n\nprint(\"Procesando audios y generando imágenes RGB...\")\nfor especie in tqdm(especies_objetivo):\n    especie_path = os.path.join(base_path, especie)\n    if not os.path.exists(especie_path): continue\n\n    for archivo in os.listdir(especie_path):\n        if archivo.endswith(\".ogg\"):\n            file_path = os.path.join(especie_path, archivo)\n            try:\n                rgb_spec = audio_to_rgb_melspec(file_path)\n                X.append(rgb_spec)\n                y.append(class_to_idx[especie])\n            except Exception as e:\n                print(f\"⚠️ Error con {file_path}: {e}\")\n\n# Convertimos a arrays\nX = np.array(X)\ny = np.array(y)\n\nprint(f\"✅ Datos listos: {X.shape[0]} muestras de {len(clases)} clases.\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-11T18:39:35.355851Z","iopub.execute_input":"2025-05-11T18:39:35.356347Z","iopub.status.idle":"2025-05-11T18:47:15.865509Z","shell.execute_reply.started":"2025-05-11T18:39:35.356319Z","shell.execute_reply":"2025-05-11T18:47:15.864081Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 2. Entrenamiento con EfficientNetB0, RGB y Fine-Tuning\n\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.layers import Input, GlobalAveragePooling2D, Dense, Dropout\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.optimizers import Adam\nimport pickle\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom sklearn.metrics import f1_score, precision_score, recall_score\nimport tensorflow as tf\n\n# One-hot encoding de etiquetas\ny_cat = to_categorical(y, num_classes=len(clases))\n\n# División entrenamiento/validación\nX_train, X_val, y_train, y_val = train_test_split(\n    X, y_cat, test_size=0.2, stratify=y, random_state=42)\n\nprint(f\" Datos listos: {X_train.shape[0]} entrenamiento / {X_val.shape[0]} validación\")\n\n# Normalización de imágenes (añadido para mejorar el entrenamiento)\nX_train = X_train / 255.0\nX_val = X_val / 255.0\n\n# Aumentación de datos (opcional pero recomendado para mejorar generalización)\ndata_augmentation = tf.keras.Sequential([\n    tf.keras.layers.RandomFlip(\"horizontal\"),\n    tf.keras.layers.RandomRotation(0.1),\n    tf.keras.layers.RandomZoom(0.1),\n])\n\n# Métricas personalizadas para F1 score (importante para la competencia)\ndef f1_metric(y_true, y_pred):\n    # Convierte las probabilidades en etiquetas\n    y_pred_classes = tf.argmax(y_pred, axis=1)\n    y_true_classes = tf.argmax(y_true, axis=1)\n    \n    # Calcula precisión y recall\n    precision = tf.reduce_sum(\n        tf.cast(tf.logical_and(tf.equal(y_true_classes, y_pred_classes), \n                              tf.not_equal(y_true_classes, 0)), tf.float32)\n    ) / (tf.reduce_sum(tf.cast(tf.not_equal(y_pred_classes, 0), tf.float32)) + tf.keras.backend.epsilon())\n    \n    recall = tf.reduce_sum(\n        tf.cast(tf.logical_and(tf.equal(y_true_classes, y_pred_classes), \n                              tf.not_equal(y_true_classes, 0)), tf.float32)\n    ) / (tf.reduce_sum(tf.cast(tf.not_equal(y_true_classes, 0), tf.float32)) + tf.keras.backend.epsilon())\n    \n    # F1 Score\n    f1 = 2 * precision * recall / (precision + recall + tf.keras.backend.epsilon())\n    return f1\n\n# Crear modelo con EfficientNetB0\ndef build_model(trainable=False, dropout_rate=0.2):\n    input_tensor = Input(shape=(224, 224, 3))\n    base_model = EfficientNetB0(\n        include_top=False,\n        weights='imagenet',\n        input_tensor=input_tensor\n    )\n    base_model.trainable = trainable\n\n    # Añadir capas superiores\n    x = base_model.output\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(dropout_rate)(x)  # Añadir dropout para evitar sobreajuste\n    output = Dense(len(clases), activation='softmax')(x)\n\n    model = Model(inputs=input_tensor, outputs=output)\n    return model, base_model\n\n# FASE 1: Entrenamiento con capas congeladas (feature extraction)\nmodel, base_model = build_model(trainable=False)\n\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-4),\n    loss='categorical_crossentropy',\n    metrics=['accuracy', f1_metric]\n)\n\n# Callbacks\ncallbacks_phase1 = [\n    EarlyStopping(monitor='val_f1_metric', patience=5, mode='max', restore_best_weights=True),\n    ReduceLROnPlateau(monitor='val_f1_metric', patience=2, factor=0.5, mode='max'),\n]\n\nprint(\"Fase 1: Entrenamiento inicial con capas congeladas\")\nhistory_phase1 = model.fit(\n    X_train, y_train,\n    validation_data=(X_val, y_val),\n    epochs=10,  # Menos épocas para la primera fase\n    batch_size=32,\n    callbacks=callbacks_phase1,\n    verbose=1\n)\nmodel.save('/kaggle/working/modelo_efficientnet_rgb_finetuned1.h5')\n\n# FASE 2: Fine-tuning de las últimas capas\nprint(\"Fase 2: Fine-tuning de las últimas capas\")\n\n# Cargar el mejor modelo de la fase 1\nmodel.load_weights('/kaggle/working/modelo_efficientnet_rgb_finetuned1.h5')\n\n# Descongelar las últimas capas (por ejemplo, los últimos 20 bloques)\n# EfficientNetB0 tiene 236 capas en total\nbase_model.trainable = True\n\n# Congelar todas las capas excepto las últimas N\nfine_tune_at = len(base_model.layers) - 20  # Ajustar este número según necesites\nfor layer in base_model.layers[:fine_tune_at]:\n    layer.trainable = False\n\n# Verificar qué capas son entrenables\ntrainable_layers = [layer.name for layer in base_model.layers if layer.trainable]\nprint(f\"Capas entrenables: {len(trainable_layers)} de {len(base_model.layers)}\")\nprint(f\"Primeras 5 capas entrenables: {trainable_layers[:5]}\")\n\n# Recompilar el modelo con una tasa de aprendizaje más baja para fine-tuning\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-4),  # Tasa de aprendizaje más baja\n    loss='categorical_crossentropy',\n    metrics=['accuracy', f1_metric]\n)\n\n# Callbacks para la fase de fine-tuning\ncallbacks_phase2 = [\n    EarlyStopping(monitor='val_f1_metric', patience=7, mode='max', restore_best_weights=True),\n    ReduceLROnPlateau(monitor='val_f1_metric', patience=3, factor=0.2, mode='max'),\n]\n\n# Entrenamiento con fine-tuning\nhistory_phase2 = model.fit(\n    X_train, y_train,\n    validation_data=(X_val, y_val),\n    epochs=8,  # Más épocas para fine-tuning\n    batch_size=16,  # Batch size más pequeño para fine-tuning\n    callbacks=callbacks_phase2,\n    verbose=1\n)\n\n\n## Segunda parte\n\nmodel.save('/kaggle/working/modelo_efficientnet_rgb_finetuned2.h5')\n\n# Combinar historiales\nhistory = {}\nfor key in history_phase1.history:\n    history[key] = history_phase1.history[key] + history_phase2.history[key]\n\n# Evaluar el modelo final\nprint(\"Evaluando modelo final...\")\nmodel.load_weights('/kaggle/working/modelo_efficientnet_rgb_finetuned2.h5')  # Cargar el mejor modelo\ntest_loss, test_acc, test_f1 = model.evaluate(X_val, y_val)\nprint(f\"Rendimiento final: Accuracy = {test_acc:.4f}, F1 Score = {test_f1:.4f}\")\n\n# Hacer predicciones y calcular métricas adicionales\ny_pred = model.predict(X_val)\ny_pred_classes = np.argmax(y_pred, axis=1)\ny_true_classes = np.argmax(y_val, axis=1)\n\n# Calcular F1, precisión y recall\nf1 = f1_score(y_true_classes, y_pred_classes, average='macro')\nprecision = precision_score(y_true_classes, y_pred_classes, average='macro')\nrecall = recall_score(y_true_classes, y_pred_classes, average='macro')\n\nprint(f\"Métricas detalladas:\")\nprint(f\"F1 Score: {f1:.4f}\")\nprint(f\"Precision: {precision:.4f}\")\nprint(f\"Recall: {recall:.4f}\")\n\n# Guardar modelo entrenado y su historial\nmodel.save('/kaggle/working/modelo_efficientnet_rgb_finetuned2.h5')\nwith open('/kaggle/working/history_entrenamiento_finetuned.pkl', 'wb') as f:\n    pickle.dump(history, f)\n\n# Visualizar el entrenamiento\nplt.figure(figsize=(15, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history['accuracy'])\nplt.plot(history['val_accuracy'])\nplt.title('Accuracy del modelo')\nplt.ylabel('Accuracy')\nplt.xlabel('Época')\nplt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\nplt.subplot(1, 2, 2)\nplt.plot(history['f1_metric'])\nplt.plot(history['val_f1_metric'])\nplt.title('F1 Score del modelo')\nplt.ylabel('F1 Score')\nplt.xlabel('Época')\nplt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\nplt.tight_layout()\nplt.savefig('/kaggle/working/training_metrics.png')\nplt.show()\n\nprint(\"✅ Modelo entrenado y guardado con EfficientNetB0 y fine-tuning.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T17:53:18.347831Z","iopub.execute_input":"2025-05-11T17:53:18.348860Z","iopub.status.idle":"2025-05-11T17:53:18.401964Z","shell.execute_reply.started":"2025-05-11T17:53:18.348819Z","shell.execute_reply":"2025-05-11T17:53:18.400261Z"},"jupyter":{"source_hidden":true,"outputs_hidden":true},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"## Segunda parte\n\nmodel.save('/kaggle/working/modelo_efficientnet_rgb_finetuned2.h5')\n\n# Combinar historiales\nhistory = {}\nfor key in history_phase1.history:\n    history[key] = history_phase1.history[key] + history_phase2.history[key]\n\n# Evaluar el modelo final\nprint(\"Evaluando modelo final...\")\nmodel.load_weights('/kaggle/working/modelo_efficientnet_rgb_finetuned2.h5')  # Cargar el mejor modelo\ntest_loss, test_acc, test_f1 = model.evaluate(X_val, y_val)\nprint(f\"Rendimiento final: Accuracy = {test_acc:.4f}, F1 Score = {test_f1:.4f}\")\n\n# Hacer predicciones y calcular métricas adicionales\ny_pred = model.predict(X_val)\ny_pred_classes = np.argmax(y_pred, axis=1)\ny_true_classes = np.argmax(y_val, axis=1)\n\n# Calcular F1, precisión y recall\nf1 = f1_score(y_true_classes, y_pred_classes, average='macro')\nprecision = precision_score(y_true_classes, y_pred_classes, average='macro')\nrecall = recall_score(y_true_classes, y_pred_classes, average='macro')\n\nprint(f\"Métricas detalladas:\")\nprint(f\"F1 Score: {f1:.4f}\")\nprint(f\"Precision: {precision:.4f}\")\nprint(f\"Recall: {recall:.4f}\")\n\n# Guardar modelo entrenado y su historial\nmodel.save('/kaggle/working/modelo_efficientnet_rgb_finetuned2.h5')\nwith open('/kaggle/working/history_entrenamiento_finetuned.pkl', 'wb') as f:\n    pickle.dump(history, f)\n\n# Visualizar el entrenamiento\nplt.figure(figsize=(15, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history['accuracy'])\nplt.plot(history['val_accuracy'])\nplt.title('Accuracy del modelo')\nplt.ylabel('Accuracy')\nplt.xlabel('Época')\nplt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\nplt.subplot(1, 2, 2)\nplt.plot(history['f1_metric'])\nplt.plot(history['val_f1_metric'])\nplt.title('F1 Score del modelo')\nplt.ylabel('F1 Score')\nplt.xlabel('Época')\nplt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\nplt.tight_layout()\nplt.savefig('/kaggle/working/training_metrics.png')\nplt.show()\n\nprint(\"✅ Modelo entrenado y guardado con EfficientNetB0 y fine-tuning.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T14:47:24.439019Z","iopub.execute_input":"2025-05-11T14:47:24.439458Z","iopub.status.idle":"2025-05-11T14:50:33.716112Z","shell.execute_reply.started":"2025-05-11T14:47:24.439424Z","shell.execute_reply":"2025-05-11T14:50:33.714965Z"},"jupyter":{"outputs_hidden":true,"source_hidden":true},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# PARTE A\n\n# Evaluación del modelo RGB con métricas F1 Score y exportación a CSV\n\nimport os\nimport numpy as np\nimport pandas as pd\nimport librosa\nimport cv2\nfrom tensorflow.keras.models import load_model\nfrom sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score, multilabel_confusion_matrix\n\n# === Configuración ===\nSAMPLE_RATE = 32000\nSIGNAL_LENGTH = 5  # en segundos\nSPEC_SHAPE = (224, 224)\nTHRESHOLD = 0.1\n\n# === Clases ===\nespecies_objetivo = [\n    'bkcchi', 'grycat', 'blujay', 'bobfly1', 'hofwoo1', 'sonspa', 'amegfi', 'grekis', 'rewbla',\n    'rucwar', 'brnjay', 'chswar', 'swaspa', 'norcar', 'haiwoo', 'reevir1', 'obnthr1', 'rubwre1',\n    'orcpar', 'runwre1', 'comgra', 'rtlhum', 'sthwoo1', 'plawre1', 'yebsap', 'ovenbi1', 'orfpar',\n    'norwat', 'comyel', 'belkin1', 'bucmot2', 'dowwoo', 'eastow', 'eawpew', 'grhcha1',\n    'amecro', 'amerob', 'balori', 'bucmot2', 'cangoo', 'clcrob', 'crfpar', 'norfli', 'rebwoo',\n    'whcpar', 'woothr', 'yehcar1', 'melbla1', 'gockin', 'nocall'\n]\nclases = sorted(especies_objetivo)\nlabel_to_idx = {l: i for i, l in enumerate(clases)}\n\n# === Funciones auxiliares ===\ndef mono_to_color(X, eps=1e-6):\n    mean = X.mean()\n    std = X.std()\n    X = (X - mean) / (std + eps)\n    _min, _max = X.min(), X.max()\n    if (_max - _min) > eps:\n        V = np.clip(X, _min, _max)\n        V = 255 * (V - _min) / (_max - _min)\n        V = V.astype(np.uint8)\n    else:\n        V = np.zeros_like(X, dtype=np.uint8)\n    return np.stack([V, V, V], axis=-1)\n\ndef predict_fragment(chunk, model):\n    spec = librosa.feature.melspectrogram(y=chunk, sr=SAMPLE_RATE, n_mels=SPEC_SHAPE[0], fmin=500, fmax=12500)\n    spec = librosa.power_to_db(spec).astype(np.float32)\n    spec = cv2.resize(spec, SPEC_SHAPE)\n    rgb = mono_to_color(spec)\n    rgb = np.expand_dims(rgb, axis=0)\n    return model.predict(rgb, verbose=0)[0]\n\ndef compute_metrics_per_example(true_labels, pred_labels):\n    \"\"\"\n    Calcula métricas para un único ejemplo según las fórmulas definidas:\n    - Precision = TP / (TP + FP)\n    - Recall = TP / (TP + FN)\n    - F1 = 2 * (Precision * Recall) / (Precision + Recall)\n    - Accuracy = (TP + TN) / (TP + TN + FP + FN)\n    - Specificity = TN / (TN + FP)\n    \"\"\"\n    true_set = set(true_labels)\n    pred_set = set(pred_labels)\n    \n    # Para clasificación multilabel\n    tp = len(true_set & pred_set)  # Verdaderos positivos: especies correctamente predichas\n    fp = len(pred_set - true_set)  # Falsos positivos: especies predichas pero no presentes\n    fn = len(true_set - pred_set)  # Falsos negativos: especies presentes pero no predichas\n    \n    # En clasificación multilabel, para calcular TN necesitamos considerar todas las clases\n    # TN son las especies que correctamente NO se predicen porque NO están presentes\n    all_classes = set(clases)\n    tn = len(all_classes - true_set - pred_set)  # Verdaderos negativos\n    \n    # Cálculo de métricas según las fórmulas\n    precision = tp / (tp + fp) if (tp + fp) > 0 else 0\n    recall = tp / (tp + fn) if (tp + fn) > 0 else 0\n    f1 = 2 * (precision * recall) / (precision + recall) if (precision + recall) > 0 else 0\n    accuracy = (tp + tn) / (tp + tn + fp + fn) if (tp + tn + fp + fn) > 0 else 0\n    specificity = tn / (tn + fp) if (tn + fp) > 0 else 0\n    \n    return {\n        \"precision\": precision,\n        \"recall\": recall,\n        \"f1\": f1,\n        \"accuracy\": accuracy,\n        \"specificity\": specificity,\n        \"tp\": tp,\n        \"fp\": fp,\n        \"fn\": fn,\n        \"tn\": tn\n    }\n\ndef get_metrics_from_binary_matrices(y_true_bin, y_pred_bin):\n    \"\"\"\n    Calcula métricas globales a partir de matrices binarias de verdad y predicción\n    \"\"\"\n    # Métricas usando sklearn para el micro-promedio (global)\n    precision = precision_score(y_true_bin, y_pred_bin, average='micro', zero_division=0)\n    recall = recall_score(y_true_bin, y_pred_bin, average='micro', zero_division=0)\n    f1 = f1_score(y_true_bin, y_pred_bin, average='micro', zero_division=0)\n    accuracy = accuracy_score(y_true_bin, y_pred_bin)\n    \n    # Para la especificidad, calculamos el promedio de las matrices de confusión por clase\n    mcm = multilabel_confusion_matrix(y_true_bin, y_pred_bin)\n    specificity = np.mean([\n        m[0, 0] / (m[0, 0] + m[0, 1]) if (m[0, 0] + m[0, 1]) > 0 else 0\n        for m in mcm\n    ])\n    \n    return {\n        \"precision\": precision,\n        \"recall\": recall,\n        \"f1\": f1,\n        \"accuracy\": accuracy,\n        \"specificity\": specificity\n    }\n\ndef multilabel_to_binary(y_list, label_map):\n    \"\"\"\n    Convierte una lista de etiquetas multilabel a su representación binaria\n    \"\"\"\n    bin_labels = []\n    for labels in y_list:\n        row = [0] * len(label_map)\n        for l in labels:\n            l = l.strip()\n            idx = label_map.get(l, None)\n            if idx is not None and idx < len(row):\n                row[idx] = 1\n        bin_labels.append(row)\n    return np.array(bin_labels)\n\n# === Cargar modelo entrenado ===\nprint(\"🔄 Cargando modelo...\")\nmodel_path = \"/kaggle/working/modelo_efficientnet_rgb_finetuned2.h5\"\nmodel = load_model(model_path)\nprint(\"✅ Modelo cargado correctamente\")\n\n# === Datos ===\nprint(\"🔄 Cargando datos de etiquetas...\")\ngt_df = pd.read_csv(\"/kaggle/input/birdclef-2021/train_soundscape_labels.csv\")\ngt_df['birds'] = gt_df['birds'].fillna(\"nocall\")\ngt_dict = dict(zip(gt_df['row_id'], gt_df['birds']))\nprint(f\"✅ Cargadas {len(gt_df)} etiquetas\")\n\n# === Evaluación por audio ===\nsoundscape_dir = \"/kaggle/input/birdclef-2021/train_soundscapes/\"\nmetricas_por_audio = []\n\n# === Para guardar las predicciones detalladas ===\nall_predictions = []\n\n# === Para métricas globales ===\nall_tp = 0\nall_fp = 0\nall_fn = 0\nall_tn = 0\n\nprint(\"🔍 Evaluando soundscapes...\\n\")\n\nfor fname in sorted(os.listdir(soundscape_dir)):\n    if not fname.endswith(\".ogg\"): continue\n    print(f\"🔄 Procesando {fname}...\")\n    base, site = fname.split('_')[0], fname.split('_')[1]\n    y, _ = librosa.load(os.path.join(soundscape_dir, fname), sr=SAMPLE_RATE)\n\n    y_true_rows = []\n    y_pred_rows = []\n    \n    # Para métricas acumuladas por audio\n    audio_tp = 0\n    audio_fp = 0 \n    audio_fn = 0\n    audio_tn = 0\n    \n    metricas_por_fragmento = []\n\n    for i in range(0, len(y), SIGNAL_LENGTH * SAMPLE_RATE):\n        chunk = y[i:i + SIGNAL_LENGTH * SAMPLE_RATE]\n        if len(chunk) < SIGNAL_LENGTH * SAMPLE_RATE:\n            continue\n\n        seconds = (i // (SAMPLE_RATE * SIGNAL_LENGTH) + 1) * SIGNAL_LENGTH\n        row_id = f\"{base}_{site}_{seconds}\"\n        \n        # Predecir especies\n        pred_probs = predict_fragment(chunk, model)\n        \n        # Obtener top 3 predicciones para el análisis\n        top_indices = np.argsort(pred_probs)[-3:][::-1]  # Top 3 predicciones\n        top_birds = [(clases[idx], pred_probs[idx]) for idx in top_indices]\n        \n        # Obtener etiquetas reales y predicciones basadas en umbral\n        true_birds = gt_dict.get(row_id, \"nocall\").split()\n        pred_labels = [clases[j] for j, p in enumerate(pred_probs) if p > THRESHOLD]\n        if not pred_labels:\n            pred_labels = [\"nocall\"]\n            \n        # Calcular métricas por fragmento individualmente\n        metrics = compute_metrics_per_example(true_birds, pred_labels)\n        \n        # Acumular métricas para este audio\n        audio_tp += metrics[\"tp\"]\n        audio_fp += metrics[\"fp\"]\n        audio_fn += metrics[\"fn\"]\n        audio_tn += metrics[\"tn\"]\n        \n        # Acumular métricas globales\n        all_tp += metrics[\"tp\"]\n        all_fp += metrics[\"fp\"]\n        all_fn += metrics[\"fn\"]\n        all_tn += metrics[\"tn\"]\n        \n        # Guardar datos para el análisis detallado\n        prediction_data = {\n            \"row_id\": row_id,\n            \"birds\": \" \".join(true_birds),\n            \"prediction\": \" \".join(pred_labels),\n            \"pred1\": top_birds[0][0],\n            \"score1\": top_birds[0][1],\n            \"pred2\": top_birds[1][0],\n            \"score2\": top_birds[1][1],\n            \"pred3\": top_birds[2][0],\n            \"score3\": top_birds[2][1],\n            \"precision\": metrics[\"precision\"],\n            \"recall\": metrics[\"recall\"],\n            \"f1\": metrics[\"f1\"],\n            \"accuracy\": metrics[\"accuracy\"],\n            \"specificity\": metrics[\"specificity\"]\n        }\n        all_predictions.append(prediction_data)\n        metricas_por_fragmento.append(prediction_data)\n        \n        # Guardar para la evaluación global\n        y_pred_rows.append(pred_labels)\n        y_true_rows.append(true_birds)\n\n    # === Métricas por audio ===\n    if y_true_rows:\n        # Calcular métricas usando matrices binarias (enfoque sklearn)\n        y_true_bin = multilabel_to_binary(y_true_rows, label_to_idx)\n        y_pred_bin = multilabel_to_binary(y_pred_rows, label_to_idx)\n        metrics_from_bin = get_metrics_from_binary_matrices(y_true_bin, y_pred_bin)\n        \n        # Calcular métricas manualmente desde los totales acumulados\n        total = audio_tp + audio_fp + audio_fn + audio_tn\n        precision_manual = audio_tp / (audio_tp + audio_fp) if (audio_tp + audio_fp) > 0 else 0\n        recall_manual = audio_tp / (audio_tp + audio_fn) if (audio_tp + audio_fn) > 0 else 0\n        f1_manual = 2 * (precision_manual * recall_manual) / (precision_manual + recall_manual) if (precision_manual + recall_manual) > 0 else 0\n        accuracy_manual = (audio_tp + audio_tn) / total if total > 0 else 0\n        specificity_manual = audio_tn / (audio_tn + audio_fp) if (audio_tn + audio_fp) > 0 else 0\n        \n        # Usar las métricas calculadas con sklearn como las \"oficiales\"\n        fila = {\n            \"Audio\": fname,\n            \"Precisión\": metrics_from_bin[\"precision\"],\n            \"Sensibilidad\": metrics_from_bin[\"recall\"],\n            \"F1 Score\": metrics_from_bin[\"f1\"],\n            \"Precisión global\": metrics_from_bin[\"accuracy\"],\n            \"Especificidad\": metrics_from_bin[\"specificity\"],\n            # Incluir también las métricas calculadas manualmente para comparación\n            \"Precisión_manual\": precision_manual,\n            \"Sensibilidad_manual\": recall_manual,\n            \"F1 Score_manual\": f1_manual,\n            \"Precisión global_manual\": accuracy_manual,\n            \"Especificidad_manual\": specificity_manual,\n            # Contadores\n            \"TP\": audio_tp,\n            \"FP\": audio_fp,\n            \"FN\": audio_fn,\n            \"TN\": audio_tn,\n            \"Fragmentos\": len(y_true_rows)\n        }\n        metricas_por_audio.append(fila)\n\n        print(f\"🎧 {fname}:\")\n        print(f\"  - Fragmentos evaluados: {len(y_true_rows)}\")\n        print(f\"  - Precisión:         {metrics_from_bin['precision']:.4f}\")\n        print(f\"  - Sensibilidad:      {metrics_from_bin['recall']:.4f}\")\n        print(f\"  - F1 Score:          {metrics_from_bin['f1']:.4f}\")\n        print(f\"  - Precisión global:  {metrics_from_bin['accuracy']:.4f}\")\n        print(f\"  - Especificidad:     {metrics_from_bin['specificity']:.4f}\")\n        print(\"\")\n        \n        # Guardar métricas por fragmento para este audio\n        audio_fragments_df = pd.DataFrame(metricas_por_fragmento)\n        audio_name = fname.replace(\".ogg\", \"\")\n        audio_fragments_df.to_csv(f\"/kaggle/working/metricas_{audio_name}.csv\", index=False)\n        print(f\"💾 Métricas por fragmento guardadas en 'metricas_{audio_name}.csv'\")\n\n# === Calcular métricas globales ===\ntotal_global = all_tp + all_fp + all_fn + all_tn\nprecision_global = all_tp / (all_tp + all_fp) if (all_tp + all_fp) > 0 else 0\nrecall_global = all_tp / (all_tp + all_fn) if (all_tp + all_fn) > 0 else 0\nf1_global = 2 * (precision_global * recall_global) / (precision_global + recall_global) if (precision_global + recall_global) > 0 else 0\naccuracy_global = (all_tp + all_tn) / total_global if total_global > 0 else 0\nspecificity_global = all_tn / (all_tn + all_fp) if (all_tn + all_fp) > 0 else 0\n\nprint(\"\\n📊 MÉTRICAS GLOBALES:\")\nprint(f\"  - Precisión:         {precision_global:.4f}\")\nprint(f\"  - Sensibilidad:      {recall_global:.4f}\")\nprint(f\"  - F1 Score:          {f1_global:.4f}\")\nprint(f\"  - Precisión global:  {accuracy_global:.4f}\")\nprint(f\"  - Especificidad:     {specificity_global:.4f}\")\nprint(f\"  - Total TP: {all_tp}, FP: {all_fp}, FN: {all_fn}, TN: {all_tn}\")\n\n# === Mostrar tabla final ===\ntabla_metricas = pd.DataFrame(metricas_por_audio)\nprint(\"\\n📊 Tabla completa de métricas por audio:\")\nprint(tabla_metricas[[\"Audio\", \"Precisión\", \"Sensibilidad\", \"F1 Score\", \"Precisión global\", \"Especificidad\", \"Fragmentos\"]])\n\n# === Guardar resultados en archivos CSV ===\ntabla_metricas.to_csv(\"/kaggle/working/metricas_por_audio.csv\", index=False)\nprint(\"\\n💾 Métricas por audio guardadas en 'metricas_por_audio.csv'\")\n\n# === Guardar todas las predicciones detalladas ===\npredictions_df = pd.DataFrame(all_predictions)\npredictions_df.to_csv(\"/kaggle/working/predicciones_detalladas.csv\", index=False)\nprint(\"💾 Predicciones detalladas guardadas en 'predicciones_detalladas.csv'\")\n\n# === Guardar métricas globales ===\nmetricas_globales = pd.DataFrame([{\n    \"Precisión\": precision_global,\n    \"Sensibilidad\": recall_global,\n    \"F1 Score\": f1_global,\n    \"Precisión global\": accuracy_global,\n    \"Especificidad\": specificity_global,\n    \"TP\": all_tp,\n    \"FP\": all_fp,\n    \"FN\": all_fn,\n    \"TN\": all_tn\n}])\nmetricas_globales.to_csv(\"/kaggle/working/metricas_globales.csv\", index=False)\nprint(\"💾 Métricas globales guardadas en 'metricas_globales.csv'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T15:51:14.684827Z","iopub.execute_input":"2025-05-11T15:51:14.685209Z","iopub.status.idle":"2025-05-11T15:57:56.119079Z","shell.execute_reply.started":"2025-05-11T15:51:14.685185Z","shell.execute_reply":"2025-05-11T15:57:56.117797Z"},"jupyter":{"outputs_hidden":true,"source_hidden":true},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# PARTE B\n# Mostrar tabla detallada con TODAS las predicciones y estadísticas\n\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom IPython.display import display, HTML\n\n# === Configuración ===\nCOLUMNAS_MOSTRAR = 20  # Número máximo de filas a mostrar en la tabla detallada\n\nprint(\"🔄 Cargando predicciones detalladas...\")\n# Cargar los datos de predicciones generados en la Parte A\ntry:\n    predictions_df = pd.read_csv(\"/kaggle/working/predicciones_detalladas.csv\")\n    print(f\"✅ Cargadas {len(predictions_df)} predicciones\")\nexcept FileNotFoundError:\n    print(\"❌ Error: El archivo de predicciones no se encuentra. Ejecute primero la Parte A.\")\n    predictions_df = pd.DataFrame()\n\nif not predictions_df.empty:\n    # === 1. Crear tabla de resultados con formato visual mejorado ===\n    print(\"\\n🔍 Generando tabla detallada de predicciones...\")\n    \n    # Crear nueva tabla para visualización\n    results_table = []\n    \n    for idx, row in predictions_df.iterrows():\n        row_id = row['row_id']\n        true_birds = row['birds']\n        pred_birds = row['prediction']\n        \n        # Verificar si predicción es correcta o no\n        true_set = set(true_birds.split())\n        pred_set = set(pred_birds.split())\n        \n        # Para cada especie en true y pred\n        common = true_set.intersection(pred_set)\n        false_pos = pred_set - true_set\n        false_neg = true_set - pred_set\n        \n        # Formato para mostrar coincidencias\n        formatted_prediction = \"\"\n        for bird in pred_birds.split():\n            if bird in common:\n                formatted_prediction += f\"<span style='color:green'>{bird}</span> \"\n            else:\n                formatted_prediction += f\"<span style='color:red'>{bird}</span> \"\n        \n        # Formato para mostrar etiquetas reales\n        formatted_true = \"\"\n        for bird in true_birds.split():\n            if bird in common:\n                formatted_true += f\"<span style='color:green'>{bird}</span> \"\n            else:\n                formatted_true += f\"<span style='color:orange'>{bird}</span> \"\n        \n        # Resultado general: éxito completo, parcial o error\n        if common == true_set and common == pred_set:\n            result = \"✓\"\n            result_class = \"success\"\n        elif len(common) > 0:\n            result = \"±\"  # Parcialmente correcto\n            result_class = \"partial\"\n        else:\n            result = \"✗\"\n            result_class = \"error\"\n            \n        # Métricas para este fragmento\n        precision = row.get('precision', 0)\n        recall = row.get('recall', 0)\n        f1 = row.get('f1', 0)\n            \n        # Crear fila para la tabla\n        results_table.append({\n            \"row_id\": row_id,\n            \"ground_truth\": formatted_true.strip(),\n            \"prediction\": formatted_prediction.strip(),\n            \"result\": result,\n            \"result_class\": result_class,\n            \"precision\": f\"{precision:.4f}\",\n            \"recall\": f\"{recall:.4f}\",\n            \"f1_score\": f\"{f1:.4f}\",\n            \"top1\": f\"{row['pred1']} ({row['score1']:.4f})\",\n            \"top2\": f\"{row['pred2']} ({row['score2']:.4f})\",\n            \"top3\": f\"{row['pred3']} ({row['score3']:.4f})\"\n        })\n    \n    # Convertir a DataFrame\n    results_df = pd.DataFrame(results_table)\n    \n    # Mostrar tabla interactiva con formato HTML\n    css = \"\"\"\n    <style>\n    .dataframe {\n        font-family: Arial, sans-serif;\n        border-collapse: collapse;\n        width: 100%;\n    }\n    .dataframe th {\n        background-color: #4b6584;\n        color: white;\n        text-align: left;\n        padding: 8px;\n    }\n    .dataframe td {\n        border: 1px solid #ddd;\n        padding: 8px;\n    }\n    .dataframe tr:nth-child(even) {\n        background-color: #f2f2f2;\n    }\n    .dataframe tr:hover {\n        background-color: #ddd;\n    }\n    .success {\n        background-color: #badc58 !important;\n        font-weight: bold;\n    }\n    .partial {\n        background-color: #f9ca24 !important;\n    }\n    .error {\n        background-color: #ff7979 !important;\n    }\n    </style>\n    \"\"\"\n    \n    # Aplicar formato condicional\n    def style_result(val):\n        if val == \"✓\":\n            return 'background-color: #badc58'\n        elif val == \"±\":\n            return 'background-color: #f9ca24'\n        else:\n            return 'background-color: #ff7979'\n    \n    # Guardar tabla completa sin HTML para exportación\n    export_df = results_df.copy()\n    # Limpiar las etiquetas HTML para la exportación\n    for col in ['ground_truth', 'prediction']:\n        export_df[col] = export_df[col].str.replace('<[^<]+?>', '', regex=True)\n    \n    export_df.to_csv(\"/kaggle/working/tabla_predicciones_completa.csv\", index=False)\n    print(f\"💾 Tabla completa guardada en 'tabla_predicciones_completa.csv' ({len(export_df)} filas)\")\n    \n    # Mostrar solo las primeras N filas en consola\n    print(f\"\\n📊 Muestra de las primeras {min(COLUMNAS_MOSTRAR, len(results_df))} predicciones:\")\n    display_df = results_df.head(COLUMNAS_MOSTRAR).copy()\n    display(HTML(css + display_df.to_html(escape=False)))\n    \n    # === 2. Análisis estadístico de predicciones ===\n    print(\"\\n📈 Estadísticas generales de predicción:\")\n    \n    # Totales por tipo de resultado\n    total_predictions = len(results_df)\n    correct_predictions = len(results_df[results_df['result'] == \"✓\"])\n    partial_predictions = len(results_df[results_df['result'] == \"±\"])\n    incorrect_predictions = len(results_df[results_df['result'] == \"✗\"])\n    \n    print(f\"Total de fragmentos evaluados: {total_predictions}\")\n    print(f\"Predicciones completamente correctas: {correct_predictions} ({correct_predictions/total_predictions*100:.2f}%)\")\n    print(f\"Predicciones parcialmente correctas: {partial_predictions} ({partial_predictions/total_predictions*100:.2f}%)\")\n    print(f\"Predicciones incorrectas: {incorrect_predictions} ({incorrect_predictions/total_predictions*100:.2f}%)\")\n    \n    # === 3. Análisis por especie ===\n    # Preparar datos\n    all_birds = set()\n    for birds in predictions_df['birds'].str.split():\n        all_birds.update(birds)\n    \n    # Inicializar estadísticas por especie\n    species_stats = {bird: {\"tp\": 0, \"fp\": 0, \"fn\": 0, \"total_true\": 0, \"total_pred\": 0} for bird in all_birds}\n    \n    # Calcular estadísticas\n    for idx, row in predictions_df.iterrows():\n        true_set = set(row['birds'].split())\n        pred_set = set(row['prediction'].split())\n        \n        for bird in all_birds:\n            # Si la especie está en verdadero positivo\n            if bird in true_set and bird in pred_set:\n                species_stats[bird][\"tp\"] += 1\n            # Si es un falso positivo\n            elif bird in pred_set:\n                species_stats[bird][\"fp\"] += 1\n            # Si es un falso negativo\n            elif bird in true_set:\n                species_stats[bird][\"fn\"] += 1\n                \n            # Contar apariciones totales\n            if bird in true_set:\n                species_stats[bird][\"total_true\"] += 1\n            if bird in pred_set:\n                species_stats[bird][\"total_pred\"] += 1\n    \n    # Calcular métricas por especie\n    species_metrics = []\n    for bird, stats in species_stats.items():\n        if stats[\"total_true\"] == 0 and stats[\"total_pred\"] == 0:\n            continue\n            \n        tp = stats[\"tp\"]\n        fp = stats[\"fp\"]\n        fn = stats[\"fn\"]\n        \n        precision = tp / (tp + fp) if (tp + fp) > 0 else 0\n        recall = tp / (tp + fn) if (tp + fn) > 0 else 0\n        f1 = 2 * precision * recall / (precision + recall) if (precision + recall) > 0 else 0\n        \n        species_metrics.append({\n            \"Especie\": bird,\n            \"Total_real\": stats[\"total_true\"],\n            \"Total_pred\": stats[\"total_pred\"],\n            \"VP\": tp,\n            \"FP\": fp,\n            \"FN\": fn,\n            \"Precision\": precision,\n            \"Recall\": recall,\n            \"F1\": f1\n        })\n    \n    # Crear DataFrame y ordenar por número de apariciones\n    species_df = pd.DataFrame(species_metrics).sort_values(\"Total_real\", ascending=False)\n    \n    # Formatear para mejor visualización\n    species_display = species_df.copy()\n    for col in [\"Precision\", \"Recall\", \"F1\"]:\n        species_display[col] = species_display[col].map(\"{:.4f}\".format)\n    \n    print(\"\\n🦜 Estadísticas por especie de ave (Top 15):\")\n    print(species_display.head(15).to_string(index=False))\n    \n    # Guardar resultados\n    species_df.to_csv(\"/kaggle/working/estadisticas_por_especie.csv\", index=False)\n    print(\"\\n💾 Estadísticas por especie guardadas en 'estadisticas_por_especie.csv'\")\n    \n    # === 4. Visualización de distribución de F1 Score ===\n    if 'f1' in predictions_df.columns:\n        plt.figure(figsize=(10, 6))\n        sns.histplot(predictions_df['f1'], bins=20, kde=True)\n        plt.title('Distribución de F1 Score por fragmento')\n        plt.xlabel('F1 Score')\n        plt.ylabel('Frecuencia')\n        plt.grid(True, alpha=0.3)\n        plt.savefig('/kaggle/working/distribucion_f1_score.png', dpi=300, bbox_inches='tight')\n        print(\"\\n💾 Gráfico de distribución de F1 Score guardado como 'distribucion_f1_score.png'\")\n        plt.close()\n    \n    # === 5. Gráfica de métricas por audio ===\n    try:\n        audio_metrics_df = pd.read_csv(\"/kaggle/working/metricas_por_audio.csv\")\n        if not audio_metrics_df.empty:\n            # Ordenar por F1 Score para mejor visualización\n            audio_metrics_df = audio_metrics_df.sort_values(\"F1 Score\", ascending=False)\n            \n            # Extraer nombres cortos de audio para etiquetas\n            audio_metrics_df['Audio_short'] = audio_metrics_df['Audio'].apply(lambda x: x.split('.')[0][:10])\n            \n            # Crear gráfica de barras agrupadas\n            metrics_to_plot = [\"Precisión\", \"Sensibilidad\", \"F1 Score\", \"Precisión global\", \"Especificidad\"]\n            \n            plt.figure(figsize=(14, 8))\n            x = np.arange(len(audio_metrics_df))\n            width = 0.15\n            offsets = np.linspace(-(len(metrics_to_plot)-1)/2*width, (len(metrics_to_plot)-1)/2*width, len(metrics_to_plot))\n            \n            for i, metric in enumerate(metrics_to_plot):\n                plt.bar(x + offsets[i], audio_metrics_df[metric], width=width, label=metric)\n            \n            plt.xlabel('Audio')\n            plt.ylabel('Valor')\n            plt.title('Métricas por archivo de audio')\n            plt.xticks(x, audio_metrics_df['Audio_short'], rotation=45, ha='right')\n            plt.ylim(0, 1.05)\n            plt.legend(loc='upper right')\n            plt.grid(True, alpha=0.3, axis='y')\n            plt.tight_layout()\n            plt.savefig('/kaggle/working/metricas_por_audio.png', dpi=300, bbox_inches='tight')\n            print(\"\\n💾 Gráfico de métricas por audio guardado como 'metricas_por_audio.png'\")\n            plt.close()\n            \n            # === 6. Gráfica comparativa de F1 Score vs Precisión global ===\n            plt.figure(figsize=(10, 6))\n            plt.scatter(audio_metrics_df['F1 Score'], audio_metrics_df['Precisión global'], \n                      s=80, alpha=0.7, c=audio_metrics_df['Fragmentos'], cmap='viridis')\n            \n            # Añadir etiquetas a los puntos\n            for i, row in audio_metrics_df.iterrows():\n                plt.annotate(row['Audio_short'], \n                             (row['F1 Score'], row['Precisión global']),\n                             xytext=(5, 5), textcoords='offset points',\n                             fontsize=8)\n            \n            plt.colorbar(label='Número de fragmentos')\n            plt.xlabel('F1 Score')\n            plt.ylabel('Precisión global (Accuracy)')\n            plt.title('Relación entre F1 Score y Precisión Global por Audio')\n            plt.grid(True, alpha=0.3)\n            plt.savefig('/kaggle/working/f1_vs_accuracy.png', dpi=300, bbox_inches='tight')\n            print(\"\\n💾 Gráfico de F1 vs Precisión guardado como 'f1_vs_accuracy.png'\")\n            plt.close()\n            \n    except Exception as e:\n        print(f\"\\n⚠️ No se pudieron generar los gráficos por audio: {e}\")\n    \n    # === 7. Análisis de confusión por clase ===\n    # Preparar matriz de confusión simplificada: para cada clase, ¿cuántas veces se confunde con otra?\n    confusion = {}\n    \n    # Inicializar contadores de confusión\n    for bird in all_birds:\n        confusion[bird] = {\"TP\": 0}  # Inicializamos con verdaderos positivos\n    \n    # Analizar cada predicción\n    for idx, row in predictions_df.iterrows():\n        true_birds = set(row['birds'].split())\n        pred_birds = set(row['prediction'].split())\n        \n        # Para cada ave real, ver qué se predijo erróneamente\n        for true_bird in true_birds:\n            if true_bird in pred_birds:\n                confusion[true_bird][\"TP\"] += 1\n            else:\n                # Si el ave real no fue predicha, anotamos qué se predijo en su lugar\n                for wrong_bird in pred_birds:\n                    if wrong_bird not in true_birds:  # Solo las predicciones incorrectas\n                        if wrong_bird not in confusion[true_bird]:\n                            confusion[true_bird][wrong_bird] = 0\n                        confusion[true_bird][wrong_bird] += 1\n    \n    # Crear una tabla de las aves más confundidas (top 15)\n    confusions_list = []\n    \n    for bird, confusions in confusion.items():\n        if \"TP\" not in confusions or confusions[\"TP\"] == 0:\n            continue  # Saltamos aves sin predicciones correctas\n        \n        # Eliminar TP y ordenar el resto por frecuencia\n        del confusions[\"TP\"]\n        if not confusions:\n            continue  # Si no hay confusiones, continuamos\n        \n        top_confusion = sorted(confusions.items(), key=lambda x: x[1], reverse=True)\n        \n        # Tomar solo el ave con la que más se confunde\n        if top_confusion:\n            confused_with, count = top_confusion[0]\n            total_real = species_stats[bird][\"total_true\"]\n            confusions_list.append({\n                \"Especie\": bird,\n                \"Total_real\": total_real,\n                \"Confundida_con\": confused_with,\n                \"Veces_confundida\": count,\n                \"Porcentaje\": count / total_real * 100 if total_real > 0 else 0\n            })\n    \n    # Si hay confusiones, mostrarlas\n    if confusions_list:\n        confusion_df = pd.DataFrame(confusions_list).sort_values(\"Veces_confundida\", ascending=False)\n        confusion_df[\"Porcentaje\"] = confusion_df[\"Porcentaje\"].map(\"{:.2f}%\".format)\n        \n        print(\"\\n🔄 Top confusiones entre especies:\")\n        print(confusion_df.head(15).to_string(index=False))\n        \n        # Guardar resultados\n        confusion_df.to_csv(\"/kaggle/working/confusiones_especies.csv\", index=False)\n        print(\"\\n💾 Análisis de confusiones guardado en 'confusiones_especies.csv'\")\n    \n    # === 8. Resumen final de métricas ===\n    try:\n        metricas_globales = pd.read_csv(\"/kaggle/working/metricas_globales.csv\")\n        \n        print(\"\\n📊 RESUMEN DE MÉTRICAS GLOBALES:\")\n        for col in metricas_globales.columns:\n            if col in [\"TP\", \"FP\", \"FN\", \"TN\"]:\n                print(f\"  - {col}: {metricas_globales[col].values[0]}\")\n            else:\n                print(f\"  - {col}: {metricas_globales[col].values[0]:.4f}\")\n        \n        # Crear un gráfico de radar para las métricas\n        metrics = [\"Precisión\", \"Sensibilidad\", \"F1 Score\", \"Precisión global\", \"Especificidad\"]\n        values = [metricas_globales[m].values[0] for m in metrics]\n        \n        # Crear figura para radar chart\n        plt.figure(figsize=(8, 8))\n        ax = plt.subplot(111, polar=True)\n        \n        # Agregar valores\n        angles = np.linspace(0, 2*np.pi, len(metrics), endpoint=False).tolist()\n        values += values[:1]  # Cerrar el polígono\n        angles += angles[:1]  # Cerrar el polígono\n        \n        # Dibujar las métricas\n        ax.plot(angles, values, 'o-', linewidth=2, color='#3498db')\n        ax.fill(angles, values, alpha=0.25, color='#3498db')\n        \n        # Etiquetas\n        ax.set_thetagrids(np.degrees(angles[:-1]), metrics)\n        ax.set_ylim(0, 1)\n        ax.set_yticks([0.2, 0.4, 0.6, 0.8, 1.0])\n        ax.grid(True)\n        plt.title('Resumen de Métricas Globales', size=15)\n        \n        # Guardar\n        plt.tight_layout()\n        plt.savefig('/kaggle/working/radar_metricas_globales.png', dpi=300, bbox_inches='tight')\n        print(\"\\n💾 Gráfico de radar de métricas guardado como 'radar_metricas_globales.png'\")\n        plt.close()\n        \n    except Exception as e:\n        print(f\"\\n⚠️ No se pudo generar el resumen de métricas globales: {e}\")\n    \n    print(\"\\n✅ Análisis completo finalizado!\")\nelse:\n    print(\"\\n❌ No hay datos de predicciones para analizar. Ejecute primero la Parte A.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T16:03:04.642211Z","iopub.execute_input":"2025-05-11T16:03:04.642573Z","iopub.status.idle":"2025-05-11T16:03:08.320429Z","shell.execute_reply.started":"2025-05-11T16:03:04.642551Z","shell.execute_reply":"2025-05-11T16:03:08.319568Z"},"jupyter":{"outputs_hidden":true,"source_hidden":true},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Para descargar todo el workspace en zip\n\nimport os\nimport zipfile\nfrom IPython.display import FileLink\n\n# Directorio de trabajo de Kaggle\nworking_dir = \"/kaggle/working\"\n\n# Crear un archivo zip con todos los archivos del directorio\nzip_filename = \"todos_los_archivos.zip\"\nzip_path = os.path.join(working_dir, zip_filename)\n\nwith zipfile.ZipFile(zip_path, 'w') as zipf:\n    # Añadir todos los archivos del directorio working al zip\n    for file in os.listdir(working_dir):\n        if file != zip_filename:  # Evitar incluir el propio zip\n            file_path = os.path.join(working_dir, file)\n            if os.path.isfile(file_path):\n                zipf.write(file_path, arcname=file)\n\nprint(f\"✅ Todos los archivos comprimidos en '{zip_filename}'\")\n\n# Crear un enlace de descarga para el archivo zip\ndisplay(FileLink(zip_path))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T16:12:21.621901Z","iopub.execute_input":"2025-05-11T16:12:21.622314Z","iopub.status.idle":"2025-05-11T16:12:21.887258Z","shell.execute_reply.started":"2025-05-11T16:12:21.622287Z","shell.execute_reply":"2025-05-11T16:12:21.886035Z"},"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Para borrar lo que hay en \"/kaggle/working\" excepto dos archivos\n\nimport os\nimport shutil\n\n# Directorio de trabajo de Kaggle\nworking_dir = \"/kaggle/working\"\n\n# Archivos que no se deben eliminar\nexcluir = {\"modelo_efficientnet_rgb_finetuned2.h5\", \"modelo_efficientnet_rgb_finetuned1.h5\"}\n\n# Listar todos los archivos antes de borrarlos\nprint(\"Archivos que serán eliminados:\")\nfor file in os.listdir(working_dir):\n    if file not in excluir:\n        print(f\"- {file}\")\n\n# Confirmar antes de borrar\nconfirm = input(\"¿Estás seguro de que quieres borrar todos los archivos excepto los excluidos? (s/n): \")\n\nif confirm.lower() == 's':\n    # Borrar todos los archivos excepto los excluidos\n    for file in os.listdir(working_dir):\n        if file in excluir:\n            continue\n        file_path = os.path.join(working_dir, file)\n        try:\n            if os.path.isfile(file_path) or os.path.islink(file_path):\n                os.unlink(file_path)\n            elif os.path.isdir(file_path):\n                shutil.rmtree(file_path)\n        except Exception as e:\n            print(f\"Error al eliminar {file_path}: {e}\")\n    \n    print(\"✅ Archivos eliminados, excepto los excluidos.\")\nelse:\n    print(\"❌ Operación cancelada. No se ha eliminado ningún archivo.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T16:34:09.192441Z","iopub.execute_input":"2025-05-11T16:34:09.193488Z","iopub.status.idle":"2025-05-11T16:34:10.190176Z","shell.execute_reply.started":"2025-05-11T16:34:09.193454Z","shell.execute_reply":"2025-05-11T16:34:10.189027Z"},"jupyter":{"source_hidden":true,"outputs_hidden":true},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"---\n\n## Ahora probaremos la versión 9.2, es decir, cargando los pesos del modelo 6 más completo que teníamos. Tmabién descongelamos solo 10 capas.","metadata":{}},{"cell_type":"code","source":"# 2. Fine-Tuning con EfficientNetB0 pre-entrenado, RGB\n\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.layers import Input, GlobalAveragePooling2D, Dense, Dropout\nfrom tensorflow.keras.models import Model, load_model\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.optimizers import Adam\nimport pickle\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom sklearn.metrics import f1_score, precision_score, recall_score\nimport tensorflow as tf\n\n# One-hot encoding de etiquetas\ny_cat = to_categorical(y, num_classes=len(clases))\n\n# División entrenamiento/validación\nX_train, X_val, y_train, y_val = train_test_split(\n    X, y_cat, test_size=0.2, stratify=y, random_state=42)\n\nprint(f\" Datos listos: {X_train.shape[0]} entrenamiento / {X_val.shape[0]} validación\")\n\n# Normalización de imágenes\nX_train = X_train / 255.0\nX_val = X_val / 255.0\n\n# Aumentación de datos (comentada según lo solicitado)\n# data_augmentation = tf.keras.Sequential([\n#     tf.keras.layers.RandomFlip(\"horizontal\"),\n#     tf.keras.layers.RandomRotation(0.1),\n#     tf.keras.layers.RandomZoom(0.1),\n# ])\n\n# Métricas personalizadas para F1 score\ndef f1_metric(y_true, y_pred):\n    # Convierte las probabilidades en etiquetas\n    y_pred_classes = tf.argmax(y_pred, axis=1)\n    y_true_classes = tf.argmax(y_true, axis=1)\n    \n    # Calcula precisión y recall\n    precision = tf.reduce_sum(\n        tf.cast(tf.logical_and(tf.equal(y_true_classes, y_pred_classes), \n                              tf.not_equal(y_true_classes, 0)), tf.float32)\n    ) / (tf.reduce_sum(tf.cast(tf.not_equal(y_pred_classes, 0), tf.float32)) + tf.keras.backend.epsilon())\n    \n    recall = tf.reduce_sum(\n        tf.cast(tf.logical_and(tf.equal(y_true_classes, y_pred_classes), \n                              tf.not_equal(y_true_classes, 0)), tf.float32)\n    ) / (tf.reduce_sum(tf.cast(tf.not_equal(y_true_classes, 0), tf.float32)) + tf.keras.backend.epsilon())\n    \n    # F1 Score\n    f1 = 2 * precision * recall / (precision + recall + tf.keras.backend.epsilon())\n    return f1\n\n# FASE ÚNICA: Cargar modelo pre-entrenado y hacer fine-tuning\nprint(\"Cargando modelo pre-entrenado...\")\n\n# Cargar el modelo existente\nmodel = load_model('/kaggle/input/modelo_normal/tensorflow2/default/1/modelo_efficientnet_rgb.h5', \n                  custom_objects={'f1_metric': f1_metric})\n\n# Obtener el modelo base (EfficientNetB0)\n#for layer in model.layers:\n#    if isinstance(layer, tf.keras.Model):  # Encuentra el modelo base\n#        base_model = layer\n#        break\n\n# Alternativamente, si el código anterior no funciona para extraer base_model\n# Podemos recrear la arquitectura y copiar los pesos\ninput_tensor = Input(shape=(224, 224, 3))\nbase_model = EfficientNetB0(include_top=False, weights='imagenet', input_tensor=input_tensor)\n\nprint(\"Preparando modelo para fine-tuning...\")\n\n# Descongelar las últimas capas (por ejemplo, los últimos 20 bloques)\nbase_model.trainable = True\n\n# Congelar todas las capas excepto las últimas N\nfine_tune_at = len(base_model.layers) - 10  # Ajustar este número según necesites\nfor layer in base_model.layers[:fine_tune_at]:\n    layer.trainable = False\n\n# Verificar qué capas son entrenables\ntrainable_layers = [layer.name for layer in base_model.layers if layer.trainable]\nprint(f\"Capas entrenables: {len(trainable_layers)} de {len(base_model.layers)}\")\nprint(f\"Primeras 5 capas entrenables: {trainable_layers[:5]}\")\n\n# Recompilar el modelo con una tasa de aprendizaje más baja para fine-tuning\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-3),  # Tasa de aprendizaje más baja\n    loss='categorical_crossentropy',\n    metrics=['accuracy', f1_metric]\n)\n\n# Callbacks para la fase de fine-tuning\ncallbacks = [\n    EarlyStopping(monitor='val_f1_metric', patience=7, mode='max', restore_best_weights=True),\n    ReduceLROnPlateau(monitor='val_f1_metric', patience=3, factor=0.2, mode='max'),\n]\n\nprint(\"Iniciando fine-tuning...\")\n# Entrenamiento con fine-tuning\nhistory = model.fit(\n    X_train, y_train,\n    validation_data=(X_val, y_val),\n    epochs=3, \n    batch_size=16,  # Batch size más pequeño para fine-tuning\n    callbacks=callbacks,\n    verbose=1\n)\n\nmodel.save('/kaggle/working/modelo_efficientnet_rgb_finetuned3.h5')\n\n# Evaluar el modelo final\nprint(\"Evaluando modelo final...\")\ntest_loss, test_acc, test_f1 = model.evaluate(X_val, y_val)\nprint(f\"Rendimiento final: Accuracy = {test_acc:.4f}, F1 Score = {test_f1:.4f}\")\n\n# Hacer predicciones y calcular métricas adicionales\ny_pred = model.predict(X_val)\ny_pred_classes = np.argmax(y_pred, axis=1)\ny_true_classes = np.argmax(y_val, axis=1)\n\n# Calcular F1, precisión y recall\nf1 = f1_score(y_true_classes, y_pred_classes, average='macro')\nprecision = precision_score(y_true_classes, y_pred_classes, average='macro')\nrecall = recall_score(y_true_classes, y_pred_classes, average='macro')\n\nprint(f\"Métricas detalladas:\")\nprint(f\"F1 Score: {f1:.4f}\")\nprint(f\"Precision: {precision:.4f}\")\nprint(f\"Recall: {recall:.4f}\")\n\n# Guardar modelo entrenado y su historial\nwith open('/kaggle/working/history_finetuning.pkl', 'wb') as f:\n    pickle.dump(history.history, f)\n\n# Visualizar el entrenamiento\nplt.figure(figsize=(15, 5))\n\nplt.subplot(1, 2, 1)\nplt.plot(history.history['accuracy'])\nplt.plot(history.history['val_accuracy'])\nplt.title('Accuracy del modelo')\nplt.ylabel('Accuracy')\nplt.xlabel('Época')\nplt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\nplt.subplot(1, 2, 2)\nplt.plot(history.history['f1_metric'])\nplt.plot(history.history['val_f1_metric'])\nplt.title('F1 Score del modelo')\nplt.ylabel('F1 Score')\nplt.xlabel('Época')\nplt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\nplt.tight_layout()\nplt.savefig('/kaggle/working/training_metrics_finetuning.png')\nplt.show()\n\nprint(\"✅ Modelo cargado, fine-tuneado y guardado con EfficientNetB0.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-11T18:47:52.182139Z","iopub.execute_input":"2025-05-11T18:47:52.182501Z","iopub.status.idle":"2025-05-11T19:05:14.513007Z","shell.execute_reply.started":"2025-05-11T18:47:52.182477Z","shell.execute_reply":"2025-05-11T19:05:14.511026Z"},"jupyter":{"source_hidden":true,"outputs_hidden":true},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# BORRAR 3. Fine-Tuning con EfficientNetB0 pre-entrenado, RGB\n\nfrom sklearn.model_selection import train_test_split\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.applications import EfficientNetB0\nfrom tensorflow.keras.layers import Input, GlobalAveragePooling2D, Dense, Dropout\nfrom tensorflow.keras.models import Model, load_model\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau, ModelCheckpoint\nfrom tensorflow.keras.optimizers import Adam\nimport pickle\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom sklearn.metrics import f1_score, precision_score, recall_score\nimport tensorflow as tf\n\n# ========== BLOQUE DE PREPARACIÓN DE DATOS ==========\n# IMPORTANTE: Este bloque debe ajustarse según cómo están definidos tus datos\n# Asumiendo que ya tienes los datos cargados previamente en tu notebook/script\n# Si no es así, debes cargarlos explícitamente antes de este punto\n\n# Verificar que X e y existen y tienen las dimensiones correctas\ntry:\n    print(f\"Forma de los datos X: {X.shape}\")\n    print(f\"Forma de las etiquetas y: {y.shape}\")\n    print(f\"Número de clases: {len(clases)}\")\nexcept NameError:\n    print(\"ERROR: Las variables X, y o clases no están definidas.\")\n    print(\"Asegúrate de cargar tus datos antes de ejecutar este script.\")\n    # Descomenta estas líneas y ajusta las rutas si necesitas cargar los datos aquí\n    # X = np.load('/kaggle/path/to/your/data.npy')\n    # y = np.load('/kaggle/path/to/your/labels.npy')\n    # clases = [...] # Define tus clases aquí\n\n# One-hot encoding de etiquetas\ny_cat = to_categorical(y, num_classes=len(clases))\n\n# División entrenamiento/validación\nX_train, X_val, y_train, y_val = train_test_split(\n    X, y_cat, test_size=0.2, stratify=y, random_state=42)\n\nprint(f\"Datos listos: {X_train.shape[0]} entrenamiento / {X_val.shape[0]} validación\")\nprint(f\"Forma de X_train: {X_train.shape}, y_train: {y_train.shape}\")\n\n# Normalización de imágenes\nX_train = X_train / 255.0\nX_val = X_val / 255.0\n\n# Aumentación de datos (comentada según lo solicitado)\n# data_augmentation = tf.keras.Sequential([\n#     tf.keras.layers.RandomFlip(\"horizontal\"),\n#     tf.keras.layers.RandomRotation(0.1),\n#     tf.keras.layers.RandomZoom(0.1),\n# ])\n\n# ========== DEFINICIÓN DE MÉTRICAS Y FUNCIONES AUXILIARES ==========\n# Métricas personalizadas para F1 score\ndef f1_metric(y_true, y_pred):\n    # Convierte las probabilidades en etiquetas\n    y_pred_classes = tf.argmax(y_pred, axis=1)\n    y_true_classes = tf.argmax(y_true, axis=1)\n    \n    # Calcula precisión y recall\n    precision = tf.reduce_sum(\n        tf.cast(tf.logical_and(tf.equal(y_true_classes, y_pred_classes), \n                              tf.not_equal(y_true_classes, 0)), tf.float32)\n    ) / (tf.reduce_sum(tf.cast(tf.not_equal(y_pred_classes, 0), tf.float32)) + tf.keras.backend.epsilon())\n    \n    recall = tf.reduce_sum(\n        tf.cast(tf.logical_and(tf.equal(y_true_classes, y_pred_classes), \n                              tf.not_equal(y_true_classes, 0)), tf.float32)\n    ) / (tf.reduce_sum(tf.cast(tf.not_equal(y_true_classes, 0), tf.float32)) + tf.keras.backend.epsilon())\n    \n    # F1 Score\n    f1 = 2 * precision * recall / (precision + recall + tf.keras.backend.epsilon())\n    return f1\n\n# ========== CARGA DEL MODELO PRE-ENTRENADO ==========\nprint(\"Cargando modelo pre-entrenado...\")\ntry:\n    # Intentar cargar el modelo con métricas personalizadas\n    model = load_model('/kaggle/input/modelo_normal/tensorflow2/default/1/modelo_efficientnet_rgb.h5', \n                    custom_objects={'f1_metric': f1_metric})\n    print(\"Modelo cargado exitosamente.\")\n    \n    # Intentar obtener el modelo base\n    base_model = None\n    for layer in model.layers:\n        if isinstance(layer, tf.keras.Model):  # Encuentra el modelo base\n            base_model = layer\n            print(\"Modelo base encontrado dentro del modelo cargado.\")\n            break\n    \n    if base_model is None:\n        print(\"No se pudo encontrar el modelo base automáticamente.\")\n        print(\"Creando un nuevo modelo base EfficientNetB0...\")\n        # Si no podemos extraer el modelo base, creamos uno nuevo\n        input_shape = model.input_shape[1:] # Obtener shape de entrada del modelo cargado\n        input_tensor = Input(shape=input_shape)\n        base_model = EfficientNetB0(include_top=False, weights='imagenet', input_tensor=input_tensor)\n        print(f\"Modelo base creado con shape de entrada: {input_shape}\")\n        \nexcept Exception as e:\n    print(f\"Error al cargar el modelo: {e}\")\n    print(\"Creando un nuevo modelo desde cero...\")\n    \n    # Crear modelo desde cero si no se puede cargar\n    input_tensor = Input(shape=(224, 224, 3))\n    base_model = EfficientNetB0(include_top=False, weights='imagenet', input_tensor=input_tensor)\n    x = base_model.output\n    x = GlobalAveragePooling2D()(x)\n    x = Dropout(0.2)(x)\n    output = Dense(len(clases), activation='softmax')(x)\n    model = Model(inputs=base_model.input, outputs=output)\n    print(\"Nuevo modelo creado con éxito.\")\n\n# ========== CONFIGURACIÓN DEL FINE-TUNING ==========\nprint(\"Preparando modelo para fine-tuning...\")\n\n# Descongelar las últimas capas\nbase_model.trainable = True\n\n# Congelar todas las capas excepto las últimas N\nfine_tune_at = len(base_model.layers) - 20  # Ajustar este número según necesites\nfor layer in base_model.layers[:fine_tune_at]:\n    layer.trainable = False\n\n# Verificar qué capas son entrenables\ntrainable_layers = [layer.name for layer in base_model.layers if layer.trainable]\nprint(f\"Capas entrenables: {len(trainable_layers)} de {len(base_model.layers)}\")\nif trainable_layers:\n    print(f\"Primeras 5 capas entrenables (o menos): {trainable_layers[:min(5, len(trainable_layers))]}\")\n\n# Recompilar el modelo con una tasa de aprendizaje más baja para fine-tuning\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-4),  # Tasa de aprendizaje más baja\n    loss='categorical_crossentropy',\n    metrics=['accuracy', f1_metric]\n)\n\n# ========== ENTRENAMIENTO (FINE-TUNING) ==========\n# Callbacks para la fase de fine-tuning\ncallbacks = [\n    EarlyStopping(monitor='val_f1_metric', patience=7, mode='max', restore_best_weights=True),\n    ReduceLROnPlateau(monitor='val_f1_metric', patience=3, factor=0.2, mode='max'),\n    ModelCheckpoint('/kaggle/working/modelo_efficientnet_rgb_finetuned_best.h5', \n                    monitor='val_f1_metric', mode='max', save_best_only=True)\n]\n\nprint(\"Iniciando fine-tuning...\")\n# Entrenamiento con fine-tuning\ntry:\n    history = model.fit(\n        X_train, y_train,\n        validation_data=(X_val, y_val),\n        epochs=10,  # Ajustar según necesidad\n        batch_size=16,  # Batch size más pequeño para fine-tuning\n        callbacks=callbacks,\n        verbose=1\n    )\n    print(\"Fine-tuning completado con éxito.\")\n    model.save('/kaggle/working/modelo_efficientnet_rgb_finetuned.h5')\nexcept Exception as e:\n    print(f\"Error durante el entrenamiento: {e}\")\n    print(\"Revisa las dimensiones de tus datos y la configuración del modelo.\")\n    # Si hay error, imprimir formas para debug\n    print(f\"Forma de X_train: {X_train.shape}, y_train: {y_train.shape}\")\n    print(f\"Forma esperada de entrada al modelo: {model.input_shape}\")\n    print(f\"Forma esperada de salida del modelo: {model.output_shape}\")\n\n# ========== EVALUACIÓN DEL MODELO ==========\ntry:\n    print(\"Evaluando modelo final...\")\n    # Cargar el mejor modelo guardado durante el entrenamiento\n    model = load_model('/kaggle/working/modelo_efficientnet_rgb_finetuned_best.h5', \n                      custom_objects={'f1_metric': f1_metric})\n    \n    test_loss, test_acc, test_f1 = model.evaluate(X_val, y_val)\n    print(f\"Rendimiento final: Accuracy = {test_acc:.4f}, F1 Score = {test_f1:.4f}\")\n\n    # Hacer predicciones y calcular métricas adicionales\n    y_pred = model.predict(X_val)\n    y_pred_classes = np.argmax(y_pred, axis=1)\n    y_true_classes = np.argmax(y_val, axis=1)\n\n    # Calcular F1, precisión y recall\n    f1 = f1_score(y_true_classes, y_pred_classes, average='macro')\n    precision = precision_score(y_true_classes, y_pred_classes, average='macro')\n    recall = recall_score(y_true_classes, y_pred_classes, average='macro')\n\n    print(f\"Métricas detalladas:\")\n    print(f\"F1 Score: {f1:.4f}\")\n    print(f\"Precision: {precision:.4f}\")\n    print(f\"Recall: {recall:.4f}\")\n\n    # Guardar historial\n    with open('/kaggle/working/history_finetuning.pkl', 'wb') as f:\n        pickle.dump(history.history, f)\n\n    # Visualizar el entrenamiento\n    plt.figure(figsize=(15, 5))\n\n    plt.subplot(1, 2, 1)\n    plt.plot(history.history['accuracy'])\n    plt.plot(history.history['val_accuracy'])\n    plt.title('Accuracy del modelo')\n    plt.ylabel('Accuracy')\n    plt.xlabel('Época')\n    plt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\n    plt.subplot(1, 2, 2)\n    plt.plot(history.history['f1_metric'])\n    plt.plot(history.history['val_f1_metric'])\n    plt.title('F1 Score del modelo')\n    plt.ylabel('F1 Score')\n    plt.xlabel('Época')\n    plt.legend(['Entrenamiento', 'Validación'], loc='lower right')\n\n    plt.tight_layout()\n    plt.savefig('/kaggle/working/training_metrics_finetuning.png')\n    plt.show()\n    \nexcept Exception as e:\n    print(f\"Error durante la evaluación: {e}\")\n\nprint(\"✅ Proceso completado: modelo cargado, fine-tuneado y evaluado.\")","metadata":{"trusted":true,"jupyter":{"outputs_hidden":true,"source_hidden":true},"execution":{"iopub.status.busy":"2025-05-11T17:02:12.825101Z","iopub.execute_input":"2025-05-11T17:02:12.825443Z","iopub.status.idle":"2025-05-11T17:02:12.868533Z","shell.execute_reply.started":"2025-05-11T17:02:12.825420Z","shell.execute_reply":"2025-05-11T17:02:12.867223Z"},"collapsed":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}