12.6. Diccionarios #

12.6.1. Palabras vacías (Stop Words)
12.6.2. Diccionario Simple
12.6.3. Diccionario de Sinónimos
12.6.4. Diccionario de Tesauro (Thesaurus)
12.6.5. Diccionario Ispell
12.6.6. Diccionario Snowball

Los diccionarios se utilizan para eliminar palabras que no deben considerarse en una búsqueda (palabras vacías o stop words), y para normalizar palabras de modo que coincidan diferentes formas derivadas de la misma palabra. Una palabra normalizada con éxito se denomina lexema. Además de mejorar la calidad de la búsqueda, la normalización y la eliminación de palabras vacías reducen el tamaño de la representación tsvector de un documento, mejorando así el rendimiento. La normalización no siempre tiene un significado lingüístico y suele depender de la semántica de la aplicación.

Algunos ejemplos de normalización:

Un diccionario es un programa que acepta un token como entrada y devuelve:

PostgreSQL proporciona diccionarios predefinidos para muchos idiomas. También hay varias plantillas predefinidas que se pueden utilizar para crear nuevos diccionarios con parámetros personalizados. Cada plantilla de diccionario predefinida se describe a continuación. Si ninguna plantilla existente es adecuada, es posible crear otras nuevas; consulta el área contrib/ de la distribución de PostgreSQL para ver ejemplos.

Una configuración de búsqueda de texto vincula un analizador junto con un conjunto de diccionarios para procesar los tokens de salida del analizador. Para cada tipo de token que el analizador puede devolver, la configuración especifica una lista separada de diccionarios. Cuando el analizador encuentra un token de ese tipo, se consulta cada diccionario de la lista por turno, hasta que algún diccionario lo reconozca como una palabra conocida. Si se identifica como una palabra vacía, o si ningún diccionario reconoce el token, se descartará y no se indexará ni se buscará. Normalmente, el primer diccionario que devuelve una salida que no es NULL determina el resultado, y los diccionarios restantes no se consultan; pero un diccionario de filtrado puede reemplazar la palabra dada con una palabra modificada, la cual se pasa luego a los diccionarios subsiguientes.

La regla general para configurar una lista de diccionarios es colocar primero el diccionario más estrecho y específico, luego los diccionarios más generales, terminando con un diccionario muy general, como un derivador (stemmer) Snowball o simple, el cual reconoce todo. Por ejemplo, para una búsqueda específica de astronomía (configuración astro_en) se podría vincular el tipo de token asciiword (palabra ASCII) a un diccionario de sinónimos de términos astronómicos, un diccionario general de inglés y un derivador (stemmer) de inglés Snowball:

ALTER TEXT SEARCH CONFIGURATION astro_en
    ADD MAPPING FOR asciiword WITH astrosyn, english_ispell, english_stem;

Se puede colocar un diccionario de filtrado en cualquier parte de la lista, excepto al final, donde sería inútil. Los diccionarios de filtrado son útiles para normalizar parcialmente las palabras y simplificar la tarea de los diccionarios posteriores. Por ejemplo, se podría utilizar un diccionario de filtrado para eliminar los acentos de las letras acentuadas, tal como lo hace el módulo unaccent.

12.6.1. Palabras vacías (Stop Words) #

Las palabras vacías son palabras muy comunes, que aparecen en casi todos los documentos y no tienen valor discriminatorio. Por lo tanto, se pueden ignorar en el contexto de la búsqueda de texto completo. Por ejemplo, todo texto en inglés contiene palabras como a y the, por lo que es inútil almacenarlas en un índice. Sin embargo, las palabras vacías sí afectan las posiciones en tsvector, lo que a su vez afecta la clasificación (ranking):

SELECT to_tsvector('english', 'in the list of stop words');
        to_tsvector
----------------------------
 'list':3 'stop':5 'word':6

Las posiciones faltantes 1, 2, 4 se deben a las palabras vacías. Las clasificaciones calculadas para documentos con y sin palabras vacías son bastante diferentes:

SELECT ts_rank_cd (to_tsvector('english', 'in the list of stop words'), to_tsquery('list & stop'));
 ts_rank_cd
------------
       0.05

SELECT ts_rank_cd (to_tsvector('english', 'list stop words'), to_tsquery('list & stop'));
 ts_rank_cd
------------
        0.1

Depende de cada diccionario específico cómo trata las palabras vacías. Por ejemplo, los diccionarios ispell primero normalizan las palabras y luego buscan en la lista de palabras vacías, mientras que los derivadores (stemmers) Snowball primero verifican la lista de palabras vacías. La razón de este comportamiento diferente es el intento de reducir el ruido.

12.6.2. Diccionario Simple #

La plantilla de diccionario simple funciona convirtiendo el token de entrada a minúsculas y verificándolo contra un archivo de palabras vacías. Si se encuentra en el archivo, se devuelve un array vacío, lo que hace que el token sea descartado. Si no, la forma en minúsculas de la palabra se devuelve como el lexema normalizado. Alternativamente, el diccionario se puede configurar para reportar las palabras que no son vacías como no reconocidas, permitiendo que se pasen al siguiente diccionario de la lista.

Aquí tienes un ejemplo de una definición de diccionario utilizando la plantilla simple:

CREATE TEXT SEARCH DICTIONARY public.simple_dict (
    TEMPLATE = pg_catalog.simple,
    STOPWORDS = english
);

Aquí, english es el nombre base de un archivo de palabras vacías. El nombre completo del archivo será $SHAREDIR/tsearch_data/english.stop, donde $SHAREDIR significa el directorio de datos compartidos de la instalación de PostgreSQL, que a menudo es /usr/local/share/postgresql (utiliza el comando pg_config --sharedir para determinarlo si no estás seguro). El formato del archivo es simplemente una lista de palabras, una por línea. Las líneas en blanco y los espacios finales se ignoran, y las mayúsculas se convierten a minúsculas, pero no se realiza ningún otro procesamiento en el contenido del archivo.

Ahora podemos probar nuestro diccionario:

SELECT ts_lexize('public.simple_dict', 'YeS');
 ts_lexize
-----------
 {yes}

SELECT ts_lexize('public.simple_dict', 'The');
 ts_lexize
-----------
 {}

También podemos optar por devolver NULL, en lugar de la palabra en minúsculas, si no se encuentra en el archivo de palabras vacías. Este comportamiento se selecciona estableciendo el parámetro Accept del diccionario en false. Continuando con el ejemplo:

ALTER TEXT SEARCH DICTIONARY public.simple_dict ( Accept = false );

SELECT ts_lexize('public.simple_dict', 'YeS');
 ts_lexize
-----------


SELECT ts_lexize('public.simple_dict', 'The');
 ts_lexize
-----------
 {}

Con la configuración predeterminada de Accept = true, solo es útil colocar un diccionario simple al final de una lista de diccionarios, ya que nunca pasará ningún token a un diccionario posterior. Por el contrario, Accept = false solo es útil cuando hay al menos un diccionario posterior.

Caution

La mayoría de los tipos de diccionarios dependen de archivos de configuración, como archivos de palabras vacías. Estos archivos deben guardarse en codificación UTF-8. Se traducirán a la codificación real de la base de datos, si esta es diferente, cuando se lean en el servidor.

Caution

Normalmente, una sesión de base de datos leerá un archivo de configuración de diccionario solo una vez, cuando se utiliza por primera vez dentro de la sesión. Si modificas un archivo de configuración y deseas forzar a las sesiones existentes a que tomen el nuevo contenido, ejecuta un comando ALTER TEXT SEARCH DICTIONARY sobre el diccionario. Esto puede ser una actualización ficticia (dummy) que en realidad no cambie ningún valor de parámetro.

12.6.3. Diccionario de Sinónimos #

Esta plantilla de diccionario se utiliza para crear diccionarios que reemplazan una palabra por un sinónimo. Las frases no están admitidas (utiliza la plantilla de thesaurus (Section 12.6.4) para eso). Se puede utilizar un diccionario de sinónimos para superar problemas lingüísticos, por ejemplo, para evitar que un diccionario derivador (stemmer) de inglés reduzca la palabra Paris a pari. Es suficiente tener una línea Paris paris en el diccionario de sinónimos y colocarlo antes del diccionario english_stem. Por ejemplo:

SELECT * FROM ts_debug('english', 'Paris');
   alias   |   description   | token |  dictionaries  |  dictionary  | lexemes
-----------+-----------------+-------+----------------+--------------+---------
 asciiword | Word, all ASCII | Paris | {english_stem} | english_stem | {pari}

CREATE TEXT SEARCH DICTIONARY my_synonym (
    TEMPLATE = synonym,
    SYNONYMS = my_synonyms
);

ALTER TEXT SEARCH CONFIGURATION english
    ALTER MAPPING FOR asciiword
    WITH my_synonym, english_stem;

SELECT * FROM ts_debug('english', 'Paris');
   alias   |   description   | token |       dictionaries        | dictionary | lexemes
-----------+-----------------+-------+---------------------------+------------+---------
 asciiword | Word, all ASCII | Paris | {my_synonym,english_stem} | my_synonym | {paris}

El único parámetro requerido por la plantilla synonym es SYNONYMS, que es el nombre base de su archivo de configuración — my_synonyms en el ejemplo anterior. El nombre completo del archivo será $SHAREDIR/tsearch_data/my_synonyms.syn (donde $SHAREDIR significa el directorio de datos compartidos de la instalación de PostgreSQL). El formato del archivo es solo una línea por palabra a sustituir, con la palabra seguida por su sinónimo, separados por espacios en blanco. Las líneas en blanco y los espacios finales se ignoran.

La plantilla synonym también tiene un parámetro opcional CaseSensitive, cuyo valor predeterminado es false. Cuando CaseSensitive es false, las palabras en el archivo de sinónimos se convierten a minúsculas, al igual que los tokens de entrada. Cuando es true, las palabras y los tokens no se convierten a minúsculas, sino que se comparan tal cual.

Se puede colocar un asterisco (*) al final de un sinónimo en el archivo de configuración. Esto indica que el sinónimo es un prefijo. El asterisco se ignora cuando la entrada se utiliza en to_tsvector(), pero cuando se utiliza en to_tsquery(), el resultado será un elemento de consulta con la marca de coincidencia de prefijo (ver la Section 12.3.2). Por ejemplo, supongamos que tenemos estas entradas en $SHAREDIR/tsearch_data/synonym_sample.syn:

postgres        pgsql
postgresql      pgsql
postgre pgsql
gogle   googl
indices index*

Entonces obtendremos estos resultados:

mydb=# CREATE TEXT SEARCH DICTIONARY syn (template=synonym, synonyms='synonym_sample');
mydb=# SELECT ts_lexize('syn', 'indices');
 ts_lexize
-----------
 {index}
(1 row)

mydb=# CREATE TEXT SEARCH CONFIGURATION tst (copy=simple);
mydb=# ALTER TEXT SEARCH CONFIGURATION tst ALTER MAPPING FOR asciiword WITH syn;
mydb=# SELECT to_tsvector('tst', 'indices');
 to_tsvector
-------------
 'index':1
(1 row)

mydb=# SELECT to_tsquery('tst', 'indices');
 to_tsquery
------------
 'index':*
(1 row)

mydb=# SELECT 'indexes are very useful'::tsvector;
            tsvector
---------------------------------
 'are' 'indexes' 'useful' 'very'
(1 row)

mydb=# SELECT 'indexes are very useful'::tsvector @@ to_tsquery('tst', 'indices');
 ?column?
----------
 t
(1 row)

12.6.4. Diccionario de Tesauro (Thesaurus) #

Un diccionario de tesauro (a veces abreviado como TZ) es una colección de palabras que incluye información sobre las relaciones entre palabras y frases, es decir, términos más amplios (BT), términos más específicos (NT), términos preferidos, términos no preferidos, términos relacionados, etc.

Básicamente, un diccionario de tesauro reemplaza todos los términos no preferidos por un término preferido y, opcionalmente, conserva también los términos originales para la indexación. La implementación actual de PostgreSQL del diccionario de tesauro es una extensión del diccionario de sinónimos con soporte de frases añadido. Un diccionario de tesauro requiere un archivo de configuración con el siguiente formato:

# este es un comentario
sample word(s) : indexed word(s)
more sample word(s) : more indexed word(s)
...

donde el símbolo de dos puntos (:) actúa como delimitador entre una frase y su reemplazo.

Un diccionario de tesauro utiliza un subdiccionario (que se especifica en la configuración del diccionario) para normalizar el texto de entrada antes de verificar las coincidencias de frases. Solo es posible seleccionar un subdiccionario. Se reportará un error si el subdiccionario no reconoce una palabra. En ese caso, debes eliminar el uso de la palabra o enseñarle al subdiccionario a reconocerla. Puedes colocar un asterisco (*) al comienzo de una palabra indexada para evitar aplicar el subdiccionario a ella, pero todas las palabras de ejemplo deben ser conocidas por el subdiccionario.

El diccionario de tesauro elige la coincidencia más larga si hay varias frases que coinciden con la entrada, y los empates se resuelven utilizando la última definición.

No se pueden especificar palabras vacías específicas reconocidas por el subdiccionario; en su lugar, utiliza ? para marcar la ubicación donde puede aparecer cualquier palabra vacía. Por ejemplo, asumiendo que a y the son palabras vacías según el subdiccionario:

? one ? two : swsw

coincide con a one the two y con the one a two; ambas serían reemplazadas por swsw.

Dado que un diccionario de tesauro tiene la capacidad de reconocer frases, debe recordar su estado e interactuar con el analizador. Un diccionario de tesauro utiliza estas asignaciones para comprobar si debe manejar la siguiente palabra o detener la acumulación. El diccionario de tesauro debe configurarse con cuidado. Por ejemplo, si el diccionario de tesauro está asignado para manejar solo el token asciiword, entonces una definición de diccionario de tesauro como one 7 no funcionará ya que el tipo de token uint no está asignado al diccionario de tesauro.

Caution

Los tesauros se utilizan durante la indexación, por lo que cualquier cambio en los parámetros del diccionario de tesauro requiere reindexar. Para la mayoría de los demás tipos de diccionario, los cambios pequeños como añadir o eliminar palabras vacías no obligan a reindexar.

12.6.4.1. Configuración de Tesauro #

Para definir un nuevo diccionario de tesauro, utiliza la plantilla thesaurus. Por ejemplo:

CREATE TEXT SEARCH DICTIONARY thesaurus_simple (
    TEMPLATE = thesaurus,
    DictFile = mythesaurus,
    Dictionary = pg_catalog.english_stem
);

Aquí:

  • thesaurus_simple es el nombre del nuevo diccionario.

  • mythesaurus es el nombre base del archivo de configuración del tesauro. (Su nombre completo será $SHAREDIR/tsearch_data/mythesaurus.ths, donde $SHAREDIR significa el directorio de datos compartidos de la instalación).

  • pg_catalog.english_stem es el subdiccionario (aquí, un derivador inglés Snowball) que se utilizará para la normalización del tesauro. Ten en cuenta que el subdiccionario tendrá su propia configuración (por ejemplo, palabras vacías), la cual no se muestra aquí.

Ahora es posible vincular el diccionario de tesauro thesaurus_simple a los tipos de tokens deseados en una configuración, por ejemplo:

ALTER TEXT SEARCH CONFIGURATION russian
    ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
    WITH thesaurus_simple;

12.6.4.2. Ejemplo de Tesauro #

Consideremos un tesauro astronómico simple thesaurus_astro, que contiene algunas combinaciones de palabras astronómicas:

supernovae stars : sn
crab nebulae : crab

A continuación creamos un diccionario y vinculamos algunos tipos de tokens a un tesauro astronómico y a un derivador inglés (stemmer):

CREATE TEXT SEARCH DICTIONARY thesaurus_astro (
    TEMPLATE = thesaurus,
    DictFile = thesaurus_astro,
    Dictionary = english_stem
);

ALTER TEXT SEARCH CONFIGURATION russian
    ALTER MAPPING FOR asciiword, asciihword, hword_asciipart
    WITH thesaurus_astro, english_stem;

Ahora podemos ver cómo funciona. ts_lexize no es muy útil para probar un tesauro, porque trata su entrada como un solo token. En su lugar, podemos utilizar plainto_tsquery y to_tsvector, que dividirán sus cadenas de entrada en múltiples tokens:

SELECT plainto_tsquery('supernova star');
 plainto_tsquery
-----------------
 'sn'

SELECT to_tsvector('supernova star');
 to_tsvector
-------------
 'sn':1

En principio, se puede utilizar to_tsquery si se entrecomilla el argumento:

SELECT to_tsquery('''supernova star''');
 to_tsquery
------------
 'sn'

Observa que supernova star coincide con supernovae stars en thesaurus_astro porque especificamos el derivador english_stem en la definición del tesauro. El derivador eliminó la e y la s.

Para indexar la frase original así como el sustituto, simplemente inclúyela en la parte derecha de la definición:

supernovae stars : sn supernovae stars

SELECT plainto_tsquery('supernova star');
       plainto_tsquery
-----------------------------
 'sn' & 'supernova' & 'star'

12.6.5. Diccionario Ispell #

La plantilla de diccionario Ispell admite diccionarios morfológicos, que pueden normalizar muchas formas lingüísticas diferentes de una palabra en el mismo lexema. Por ejemplo, un diccionario Ispell de inglés puede hacer coincidir todas las declinaciones y conjugaciones del término de búsqueda bank, por ejemplo, banking, banked, banks, banks' y bank's.

La distribución estándar de PostgreSQL no incluye ningún archivo de configuración de Ispell. Los diccionarios para una gran cantidad de idiomas están disponibles en Ispell. Además, se admiten algunos formatos de archivos de diccionarios más modernos: MySpell (OO < 2.0.1) y Hunspell (OO >= 2.0.2). Hay disponible una gran lista de diccionarios en la Wiki de OpenOffice.

Para crear un diccionario Ispell, realiza los siguientes pasos:

  • Descarga los archivos de configuración del diccionario. Los archivos de extensión de OpenOffice tienen la extensión .oxt. Es necesario extraer los archivos .aff y .dic, y cambiar sus extensiones a .affix y .dict. Para algunos archivos de diccionario, también es necesario convertir los caracteres a la codificación UTF-8 con comandos (por ejemplo, para un diccionario de idioma noruego):

    iconv -f ISO_8859-1 -t UTF-8 -o nn_no.affix nn_NO.aff
    iconv -f ISO_8859-1 -t UTF-8 -o nn_no.dict nn_NO.dic
    

  • Copia los archivos en el directorio $SHAREDIR/tsearch_data.

  • Carga los archivos en PostgreSQL con el siguiente comando:

    CREATE TEXT SEARCH DICTIONARY english_hunspell (
        TEMPLATE = ispell,
        DictFile = en_us,
        AffFile = en_us,
        Stopwords = english);
    

Aquí, DictFile, AffFile y StopWords especifican los nombres base de los archivos de diccionario, afijos y palabras vacías. El archivo de palabras vacías tiene el mismo formato explicado anteriormente para el tipo de diccionario simple. El formato de los otros archivos no se especifica aquí, pero está disponible en los sitios web mencionados anteriormente.

Los diccionarios Ispell suelen reconocer un conjunto limitado de palabras, por lo que deben ir seguidos de otro diccionario más amplio; por ejemplo, un diccionario Snowball, que reconoce todo.

El archivo .affix de Ispell tiene la siguiente estructura:

prefixes
flag *A:
    .           >   RE      # As in enter > reenter
suffixes
flag T:
    E           >   ST      # As in late > latest
    [^AEIOU]Y   >   -Y,IEST # As in dirty > dirtiest
    [AEIOU]Y    >   EST     # As in gray > grayest
    [^EY]       >   EST     # As in small > smallest

Y el archivo .dict tiene la siguiente estructura:

lapse/ADGRS
lard/DGRS
large/PRTY
lark/MRS

El formato del archivo .dict es:

basic_form/affix_class_name

En el archivo .affix, cada bandera de afijo se describe en el siguiente formato:

condition > [-stripping_letters,] adding_affix

Aquí, la condición tiene un formato similar al de las expresiones regulares. Puede utilizar agrupaciones [...] y [^...]. Por ejemplo, [AEIOU]Y significa que la última letra de la palabra es "y" y la penúltima letra es "a", "e", "i", "o" o "u". [^EY] significa que la última letra no es ni "e" ni "y".

Los diccionarios Ispell admiten la división de palabras compuestas; una característica muy útil. Ten en cuenta que el archivo de afijos debe especificar una bandera especial utilizando la sentencia compoundwords controlled que marca las palabras del diccionario que pueden participar en la formación de palabras compuestas:

compoundwords  controlled z

Aquí tienes algunos ejemplos para el idioma noruego:

SELECT ts_lexize('norwegian_ispell', 'overbuljongterningpakkmesterassistent');
   {over,buljong,terning,pakk,mester,assistent}
SELECT ts_lexize('norwegian_ispell', 'sjokoladefabrikk');
   {sjokoladefabrikk,sjokolade,fabrikk}

El formato de MySpell es un subconjunto de Hunspell. El archivo .affix de Hunspell tiene la siguiente estructura:

PFX A Y 1
PFX A   0     re         .
SFX T N 4
SFX T   0     st         e
SFX T   y     iest       [^aeiou]y
SFX T   0     est        [aeiou]y
SFX T   0     est        [^ey]

La primera línea de una clase de afijo es la cabecera. Los campos de las reglas de afijo se enumeran después de la cabecera:

  • Nombre del parámetro (PFX o SFX)

  • Bandera (nombre de la clase de afijo)

  • Eliminación de caracteres del principio (en el prefijo) o del final (en el sufijo) de la palabra

  • Adición del afijo

  • Condición que tiene un formato similar al formato de las expresiones regulares.

El archivo .dict se parece al archivo .dict de Ispell:

larder/M
lardy/RT
large/RSPMYT
largehearted

Note

MySpell no admite palabras compuestas. Hunspell tiene un soporte sofisticado para palabras compuestas. En la actualidad, PostgreSQL implementa únicamente las operaciones básicas de palabras compuestas de Hunspell.

12.6.6. Diccionario Snowball #

La plantilla de diccionario Snowball se basa en un proyecto de Martin Porter, inventor del popular algoritmo de derivación de Porter para el idioma inglés. Snowball ahora proporciona algoritmos de derivación para muchos idiomas (consulta el sitio de Snowball para obtener más información). Cada algoritmo entiende cómo reducir formas variantes comunes de palabras a una ortografía base, o raíz (stem), dentro de su idioma. Un diccionario Snowball requiere un parámetro de idioma (language) para identificar qué derivador usar, y opcionalmente puede especificar un nombre de archivo de palabras vacías (stopword) que proporciona una lista de palabras a eliminar. (Las listas de palabras vacías estándar de PostgreSQL también son proporcionadas por el proyecto Snowball). Por ejemplo, existe una definición integrada equivalente a:

CREATE TEXT SEARCH DICTIONARY english_stem (
    TEMPLATE = snowball,
    Language = english,
    StopWords = english
);

El formato del archivo de palabras vacías es el mismo que ya se ha explicado.

Un diccionario Snowball reconoce todo, sea o no capaz de simplificar la palabra, por lo que debe colocarse al final de la lista de diccionarios. Es inútil tenerlo antes de cualquier otro diccionario porque un token nunca pasará a través de él al siguiente diccionario.