{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":116062,"databundleVersionId":14084779,"sourceType":"competition"},{"sourceId":269639663,"sourceType":"kernelVersion"}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# ==========================================\n# 🚀 CONFIGURACIÓN DEL ENTORNO DE EJECUCIÓN\n# ==========================================\n\n- Qué hace esta configuración\n\n- Controla versiones para garantizar reproducibilidad.\n\n- Activa CUDA tanto en PyTorch como TensorFlow (por si el modelo de embeddings usa uno u otro).\n\n- Establece semillas aleatorias para resultados deterministas.\n\n- Silencia warnings para mantener limpio el notebook.\n\n- Verifica automáticamente GPU y dependencias, útil para depurar notebooks en Kaggle, Colab o servidores HPC.","metadata":{}},{"cell_type":"code","source":"!pip install -q torch torchvision torchaudio \\\n                tensorflow==2.17.0 \\\n                transformers==4.44.0 \\\n                sentence-transformers==3.1.0 \\\n                scikit-learn seaborn plotly tqdm pandas numpy \\\n                Bio \\\n                goatools\n\nimport torch\nimport tensorflow as tf\nimport platform\nimport os\n\nimport warnings\nwarnings.simplefilter(action='ignore', category=FutureWarning)\nwarnings.filterwarnings(\"ignore\")\n\nimport logging\nlogging.getLogger(\"transformers.modeling_utils\").setLevel(logging.ERROR)\n\nprint(f\"🧠 Entorno: {platform.platform()}\")\nprint(f\"📦 Torch CUDA disponible: {torch.cuda.is_available()}\")\nif torch.cuda.is_available():\n    gpu = torch.cuda.get_device_name(0)\n    print(f\"   ➤ GPU: {gpu}\")\n\n# print(f\"📦 TensorFlow detecta GPU: {len(tf.config.list_physical_devices('GPU')) > 0}\")\n\n# Desactivar paralelismo de tokenizers\nos.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"\nos.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"\n\n# ✔ Forma correcta de mostrar la variable de entorno\nprint(\"TOKENIZERS_PARALLELISM:\", os.environ.get(\"TOKENIZERS_PARALLELISM\"))\nprint(\"PYTORCH_CUDA_ALLOC_CONF:\", os.environ.get(\"PYTORCH_CUDA_ALLOC_CONF\"))\n\nprint(\"✅ Entorno Kaggle listo para EDA, embeddings y modelado con GPU P100.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:37.519462Z","iopub.execute_input":"2025-11-17T12:49:37.520094Z","iopub.status.idle":"2025-11-17T12:49:48.821092Z","shell.execute_reply.started":"2025-11-17T12:49:37.520059Z","shell.execute_reply":"2025-11-17T12:49:48.819980Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧩 AJUSTE DE DEPENDENCIAS Y ENTORNO CUDA","metadata":{}},{"cell_type":"code","source":"!nvidia-smi","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:48.822647Z","iopub.execute_input":"2025-11-17T12:49:48.823196Z","iopub.status.idle":"2025-11-17T12:49:49.022243Z","shell.execute_reply.started":"2025-11-17T12:49:48.823159Z","shell.execute_reply":"2025-11-17T12:49:49.021214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==================================================\n# 🧩 CELDA DE LIBRERÍAS COMPLETA – EDA + EMBEDDINGS\n# ==================================================\n\n# -----------------------------\n# Manipulación y análisis\n# -----------------------------\nimport pandas as pd\nimport numpy as np\nfrom Bio import SeqIO\nimport os\n\n# -----------------------------\n# Visualización\n# -----------------------------\nimport seaborn as sns\nimport matplotlib.pyplot as plt\nimport plotly.express as px\n\nsns.set(style=\"whitegrid\", palette=\"pastel\", font_scale=1.1)\n\n# -----------------------------\n# PyTorch y embeddings\n# -----------------------------\nimport torch\nfrom sentence_transformers import SentenceTransformer\nfrom transformers import AutoTokenizer, AutoModel\n\n# -----------------------------\n# Preprocesamiento y métricas\n# -----------------------------\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.metrics.pairwise import cosine_similarity\nfrom tqdm.autonotebook import tqdm, trange\n\n# -----------------------------\n# Guardado y exportación\n# -----------------------------\nimport pickle\nimport pyarrow.parquet as pq\n\n# -----------------------------\n# Información GPU\n# -----------------------------\nprint(\"🧠 Entorno:\", platform.platform())\nprint(\"📦 Torch CUDA disponible:\", torch.cuda.is_available())\nif torch.cuda.is_available():\n    print(\"   ➤ GPU:\", torch.cuda.get_device_name(0))\n\n\nfrom tqdm.notebook import tqdm\n\nprint(\"\\n✅ Librerías cargadas y listas para EDA + embeddings en GPU P100\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:49.023469Z","iopub.execute_input":"2025-11-17T12:49:49.023814Z","iopub.status.idle":"2025-11-17T12:49:50.951202Z","shell.execute_reply.started":"2025-11-17T12:49:49.023776Z","shell.execute_reply":"2025-11-17T12:49:50.950294Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# ==========================================\n# 🚀PREPARACION DE LOS DATOS PARA EDA\n# ==========================================\n\n\n🔧 Definición de rutas y carga de datos (train.fasta, test.fasta, train_terms.tsv, train_taxonomy.tsv, etc.)\n\n🧬 Lectura de secuencias con BioPython (SeqIO)\n\n🧹 Limpieza básica (duplicados, vacíos, normalización de IDs)\n\n📊 Preparación de un DataFrame estructurado con la información combinada para el EDA.","metadata":{}},{"cell_type":"markdown","source":"## 📂 CARGA DE DATOS Y DEFINICIÓN DE RUTAS","metadata":{}},{"cell_type":"code","source":"# Archivos esperados\nFILES = {\n    \"train_fasta\": \"/kaggle/input/cafa-6-protein-function-prediction/Test/testsuperset.fasta\",\n    \"test_fasta\":  \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_sequences.fasta\",\n    \"train_terms\": \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_terms.tsv\",\n    \"taxonomy\":    \"/kaggle/input/cafa-6-protein-function-prediction/Train/train_taxonomy.tsv\",\n    \"go_ontology\": \"/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo\"\n}\n\nprint(\"📁 Archivos detectados:\")\nfor name, path in FILES.items():\n    print(f\" - {name:<15}: {'✅' if os.path.exists(path) else '❌'} {path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:50.952023Z","iopub.execute_input":"2025-11-17T12:49:50.952526Z","iopub.status.idle":"2025-11-17T12:49:50.970031Z","shell.execute_reply.started":"2025-11-17T12:49:50.952503Z","shell.execute_reply":"2025-11-17T12:49:50.969229Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧬 LECTURA DE SECUENCIAS Y ANOTACIONES","metadata":{}},{"cell_type":"code","source":"# ==========================================\n# 🧬 LECTURA DE SECUENCIAS Y ANOTACIONES\n# ==========================================\n\n# Cargar secuencias de entrenamiento\ntrain_sequences = {}\nfor record in SeqIO.parse(FILES[\"train_fasta\"], \"fasta\"):\n    train_sequences[record.id] = str(record.seq)\n\n# Cargar secuencias de test\ntest_sequences = {}\nfor record in SeqIO.parse(FILES[\"test_fasta\"], \"fasta\"):\n    test_sequences[record.id] = str(record.seq)\n\n# Cargar términos GO y taxonomía\ntrain_terms = pd.read_csv(FILES[\"train_terms\"], sep=\"\\t\")\ntrain_taxonomy = pd.read_csv(FILES[\"taxonomy\"], sep=\"\\t\")\n\nprint(f\"✅ Secuencias de entrenamiento: {len(train_sequences):,}\")\nprint(f\"✅ Secuencias de test: {len(test_sequences):,}\")\nprint(f\"✅ Anotaciones GO: {train_terms.shape}\")\nprint(f\"✅ Taxonomía: {train_taxonomy.shape}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:50.972146Z","iopub.execute_input":"2025-11-17T12:49:50.972556Z","iopub.status.idle":"2025-11-17T12:49:52.616552Z","shell.execute_reply.started":"2025-11-17T12:49:50.972538Z","shell.execute_reply":"2025-11-17T12:49:52.615670Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧹 LIMPIEZA Y UNIFICACIÓN DE DATOS","metadata":{}},{"cell_type":"code","source":"# Convertir diccionario de secuencias a DataFrame\ndf_train = pd.DataFrame({\n    \"EntryID\": list(train_sequences.keys()),\n    \"Sequence\": list(train_sequences.values())\n})\n\n# Fusionar con anotaciones GO\ndf_train = df_train.merge(train_terms, on=\"EntryID\", how=\"left\")\n\n# Añadir taxonomía (si está disponible)\nif \"TaxID\" in train_taxonomy.columns:\n    df_train = df_train.merge(train_taxonomy, on=\"TaxID\", how=\"left\")\n\n# Eliminar duplicados y secuencias vacías\ndf_train.drop_duplicates(subset=[\"EntryID\"], inplace=True)\ndf_train = df_train[df_train[\"Sequence\"].str.len() > 0]\n\nprint(f\"📊 Dataset final: {df_train.shape[0]:,} secuencias válidas\")\ndf_train.head(3)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:52.617304Z","iopub.execute_input":"2025-11-17T12:49:52.617542Z","iopub.status.idle":"2025-11-17T12:49:53.117818Z","shell.execute_reply.started":"2025-11-17T12:49:52.617524Z","shell.execute_reply":"2025-11-17T12:49:53.117086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Guardar dataset procesado para el EDA\noutput_path = \"/kaggle/working/train_dataset_clean.csv\"\ndf_train.to_csv(output_path, index=False)\nprint(f\"💾 Dataset guardado en: {output_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:53.118591Z","iopub.execute_input":"2025-11-17T12:49:53.118783Z","iopub.status.idle":"2025-11-17T12:49:55.903574Z","shell.execute_reply.started":"2025-11-17T12:49:53.118768Z","shell.execute_reply":"2025-11-17T12:49:55.902522Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# ============================================================\n# 📊 ANÁLISIS EXPLORATORIO DE DATOS (EDA)\n# ============================================================\n\nEste bloque está estructurado para que puedas detectar problemas de calidad de datos, sesgos y posibles errores de modelado antes de pasar a generar embeddings o entrenar redes neuronales.","metadata":{}},{"cell_type":"markdown","source":"## Cargar el dataset","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv('//kaggle/working/train_dataset_clean.csv')  # ⚠️ reemplaza con tu archivo real\nprint(\"✅ Dataset cargado correctamente.\")\nprint(f\"📏 Dimensiones: {df.shape[0]} filas x {df.shape[1]} columnas\\n\")\n\n# Vista general del dataset\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:55.904462Z","iopub.execute_input":"2025-11-17T12:49:55.904767Z","iopub.status.idle":"2025-11-17T12:49:57.124395Z","shell.execute_reply.started":"2025-11-17T12:49:55.904740Z","shell.execute_reply":"2025-11-17T12:49:57.123427Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🧱 Información general","metadata":{}},{"cell_type":"code","source":"print(\"🔎 Información general del dataset:\")\ndf.info()\nprint(\"\\n🔢 Tipos de datos:\\n\", df.dtypes.value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:57.125296Z","iopub.execute_input":"2025-11-17T12:49:57.125609Z","iopub.status.idle":"2025-11-17T12:49:57.190811Z","shell.execute_reply.started":"2025-11-17T12:49:57.125588Z","shell.execute_reply":"2025-11-17T12:49:57.189901Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🧮 Estadísticas descriptivas","metadata":{}},{"cell_type":"code","source":"print(\"\\n📈 Estadísticas descriptivas numéricas:\")\ndisplay(df.describe().T)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:57.191694Z","iopub.execute_input":"2025-11-17T12:49:57.192397Z","iopub.status.idle":"2025-11-17T12:49:57.596909Z","shell.execute_reply.started":"2025-11-17T12:49:57.192369Z","shell.execute_reply":"2025-11-17T12:49:57.596021Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🔍 Valores nulos y duplicados","metadata":{"execution":{"iopub.status.busy":"2025-11-09T19:29:51.235728Z","iopub.execute_input":"2025-11-09T19:29:51.236371Z","iopub.status.idle":"2025-11-09T19:29:51.239671Z","shell.execute_reply.started":"2025-11-09T19:29:51.236349Z","shell.execute_reply":"2025-11-09T19:29:51.239052Z"}}},{"cell_type":"code","source":"print(\"\\n🚫 Valores nulos por columna:\")\ndisplay(df.isnull().sum().sort_values(ascending=False))\n\nprint(f\"\\n📀 Registros duplicados: {df.duplicated().sum()}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:57.597770Z","iopub.execute_input":"2025-11-17T12:49:57.598020Z","iopub.status.idle":"2025-11-17T12:49:57.952276Z","shell.execute_reply.started":"2025-11-17T12:49:57.598004Z","shell.execute_reply":"2025-11-17T12:49:57.951392Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Vista general del dataset\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:57.953017Z","iopub.execute_input":"2025-11-17T12:49:57.953214Z","iopub.status.idle":"2025-11-17T12:49:57.961366Z","shell.execute_reply.started":"2025-11-17T12:49:57.953198Z","shell.execute_reply":"2025-11-17T12:49:57.960727Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### ⚖️ Distribución de variables categóricas","metadata":{"execution":{"iopub.status.busy":"2025-11-09T19:30:45.529629Z","iopub.execute_input":"2025-11-09T19:30:45.530192Z","iopub.status.idle":"2025-11-09T19:30:45.533216Z","shell.execute_reply.started":"2025-11-09T19:30:45.530173Z","shell.execute_reply":"2025-11-09T19:30:45.532591Z"}}},{"cell_type":"code","source":"\n\nsns.set(style=\"whitegrid\", palette=\"pastel\", font_scale=0.65)\n\ncat_cols = [col for col in df.columns if df[col].dtype == 'object']\n\n# Carpeta para guardar datos de gráficos\noutput_dir = \"/kaggle/working/EDA\"\nos.makedirs(output_dir, exist_ok=True)\n\nif cat_cols:\n    print(\"\\n🧩 Variables categóricas encontradas:\", cat_cols)\n    print(f\"📊 Generando gráficos para {len(cat_cols[:5])} primeras columnas...\\n\")\n    \n    for col in tqdm(cat_cols[:5], desc=\"Procesando columnas categóricas\", colour=\"green\"):\n        # Contar valores y limitar top 50 para acelerar\n        counts = df[col].value_counts().nlargest(50)\n        \n        # Guardar los datos en un CSV\n        counts.to_csv(os.path.join(output_dir, f\"{col}_counts.csv\"), header=True)\n        \n        # Generar gráfico\n        plt.figure(figsize=(10,6))\n        sns.barplot(y=counts.index, x=counts.values, palette=\"viridis\")\n        plt.title(f\"Distribución de {col}\")\n        plt.xlabel(\"Frecuencia\")\n        plt.ylabel(col)\n        \n        # Ajustar márgenes manualmente para evitar warnings\n        plt.subplots_adjust(left=0.25, right=0.95, top=0.9, bottom=0.1)\n        plt.show()\nelse:\n    print(\"\\n⚠️ No se encontraron columnas categóricas.\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:49:57.962201Z","iopub.execute_input":"2025-11-17T12:49:57.962430Z","iopub.status.idle":"2025-11-17T12:50:00.231697Z","shell.execute_reply.started":"2025-11-17T12:49:57.962415Z","shell.execute_reply":"2025-11-17T12:50:00.230856Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🔢 Distribución de variables numéricas","metadata":{}},{"cell_type":"code","source":"\nimport pandas as pd\nimport numpy as np\n\nsns.set(style=\"whitegrid\", palette=\"pastel\", font_scale=0.65)\n\noutput_dir = \"/kaggle/working/EDA/eda_results\"\nos.makedirs(output_dir, exist_ok=True)\n\n# ============================================================\n# 🔢 Crear columnas numéricas a partir de secuencias\n# ============================================================\nif 'Sequence' in df.columns:\n    print(\"🧬 Generando columnas numéricas a partir de secuencias...\")\n    # Longitud de la secuencia\n    df['seq_length'] = df['Sequence'].apply(len)\n    \n    # Conteo de aminoácidos (solo los 20 estándar)\n    amino_acids = list(\"ACDEFGHIKLMNPQRSTVWY\")\n    for aa in amino_acids:\n        df[f\"count_{aa}\"] = df['Sequence'].apply(lambda x: x.count(aa))\nelse:\n    print(\"⚠️ No se encontró columna 'Sequence' para procesar.\")\n\n# ============================================================\n# 🔹 Columnas categóricas\n# ============================================================\ncat_cols = [col for col in df.columns if df[col].dtype == 'object']\n\nif cat_cols:\n    print(\"\\n🧩 Variables categóricas encontradas:\", cat_cols)\n    for col in tqdm(cat_cols[:5], desc=\"Procesando columnas categóricas\", colour=\"green\"):\n        counts = df[col].value_counts().nlargest(50)\n        counts.to_csv(os.path.join(output_dir, f\"{col}_counts.csv\"), header=True)\n        \n        plt.figure(figsize=(10,6))\n        sns.barplot(y=counts.index, x=counts.values, palette=\"viridis\")\n        plt.title(f\"Distribución de {col}\")\n        plt.xlabel(\"Frecuencia\")\n        plt.ylabel(col)\n        plt.subplots_adjust(left=0.25, right=0.95, top=0.9, bottom=0.1)\n        plt.show()\nelse:\n    print(\"\\n⚠️ No se encontraron columnas categóricas.\")\n\n# ============================================================\n# 🔹 Columnas numéricas\n# ============================================================\nnum_cols = df.select_dtypes(include=np.number).columns.tolist()\nif num_cols:\n    print(\"\\n📊 Estadísticas de variables numéricas:\")\n    df[num_cols].describe().to_csv(os.path.join(output_dir, \"numerical_stats.csv\"))\n    \n    df[num_cols].hist(bins=30, figsize=(15,10), color=\"#008b8b\")\n    plt.suptitle(\"Distribución de variables numéricas\", fontsize=14)\n    plt.show()\n    \n    if len(num_cols) >= 2:\n        corr = df[num_cols].corr()\n        plt.figure(figsize=(10,8))\n        sns.heatmap(corr, annot=True, fmt=\".2f\", cmap=\"coolwarm\", square=True)\n        plt.title(\"Matriz de correlación\")\n        plt.show()\n        corr.to_csv(os.path.join(output_dir, \"correlation_matrix.csv\"))\nelse:\n    print(\"\\n⚠️ No se encontraron columnas numéricas.\")\n\n# ============================================================\n# 🎯 Variable objetivo\n# ============================================================\ntarget_candidates = [col for col in df.columns if 'target' in col.lower() or 'label' in col.lower() or 'y' == col.lower()]\nif target_candidates:\n    target_col = target_candidates[0]\n    print(f\"\\n🎯 Variable objetivo detectada automáticamente: {target_col}\")\n    counts = df[target_col].value_counts()\n    counts.to_csv(os.path.join(output_dir, f\"{target_col}_class_counts.csv\"))\n    \n    counts.plot(kind='bar', color='#4682B4')\n    plt.title(\"Distribución de clases\")\n    plt.xlabel(\"Clase\")\n    plt.ylabel(\"Frecuencia\")\n    plt.tight_layout()\n    plt.show()\nelse:\n    print(\"\\n⚠️ No se detectó automáticamente una variable objetivo (target).\")\n\nprint(\"\\n✅ EDA profesional completado. Todos los resultados guardados en:\", output_dir)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:50:00.236260Z","iopub.execute_input":"2025-11-17T12:50:00.236497Z","iopub.status.idle":"2025-11-17T12:50:11.767610Z","shell.execute_reply.started":"2025-11-17T12:50:00.236481Z","shell.execute_reply":"2025-11-17T12:50:11.766708Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Verificar dónde están los NaN","metadata":{}},{"cell_type":"code","source":"df.isna().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:50:11.768491Z","iopub.execute_input":"2025-11-17T12:50:11.768724Z","iopub.status.idle":"2025-11-17T12:50:11.834977Z","shell.execute_reply.started":"2025-11-17T12:50:11.768707Z","shell.execute_reply":"2025-11-17T12:50:11.834312Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Arreglar NaNs ANTES de separar rare / normal","metadata":{}},{"cell_type":"code","source":"# ======================================================\n# 🧼 Limpieza de NaNs antes de procesar clases raras\n# ======================================================\n\n# Eliminar filas donde falte term o Sequence (no sirven para entrenar)\ndf = df.dropna(subset=[\"term\", \"Sequence\"]).reset_index(drop=True)\n\n# Para el resto de columnas numéricas, rellenar con 0 o la media\nnum_cols = df.select_dtypes(include=[\"float64\", \"int64\"]).columns\ndf[num_cols] = df[num_cols].fillna(0)\n\nprint(\"✅ NaNs eliminados o rellenados\")\nprint(df.isna().sum().sum(), \"NaNs restantes\")\n\n\n# Vista general del dataset\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:50:11.835732Z","iopub.execute_input":"2025-11-17T12:50:11.835966Z","iopub.status.idle":"2025-11-17T12:50:11.982383Z","shell.execute_reply.started":"2025-11-17T12:50:11.835950Z","shell.execute_reply":"2025-11-17T12:50:11.981586Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======================================================\n# 🧪 Estrategia híbrida para clases raras (1 ejemplo)\n# ======================================================\n\nlabel_col = \"term\"\n\n# Contar frecuencia de clases\nvalue_counts = df[label_col].value_counts()\n\n# Identificar clases con 1 sola muestra\nrare_classes = value_counts[value_counts == 1].index.tolist()\nprint(f\"🔍 Clases con solo 1 muestra: {len(rare_classes)}\")\n\n# Separar dataset en:\n# - normal_data → clases con 2 o más muestras\n# - rare_data   → clases ultra-raras\nrare_data = df[df[label_col].isin(rare_classes)].copy()\nnormal_data = df[~df[label_col].isin(rare_classes)].copy()\n\nprint(f\"📦 normal_data: {normal_data.shape[0]:,} filas\")\nprint(f\"📦 rare_data:   {rare_data.shape[0]:,} filas (no se envían a test)\")\n\n# ======================================================\n# 📌 Oversampling suave para rare_data\n# (para que el modelo vea la clase más de 1 vez)\n# ======================================================\n\nOVERSAMPLE_FACTOR = 3  # no exageramos\n\nif not rare_data.empty:\n    rare_data_oversampled = pd.concat([rare_data] * OVERSAMPLE_FACTOR, ignore_index=True)\nelse:\n    rare_data_oversampled = rare_data\n\nprint(f\"📈 rare_data tras oversampling: {rare_data_oversampled.shape[0]:,}\")\n\n# ======================================================\n# 🔀 Train/Test split SOLO con clases seguras\n# ======================================================\n\nfrom sklearn.model_selection import train_test_split\n\ntrain_normal, test_normal = train_test_split(\n    normal_data,\n    test_size=0.2,\n    stratify=normal_data[label_col],\n    random_state=42\n)\n\nprint(f\"\\n📊 normal_train: {train_normal.shape[0]:,} | normal_test: {test_normal.shape[0]:,}\")\n\n# ======================================================\n# 🔗 Integrar datos raros SOLO en entrenamiento\n# ======================================================\n\ndf_train = pd.concat([train_normal, rare_data_oversampled], ignore_index=True)\ndf_test = test_normal  # test nunca debe contener clases con 1 ejemplo\n\nprint(\"\\n==============================\")\nprint(\"📌 Dataset final preparado\")\nprint(f\"Entrenamiento total: {df_train.shape[0]:,}\")\nprint(f\"Test total:         {df_test.shape[0]:,}\")\nprint(\"==============================\")\n\n# ======================================================\n# 🔁 Extraer secuencias y etiquetas\n# ======================================================\n\ntrain_sequences = df_train[\"Sequence\"].tolist()\ntest_sequences  = df_test[\"Sequence\"].tolist()\n\ntrain_labels = df_train[label_col].tolist()\ntest_labels  = df_test[label_col].tolist()\n\nprint(\"✅ Secuencias listas para tokenización.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:50:11.983297Z","iopub.execute_input":"2025-11-17T12:50:11.983542Z","iopub.status.idle":"2025-11-17T12:50:12.208158Z","shell.execute_reply.started":"2025-11-17T12:50:11.983525Z","shell.execute_reply":"2025-11-17T12:50:12.207246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from transformers import AutoTokenizer, AutoModel\nfrom sklearn.preprocessing import LabelEncoder\nimport torch\nfrom torch.utils.data import TensorDataset, DataLoader\n\n# ======================================================\n# 🏷️ Codificar etiquetas\n# ======================================================\n\nlabel_encoder = LabelEncoder()\nlabel_encoder.fit(train_labels)\n\ny_train = label_encoder.transform(train_labels)\ny_test  = label_encoder.transform(test_labels)\n\nnum_classes = len(label_encoder.classes_)\nprint(f\"🔢 Número total de clases: {num_classes}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:50:12.209129Z","iopub.execute_input":"2025-11-17T12:50:12.209804Z","iopub.status.idle":"2025-11-17T12:50:12.304948Z","shell.execute_reply.started":"2025-11-17T12:50:12.209778Z","shell.execute_reply":"2025-11-17T12:50:12.304109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======================================================\n# 🔠 Tokenizer\n# ======================================================\nwarnings.filterwarnings(\"ignore\")\n\nMODEL_NAME = \"facebook/esm2_t12_35M_UR50D\"  # o el que estés usando\ntokenizer = AutoTokenizer.from_pretrained(\n    MODEL_NAME,\n    use_fast=False,                     # ESM usa tokenizer lento pero seguro\n    clean_up_tokenization_spaces=True   # evita el FutureWarning\n    )\n\nmodel = AutoModel.from_pretrained(\n    MODEL_NAME,\n    trust_remote_code=True\n    ).cuda()# usar GPU\n\nMAX_LEN = 256  # ajusta según tu GPU\n\n# Tokenizar secuencias\ntrain_enc = tokenizer(\n    train_sequences,\n    padding=True,\n    truncation=True,\n    max_length=MAX_LEN,\n    return_tensors=\"pt\"\n)\n\ntest_enc = tokenizer(\n    test_sequences,\n    padding=True,\n    truncation=True,\n    max_length=MAX_LEN,\n    return_tensors=\"pt\"\n)\nprint(\"📦 train_enc shape:\", {k: v.shape for k, v in train_enc.items()})\nprint(\"📦 test_enc shape:\", {k: v.shape for k, v in test_enc.items()})\nprint(f\"train_enc: {train_enc}\")\nprint(f\"test_enc: {test_enc}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:50:12.305806Z","iopub.execute_input":"2025-11-17T12:50:12.306159Z","iopub.status.idle":"2025-11-17T12:51:30.148969Z","shell.execute_reply.started":"2025-11-17T12:50:12.306140Z","shell.execute_reply":"2025-11-17T12:51:30.148075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======================================================\n# 🔧 Crear tensores de PyTorch\n# ======================================================\n\nX_train_input_ids = train_enc[\"input_ids\"]\nX_train_attention = train_enc[\"attention_mask\"]\n\nX_test_input_ids = test_enc[\"input_ids\"]\nX_test_attention = test_enc[\"attention_mask\"]\n\ny_train_tensor = torch.tensor(y_train, dtype=torch.long)\ny_test_tensor = torch.tensor(y_test, dtype=torch.long)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:30.149951Z","iopub.execute_input":"2025-11-17T12:51:30.150250Z","iopub.status.idle":"2025-11-17T12:51:30.155657Z","shell.execute_reply.started":"2025-11-17T12:51:30.150226Z","shell.execute_reply":"2025-11-17T12:51:30.154826Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======================================================\n# 📦 DataLoaders\n# ======================================================\n\nbatch_size = 8  # ajusta según GPU\n\ntrain_dataset = TensorDataset(\n    X_train_input_ids, X_train_attention, y_train_tensor\n)\ntest_dataset = TensorDataset(\n    X_test_input_ids, X_test_attention, y_test_tensor\n)\n\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)\ntest_loader  = DataLoader(test_dataset, batch_size=batch_size)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:30.156554Z","iopub.execute_input":"2025-11-17T12:51:30.157233Z","iopub.status.idle":"2025-11-17T12:51:30.172036Z","shell.execute_reply.started":"2025-11-17T12:51:30.157212Z","shell.execute_reply":"2025-11-17T12:51:30.171369Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Suponiendo que tu DataFrame se llama df\n# df debe tener al menos estas columnas: 'EntryID', 'Sequence', 'term'\n\n# -----------------------------\n# 1️⃣ Detectar duplicados por secuencia\n# -----------------------------\ndup_seqs = df[df.duplicated(subset=[\"Sequence\"], keep=False)].sort_values(\"Sequence\")\nnum_dup_sequences = dup_seqs[\"Sequence\"].nunique()\nnum_dup_rows = len(dup_seqs)\n\nprint(f\"🔎 Secuencias duplicadas detectadas: {num_dup_sequences}\")\nprint(f\"Total de filas involucradas en duplicados: {num_dup_rows}\")\n\n# -----------------------------\n# 2️⃣ Analizar etiquetas distintas por secuencia duplicada\n# -----------------------------\ndef count_unique_terms(series):\n    return len(set(series))\n\ndup_analysis = dup_seqs.groupby(\"Sequence\")[\"term\"].agg([\n    (\"num_unique_terms\", count_unique_terms),\n    (\"total_occurrences\", \"count\")\n]).reset_index()\n\n# Cuántas secuencias tienen etiquetas distintas\nconflicting_seqs = dup_analysis[dup_analysis[\"num_unique_terms\"] > 1]\nprint(f\"⚠️ Secuencias con etiquetas distintas: {len(conflicting_seqs)}\")\n\n# -----------------------------\n# 3️⃣ Opcional: Agrupar etiquetas para multi-label\n# -----------------------------\n# Esto convierte varias etiquetas en una lista o string separada por ';'\ndf = df.groupby(\"Sequence\").agg({\n    \"term\": lambda x: \";\".join(sorted(set(x))),  # etiquetas únicas combinadas\n    \"aspect\": \"first\",       # tomamos el primer aspecto\n    \"seq_length\": \"first\",   # longitud de la secuencia\n    # si tienes columnas de counts, puedes usar \"first\" o \"mean\"\n    # ejemplo para counts de aminoácidos:\n    **{col: \"first\" for col in df.columns if col.startswith(\"count_\")}\n}).reset_index()\n\nprint(f\"✅ Dataset agrupado listo. Filas originales: {len(df)}, filas agrupadas: {len(df)}\")\nprint(df.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:30.172754Z","iopub.execute_input":"2025-11-17T12:51:30.173130Z","iopub.status.idle":"2025-11-17T12:51:31.393392Z","shell.execute_reply.started":"2025-11-17T12:51:30.173114Z","shell.execute_reply":"2025-11-17T12:51:31.392577Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Diagnóstico del dataset","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# 🔬 DIAGNÓSTICO AVANZADO DEL DATASET\n# ============================================================\n\ndef dataset_deep_diagnosis(df):\n    print(\"\\n\\n===============================\")\n    print(\"🔬 DIAGNÓSTICO AVANZADO\")\n    print(\"===============================\\n\")\n    print(\"\\n el df que se esta analizando es {}\", df)\n\n    # ---------------------------------------------------\n    # 1️⃣ Revisar duplicados\n    duplicates = df.duplicated().sum()\n    print(f\"🧩 Duplicados totales: {duplicates}\")\n    if duplicates > 0:\n        print(\"   ➤ Recomendación: eliminar duplicados antes de entrenar.\\n\")\n\n    # ---------------------------------------------------\n    # 2️⃣ Revisar NaNs por columna\n    print(\"\\n📌 Valores faltantes por columna:\")\n    missing_report = df.isna().sum().sort_values(ascending=False)\n    print(missing_report)\n\n    # ---------------------------------------------------\n    # 3️⃣ Columnas con cardinalidad extraña\n    print(\"\\n🔎 Columnas con cardinalidad alta (riesgo de overfitting):\")\n    for col in df.columns:\n        nunique = df[col].nunique()\n        if nunique == df.shape[0]:\n            print(f\"   ➤ {col}: TODAS las filas son únicas ❗\")\n        elif nunique > 0.5 * df.shape[0]:\n            print(f\"   ➤ {col}: cardinalidad muy alta ({nunique})\")\n\n    # ---------------------------------------------------\n    # 4️⃣ Outliers (solo columnas numéricas)\n    num_cols = df.select_dtypes(include=np.number).columns.tolist()\n    if num_cols:\n        print(\"\\n📊 Outliers detectados (IQR):\")\n        for col in num_cols:\n            Q1 = df[col].quantile(0.25)\n            Q3 = df[col].quantile(0.75)\n            IQR = Q3 - Q1\n            outliers = df[(df[col] < Q1 - 1.5*IQR) | (df[col] > Q3 + 1.5*IQR)][col].count()\n            print(f\"   ➤ {col}: {outliers} outliers\")\n\n    # ---------------------------------------------------\n    # 5️⃣ Análisis de longitud de secuencias (si existen)\n    if \"Sequence\" in df.columns:\n        df[\"seq_len\"] = df[\"Sequence\"].dropna().apply(len)\n        print(\"\\n🧬 Longitud de secuencias:\")\n        print(df[\"seq_len\"].describe())\n        print(f\"   ➤ Longitud mínima: {df['seq_len'].min()}\")\n        print(f\"   ➤ Longitud máxima: {df['seq_len'].max()}\")\n\n    # ---------------------------------------------------\n    # 6️⃣ Distribución de clases (si existe label o term)\n    target_col = None\n    for col in [\"label\", \"Label\", \"term\", \"Term\", \"y\", \"target\"]:\n        if col in df.columns:\n            target_col = col\n            break\n\n    if target_col:\n        print(f\"\\n🎯 Análisis detallado de la variable objetivo '{target_col}':\")\n        class_counts = df[target_col].value_counts()\n        print(class_counts)\n\n        print(\"\\n   ➤ Clases con 1 sola muestra:\")\n        print(class_counts[class_counts == 1])\n\n        print(\"\\n   ➤ Clases con menos de 5 muestras:\")\n        print(class_counts[class_counts < 5])\n\n    else:\n        print(\"\\n⚠️ No se encontró columna de etiquetas. No se puede analizar distribución de clases.\")\n\n    # ---------------------------------------------------\n    # 7️⃣ Colinealidad (solo numéricas)\n    if len(num_cols) > 1:\n        print(\"\\n📈 Matriz de correlación (top correlaciones):\")\n        corr = df[num_cols].corr().abs()\n        high_corr = corr.unstack().sort_values(ascending=False)\n        high_corr = high_corr[high_corr < 1]  # excluir diagonal\n        print(high_corr.head(10))\n\n    # ---------------------------------------------------\n    # 8️⃣ Columnas inútiles (constantes)\n    print(\"\\n♻️ Columnas constantes (sin información):\")\n    constant_cols = [col for col in df.columns if df[col].nunique() <= 1]\n    if constant_cols:\n        for col in constant_cols:\n            print(f\"   ➤ {col}: solo un valor único ❗\")\n    else:\n        print(\"   ➤ No hay columnas constantes, perfecto.\")\n\n    print(\"\\n✅ Diagnóstico avanzado completado.\\n\")\n\n\n# Ejecutar análisis profundo\ndataset_deep_diagnosis(df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:31.394285Z","iopub.execute_input":"2025-11-17T12:51:31.394529Z","iopub.status.idle":"2025-11-17T12:51:32.168067Z","shell.execute_reply.started":"2025-11-17T12:51:31.394510Z","shell.execute_reply":"2025-11-17T12:51:32.167202Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🔹 Preparación completa del dataset","metadata":{}},{"cell_type":"code","source":"from sentence_transformers import SentenceTransformer\nfrom sklearn.preprocessing import StandardScaler\n\n# -----------------------------\n# 1️⃣ Definir variable objetivo\n# -----------------------------\nTARGET = \"term\"   # Cambia si usas 'aspect'\nmodel_name = \"facebook/esm2_t33_650M_UR50D\"\n\n# Filtrar registros donde el target no esté vacío\ndf_train = df.dropna(subset=[TARGET]).reset_index(drop=True)\nprint(f\"📌 Filas después de filtrar valores faltantes del target '{TARGET}': {df_train.shape[0]}\")\n\n# -----------------------------\n# 2️⃣ Columnas numéricas\n# -----------------------------\nnum_cols = df_train.select_dtypes(include=np.number).columns.tolist()\nscaler = StandardScaler()\ndf_train[num_cols] = scaler.fit_transform(df_train[num_cols])\nprint(f\"✅ Columnas numéricas escaladas: {num_cols}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:32.168967Z","iopub.execute_input":"2025-11-17T12:51:32.169166Z","iopub.status.idle":"2025-11-17T12:51:32.251297Z","shell.execute_reply.started":"2025-11-17T12:51:32.169152Z","shell.execute_reply":"2025-11-17T12:51:32.250419Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4️⃣ Crear matriz numérica X_num\nX_num = df_train[num_cols].values\nprint(\"X_num:\", X_num.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:32.252095Z","iopub.execute_input":"2025-11-17T12:51:32.253336Z","iopub.status.idle":"2025-11-17T12:51:32.268154Z","shell.execute_reply.started":"2025-11-17T12:51:32.253306Z","shell.execute_reply":"2025-11-17T12:51:32.267298Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# -----------------------------\n# 3️⃣ Generación de embeddings\n# -----------------------------","metadata":{"execution":{"iopub.status.busy":"2025-11-15T17:49:51.050170Z","iopub.execute_input":"2025-11-15T17:49:51.050843Z","iopub.status.idle":"2025-11-15T17:52:30.741965Z","shell.execute_reply.started":"2025-11-15T17:49:51.050819Z","shell.execute_reply":"2025-11-15T17:52:30.740722Z"}}},{"cell_type":"code","source":"import torch\nimport numpy as np\nimport pandas as pd\nfrom transformers import AutoTokenizer, EsmModel\nfrom tqdm import tqdm\nimport os\nimport gc\n\n# ------------------------------------------------\n# 1. CONFIGURACIÓN\n# ------------------------------------------------\n\nMODEL_NAME = \"facebook/esm2_t12_35M_UR50D\"   # Cambia a uno pequeño si te peta la RAM\nMAX_LEN = 256                                 # Reduce tokens → SUPER importante\nCHECKPOINT_FILE = \"embeddings_partial.npy\"    # Guardado incremental\nBATCH_SIZE = 4                                 # Seguro en P100 para modelos grandes\n# Para modelo pequeño (8M, 35M): puedes subirlo a 16–32\n\n\n# ------------------------------------------------\n# 🔄 Validación del checkpoint\n# ------------------------------------------------\ndef check_checkpoint(df, checkpoint_file):\n    \"\"\"Elimina el checkpoint si no coincide con el tamaño actual del df.\"\"\"\n    if os.path.exists(checkpoint_file):\n        emb_partial = np.load(checkpoint_file)\n        if emb_partial.shape[0] != len(df):\n            print(f\"⚠️ Checkpoint desalineado ({emb_partial.shape[0]} filas) vs df ({len(df)} filas). Se eliminará.\")\n            os.remove(checkpoint_file)\n        else:\n            print(\"✅ Checkpoint válido. Se puede reanudar la generación de embeddings.\")\n    else:\n        print(\"✅ No hay checkpoint previo. Se generarán embeddings desde cero.\")\n\n# Ejecutar verificación\ncheck_checkpoint(df, CHECKPOINT_FILE)\nprint(f\"Existe Checkpoint previo: {os.path.exists(CHECKPOINT_FILE)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:32.268892Z","iopub.execute_input":"2025-11-17T12:51:32.269109Z","iopub.status.idle":"2025-11-17T12:51:32.277106Z","shell.execute_reply.started":"2025-11-17T12:51:32.269089Z","shell.execute_reply":"2025-11-17T12:51:32.276300Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ------------------------------------------------\n# 2. DISPOSITIVO\n# ------------------------------------------------\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Usando:\", device)\n\n# ------------------------------------------------\n# 3. CARGA MODELO + TOKENIZER (en GPU)\n# ------------------------------------------------\ntokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)\nmodel = EsmModel.from_pretrained(MODEL_NAME)\nmodel = model.to(device)\nmodel.eval()\n\n\n# ------------------------------------------------\n# 4. CARGA DE DATOS\n# ------------------------------------------------\nsequences = df[\"Sequence\"].tolist()\ntotal = len(sequences)\nprint(f\"Total de secuencias: {total}\")\n\n# ------------------------------------------------\n# 5. REANUDACIÓN DESDE CHECKPOINT (si existe)\n# ------------------------------------------------\nstart_idx = 0\nemb_list = []\n\nif os.path.exists(CHECKPOINT_FILE):\n    print(\"⚠️ Checkpoint detectado. Reanudando...\")\n    emb_partial = np.load(CHECKPOINT_FILE)\n    start_idx = emb_partial.shape[0]\n    emb_list = [emb_partial]\n    print(f\"➡️ Reanudando desde índice {start_idx} / {total}\")\n\n# ------------------------------------------------\n# 6. LOOP PRINCIPAL DE EMBEDDINGS\n# ------------------------------------------------\n\nfor i in tqdm(range(start_idx, total, BATCH_SIZE)):\n    batch = sequences[i : i + BATCH_SIZE]\n\n    tokens = tokenizer(\n        batch,\n        return_tensors=\"pt\",\n        padding=True,\n        truncation=True,\n        max_length=MAX_LEN\n    ).to(device)\n\n    with torch.no_grad():\n        outputs = model(**tokens)\n        batch_emb = outputs.last_hidden_state[:, 0, :].cpu().numpy()\n\n    emb_list.append(batch_emb)\n\n    # ------------------------------------------------\n    # Guardado incremental cada 500 batches\n    # ------------------------------------------------\n    if (i // BATCH_SIZE) % 500 == 0:\n        temp_emb = np.vstack(emb_list)\n        np.save(CHECKPOINT_FILE, temp_emb)\n        print(f\"💾 Checkpoint guardado en índice {i}\")\n        with open(\"checkpoint_index.txt\", \"w\") as f:\n            f.write(str(i + BATCH_SIZE))\n\n    # Liberar memoria\n    del tokens, outputs, batch_emb\n    torch.cuda.empty_cache()\n    gc.collect()\n\n# ------------------------------------------------\n# 7. GUARDADO FINAL\n# ------------------------------------------------\nfinal_embeddings = np.vstack(emb_list)\nnp.save(\"embeddings_final.npy\", final_embeddings)\n\nprint(\"🎯 Embeddings generados con éxito\")\nprint(\"Shape final:\", final_embeddings.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-17T12:51:32.278018Z","iopub.execute_input":"2025-11-17T12:51:32.278220Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Generación de archivos necesarios\n\nCreamos los siguiente archivos:\n\n- go_index.json:\n porque en los modelos GO se necesita mapear cada GO term a una posición de vector.","metadata":{}},{"cell_type":"code","source":"from goatools.obo_parser import GODag\nimport json\n\nobo_path = \"/kaggle/input/cafa-6-protein-function-prediction/Train/go-basic.obo\"\n\ngo = GODag(obo_path)\n\n# 1. GO → índice\ngo_index = {term: idx for idx, term in enumerate(go.keys())}\n\nwith open(\"go_index.json\", \"w\") as f:\n    json.dump(go_index, f, indent=2)\n\n# 2. GO → lista de ancestros\ngo_ancestors = {\n    term: list(obj.get_all_parents())\n    for term, obj in go.items()\n}\n\nwith open(\"go_ancestors.json\", \"w\") as f:\n    json.dump(go_ancestors, f, indent=2)\n\nprint(\"Listo: go_index.json y go_ancestors.json generados.\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ======================================================\n# 5️⃣ COMBINAR EMBEDDINGS + NUMÉRICAS\n# ======================================================\nX = np.hstack([final_embeddings, X_num])\ny = df[TARGET].values\n\nprint(f\"📌 Matriz final X: {X.shape}\")\n\n# -----------------------------\n# 4️⃣ Guardar resultados\n# -----------------------------\n# Crear carpeta si no existe\noutput_dir = \"/kaggle/working/EDA/embeddings\"\nos.makedirs(output_dir, exist_ok=True)\n# Guardar dataset limpio\ndf.to_csv(f\"{output_dir}/df_train_prepared.csv\", index=False)\n# Guardar embeddings\nnp.save(f\"{output_dir}/sequence_embeddings.npy\", final_embeddings)\n\nprint(f\"\\n✅ Dataset limpio y embeddings guardados en: {output_dir}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🔹Train finetune\n\n","metadata":{}},{"cell_type":"code","source":"#!/usr/bin/env python3\n\"\"\"\ntrain_finetune.py\nFine-tune an ESM model (facebook/esm2_t33_650M_UR50D) for CAFA6-style multi-label GO prediction.\n\nExpected input CSV format (train/val/test):\n- Sequence: protein sequence (string)\n- labels: semicolon-separated GO terms (e.g. \"GO:0008150;GO:0003674\")\nOptional:\n- entry_id: identifier to keep for final submission\n\nRequires GPU for practical training speed.\n\nUsage example:\npython train_finetune.py \\\n  --train_csv data/train.csv \\\n  --val_csv data/val.csv \\\n  --go_index_json data/go_index.json \\\n  --go_ancestors_json data/go_ancestors.json \\\n  --output_dir outputs/esm_finetune \\\n  --model_name facebook/esm2_t33_650M_UR50D \\\n  --epochs 3 \\\n  --batch_size 8\n\"\"\"\n\nimport argparse\nimport os\nimport json\nimport gc\nimport random\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim import AdamW\nfrom transformers import AutoModel, AutoTokenizer, get_linear_schedule_with_warmup\nfrom sklearn.metrics import f1_score\nfrom tqdm import tqdm\nfrom typing import List\n\n# -----------------------\n# Utilities\n# -----------------------\ndef set_seed(seed: int = 42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\ndef load_labels_from_str(s: str) -> List[str]:\n    if not s: return []\n    s = str(s).strip()\n    if \";\" in s:\n        return [t.strip() for t in s.split(\";\") if t.strip()]\n    else:\n        return [t.strip() for t in s.split(\",\") if t.strip()]\n\n# -----------------------\n# Dataset\n# -----------------------\nclass ProteinDataset(Dataset):\n    def __init__(self, df, tokenizer, go_to_idx, max_length=256, go_ancestors=None):\n        self.df = df.reset_index(drop=True)\n        self.tokenizer = tokenizer\n        self.go_to_idx = go_to_idx\n        self.max_length = max_length\n        self.go_ancestors = go_ancestors or {}\n        self.num_classes = len(go_to_idx)\n        self.sequences = self.df[\"Sequence\"].astype(str).tolist()\n        label_col = \"labels\" if \"labels\" in self.df.columns else None\n        self.labels_raw = self.df[label_col].fillna(\"\").astype(str).tolist() if label_col else [\"\"]*len(self.df)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        seq = self.sequences[idx]\n        labels_list = load_labels_from_str(self.labels_raw[idx])\n        multi_hot = np.zeros(self.num_classes, dtype=np.float32)\n        for term in labels_list:\n            if term in self.go_to_idx:\n                multi_hot[self.go_to_idx[term]] = 1.0\n                if term in self.go_ancestors:\n                    for anc in self.go_ancestors[term]:\n                        if anc in self.go_to_idx:\n                            multi_hot[self.go_to_idx[anc]] = 1.0\n        toks = self.tokenizer(seq, truncation=True, padding=\"max_length\", max_length=self.max_length, return_tensors=\"pt\")\n        item = {k: v.squeeze(0) for k, v in toks.items()}\n        item[\"labels\"] = torch.from_numpy(multi_hot)\n        if \"entry_id\" in self.df.columns:\n            item[\"entry_id\"] = self.df.loc[idx, \"entry_id\"]\n        return item\n\n# -----------------------\n# Model\n# -----------------------\nclass ESMForMultiLabel(nn.Module):\n    def __init__(self, backbone, hidden_size, num_labels, dropout=0.2):\n        super().__init__()\n        self.backbone = backbone\n        self.classifier = nn.Sequential(\n            nn.Dropout(dropout),\n            nn.Linear(hidden_size, num_labels)\n        )\n\n    def forward(self, input_ids=None, attention_mask=None, labels=None):\n        outputs = self.backbone(input_ids=input_ids, attention_mask=attention_mask, return_dict=True)\n        last_hidden = outputs.last_hidden_state\n        if attention_mask is None:\n            pooled = last_hidden.mean(dim=1)\n        else:\n            mask = attention_mask.unsqueeze(-1)\n            pooled = (last_hidden * mask).sum(dim=1) / (mask.sum(dim=1).clamp(min=1e-9))\n        logits = self.classifier(pooled)\n        out = {\"logits\": logits}\n        if labels is not None:\n            loss_fct = nn.BCEWithLogitsLoss()\n            out[\"loss\"] = loss_fct(logits, labels)\n        return out\n\n# -----------------------\n# Collate function\n# -----------------------\ndef collate_fn(batch):\n    input_ids = torch.stack([b[\"input_ids\"] for b in batch], dim=0)\n    attention_mask = torch.stack([b[\"attention_mask\"] for b in batch], dim=0)\n    labels = torch.stack([b[\"labels\"] for b in batch], dim=0)\n    out = {\"input_ids\": input_ids, \"attention_mask\": attention_mask, \"labels\": labels}\n    if \"entry_id\" in batch[0]:\n        out[\"entry_id\"] = [b.get(\"entry_id\", None) for b in batch]\n    return out\n\n# -----------------------\n# Evaluation\n# -----------------------\ndef evaluate(model, dataloader, device):\n    model.eval()\n    ys_true, ys_pred = [], []\n    with torch.no_grad():\n        for batch in dataloader:\n            input_ids = batch[\"input_ids\"].to(device)\n            attention_mask = batch[\"attention_mask\"].to(device)\n            labels = batch[\"labels\"].to(device)\n            out = model(input_ids=input_ids, attention_mask=attention_mask)\n            probs = torch.sigmoid(out[\"logits\"]).cpu().numpy()\n            preds = (probs >= 0.5).astype(int)\n            ys_true.append(labels.cpu().numpy())\n            ys_pred.append(preds)\n    ys_true = np.vstack(ys_true)\n    ys_pred = np.vstack(ys_pred)\n    try:\n        micro_f1 = f1_score(ys_true, ys_pred, average=\"micro\", zero_division=0)\n        macro_f1 = f1_score(ys_true, ys_pred, average=\"macro\", zero_division=0)\n    except:\n        micro_f1, macro_f1 = 0.0, 0.0\n    return {\"micro_f1\": micro_f1, \"macro_f1\": macro_f1}\n\n# -----------------------\n# Main\n# -----------------------\ndef main(args):\n    torch.cuda.empty_cache()\n    gc.collect()\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    os.makedirs(args.output_dir, exist_ok=True)\n    set_seed(args.seed)\n\n    import pandas as pd\n    train_df = pd.read_csv(args.train_csv)\n    val_df = pd.read_csv(args.val_csv) if args.val_csv else None\n\n    with open(args.go_index_json, \"r\") as f:\n        go_to_idx = json.load(f)\n    num_labels = len(go_to_idx)\n\n    go_anc = {}\n    if args.go_ancestors_json:\n        with open(args.go_ancestors_json, \"r\") as f:\n            go_anc = json.load(f)\n\n    tokenizer = AutoTokenizer.from_pretrained(args.model_name, use_fast=False)\n    backbone = AutoModel.from_pretrained(args.model_name, trust_remote_code=True)\n    backbone.gradient_checkpointing_enable()  # salva memoria\n\n    hidden_size = getattr(backbone.config, \"hidden_size\", 1280)\n    model = ESMForMultiLabel(backbone, hidden_size, num_labels, dropout=args.dropout)\n    model.to(device)\n\n    safe_batch_size = 1\n    max_len = min(args.max_length, 256)\n    grad_accum = args.gradient_accumulation_steps\n\n    train_dataset = ProteinDataset(train_df, tokenizer, go_to_idx, max_length=max_len, go_ancestors=go_anc)\n    train_loader = DataLoader(train_dataset, batch_size=safe_batch_size, shuffle=True, num_workers=2,\n                              pin_memory=True, collate_fn=collate_fn)\n\n    val_loader = None\n    if val_df is not None:\n        val_dataset = ProteinDataset(val_df, tokenizer, go_to_idx, max_length=max_len, go_ancestors=go_anc)\n        val_loader = DataLoader(val_dataset, batch_size=safe_batch_size, shuffle=False, num_workers=2,\n                                collate_fn=collate_fn)\n\n    no_decay = [\"bias\", \"LayerNorm.weight\"]\n    optimizer_grouped_parameters = [\n        {\"params\": [p for n,p in model.named_parameters() if not any(nd in n for nd in no_decay)], \"weight_decay\": args.weight_decay},\n        {\"params\": [p for n,p in model.named_parameters() if any(nd in n for nd in no_decay)], \"weight_decay\": 0.0},\n    ]\n    optimizer = AdamW(optimizer_grouped_parameters, lr=args.lr)\n    total_steps = len(train_loader) // grad_accum * args.epochs\n    scheduler = get_linear_schedule_with_warmup(optimizer, num_warmup_steps=int(0.06*total_steps), num_training_steps=total_steps)\n\n    scaler = torch.amp.GradScaler()\n\n    # Checkpoint\n    checkpoint_file = os.path.join(args.output_dir, \"checkpoint_last.pt\")\n    start_epoch, global_step = 1, 0\n    if os.path.exists(checkpoint_file):\n        checkpoint = torch.load(checkpoint_file, map_location=device)\n        model.load_state_dict(checkpoint[\"model_state\"])\n        optimizer.load_state_dict(checkpoint[\"optimizer_state\"])\n        scheduler.load_state_dict(checkpoint[\"scheduler_state\"])\n        scaler.load_state_dict(checkpoint[\"scaler_state\"])\n        start_epoch = checkpoint[\"epoch\"]\n        global_step = checkpoint[\"global_step\"]\n        print(f\"Resuming from epoch {start_epoch}, step {global_step}\")\n\n    best_micro = -1.0\n    for epoch in range(start_epoch, args.epochs+1):\n        model.train()\n        epoch_loss = 0.0\n        optimizer.zero_grad()\n        pbar = tqdm(train_loader, desc=f\"Epoch {epoch}\", leave=False)\n        for step, batch in enumerate(pbar, 1):\n            global_step += 1\n            input_ids = batch[\"input_ids\"].to(device)\n            attention_mask = batch[\"attention_mask\"].to(device)\n            labels = batch[\"labels\"].to(device)\n\n            with torch.amp.autocast(device_type='cuda'):\n                out = model(input_ids=input_ids, attention_mask=attention_mask, labels=labels)\n                loss = out[\"loss\"] / grad_accum\n\n            scaler.scale(loss).backward()\n\n            if step % grad_accum == 0:\n                scaler.unscale_(optimizer)\n                torch.nn.utils.clip_grad_norm_(model.parameters(), args.max_grad_norm)\n                scaler.step(optimizer)\n                scaler.update()\n                scheduler.step()\n                optimizer.zero_grad()\n\n            epoch_loss += loss.item() * grad_accum\n            pbar.set_postfix({\"loss\": f\"{loss.item()*grad_accum:.4f}\"})\n\n            # Checkpoint\n            if args.save_steps and global_step % args.save_steps == 0:\n                torch.save({\n                    \"model_state\": model.state_dict(),\n                    \"optimizer_state\": optimizer.state_dict(),\n                    \"scheduler_state\": scheduler.state_dict(),\n                    \"scaler_state\": scaler.state_dict(),\n                    \"epoch\": epoch,\n                    \"global_step\": global_step\n                }, checkpoint_file)\n\n            del input_ids, attention_mask, labels, out\n            torch.cuda.empty_cache()\n            gc.collect()\n\n        avg_loss = epoch_loss / len(train_loader)\n        print(f\"Epoch {epoch} completed. Avg loss: {avg_loss:.4f}\")\n\n        if val_loader:\n            metrics = evaluate(model, val_loader, device)\n            print(f\"Validation micro F1: {metrics['micro_f1']:.4f}, macro F1: {metrics['macro_f1']:.4f}\")\n            if metrics[\"micro_f1\"] > best_micro:\n                best_micro = metrics[\"micro_f1\"]\n                torch.save(model.state_dict(), os.path.join(args.output_dir, \"best_model.pt\"))\n\n    torch.save(model.state_dict(), os.path.join(args.output_dir, \"final_model.pt\"))\n    print(\"Training finished. Final model saved.\")\n\n\n# -----------------------\n# Collate function (simple)\n# -----------------------\ndef collate_fn(batch, device=None):\n    # batch is list of dicts with keys input_ids, attention_mask, labels, maybe entry_id\n    input_ids = torch.stack([b[\"input_ids\"] for b in batch], dim=0)\n    attention_mask = torch.stack([b[\"attention_mask\"] for b in batch], dim=0)\n    labels = torch.stack([b[\"labels\"] for b in batch], dim=0)\n    out = {\"input_ids\": input_ids, \"attention_mask\": attention_mask, \"labels\": labels}\n    # preserve entry_ids\n    if \"entry_id\" in batch[0]:\n        out[\"entry_id\"] = [b.get(\"entry_id\", None) for b in batch]\n    return out","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Graficacion del dataset","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nfrom sklearn.decomposition import PCA\nfrom sklearn.preprocessing import LabelEncoder\n\n# Supongamos que tienes embeddings en `embeddings_final.npy`\nembeddings = np.load(\"embeddings_final.npy\")\n\n# Reducir dimensiones a 2D\npca = PCA(n_components=3)\nemb_3d = pca.fit_transform(embeddings)\n\n# Colorear por alguna etiqueta, por ejemplo aspect\nlabels = df_train['aspect'].fillna('unknown')\nle = LabelEncoder()\nlabels_encoded = le.fit_transform(labels)\n\nplt.figure(figsize=(10,8))\nplt.scatter(emb_3d[:,0], emb_3d[:,1], emb_3d[:,2], c=labels_encoded, cmap='tab20', alpha=0.6)\nplt.colorbar(ticks=range(len(le.classes_)), label='aspect')\nplt.title(\"Distribución de embeddings (PCA 3D) por aspect\")\nplt.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install --upgrade scikit-learn umap-learn\n\n\n\nimport numpy as np\nimport pandas as pd\nimport plotly.express as px\nimport umap\nfrom sklearn.preprocessing import LabelEncoder\n\n# =========================\n# 1️⃣ CARGA DE DATOS\n# =========================\ndf_train = pd.read_csv(\"/kaggle/working/train_dataset_clean.csv\")\nembeddings = np.array(np.load(\"embeddings_final.npy\"), dtype=np.float32)  # importante: float32\n\n# Etiquetas para colorear (aspect o TaxID)\nlabels = df_train['aspect'].fillna('unknown')\nle = LabelEncoder()\nlabels_encoded = le.fit_transform(labels)\n\n# Tamaño de los puntos según longitud de secuencia (opcional)\nsizes = df_train['Sequence'].str.len()\n\n# =========================\n# 2️⃣ SUBMUESTREO SI ES NECESARIO\n# =========================\nsample_size = 10000\nif embeddings.shape[0] > sample_size:\n    idx = np.random.choice(embeddings.shape[0], sample_size, replace=False)\n    emb_sample = embeddings[idx]\n    labels_sample = labels_encoded[idx]\n    sizes_sample = sizes.iloc[idx]\nelse:\n    emb_sample = embeddings\n    labels_sample = labels_encoded\n    sizes_sample = sizes\n\n# =========================\n# 3️⃣ REDUCCIÓN DE DIMENSIONALIDAD CON UMAP 3D\n# =========================\nreducer = umap.UMAP(n_components=3, random_state=42)\n# Fit-transform sin argumentos extra\nemb_umap = reducer.fit_transform(emb_sample)\n\n# =========================\n# 4️⃣ GRAFICO INTERACTIVO\n# =========================\nfig = px.scatter_3d(\n    x=emb_umap[:,0],\n    y=emb_umap[:,1],\n    z=emb_umap[:,2],\n    color=[le.classes_[i] for i in labels_sample],\n    size=sizes_sample,\n    opacity=0.7,\n    labels={'color': 'aspect', 'size': 'Sequence length'},\n    title=\"Distribución interactiva de embeddings (UMAP 3D)\"\n)\n\nfig.update_layout(scene=dict(\n                    xaxis_title='UMAP 1',\n                    yaxis_title='UMAP 2',\n                    zaxis_title='UMAP 3'),\n                  legend_title=\"aspect\"\n                 )\n\nfig.show()\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# -----------------------\n# Argparse\n# -----------------------\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--train_csv\", required=False, default=\"/kaggle/working/EDA/embeddings/df_train_prepared.csv\", help=\"Train CSV path\")\n    parser.add_argument(\"--val_csv\", required=False, default=None, help=\"Validation CSV path\")\n    parser.add_argument(\"--go_index_json\", required=False, default=\"/kaggle/working/go_index.json\", help=\"JSON file mapping GO term to index (term -> idx)\")\n    parser.add_argument(\"--go_ancestors_json\", required=False, default=\"/kaggle/working/go_ancestors.json\", help=\"JSON file mapping term -> [ancestor terms]\")\n    \n    parser.add_argument(\"--model_name\", default=\"facebook/esm2_t12_35M_UR50D\", help=\"Backbone model name\")\n    parser.add_argument(\"--output_dir\", default=\"outputs\", help=\"Where to save checkpoints\")\n    parser.add_argument(\"--epochs\", type=int, default=3)\n    parser.add_argument(\"--batch_size\", type=int, default=8)\n    parser.add_argument(\"--lr\", type=float, default=5e-6)\n    parser.add_argument(\"--weight_decay\", type=float, default=0.01)\n    parser.add_argument(\"--max_length\", type=int, default=256)\n    parser.add_argument(\"--dropout\", type=float, default=0.2)\n    parser.add_argument(\"--max_grad_norm\", type=float, default=1.0)\n    parser.add_argument(\"--save_steps\", type=int, default=0, help=\"Save intermediate checkpoint every N steps (0 = disabled)\")\n    parser.add_argument(\"--log_every\", type=int, default=50)\n    parser.add_argument(\"--seed\", type=int, default=42)\n    parser.add_argument(\"--gradient_accumulation_steps\", type=int, default=1,help=\"Number of steps to accumulate gradients before optimizer step\")\n\n\n    # ⬇⬇⬇ CAMBIO CLAVE: evitar error del argumento \"-f\"\n    args, _ = parser.parse_known_args()\n    # ⬆⬆⬆\n\n    main(args)\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Ensemble predict","metadata":{}},{"cell_type":"code","source":"from sklearn.preprocessing import LabelEncoder\n\n# ======================================================\n# 🔹 PREPARAR SECUENCIAS Y ETIQUETAS\n# ======================================================\nlabel_col = \"term\"  # o el nombre correcto de tu columna de etiquetas\ntrain_sequences = df_train[\"Sequence\"].toli#!/usr/bin/env python3\n\"\"\"\nensemble_predict.py\n\nEnsemble multiple model checkpoints to produce final predictions.\n\nUsage example:\npython ensemble_predict.py \\\n  --test_csv data/test.csv \\\n  --go_index_json data/go_index.json \\\n  --model_name facebook/esm2_t33_650M_UR50D \\\n  --checkpoints outputs/seed1/best_model.pt outputs/seed2/best_model.pt \\\n  --output predictions/ensemble_preds.csv \\\n  --threshold 0.5\n\"\"\"\n\nimport os\nimport argparse\nimport json\nimport torch\nimport numpy as np\nimport pandas as pd\nfrom transformers import AutoModel, AutoTokenizer\nfrom train_finetune import ESMForMultiLabel, ProteinDataset, collate_fn  # reuse definitions\n\ndef load_model_from_checkpoint(model_name, ckpt_path, num_labels, device, dropout=0.2):\n    tokenizer = AutoTokenizer.from_pretrained(model_name, use_fast=False)\n    backbone = AutoModel.from_pretrained(model_name, trust_remote_code=True)\n    hidden_size = backbone.config.hidden_size if hasattr(backbone.config, \"hidden_size\") else 1280\n    model = ESMForMultiLabel(backbone=backbone, hidden_size=hidden_size, num_labels=num_labels, dropout=dropout)\n    state = torch.load(ckpt_path, map_location=device)\n    model.load_state_dict(state, strict=False)\n    model.to(device)\n    model.eval()\n    return model, tokenizer\n\ndef predict_with_model(model, dataloader, device):\n    probs_list = []\n    with torch.no_grad():\n        for batch in dataloader:\n            input_ids = batch[\"input_ids\"].to(device)\n            attention_mask = batch[\"attention_mask\"].to(device)\n            out = model(input_ids=input_ids, attention_mask=attention_mask)\n            logits = out[\"logits\"]\n            probs = torch.sigmoid(logits).cpu().numpy()\n            probs_list.append(probs)\n    probs = np.vstack(probs_list)\n    return probs\n\ndef main(args):\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    os.makedirs(os.path.dirname(args.output), exist_ok=True)\n\n    # load test CSV\n    test_df = pd.read_csv(args.test_csv)\n    # load go index\n    with open(args.go_index_json, \"r\") as f:\n        go_to_idx = json.load(f)\n    idx_to_go = {int(v): k for k, v in go_to_idx.items()}\n    num_labels = len(go_to_idx)\n\n    # We'll load tokenizer from the first checkpoint/model\n    models = []\n    tokenizers = []\n    for ckpt in args.checkpoints:\n        model, tokenizer = load_model_from_checkpoint(args.model_name, ckpt, num_labels, device, dropout=args.dropout)\n        models.append(model)\n        tokenizers.append(tokenizer)\n        print(\"Loaded checkpoint\", ckpt)\n\n    # Use tokenizer from first model for dataset\n    dataset = ProteinDataset(test_df, tokenizers[0], go_to_idx, max_length=args.max_length)\n    loader = torch.utils.data.DataLoader(dataset, batch_size=args.batch_size, shuffle=False, collate_fn=lambda x: collate_fn(x, device=None), num_workers=4)\n\n    # predict with each model\n    all_probs = []\n    for model in models:\n        probs = predict_with_model(model, loader, device)\n        all_probs.append(probs)\n    all_probs = np.stack(all_probs, axis=0)  # (n_models, N, n_labels)\n\n    # average / geometric / median ensemble options\n    if args.mode == \"mean\":\n        mean_probs = np.mean(all_probs, axis=0)\n    elif args.mode == \"median\":\n        mean_probs = np.median(all_probs, axis=0)\n    elif args.mode == \"min\":\n        mean_probs = np.min(all_probs, axis=0)\n    else:\n        mean_probs = np.mean(all_probs, axis=0)\n\n    # apply threshold\n    thresh = args.threshold\n    preds_bin = (mean_probs >= thresh).astype(int)\n\n    # produce CSV with entry_id and predicted labels (semicolon separated)\n    rows = []\n    for i in range(len(dataset)):\n        entry_id = test_df.loc[i, \"entry_id\"] if \"entry_id\" in test_df.columns else i\n        term_idxs = np.where(preds_bin[i] == 1)[0].tolist()\n        terms = [idx_to_go[int(idx)] for idx in term_idxs]\n        rows.append({\"entry_id\": entry_id, \"predicted_labels\": \";\".join(terms)})\n\n    out_df = pd.DataFrame(rows)\n    out_df.to_csv(args.output, index=False)\n    print(\"Saved ensemble predictions to\", args.output)\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--test_csv\", required=True)\n    parser.add_argument(\"--go_index_json\", required=True)\n    parser.add_argument(\"--model_name\", default=\"facebook/esm2_t33_650M_UR50D\")\n    parser.add_argument(\"--checkpoints\", nargs=\"+\", required=True, help=\"Paths to model checkpoint files\")\n    parser.add_argument(\"--output\", default=\"ensemble_preds.csv\")\n    parser.add_argument(\"--mode\", default=\"mean\", choices=[\"mean\", \"median\", \"min\"])\n    parser.add_argument(\"--threshold\", type=float, default=0.5)\n    parser.add_argument(\"--batch_size\", type=int, default=8)\n    parser.add_argument(\"--max_length\", type=int, default=1024)\n    parser.add_argument(\"--dropout\", type=float, default=0.2)\n    args = parser.parse_args()\n    main(args)\nst()\ntest_sequences = df_test[\"Sequence\"].tolist()\n\n# Codificar etiquetas a enteros\nle = LabelEncoder()\ntrain_labels = le.fit_transform(df_train[label_col])\ntest_labels = le.transform(df_test[label_col])\n\nprint(f\"✅ Etiquetas codificadas: {len(le.classes_)} clases\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}