LexiMus-BETO-per-v1 / cargar_modelo.py
LexiMusUSAL's picture
Subida inicial LexiMus-BETO-per-v1 [2/25]
c032fa2 verified
Raw
History Blame Contribute Delete
7.02 kB
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
cargar_modelo.py
────────────────
Ejemplo de uso completo de LexiMus-BETO-per-v1.
Carga el modelo NER transformer + Entity Ruler desde CSV y aplica
la inferencia sobre textos de prensa musical española histórica.
Requisitos:
pip install spacy>=3.8,<3.9 spacy-transformers
Uso:
python cargar_modelo.py
python cargar_modelo.py --texto "Anoche actuó la Orquesta Nacional"
python cargar_modelo.py --archivo mis_textos.txt
"""
import csv
import argparse
from pathlib import Path
import spacy
# ── Configuración ──────────────────────────────────────────────────────────────
ETIQUETAS = {"COMPOSITOR", "INTERPRETE", "CANTANTE", "AGRUPACION"}
COLORES_TERMINAL = {
"COMPOSITOR": "\033[94m", # azul
"INTERPRETE": "\033[93m", # amarillo
"CANTANTE": "\033[92m", # verde
"AGRUPACION": "\033[91m", # rojo
"RESET": "\033[0m",
}
EJEMPLOS = [
"El maestro Arbós dirigió anoche la Orquesta Sinfónica de Madrid interpretando la Sinfonía en re menor de Beethoven.",
"La soprano Conchita Supervía cosechó grandes ovaciones en el Teatro Real junto al tenor Miguel Fleta.",
"El Cuarteto Francés, formado por los profesores Francés, Outumuro, Del Campo y Fontova, ofreció una sesión de música de cámara.",
"Wagner ocupa un lugar central en el programa de la temporada, con la colaboración del Orfeón Pamplonés.",
"Falla presentó su nuevo ballet ante un público entregado; la coreografía corrió a cargo de la compañía de Diaghilev.",
]
# ── Funciones ──────────────────────────────────────────────────────────────────
def cargar_patrones_csv(ruta_csv: Path) -> list:
"""Lee el CSV del Entity Ruler y devuelve lista de patrones spaCy."""
patrones = []
vistos = set()
with open(ruta_csv, encoding="utf-8") as f:
reader = csv.DictReader(f)
for fila in reader:
etq = fila["etiqueta"]
if etq not in ETIQUETAS:
continue
textos_patron = {fila["texto"].strip()}
if fila.get("variante_limpia", "").strip():
textos_patron.add(fila["variante_limpia"].strip())
notas = fila.get("notas", "")
if "Apodo:" in notas:
apodo = notas.split("Apodo:")[-1].split("|")[0].strip()
if apodo:
textos_patron.add(apodo)
for t in textos_patron:
if not t or len(t) < 2:
continue
clave = (t.lower(), etq)
if clave in vistos:
continue
vistos.add(clave)
patrones.append({"label": etq, "pattern": t})
return patrones
def cargar_leximus(ruta_modelo: str | Path, ruta_csv: str | Path) -> spacy.language.Language:
"""
Carga el modelo LexiMus con Entity Ruler integrado.
Args:
ruta_modelo: Ruta al directorio del modelo spaCy (model-best/).
ruta_csv: Ruta al archivo entidades_ner_leximus.csv.
Returns:
nlp: Pipeline spaCy listo para inferencia.
"""
ruta_modelo = Path(ruta_modelo)
ruta_csv = Path(ruta_csv)
if not ruta_modelo.exists():
raise FileNotFoundError(f"Modelo no encontrado: {ruta_modelo}")
if not ruta_csv.exists():
raise FileNotFoundError(f"CSV de patrones no encontrado: {ruta_csv}")
print(f"Cargando modelo desde {ruta_modelo}...")
nlp = spacy.load(ruta_modelo)
# Eliminar Entity Ruler previo (puede tener patrones desactualizados)
if "entity_ruler" in nlp.component_names:
nlp.remove_pipe("entity_ruler")
patrones = cargar_patrones_csv(ruta_csv)
ruler = nlp.add_pipe("entity_ruler", last=True, config={"overwrite_ents": False})
ruler.add_patterns(patrones)
print(f"Sistema listo: NER transformer + Entity Ruler ({len(ruler)} patrones)")
return nlp
def analizar_texto(nlp: spacy.language.Language, texto: str, color: bool = True) -> list:
"""
Analiza un texto y devuelve las entidades musicales detectadas.
Args:
nlp: Pipeline cargado con cargar_leximus().
texto: Texto a analizar.
color: Si True, imprime con colores ANSI en terminal.
Returns:
Lista de dicts con keys: texto, etiqueta, inicio, fin.
"""
doc = nlp(texto)
entidades = [
{"texto": e.text, "etiqueta": e.label_, "inicio": e.start_char, "fin": e.end_char}
for e in doc.ents if e.label_ in ETIQUETAS
]
if color:
print(f"\nTexto: {texto[:100]}{'...' if len(texto) > 100 else ''}")
if entidades:
for e in entidades:
c = COLORES_TERMINAL.get(e["etiqueta"], "")
r = COLORES_TERMINAL["RESET"]
print(f" {c}[{e['etiqueta']}]{r} {e['texto']}")
else:
print(" (sin entidades detectadas)")
return entidades
def analizar_archivo(nlp: spacy.language.Language, ruta: str) -> None:
"""Analiza cada línea no vacía de un archivo de texto."""
ruta = Path(ruta)
lineas = [l.strip() for l in ruta.read_text(encoding="utf-8").splitlines() if l.strip()]
print(f"\nAnalizando {len(lineas)} textos de {ruta.name}...")
total_ents = 0
for linea in lineas:
ents = analizar_texto(nlp, linea)
total_ents += len(ents)
print(f"\nTotal: {total_ents} entidades detectadas en {len(lineas)} textos.")
# ── Main ───────────────────────────────────────────────────────────────────────
def main():
parser = argparse.ArgumentParser(description="LexiMus NER - inferencia sobre texto musical español")
parser.add_argument("--modelo", default=".", help="Ruta al directorio del modelo (por defecto: directorio actual)")
parser.add_argument("--csv", default="entidades_ner_leximus.csv", help="Ruta al CSV del Entity Ruler")
parser.add_argument("--texto", help="Texto a analizar directamente")
parser.add_argument("--archivo", help="Archivo .txt con un texto por línea")
args = parser.parse_args()
nlp = cargar_leximus(args.modelo, args.csv)
if args.texto:
analizar_texto(nlp, args.texto)
elif args.archivo:
analizar_archivo(nlp, args.archivo)
else:
# Ejecutar ejemplos por defecto
print("\n── Ejemplos de inferencia ─────────────────────────────────────────")
for ejemplo in EJEMPLOS:
analizar_texto(nlp, ejemplo)
if __name__ == "__main__":
main()