Profanity Filter Performance en Java

9

Tengo un requisito para filtrar las malas palabras de los envíos de los usuarios en una aplicación web basada en Java. El cliente conoce tanto el problema de Scunthorpe como el problema de Clbuttic y ha aceptado las consecuencias. Por favor, no deseo un debate sobre los méritos de la falta de censura.

Hay dos bits de datos:

  1. El envío del usuario, que potencialmente puede contener aproximadamente 500 palabras;
  2. Una tabla de base de datos de una sola columna que contiene palabras que no están permitidas. Puede haber muchos miles de registros en esta tabla.

La solución actual me parece incorrecta:

  1. La tabla completa se carga en una cadena estática [] al inicio en un Singleton (por lo tanto, reside en la memoria).
  2. Para cada envío de usuario, recorremos la matriz y hacemos un .indexOf () para ver si alguna palabra dada en la Cadena [] aparece en el envío.
  3. Si aparece, lo reemplazamos con% $ # @% - caracteres de estilo. Esto se realiza mediante la tokenización del envío del usuario, recorriendo todo el envío del usuario como tokens (nuevamente) y reemplazando cada instancia de la palabra encontrada.

Puede haber brillo en esta solución, pero soy escéptico. Y después de haberlo mirado por un tiempo, no puedo encontrar mi camino más allá.

La pregunta es, ¿cuál es una solución que proporcionará un buen rendimiento y esperamos que sea razonablemente razonable que los futuros desarrolladores mantengan después de que me despidan por no filtrar alguna palabra oscura de la que nunca he oído hablar?

pez dorado
fuente
Dices que te parece mal, sin decirnos por qué piensas que está mal. Luego, solicita una solución eficaz, sin decirnos, de qué manera la solución actual no es suficiente. ¿Cuántos textos por segundo obtienes, cuántos de ellos puedes procesar?
usuario desconocido el
Pensé que la solución era incorrecta, principalmente porque la base de código en la que estoy trabajando es inadecuada y descuidada. Dado mi sesgo, no confiaba en mi propia desconfianza. Sentí que la opinión de los demás sería beneficiosa. Las cosas que activaron las alarmas para mí fueron la Cadena [] (¿qué, es este 1999?), Haciendo un bucle sobre la Cadena muy grande [] en lugar del conjunto de datos mucho más pequeño que envía el usuario, anidando un bucle dentro del bucle Cadena [] con envío tokenizado de usuarios, etc. La utilización esperada no está especificada, lo ideal sería una solución elegante con un rendimiento razonable.
blueishgoldfish
2
'Rendimiento razonable' puede significar cualquier cosa. Si no tiene una meta concreta, no puede saber si la alcanzó. Si acelera un proceso, de modo que sea 100 veces más rápido, ¿es este un objetivo? Si el usuario espera 1ms o 1 / 10s? El usuario no se beneficiará de su trabajo.
usuario desconocido

Respuestas:

18

La única forma de hacer un filtro de palabras de manera inteligente es usar un sistema de correspondencia fonética. Escribí un filtro de blasfemias muy efectivo para un juego en línea multijugador masivo muy popular para preadolescentes y adolescentes hace unos años en Java.

Se basó en un algoritmo Double MetaPhone altamente modificado que se ajustó para ser más preciso en lugar del valor predeterminado, que es hacer coincidir tantas cosas como sea posible. Fue extremadamente efectivo ya que detectó faltas de ortografía y deletreo fonético de la misma manera que las palabras reales. Añadí l33thablar y txthablar con el algoritmo de metaphone así, lo que hace más de un algoritmo Triple / Quad Metaphone.

Presentaba un preprocesador que comprimía las letras en ejecución y detectaba cosas como que los niños pusieran cosas w o r d sal comprimir inteligentemente las letras juntas y eliminaba los duplicados en ejecución wwoorrddss, era muy especializado solo en inglés.

Hace 8 años, fue lo suficientemente rápido como para usarse en un flujo de sistema de chat en tiempo real sin latencia notable con decenas de miles de usuarios en un sistema de CPU de un solo núcleo.

Teníamos una lista de palabras codificadas por Metaphone en una tabla de la base de datos, y estaba cargada en un Mapa estático que era sorprendentemente pequeño y nunca tuvimos que hacer nada especial para acceder a la lista de palabras prohibidas, pude agregar detección de frases utilizando las mismas técnicas de forma casi gratuita.

Por supuesto, tenía un registro de todos los chats de miles de niños que intentaban romper el sistema en tiempo real, así que tenía un conjunto de datos bastante completo para trabajar. La forma en que hice el registro fue cuando alguien activó el filtro con un positivo, registré los siguientes mensajes de chat que no activaron el filtro , de esa manera, si encontraban una forma de evitar una palabra o frase en particular, podría adaptar mi sistema y atrapar eso. Estaba a prueba de balas después de solo un par de semanas.


fuente
3
Esta solución parece ser la mejor. El problema es (o era en este punto) que tuve que resolverlo en una tarde. Si hay tiempo suficiente, adoptaré el enfoque de Doble MetaPhone o te contrataré para que lo hagas. :-)
blueishgoldfish
Entonces, supongo que la mitad de la gente dejará de jugar ahora: D
Davor Ždralo
2

Si desea hacer la correspondencia de manera eficiente, el algoritmo Aho Corasick es una opción bastante buena (estoy seguro de que puede encontrar una implementación de Java flotando).

Por supuesto, es probable que desee preprocesar el envío para reemplazar cualquier irregularidad ortográfica ('$' -> 's', '@' -> 'a', '| <' -> 'k', etc.)

Dmitri
fuente
Precisamente lo que estaba buscando, gracias! Aquí hay una implementación de Java: hkn.eecs.berkeley.edu/~dyoo/java
Remi Mélisson
0

En lugar de cargar en una Cadena estática [], utilice el HashMap [] o algún otro tipo de árbol binario (si desea mejorar la búsqueda) haciendo de la cadena su clave en el hash. Divide tu cadena por espacios y elimina la puntuación. Luego puede consultar el HashMap para cada palabra en su división de cadena; si el hashmap vuelve con no nulo, entonces sabes que tienes una mala palabra.

Lo que falla aquí es el problema Clbuttic donde alguien agrega caracteres aleatorios alrededor de la palabra mala ex. bhassda

Suroot
fuente
Creo que la última advertencia es lo que hace que esta solución sea bastante inútil: no hay forma de extenderla a nada más que coincidencias de palabras completas.
Esa es una declaración justa; pero se hace difícil capturar cada cosa posible que la mente humana pueda idear para evadir un filtro de blasfemias. Siempre puede crear una gran expresión regular con instrucciones OR para combinar todas las opciones y luego hacer coincidir la expresión regular con la entrada. O bien, puede hacer una selección de la base de datos con el "campo de palabras malas" de la base de datos con un RLIKE contra la entrada. Retorno indica mala palabra y también devolverá la mala palabra.
@Suroot no es difícil capturar casi cualquier palabra o frase con coincidencia fonética como se menciona en mi pregunta. Las coincidencias absolutas nunca funcionarán o escalarán, pero la coincidencia fonética funciona tan cerca del 100% del tiempo una vez que sintonice como sea posible.
-1

El uso de un sistema fónico no es la única solución de ninguna manera, pero podría ser la más simple ya que hay muchas bibliotecas de código abierto que hacen ese tipo de cosas.

La parte difícil siempre será la parte correspondiente de cualquier algoritmo y parece que su coincidencia es bastante lenta e ingenua. No puede suponer que indexOf coincidirá correctamente sin alguna forma de verificación auxiliar.

Además, terminarás recorriendo toda la cadena N veces, donde N es el número de palabras en tu lista negra. Las sugerencias para usar Set o HashMap definitivamente mejorarán un poco las cosas.

En la mayoría de los casos, un algoritmo basado en estado lineal es el mejor y más rápido. Escribí la solución para Clean Speak y utiliza este tipo de algoritmo con un sistema de coincidencia fonética previo al proceso. Esta fue la única solución que no se complicó cuando se profanó blasfemias (si foo es blasfemias, la incrustación es foosucker) y pudo mantener un alto nivel de rendimiento. También se escala muy bien para otros idiomas sin implementaciones de nuevos códices.

Por último, el preprocesamiento de cualquier forma generalmente es algo que debe evitarse. En la mayoría de los casos, puede hacer lo mismo de forma lineal mientras maneja cada uno de los caracteres de la cadena.

Por supuesto, sugeriría buscar otras soluciones a largo plazo porque en la mayoría de las aplicaciones el manejo de contenido generado por el usuario es más complejo que el simple filtrado de malas palabras. A menudo, también desea filtrar información personal como correos electrónicos y números de seguridad social y, a veces, cosas como URL. Además, hemos descubierto que la mayoría de las aplicaciones necesitan algún tipo de sistema de moderación y búsqueda de contenido. Estos aumentan la complejidad considerablemente.

Brian Pontarelli
fuente
-2

Lo que quiere hacer en un caso como este es determinar cuál de las dos listas de palabras es la más pequeña. Digamos que su lista "verboten" contiene 2000 palabras y el envío máximo de usuarios es de 500 palabras. En ese caso, recorrerá la lista de palabras en el envío del usuario y las buscará una por una en la lista de palabras prohibidas y viceversa.

El otro cambio que haría es que no mantienes la lista de palabras prohibidas en una Cadena []: si buscas en la matriz, tienes una búsqueda O (n) por palabra en el envío del usuario. Eso está muy mal. Intentaría poner la estructura de datos que está buscando en algún tipo de contenedor asociativo o estructura de árbol que tenga un mejor rendimiento de búsqueda (log n en lugar de n). El desafío aquí sería que si coloca el envío del usuario en este contenedor, tendrá que realizar un seguimiento de la posición de la palabra para que pueda reconstruir la entrada o actualizar la cadena de entrada si tiene un resultado de búsqueda.

Timo Geusch
fuente