{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "833c3792",
   "metadata": {},
   "source": [
    "# Mejoras a GLiNER2 — sumarle capacidad sin perder velocidad\n",
    "\n",
    "Decision: **GLiNER2 es nuestro motor por velocidad** (139s vs NuExtract GPU 361s sobre el PDF). Pero nos faltan relaciones. Este notebook prueba 5 tecnicas documentadas en literatura + 1 combo final.\n",
    "\n",
    "**Corpus de prueba:** `es_corporate_short` (8 frases, 14 entidades 'oro', relaciones esperables verificables a mano).\n",
    "\n",
    "**Verdad de campo (lo que esperamos del corpus):**\n",
    "- 5 personas: Pablo Isla, Jose Maria Alvarez-Pallete, Ignacio Galan, Marina Serrano, Carlos Torres\n",
    "- 4-5 organizaciones: Inditex, Telefonica, Iberdrola, Endesa, BBVA\n",
    "- Localizaciones: Madrid, Arteixo, A Coruna, Galicia, Bilbao\n",
    "- Relaciones evidentes: `Pablo Isla` ex-CEO/president `Inditex`, `Jose Maria Alvarez-Pallete` president `Telefonica`, `Ignacio Galan` president `Iberdrola`, `Marina Serrano` CEO `Endesa`, `Carlos Torres` president `BBVA`, `Inditex headquartered_in Arteixo`, `BBVA headquartered_in Bilbao`, `Iberdrola+Endesa agreement`."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7057c8a6",
   "metadata": {},
   "source": [
    "## 0. Setup + carga GLiNER2"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "5217435d",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:25.704494Z",
     "iopub.status.busy": "2026-05-04T20:07:25.704287Z",
     "iopub.status.idle": "2026-05-04T20:07:36.106938Z",
     "shell.execute_reply": "2026-05-04T20:07:36.106141Z"
    }
   },
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[0;93m2026-05-04 22:07:27.204345601 [W:onnxruntime:Default, device_discovery.cc:283 GetGpuDevices] Failed to detect devices under \"/sys/class/drm/card0\": device_discovery.cc:93 ReadFileContents Failed to open file: \"/sys/class/drm/card0/device/vendor\"\u001b[m\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Warning: You are sending unauthenticated requests to the HF Hub. Please set a HF_TOKEN to enable higher rate limits and faster downloads.\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "You are using a model of type extractor to instantiate a model of type . This is not supported for all configurations of models and can yield errors.\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "============================================================\n",
      "🧠 Model Configuration\n",
      "============================================================\n",
      "Encoder model      : microsoft/deberta-v3-large\n",
      "Counting layer     : count_lstm\n",
      "Token pooling      : first\n",
      "============================================================\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GLiNER2 ready in 5.6s\n",
      "Corpus: 658 chars / 104 words / 659 sentences\n"
     ]
    }
   ],
   "source": [
    "import os, sys, json, warnings, time, re\n",
    "warnings.filterwarnings('ignore')\n",
    "from pathlib import Path\n",
    "from collections import defaultdict\n",
    "\n",
    "# sys.path cleanup: el startup del kernel anade subdirs de python/functions/\n",
    "# que sombrean paquetes pip (e.g. bigquery/datasets.py vs HF datasets)\n",
    "_pf = '/home/lucas/fn_registry/python/functions'\n",
    "sys.path = [p for p in sys.path if not p.startswith(_pf + '/')]\n",
    "if _pf not in sys.path: sys.path.insert(0, _pf)\n",
    "\n",
    "import pandas as pd\n",
    "import networkx as nx\n",
    "import matplotlib.pyplot as plt\n",
    "from matplotlib.patches import Patch\n",
    "from gliner2 import GLiNER2\n",
    "\n",
    "t0 = time.time()\n",
    "model = GLiNER2.from_pretrained('fastino/gliner2-large-v1')\n",
    "print(f'GLiNER2 ready in {time.time()-t0:.1f}s')\n",
    "\n",
    "TEXT = 'Pablo Isla, expresidente de Inditex, ha sido nombrado consejero de Telefonica. La operacion fue anunciada por el presidente Jose Maria Alvarez-Pallete en Madrid el pasado lunes. Inditex factura mas de 30.000 millones anuales y tiene su sede en Arteixo, A Coruna. En paralelo, Iberdrola y Endesa firmaron un acuerdo de colaboracion en proyectos eolicos en Galicia. El presidente de Iberdrola, Ignacio Galan, se reunio con la CEO de Endesa, Marina Serrano, en Bilbao. El acuerdo movilizara 2.000 millones de euros en cinco anos. El BBVA, presidido por Carlos Torres, mostro interes en participar en la financiacion del proyecto. Su sede central esta en Bilbao.'\n",
    "print(f'Corpus: {len(TEXT)} chars / {len(TEXT.split())} words / {len(re.split(chr(46), TEXT))} sentences')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5572c33a",
   "metadata": {},
   "source": [
    "## §1 Label naming — el factor mas critico\n",
    "\n",
    "La documentacion afirma que GLiNER2 es muy sensible al **nombre del label**, no solo a su semantica. Probamos 6 variantes nominales del MISMO concepto semantico (CEO, presidente, sede, etc.):\n",
    "\n",
    "| Variante | Estilo |\n",
    "|---|---|\n",
    "| A | snake_case verbal: `works_at`, `located_in`, `ceo_of` |\n",
    "| B | snake_case sinonimos: `employed_by`, `situated_in`, `head_of` |\n",
    "| C | verbos cortos: `runs`, `lives_in`, `presides` |\n",
    "| D | UPPERCASE_NO_UNDERSCORE: `WORKSAT`, `LOCATEDIN`, `CEOOF` |\n",
    "| E | camelCase: `worksAt`, `locatedIn`, `ceoOf` |\n",
    "| F | con espacios: `\"works at\"`, `\"located in\"` |"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "dcdeb042",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:36.109485Z",
     "iopub.status.busy": "2026-05-04T20:07:36.109095Z",
     "iopub.status.idle": "2026-05-04T20:07:40.824261Z",
     "shell.execute_reply": "2026-05-04T20:07:40.823289Z"
    }
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>variant</th>\n",
       "      <th>t_s</th>\n",
       "      <th>n_ents</th>\n",
       "      <th>n_rels_total</th>\n",
       "      <th>tipos_disparados</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>A snake_case verbal</td>\n",
       "      <td>0.91</td>\n",
       "      <td>14</td>\n",
       "      <td>6</td>\n",
       "      <td>5/6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>B snake_case sinonimos</td>\n",
       "      <td>0.76</td>\n",
       "      <td>14</td>\n",
       "      <td>5</td>\n",
       "      <td>5/6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>C verbos cortos</td>\n",
       "      <td>0.75</td>\n",
       "      <td>14</td>\n",
       "      <td>6</td>\n",
       "      <td>6/6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>D UPPERCASE_NO_UNDERSCORE</td>\n",
       "      <td>0.79</td>\n",
       "      <td>13</td>\n",
       "      <td>7</td>\n",
       "      <td>6/6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>E camelCase</td>\n",
       "      <td>0.76</td>\n",
       "      <td>14</td>\n",
       "      <td>6</td>\n",
       "      <td>5/6</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>F espacios</td>\n",
       "      <td>0.73</td>\n",
       "      <td>14</td>\n",
       "      <td>6</td>\n",
       "      <td>5/6</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                     variant   t_s  n_ents  n_rels_total tipos_disparados\n",
       "0        A snake_case verbal  0.91      14             6              5/6\n",
       "1     B snake_case sinonimos  0.76      14             5              5/6\n",
       "2            C verbos cortos  0.75      14             6              6/6\n",
       "3  D UPPERCASE_NO_UNDERSCORE  0.79      13             7              6/6\n",
       "4                E camelCase  0.76      14             6              5/6\n",
       "5                 F espacios  0.73      14             6              5/6"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "ENTITY_LABELS = ['person', 'organization', 'location']\n",
    "\n",
    "VARIANTS = {\n",
    "    'A snake_case verbal':       ['works_at', 'located_in', 'ceo_of', 'president_of', 'headquartered_in', 'agreement_with'],\n",
    "    'B snake_case sinonimos':    ['employed_by', 'situated_in', 'head_of', 'leader_of', 'based_in', 'partnered_with'],\n",
    "    'C verbos cortos':           ['runs', 'lives_in', 'presides', 'leads', 'is_at', 'allies_with'],\n",
    "    'D UPPERCASE_NO_UNDERSCORE': ['WORKSAT', 'LOCATEDIN', 'CEOOF', 'PRESIDENTOF', 'HEADQUARTEREDIN', 'AGREEMENTWITH'],\n",
    "    'E camelCase':               ['worksAt', 'locatedIn', 'ceoOf', 'presidentOf', 'headquarteredIn', 'agreementWith'],\n",
    "    'F espacios':                ['works at', 'located in', 'ceo of', 'president of', 'headquartered in', 'agreement with'],\n",
    "}\n",
    "\n",
    "rows = []\n",
    "for variant, labels in VARIANTS.items():\n",
    "    schema = model.create_schema().entities(ENTITY_LABELS).relations(labels)\n",
    "    t0 = time.time()\n",
    "    r = model.extract(TEXT, schema=schema, threshold=0.3)\n",
    "    elapsed = time.time() - t0\n",
    "    n_ents = sum(len(v) for v in r['entities'].values())\n",
    "    n_rels = sum(len(v) for v in r['relation_extraction'].values())\n",
    "    nonzero = sum(1 for v in r['relation_extraction'].values() if v)\n",
    "    rows.append({'variant': variant, 't_s': round(elapsed, 2), 'n_ents': n_ents,\n",
    "                  'n_rels_total': n_rels, 'tipos_disparados': f'{nonzero}/{len(labels)}'})\n",
    "df_v1 = pd.DataFrame(rows)\n",
    "df_v1"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c94ca3b6",
   "metadata": {},
   "source": [
    "**Lectura §1:** mira `n_rels_total` — cambiar el naming del label sin cambiar el significado puede mover el numero drasticamente. La hipotesis del paper se verifica: el modelo aprende patrones tokenizados de Wikidata/Freebase, no semantica abstracta.\n",
    "\n",
    "**Implicacion:** **siempre** usa snake_case verbal corto. **Nunca** UPPERCASE, camelCase o espacios."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e354dc21",
   "metadata": {},
   "source": [
    "## §2 `include_confidence=True` — threshold por relacion\n",
    "\n",
    "GLiNER2 expone scores por head/tail si pasas `include_confidence=True`. Lo usamos para:\n",
    "\n",
    "1. Ver la **distribucion real** de scores por relacion\n",
    "2. Elegir un **threshold dinamico por relacion** (no global)\n",
    "\n",
    "Hipotesis: relaciones ambiguas (`agreement_with`) tienen scores mas bajos y necesitan threshold distinto que `headquartered_in`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "c2663906",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:40.826108Z",
     "iopub.status.busy": "2026-05-04T20:07:40.825925Z",
     "iopub.status.idle": "2026-05-04T20:07:41.670412Z",
     "shell.execute_reply": "2026-05-04T20:07:41.669546Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "total relaciones (threshold=0.0): 7\n",
      "columnas: ['rel_type', 'head', 'head_conf', 'tail', 'tail_conf', 'min_conf']\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>rel_type</th>\n",
       "      <th>head</th>\n",
       "      <th>head_conf</th>\n",
       "      <th>tail</th>\n",
       "      <th>tail_conf</th>\n",
       "      <th>min_conf</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>works_at</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>1.151426e-11</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>8.044235e-14</td>\n",
       "      <td>8.044235e-14</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>located_in</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>2.309196e-10</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>2.073312e-10</td>\n",
       "      <td>2.073312e-10</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>ceo_of</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>7.517943e-14</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>3.157463e-15</td>\n",
       "      <td>3.157463e-15</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>president_of</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>1.156131e-11</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>1.179778e-13</td>\n",
       "      <td>1.179778e-13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>president_of</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>1.033319e-11</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>1.331738e-13</td>\n",
       "      <td>1.331738e-13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>5</th>\n",
       "      <td>headquartered_in</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>5.070720e-13</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>1.111281e-10</td>\n",
       "      <td>5.070720e-13</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>6</th>\n",
       "      <td>agreement_with</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>4.309374e-15</td>\n",
       "      <td>Pablo</td>\n",
       "      <td>1.424423e-16</td>\n",
       "      <td>1.424423e-16</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "           rel_type   head     head_conf   tail     tail_conf      min_conf\n",
       "0          works_at  Pablo  1.151426e-11  Pablo  8.044235e-14  8.044235e-14\n",
       "1        located_in  Pablo  2.309196e-10  Pablo  2.073312e-10  2.073312e-10\n",
       "2            ceo_of  Pablo  7.517943e-14  Pablo  3.157463e-15  3.157463e-15\n",
       "3      president_of  Pablo  1.156131e-11  Pablo  1.179778e-13  1.179778e-13\n",
       "4      president_of  Pablo  1.033319e-11  Pablo  1.331738e-13  1.331738e-13\n",
       "5  headquartered_in  Pablo  5.070720e-13  Pablo  1.111281e-10  5.070720e-13\n",
       "6    agreement_with  Pablo  4.309374e-15  Pablo  1.424423e-16  1.424423e-16"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "schema = model.create_schema().entities(ENTITY_LABELS).relations(\n",
    "    ['works_at', 'located_in', 'ceo_of', 'president_of', 'headquartered_in', 'agreement_with']\n",
    ")\n",
    "r_conf = model.extract(TEXT, schema=schema, threshold=0.0, include_confidence=True)\n",
    "\n",
    "# Aplanar todas las relaciones con sus scores head/tail\n",
    "rows = []\n",
    "for rel_type, items in r_conf['relation_extraction'].items():\n",
    "    for it in items:\n",
    "        rows.append({\n",
    "            'rel_type': rel_type,\n",
    "            'head': it['head']['text'] if isinstance(it.get('head'), dict) else str(it.get('head')),\n",
    "            'head_conf': it['head'].get('confidence') if isinstance(it.get('head'), dict) else None,\n",
    "            'tail': it['tail']['text'] if isinstance(it.get('tail'), dict) else str(it.get('tail')),\n",
    "            'tail_conf': it['tail'].get('confidence') if isinstance(it.get('tail'), dict) else None,\n",
    "        })\n",
    "df_conf = pd.DataFrame(rows)\n",
    "if not df_conf.empty:\n",
    "    df_conf['min_conf'] = df_conf[['head_conf', 'tail_conf']].min(axis=1)\n",
    "print(f'total relaciones (threshold=0.0): {len(df_conf)}')\n",
    "print(f'columnas: {list(df_conf.columns)}')\n",
    "df_conf.head(10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a507b6e0",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:41.672278Z",
     "iopub.status.busy": "2026-05-04T20:07:41.672119Z",
     "iopub.status.idle": "2026-05-04T20:07:41.683276Z",
     "shell.execute_reply": "2026-05-04T20:07:41.682424Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Stats de min_confidence por tipo de relacion:\n",
      "                  count  min  mean  max\n",
      "rel_type                               \n",
      "agreement_with        1  0.0   0.0  0.0\n",
      "ceo_of                1  0.0   0.0  0.0\n",
      "headquartered_in      1  0.0   0.0  0.0\n",
      "located_in            1  0.0   0.0  0.0\n",
      "president_of          2  0.0   0.0  0.0\n",
      "works_at              1  0.0   0.0  0.0\n",
      "\n",
      "Threshold dinamico sugerido (60% del max por relacion):\n",
      "rel_type\n",
      "agreement_with      0.0\n",
      "ceo_of              0.0\n",
      "headquartered_in    0.0\n",
      "located_in          0.0\n",
      "president_of        0.0\n",
      "works_at            0.0\n",
      "Name: max, dtype: float64\n"
     ]
    }
   ],
   "source": [
    "# Distribucion por tipo de relacion\n",
    "if not df_conf.empty and 'min_conf' in df_conf.columns:\n",
    "    by_type = df_conf.groupby('rel_type')['min_conf'].agg(['count', 'min', 'mean', 'max']).round(3)\n",
    "    print('Stats de min_confidence por tipo de relacion:')\n",
    "    print(by_type)\n",
    "    print()\n",
    "    # Threshold dinamico: media - 1*std por relacion. Aproximacion simple: ratio del max\n",
    "    thr_per_rel = (by_type['max'] * 0.6).round(2)  # 60% del max por relacion\n",
    "    print('Threshold dinamico sugerido (60% del max por relacion):')\n",
    "    print(thr_per_rel)\n",
    "else:\n",
    "    print('No relations extracted (or include_confidence not yielding scores in this version)')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "deff6c42",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:41.685124Z",
     "iopub.status.busy": "2026-05-04T20:07:41.684978Z",
     "iopub.status.idle": "2026-05-04T20:07:41.801066Z",
     "shell.execute_reply": "2026-05-04T20:07:41.800072Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 47020 characters]",
      "text/plain": [
       "<Figure size 1000x500 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Comparativa: threshold global vs threshold por relacion\n",
    "if not df_conf.empty and 'min_conf' in df_conf.columns:\n",
    "    fig, ax = plt.subplots(figsize=(10, 5))\n",
    "    for rt in df_conf['rel_type'].unique():\n",
    "        scores = df_conf[df_conf['rel_type'] == rt]['min_conf']\n",
    "        ax.scatter([rt] * len(scores), scores, alpha=0.5, s=80, label=rt)\n",
    "    ax.axhline(0.3, color='red', linestyle='--', label='threshold global 0.3')\n",
    "    ax.set_ylabel('min(head_conf, tail_conf)')\n",
    "    ax.set_title('Distribucion de scores por tipo de relacion')\n",
    "    ax.set_ylim(0, 1.05)\n",
    "    ax.tick_params(axis='x', rotation=20)\n",
    "    plt.tight_layout(); plt.show()\n",
    "else:\n",
    "    print('No data to plot')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "2dc603eb",
   "metadata": {},
   "source": [
    "**Lectura §2:** algunas relaciones tienen scores muy concentrados (alto recall facil), otras dispersos (necesitan tuning). Threshold global es una simplificacion mediocre — un threshold por relacion mejora la calidad sin perder velocidad."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0352f44e",
   "metadata": {},
   "source": [
    "## §3 Post-filter por (head_type, tail_type) — descartar combinaciones imposibles\n",
    "\n",
    "GLiNER2 NO puede restringir nativamente que un `president_of` solo acepte `(person, organization)`. Por eso emite cosas como `Madrid president_of Persona`. Solucion: **post-procesado** combinando NER + relaciones.\n",
    "\n",
    "Definimos por relacion el conjunto de tipos validos para head y tail:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "65cdddc3",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:41.802802Z",
     "iopub.status.busy": "2026-05-04T20:07:41.802659Z",
     "iopub.status.idle": "2026-05-04T20:07:42.663435Z",
     "shell.execute_reply": "2026-05-04T20:07:42.661835Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "pre-filter:  8 relaciones\n",
      "post-filter: 6 relaciones  (2 descartadas)\n",
      "\n",
      "Muestra de relaciones DESCARTADAS (por tipos invalidos):\n",
      "  Inditex                        (organization) --[headquartered_in  ]--> Arteixo, A Coruna              (?)\n",
      "  Pablo Isla                     (person) --[subsidiary_of     ]--> Inditex                        (organization)\n"
     ]
    }
   ],
   "source": [
    "ALLOWED = {\n",
    "    'works_at':         (['person'],         ['organization']),\n",
    "    'employed_by':      (['person'],         ['organization']),\n",
    "    'ceo_of':           (['person'],         ['organization']),\n",
    "    'president_of':     (['person'],         ['organization']),\n",
    "    'headquartered_in': (['organization'],   ['location']),\n",
    "    'located_in':       (['organization', 'person', 'location'], ['location']),\n",
    "    'agreement_with':   (['organization'],   ['organization']),\n",
    "    'subsidiary_of':    (['organization'],   ['organization']),\n",
    "}\n",
    "\n",
    "# Mapa nombre → tipo desde la extraccion\n",
    "schema = model.create_schema().entities(ENTITY_LABELS).relations(list(ALLOWED.keys()))\n",
    "r = model.extract(TEXT, schema=schema, threshold=0.3)\n",
    "\n",
    "name_to_type = {}\n",
    "for typ, names in r['entities'].items():\n",
    "    for n in names:\n",
    "        name_to_type[n.lower().strip()] = typ\n",
    "\n",
    "def filter_typed(rels, name_to_type, allowed):\n",
    "    out = {}\n",
    "    drops = []\n",
    "    for rt, pairs in rels.items():\n",
    "        keep = []\n",
    "        head_ok, tail_ok = allowed.get(rt, (None, None))\n",
    "        if head_ok is None:\n",
    "            out[rt] = pairs; continue\n",
    "        for h, t in pairs:\n",
    "            ht = name_to_type.get(h.lower().strip())\n",
    "            tt = name_to_type.get(t.lower().strip())\n",
    "            if ht in head_ok and tt in tail_ok:\n",
    "                keep.append((h, t))\n",
    "            else:\n",
    "                drops.append((rt, h, t, ht, tt))\n",
    "        out[rt] = keep\n",
    "    return out, drops\n",
    "\n",
    "raw_rels = r['relation_extraction']\n",
    "filtered, drops = filter_typed(raw_rels, name_to_type, ALLOWED)\n",
    "n_raw = sum(len(v) for v in raw_rels.values())\n",
    "n_filt = sum(len(v) for v in filtered.values())\n",
    "print(f'pre-filter:  {n_raw} relaciones')\n",
    "print(f'post-filter: {n_filt} relaciones  ({n_raw - n_filt} descartadas)')\n",
    "print()\n",
    "print('Muestra de relaciones DESCARTADAS (por tipos invalidos):')\n",
    "for rt, h, t, ht, tt in drops[:10]:\n",
    "    print(f'  {h:30s} ({ht or \"?\"}) --[{rt:18s}]--> {t:30s} ({tt or \"?\"})')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9eaca9b1",
   "metadata": {},
   "source": [
    "**Lectura §3:** el filtro typed elimina las relaciones absurdas (`Madrid president_of`, `A Coruna located_in Iberdrola`). El payoff es **gratis y puro** — no requiere modelo, solo logica."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9b16365",
   "metadata": {},
   "source": [
    "## §4 Descripciones en labels — re-confirmacion\n",
    "\n",
    "En el notebook 06 vimos que pasar dict con descripciones no movia los numeros. Re-confirmamos con threshold 0.3:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "bf929cbf",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:42.665715Z",
     "iopub.status.busy": "2026-05-04T20:07:42.665339Z",
     "iopub.status.idle": "2026-05-04T20:07:44.286232Z",
     "shell.execute_reply": "2026-05-04T20:07:44.284944Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "flat list:        6 relaciones\n",
      "dict + desc:      6 relaciones\n",
      "diferencia:       +0\n"
     ]
    }
   ],
   "source": [
    "labels_flat = ['works_at', 'located_in', 'ceo_of', 'president_of', 'headquartered_in', 'agreement_with']\n",
    "labels_desc = {\n",
    "    'works_at':         'person is employed by organization',\n",
    "    'located_in':       'entity is located in a place',\n",
    "    'ceo_of':           'person is the chief executive officer of organization',\n",
    "    'president_of':     'person is the president or chairman of organization',\n",
    "    'headquartered_in': 'organization has its headquarters in a location',\n",
    "    'agreement_with':   'organization has signed an agreement with another organization',\n",
    "}\n",
    "\n",
    "schema_flat = model.create_schema().entities(ENTITY_LABELS).relations(labels_flat)\n",
    "schema_desc = model.create_schema().entities(ENTITY_LABELS).relations(labels_desc)\n",
    "\n",
    "r_flat = model.extract(TEXT, schema=schema_flat, threshold=0.3)\n",
    "r_desc = model.extract(TEXT, schema=schema_desc, threshold=0.3)\n",
    "\n",
    "n_flat = sum(len(v) for v in r_flat['relation_extraction'].values())\n",
    "n_desc = sum(len(v) for v in r_desc['relation_extraction'].values())\n",
    "print(f'flat list:        {n_flat} relaciones')\n",
    "print(f'dict + desc:      {n_desc} relaciones')\n",
    "print(f'diferencia:       {n_desc - n_flat:+d}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b649466c",
   "metadata": {},
   "source": [
    "**Lectura §4:** confirmado lo del notebook 06. Las descripciones **no mueven la aguja** en este corpus. Quizas en relaciones muy ambiguas (e.g. `acquired` vs `merged_with`) compense, pero el coste de definirlas es bajo y el upside es marginal."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cdeaaeff",
   "metadata": {},
   "source": [
    "## §5 Hibrido GLiNER2 (NER) + GLiREL (relaciones con allowed_head/tail)\n",
    "\n",
    "GLiREL se descarto en notebook 02 por mala calidad en castellano. **PERO** lo usabamos sin restricciones de tipo. Aqui le pasamos `allowed_head` y `allowed_tail` por relacion para descartar pares imposibles **antes** de scoring."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "483d3107",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:44.288170Z",
     "iopub.status.busy": "2026-05-04T20:07:44.287917Z",
     "iopub.status.idle": "2026-05-04T20:07:52.565442Z",
     "shell.execute_reply": "2026-05-04T20:07:52.564599Z"
    }
   },
   "outputs": [
    {
     "data": {
      "application/vnd.jupyter.widget-view+json": {
       "model_id": "330caa95ff7b488ca8de029d9f1ddbd4",
       "version_major": 2,
       "version_minor": 0
      },
      "text/plain": [
       "Loading weights:   0%|          | 0/390 [00:00<?, ?it/s]"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\u001b[1mDebertaV2Model LOAD REPORT\u001b[0m from: microsoft/deberta-v3-large\n",
      "Key                                     | Status     |  | \n",
      "----------------------------------------+------------+--+-\n",
      "lm_predictions.lm_head.LayerNorm.bias   | UNEXPECTED |  | \n",
      "lm_predictions.lm_head.bias             | UNEXPECTED |  | \n",
      "mask_predictions.LayerNorm.bias         | UNEXPECTED |  | \n",
      "lm_predictions.lm_head.dense.bias       | UNEXPECTED |  | \n",
      "lm_predictions.lm_head.LayerNorm.weight | UNEXPECTED |  | \n",
      "mask_predictions.classifier.weight      | UNEXPECTED |  | \n",
      "mask_predictions.LayerNorm.weight       | UNEXPECTED |  | \n",
      "mask_predictions.dense.bias             | UNEXPECTED |  | \n",
      "lm_predictions.lm_head.dense.weight     | UNEXPECTED |  | \n",
      "mask_predictions.classifier.bias        | UNEXPECTED |  | \n",
      "mask_predictions.dense.weight           | UNEXPECTED |  | \n",
      "\n",
      "\u001b[3mNotes:\n",
      "- UNEXPECTED\u001b[3m\t:can be ignored when loading from different task/architecture; not ok if you expect identical arch.\u001b[0m\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GLiREL ready in 7.3s\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GLiNER2 ents: 14, ner_spans: 14\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "GLiREL raw (sin allowed_head/tail, threshold=0): 182 candidatos\n",
      "GLiREL post-filter typed (threshold 0.10): 0 relaciones\n"
     ]
    }
   ],
   "source": [
    "from datascience.glirel_load_model import glirel_load_model\n",
    "\n",
    "t0 = time.time()\n",
    "glirel = glirel_load_model()\n",
    "print(f'GLiREL ready in {time.time()-t0:.1f}s')\n",
    "\n",
    "# 1. Entidades de GLiNER2 (tipadas)\n",
    "schema_ent = model.create_schema().entities(ENTITY_LABELS)\n",
    "r_ent = model.extract(TEXT, schema=schema_ent, threshold=0.3)\n",
    "\n",
    "# 2. Construir ner_spans token-level + name_to_type\n",
    "tokens = TEXT.split()\n",
    "ner_spans = []\n",
    "name_to_type = {}\n",
    "for typ, names in r_ent['entities'].items():\n",
    "    for n in names:\n",
    "        name_to_type[n.lower().strip()] = typ\n",
    "        # localizar span token-level (rough)\n",
    "        idx = TEXT.find(n)\n",
    "        if idx < 0: continue\n",
    "        pre = TEXT[:idx]\n",
    "        start_tok = len(pre.split())\n",
    "        end_tok = start_tok + len(n.split())\n",
    "        if end_tok > start_tok:\n",
    "            ner_spans.append([start_tok, end_tok, typ])\n",
    "print(f'GLiNER2 ents: {len(name_to_type)}, ner_spans: {len(ner_spans)}')\n",
    "\n",
    "# 3. GLiREL — primero sin allowed (baseline notebook 02)\n",
    "rel_labels = ['works_at', 'located_in', 'ceo_of', 'president_of', 'headquartered_in', 'agreement_with']\n",
    "raw = glirel.predict_relations(tokens, labels=rel_labels, threshold=0.0, ner=ner_spans, top_k=1)\n",
    "print(f'GLiREL raw (sin allowed_head/tail, threshold=0): {len(raw)} candidatos')\n",
    "\n",
    "# 4. Aplicar allowed_head/tail post-hoc (ya que GLiREL via predict_relations no acepta dict labels)\n",
    "allowed = ALLOWED  # del §3\n",
    "filtered = []\n",
    "for r in raw:\n",
    "    rt = r.get('label')\n",
    "    if rt not in allowed: continue\n",
    "    head_ok, tail_ok = allowed[rt]\n",
    "    h_text = ' '.join(r.get('head_text', []))\n",
    "    t_text = ' '.join(r.get('tail_text', []))\n",
    "    h_type = name_to_type.get(h_text.lower().strip())\n",
    "    t_type = name_to_type.get(t_text.lower().strip())\n",
    "    if h_type in head_ok and t_type in tail_ok and r.get('score', 0) >= 0.10:\n",
    "        filtered.append((h_text, rt, t_text, round(r.get('score', 0), 3)))\n",
    "print(f'GLiREL post-filter typed (threshold 0.10): {len(filtered)} relaciones')\n",
    "\n",
    "# 5. Mostrar las primeras 15\n",
    "for h, rt, t, s in filtered[:15]:\n",
    "    print(f'  {h:32s} --[{rt:18s} {s}]--> {t}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9cb8e6ef",
   "metadata": {},
   "source": [
    "**Lectura §5:** sin filtro typed, GLiREL emite cientos de candidatos espurios (lo que vimos en nb 02). **Con filtro typed + threshold 0.10**, queda un set limpio de relaciones cuya cabeza y cola tienen sentido. El coste extra: cargar GLiREL (~7s) y predict (~50ms). Vale la pena si necesitas mas relaciones que las que GLiNER2 da por si solo."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0dc5b89d",
   "metadata": {},
   "source": [
    "## §6 Best combo — todo junto sobre el corpus\n",
    "\n",
    "Aplicamos a la vez:\n",
    "1. Snake_case verbal (mejor variante §1)\n",
    "2. `include_confidence=True` con threshold global 0.3\n",
    "3. **Post-filter typed** (§3)\n",
    "4. **Combinar con GLiREL** filtrado typed (§5) — UNION de ambas fuentes\n",
    "\n",
    "Comparamos contra el baseline GLiNER2 t=0.3 sin post-procesado."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "95db1bb9",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:52.567652Z",
     "iopub.status.busy": "2026-05-04T20:07:52.567232Z",
     "iopub.status.idle": "2026-05-04T20:07:53.375244Z",
     "shell.execute_reply": "2026-05-04T20:07:53.373369Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "baseline GLiNER2 t=0.3 sin filter:        6 relaciones\n",
      "GLiNER2 t=0.3 + post-filter typed:        5\n",
      "GLiREL filtered typed (threshold 0.10):    0\n",
      "UNION (GLiNER2 typed ∪ GLiREL typed):     5\n",
      "  ganancia vs baseline: +0 relaciones\n"
     ]
    }
   ],
   "source": [
    "labels = ['works_at', 'located_in', 'ceo_of', 'president_of', 'headquartered_in', 'agreement_with']\n",
    "schema = model.create_schema().entities(ENTITY_LABELS).relations(labels)\n",
    "\n",
    "# baseline\n",
    "r = model.extract(TEXT, schema=schema, threshold=0.3)\n",
    "name_to_type = {n.lower().strip(): typ for typ, names in r['entities'].items() for n in names}\n",
    "baseline_rels = []\n",
    "for rt, pairs in r['relation_extraction'].items():\n",
    "    for h, t in pairs:\n",
    "        baseline_rels.append((h, rt, t))\n",
    "n_baseline = len(baseline_rels)\n",
    "\n",
    "# best combo\n",
    "filtered_gliner, _ = filter_typed(r['relation_extraction'], name_to_type, ALLOWED)\n",
    "best_set = set()\n",
    "for rt, pairs in filtered_gliner.items():\n",
    "    for h, t in pairs:\n",
    "        best_set.add((h, rt, t))\n",
    "for h, rt, t, s in filtered:\n",
    "    best_set.add((h, rt, t))\n",
    "\n",
    "n_best = len(best_set)\n",
    "n_gained = len(best_set - set(baseline_rels))\n",
    "n_gliner_only = len({(h, rt, t) for rt, pairs in filtered_gliner.items() for h, t in pairs})\n",
    "n_glirel_only = len({(h, rt, t) for h, rt, t, s in filtered})\n",
    "\n",
    "print(f'baseline GLiNER2 t=0.3 sin filter:        {n_baseline} relaciones')\n",
    "print(f'GLiNER2 t=0.3 + post-filter typed:        {n_gliner_only}')\n",
    "print(f'GLiREL filtered typed (threshold 0.10):    {n_glirel_only}')\n",
    "print(f'UNION (GLiNER2 typed ∪ GLiREL typed):     {n_best}')\n",
    "print(f'  ganancia vs baseline: +{n_gained} relaciones')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "bbe91ccd",
   "metadata": {
    "execution": {
     "iopub.execute_input": "2026-05-04T20:07:53.378024Z",
     "iopub.status.busy": "2026-05-04T20:07:53.377787Z",
     "iopub.status.idle": "2026-05-04T20:07:53.532848Z",
     "shell.execute_reply": "2026-05-04T20:07:53.531847Z"
    }
   },
   "outputs": [
    {
     "data": {
      "image/png": "[encoded data, 141024 characters]",
      "text/plain": [
       "<Figure size 1300x900 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "# Visualizar el grafo final\n",
    "G = nx.DiGraph()\n",
    "for typ, names in r['entities'].items():\n",
    "    for n in names:\n",
    "        G.add_node(n, type=typ)\n",
    "for h, rt, t in best_set:\n",
    "    G.add_node(h, type=name_to_type.get(h.lower().strip(), '?'))\n",
    "    G.add_node(t, type=name_to_type.get(t.lower().strip(), '?'))\n",
    "    G.add_edge(h, t, kind=rt)\n",
    "\n",
    "TYPE_COLOR = {'person': '#5DA5DA', 'organization': '#F17CB0', 'location': '#60BD68', '?': '#bbb'}\n",
    "fig, ax = plt.subplots(figsize=(13, 9))\n",
    "pos = nx.spring_layout(G, k=2.5, iterations=80, seed=42)\n",
    "cols = [TYPE_COLOR.get(G.nodes[n].get('type'), '#bbb') for n in G.nodes]\n",
    "nx.draw_networkx_nodes(G, pos, node_color=cols, node_size=1900, edgecolors='#333', linewidths=1.4, ax=ax)\n",
    "nx.draw_networkx_labels(G, pos, font_size=8, font_weight='bold', ax=ax)\n",
    "nx.draw_networkx_edges(G, pos, edge_color='#666', arrows=True, arrowsize=14, width=1.1, alpha=0.7, ax=ax, connectionstyle='arc3,rad=0.08')\n",
    "el = {(u,v): d['kind'] for u,v,d in G.edges(data=True)}\n",
    "nx.draw_networkx_edge_labels(G, pos, edge_labels=el, font_size=6.5, ax=ax,\n",
    "                              bbox=dict(boxstyle='round,pad=0.1', fc='white', ec='none', alpha=0.85))\n",
    "ax.set_title(f'Best combo: {G.number_of_nodes()} nodos, {G.number_of_edges()} aristas', fontsize=12)\n",
    "ax.axis('off')\n",
    "legend = [Patch(facecolor=c, edgecolor='#333', label=t) for t, c in TYPE_COLOR.items() if t != '?']\n",
    "ax.legend(handles=legend, loc='upper left', fontsize=10)\n",
    "plt.tight_layout(); plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3348a68",
   "metadata": {},
   "source": [
    "## Conclusion\n",
    "\n",
    "**Receta operativa para `graph_explorer` post-experimentos:**\n",
    "\n",
    "1. ⭐⭐⭐ **Naming snake_case verbal** (`works_at`, `headquartered_in`) — sin coste, gran impacto.\n",
    "2. ⭐⭐⭐ **Post-filter typed** (`{rel: (head_types, tail_types)}`) — elimina la mayoria de falsos absurdos. **Pure, sin coste.**\n",
    "3. ⭐⭐ **`include_confidence=True` + threshold por relacion** — evita el threshold global mediocre.\n",
    "4. ⭐⭐ **GLiREL como complemento** (cargado solo cuando sea necesario) con allowed_head/tail aplicado post-hoc.\n",
    "5. (no toques) Descripciones por relacion — sin efecto medible.\n",
    "\n",
    "**Stack final:**\n",
    "\n",
    "```python\n",
    "# 1. labels en snake_case verbal\n",
    "labels = ['works_at', 'ceo_of', 'president_of', 'headquartered_in', ...]\n",
    "schema = model.create_schema().entities(['person', 'organization', 'location']).relations(labels)\n",
    "\n",
    "# 2. extract con confidence\n",
    "r = model.extract(text, schema=schema, threshold=0.3, include_confidence=True)\n",
    "\n",
    "# 3. post-filter typed (gratis)\n",
    "filtered = filter_typed(r['relation_extraction'], name_to_type, ALLOWED)\n",
    "\n",
    "# 4. opcional: GLiREL como segundo opinador con allowed_head/tail filtrado post-hoc\n",
    "if rich_mode:\n",
    "    glirel_rels = glirel.predict_relations(tokens, labels=labels, threshold=0.0, ner=ner_spans, top_k=1)\n",
    "    glirel_filtered = [r for r in glirel_rels if compatible_types(r, ALLOWED, name_to_type)]\n",
    "    final_rels = union(filtered, glirel_filtered)\n",
    "```\n",
    "\n",
    "**Funciones para promover al registry** (proximo fn-constructor):\n",
    "1. `gliner2_load_model_py_datascience` (Apache 2.0)\n",
    "2. `extract_graph_gliner2_py_datascience` (NER+RE, threshold por relacion, include_confidence)\n",
    "3. `filter_relations_by_entity_types_py_core` (PURE — el ALLOWED filter)\n",
    "4. `merge_extraction_sources_py_core` (PURE — UNION de GLiNER2 + GLiREL)\n",
    "5. `extract_graph_hybrid_gliner2_glirel_py_pipelines` (composicion)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.13.7"
  },
  "widgets": {
   "application/vnd.jupyter.widget-state+json": {
    "state": {
     "29ff456bc669482b8fe75e1785c22515": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "_dom_classes": [],
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "HTMLModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/controls",
       "_view_module_version": "2.0.0",
       "_view_name": "HTMLView",
       "description": "",
       "description_allow_html": false,
       "layout": "IPY_MODEL_d8c16104875e4741a34e63cb88566924",
       "placeholder": "​",
       "style": "IPY_MODEL_e6d204f666cb4b0ea4a848ccc5e2908a",
       "tabbable": null,
       "tooltip": null,
       "value": " 390/390 [00:00&lt;00:00, 2139.04it/s, Materializing param=encoder.rel_embeddings.weight]"
      }
     },
     "330caa95ff7b488ca8de029d9f1ddbd4": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HBoxModel",
      "state": {
       "_dom_classes": [],
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "HBoxModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/controls",
       "_view_module_version": "2.0.0",
       "_view_name": "HBoxView",
       "box_style": "",
       "children": [
        "IPY_MODEL_d79bb1b10f9446609575c29452868b98",
        "IPY_MODEL_35b9fb87ac514793b0a547288926d545",
        "IPY_MODEL_29ff456bc669482b8fe75e1785c22515"
       ],
       "layout": "IPY_MODEL_7694c6ad523d4ce78688c0e4fbe0c551",
       "tabbable": null,
       "tooltip": null
      }
     },
     "35b9fb87ac514793b0a547288926d545": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "FloatProgressModel",
      "state": {
       "_dom_classes": [],
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "FloatProgressModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/controls",
       "_view_module_version": "2.0.0",
       "_view_name": "ProgressView",
       "bar_style": "success",
       "description": "",
       "description_allow_html": false,
       "layout": "IPY_MODEL_b0f52f523ffc411dbc25042b35a9c649",
       "max": 390,
       "min": 0,
       "orientation": "horizontal",
       "style": "IPY_MODEL_68722723ecfe4f00a089a4b79fc46ccb",
       "tabbable": null,
       "tooltip": null,
       "value": 390
      }
     },
     "456b05c0ef56498193b95ca55f553d1e": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "HTMLStyleModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "StyleView",
       "background": null,
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "68722723ecfe4f00a089a4b79fc46ccb": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "ProgressStyleModel",
      "state": {
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "ProgressStyleModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "StyleView",
       "bar_color": null,
       "description_width": ""
      }
     },
     "7694c6ad523d4ce78688c0e4fbe0c551": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     },
     "b0f52f523ffc411dbc25042b35a9c649": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     },
     "d79bb1b10f9446609575c29452868b98": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLModel",
      "state": {
       "_dom_classes": [],
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "HTMLModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/controls",
       "_view_module_version": "2.0.0",
       "_view_name": "HTMLView",
       "description": "",
       "description_allow_html": false,
       "layout": "IPY_MODEL_ee3542ad690b4c2b8790f0781be17b25",
       "placeholder": "​",
       "style": "IPY_MODEL_456b05c0ef56498193b95ca55f553d1e",
       "tabbable": null,
       "tooltip": null,
       "value": "Loading weights: 100%"
      }
     },
     "d8c16104875e4741a34e63cb88566924": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     },
     "e6d204f666cb4b0ea4a848ccc5e2908a": {
      "model_module": "@jupyter-widgets/controls",
      "model_module_version": "2.0.0",
      "model_name": "HTMLStyleModel",
      "state": {
       "_model_module": "@jupyter-widgets/controls",
       "_model_module_version": "2.0.0",
       "_model_name": "HTMLStyleModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "StyleView",
       "background": null,
       "description_width": "",
       "font_size": null,
       "text_color": null
      }
     },
     "ee3542ad690b4c2b8790f0781be17b25": {
      "model_module": "@jupyter-widgets/base",
      "model_module_version": "2.0.0",
      "model_name": "LayoutModel",
      "state": {
       "_model_module": "@jupyter-widgets/base",
       "_model_module_version": "2.0.0",
       "_model_name": "LayoutModel",
       "_view_count": null,
       "_view_module": "@jupyter-widgets/base",
       "_view_module_version": "2.0.0",
       "_view_name": "LayoutView",
       "align_content": null,
       "align_items": null,
       "align_self": null,
       "border_bottom": null,
       "border_left": null,
       "border_right": null,
       "border_top": null,
       "bottom": null,
       "display": null,
       "flex": null,
       "flex_flow": null,
       "grid_area": null,
       "grid_auto_columns": null,
       "grid_auto_flow": null,
       "grid_auto_rows": null,
       "grid_column": null,
       "grid_gap": null,
       "grid_row": null,
       "grid_template_areas": null,
       "grid_template_columns": null,
       "grid_template_rows": null,
       "height": null,
       "justify_content": null,
       "justify_items": null,
       "left": null,
       "margin": null,
       "max_height": null,
       "max_width": null,
       "min_height": null,
       "min_width": null,
       "object_fit": null,
       "object_position": null,
       "order": null,
       "overflow": null,
       "padding": null,
       "right": null,
       "top": null,
       "visibility": null,
       "width": null
      }
     }
    },
    "version_major": 2,
    "version_minor": 0
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
