26. Anwendung: Ein Spamfilter

Leitfragen

Besser als der Naive Bayesian Classifier zumindest zur Spamerkennung ist der um Heuristiken angereicherte Plan for Spam von Paul Graham. Interessant ist dabei vor allem die Kombination von ” first principles“ und Einsichten über den Objektbereich. Ich werde hier nur ein paar grundsätzliche Punkte behandeln, mehr ist auf der oben zitierten Seite zu finden.

Nehmen wir zunächst an, wir wollten nur mit einem einzigen Wort w zwischen S und nicht S klassifizieren. Dann ist einfach

P (w |S)P (S)
P(S |w ) = --------------
               P (w ).

Alle Größen auf der rechten Seite sind, z.B. per ML, aus D schätzbar; P(S|click) könnte beispielsweise 0.95 sein, wenn 95% aller Vorkommen von ” click“ im Spam-Korpus sind.

Um Entartungen zu vermeiden, legen wir außerdem fest, dass die größten und kleinsten Wahrscheinlichkeiten 0.99 und 0.01 sind und inferieren nur Wahrscheinlichkeiten für Wörter, die mehr als etwa 5 Mal im Gesamtkorpus vorkommen. Hier sehen wir wieder mal einen nicht-erwartungstreuen Schätzer. Tatsächlich würden wir hier ja vielleicht lieber mit Good-Turing anrücken – aber unser Klassifikationsproblem verträgt diese heuristische Vorschrift in der Tat besser als einen ordentlich begründeten Schätzer.

Wir wollen jetzt P(S|ω) aus mehreren Wörtern schätzen. Wir haben weiter die Naive Bayesian Assumption, müssen also nur ∏ wωP(w|S) multiplizieren.

Es fehlen noch der Prior und die Normalisierung. Als Prior verwenden wir P(H) = P(S) = 0.5. Das mag willkürlich wirken, aber die Annahme von Gleichverteilung gilt gemeinhin als harmlos. Könnten wir uns auf unseren Korpus in dem Sinn verlassen, dass er tatsächlich aus dem ” typischen“ Mailaufkommen gezogen ist, also so viel Spam und Ham enthält wie eben an Mail reinkommt, könnten wir die Frequenzen im Korpus verwenden. Wirklich schlau wäre das aber nicht, weil dies viele in Wirklichkeit wichtige Parameter verdecken würde. Wenigstens in Europa kommt z.B. der größte Teil des Spam über Nacht an, man bräuchte also zeitabhängige Prioren. Allerdings stellt sich heraus, dass der Prior im Groben unwichtig ist und nur bei verschwindend wenigen Mails seine (vernünftige) Wahl einen Einfluss auf die Entscheidung hätte.

Die Normalisierung ist auch hier im Prinzip nicht nötig, aber es ist toller, wenn man eine Mail mit einer Wahrscheinlichkeit von 0.99 oder 0.71 als Spam klassifizieren kann.

Die Normalisierung könnten wir durch die volle Bayes-Formel machen (Division durch P(w)), einfacher aber ist es, P(S|ω) und P(H|ω) zu berechnen und durch die Summe zu teilen – wir wissen, dass P(c|ω) normalisiert sein muss.

Zusammen ergibt sich:

∏
           ∏--------P-(w∏|S-)P-(S-)-------
P(S |ω ) =   P (w |S ) +   (1 - P (w |S ))

Das Ganze funktioniert noch erheblich besser, wenn man nur die N ” signifikantesten“ Wörter verwendet, d.h. die mit den größten Abweichungen vom ” neutralen“ 0.5. Graham empfiehlt N= 15. Dies beugt auch Numerikproblemen vor.

Übungen zu diesem Abschnitt

Ihr solltet euch wenigstens an den rötlich unterlegten Aufgaben versuchen

(1)

Holt euch das Skript naiveBayesian.py von der Webseite zur Vorlesung. Macht euch klar, dass sowohl NaiveClassifier als auch GrahamClassifier das tun, was wir in der Vorlesung dazu gesagt haben. Dazu müsst ihr kurz sehen, was in den jeweiligen learn- und classify-Methoden steht. rawData wird von TrainableClassifier.train ausgefüllt; in dessen Docstring steht, was der Inhalt zu verstehen ist.

(2)

Besorgt euch eine Trainingsmenge von Ham und Spam. Die testClassifier-Funktion unten erwartet Spam- und Ham-Korpora in Dateien spam.canned und ham.canned (das sollten schon einige 100k sein) und Validierungsdaten als Einzeldateien in Unterverzeichnissen validation.ham und validation.spam. Die vorgefertigten Daten bestehen aus Ausschnitten aus Mailinglistenarchiven von GnuPG und einer Mukoviszidose-Selbsthilfegruppe (ham) und an mich adressiertem Spam. Das Spielen macht mehr Spaß, wenn ihr eure eigenen Daten verwendet.

Ruft testClassifier sowohl mit NaiveClassifier als auch mit GrahamClassifier auf. In der Ausgabe könnt ihr sehen, wie gut das funktioniert hat.

(3)

Sucht euch ein paar eigene Daten zum Klassifizieren. Das Ergebnis sollte schlechter bis katastrophal sein. Warum? Um dem Ganzen auf die Spur zu kommen, lasst euch beim GrahamClassifier die ersten paar (vielleicht 20) Elemente von sortedEvidence in _computePosProb ausgeben. Beim NaiveClassifier könnt ihr euch in der Schleife in classify word und die zugehörige wDist ausgeben lassen (bevorzugt, wenn die Dokumente, die ihr klassifizieren lassen wollt, nicht so lang sind).

(4)

Fügt Trainingsdaten, die zu den Dokumenten, die ihr klassifiziert haben möchtet, an die gegebenen Trainingsdaten an. Wie viele Daten braucht ihr, um die Klassifizierer auf eure speziellen Varianten von Ham und Spam zu trainieren?

(5)

Überlegt euch, ob ihr in eurem Datenleben andere, ähnlich zu behandelnde Klassifikationsprobleme habt. Vorschlag zu einer Implementierung: Umgebungen eines Wortes (z.B. Birne) als Dokumente nehmen und sehen, ob ihr die verschiedenen Lesungen mit den Klassifizierern auseinanderhalten könnt (dazu müsst ihr natürlich zunächst Trainingsdaten sammeln).

Dateien zu diesem Abschnitt


Markus Demleitner

Copyright Notice