Performances du filtre de blasphème en Java

9

J'ai une obligation de filtrer le blasphème des soumissions des utilisateurs dans une application Web basée sur Java. Le client est conscient à la fois du problème Scunthorpe et du problème Clbuttic et en a accepté les conséquences. Je vous en prie, je ne souhaite pas un débat sur le bien-fondé de l'absence de censure.

Il y a deux bits de données:

  1. La soumission de l'utilisateur, qui peut potentiellement contenir environ 500 mots;
  2. Une table de base de données à colonne unique contenant des mots qui ne sont pas autorisés. Il peut y avoir plusieurs milliers d'enregistrements dans ce tableau.

La solution actuelle me semble fausse:

  1. La table entière est chargée dans une chaîne statique [] au démarrage dans un singleton (résidant ainsi en mémoire).
  2. Pour chaque soumission d'utilisateur, nous parcourons le tableau et faisons un .indexOf () pour voir si un mot donné dans la chaîne [] apparaît dans la soumission.
  3. S'il apparaît, nous le remplaçons par des caractères de style% $ # @%. Pour cela, vous devez tokeniser la soumission de l'utilisateur, parcourir (à nouveau) toute la soumission de l'utilisateur en tant que jetons et remplacer chaque instance du mot trouvé.

Il peut y avoir de l'éclat dans cette solution, mais je suis sceptique. Et après l'avoir regardé pendant un moment, je ne peux pas trouver mon chemin.

La question est de savoir quelle est la solution qui donnera de bonnes performances et, espérons-le, sera raisonnablement raisonnable pour les futurs développeurs à maintenir après que je sois viré pour avoir échoué à filtrer un mot obscur dont je n'ai jamais entendu parler?

poisson bleuâtre
la source
Vous dites que cela vous semble mal, sans nous dire pourquoi vous pensez que c'est mal. Ensuite, vous demandez une solution performante, sans nous le dire, en quoi la solution actuelle n'est pas suffisante. Combien de textes par seconde recevez-vous, combien pouvez-vous traiter?
utilisateur inconnu
Je pensais que la solution était mauvaise, principalement parce que la base de code dans laquelle je travaille est inadéquate et bâclée. Étant donné mon parti pris, je ne faisais pas confiance à ma propre méfiance. Je pensais que l'opinion des autres serait bénéfique. Les choses qui ont déclenché des alarmes pour moi étaient la chaîne [] (quoi, c'est 1999?), Bouclant sur la très grande chaîne [] au lieu de l'ensemble beaucoup plus petit de données que l'utilisateur soumet, imbriquant une boucle à l'intérieur de la boucle chaîne [] avec la soumission utilisateur symbolisée, etc. L'utilisation prévue n'est pas spécifiée, idéalement une solution élégante avec des performances raisonnables serait charmante.
blueishgoldfish
2
Une «performance raisonnable» peut signifier n'importe quoi. Si vous n'avez pas d'objectif concret, vous ne pouvez pas savoir si vous l'avez atteint. Si vous accélérez un processus, de sorte qu'il soit 100 fois plus rapide - est-ce un objectif? Si l'utilisateur attend 1 ms ou 1 / 10s? L'utilisateur ne bénéficiera pas de votre travail.
utilisateur inconnu

Réponses:

18

La seule façon de faire un filtre de mots intelligemment est d'utiliser un système de correspondance phonétique. J'ai écrit un filtre de blasphème très efficace pour un jeu en ligne massivement multijoueur très populaire pour les préadolescents et les adolescents il y a quelques années à Java.

Il était basé sur un algorithme Double MetaPhone hautement modifié qui a été modifié pour être plus précis au lieu de la valeur par défaut qui doit correspondre à autant de choses que possible. Il était extrêmement efficace car il reprenait les fautes d'orthographe et l'orthographe phonétique exactement comme les mots réels. J'ai également ajouté l33tparler et txtparler de l'algorithme MetaPhone, ce qui en fait plus un algorithme de métaphone triple / quadruple.

Il comprenait un pré-processeur qui compressait les lettres en cours d'exécution et détectait des choses comme les enfants, w o r d sen compressant intelligemment les lettres et en éliminant les doublons en cours d'exécution wwoorrddss, il était très spécialisé pour l'anglais uniquement.

Il était assez rapide il y a 8 ans pour être utilisé dans un flux de système de chat en temps réel sans latence notable avec des dizaines de milliers d'utilisateurs sur un système CPU monocœur.

Nous avions une liste de mots qui étaient codés par métaphone dans une table de la base de données, et elle était chargée dans une carte statique qui était étonnamment petite et nous n'avons jamais eu à faire quoi que ce soit de spécial pour accéder à la liste des mots interdits, j'ai pu ajouter détection de phrases en utilisant les mêmes techniques pour presque gratuitement.

Bien sûr, j'avais un journal de toutes les conversations de milliers d'enfants essayant de briser le système en temps réel, j'avais donc un ensemble de données assez complet sur lequel travailler. La façon dont je me suis connecté était quand quelqu'un a déclenché le filtre avec un positif, j'ai enregistré les prochains messages de chat qui n'ont pas déclenché le filtre de leur part, de cette façon s'ils trouvaient un moyen de contourner un mot ou une phrase en particulier, je pourrais adapter mon système et attraper ça. J'étais assez à l'épreuve des balles après seulement quelques semaines.


la source
3
Cette solution semble être la meilleure. Le problème est (ou était à ce stade) que j'ai dû le résoudre en un après-midi. S'il reste suffisamment de temps, je vais soit adopter l'approche Double MetaPhone, soit vous engager pour le faire. :-)
blueishgoldfish
Donc, je suppose que la moitié des gens arrêteront de jouer maintenant: D
Davor Ždralo
2

Si vous voulez faire la correspondance efficacement, l' algorithme Aho Corasick est une assez bonne option (je suis sûr que vous pouvez trouver une implémentation Java flottante).

Bien sûr, vous voudrez probablement prétraiter la soumission pour remplacer toute irrégularité d'orthographe ('$' -> 's', '@' -> 'a', '| <' -> 'k', etc.)

Dmitri
la source
Précisément ce que je cherchais, merci! Voici une implémentation Java: hkn.eecs.berkeley.edu/~dyoo/java
Remi Mélisson
0

Au lieu de charger dans une chaîne statique [], utilisez le HashMap [] ou un autre type d'arbre binaire (si vous souhaitez améliorer la recherche) faisant de la chaîne votre clé dans le hachage. Divisez votre chaîne par des espaces et supprimez la ponctuation. Ensuite, vous pouvez interroger le HashMap pour chaque mot de votre chaîne de partage; si le hashmap revient avec non null alors vous savez que vous avez un mauvais mot.

La chose qui échoue ici est le problème Clbuttic où quelqu'un ajoute des caractères aléatoires autour du mauvais mot ex. bhassda

Suroot
la source
Je pense que la dernière mise en garde est ce qui rend cette solution à peu près inutile - il n'y a aucun moyen de l'étendre à autre chose que des correspondances de mots entiers.
C'est une déclaration juste; mais il devient difficile de capturer tout ce que l'esprit humain peut imaginer pour échapper à un filtre de blasphème. Vous pouvez toujours créer une énorme expression régulière avec des instructions OR pour combiner toutes les options, puis faire correspondre l'expression régulière avec l'entrée. OU vous pouvez faire une sélection dans la base de données avec le "champ de mauvais mots" de la base de données avec un RLIKE contre l'entrée. Retour indique un mauvais mot et renvoie également le mauvais mot.
@Suroot, il n'est pas difficile de capturer à peu près n'importe quel mot ou expression avec correspondance phonétique comme le dit ma question. Les correspondances absolues ne fonctionneront jamais ou ne seront pas mises à l'échelle, mais la correspondance phonétique fonctionne aussi près de 100% du temps une fois que vous vous êtes accordé que possible.
-1

L'utilisation d'un système phonique n'est pas la seule solution, mais ce pourrait être la plus simple car il existe de nombreuses bibliothèques open source qui font ce genre de choses.

La partie difficile sera toujours la partie correspondante de tout algorithme et il semble que votre correspondance soit assez lente et naïve. Vous ne pouvez pas supposer que indexOf correspondra correctement sans une forme de vérification auxiliaire.

De plus, vous finirez par faire une boucle sur l'ensemble de la chaîne N fois, où N est le nombre de mots sur votre liste noire. Les suggestions d'utilisation de Set ou HashMap vont certainement améliorer quelque peu les choses.

Dans la plupart des cas, un algorithme basé sur un état linéaire est le meilleur et le plus rapide. J'ai écrit la solution pour Clean Speak et elle utilise ce type d'algorithme avec un système de correspondance phonique de pré-processus. C'était la seule solution qui ne s'est pas compliquée lorsque le blasphème est intégré (si foo est blasphème, l'incorporation est foosucker) et a pu conserver un niveau élevé de performances. Il évolue également bien pour d'autres langages sans implémentations de nouveaux codex.

Enfin, le prétraitement de tout formulaire est généralement à éviter. Dans la plupart des cas, vous pouvez faire la même chose de manière linéaire en manipulant chacun des caractères de la chaîne.

Bien sûr, je suggérerais d'étudier d'autres solutions à long terme, car dans la plupart des applications, la gestion du contenu généré par l'utilisateur est plus complexe que le simple filtrage grossier. Souvent, vous souhaitez également filtrer les informations personnelles telles que les e-mails et les numéros de sécurité sociale et parfois des choses comme les URL. De plus, nous avons constaté que la plupart des applications ont besoin d'une certaine forme de système de modération et de recherche de contenu. Celles-ci augmentent considérablement la complexité.

Brian Pontarelli
la source
-2

Ce que vous voulez faire dans un cas comme celui-ci est de déterminer laquelle des deux listes de mots est la plus petite. Supposons que votre liste "verboten" contienne 2 000 mots et que la soumission maximale de l'utilisateur soit 500 mots. Dans ce cas, vous parcourrez la liste des mots dans la soumission de l'utilisateur et les rechercherez un par un dans la liste des mots interdits et vice versa.

L'autre changement que je ferais est que vous ne conservez pas la liste des mots interdits dans une chaîne [] - si vous recherchez dans le tableau, vous avez une recherche O (n) par mot dans la soumission de l'utilisateur. C'est assez mauvais. J'essaierais de mettre la structure de données que vous recherchez dans une sorte de conteneur associatif ou une structure arborescente qui a une meilleure performance de recherche (log n au lieu de n). Le défi ici serait que si vous placez la soumission de l'utilisateur dans ce conteneur, vous devrez garder une trace de la position du mot afin de pouvoir soit reconstruire l'entrée, soit mettre à jour la chaîne d'entrée si vous avez un résultat de recherche.

Timo Geusch
la source