Profanity Filter Performance in Java

9

Ich muss die Profanität aus den Einsendungen der Benutzer in einer Java-basierten Webanwendung herausfiltern. Der Kunde ist sich sowohl des Scunthorpe-Problems als auch des Clbuttic-Problems bewusst und hat die Konsequenzen akzeptiert. Bitte, ich wünsche mir keine Debatte über das Fehlen einer Zensur.

Es gibt zwei Datenbits:

  1. Die Übermittlung des Benutzers, die möglicherweise 500 Wörter oder so enthalten kann;
  2. Eine einspaltige Datenbanktabelle mit Wörtern, die nicht zulässig sind. Diese Tabelle enthält möglicherweise viele tausend Datensätze.

Die vorliegende Lösung scheint mir falsch:

  1. Die gesamte Tabelle wird beim Start in einen Singleton (also im Speicher) in einen statischen String [] geladen.
  2. Für jede Benutzerübermittlung durchlaufen wir das Array und führen eine .indexOf () durch, um festzustellen, ob ein bestimmtes Wort in der Zeichenfolge [] in der Übermittlung erscheint.
  3. Wenn es erscheint, ersetzen wir durch Zeichen im Stil von $ $ # @%. Dies erfolgt durch Tokenisieren der Benutzerübermittlung, Durchlaufen der gesamten Benutzerübermittlung als Token (erneut) und Ersetzen jeder Instanz des gefundenen Wortes.

Diese Lösung mag brillant sein, aber ich bin skeptisch. Und nachdem ich es mir eine Weile angesehen habe, kann ich mich nicht daran vorbei finden.

Die Frage ist, was ist eine Lösung, die eine gute Leistung erbringt und hoffentlich für zukünftige Entwickler einigermaßen vernünftig ist, wenn ich entlassen werde, weil ich kein obskures Wort herausgefiltert habe, von dem ich noch nie gehört habe?

bläulichgoldfisch
quelle
Sie sagen, es scheint Ihnen falsch zu sein, ohne uns zu sagen, warum Sie denken, dass es falsch ist. Dann fragen Sie nach einer performanten Lösung, ohne uns mitzuteilen, auf welche Weise die aktuelle Lösung nicht ausreicht. Wie viele Texte pro Sekunde erhalten Sie, wie viele davon können Sie verarbeiten?
Benutzer unbekannt
Ich dachte, die Lösung sei falsch, vor allem, weil die Codebasis, in der ich arbeite, unzureichend und schlampig ist. Angesichts meiner Vorurteile vertraute ich meinem eigenen Misstrauen nicht. Ich hatte das Gefühl, dass die Meinung anderer von Vorteil sein würde. Dinge, die für mich Alarm auslösten, waren der String [] (was ist das 1999?), Der anstelle des viel kleineren Datensatzes, den der Benutzer übermittelt, den sehr großen String [] durchläuft und eine Schleife innerhalb der String [] -Schleife verschachtelt mit tokenisierter Benutzerübermittlung und so weiter. Die erwartete Nutzung ist nicht spezifiziert, idealerweise wäre eine elegante Lösung mit angemessener Leistung sehr schön.
Blueishgoldfish
2
"Angemessene Leistung" kann alles bedeuten. Wenn Sie kein konkretes Ziel haben, können Sie nicht wissen, ob Sie es erreicht haben. Wenn Sie einen Prozess so beschleunigen, dass er 100-mal schneller ist - ist dies ein Ziel? Wenn der Benutzer 1ms oder 1 / 10s wartet? Der Benutzer wird von Ihrer Arbeit nicht profitieren.
Benutzer unbekannt

Antworten:

18

Die einzige Möglichkeit, einen Wortfilter intelligent durchzuführen, ist die Verwendung eines Phonic-Matching-Systems. Ich habe vor einigen Jahren in Java einen sehr effektiven Obszönitätsfilter für ein sehr beliebtes Online-Spiel für Tweens und Teens mit mehreren Spielern geschrieben.

Es basierte auf einem stark modifizierten Double MetaPhone- Algorithmus, der so optimiert wurde, dass er genauer ist als der Standard, der so vielen Dingen wie möglich entspricht. Es war so äußerst effektiv, da es falsche und phonetische Schreibweisen genauso wie die tatsächlichen Wörter auffing. Ich habe auch l33tSprechen und txtSprechen mit dem MetaPhone-Algorithmus hinzugefügt , wodurch er eher zu einem Triple / Quad-Metaphon-Algorithmus wird.

Es gab einen Vorprozessor, der laufende Buchstaben komprimierte und Dinge wie die Kinder erkannte, die Dinge zusammenstellten, w o r d sindem sie die Buchstaben intelligent zusammen komprimierten und laufende Duplikate eliminierten wwoorrddss. Es war nur auf Englisch spezialisiert.

Es war vor 8 Jahren schnell genug, um in einem Echtzeit-Chat-System-Stream ohne merkliche Latenz mit Zehntausenden von Benutzern auf einem Single-Core-CPU-System verwendet zu werden.

Wir hatten eine Liste von Wörtern, die Metaphone-codiert waren, in einer Tabelle in der Datenbank, und sie wurde in eine statische Karte geladen, die überraschend klein war, und wir mussten nie etwas Besonderes tun, um auf die Liste der gesperrten Wörter zuzugreifen, konnte ich hinzufügen Phrasenerkennung mit den gleichen Techniken fast kostenlos.

Natürlich hatte ich ein laufendes Protokoll aller Chats von Tausenden von Kindern, die versuchten, das System in Echtzeit zu beschädigen, sodass ich einen ziemlich umfassenden Datensatz hatte, gegen den ich arbeiten konnte. So wie ich die Protokollierung tat , war , wenn jemand die Filter mit einem positiven ausgelöst, ich die nächsten paar Chat - Nachrichten protokollierte , die nicht die Filter von ihnen auslösen, dass die Art und Weise , wenn sie einen Weg , um ein bestimmtes Wort oder einen Satz finden sind, konnte ich Passen Sie mein System an und fangen Sie das. Nach nur ein paar Wochen war ich ziemlich kugelsicher.


quelle
3
Diese Lösung scheint am besten zu sein. Das Problem ist (oder war zu diesem Zeitpunkt), dass ich es an einem Nachmittag lösen musste. Wenn genügend Zeit vorhanden ist, werde ich entweder den Double MetaPhone-Ansatz wählen oder Sie damit beauftragen. :-)
blueishgoldfish
Also, ich denke, die Hälfte der Leute wird jetzt aufhören, das Spiel zu spielen: D
Davor Ždralo
2

Wenn Sie den Abgleich effizient durchführen möchten, ist der Aho Corasick- Algorithmus eine ziemlich gute Option (ich bin sicher, dass Sie eine Java-Implementierung finden können).

Natürlich möchten Sie die Übermittlung wahrscheinlich vorverarbeiten, um Rechtschreibunregelmäßigkeiten zu ersetzen ('$' -> 's', '@' -> 'a', '| <' -> 'k' usw.).

Dmitri
quelle
Genau das, wonach ich gesucht habe, danke! Hier ist eine Java-Implementierung: hkn.eecs.berkeley.edu/~dyoo/java
Remi Mélisson
0

Anstatt in einen statischen String [] zu laden, verwenden Sie die HashMap [] oder einen anderen Typ von Binärbaum (wenn Sie die Suche verbessern möchten), um den String zu Ihrem Schlüssel im Hash zu machen. Teilen Sie Ihren String durch Leerzeichen und entfernen Sie Satzzeichen. Anschließend können Sie die HashMap für jedes Wort in Ihrer Zeichenfolgenaufteilung abfragen. Wenn die Hashmap mit nicht null zurückkommt, wissen Sie, dass Sie ein schlechtes Wort haben.

Die Sache, die hier fehlschlägt, ist das Clbuttic-Problem, bei dem jemand zufällige Zeichen um das schlechte Wort ex hinzufügt. bhassda

Suroot
quelle
Ich denke, dass die letzte Einschränkung diese Lösung so gut wie nutzlos macht - es gibt keine Möglichkeit, sie auf etwas anderes als Ganzwort-Übereinstimmungen auszudehnen.
Das ist eine faire Aussage; Aber es wird schwierig, alles zu erfassen, was der menschliche Verstand sich einfallen lassen kann, um einem Obszönitätsfilter auszuweichen. Sie können jederzeit einen großen regulären Ausdruck mit ODER-Anweisungen erstellen, um alle Optionen zu kombinieren und dann den regulären Ausdruck mit der Eingabe abzugleichen. ODER Sie können eine Auswahl aus der Datenbank mit dem "Feld für fehlerhafte Wörter" aus der Datenbank mit einem RLIKE für die Eingabe vornehmen. Return zeigt ein schlechtes Wort an und gibt auch das schlechte Wort zurück.
@ Suroot es ist nicht schwer, so gut wie jedes Wort oder jede Phrase mit phonetischem Matching zu erfassen, wie es in meiner Frage beschrieben wird. Absolute Übereinstimmungen werden niemals funktionieren oder skaliert, aber phonetische Übereinstimmungen funktionieren in nahezu 100% der Fälle, sobald Sie sie so eingestellt haben, wie es nur möglich ist.
-1

Die Verwendung eines Phonic-Systems ist keineswegs die einzige Lösung, aber möglicherweise die einfachste, da es viele Open-Source-Bibliotheken gibt, die solche Aufgaben ausführen.

Der schwierige Teil wird immer der passende Teil eines Algorithmus sein und es klingt so, als ob Ihre Übereinstimmung ziemlich langsam und naiv ist. Sie können nicht davon ausgehen, dass indexOf ohne irgendeine Form der Hilfsprüfung korrekt übereinstimmt.

Außerdem durchlaufen Sie den gesamten String N-mal, wobei N die Anzahl der Wörter auf Ihrer schwarzen Liste ist. Die Vorschläge zur Verwendung von Set oder HashMap werden die Dinge definitiv etwas verbessern.

In den meisten Fällen ist ein linearer zustandsbasierter Algorithmus am besten und schnellsten. Ich habe die Lösung für Clean Speak geschrieben und sie verwendet diese Art von Algorithmus mit einem Phonic-Matching-System vor dem Prozess. Dies war die einzige Lösung, die nicht kompliziert wurde, wenn Profanität eingebettet wurde (wenn foo Profanität ist, ist Einbettung Foosucker) und ein hohes Leistungsniveau beibehalten konnte. Es lässt sich auch gut für andere Sprachen skalieren, ohne dass neue Codexe implementiert werden müssen.

Schließlich ist eine Vorverarbeitung jeglicher Form im Allgemeinen zu vermeiden. In den meisten Fällen können Sie das Gleiche linear tun, während Sie mit den einzelnen Zeichen in der Zeichenfolge umgehen.

Natürlich würde ich vorschlagen, langfristig nach anderen Lösungen zu suchen, da in den meisten Anwendungen die Verarbeitung von benutzergenerierten Inhalten komplexer ist als nur die Filterung von Obszönitäten. Oft möchten Sie auch persönliche Informationen wie E-Mails und Sozialversicherungsnummern und manchmal auch URLs filtern. Außerdem haben wir festgestellt, dass die meisten Anwendungen eine Art Moderationssystem und Inhaltssuche benötigen. Diese erhöhen die Komplexität erheblich.

Brian Pontarelli
quelle
-2

In einem solchen Fall möchten Sie feststellen, welche der beiden Wortlisten die kleinere ist. Angenommen, Ihre "verbotene" Liste enthält 2000 Wörter und die maximale Benutzerübermittlung beträgt 500 Wörter. In diesem Fall durchlaufen Sie die Liste der Wörter in der Benutzerübermittlung und suchen sie einzeln in der Liste der verbotenen Wörter nach und umgekehrt.

Die andere Änderung, die ich vornehmen würde, ist, dass Sie die Liste der verbotenen Wörter nicht in einem String [] aufbewahren. Wenn Sie im Array suchen, haben Sie eine O (n) -Suche pro Wort in der Benutzerübermittlung. Das ist ziemlich schlimm Ich würde versuchen, die Datenstruktur, in der Sie suchen, in eine Art assoziativen Container- oder Baumstruktur zu integrieren, der eine bessere Suchleistung aufweist (log n statt n). Die Herausforderung hierbei wäre, dass Sie, wenn Sie die Benutzerübermittlung in diesen Container einfügen, die Wortposition verfolgen müssen, damit Sie entweder die Eingabe rekonstruieren oder die Eingabezeichenfolge aktualisieren können, wenn Sie einen Suchtreffer haben.

Timo Geusch
quelle