Hallo Alexander,
bitte entschuldige, dass ich mich wiederhole: Meiner Meinung nach solltest Du
unbedingt anhand eines Beispiels das Vorgehen und die Wirkung des Spamfilters
demonstrieren. Dein Artikel gibt darauf bisher leider keinen offensichtlichen Hinweis.
Ganz besonders wichtig finde ich eine Erläuterung in einfachen Worten, vergleiche meine Anmerkung
Hauptabschnitt Bayesfilter
- Konzept des Bayestheorem in einfachen Worten
(Nein, nicht die Mathematik)
und ich zitiere aus Blaubarts Beitrag
Sehe ich auch so. Darüber hinaus stelle ich fest, daß insgesamt gar nicht so recht darauf eingegangen wird, was das Bayestheorem hier überhaupt verloren hat. "So sieht es aus: [Formel] ... und dann den Quotienten für P(B|A) in die Bayesformel einsetzen" ist wirklich zu knapp. Eine ausführlichere umgangssprachliche Erläuterung (d. h. nicht in Formeln gegossen oder diese lediglich wiedergebend) deines Vorgehens kann sicher auch den ein oder anderen Mathefeind zum Weiterlesen animieren.
der offensichtlich der gleichen Meinung ist. Ein Beispiel zu einer weiteren
Verbesserung wäre, ich zitiere aus Deinem Artikelentwurf,
<zitat>
Wahrscheinlichkeit eines Ereignisses A, in diesem Fall dass ein Kommentar
von einem Spammer stammt unter der Vorraussetzung dass vorher ein Ereignis B
stattgefunden hat
</zitat>
dass Du Beispiele für dieses Ereignis B anführst, so wie Du dies für das Ereignis A gemacht hast.
Einfache Tat, große Wirkung.
zu meinem Abschnitt a) möchte ich noch anmerken, dass mir Änderungen, wie sie
seth vorgeschlagen hat, angemessener erscheinen als meine.
Ich möchte noch ein wenig auf Deine Antworten auf meine Fragen und Anregungen
eingehen:
-- Es ist gute Praxis, SQL-Schlüsselwörter groß zu schreiben, siehe z.B.
-- MySQL-Handbuch
Ich mag diese Praxis nicht, ich habe die ganze Zeit das Gefühl, dass meine SQL-Statements mich anschreien.
ich mag diese Praxis, ganz besonders wenn keine Syntaxhervorhebung zur Verfügung steht :-)
Ich schrieb früher auch die HTML-Tags groß.
-- Warum ausgerechnet VARBINARY, warum genau dieser Datentyp.
Weil der Fall relevant ist, wenn ich varchar nehme kann ich nicht "Foo" und "foo" gleichzeitig in der Tabelle speichern.
Siehst Du, das ist ein gutes Beispiel dafür, was ich mit
Warum _keine_ fertigen Statements? Es ist wichtiger, dass man versteht,
welche Daten abgespeichert werden müssen. Das sind ja nicht besonders
meine. Wo kann man in Deinem Artikel lesen, dass dieser Fall relevant ist? Das
ist mir entgangen. Anderseits irrst Du, wenn Du glaubst, dass deswegen bei
MySQL die Verwendung von VARBINARY zwingend erforderlich wäre.
Selbstverständlich kann man auch beim Datentyp VARCHAR "Foo" und "foo" in
unterschiedlichen Datensätzen der gleichen Spalte abspeichern, auch wenn diese
Träger des Primärschlüssels ist. Bei MySQL benötigst Du lediglich noch das
Schlüsselwort BINARY im CREATE-Statement. Somit gibt es (mindestens) zwei
gleichwertige Wege, das gleiche Ziel zu erreichen.
Das Ziel sollte im Vordergrund stehen, nicht der Weg dahin.
Übrigens setzen laut betreffendem Handbuchabschnitt, beide Wege MySQL 4.1.2
voraus. Du solltest Deinen Artikel dahingehend korrigieren.
-- Begründe die 50.
Relativ großes Maximum, mit dem man herumexperimentieren kann.
Schreibe das:
"Mit 50 Bytes Nutzspeicher können die fast alle Tokens problemlos abgespeichert werden. Experimentieren Sie mit dieser Grenze!"
Wie handhabst Du zu lange Token? Bei Spalten variabler Länge dürftest D
problemlos 200 Byte und mehr verwenden können, ohne dass die Tabelle
signifikant umfangreicher würde. Der Speicherbedarf bei den Datentypen mit
variablem Umfang richtet sich ja nach dem tatsächlichen Inhalt, nicht dem
maximal möglichen.
-- die Angabe der Storage-Engine ist überflüssig, oder hast Du bestimmte
-- Gründe ausgerechnet MyIsam zu verwenden?
Ja, Performance. In meiner MySQL-Installation ist InnoDB voreingestellt, ich brauche aber weder Transaktionen noch referentielle Integrität, auch nicht wenn mehrere Benutzer gleichzeitig Trainingsdaten einfügen.
Wie groß ist der gemessene Performance-Unterschied der Datenbankzugriffe bei
den beiden Storage-Engines? Ist das wirklich ein Flaschenhals? Wenn nein,
fügt die Festlegung der Storage-Engine meiner Meinung nach eine überflüssige
Information hinzu, die für das Verständnis der Anwendung nicht nützlich ist.
Lass sie weg - und füge einen Satz zu möglicher Optimierung hinzu.
Viel wichtiger als das SQL-Statement fände ich jedoch einfach eine
Beispieltabelle mit ein paar realistischen Daten aus Deiner Praxis.
Beispieldaten erleichern das Verständnis ungemein. Lies einfach ein paar
Datenbankthreads hier im Forum(sarchiv).
Eine neue Kategorie bedeutet bei Dir
eine neue Spalte. Das ist normalerweise ein Anzeichen eines ungünstig
gewählten Tabellendesigns.
Das ist mir bewusst, aber die Spamfilter-Klasse ist nicht die Text-Kategorisierer-Klasse.
Der Spamfilter kann nur zwei Kategorien, aber dafür verbraucht er auch relativ wenig Speicherplatz.
Und dennoch ist Dein Tabellendesign falsch gewählt. Dies zeigt der wenig
geeignete Spaltenname Deiner ersten Spalte.
Dann füllst Du sie mit zwei Datensätzen. Viel einfacher hättest Du einfach
den Initialinhalt der Tabelle hingeschrieben. Einfacher, verständlicher.
Ich sollte dies dreifach unterstreichen.
"lnum" ist der (natürliche) Logarithmus der Anzahl der Kommentare
"lwords" ist der (natürliche) Logarithmus der Anzahl der Wörter
In dieser Tabelle sind Zeit ihres Lebens nur diese beiden Datensätze, die halt die Anzahl Wörter und die Anzahl Texte speichern. Wo genau ist jetzt das Problem?
Ganz einfach. Du solltest statt dem aussagelosen "lnum" das angemessene
"lcomments" verwenden. Es bietet sich an. Es drängt sich auf. Vernünftig
und einheitlich gewählte Bezeichner erleichtern das Verständnis,
erleichtern das Lesen von Code.
Meinst du so eine Tabelle für die Statistiken?
create table filter_stats (
id integer undigned auto_increment primary key,
category varchar(10) not null unique,
lnum float default 0,
lwords float default 0
);
So in etwa. Nur keine SQL-Statements, sondern Inhalt:
Die Statistiktabelle filter\_stats sieht unmittelbar nach dem Anlegen
wie folgt aus:
category | lcomments | lwords
\------------------------------
ham | 0 | 0
spam | 0 | 0
Diese Tabelle erklärt sich und ihren Aufbau besser als die drei (bzw. zwei)
dafür erforderlichen SQL-Statements.
Die Tabelle wird nie mehr als diese zwei Zeilen haben. Nur die Werte in
den Spalten lcomments und lwords verändern sich im Laufe der Zeit.
Dazu noch ein Beispiel für den Zustand nach dem Trainieren des Filters mit
einer angemessenen Beitragsanzahl. Welche das ist, wie die Zahlen aussehen,
untermauere mit Daten aus Deiner Praxis. Sowas finde ich anschaulich.
Zudem plädiere ich bei dieser Tabelle aus "Performancegründen" auf die
Verwendung von CHAR im Gegensatz zu VARCHAR. Datensätze gleicher Länge sind
vermutlich performanter zu lesen und zu schreiben. \*bg\* [1]
Zusammenfassend zur wirklich kleinen Datenbank:
Gib an, was für das Speichern relevant ist. Überlasse das Wie dem
interessierten Leser (zur Übung oder zum Nachlesen im Code).
Derzeit hast Du außerdem noch einen Syntaxfehler im SQL-Code:
~~~sql
insert ignore into filter_words (word)
values("foo", "bar");
Hier fehlen eine schließende und eine öffnende Klammer:
- Halte Dir bitte beim Lesen die Ohren zu :-) -
INSERT IGNORE INTO filter_words
(word)
VALUES
("foo"),
("bar");
Bitte beachte seths und Blaubarts Vorschläge zur Kommasetzung und ganz
besonders zum Aufteilen mehrteiliger Sätze in Einzelsätze (durch Punkt
getrennt). Ich neige ebenfalls zu diesen unnötig langen Sätze. Diese
sind nicht gut lesbar. Halte es daher mit Tucholsky:
"Hauptsätze, Hauptsätze, Hauptsätze."
und mit mir:
"Beispiele, Beispiele, Beispiele."
Freundliche Grüße
Vinzenz
[1] Bitte interpretiere das folgendermaßen:
Ich halte dies für genauso unwichtig wie die Wahl der Storage-Engine.
Um nicht zu relativieren: ich halte beides für unwichtig.