Stemmer en castellano para SPHINX
Sábado, Mayo 26th, 2007He hablado más de una vez del indexer/searcher Sphinx en el blog y de que es una herramienta genial para realizar buscadores de texto completo de alto rendimiento. Si todavía no conoces sphinx echa un ojo a:
- Página oficial de la herramienta
- Instalación de sphinx
- Proceso de creación de un Buscador de wikipedia
Lo que ahora quiero presentar es un stemmer en castellano que desarrollé hace unos meses para sphinx.
¿Qué es un stemmer?
Un stemmer es un algoritmo que realiza un proceso de stemming o lematización por el cual cada palabra es reducida a su lexema o raíz.
Este proceso es realmente útil para aumentar el recall o exhaustividad de un sistema de recuperación de información. Es muy importante tener en cuenta que el stemming es completamente dependiente del idioma en el que está la información a procesar.
Genial, pero ¿Qué es un stemmer?
Imaginemos por un momento que tenemos un directorio de empresas y un buscador asociado. Supongamos que disponemos en base de datos los siguientes registros:
- ID Nombre
- 1 "Fontanería Los rápidos"
- 2 "Perez Fontaneros"
- 3 "El fontanero feliz"
Si no disponemos de una herramienta de lematización, tendríamos los siguientes resultados:
- Consulta ID-resultante
- "fontanería" 1
- "fontaneros" 2
- "fontanero" 3
Esos resultados no sólo serían diferentes entre sí, sino que además estarían incompletos. En cambio si para esos mismo registros de base de datos usamos un stemmer para castellano:
- Consulta ID-resultante
- "fontanería" 1 2 3
- "fontaneros" 1 2 3
- "fontanero" 1 2 3
Creo que ya ha quedado claro la importancia de usar técnicas de stemming.
Sphinx y el stemmer
En este caso en particular, sphinx provee de dos stemmers, uno para inglés y otro para ruso (ya que el desarrollador es ruso). Si queremos realizar un buscador web con sphinx que aplique técnicas de stemming sólo hay una opción: desarrollar el stemmer nosotros mismos.
Después de estudiar un poco como es la parte de lematización en sphinx por dentro se ve que sería suficiente con desarrollar una función en C/C++ que recibida una palabra por parámetro devuelva su lexema y luego integrarlo con un par de directivas y actualización de los makes.
¿Por dónde empezar?. Bueno, pues es importante destacar que hay una persona que se encargó en su día de definir los algoritmos de stemming y es el verdadero referente en el tema. Se llama Martin Porter , el ya nos ha hecho todo el trabajo sucio y nos pone en bandeja la codificación.
Fases del stemmer
- Fase 0: Eliminar pronombres
- Fase 1: Eliminar sufijos estándar
- Fase 2a: Eliminar sufijos verbales que empiecen por y
- Fase 2b: Eliminar otros sufijos
- Fase 3: Eliminar sufijos residuales
- Final: Convertir acentos
Instalación e integración del stemmer con Sphinx
La versión de sphinx para la que se va a explicar todo es la 0.9.7 (la última).
El stemmer en si, es un único fichero sphinxstemes.cpp con la función que realiza la lematización. Pero para integrarlo en sphinx es necesario incluir un par de líneas en 4 archivos:
En el fichero sphinx.h, en la línea 251 esta definido el enum de morfologías. Se debe cambiar por este:
CODE:
-
/// morphology flags
-
enum ESphMorphology
-
{
-
SPH_MORPH_NONE = 0,
-
SPH_MORPH_STEM_EN = (1UL<<1),
-
SPH_MORPH_STEM_RU_CP1251 = (1UL<<2),
-
SPH_MORPH_STEM_RU_UTF8 = (1UL<<3),
-
SPH_MORPH_SOUNDEX = (1UL<<4),
-
SPH_MORPH_STEM_ES = (1UL<<5),
-
SPH_MORPH_UNKNOWN = (1UL<<30)
-
};
En el fichero sphinx.cpp, línea 8300 aproximadamente, cambiar el método GetWordID por:
CODE:
-
DWORD CSphDict_CRC32::GetWordID ( BYTE * pWord )
-
{
-
if ( m_iMorph & SPH_MORPH_STEM_EN )
-
stem_en ( pWord );
-
if ( m_iMorph & SPH_MORPH_STEM_ES )
-
stem_es ( pWord );
-
if ( m_iMorph & SPH_MORPH_STEM_RU_CP1251 )
-
stem_ru_cp1251 ( pWord );
-
if ( m_iMorph & SPH_MORPH_STEM_RU_UTF8 )
-
stem_ru_utf8 ( (WORD*)pWord );
-
if ( m_iMorph & SPH_MORPH_SOUNDEX )
-
stem_soundex ( pWord );
-
-
return FilterStopword ( sphCRC32 ( pWord ) );
-
}
En el fichero sphinxutils.cpp, hay que modificar el parser del fichero de configuración. en la línea 419 la función sphConfMorphology debe tener esta pinta:
CODE:
-
DWORD sphConfMorphology ( const CSphConfigSection & hIndex, bool bUseUTF8 )
-
{
-
if ( !hIndex("morphology") )
-
return SPH_MORPH_NONE;
-
-
const CSphString & sOption = hIndex["morphology"];
-
-
DWORD iMorph = SPH_MORPH_UNKNOWN;
-
DWORD iStemRu = ( bUseUTF8 ? SPH_MORPH_STEM_RU_UTF8 : SPH_MORPH_STEM_RU_CP1251 );
-
-
if ( sOption=="stem_en" )
-
iMorph = SPH_MORPH_STEM_EN;
-
-
else if ( sOption=="stem_es" )
-
iMorph = SPH_MORPH_STEM_ES;
-
-
else if ( sOption=="stem_ru" )
-
iMorph = iStemRu;
-
-
else if ( sOption=="stem_enru" )
-
iMorph = iStemRu | SPH_MORPH_STEM_EN;
-
-
else if ( sOption=="soundex" )
-
iMorph = SPH_MORPH_SOUNDEX;
-
-
else if ( sOption.IsEmpty() || sOption=="none" )
-
iMorph = SPH_MORPH_NONE;
-
-
return iMorph;
-
}
Por último en el fichero sphinxstem.h, se debe poner la definición de la función de stemming. Añadir
CODE:
-
/// stem lowercase Spanish word
-
void stem_es ( BYTE * pWord );
Una vez modificado esto, sólo queda actualizar los make para reconstruir el proyecto. Incluir sphinxstemes en am__objects
CODE:
-
am__objects_1 = sphinx.$(OBJEXT) sphinxexcerpt.$(OBJEXT) \
-
sphinxquery.$(OBJEXT) sphinxsoundex.$(OBJEXT) \
-
sphinxstemen.$(OBJEXT) sphinxstemes.$(OBJEXT) sphinxstemru.$(OBJEXT) \
-
sphinxutils.$(OBJEXT) md5.$(OBJEXT) sphinxstd.$(OBJEXT)
y también el cpp en los fuentes
CODE:
-
SRC_SPHINX = sphinx.cpp sphinxexcerpt.cpp sphinxquery.cpp sphinxsoundex.cpp sphinxstemen.cpp sphinxstemes.cpp sphinxstemru.cpp sphinxutils.cpp md5.cpp sphinxstd.cpp
Configuración
Una vez recompilado sphinx con el soporte para el stemmer en castellano, podemos configurar un índice que lo use índicandoselo en el fichero de configuración de esta manera (omitidos los campos que no cambian de una configuración normal):
CODE:
-
index x
-
{
-
morphology = stem_es
-
charset_type = utf-8
-
charset_table = 0..9, A..Z->a..z, _, a..z, U+C9->U+E9, U+C1->U+E1, \
-
U+DA->U+FA, U+D1->U+F1, U+D3->U+F3, U+CD->U+ED, U+E1, \
-
U+E9, U+FA, U+F1, U+F3, U+ED
-
}
Hay que tener mucho cuidado con la charset_table, porque es ahí, dónde se definen que caracteres aceptamos para el índice y cuales reconvertimos. El formato U+XX es unicode, y podeis ver unas tablas de códigos en unicode.org.
En la charset_table definida se aceptan los números, los guiones bajos, las letras acentuadas (sólo acentos del castellano) y las letras sin acentuar. Además son convertidas de mayusculas a minúsculas (eso quiere decir la -> ). Una mala configuración de este parámetro puede darnos muchos dolores de cabeza, asi que recomiendo estudiarlo con detenimiento. Por ejemplo, en el caso del búscador de la wikipedia, no se acepta el guión bajo, ya que es el usado en los títulos a modo de espacio.
Si por alguna razon, no quisieramos usar stemming pero si indizar texto en castellano, habría que retocar esta tabla para convertir los caracteres acentuados a su equivalente sin acentuar (más la ñ).
Pruebas del stemmer
En la web de Porter, se da una lista de 28000 palabras y su lematización correcta. El stemmer ha sido probado con esa lista y cotejando el resultado esperado con el obtenido. A excepción de 5 palabras (que debo revisar) todo va igual.
Descarga del stemmer
El stemmer al igual que el proyecto sphinx, es GPL, asi que puedes usarlo con libertad.
Puedes descargarlo de aqui: stemmer-castellano
Donde ver un buscador con el stemmer aplicado
El stemmer se puede ver en buscador de la wikipedia y también en el agregador de blogs agregax, un proyecto muy interesante que lleva a cabo Pau Iglesias (recientemente le hicieron un artículo en El País).




