Kurs BZQ III - Stochastikpraktikum WS 2013/14 Humboldt-Universität zu Berlin Randolf Altmeyer Philip-Moritz Eckert Projektaufgaben Block 2 Abgabe: bis 10.12.2013 Zur Einstimmung (freiwillig, ohne Abgabe) Auf der Webseite findet man bei den Projektaufgaben zu Block 2 die Dateien regressionline.r und testlinmodel.r. Diese R-Dateien wurden benutzt, um die Beispielplots im Skript zu Block 2 zu erzeugen. Verwende diese Dateien, um dich mit dem linearen Modell in R vertraut zu machen. Verwende auch den Befehl predict. SPAM-Klassifizierer (SPAM oder HAM) Das Ziel dieser Aufgabe ist drei verschiedene SPAM-Klassifizierer zu implementieren und auf verschiedenen Datensätzen miteinander zu vergleichen. Zur Begriffsherkunft von SPAM siehe Wikipedia. Lies zunächst die nachfolgenden Informationen über die Methoden, Datensätze, Features usw. gründlich durch. Die eigentliche Aufgabenstellung steht weiter unten. Methoden Konkret betrachten wir die drei Methoden aus der Vorlesung: Lineare Regression, Logistische Regression und Naive Bayes. Für Lineare Regression und Logistische Regression können die R-Befehle lm bzw. glm verwendet werden (zusammen mit predict, wie in der Einstimmung weiter oben). Wer möchte kann die Lineare Regression auch selber implementieren mit der Formel für den kleinste-quadrate- Schätzer aus der Vorlesung (verwende dann aber QR-Zerlegung für die Inversion von X T X, z.b. mit qr). Datensätze Wir arbeiten mit den folgenden Datensätzen: 1
1. Der Data.frame spam aus dem R-package ElemStatLearn (enthält zahlreiche Beispiele aus dem Buch Hastie, Trevor and Tibshirani, Robert and Friedman, Jerome H., The Elements of Statistical Learning: Data Mining, Inference, and Prediction, das auch in der Vorlesung erwähnt worden ist). Enthält 4601 private Emails von George Forman, davon 1813 SPAM. Dementsprechend ist George eines der häufigsten Worte in den Emails. Die eigentlichen Emails sind hier nicht verfügbar, sondern nur eine Reihe von Features mit Klassifikation als SPAM oder nicht-spam. Für jede Email wurden 58 Features ausgewertet (hauptsächlich Wort-Häufigkeiten, aber auch Satzzeichen, usw.). Das letzte Feature enthält die Klassifikation in SPAM oder nicht-spam. Mehr Information erhält man mit?spam, nachdem man das R-package oben installiert hat. Außerdem findet man eine genaue Beschreibung der Features in der Datei spambase.names auf der Webseite. 2. Eine Sammlung von etwa 1000 Email-Textdateien, eingesammelt von einer Linguistik-Mailingliste. Die Dateien findet man als zip-datei emails.zip auf der Webseite. Im entpackten Ordner findet man die Email-Texte, die allerdings bereits vorverarbeitet wurden (alles Kleinbuchstaben, Worte auf Grundformen zurückgeführt, sehr häufige Worte wie Artikel und Personalpronomen wurden entfernt). Die Texte sind daher nicht mehr unbedingt verständlich. Sprachmodell und Features Für diese Aufgaben modellieren wir Emails als bag of words, d.h. jede Email wird durch die auftauchenden Worte repräsentiert, ohne Grammatik und ohne Beachtung der Wortreihenfolge (siehe auch Wikipedia). Dies ist ein entscheidender Schritt in der modernen Sprachverarbeitung, um die Komplexität der Daten zu reduzieren und wenn nur wenige Daten verfügbar sind. Um Features zu erzeugen gibt es nun mehrere Möglichkeiten. Da wir aber das bag of words Modell benutzen, verzichten wir von Anfang auf die Information welche Worte aufeinanderfolgen, was allerdings auch sehr informativ sein kann. Für die SPAM-Klassifikation reicht es aber oft aus nur auf bestimmte Schlüsselwörter zu achten. Dafür legt man zunächst ein Wörterbuch W fest, z.b. die häufigsten 250 Wörter in den Daten. Wir betrachten die folgenden beiden Möglichkeiten zur Erzeugung von Features, separat für jede Email in den Daten: 1. Binäre Features. Wie in der Vorlesung erwähnt, enthält der Feature-Vektor für jedes auftauchende Wort im Wörterbuch eine 1 und für jedes nicht auftauchende Wort eine 0. Wenn das Wörterbuch W die Länge W hat, dann ist also der Feature-Vektor X ein Vektor in {0, 1} W. 2. Stetige Features. Die Features sind relative Worthäufigkeiten in Prozent, d.h. wenn z.b. Apfel das k-te Wort in W ist, dann ist Also ist X [0, 100] W. X k = 100 #Apfel in Email #Anzahl aller W orte in Email. Training vs. Testing Das Vorgehen bei der Implementierung der drei genannten Methoden ist bei beiden Datensätzen und jeder Wahl von Features gleich: 1. Teile den Datensatz in Trainings- und Testdaten auf. 2. Trainiere den Klassifizierer auf den Trainingsdaten. 2
3. Teste den Klassifizierer auf neuen Daten, d.h. den Testdaten. Für welche Ergebnisse wir uns nach dem Testen interessieren wird unter Auswertung weiter unten erläutert. Zusätzlich zu dem einfachen Vorgehen oben soll eine Kreuzvalidierung (engl. Cross-validation) durchgeführt werden (siehe auch Wikipedia, der englische Wikipedia-Eintrag ist wesentlich informativer). Kurz zusammengefasst bedeutet dies: 1. Teile den Datensatz in n Mengen auf (n wird vorher festgelegt, oft ist n = 4 oder n = 10). 2. Für jede der n Mengen trainiere den Klassifizierer auf den übrigen n 1 Mengen und teste auf der betrachtenten Menge. 3. Mittele die Ergebnisse durch n. Auswertung In der Statistik- bzw. Machine Learning-Literatur findet man viele viele verschiedene Ansätze, um Ergebnisse von Regression bzw. Klassifikation zu untersuchen. Wir interessieren uns hier vorallem für accuracy, precision und recall (siehe Wikipedia für Definitionen). Gibt es noch mehr interessante Auswertungsmerkmale? Implementation von Naive Bayes Während für Lineare und Logistische Regression direkt die entsprechenden R-Methoden verwendet werden sollen, soll der Naive Bayes Klassifizierer explizit implementiert werden. Wie in der Vorlesung beschrieben muss man dafür im Wesentlichen die A-Priori-Wahrscheinlichkeiten P (Y = 1) bzw. P (Y = 0) und die bedingten Klassenwahrscheinlichkeiten P (X k Y = 1) bzw. P (X k Y = 0) aus den Daten berechnen, wobei Y die Zielvariable (SPAM oder nicht-spam) und X k das k-te Feature ist. Je nach Wahl der Features hat der zweite Schritt die folgende Form: 1. Binäre Features. X k nimmt nur die Werte 0 oder 1 an. Dann schätzt man also z.b. P (X k = 1 Y = 1) = #Anzahl der nicht SP AM Mail wo X k auftaucht + 1. #Anzahl aller nicht SP AM Mails + W Die 1 im Zähler bzw. W im Nenner (wobei W das Wörterbuch ist) entsprechen dem Laplace- Smoothing, um zu vermeiden, dass man 0-Wahrscheinlichkeiten erhält (Intuition: man fügt eine konzeptuelle Email hinzu, die alle Wörter im Wörterbuch enthält). 2. Stetige Features. Man nimmt an, dass X k bedingt auf Y durch eine Normalverteilung gegeben ist. Wir setzen dann P (X k = x Y = 1) = f (x, µ k,1, σ k,1 ), (1) für k = 1,..., W, wobei f die Dichte der Standardnormalverteilung ist, x R, µ k,1 R der Mittelwert von X k in der Klasse Y = 1 und σ k,1 die Standardabweichung in der Klasse Y = 1. Entsprechend definiert man µ k,0 und σ k,0. Bemerke, dass die Notation in (1) keine Standardnotation ist. Allerdings ist die linke Seite der Ausdruck aus der Bayesformel, und wird deswegen übernommen. 3
Aufgabenstellung Schreibe ein Programm, das die gegebenen Datensätze mit den beschriebenen Features und Methoden analysiert, mit Kreuzvalidierung. Für jeden Datensatz und jede Feature-Wahl soll eine Matrix ausgegeben werden, in der die Ergebnisse der Analyse für alle drei Klassifizierer angezeigt werden (Zeilen: Klassifizierer, Spalten: Auswertungsmethode). Insgesamt sollen also vier Matrizen erzeugt werden. Als Vorlage kann die Datei testclassifiers_vorlage.r von der Webseite verwendet werden. Bearbeite konkret wenigstens die folgenden Schritte (in der Reihenfolge, die du für richtig hälst): 1. Für den Grundlagenteil der Auswertung: (a) Beschreibe und vergleiche die einzelnen Methoden (Lineare Regression, Logistische Regression und Naive Bayes). (b) Beschreibe die Methode der Kreuzvalidierung und warum man sie in der Klassifikation einsetzt. Erkläre auch die Schwierigkeiten die auftreten können, wenn man die Daten in Teilmengen aufteilt im Bezug auf Ausgewogenheit der zu betrachtenden Klassen. (c) Beschreibe die erwähnten Methoden der Auswertung der Klassifikationen. Warum genügt accuracy im Bereich der Klassifikation nicht? Welchen Vorteil haben recall und accuracy? (d) Erkläre bzw. beweise, dass Gleichung (1) sinnvoll ist, d.h. zeige, dass f (x, µ k,1, σ k,1 ) eine sinnvolle Schätzung von P (X k = x Y = 1) im Rahmen der Naive Bayes-Methode ist, so wie sie in der Vorlesung präsentiert worden ist (Stichwort: Bayesformel für die Dichten). 2. Für den Teil Algorithmische Problemstellung der Auswertung: Erkläre die Grundidee des Programms, so wie du es geschrieben hast. Erläutere wichtige Methoden und Strukturen in R, die du verwendet hast. 3. Für den Ergebnisteil der Auswertung: (a) Erzeuge die vier Matrizen wie beschrieben und markiere in jeder Spalte den besten Klassifizierer. Vergleiche die Ergebnisse, insbesondere im Bezug auf Feature-Wahl. Sind die Ergebnisse erwartungsgemäß? (b) Diskutiere wie die Ergebnisse verbessert werden könnten, d.h. insbesondere wie der recall verbessert werden kann (im Grundlagenteil sollte klar geworden sein, dass der recall besonders wichtig ist). (c) Diskutiere (und demonstriere mit Zahlen) wie man bei Linearer und Logistischer Regression bestimmen kann, welche Features nicht signifikant sind, d.h. welche Features man auch weglassen könnte ohne das Ergebnis entscheidend zu beeinflussen. Welche Worte/Features sind besonders wichtig für die Klassifikation? 4. Zur Implementierung: (a) Analysiere zunächst den ersten Datensatz (spam), da dieser bereits ein data.frame ist und daher bereit zur Analyse. Die Features sind dort bereits als relative Häufigkeiten (wie oben beschrieben) gegeben. Um die Klassifikation mit binären Features durchzuführen, muss also eine Funktion geschrieben werden, die nichts anderes macht als nachzuschauen ob ein Wort/Feature in einer Email auftaucht oder nicht. (b) Für den zweiten Datensatz müssen erst alle Emails als Strings in R eingelesen und Worte gezählt werden. Außerdem muss ein Wörterbuch festgelegt weren (z.b. die ersten 250 häufigsten Worte). Erzeuge anschließend einen data.frame mit derselben Struktur wie für spam. Dann können alle Methoden genauso verwendet werden wie beim ersten Datensatz. 4
Hinweise zur Implementierung Essentiell für die ganze Aufgabe ist das Arbeiten mit data.frame-objekten in R. Data-Frames sind im Wesentlichen Matrizen, bei denen die Spalten Namen haben und die Datentypen in den Einträgen nicht beschränkt sind (für Matrizen müssen alle Einträge denselben Typ haben). Insbesondere kann man auf Spalten und Zeilen mit den jeweiligen Namen zugreifen. Sehr hilfreich sind z.b. (wenn datatrain ein data.frame ist) # z e i g e nur d i e S p a l t e n von datatrain an, d i e n i c h t den Namen "spam" haben datatrain [, colnames ( datatrain )!= "spam" ] # z e i g e nur d i e S p a l t e n "spam" an datatrain [, "spam" ] # normaler Z u g r i f f wie b e i Matrizen datatrain [ 3, 2 ] # f ü r Komponente in der d r i t t e n Z e i l e und z w e i t e n S p a l t e man sich mit names die Namen eines Objektes anzeigen lassen (funktioniert bei data.frames, Matrizen, Vektoren und Listen) für die Auwertung der Klassifizierer ist der Befehl table sehr hilfreich, da man damit sehr leicht Wahrheitsmatrizen erstellen (engl. confusion matrix), um zu sehen welche Daten richtig bzw. falsch klassifiziert worden sind, versuche z.b. table ( c ( " email ", " email ", "spam", " email " ), c ( " email ", "spam", "spam", " email " ) ) beim Naive Bayes Klassifizierer mit stetigen Features muss man überprüfen, ob der Vektor, aus dem man die Standardabweichung berechnet, nur 0-Einträge besitzt, weil in diesem die Normalverteilung nicht definiert ist und man keine Schätzung angeben kann sehr praktisch beim Umgang mit kategoriellen Daten ist der Befehl factor, z.b. in grouping < factor ( spam [, "spam" ] ) ; viele Befehle nehmen direkt einen Faktor als Argument, z.b. p r i o r s < table ( grouping ) /length ( grouping ) # wenn v ein Z a h l e n v e k t o r i s t tapply ( v, grouping, sd ) zur Kreuzvalidierung: Wenn alldata ein data.frame ist, in dem jede Zeile einer Email entspricht, so muss man für die Kreuzvalidierung diese Email in n Mengen aufteilen. Das kann man z.b. machen, indem man jeder Zeile eine Zahl zuordnet zwischen 1 und n und dann beim Trainieren bzw. Testen nur die Zeilen mit den entsprechenden Zahlen betrachtet, z.b. d a t a I d s < sample ( 1 : n, nrow( a l l D a t a ), replace = TRUE) for (ncv i n 1 : n ) { datatrain < a l l D a t a [ d a t a I d s!= ncv, ] datatest < a l l D a t a [ d a t a I d s == ncv, ] # e i g e n t l i c h e K l a s s i f i k a t i o n... } Beim zweiten Datensatz muss der data.frame mit den Wortfrequenzen erst selber erzeugt werden. Dafür sind die folgenden Befehle sehr hilfreich: 5
# A u f l i s t e n a l l e r Dateien in U n t e r v e r z e i c h n i s s e n von emails f i l e s < l i s t. f i l e s ( " e m a i l s ", r e c u r s i v e=true, f u l l. names=true) # E i n l e s e n e i n e r Datei a l s Vektor von S t r i n g s words < scan ( f i l e = f i l e, what = character ( ) ) ) # r e d u z i e r e n Vektor auf e i n d e u t i g e Einträge unique ( d i c t i o n a r y ) # z ä h l e f ü r j e d e s Wort im d i c t i o n a r y wie o f t es in words vorkommt table ( factor ( words, l e v e l s = d i c t i o n a r y ) ) 6