Actualmente estoy trabajando en la implementación de una búsqueda difusa de un servicio web de terminología y estoy buscando sugerencias sobre cómo podría mejorar la implementación actual. Es demasiado código para compartir, pero creo que una explicación puede ser suficiente para sugerir reflexiones. Me doy cuenta de que es mucho para leer, pero agradecería cualquier ayuda.Asesoramiento sobre cómo mejorar una implementación de búsqueda difusa actual
Primero, la terminología es básicamente solo una cantidad de nombres (o términos). Para cada palabra, la dividimos en tokens por espacio y luego iteramos a través de cada carácter para agregarlo al trie. En un nodo terminal (como cuando se alcanza el carácter y en strawberry) almacenamos en una lista un índice para la lista de términos principales. Por lo tanto, un nodo terminal puede tener múltiples índices (ya que el nodo terminal para fresa coincidirá con 'fresa' y 'alergia a fresa').
En cuanto a la búsqueda real, la consulta de búsqueda también se divide en tokens por espacio. El algoritmo de búsqueda se ejecuta para cada token. El primer carácter del token de búsqueda debe coincidir (por lo que traw nunca coincidirá con strawberry). Después de eso, pasamos por los niños de cada nodo sucesivo. Si hay un elemento secundario con un carácter que coincida, continuamos la búsqueda con el siguiente carácter del token de búsqueda. Si un niño no concuerda con el personaje dado, observamos a los niños usando el carácter actual del token de búsqueda (para no avanzar). Esta es la parte borrosa, por lo que 'stwb' coincidirá con 'fresa'.
Cuando lleguemos al final del token de búsqueda, buscaremos en el resto de la estructura trie en ese nodo para obtener todas las coincidencias posibles (ya que los índices de la lista de términos maestros están solo en los nodos del terminal). Llamamos a esto el roll up. Almacenamos los índices al establecer su valor en un BitSet. Entonces, simplemente y los BitSets de los resultados de cada resultado de token de búsqueda. Luego tomamos, digamos, los primeros 1000 o 5000 índices de los BitSets anidados y buscamos los términos reales a los que corresponden. Usamos Levenshtein para puntuar cada término y luego ordenarlo por puntaje para obtener nuestros resultados finales.
Esto funciona bastante bien y es bastante rápido. Hay más de 390k nodos en el árbol y más de 1,1 millones de nombres de términos reales. Sin embargo, hay problemas con esto tal como está.
Por ejemplo, la búsqueda de 'gato automóvil' devolverá el cateterismo, cuando no lo deseemos (dado que la consulta de búsqueda es de dos palabras, el resultado debe ser al menos dos). Eso sería fácil de verificar, pero no soluciona una situación como el Procedimiento de cateterismo, ya que son dos palabras. Idealmente, nos gustaría que coincida con algo como la cateterización cardíaca.
En función de la necesidad de corregir esto, se produjeron algunos cambios. Por un lado, pasamos por el trie en una búsqueda mixta de profundidad/amplitud. Esencialmente profundizamos primero siempre que un personaje coincida. Esos nodos secundarios que no coinciden se agregan a una cola de prioridad. La cola de prioridad se ordena por distancia de edición, que se puede calcular mientras se busca el trie (ya que si hay una coincidencia de caracteres, la distancia sigue siendo la misma y si no, se incrementa en 1). Al hacer esto, obtenemos la distancia de edición para cada palabra. Ya no estamos usando el BitSet. En cambio, es un mapa del índice de un objeto Terminfo. Este objeto almacena el índice de la frase de consulta y el término frase y puntaje. Por lo tanto, si la búsqueda es "gato de automóvil" y un término emparejado es "procedimiento de cateterización", el término "índices de frase" será 1, al igual que los índices de frase de consulta. Para "cateterismo cardíaco", el término índices de frase será 1,2, al igual que los índices de frase de consulta. Como puede ver, después es muy simple observar el recuento de los índices de frase terminológica y los índices de frase de consulta, y si no son al menos iguales al recuento de palabras de búsqueda, pueden descartarse.
Después de eso, sumamos las distancias de edición de las palabras, eliminamos las palabras del término que coinciden con el término índice de frase, y contamos las letras restantes para obtener la distancia de edición verdadera.Por ejemplo, si coincide con el término "alergia a las fresas" y su consulta de búsqueda fue "paja", obtendría un puntaje de 7 de las fresas, luego utilizaría el término índice de frase para descartar las fresas del término, y solo contaría "alergia a" (sin los espacios) para obtener el puntaje de 16.
Esto nos da los resultados precisos que esperamos. Sin embargo, es demasiado lento. Donde antes podíamos obtener 25-40 ms en una búsqueda de palabras, ahora podría ser tanto como medio segundo. Se trata en gran medida de cosas como instanciar objetos TermInfo, usando operaciones .add(), operaciones .put() y el hecho de que tenemos que devolver una gran cantidad de coincidencias. Podríamos limitar cada búsqueda para que solo devuelva 1000 coincidencias, pero no hay garantía de que los primeros 1000 resultados para "automóvil" coincidan con cualquiera de las primeras 1000 coincidencias para "gato" (recuerde, hay más de 1.1 millones de términos).
Incluso para una sola palabra de consulta, como cat, aún necesitamos una gran cantidad de coincidencias. Esto es porque si buscamos 'cat' la búsqueda va a coincidir con el auto y enrollaremos todos los nodos terminales debajo de él (que será mucho). Sin embargo, si limitáramos la cantidad de resultados, pondría demasiado énfasis en palabras que comiencen con la consulta y no en la distancia de edición. Por lo tanto, las palabras como el cateterismo tendrían más probabilidades de ser incluidas que algo como el pelaje.
Así que, básicamente, ¿hay alguna idea sobre cómo podríamos manejar los problemas que la segunda implementación solucionó, pero sin tanta velocidad de ralentización que introdujo? Puedo incluir algún código seleccionado si pudiera aclarar las cosas, pero no quería publicar una pared gigante de código.
Debo señalar que los términos reales (para los que se usan los índices de nodos terminales) contienen más que un nombre, pero solo queremos buscar en el nombre. – AHungerArtist
Este problema podría reasignarse a una serie temporal; entonces su problema se convierte en cómo encontrar series de tiempo coincidentes. Uso el algoritmo FTSE para series de tiempo coincidentes de http://www.eecs.umich.edu/db/files/sigmod07timeseries.pdf – Adrian