¿Cómo funciona el lematizante spacy?

Para la lematización, spacy tiene una lista de palabras : adjetivos, adverbios, verbos … y también listas para excepciones: adverbs_irreg … para los regulares hay un conjunto de reglas

Tomemos como ejemplo la palabra “wide”

Como es un adjetivo, la regla para la lematización debe tomarse de esta lista:

ADJECTIVE_RULES = [ ["er", ""], ["est", ""], ["er", "e"], ["est", "e"] ] 

Según entiendo el proceso será así:

1) Obtenga la etiqueta POS de la palabra para saber si es un sustantivo, un verbo …
2) Si la palabra está en la lista de casos irregulares, se reemplaza directamente si no se aplica una de las reglas.

Ahora, ¿cómo se decide usar “er” -> “e” en lugar de “er” -> “” para obtener “wide” y no “wid”?

Aquí se puede probar.

Comencemos con la definición de clase: https://github.com/explosion/spaCy/blob/develop/spacy/lemmatizer.py

Clase

Comienza con la inicialización de 3 variables:

 class Lemmatizer(object): @classmethod def load(cls, path, index=None, exc=None, rules=None): return cls(index or {}, exc or {}, rules or {}) def __init__(self, index, exceptions, rules): self.index = index self.exc = exceptions self.rules = rules 

Ahora, mirando el self.exc para inglés, vemos que apunta a https://github.com/explosion/spaCy/tree/develop/spacy/lang/en/lemmatizer/ init .py donde está cargando archivos desde el directorio https://github.com/explosion/spaCy/tree/master/spacy/en/lemmatizer

¿Por qué Spacy no acaba de leer un archivo?

Lo más probable es que la statement de la cadena en el código sea más rápida que la transmisión de cadenas a través de I / O.


¿De dónde vienen estos índices, excepciones y reglas?

Mirándolo de cerca, todos parecen provenir del original Princeton WordNet https://wordnet.princeton.edu/man/wndb.5WN.html

Reglas

Mirándolo más de cerca, las reglas en https://github.com/explosion/spaCy/tree/develop/spacy/lang/en/lemmatizer/_lemma_rules.py son similares a las reglas nltk de nltk https: // github. com / nltk / nltk / blob / develop / nltk / corpus / reader / wordnet.py # L1749

Y estas reglas provienen originalmente del software Morphy https://wordnet.princeton.edu/man/morphy.7WN.html

Además, spacy había incluido algunas reglas de puntuación que no son de Princeton Morphy:

 PUNCT_RULES = [ ["“", "\""], ["”", "\""], ["\u2018", "'"], ["\u2019", "'"] ] 

Excepciones

En cuanto a las excepciones, se almacenaron en los archivos *_irreg.py en spacy y parece que también provienen de Princeton Wordnet.

Es evidente si nos fijamos en un espejo de los archivos originales de WordNet .exc (exclusión) (por ejemplo, https://github.com/extjwnl/extjwnl-data-wn21/blob/master/src/main/resources/net/sf /extjwnl/data/wordnet/wn21/adj.exc ) y si descarga el paquete wordnet de nltk , vemos que es la misma lista:

 alvas@ubi:~/nltk_data/corpora/wordnet$ ls adj.exc cntlist.rev data.noun index.adv index.verb noun.exc adv.exc data.adj data.verb index.noun lexnames README citation.bib data.adv index.adj index.sense LICENSE verb.exc alvas@ubi:~/nltk_data/corpora/wordnet$ wc -l adj.exc 1490 adj.exc 

Índice

Si nos fijamos en el index del lematizante de Spacy, vemos que también proviene de Wordnet, por ejemplo, https://github.com/explosion/spaCy/tree/develop/spacy/lang/en/lemmatizer/_adjectives.py y el contenido Copia distribuida de wordnet en nltk :

 alvas@ubi:~/nltk_data/corpora/wordnet$ head -n40 data.adj 1 This software and database is being provided to you, the LICENSEE, by 2 Princeton University under the following license. By obtaining, using 3 and/or copying this software and database, you agree that you have 4 read, understood, and will comply with these terms and conditions.: 5 6 Permission to use, copy, modify and distribute this software and 7 database and its documentation for any purpose and without fee or 8 royalty is hereby granted, provided that you agree to comply with 9 the following copyright notice and statements, including the disclaimer, 10 and that the same appear on ALL copies of the software, database and 11 documentation, including modifications that you make for internal 12 use or for distribution. 13 14 WordNet 3.0 Copyright 2006 by Princeton University. All rights reserved. 15 16 THIS SOFTWARE AND DATABASE IS PROVIDED "AS IS" AND PRINCETON 17 UNIVERSITY MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR 18 IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PRINCETON 19 UNIVERSITY MAKES NO REPRESENTATIONS OR WARRANTIES OF MERCHANT- 20 ABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE 21 OF THE LICENSED SOFTWARE, DATABASE OR DOCUMENTATION WILL NOT 22 INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR 23 OTHER RIGHTS. 24 25 The name of Princeton University or Princeton may not be used in 26 advertising or publicity pertaining to distribution of the software 27 and/or database. Title to copyright in this software, database and 28 any associated documentation shall at all times remain with 29 Princeton University and LICENSEE agrees to preserve same. 00001740 00 a 01 able 0 005 = 05200169 n 0000 = 05616246 n 0000 + 05616246 n 0101 + 05200169 n 0101 ! 00002098 a 0101 | (usually followed by `to') having the necessary means or skill or know-how or authority to do something; "able to swim"; "she was able to program her computer"; "we were at last able to buy a car"; "able to get a grant for the project" 00002098 00 a 01 unable 0 002 = 05200169 n 0000 ! 00001740 a 0101 | (usually followed by `to') not having the necessary means or skill or know-how; "unable to get to town without a car"; "unable to obtain funds" 00002312 00 a 02 abaxial 0 dorsal 4 002 ;c 06037666 n 0000 ! 00002527 a 0101 | facing away from the axis of an organ or organism; "the abaxial surface of a leaf is the underside or side facing away from the stem" 00002527 00 a 02 adaxial 0 ventral 4 002 ;c 06037666 n 0000 ! 00002312 a 0101 | nearest to or facing toward the axis of an organ or organism; "the upper side of a leaf is known as the adaxial surface" 00002730 00 a 01 acroscopic 0 002 ;c 06066555 n 0000 ! 00002843 a 0101 | facing or on the side toward the apex 00002843 00 a 01 basiscopic 0 002 ;c 06066555 n 0000 ! 00002730 a 0101 | facing or on the side toward the base 00002956 00 a 02 abducent 0 abducting 0 002 ;c 06080522 n 0000 ! 00003131 a 0101 | especially of muscles; drawing away from the midline of the body or from an adjacent part 00003131 00 a 03 adducent 0 adductive 0 adducting 0 003 ;c 06080522 n 0000 + 01449236 v 0201 ! 00002956 a 0101 | especially of muscles; bringing together or drawing toward the midline of the body or toward an adjacent part 00003356 00 a 01 nascent 0 005 + 07320302 n 0103 ! 00003939 a 0101 & 00003553 a 0000 & 00003700 a 0000 & 00003829 a 0000 | being born or beginning; "the nascent chicks"; "a nascent insurgency" 00003553 00 s 02 emergent 0 emerging 0 003 & 00003356 a 0000 + 02625016 v 0102 + 00050693 n 0101 | coming into existence; "an emergent republic" 00003700 00 s 01 dissilient 0 002 & 00003356 a 0000 + 07434782 n 0101 | bursting open with force, as do some ripe seed vessels 

Sobre la base de que el diccionario, las excepciones y las reglas que utiliza el lematizador spacy son en gran parte de Princeton WordNet y su software Morphy, podemos ver la implementación real de cómo spacy aplica las reglas mediante el índice y las excepciones.

Regresamos a la https://github.com/explosion/spaCy/blob/develop/spacy/lemmatizer.py

La acción principal proviene de la función en lugar de la clase Lemmatizer :

 def lemmatize(string, index, exceptions, rules): string = string.lower() forms = [] # TODO: Is this correct? See discussion in Issue #435. #if string in index: # forms.append(string) forms.extend(exceptions.get(string, [])) oov_forms = [] for old, new in rules: if string.endswith(old): form = string[:len(string) - len(old)] + new if not form: pass elif form in index or not form.isalpha(): forms.append(form) else: oov_forms.append(form) if not forms: forms.extend(oov_forms) if not forms: forms.append(string) return set(forms) 

¿Por qué el método lemmatize fuera de la clase Lemmatizer ?

No estoy seguro, pero tal vez, es para asegurar que la función de lematización se pueda llamar fuera de una instancia de clase, pero dado que existen @staticmethod y @classmethod , quizás haya otras consideraciones sobre por qué la función y la clase se han desacoplado.

Morphy vs Spacy

Comparando la función lemmatize morphy() función morphy morphy() en nltk (que originalmente proviene de http://blog.osteele.com/2004/04/pywordnet-20/ creado hace más de una década), morphy() , the Los principales procesos en el puerto Python de Oliver Steele de la red de WordNet son:

  1. Consulta las listas de excepciones.
  2. Aplique las reglas una vez a la entrada para obtener y1, y2, y3, etc.
  3. Devuelva todo lo que está en la base de datos (y verifique el original también)
  4. Si no hay coincidencias, siga aplicando las reglas hasta que encontremos una coincidencia.
  5. Devuelve una lista vacía si no podemos encontrar nada.

Para spacy , posiblemente aún esté en desarrollo, dado el TODO en la línea https://github.com/explosion/spaCy/blob/develop/spacy/lemmatizer.py#L76

Pero el proceso general parece ser:

  1. Busque las excepciones, consígalos si el lema está en la lista de excepciones si la palabra está en ella.
  2. Aplicar las reglas
  3. Guarda los que están en las listas de índice.
  4. Si no hay un lema en el paso 1-3, entonces solo haga un seguimiento de las palabras que están fuera del vocabulario (OOV) y también agregue la cadena original a las formas del lema.
  5. Devuelve las formas de lemma.

En términos de manejo de OOV, spacy devuelve la cadena original si no se encuentra una forma nltk , en ese sentido, la implementación nltk de morphy hace lo mismo, por ejemplo

 >>> from nltk.stem import WordNetLemmatizer >>> wnl = WordNetLemmatizer() >>> wnl.lemmatize('alvations') 'alvations' 

Comprobación de infinitivo antes de la lematización

Posiblemente, otro punto de diferencia es cómo morphy y spacy deciden qué POS asignar a la palabra. En ese sentido, spacy coloca una regla de lingüística en Lemmatizer() para decidir si una palabra es la forma básica y omite la lematización por completo si la palabra ya está en la forma infinitiva (is_base_form ()) , esto ahorrará un poco si La lematización debía hacerse para todas las palabras en el corpus y una buena parte de ellas son infinitivas (ya la forma del lema).

Pero eso es posible en spacy porque le permitió al lematizante acceder al TPV que está estrechamente relacionado con algunas reglas morfológicas. Si bien para morphy aunque es posible descubrir alguna morfología utilizando las tags POS de PTB de grano fino, aún es necesario hacer un esfuerzo para determinar qué formas son infinitivas.

Generalment, las 3 señales principales de las características de morfología deben ser identificadas en la etiqueta POS:

  • persona
  • número
  • género

Actualizado

SpaCy hizo cambios en su lematizante después de la respuesta inicial (12 de mayo 17). Creo que el propósito era hacer que la lematización fuera más rápida sin búsquedas ni procesamiento de reglas.

Entonces, hacen un pre-lematismo de las palabras y las dejan en una tabla hash de búsqueda para hacer la recuperación O (1) de las palabras que tienen pre-lemmatizadas https://github.com/explosion/spaCy/blob/master/spacy/lang /en/lemmatizer/lookup.py

Además, en un esfuerzo por unificar los lematizadores en todos los idiomas, el lematizador ahora se encuentra en https://github.com/explosion/spaCy/blob/develop/spacy/lemmatizer.py#L92

Pero los pasos subyacentes de la lematización mencionados anteriormente aún son relevantes para la versión actual de 4d2d7d586608ddc0bcb2857fb3c2d0d4c151ebfc ( 4d2d7d586608ddc0bcb2857fb3c2d0d4c151ebfc )


Epílogo

Supongo que ahora que sabemos que funciona con reglas lingüísticas y todo, la otra pregunta es “¿hay algún método no basado en reglas para la lematización?”

Pero incluso antes de responder la pregunta anterior, “¿Qué es exactamente un lema?” ¿Podría la mejor pregunta para preguntar.

TLDR: spaCy comprueba si el lema que intenta generar se encuentra en la lista conocida de palabras o excepciones para esa parte del discurso.

Respuesta larga:

Revise el archivo lemmatizer.py , específicamente la función lemmatize en la parte inferior.

 def lemmatize(string, index, exceptions, rules): string = string.lower() forms = [] forms.extend(exceptions.get(string, [])) oov_forms = [] for old, new in rules: if string.endswith(old): form = string[:len(string) - len(old)] + new if not form: pass elif form in index or not form.isalpha(): forms.append(form) else: oov_forms.append(form) if not forms: forms.extend(oov_forms) if not forms: forms.append(string) return set(forms) 

Para los adjetivos en inglés, por ejemplo, incluye la cadena que estamos evaluando, el index de adjetivos conocidos, las exceptions y las rules , como se ha mencionado, de este directorio (para el modelo en inglés).

Lo primero que hacemos en lemmatize después de hacer que la cadena esté en minúscula es verificar si la cadena está en nuestra lista de excepciones conocidas, que incluye reglas de lema para palabras como “peor” -> “malo”.

Luego, repasamos nuestras rules y aplicamos cada una a la cadena si es aplicable. Para la palabra wider , aplicaríamos las siguientes reglas:

 ["er", ""], ["est", ""], ["er", "e"], ["est", "e"] 

y daríamos salida a las siguientes formas: ["wid", "wide"] .

Luego, verificamos si esta forma está en nuestro index de adjetivos conocidos. Si es así, lo adjuntamos a los formularios. De lo contrario, lo agregamos a oov_forms , que supongo que es corto para el vocabulario. wide está en el índice, por lo que se agrega. wid se agrega a oov_forms .

Por último, devolvemos un conjunto de los lemas encontrados, o cualquier lema que coincidiera con las reglas pero que no estuvieran en nuestro índice, o simplemente la palabra en sí.

El enlace de palabra-lematización que publicaste anteriormente funciona para wider , porque wide está en el índice de palabras. Intente algo como que He is blandier than I. SpaCy marcará más blandier (palabra que blandier ) como adjetivo, pero no está en el índice, por lo que simplemente volverá más blandier como el lema.

Hay un conjunto de reglas y un conjunto de palabras conocidas para cada tipo de palabra (adjetivo, sustantivo, verbo, adverbio). El mapeo ocurre aquí :

 INDEX = { "adj": ADJECTIVES, "adv": ADVERBS, "noun": NOUNS, "verb": VERBS } EXC = { "adj": ADJECTIVES_IRREG, "adv": ADVERBS_IRREG, "noun": NOUNS_IRREG, "verb": VERBS_IRREG } RULES = { "adj": ADJECTIVE_RULES, "noun": NOUN_RULES, "verb": VERB_RULES, "punct": PUNCT_RULES } 

Luego, en esta línea en lemmatizer.py, se cargan el índice, las reglas y las reglas correctas (excl, creo que significa excepciones, por ejemplo, ejemplos irregulares):

 lemmas = lemmatize(string, self.index.get(univ_pos, {}), self.exc.get(univ_pos, {}), self.rules.get(univ_pos, [])) 

Toda la lógica restante está en la función lematizar y es sorprendentemente corta. Realizamos las siguientes operaciones:

  1. Si hay una excepción (es decir, la palabra es irregular) que incluye la cadena provista, úsela y agréguela a las formas lematizadas.
  2. Para cada regla en el orden en que se dan para el tipo de palabra seleccionado, compruebe si coincide con la palabra dada. Si lo hace intenta aplicarlo.

    2a. Si después de aplicar la regla, la palabra está en la lista de palabras conocidas (es decir, índice), agréguela a las formas lematizadas de la palabra

    2b. De lo contrario, agregue la palabra a una lista separada llamada oov_forms (aquí creo que oov significa “fuera de vocabulario”)

  3. En caso de que hayamos encontrado al menos un formulario utilizando las reglas anteriores, devolvemos la lista de formularios encontrados, de lo contrario, devolvemos la lista oov_forms.