Pipeline NPL
  • Home
  • About
  • Report an issue
  • Buy me a coffee
  1. Pipeline para el Procesamiento del Lenguaje Natural (NPL) a través de la librería spaCy

Índice

  • 1 Preproceso
    • 1.1 Ingesta del documento
    • 1.2 Carga del modelo
      • 1.2.1 spaCy
    • 1.3 Fragmentación de la información
  • 2 Pipeline NPL
    • 2.1 Tokenization
    • 2.2 Stop Words
    • 2.3 Stemming and Lemmatization
    • 2.4 POS Tagging
      • 2.4.1 Selección de nombres
      • 2.4.2 Selección de cualqueir categoría gramatical
    • 2.5 NER
      • 2.5.1 Búsqueda de una entidad en el documento
      • 2.5.2 Visualizers
        • 2.5.2.1 Dependency parse
        • 2.5.2.2 Entity recognizer
      • 2.5.3 Selección de un tipo de entidad
      • 2.5.4 Diferencia entre POS y NER

Pipeline para el Procesamiento del Lenguaje Natural (NPL) a través de la librería spaCy

Autor/a

Mario Camacho

Fecha de publicación

25 agosto 2024

Fecha de modificación

27 agosto 2024

Resumen

La aplicación de nuevas fuentes de Inteligencia Artificial en el lenguaje humano está en auge desde la aparición de las IA Generativas de texto. Es por ello que se necesita tener un pipeline con los primeros pasos de NPL que debemos mantener en la resolución de problemas de mayor complejidad.

1 Preproceso

1.1 Ingesta del documento

Leemos el libro El Quijote.

Código
# Enviar una solicitud GET a la URL
response = requests.get("https://www.gutenberg.org/cache/epub/2000/pg2000.txt")

# Verificar si la solicitud fue exitosa
if response.status_code == 200:
    quijote = response.text
    # print("Éxito al leer el texto.")
else:
    print("Error al leer el texto.")

Nos ocupamos únicamente de los primeros seis capítulos de la primera parte de El Quijote.

Código
### Inicio del primer capítulo

# Palabra objetivo
palabra_start = "Primera parte del ingenioso hidalgo don Quijote de la Mancha"

# Expresión regular para capturar todo lo que va antes de un patrón de  búsqueda
patron = re.compile(rf"{palabra_start}\s*(.*)", re.DOTALL)

coincidencia = patron.search(quijote)

if coincidencia:
    quijote = coincidencia.group(1) # 0 incluye la palabra del search, 1 no lo incluye
    console.print("[bold]Inicio del primer capítulo:\n\n[/bold]", quijote[:251])
    # print("Inicio del texto:\n\n", quijote[:251])
else:
    console.print("[bold]Inicio del texto:\n\n[/bold]", quijote[:251])
    

### Inicio del sépimo capítulo (y final del sexto)
palabra_end = "Capítulo VII"

# patron = re.compile(rf"(.*)\b{re.escape(palabra_end)}\b", re.DOTALL) # .* es codicioso ("greedy"): captura la mayor cantidad posible de caracteres antes de la coincidir con la palabra, captura hasta la última aparición de la palabra buscada
patron = re.compile(rf"(.*?)\b{re.escape(palabra_end)}\b", re.DOTALL) # .* es no codicioso ("lazy"): captura la menor cantidad posible de caracteres y evitar que el resto del texto cooincida, se detiene en la primera coincidencia

coincidencia = patron.search(quijote)

if coincidencia:
    quijote = coincidencia.group(0)
    console.print("[bold]Final del sexto capítulo:\n\n[/bold]", quijote[-231:])
    quijote = coincidencia.group(1) # Elimino el patrón de búsqueda
else:
    console.print("[bold]Final del texto:\n\n[/bold]", quijote[-231:])
Inicio del primer capítulo:

 Capítulo primero. Que trata de la condición y ejercicio del famoso hidalgo
don Quijote de la Mancha

En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho
tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua
Final del sexto capítulo:

 o el cura en oyendo el nombre— si tal libro hubiera
mandado quemar; porque su autor fue uno de los famosos poetas del mundo, no
sólo de España, y fue felicísimo en la tradución de algunas fábulas de
Ovidio.




Capítulo VII

1.2 Carga del modelo

1.2.1 spaCy

What’s spaCy? spaCy es una biblioteca gratuita de código abierto para el procesamiento avanzado del lenguaje natural en Python.

La librería spaCy se basa en modelos de aprendizaje automático que se entrenaron con grandes cantidades de datos de texto etiquetados.

Descarga y carga del modelo de lenguaje a través de la librería spaCy:

# !python -m spacy download es_core_news_md
import spacy
nlp = spacy.load("es_core_news_md")

1.3 Fragmentación de la información

Podemos dividir el texto en párrafos.

Código
for parrafo in list(nlp(quijote).sents)[:3]: # No es necesario pasarlo a una lista, solo para coger un número de ejemplos
    console.print("[blue]\nSiguiente párrafo:\n\n[/blue]", parrafo)
    #print(parrafo)
Siguiente párrafo:

 Capítulo primero.
Siguiente párrafo:

 Que trata de la condición y ejercicio del famoso hidalgo
don Quijote de la Mancha

En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho
tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua,
rocín flaco y galgo corredor.
Siguiente párrafo:

 Una olla de algo más vaca que carnero,
salpicón las más noches, duelos y quebrantos los sábados, lantejas los
viernes, algún palomino de añadidura los domingos, consumían las tres
partes de su hacienda.

Y los párrafos en palabras.

Código
for parrafo in list(nlp(quijote).sents)[1:2]:
    console.print("[blue]\nSiguiente párrafo:\n[/blue]")
    # print(colored("\nSiguiente párrafo:\n",'blue'))
    for palabra in parrafo[:5]:
        print(palabra)
Siguiente párrafo:

Que
trata
de
la
condición

Y separar cada una de las frases.

Código
documento = nlp(quijote)
sents = list(documento.sents)[:3]
sents
[Capítulo primero.,
 Que trata de la condición y ejercicio del famoso hidalgo
 don Quijote de la Mancha
 
 En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho
 tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua,
 rocín flaco y galgo corredor.,
 Una olla de algo más vaca que carnero,
 salpicón las más noches, duelos y quebrantos los sábados, lantejas los
 viernes, algún palomino de añadidura los domingos, consumían las tres
 partes de su hacienda.]

2 Pipeline NPL

text = quijote

2.1 Tokenization

Código
tokens = word_tokenize(text)
print(tokens[:10])
['Capítulo', 'primero', '.', 'Que', 'trata', 'de', 'la', 'condición', 'y', 'ejercicio']

2.2 Stop Words

Código
stop_words = set(stopwords.words('spanish'))
tokens = [w for w in tokens if w.lower() not in stop_words]
tokens_ejemplos = tokens
print(tokens[:10])
['Capítulo', 'primero', '.', 'trata', 'condición', 'ejercicio', 'famoso', 'hidalgo', 'don', 'Quijote']

2.3 Stemming and Lemmatization

https://spacy.io/models/es
https://github.com/explosion/spacy-models/releases?q=es_core_web_md&expanded=true

Código
stemmer = SnowballStemmer("spanish")
tokens = [stemmer.stem(palabra) for palabra in tokens]
tokens[:10]
['capitul',
 'primer',
 '.',
 'trat',
 'condicion',
 'ejercici',
 'famos',
 'hidalg',
 'don',
 'quijot']

2.4 POS Tagging

Part of speech.

Etiqueta las palabras de un texto determinado según sus respectivos tipos de palabras, como sustantivos, adjetivos, adverbios y verbos.

El etiquetado POS es el proceso de identificar la categoría gramatical (como verbo, sustantivo, adjetivo, etc.) de cada palabra en una oración.

Las etiquetas que spaCy utiliza se basan en el trabajo realizado por Universal Dependencies, un repositorio común que se puede utilizar para entrenar modelos como spaCy. La página de Dependencias universales tiene información sobre los corpus disponibles para cada idioma.

Las etiquetas (atributos del token) que spaCy crea para cada token podemos encontrarlas en https://spacy.io/api/token#attributes.

Merece la pena prestar especial atención al atributo pos_, que nos permitirá encontrar palabras según su categoría gramatical.

Universal POS tags

Ejemplo.

Código
print("Token:", nlp(text)[0])
Token: Capítulo
propiedades = ['text', 'lang_', 'pos_', 'is_digit', 'is_lower', 'is_upper', 'is_sent_start', 'is_sent_end',
               'like_email', 'like_url', # Espero no encontrar ninguna de estas etiquetas en El Quijote
               'sentiment', 'sent']
Código
ejemplo_01 = nlp(text)[0]

for attr in propiedades:
    print("obj.%s = %r" % (attr, getattr(ejemplo_01, attr)))
obj.text = 'Capítulo'
obj.lang_ = 'es'
obj.pos_ = 'PROPN'
obj.is_digit = False
obj.is_lower = False
obj.is_upper = False
obj.is_sent_start = True
obj.is_sent_end = False
obj.like_email = False
obj.like_url = False
obj.sentiment = 0.0
obj.sent = Capítulo primero.

Ejemplo.

Código
ejemplo_02 = nlp(re.sub(r'[^\w\s]', '', text))[:15]

# Encabezado
print(f'{"text":<12} {"lemma_":<12}{"pos":<12}{"pos_":<12}{"tag_":<12}{"dep_":<12}{"shape_":<12}{"is_alpha":<12}{"is_stop":<12}')
print("="*12*9)

# Loop que imprime los datos en formato de tabla
for token in ejemplo_02:
    print(f'{token.text:<12} {token.lemma_:<11} {token.pos:<11} {token.pos_:<11} {token.tag_:<11} {token.dep_:<12}'
          f'{token.shape_:<11} {token.is_alpha:<11} {token.is_stop:<11}')
text         lemma_      pos         pos_        tag_        dep_        shape_      is_alpha    is_stop     
============================================================================================================
Capítulo     Capítulo    96          PROPN       PROPN       nsubj       Xxxxx       1           0          
primero      primero     84          ADJ         ADJ         advmod      xxxx        1           1          
Que          Que         98          SCONJ       SCONJ       nsubj       Xxx         1           1          
trata        tratar      100         VERB        VERB        acl         xxxx        1           1          
de           de          85          ADP         ADP         case        xx          1           1          
la           el          90          DET         DET         det         xx          1           1          
condición    condición   92          NOUN        NOUN        obj         xxxx        1           0          
y            y           89          CCONJ       CCONJ       cc          x           1           1          
ejercicio    ejercicio   92          NOUN        NOUN        conj        xxxx        1           0          
del          del         85          ADP         ADP         case        xxx         1           1          
famoso       famoso      84          ADJ         ADJ         amod        xxxx        1           0          
hidalgo      hidalgo     92          NOUN        NOUN        nmod        xxxx        1           0          

           
          103         SPACE       SPACE       dep         
          0           0          
don          don         92          NOUN        NOUN        appos       xxx         1           0          
Quijote      Quijote     96          PROPN       PROPN       flat        Xxxxx       1           0          

2.4.1 Selección de nombres

En nuestro ejemplo anterior:

Código
nouns = []
for token in ejemplo_02:
    if token.pos_ == 'NOUN':
        nouns.append(token.lemma_)

nouns_total = Counter(nouns)

df = pd.DataFrame(nouns_total.most_common(), columns=['noun', 'count'])
df.style.hide(axis="index")
noun count
condición 1
ejercicio 1
hidalgo 1
don 1

En nuestro texto completo:

Código
nouns = []
for token in nlp(text):
    if token.pos_ == 'NOUN':
        nouns.append(token.lemma_)

nouns_total = Counter(nouns)

df = pd.DataFrame(nouns_total.most_common(), columns=['token', 'frecuencia'])
df[:5].style.hide(axis="index")
token frecuencia
caballero 61
don 56
libro 48
señor 38
cura 33

2.4.2 Selección de cualqueir categoría gramatical

Consultamos la Universal POS tags y definimos la categoría que queremos extraer.

categoria = 'ADV'
Código
lista_categoria = []
for token in nlp(text):
    if token.pos_ == categoria:
        lista_categoria.append(token.lemma_)

categoria_total = Counter(lista_categoria)

df = pd.DataFrame(categoria_total.most_common(), columns=['token', 'frecuencia'])
df[:5].style.hide(axis="index")
token frecuencia
no 186
más 61
tanto 50
así 44
mucho 33

2.5 NER

Named-Entity Recognition.

NER es el proceso de identificar entidades nombradas (como nombres de personas, lugares, organizaciones, etc.) en un texto. La biblioteca spaCy es particularmente útil para esta tarea.

Cada modelo de la librería spaCy tiene sus propias entidades, que se encuentran en la sección Label Scheme de la documentación del modelo. Para modelo cargado las entidades se almacenan con las etiqetas LOC, ORG, PER y MISC. (lugares, organizacoines, personas y un comodín para las tres anteriores)

Código
#code-fold: false
getattr(nlp.get_pipe('ner'), 'labels')
('LOC', 'MISC', 'ORG', 'PER')

Todas las entidades nombradas se encuentran en la propiedad document.ents.

documento = nlp(text)
Código
for named_entity in list(documento.ents)[:10]:
    print(named_entity, named_entity.label_)
Que trata de la condición MISC
hidalgo PER
la Mancha LOC
Una olla de algo más vaca MISC
El resto della MISC
Tenía en su casa una MISC
Frisaba LOC
Quieren PER
Quijada PER
Quesada LOC

2.5.1 Búsqueda de una entidad en el documento

Podemos encontrar una entidad dentro de su propio contexto.

iba aquí

Código
get_ner_in_context('hidalgo', nlp(quijote), desired_ner_labels = False)

Resultado 1.

Tipo de entidad: PER

Contexto:

Que trata de la condición y ejercicio del famoso hidalgo don Quijote de la Mancha En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor.

Resultado 2.

Tipo de entidad: PER

Contexto:

El labrador estaba admirado oyendo aquellos disparates; y, quitándole la visera, que ya estaba hecha pedazos de los palos, le limpió el rostro, que le tenía cubierto de polvo; y apenas le hubo limpiado, cuando le conoció y le dijo: — Señor Quijana —que así se debía de llamar cuando él tenía juicio y no había pasado de hidalgo sosegado a caballero andante—, ¿quién ha puesto a vuestra merced desta suerte? Pero él seguía con su romance a cuanto le preguntaba.

Resultado 3.

Tipo de entidad: PER

Contexto:

A esto respondió el labrador: — Mire vuestra merced, señor, pecador de mí, que yo no soy don Rodrigo de Narváez, ni el marqués de Mantua, sino Pedro Alonso, su vecino; ni vuestra merced es Valdovinos, ni Abindarráez, sino el honrado hidalgo del señor Quijana.

2.5.2 Visualizers

https://spacy.io/usage/visualizers

Podemos usar sus demos online que actualmente usan la versión small del modelo en español.

  • https://demos.explosion.ai/displacy
  • https://demos.explosion.ai/displacy-ent

2.5.2.1 Dependency parse

Análisis sintáctico de dependencia. Es el visualizador por defecto si no especificamos ningún estilo.

Visualizamos el texto Cuando Mario empezó a trabajar en este pipeline, poca gente imaginaba el alcance. como ejemplo.

# options: https://spacy.io/api/top-level#options-dep
options = {"compact": True,
           "color": "green",
           "bg:": "red", # no hace caso
           "arrow_width": 5,
           "word_spacing": 20,
           "distance": 120}

doc = nlp(text_for_visual)
displacy.render(doc, options=options) # = displacy.render(doc, style="dep")
Cuando SCONJ Mario PROPN empezó VERB a ADP trabajar VERB en ADP este DET pipeline, NOUN poca DET gente NOUN imaginaba VERB el DET alcance. NOUN mark nsubj advcl mark xcomp case det obl det nsubj det obj

2.5.2.2 Entity recognizer

Visualizando el reconocedor de entidades.

Usamos dos modelos distintos. Cada uno cuenta con sus propias entidades identificadas en el Label Scheme de cada modelo.

Visualizamos el texto María se fue en 2020 al río Guadiana a pescar peces coloridos mientras Mario iban a por bebida al supermercado Mercado. como ejemplo.

  • El primer modelo, es_core_news_md, el que hemos estado usando hasta ahora. Con cuatro entidades NER: LOC, MISC, ORG y PER.
# options: https://spacy.io/api/top-level#displacy_options-ent

colors = {"PER": "linear-gradient(90deg, #aa9cfc, #fc9ce7)",
          "LOC": "linear-gradient(90deg, orange, lightblue)"}
options = {#"ents": ["PER"], # si quisiéramos especificar las entidades que queremos mostrar
           "colors": colors}

doc = nlp(text_for_visual)
#displacy.server(doc, style="ent", options=options, auto_select_port=True)
displacy.render(doc, style="ent", options=options)
María PER se fue en 2020 al río Guadiana LOC a pescar peces coloridos mientras Mario PER iban a por bebida al supermercado Mercado LOC .
  • El segundo modelo, en_core_web_sm, en inglés. Con muchas más entidades con NER: CARDINAL, DATE, …, WORK_OF_ART
# options: https://spacy.io/api/top-level#displacy_options-ent

colors = {"PERSON": "linear-gradient(90deg, #aa9cfc, #fc9ce7)",
          "DATE": "linear-gradient(90deg, white, green)",
          "GPE": "linear-gradient(90deg, orange, lightblue)"}
options = {#"ents": ["PER"], # si quisiéramos especificar las entidades que queremos mostrar
           "colors": colors}

nlp_en = spacy.load("en_core_web_sm")
doc = nlp_en(text_for_visual)
displacy.render(doc, style="ent", options=options)
María se fue PERSON en 2020 DATE al río Guadiana PERSON a pescar peces coloridos mientras Mario PERSON iban a por bebida al supermercado Mercado PERSON .

2.5.3 Selección de un tipo de entidad

Podemos identificar el tipo de entidades que queremos buscar.

Ejemplo.

LOC

Código
print(f"{nlp(text).ents[2]} - {repr(nlp(text).ents[2].label_)}")
la Mancha - 'LOC'

Ejemplo.

PER

Código
pers = []
for named_entity in nlp(text).ents:
    if named_entity.label_  == 'PER':
        pers.append(named_entity.lemma_)

pers_total = Counter(pers)

df = pd.DataFrame(pers_total.most_common(), columns=['named_entity', 'frecuencia'])
df[:6]
named_entity frecuencia
0 Andrés 7
1 Nicolás 5
2 señor caballero 5
3 Amadís de Gaula 4
4 Amadís 4
5 Rocinante 4

2.5.4 Diferencia entre POS y NER

Podemos ver la diferencia entre POS y NER con el siguiente ejemplo.

Ejemplo.

María se fue en 2020 al río Guadiana a pescar peces coloridos mientras Mario iban a por bebida al supermercado Mercado.

Código
propiedades = ['text', 'pos_']

print('POS')
for token in ejemplo[:6]:
    print('---')
    for attr in propiedades:
        print("obj.%s = %r" % (attr, getattr(token, attr)))


print('NER')
print('---')
for named_entity in list(ejemplo.ents):
   print(f"{named_entity} - label: {repr(named_entity.label_)}")
POS
---
obj.text = 'María'
obj.pos_ = 'PROPN'
---
obj.text = 'se'
obj.pos_ = 'PRON'
---
obj.text = 'fue'
obj.pos_ = 'VERB'
---
obj.text = 'en'
obj.pos_ = 'ADP'
---
obj.text = '2020'
obj.pos_ = 'NOUN'
---
obj.text = 'al'
obj.pos_ = 'ADP'
NER
---
María - label: 'PER'
Guadiana - label: 'LOC'
Mario - label: 'PER'
Mercado - label: 'LOC'

Referencias

1.
Walsh, M. Introduction to Cultural Analytics & Text Analysis. (2023).
2.
Ates, H. A Comprehensive Guide to Natural Language Processing (NLP) with Python. (2023).
 
  • License