14. Introducción a los modelos NER (Named Entity Recognition)#

NOTA1: Este documento es una traducción del documento que se puede ver en este enlace .

NOTA2: Se aconseja ejecutar los códigos que aquí se presentan en google colab si no se dispone de un ordenador potente y con GPU para agilizar la ejecución del código

El reconocimiento de entidades con nombre (NER, por sus siglas en inglés) es uno de los pilares fundamentales de la comprensión del lenguaje natural. Cuando los humanos leemos un texto, identificamos y categorizamos naturalmente las entidades con nombre en función del contexto y el conocimiento del mundo. Por ejemplo, en la oración “ Satya Nadella, director ejecutivo de Microsoft, habló en una conferencia en Seattle ”, reconocemos sin esfuerzo las referencias organizativas, personales y geográficas. Sin embargo, enseñar a las máquinas a replicar esta capacidad humana aparentemente intuitiva presenta varios desafíos. Afortunadamente, este problema se puede abordar de manera eficaz utilizando un modelo de aprendizaje automático previamente entrenado.

En esta publicación, aprenderá cómo resolver el problema NER con un modelo BERT utilizando solo unas pocas líneas de código Python.

14.1. La complejidad de los sistemas NER.#

El desafío del reconocimiento de entidades con nombre va mucho más allá de la simple comparación de patrones o las búsquedas en diccionarios. Varios factores clave contribuyen a su complejidad.

Uno de los desafíos más importantes es la dependencia del contexto : comprender cómo las palabras cambian de significado según el texto que las rodea. La misma palabra puede representar diferentes tipos de entidades según su contexto. Considere estos ejemplos:

  • “ Apple anunció nuevos productos ”. (Apple es una organización).

  • “ Comí una manzana en el almuerzo ”. (Manzana es un sustantivo común, no una entidad con nombre).

  • “ Apple Street está cerrada ”. (Apple es una ubicación).

Las entidades con nombre suelen estar formadas por varias palabras, lo que hace que la detección de límites sea otro desafío. Los nombres de las entidades pueden ser complejos, como por ejemplo:

  • Entidades corporativas: “Bank of America Corporation”

  • Nombres de productos: “iPhone 14 Pro Max”

  • Nombres de personas: “Martin Luther King Jr.”

Además, el lenguaje es dinámico y evoluciona continuamente. En lugar de memorizar lo que se considera una entidad, los modelos deben deducirlo del contexto. La evolución del lenguaje introduce nuevas entidades, como empresas emergentes, nuevos productos y términos recién acuñados.

Ahora, exploremos cómo los modelos NER de última generación abordan estos desafíos.

14.2. La evolución de la tecnología NER.#

La evolución de la tecnología NER refleja el avance más amplio del procesamiento del lenguaje natural. Los primeros enfoques se basaban en sistemas basados en reglas y en la comparación de patrones (definiendo patrones gramaticales, identificando mayúsculas y utilizando marcadores contextuales (por ejemplo, “el” antes de un nombre propio). Sin embargo, estas reglas solían ser numerosas, inconsistentes y difíciles de escalar.

Para mejorar la precisión, los investigadores introdujeron enfoques estadísticos, aprovechando modelos basados en probabilidad como los modelos ocultos de Markov (HMM) y los campos aleatorios condicionales (CRF) para identificar entidades nombradas.

Con el auge del aprendizaje profundo, las redes neuronales se convirtieron en el método preferido para el aprendizaje profundo. Inicialmente, las redes LSTM bidireccionales resultaron prometedoras. Sin embargo, la introducción de mecanismos de atención y modelos basados en transformers demostró ser aún más eficaz.

14.3. El enfoque revolucionaio de BERT para el NER#

BERT (Bidirectional Encoder Representations from Transformers) ha transformado fundamentalmente NER con varias innovaciones clave:

14.3.1. comprensión contextual.#

A diferencia de los modelos tradicionales que procesan el texto en una dirección, la naturaleza bidireccional de BERT le permite considerar tanto el texto anterior como el siguiente, lo que le permite capturar dependencias de largo alcance, comprender matices contextuales sutiles y manejar casos ambiguos de manera más eficaz.

14.3.2. Tokenización y unidades de subpalabras.#

Si bien no es exclusivo de BERT, su estrategia de tokenización de subpalabras le permite manejar palabras desconocidas y, al mismo tiempo, preservar la información morfológica. Esto reduce el tamaño del vocabulario y hace que el modelo sea adaptable a diferentes idiomas y dominios.

14.3.3. El mecanismo de eqtiquetado IOB#

Los resultados de NER se pueden representar de varias maneras, pero BERT utiliza el esquema de etiquetado Inside-Outside-Beginning (IOB):

*B marca el comienzo de una entidad.

  • I indica la continuación de una entidad.

  • O significa no-entidades.

Este método permite a BERT gestionar de manera eficaz entidades de varias palabras, entidades anidadas y entidades superpuestas.

14.4. Uso de DistilBERT con el Pipeline de Hugging Face#

La forma más sencilla de realizar NER es mediante pipelinela API de Hugging Face, que elimina gran parte de la complejidad y al mismo tiempo ofrece resultados potentes. A continuación, se muestra un ejemplo:

from transformers import pipeline

# Initialize the NER pipeline
ner_pipeline = pipeline("ner", 
                        model="dbmdz/bert-large-cased-finetuned-conll03-english",
                        aggregation_strategy="simple")

# Text example
text = "Apple CEO Tim Cook announced new iPhone models in California yesterday."

# Perform NER
entities = ner_pipeline(text)

# Print the results
for entity in entities:
    print(f"Entity: {entity['word']}")
    print(f"Type: {entity['entity_group']}")
    print(f"Confidence: {entity['score']:.4f}")
    print("-" * 30)
D:\MisTrabajos\IA_generativa\venv\Lib\site-packages\tqdm\auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
Some weights of the model checkpoint at dbmdz/bert-large-cased-finetuned-conll03-english were not used when initializing BertForTokenClassification: ['bert.pooler.dense.bias', 'bert.pooler.dense.weight']
- This IS expected if you are initializing BertForTokenClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing BertForTokenClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Entity: Apple
Type: ORG
Confidence: 0.9975
------------------------------
Entity: Tim Cook
Type: PER
Confidence: 0.9996
------------------------------
Entity: iPhone
Type: MISC
Confidence: 0.9932
------------------------------
Entity: California
Type: LOC
Confidence: 0.9997
------------------------------

Ahora, analicemos este código en detalle. Primero, inicializa la canalización:

ner_pipeline = pipeline("ner", 
                        model="dbmdz/bert-large-cased-finetuned-conll03-english",
                        aggregation_strategy="simple")

La función pipeline() crea una secuencia de comandos NER lista para usar. Esto es esencial porque, si bien BERT es un modelo de aprendizaje automático, el texto debe preprocesarse antes de que el modelo pueda procesarlo. Además, la salida del modelo debe convertirse a un formato utilizable. Una secuencia de comandos maneja estos pasos automáticamente.

El argumento “ner”especifica que desea el reconocimiento de entidades con nombre y model=”dbmdz/bert-large-cased-finetuned-conll03-english” carga un modelo entrenado previamente y ajustado específicamente para NER. El argumento final, aggregation_strategy=”simple”, garantiza que las subpalabras se fusionen en palabras completas, lo que hace que el resultado sea más legible.

La secuencia anterior devuelve una lista de diccionarios, donde cada diccionario contiene:

  • word:El texto de la entidad detectada

  • entity_group:El tipo de entidad (por ejemplo, PERpara persona, ORGpara organización)

  • score:Puntuación de confianza entre 0 y 1

  • starty end: Posiciones de los personajes en el texto original

14.5. Uso explícito de DistilBERT con AutoModelForTokenClassification.#

Para tener un mayor control sobre el proceso de NER, puede omitir la canalización y trabajar directamente con el modelo y el tokenizador. Este enfoque proporciona más flexibilidad y conocimiento del proceso. A continuación, se muestra un ejemplo:

from transformers import AutoTokenizer, AutoModelForTokenClassification
import torch

# Load model and tokenizer
model_name = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForTokenClassification.from_pretrained(model_name)

# Text example
text = "Google and Microsoft are competing in the AI space while Elon Musk founded SpaceX."

# Tokenize the text
inputs = tokenizer(text, return_tensors="pt", add_special_tokens=True)

# Get predictions
with torch.no_grad():
    outputs = model(**inputs)
    predictions = torch.argmax(outputs.logits, dim=2)

# Convert predictions to labels
label_list = model.config.id2label
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
predictions = predictions[0].tolist()

# Process results
current_entity = []
current_entity_type = None

for token, prediction in zip(tokens, predictions):
    if token.startswith("##"):
        if current_entity:
            current_entity.append(token[2:])
    else:
        if current_entity:
            print(f"Entity: {''.join(current_entity)}")
            print(f"Type: {current_entity_type}")
            print("-" * 30)
            current_entity = []

        if label_list[prediction] != "O":
            current_entity = [token]
            current_entity_type = label_list[prediction]
            
# Print final entity if exists
if current_entity:
    print(f"Entity: {''.join(current_entity)}")
    print(f"Type: {current_entity_type}")
Some weights of the model checkpoint at dbmdz/bert-large-cased-finetuned-conll03-english were not used when initializing BertForTokenClassification: ['bert.pooler.dense.bias', 'bert.pooler.dense.weight']
- This IS expected if you are initializing BertForTokenClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing BertForTokenClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Entity: Google
Type: I-ORG
------------------------------
Entity: Microsoft
Type: I-ORG
------------------------------
Entity: Elon
Type: I-PER
------------------------------
Entity: Musk
Type: I-PER
------------------------------
Entity: SpaceX
Type: I-ORG
------------------------------

Esta implementación es más detallada. Veamos cómo hacerlo paso a paso. Primero, se carga el modelo y el tokenizador:

model_name = "dbmdz/bert-large-cased-finetuned-conll03-english"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForTokenClassification.from_pretrained(model_name)

La clase AutoTokenizer selecciona automáticamente el tokenizador adecuado en función de la tarjeta del modelo , lo que garantiza la compatibilidad. Los tokenizadores son responsables de transformar el texto de entrada en tokens. AutoModelForTokenClassificationCarga un modelo ajustado para tareas de clasificación de tokens, que incluye tanto la arquitectura del modelo como los pesos entrenados previamente.

A continuación, preprocesa el texto de entrada:

inputs = tokenizer(text, return_tensors="pt", add_special_tokens=True)

Este paso convierte el texto en identificadores de token que el modelo puede procesar. Un token es normalmente una palabra, pero también puede ser una subpalabra. Por ejemplo, “sub-” y “-word” pueden reconocerse por separado aunque aparezcan como una sola palabra. El return_tensors=”pt”argumento devuelve la secuencia como tensores de PyTorch, mientras que add_special_tokens=Truegarantiza la inclusión de tokens [CLS]y [SEP]al principio y al final de la salida, que son requeridos por BERT.

Luego, ejecuta el modelo en el tensor de entrada:

with torch.no_grad():
    outputs = model(**inputs)
    predictions = torch.argmax(outputs.logits, dim=2)

El uso torch.no_grad() deshabilita el cálculo de gradiente durante la inferencia, lo que ahorra tiempo y memoria. La función torch.argmax(outputs.logits, dim=2)selecciona la etiqueta más probable para cada token. El tensor predictionses un tensor de números enteros.

Para convertir la salida del modelo en texto legible para humanos, preparamos un mapeo entre los índices de predicción y las etiquetas de entidad reales:

label_list = model.config.id2label
tokens = tokenizer.convert_ids_to_tokens(inputs["input_ids"][0])
predictions = predictions[0].tolist()

El diccionario model.config.id2labeles una asignación de índices de predicción a etiquetas de entidades reales. La función convert_ids_to_tokensconvierte los identificadores de tokens enteros nuevamente en texto legible. Dado que ejecuta el modelo con una sola línea de texto de entrada, solo se espera una secuencia de salida. Convertimos las predicciones a una lista de Python para facilitar su procesamiento.

Por último, reconstruya las predicciones de entidad utilizando un bucle. Dado que el tokenizador de BERT a veces divide las palabras en subpalabras (indicadas por “##”), las vuelve a fusionar para formar palabras completas. El tipo de entidad se determina utilizando el label_listdiccionario.

14.6. Mejores prácticas para la implementación de NER#

Realizar el reconocimiento de entidades con nombre (NER) es tan sencillo como se muestra arriba. Sin embargo, no es necesario que utilices el código exacto proporcionado. En concreto, puedes cambiar entre diferentes modelos (junto con el tokenizador correspondiente). Si necesitas un procesamiento más rápido, considera utilizar un modelo DistilBERT. Si la precisión es una prioridad, opta por un modelo BERT o RoBERTa más grande. Además, si tu entrada requiere conocimiento específico del dominio, puedes beneficiarte del uso de un modelo adaptado al dominio.

Si necesita procesar un gran volumen de texto para NER, puede mejorar la eficiencia procesando las entradas en lotes. Otras técnicas, como usar una GPU para acelerar o almacenar en caché los resultados de los textos a los que se accede con frecuencia, pueden mejorar aún más el rendimiento.

En un sistema de producción, también se debe implementar una lógica de manejo de errores adecuada, que incluya la validación de la entrada, el manejo de casos extremos como cadenas vacías y caracteres especiales, y la solución de otros problemas potenciales.

A continuación se muestra un ejemplo completo que incorpora estas mejores prácticas:

from transformers import pipeline
import torch
import logging
from typing import List, Dict

class NERProcessor:
    def __init__(self,
                 model_name: str = "dbmdz/bert-large-cased-finetuned-conll03-english",
                 confidence_threshold: float = 0.8):
        self.confidence_threshold = confidence_threshold
        try:
            self.device = "cuda" if torch.cuda.is_available() else "cpu"
            self.ner_pipeline = pipeline("ner", 
                                         model=model_name,
                                         aggregation_strategy="simple",
                                         device=self.device)
        except Exception as e:
            logging.error(f"Failed to initialize NER pipeline: {str(e)}")
            raise

    def process_text(self, text: str) -> List[Dict]:
        if not text or not isinstance(text, str):
            logging.warning("Invalid input text")
            return []

        try:
            # Get predictions
            entities = self.ner_pipeline(text)
            
            # Post-process results
            filtered_entities = [
                entity for entity in entities
                if entity['score'] >= self.confidence_threshold
            ]
            
            return filtered_entities            
        except Exception as e:
            logging.error(f"Error processing text: {str(e)}")
            return []


if __name__ == "__main__":
    # Initialize processor
    processor = NERProcessor()
    
    # Text example
    text = """
    Apple Inc. CEO Tim Cook announced new partnerships with Microsoft 
    and Google during a conference in New York City. The event was also 
    attended by Sundar Pichai and Satya Nadella.
    """
    
    # Process text
    results = processor.process_text(text)
    
    # Print results
    for entity in results:
        print(f"Entity: {entity['word']}")
        print(f"Type: {entity['entity_group']}")
        print(f"Confidence: {entity['score']:.4f}")
        print("-" * 30)
Some weights of the model checkpoint at dbmdz/bert-large-cased-finetuned-conll03-english were not used when initializing BertForTokenClassification: ['bert.pooler.dense.bias', 'bert.pooler.dense.weight']
- This IS expected if you are initializing BertForTokenClassification from the checkpoint of a model trained on another task or with another architecture (e.g. initializing a BertForSequenceClassification model from a BertForPreTraining model).
- This IS NOT expected if you are initializing BertForTokenClassification from the checkpoint of a model that you expect to be exactly identical (initializing a BertForSequenceClassification model from a BertForSequenceClassification model).
Entity: Apple Inc
Type: ORG
Confidence: 0.9995
------------------------------
Entity: Tim Cook
Type: PER
Confidence: 0.9997
------------------------------
Entity: Microsoft
Type: ORG
Confidence: 0.9996
------------------------------
Entity: Google
Type: ORG
Confidence: 0.9992
------------------------------
Entity: New York City
Type: LOC
Confidence: 0.9993
------------------------------
Entity: Sundar Pichai
Type: PER
Confidence: 0.9911
------------------------------
Entity: Satya Nadella
Type: PER
Confidence: 0.9961
------------------------------