Funkcje wyodrębniania zestawu funkcji

Przypomnijmy, że wcześniej wprowadziliśmy pojęcie danych jako prostokątów. Dlatego w tym ćwiczeniu zadaniem konstruowania danych treningowych jest „prostokątizacja”. Musimy ukształtować zestaw danych e-mail, aby pasował do użytecznego zestawu funkcji. Funkcje, które wyodrębniamy z wiadomości e-mail, będą kolumnami naszych danych szkoleniowych, a każdy wiersz będzie unikatowymi wartościami z jednego e-maila wypełniającego prostokąt. Konceptualizacja danych w ten sposób jest bardzo przydatna, ponieważ musimy wziąć częściowo ustrukturyzowane dane tekstowe z wiadomości e-mail i przekształcić je w wysoce uporządkowany zestaw danych szkoleniowych, który możemy wykorzystać do uszeregowania przyszłych wiadomości e-mail.

parse.email <- funkcja (ścieżka) {

full.msg <- msg.full (ścieżka)

data <- get.date (full.msg)

od <- get.from (full.msg)

subj <- get.subject (full.msg)

msg <- get.msg (full.msg)

return (c (data, od, subj, msg, ścieżka))

}

Aby zbadać ten proces, będziemy pracować wstecz i rozpoczniemy od zbadania funkcji parse.email. Wywoła to szereg funkcji pomocniczych, które wyodrębnią odpowiednie dane z każdej wiadomości, a następnie uporządkują te elementy w jednym wektorze. Wektor utworzony za pomocą polecenia c (date, from, subj, msg, path) stanowi pojedynczy wiersz danych, który zapełni nasze dane treningowe. Proces przekształcania każdej wiadomości e-mail w te wektory wymaga jednak klasycznego hakowania tekstu. Uwzględniamy ciąg ścieżki jako ostatnią kolumnę, ponieważ będzie ona tworzyć łatwiejsze zamawianie danych podczas fazy testowania.

msg.full <- funkcja (ścieżka) {

con <- plik (ścieżka, open = “rt”, kodowanie = “latin1”)

msg <- readLines (con)

zamknij (con)

return (msg)

}

Po prostu otwieramy ścieżkę pliku połączenia i wczytujemy zawartość pliku do wektora znaków. Funkcja readLines będzie tworzyć wektor, którego elementami jest każda linia w pliku. Tutaj nie przetwarzamy danych na tym etapie, ponieważ musimy wyodrębnić różne elementy z wiadomości. Zamiast tego zwrócimy cały e-mail jako wektor znaków i napiszemy osobne funkcje do pracy nad tym wektorem, aby wyodrębnić niezbędne dane. Mając w ręku wektor wiadomości, musimy zacząć przedzierać się przez dane, aby wydobyć jak najwięcej użytecznych informacji z wiadomości e-mail – i zorganizować je w jednolity sposób – w celu zbudowania naszych danych szkoleniowych. Zaczniemy od stosunkowo łatwego zadania wyodrębnienia adresu nadawcy. Aby to zrobić – i wszystkie wyodrębnione dane w tej sekcji – musimy zidentyfikować wzorce tekstowe w wiadomościach e-mail, które identyfikują poszukiwane dane. Aby to zrobić, rzućmy okiem na kilka wiadomości e-mail.

Przykład 1. Przykłady e-maili „From” wzoru tekstu E-mail nr 1

………………………………………………..

X-Sender: fortean3@pop3.easynet.co.uk (Unverified)

Message-Id: <p05100300ba138e802c7d@[194.154.104.171]>

To: Yahoogroups Forteana <zzzzteana@yahoogroups.com>

From: Joe McNally <joe@flaneur.org.uk>

X-Yahoo-Profile: wolf_solent23

MIME-Version: 1.0

Mailing-List: list zzzzteana@yahoogroups.com; contact

forteana-owner@yahoogroups.com

………………………………………………..

Email #2

………………………………………………..

Return-Path: paul-bayes@svensson.org

Delivery-Date: Fri Sep 6 17:27:57 2002

From: paul-bayes@svensson.org (Paul Svensson)

Date: Fri, 6 Sep 2002 12:27:57 -0400 (EDT)

Subject: [Spambayes] Corpus Collection (Was: Re: Deployment)

In-Reply-To: <200209061431.g86EVM114413@pcp02138704pcs.reston01.va.comcast.net>

Message-ID: <Pine.LNX.4.44.0209061150430.6840-100000@familjen.svensson.org>

………………………………………………..

Po przejrzeniu kilku wiadomości e-mail możemy zaobserwować w tekście kluczowe wzorce identyfikujące adres e-mail nadawcy. Przykład 1 pokazuje dwa fragmenty wiadomości e-mail, które podkreślają te wzorce. Najpierw musimy zidentyfikować wiersz w każdej wiadomości zawierającej adres e-mail. Z przykładów możemy zobaczyć, że ten wiersz ma zawsze termin „From:”, który ponownie jest określony w protokole e-mail wspomnianym wcześniej. Tak więc wykorzystamy te informacje do przeszukania wektora znaków dla każdego e-maila w celu zidentyfikowania poprawny element. Jak widać z przykładu 1, w e-mailach występują różnice w sposobie pisania adresu e-mail. Wiersz ten zawsze zawiera nazwę nadawcy i adres e-mail nadawcy, ale czasami adres jest zamknięty w nawiasach kątowych (E-mail nr 1), podczas gdy w innych nie jest on ujęty w nawiasy (E-mail nr 2). Z tego powodu napiszemy funkcję get.from, która do wyodrębniania używa wyrażeń regularnych dane dla tej funkcji.

get.from <- function(msg.vec) {

from <- msg.vec[grepl(“From: “, msg.vec)]

from <- strsplit(from, ‘[“:<> ]’)[[1]]

from <- from[which(from !=”” & from !=” “)]

return(from[grepl(“@”, from)][1])

}

Jak już widzieliśmy, R ma wiele potężnych funkcji do pracy z wyrażeniami regularnymi. Funkcja grepl działa tak jak zwykła funkcja grep w celu dopasowania wzorców wyrażeń regularnych, ale „l” oznacza logikę. Zatem zamiast zwracać indeksy wektorowe, zwróci wektor o tej samej długości co msg.vec z wartościami logicznymi wskazującymi, gdzie wzorzec został dopasowany w wektorze znaków. Po pierwszym wierszu tej funkcji zmienna from jest wektorem znaków z jednym elementem: „From:”

Teraz, gdy mamy prawidłową linię, musimy wyodrębnić sam adres. W tym celu użyjemy funkcji strsplit, która podzieli element znakowy na listę według danego wzorca wyrażenia regularnego. Aby poprawnie wyodrębnić adresy, musimy uwzględnić różnice we wzorach tekstowych zaobserwowane w przykładzie 1. Aby to zrobić, tworzymy zestaw znaków dla naszego wzoru za pomocą nawiasów kwadratowych. Tutaj znakami, na które chcemy podzielić tekst, są dwukropki, nawiasy trójkątne i pusty znak. Ten wzorzec zawsze umieszcza adres jako pierwszy element na liście, więc możemy go pobrać z listy za pomocą. Jednak ze względu na zmienność wzoru doda również puste elementy do tego wektora. Aby zwrócić tylko sam adres e-mail, zignorujemy te puste elementy, a następnie wyszukamy pozostały element zawierający symbol „@” i zwrócimy go. Przetwarzaliśmy teraz jedną czwartą danych potrzebnych do wygenerowania naszych danych treningowych.

get.msg <- function(msg.vec) {

msg <- msg.vec[seq(which(msg.vec == “”)[1] + 1, length(msg.vec), 1)]

return(paste(msg, collapse=”\n”))

}

Wyodrębnienie dwóch kolejnych funkcji, tematu i treści wiadomości, jest stosunkowo proste. Wcześniej musieliśmy wyodrębnić treść wiadomości, aby określić ilościowo warunki w spamie i wiadomościach e-mail ham. Dlatego funkcja get.msg po prostu replikuje wzorzec użyty tutaj do wykonania tego samego zadania. Pamiętaj, że treść wiadomości zawsze pojawia się po pierwszym pustym wierszu w wiadomości e-mail. Po prostu szukamy pierwszego pustego elementu w msg.vec i zwracamy wszystkie elementy po tym. Aby uprościć proces eksploracji tekstu, zwijamy te wektory w wektor jednoznakowy z funkcją wklejania i zwracamy to.

get.subject <- function(msg.vec) {

subj <- msg.vec[grepl(“Subject: “, msg.vec)]

if(length(subj) > 0) {

return(strsplit(subj, “Subject: “)[[1]][2])

}

else {

return(“”)

}

}

Wyodrębnianie tematu wiadomości e-mail przypomina wyodrębnianie adresu nadawcy, ale w rzeczywistości jest nieco prostsze. Dzięki funkcji get.subject ponownie użyjemy funkcji grepl, aby wyszukać wzorzec „Temat:” w każdym e-mailu i znaleźć wiersz w wiadomości zawierającej ten temat. Jest jednak pewien haczyk: jak się okazuje, nie każda wiadomość w zbiorze danych faktycznie ma temat. W związku z tym stosowane przez nas dopasowanie wzorca wysadzi te przypadki na krawędziach. Aby tego uniknąć, po prostu przetestujemy, czy nasze wezwanie do grepl rzeczywiście coś zwróciło. Aby to sprawdzić, sprawdzamy, czy długość subj wynosi więcej niż zero. Jeśli tak, dzielimy linię na podstawie naszego wzoru i zwracamy drugi element. Jeśli nie, zwracamy pusty znak. Domyślnie w R, gdy dopasowane funkcje, takie jak grepl, nie dopasowują się, zwracane są wartości specjalne, takie jak liczba całkowita (0) lub znak (0). Wartości te mają zerową długość, więc ten rodzaj sprawdzania jest zawsze dobrym pomysłem podczas uruchamiania funkcji na wielu nieporządnych danych. Wyodrębniliśmy trzy czwarte naszych funkcji, ale to ostatni element – data i godzina otrzymania wiadomości – sprawi, że najbardziej ucierpiemy. Praca z tym polem będzie trudna z dwóch powodów. Po pierwsze, radzenie sobie z datami jest prawie zawsze bolesną perspektywą, ponieważ różne języki programowania często mają nieco inne sposoby myślenia o czasie, aw tym przypadku R nie różni się. W końcu będziemy chcieli przekonwertować ciągi dat na obiekty daty POSIX, aby uporządkować dane chronologicznie. Ale aby to zrobić, potrzebujemy wspólnej reprezentacji dat, która prowadzi bezpośrednio do drugiego powodu naszego cierpienia: w korpusie publicznym SpamAssassin występują znaczne różnice w sposobie reprezentowania dat i godzin otrzymywania wiadomości. Przykład 2 ilustruje kilka przykładów tej odmiany

Przykład 2. Przykłady daty i godziny wiadomości e-mail zmienności wzoru tekstu

Email #1

………………………………………………..

Date: Thu, 22 Aug 2002 18:26:25 +0700

Date: Wed, 21 Aug 2002 10:54:46 -0500

From: Chris Garrigues lt;cwg-dated-1030377287.06fa6d@DeepEddy.Comgt;

Message-ID: lt;1029945287.4797.TMDA@deepeddy.vircio.comgt;

………………………………………………..

Email #2

………………………………………………..

List-Unsubscribe: lt;https://example.sourceforge.net/lists/listinfo/sitescooper-talkgt;,

lt;mailto:sitescooper-talk-request@lists.sourceforge.net?subject=unsubscribegt;

List-Archive: lt;http://www.geocrawler.com/redir-sf.php3?list=sitescooper-talkgt;

X-Original-Date: 30 Aug 2002 08:50:38 -0500

Date: 30 Aug 2002 08:50:38 -0500

………………………………………………..

Email #3

………………………………………………..

Date: Wed, 04 Dec 2002 11:36:32 GMT

Subject: [zzzzteana] Re: Bomb Ikea

Reply-To: zzzzteana@yahoogroups.com

Content-Type: text/plain; charset=US-ASCII

………………………………………………..

Email #4

………………………………………………..

Path: not-for-mail

From: Michael Hudson lt;mwh@python.netgt;

Date: 04 Dec 2002 11:49:23 +0000

Message-Id: lt;2madyyyyqa0s.fsf@starship.python.netgt;

………………………………………………..

Jak widać, przy wydobywaniu informacji o dacie i godzinie z każdego e-maila musimy pamiętać o wielu rzeczach. Pierwszą rzeczą, na którą należy zwrócić uwagę w przykładach w przykładzie 4-2 jest to, że dane, które chcemy wyodrębnić, są zawsze identyfikowane przez „Data:”; istnieje jednak wiele pułapek w stosowaniu tego wzorca, o których musimy pamiętać. Jak pokazano w e-mailu nr 1 z przykładu 4-2, czasami będzie wiele linii pasujących do tego wzorca. Podobnie, e-mail nr 2 pokazuje, że niektóre wiersze mogą być częściowymi dopasowaniami i w obu przypadkach dane w tych wierszach mogą być sprzeczne – tak jak w e-mailu nr 1. Następnie możemy zaobserwować nawet w tych czterech przykładach, że daty i godziny nie są przechowywane w jednolity sposób we wszystkich wiadomościach e-mail. We wszystkich wiadomościach e-mail występują zewnętrzne przesunięcia GMT i inne rodzaje informacji na etykietach. Wreszcie format daty i godziny w e-mailu nr 4 różni się całkowicie od poprzednich dwóch. Wszystkie te informacje będą miały decydujące znaczenie dla przekształcenia danych w jednolitą i wykonalną formę. Na razie jednak musimy skupić się tylko na wyodrębnieniu informacji o dacie i godzinie bez dodatkowych informacji o przesunięciu, definiując funkcję get.date. Po uzyskaniu wszystkich ciągów daty / godziny będziemy musieli poradzić sobie z konwersją sprzecznych formatów daty / godziny na jednolity obiekt POSIX, ale nie będzie to obsługiwane przez funkcję get.date.

get.date <- function(msg.vec) {

date.grep <- grepl(“^Date: “, msg.vec)

date.grepl <- which(date.grep == TRUE)

date <- msg.vec[date.grep[1]]

date <- strsplit(date, “\\+|\\-|: “)[[1]][2]

date <- gsub(“^\\s+|\\s+$”, “”, date)

return(strtrim(date, 25))

}

Jak wspomnieliśmy, wiele e-maili ma wiele pełnych lub częściowych dopasowań do wzorca „Data:”. Zwróć jednak uwagę na wiadomości e-mail nr 1 i nr 2 w przykładzie 2, że tylko jeden wiersz wiadomości e-mail zawiera „Data:” na początku ciągu. W e-mailu nr 1 znajduje się kilka pustych znaków poprzedzających ten wzór, a w e-mailu nr 2 wzór jest częściowo dopasowany do „X-Original-Date:”. Możemy zmusić wyrażenie regularne do dopasowania tylko ciągów, które na początku łańcucha mają „Data:”, używając operatora daszka z „^ Data:”. Teraz grepl zwróci PRAWDA tylko wtedy, gdy ten wzorzec rozpoczyna element wektora wiadomości. Następnie chcemy zwrócić pierwszy element w msg.vec, który pasuje do tego wzorca. Być może uda nam się po prostu zwrócić element w msg.vec, który pasuje do naszego wzorca w grepl, ale co, jeśli wiadomość e-mail zawiera wiersz rozpoczynający się „Data:”? Jeśli taki przypadek krawędzi wystąpi, wiemy, że pierwszy element, który pasował do naszego wzorca, będzie pochodził z nagłówka wiadomości, ponieważ informacje nagłówka zawsze pojawiają się przed treścią wiadomości. Aby zapobiec temu problemowi, zawsze zwracamy pierwszy pasujący element. Teraz musimy przetworzyć ten wiersz tekstu, aby zwrócić ciąg znaków, który ostatecznie można przekonwertować na obiekt POSIX w R. Zauważyliśmy już, że istnieją obce informacje i że daty i godziny nie są przechowywane w jednolitym formacie . Aby wyodrębnić informacje o dacie i godzinie, podzielimy ciąg znaków na znaki, aby oznaczyć informacje obce. W tym przypadku będzie to dwukropek, znak plus lub znak minus. W większości przypadków pozostawi nam to tylko informacje o dacie i godzinie oraz niektóre końcowe puste znaki. Użycie funkcji gsub w następnym wierszu zastąpi wszelkie początkowe lub końcowe białe spacje w ciągu znaków. Wreszcie, aby poradzić sobie z rodzajem danych, które obserwujemy w e-mailu nr 3 w przykładzie 2, po prostu usuniemy dowolne znaki po limicie 25 znaków. Standardowy ciąg danych / czasu ma 25 znaków, więc wiemy, że cokolwiek z tego jest obcego.

easyham.docs <- dir(easyham.path)

easyham.docs <- easyham.docs[which(easyham.docs != “cmds”)]

easyham.parse <- lapply(easyham.docs, function(p) parse.email(paste(easyham.path,

p, sep=””)))

ehparse.matrix <- do.call(rbind, easyham.parse)

allparse.df <- data.frame(ehparse.matrix, stringsAsFactors=FALSE)

names(allparse.df) <- c(“Date”, “From.EMail”, “Subject”, “Message”, “Path”)

Gratulacje! Z powodzeniem cierpisz, przekształcając ten amorficzny zestaw e-maili w ustrukturyzowany prostokąt odpowiedni do szkolenia naszego algorytmu rankingu. Teraz wszystko, co musimy zrobić, to nacisnąć przełącznik. Podobnie jak wcześniej, utworzymy wektor ze wszystkimi plikami „easy ham”, usuniemy dodatkowy plik „cmds” z wektora, a następnie użyjemy funkcji lapply, aby zastosować funkcję parse.email do każdego plik e-mail. Ponieważ wskazujemy pliki w katalogu danych w poprzednich tekstach, musimy również upewnić się, że ścieżka względna do tych plików jest połączona za pomocą funkcji wklejania i naszej ścieżki easyham.path wewnątrz wywołania lapply. Następnie musimy przekonwertować listę wektorów zwracanych przez lapply na macierz – czyli nasz prostokąt danych. Tak jak poprzednio, do utworzenia obiektu ehparse.matrix użyjemy funkcji do.call z rbind. Następnie przekonwertujemy to na ramkę danych wektorów znaków, a następnie ustawimy nazwy kolumn na c („Data”, „Od.EMail”, „Temat”, „Wiadomość”, „Ścieżka”). Aby sprawdzić wyniki, użyj head (allparse.df) do sprawdzenia pierwszych kilku wierszy ramki danych. Aby zaoszczędzić miejsce, nie będziemy tego kopiować tutaj, ale zalecamy to. Jednak zanim przystąpimy do tworzenia schematu ważenia na podstawie tych danych, pozostaje jeszcze trochę porządku.

date.converter <- funkcja (daty, wzorzec1, wzorzec2) {

pattern1.convert <- strptime (daty, wzór1)

pattern2.convert <- strptime (daty, wzór2)

pattern1.convert [is.na (pattern1.convert)] <-

pattern2.convert [is.na (pattern1.convert)]

return (pattern1.convert)

}

wzorzec1 <- “% a,% d% b% Y% H:% M:% S”

wzorzec2 <- “% d% b% Y% H:% M:% S”

allparse.df $ Data <- data.converter (allparse.df $ Data, wzór1, wzór2)

Jak wspomnieliśmy, naszą pierwszą próbą wyodrębnienia dat była po prostu izolacja tekstu. Teraz musimy wziąć ten tekst i przekonwertować go na obiekty POSIX, które można porównać logicznie. Jest to konieczne, ponieważ musimy sortować wiadomości e-mail chronologicznie. Przypomnij sobie, że przejście przez całe ćwiczenie jest pojęciem czasu i tego, jak różnice czasowe między obserwowanymi cechami można wykorzystać do wnioskowania o znaczeniu. Przedstawienie postaci dat i godzin nie wystarczy. Jak widzieliśmy w przykładzie 2, istnieją dwie odmiany formatu daty. Z tych przykładów e-mail nr 3 ma ciąg daty / godziny w formacie „Środa, 04 grudnia 2002 11:36:32”, podczas gdy e-mail nr 4 ma format „04 grudnia 2002 11:49:23”. Aby przekonwertować te dwa ciągi na formaty POSIX, będziemy musieli użyć funkcji strptime, ale przekażemy jej dwa różne formaty daty / godziny, aby dokonać konwersji. Każdy element tych ciągów pasuje do określonego elementu formatu POSIX, więc będziemy musieli określić ciągi konwersji pasujące do tych wariantów.

R używa standardowych ciągów formatu daty / godziny POSIX do wykonania tych konwersji. Istnieje wiele opcji dla tych ciągów i zalecamy przejrzenie dokumentacji w funkcji strptime za pomocą polecenia? Strptime, aby zobaczyć wszystkie opcje. Będziemy tutaj używać tylko kilku wybranych, ale ich głębsze zrozumienie będzie bardzo przydatne w pracy z datami i godzinami w R. Musimy przekonwertować ciągi w kolumnie Data allparse.df na dwa różne formaty POSIX osobno , a następnie ponownie połącz je z powrotem w ramkę danych, aby zakończyć konwersję. Aby to osiągnąć, zdefiniujemy funkcję date.converter, aby pobrać dwa różne wzorce POSIX i wektor znaków ciągów daty. Gdy wzorzec przekazany do strptime nie pasuje do przekazanego mu ciągu, domyślnym zachowaniem jest zwrócenie NA. Możemy to wykorzystać do rekombinacji konwertowanych wektorów znaków, zastępując elementy NA z pierwszej konwersji na te z drugiego. Ponieważ wiemy, że w danych występują tylko dwa wzorce, wynikiem będzie pojedynczy wektor ze wszystkimi ciągami dat konwertowanymi na obiekty POSIX.

allparse.df $ Temat <- tolower (allparse.df $ Temat)

allparse.df $ From.EMail <- tolower (allparse.df $ From.EMail)

Priority.df <- allparse.df [with (allparse.df, order (Date)),]

Priority.train <- Priority.df [1: (okrągły (nrow (Prior.df) / 2)),]

Ostatnim etapem czyszczenia jest konwersja wektorów znaków w kolumnach Temat i Od wiadomości e-mail na wszystkie małe litery. Ponownie odbywa się to w celu zapewnienia, że ​​wszystkie wprowadzane dane są tak jednolite, jak to możliwe, zanim przejdziemy do fazy szkolenia. Następnie sortujemy dane chronologicznie za pomocą kombinacji poleceń with i order w R. (R ma szczególnie nieintuicyjny sposób sortowania, ale ten skrót jest czymś, co często robisz, więc najlepiej się go zaznajomić.) zwraca wektor indeksów elementów w rosnącym porządku chronologicznym. Następnie, aby uporządkować ramkę danych według tych indeksów, musimy odwołać się do elementów allparse.df w tej kolejności i dodać ostatni przecinek przed zamknięciem nawiasu kwadratowego, aby wszystkie kolumny zostały posortowane w ten sposób. Wreszcie, pierwszą połowę chronologicznie posortowanej ramki danych przechowujemy jako priorytet ity.train. Dane w tej ramce danych zostaną wykorzystane do szkolenia naszego rankingu. Później wykorzystamy drugą połowę priorytetu.df do przetestowania rankingu. Po w pełni sformułowanych danych jesteśmy gotowi rozpocząć projektowanie naszego rankingu. Biorąc pod uwagę nasz zestaw funkcji, jednym ze sposobów jest zdefiniowanie wag dla każdej obserwowanej cechy w danych treningowych.

Pisanie priorytetowej skrzynki odbiorczej

Do tej pory zauważysz trend: zanim przejdziemy do seksownych części uczenia maszynowego, musimy zabrudzić sobie ręce hackowaniem danych, aby dzielić, wyciągać i analizować dane, dopóki nie nadają się do analizy. Jak dotąd musieliśmy cierpieć tylko nieznacznie podczas tego procesu. Aby zbudować klasyfikator spamu, musieliśmy po prostu wyodrębnić treść wiadomości e-mail, a następnie pozwolić pakietowi tm wykonać wszystkie ciężkie zadania. W tym ćwiczeniu dodajemy jednak kilka innych funkcji do naszego zestawu danych i komplikujemy proces, dodając również wymiar czasowy. W związku z tym będziemy musieli znacznie więcej operować na danych. Ale jesteśmy hakerami i brudzimy się danymi, co lubimy! W tym ćwiczeniu skupimy się tylko na wiadomościach e-mail z szynką z korpusu publicznego SpamAssassin. W odróżnieniu od ćwiczenia dotyczącego klasyfikacji spamu, tutaj nie zajmujemy się rodzajem wiadomości e-mail, ale raczej tym, jak każdy z nich powinien być uszeregowany pod względem priorytetu. Dlatego użyjemy największego zestawu danych łatwej szynki i nie będziemy się martwić o dołączanie innych typów e-maili. Ponieważ możemy bezpiecznie założyć, że użytkownik nie rozróżnia wiadomości e-mail w ten sposób, określając, które wiadomości e-mail mają wyższy priorytet, nie ma powodu, aby przenosić te informacje do naszego systemu rankingowego. Zamiast tego my chcemy dowiedzieć się jak najwięcej o naszych zestawach funkcji z wiadomości e-mail pojedynczego użytkownika, dlatego będziemy używać wiadomości e-mail w pierwszym zestawie danych easy ham.

library(tm)

library(ggplot2)

data.path <- “../../03-Classification/code/data/”

easyham.path <- paste(data.path, “easy_ham/”, sep=””)

Podobnie jak wcześniej, jedynymi pakietami R, których będziemy używać w tym ćwiczeniu, są tm do wyodrębniania wspólnych terminów z tematów i treści wiadomości e-mail oraz ggplot2 do wizualizacji wyników. Ponadto, ponieważ korpus publiczny SpamAssassin jest stosunkowo dużym zestawem danych tekstowych, nie powielimy go w folderze danych / dla tej części. Zamiast tego ustawimy ścieżkę względną danych z powrotem do ich położenia w plikach. Następnie stworzymy serię funkcji, które będą ze sobą współpracować w celu parsowania każdego e-maila w zestawie funkcji zilustrowanym w późniejszym przykładzie. Z tego diagramu wiemy, że musimy wyodrębnić cztery elementy z każdej wiadomości e-mail: adres nadawcy, datę otrzymania, temat i treść wiadomości.

Funkcje priorytetowe wiadomości e-mail

Jeśli korzystasz z usługi Gmaila Google do obsługi poczty e-mail, będziesz wiedział, że pomysł „priorytetowej skrzynki odbiorczej” został po raz pierwszy spopularyzowany przez Google w 2010 roku. Oczywiście to właśnie ten problem zainspirował studium przypadku dotyczące rankingu tej części, więc przydadzą nam się ponownie podejście przyjęte przez Google przy wdrażaniu algorytmu rankingowego, gdy przechodzimy do projektowania własnego. Na szczęście, kilka miesięcy po wydaniu przez Google funkcji priorytetowej skrzynki odbiorczej, opublikowali artykuł zatytułowany „Uczenie się za priorytetową skrzynką odbiorczą Gmaila”, który opisuje ich strategię projektowania podejścia do nadzorowanego uczenia się i sposób jego wdrożenia na dużą skalę . Do naszych celów interesuje nas tylko ten pierwszy. Jak wspomnieliśmy, pomiar czasu ma kluczowe znaczenie, w przypadku Google mają luksus długiej i szczegółowej historii interakcji użytkowników z pocztą e-mail. W szczególności priorytetowa skrzynka odbiorcza Google próbuje przewidzieć prawdopodobieństwo, że użytkownik wykona określone działanie na wiadomości e-mail w ciągu ustalonej liczby sekund od jej dostarczenia. Zestaw działań, które użytkownik może wykonać w Gmailu, jest duży: czytanie, odpowiadanie, etykietowanie itp. Ponadto dostarczenie nie jest jawnym czasem, w którym serwer otrzymuje wiadomość e-mail, ale czasem, w którym jest ona dostarczana użytkownikowi —aIe, kiedy sprawdza pocztę. Podobnie jak w przypadku klasyfikacji spamu, jest to stosunkowo prosty problem do stwierdzenia: jakie jest prawdopodobieństwo, że użytkownik wykona pewne działania, w ramach naszego zestawu możliwych działań, między pewną minimalną a maksymalną liczbą sekund, biorąc pod uwagę zestaw funkcji dla tego e-maila i wiedza, że ​​użytkownik ostatnio sprawdził swój e-mail? Na jakim wszechświecie możliwych funkcji poczty e-mail Google zdecydowało się skoncentrować? Jak można się spodziewać, objęli bardzo dużą liczbę. W przeciwieństwie do klasyfikacji spamu – którą prawie wszyscy użytkownicy będą kodować w ten sam sposób – każdy ma inny sposób uporządkowania priorytetu wiadomości e-mail. Biorąc pod uwagę tę zmienność sposobu, w jaki użytkownicy mogą oceniać zestaw funkcji, podejście Google wymagało włączenia wielu funkcji. Aby rozpocząć projektowanie algorytmu, inżynierowie Google zbadali różne rodzaje funkcji e-mail, które opisują w następujący sposób:

Istnieje wiele  funkcji należących do kilku kategorii. Funkcje społecznościowe opierają się na stopniu interakcji między nadawcą a odbiorcą, np. procent poczty nadawcy czytanej przez odbiorcę. Funkcje treści próbują zidentyfikować nagłówki i ostatnie hasła, które są wysoce skorelowane z odbiorcą działającym (lub nie) na poczcie, np. obecność ostatniego terminu w temacie. Ostatnie warunki użytkownika są wykrywane jako etap wstępnego przetwarzania przed nauką. Funkcje wątku zwracają uwagę na interakcję użytkownika z wątkiem, np. jeśli użytkownik rozpoczął wątek. Funkcje etykiet sprawdzają etykiety, które użytkownik stosuje do poczty za pomocą filtrów. Obliczamy wartości funkcji podczas rankingu i tymczasowo przechowujemy te wartości w celu późniejszego uczenia się. Funkcje ciągłe są automatycznie dzielone na funkcje binarne za pomocą prostego algorytmu w stylu ID3 na histogramie wartości funkcji. Jak wspomnieliśmy, Google ma długą historię interakcji użytkowników z Gmailem, co daje im bogatą perspektywę na to, jakie działania użytkownicy wykonują w e-mailach i kiedy. Niestety, takie szczegółowe dzienniki e-mail nie są dla nas dostępne w tym ćwiczeniu. Zamiast tego ponownie użyjemy publicznego korpusu SpamAssassin, dostępnego do bezpłatnego pobrania na stronie http://spamassassin.apache.org/publiccorpus/. Chociaż ten zestaw danych został rozpowszechniony jako sposób testowania algorytmów klasyfikacji spamu, zawiera również wygodną oś czasu wiadomości e-mail jednego użytkownika. Biorąc pod uwagę ten pojedynczy wątek, możemy zmienić przeznaczenie zestawu danych w celu zaprojektowania i przetestowania priorytetowego systemu rankingu wiadomości e-mail. Ponadto skupimy się tylko na wiadomościach e-mail z szynki z tego zestawu danych, więc wiemy, że wszystkie wiadomości, które badamy, to te, których użytkownik chciałby w swojej skrzynce odbiorczej. Zanim jednak przejdziemy dalej, musimy rozważyć, w jaki sposób nasze dane różnią się od danych w szczegółowym dzienniku e-mail – np. Google – i jak wpływa to na funkcje, które będziemy mogli wykorzystywać w naszym algorytmie. Zacznijmy od przejrzenia każdej z czterech kategorii zaproponowanych przez Google i ustalenia, w jaki sposób mogą one pasować do danych, których używamy.

Najbardziej krytyczną różnicą między szczegółowym dziennikiem e-mail a tym, nad czym będziemy pracować, jest to, że widzimy tylko otrzymane wiadomości. Oznacza to, że będziemy skutecznie „latać na wpół ślepo”, ponieważ nie mamy danych na temat tego, kiedy i jak użytkownik odpowiedział na wiadomości e-mail lub czy użytkownik był autorem wątku. Jest to znaczące ograniczenie, dlatego metody i algorytmy zastosowane w tej częście należy traktować wyłącznie jako ćwiczenia, a nie przykłady, w jaki sposób należy wdrożyć priorytetowe systemy skrzynki odbiorczej dla przedsiębiorstw. Mamy nadzieję, że uda nam się pokazać, w jaki sposób, nawet przy tym ograniczeniu, możemy wykorzystać dane, które mamy do stworzenia miar proxy dla ważności wiadomości e-mail i nadal zaprojektować stosunkowo dobry system rankingowy.

Biorąc pod uwagę, że e-mail jest medium transakcyjnym, z tego wynika, że ​​funkcje społecznościowe będą miały kluczowe znaczenie przy ocenie ważności e-maila. W naszym przypadku widzimy jednak tylko połowę tej transakcji. W szczegółowym przypadku chcielibyśmy zmierzyć wielkość interakcji między użytkownikiem a różnymi nadawcami wiadomości e-mail, aby ustalić, którzy nadawcy otrzymają od użytkownika bardziej natychmiastowe działania. Dzięki naszym danym możemy jednak mierzyć tylko wielkość przychodzącą. Możemy więc założyć, że ten jednokierunkowy wolumin jest dobrym proxy dla rodzaju funkcji społecznościowych, które próbujemy wyodrębnić z danych. Oczywiście nie jest to idealne. Przypomnijmy jednak, że w tym ćwiczeniu będziemy używać tylko wiadomości ham z publicznego korpusu SpamAssassin. Jeśli ktoś otrzyma dużą liczbę wiadomości e-mail z szynki z określonego adresu, może to oznaczać, że użytkownik ma silne połączenie z nadawcą. Alternatywnie może się zdarzyć, że użytkownik zostanie zapisany na listę mailingową o dużej objętości i wolałby, aby te e-maile nie otrzymywały wysokiego priorytetu. To jest właśnie powód, dla którego musimy uwzględnić inne funkcje w celu zrównoważenia tego rodzaju informacji podczas opracowywania naszego systemu rankingowego. Jednym problemem z patrzeniem tylko na liczbę wiadomości z danego adresu jest to, że składnik czasowy jest przedłużony. Ponieważ nasz zestaw danych jest statyczny w porównaniu do w pełni szczegółowego dziennika e-mail, musimy podzielić dane na segmenty czasowe i zmierzyć objętość w tych okresach, aby lepiej zrozumieć dynamikę czasową. Jak omówimy szczegółowo później, w tym ćwiczeniu po prostu uporządkujemy wszystkie wiadomości chronologicznie, a następnie podzielimy zestaw na pół. Pierwsza połowa zostanie wykorzystana do wyszkolenia algorytmu rankingowego, a druga połowa zostanie wykorzystana do przetestowania. W związku z tym objętość wiadomości z każdego adresu e-mail w całym okresie objętym danymi szkoleniowymi zostanie wykorzystana do szkolenia funkcji społecznościowych naszego szeregującego. Biorąc pod uwagę charakter naszych danych, może to być dobry początek, ale będziemy musieli osiągnąć głębsze zrozumienie, jeśli mamy nadzieję na dokładniejsze uszeregowanie wiadomości. Jednym ze sposobów podziału danych w celu uzyskania bardziej szczegółowego obrazu tej dynamiki jest identyfikacja wątków konwersacji, a następnie pomiar aktywności wewnątrz wątku. (Aby zidentyfikować wątki, możemy pożyczyć techniki używane przez innych klientów poczty e-mail i dopasować tematy wiadomości do kluczowych terminów wątków, takich jak „RE:”). Chociaż nie wiemy, jakie działania użytkownik podejmuje dla wątku, tutaj założenie jest takie, że jeśli jest bardzo aktywny, to prawdopodobnie będzie ważniejszy niż mniej aktywne wątki. Kompresując partycje czasowe na te małe kawałki, możemy uzyskać znacznie dokładniejszy serwer proxy dla funkcji wątków potrzebnych do modelowania priorytetu wiadomości e-mail. Następnie istnieje wiele funkcji treści, które możemy wyodrębnić z wiadomości e-mail, aby dodać je do naszego zestawu funkcji. W tym przypadku nadal będziemy utrzymywać względnie prostą sytuację, rozszerzając techniki eksploracji tekstu, których użyliśmy w Części 3 na ten kontekst. W szczególności, jeśli w tematach i treściach wiadomości e-mail otrzymywanych przez użytkownika występują wspólne terminy, przyszłe wiadomości e-mail zawierające te warunki w temacie i treści mogą być ważniejsze niż te, które ich nie zawierają. Jest to właściwie powszechna technika, o której wspomina krótko w opisie priorytetowej skrzynki odbiorczej Google. Dodając funkcje treści oparte na terminach dotyczących zarówno tematu, jak i treści wiadomości e-mail, napotkamy ciekawy problem ważenia. Zazwyczaj w temacie wiadomości e-mail jest znacznie mniej terminów niż w treści; dlatego nie powinniśmy w równym stopniu równoważyć względnego znaczenia wspólnych terminów w tych dwóch cechach. Wreszcie istnieje wiele funkcji używanych w implementacjach rozproszonych priorytetowych skrzynek odbiorczych dla przedsiębiorstw – takich jak Gmail – które są po prostu niedostępne w tym ćwiczeniu. Wspomnieliśmy już, że jesteśmy ślepi na znaczną część zestawu funkcji społecznościowych i dlatego musimy używać serwerów proxy do mierzenia tych interakcji. Ponadto istnieje wiele działań użytkowników, których nie mamy nawet możliwości oszacować. Na przykład działania użytkownika, takie jak etykietowanie lub przenoszenie wiadomości e-mail, są całkowicie niewidoczne dla naszego widoku. W implementacji priorytetowej skrzynki odbiorczej Google akcje te stanowią dużą część zestawu akcji, ale tutaj ich całkowicie brakuje. Ponownie, chociaż jest to słabość opisanego tutaj podejścia w porównaniu z tymi, które używają pełnych szczegółów dzienników e-mail, ponieważ nie są one dostępne w tym przypadku, fakt, że ich brakuje, nie wpłynie na nasze wyniki. Mamy teraz podstawowy plan zestawu funkcji, których użyjemy do stworzenia naszego systemu rankingu e-mail. Zaczynamy od uporządkowania wiadomości chronologicznie, ponieważ w tym przypadku większość tego, co jesteśmy zainteresowani w przewidywaniu, jest zawarta w wymiarze czasowym. Pierwsza połowa tych wiadomości służy do trenowania naszego rankingu, a następnie mamy cztery funkcje, których będziemy używać podczas szkolenia. Pierwsza to proxy funkcji społecznościowej, która mierzy objętość wiadomości od danego użytkownika w danych szkoleniowych. Następnie próbujemy skompresować pomiary czasowe, szukając wątków i oceniając aktywne wątki wyżej niż nieaktywne. Na koniec dodajemy dwie funkcje zawartości w oparciu o częste warunki w  tematach wiadomości e-mail i treść wiadomości. W następnej sekcji zaimplementujemy opisane wyżej podejście do priorytetowej skrzynki odbiorczej. Korzystając z wymienionych tutaj funkcji, określimy schemat ważenia, który próbuje szybko wypchnąć ważniejsze wiadomości na górę stosu. Tak jak poprzednio, jednak naszym pierwszym krokiem będzie pobranie nieprzetworzonych danych e-mail i wyodrębnienie odpowiednich elementów, aby pasowały do naszego zestawu funkcji.

Zamawianie wiadomości e-mail według priorytetu

Co sprawia, że ​​e-mail jest ważny? Aby zacząć na nie odpowiadać, cofnijmy się i zastanówmy się, co to jest e-mail. Po pierwsze, jest to medium transakcyjne. Z czasem ludzie wysyłają i odbierają wiadomości. W związku z tym, aby określić znaczenie wiadomości e-mail, musimy skupić się na samych transakcjach. W przeciwieństwie do zadania klasyfikacji spamu, w którym możemy wykorzystać statyczne informacje ze wszystkich wiadomości e-mail, aby określić ich typ, aby uszeregować wiadomości e-mail według ważności, musimy skupić się na dynamice transakcji przychodzących i wychodzących. W szczególności chcemy ustalić prawdopodobieństwo, że dana osoba wejdzie w interakcję z nowym e-mailem po jego otrzymaniu. Innymi słowy, biorąc pod uwagę zestaw funkcji, które wybraliśmy do zbadania, jakie jest prawdopodobieństwo, że czytelnik wykona akcję na tym e-mailu w najbliższej przyszłości? Krytycznym nowym wymiarem tego problemu jest czas. W kontekście transakcji, aby uszeregować rzeczy według ważności, musimy mieć pojęcie czasu. Naturalnym sposobem na wykorzystanie czasu do ustalenia ważności wiadomości e-mail jest zmierzenie, ile czasu zajmuje użytkownikowi wykonanie jakiejś czynności na wiadomości e-mail. Im krótszy jest średni czas potrzebny użytkownikowi na wykonanie określonej czynności w wiadomości e-mail, biorąc pod uwagę jego zestaw funkcji, tym ważniejsze mogą być tego typu wiadomości e-mail. Domyślne założenie w tym modelu jest takie, że ważniejsze wiadomości e-mail będą reagowane na wiadomości wcześniejsze niż mniej ważne. Intuicyjnie ma to sens. Wszyscy patrzyliśmy na kolejkę w naszej skrzynce odbiorczej i filtrowaliśmy wiadomości e-mail, które wymagały natychmiastowej odpowiedzi, w porównaniu z tymi, które mogły czekać. Filtrowanie, które wykonujemy w sposób naturalny, jest tym, czego spróbujemy nauczyć naszego algorytmu w następujących sekcjach. Zanim jednak zaczniemy, musimy ustalić, które funkcje w wiadomościach e-mail są dobrymi środkami proxy dla priorytetu.

Ranking: Priorytetowa skrzynka odbiorcza

Jak posortować coś, gdy nie znasz kolejności?

W Części 3 szczegółowo omówiliśmy koncepcję klasyfikacji binarnej, tj. umieszczenia przedmiotów w jednym z dwóch typów lub klas. W wielu przypadkach będziemy zadowoleni z podejścia, które może wprowadzić takie rozróżnienie. Ale co jeśli przedmioty w jednej klasie nie są tworzone równo i chcemy uszeregować przedmioty w klasie? Krótko mówiąc, co jeśli chcemy powiedzieć, że jeden e-mail jest najbardziej spamerski, a drugi jest drugim najbardziej spamerskim, lub chcemy rozróżnić między nimi w inny znaczący sposób? Załóżmy, że nie tylko chcieliśmy filtrować spam z naszego e-maila, ale chcieliśmy również umieścić „ważniejsze” wiadomości na górze kolejki. Jest to bardzo powszechny problem w uczeniu maszynowym i będzie przedmiotem tej części. Generowanie reguł rankingowania listy przedmiotów jest coraz częstszym zadaniem w uczeniu maszynowym, ale być może nie pomyślałeś o tym w tych kategoriach. Bardziej prawdopodobne jest to, że słyszałeś o systemie rekomendacji, który pośrednio tworzy ranking produktów. Nawet jeśli nie słyszałeś o systemie rekomendacji, to prawie pewne, że w pewnym momencie korzystałeś z systemu rekomendacji lub z niego korzystałeś. Niektóre z najbardziej udanych witryn e-commerce skorzystały z wykorzystania danych na temat swoich użytkowników, aby wygenerować rekomendacje dla innych produktów, którymi mogą być zainteresowani. Na przykład, jeśli kiedykolwiek robiłeś zakupy w Amazon.com, to wchodziłeś w interakcję z systemem rekomendacji. Problem, z którym boryka się Amazon, jest prosty: jakie przedmioty w ekwipunku najczęściej kupujesz? Implikacja tego stwierdzenia polega na tym, że elementy w ekwipunku Amazon mają porządek specyficzny dla każdego użytkownika. Podobnie, Netflix.com ma ogromną bibliotekę DVD, którą klienci mogą wypożyczyć. Aby ci klienci mogli jak najlepiej wykorzystać witrynę, Netflix stosuje wyrafinowany system rekomendacji, aby przedstawiać ludziom sugestie dotyczące wynajmu. W przypadku obu firm zalecenia te oparte są na dwóch rodzajach danych. Po pierwsze, istnieją dane dotyczące samego spisu. W przypadku Amazon, jeśli produktem jest telewizor, dane te mogą zawierać typ (np. Plazmowy, LCD, LED), cenę producenta i tak dalej. W przypadku Netflix te dane mogą być gatunkiem filmu, obsadą, reżyserem, czasem trwania itp. Po drugie, istnieją dane związane z zachowaniem klientów podczas przeglądania i zakupów. Tego rodzaju dane mogą pomóc Amazonowi zrozumieć, jakich akcesoriów szuka większość ludzi przy zakupie nowego telewizora plazmowego i pomóc Netflix zrozumieć, które komedie romantyczne najczęściej wypożyczają fani George’a A. Romero. W przypadku obu typów danych funkcje są dobrze zidentyfikowane. Oznacza to, że znamy etykiety dla danych kategorycznych, takich jak typ produktu lub gatunek filmu; podobnie dane generowane przez użytkowników są dobrze uporządkowane w postaci zapisów zakupu / wynajmu i wyraźnych ocen. Ponieważ zazwyczaj mamy wyraźne przykłady interesujących wyników podczas rangowania, jest to rodzaj problemu uczenia maszynowego, który często nazywa się uczeniem nadzorowanym. Jest to przeciwieństwo uczenia się bez nadzoru, w którym nie ma wcześniejszych przykładów wyników, gdy zaczynamy pracę z danymi. Aby lepiej zrozumieć różnicę, pomyśl o nadzorowanym uczeniu się jako procesie uczenia się poprzez instrukcje. Na przykład, jeśli chcesz nauczyć kogoś, jak upiec ciasto wiśniowe, możesz podać mu przepis, a następnie pozwolić mu skosztować powstałe ciasto. Po zobaczeniu, jak smakuje wynik, może postanowić nieco dostosować składniki. Posiadanie zapisanych składników (tj. nakładów) i smaku wyniku (tj. nakładu) oznacza, że ​​może on przeanalizować wkład każdego składnika i spróbować znaleźć idealny przepis na ciasto wiśniowe. Ewentualnie, gdybyś wiedział tylko, że do potraw z mrożoną fasolą zwykle dołącza się tortille, podczas gdy do potraw z pieczonymi wiśniami zwykle dołącza się ciasto, możesz być w stanie zgrupować inne składniki w klasy, które ostatecznie przypominałyby rzeczy, których używasz do robienia meksykańskich potraw w porównaniu z rodzajami rzeczy, których używasz do robienia amerykańskich deserów. Rzeczywiście, powszechną formą uczenia się bez nadzoru jest grupowanie, w którym chcemy przypisywać elementy do określonej liczby grup na podstawie podobieństw lub różnic. Jeśli już przeczytałeś i przeszedłeś ćwiczenie z Części 3, oznacza to, że rozwiązałeś już problem nadzorowanego uczenia się. Do celów klasyfikacji spamu znaliśmy warunki związane ze spamem i wiadomościami typu ham, a także wyszkoliliśmy naszego klasyfikatora na podstawie tego przepisu. To był bardzo prosty problem, więc mogliśmy uzyskać stosunkowo dobre wyniki klasyfikacji przy użyciu zestawu funkcji zawierającego tylko jeden element: warunki wiadomości e-mail. Jednak do rankingu musimy przypisać każdemu przedmiotowi unikalną wagę, aby dokładniej je rozwarstwić. W następnej sekcji zaczniemy zajmować się pytaniem zaproponowanym w tytule : jak posortować coś, jeśli nie znasz jeszcze jego kolejności? Jak zapewne zgadłeś, aby to zrobić w kontekście zamawiania wiadomości e-mail według ich ważności, będziemy musieli przeformułować pytanie w zakresie funkcji dostępnych dla nas w danych e-mail i związku tych funkcji z priorytetem wiadomości e-mail

Poprawa wyników

Tu przedstawimy ideę klasyfikacji tekstu. Aby to zrobić, stworzyliśmy bardzo prosty klasyfikator bayesowski przy użyciu minimalnej liczby założeń i funkcji. U podstaw tego rodzaju klasyfikacji leży zastosowanie klasycznej teorii prawdopodobieństwa warunkowego we współczesnym kontekście. Pomimo tego, że wyszkoliliśmy naszego klasyfikatora tylko ułamkiem wszystkich dostępnych danych, ta prosta metoda działała dość dobrze. To powiedziawszy, wskaźniki fałszywie dodatnie i fałszywie ujemne znalezione w danych testowych są zdecydowanie zbyt wysokie dla jakiegokolwiek produkcyjnego filtra antyspamowego. Jak wspomnieliśmy w tej części, istnieje wiele prostych poprawek, które moglibyśmy zastosować, aby poprawić wyniki obecnego modelu. Na przykład nasze podejście zakłada z góry, że każdy e-mail ma takie samo prawdopodobieństwo, że zostanie szynka lub spamem. W praktyce wiemy jednak, że relacja ta jest w rzeczywistości znacznie bliższa 80% –20% szynek do spamu. Jednym ze sposobów, w jaki moglibyśmy poprawić wyniki, byłoby po prostu zmiana naszych wcześniejszych przekonań, aby odzwierciedlić ten fakt i ponownie obliczyć przewidywane prawdopodobieństwa.

spam.classifier<-function(path) {

pr.spam<-classify.email(path, spam.df, prior=0.2)

pr.ham<-classify.email(path, easyham.df, prior=0.8)

return(c(pr.spam, pr.ham, ifelse(pr.spam > pr.ham, 1, 0)))

}

Zapamiętasz, że poprzedni parametr pozostawiliśmy jako coś, co może różnić się w funkcji classify.email, więc teraz musimy jedynie dokonać prostej zmiany w spam.classify pokazanej w poprzednim przykładzie. Możemy teraz ponownie uruchomić klasyfikator i porównać wyniki, i zachęcamy do tego. Te nowe założenia naruszają jednak dystrybucję wiadomości z szynką i spamem w naszych danych szkoleniowych. Aby być bardziej dokładnym, powinniśmy wrócić i ponownie przeszkolić klasyfikatora za pomocą pełnego zestawu łatwych szynek. Przypomnijmy, że ograniczyliśmy nasze pierwotne dane treningowe do szynki tylko do pierwszych 500 komunikatów, aby nasze dane treningowe odzwierciedlały założenia bayesowskie. W tym duchu musimy uwzględnić pełny zestaw danych, aby odzwierciedlić nasze nowe założenia.

Kiedy ponownie uruchomimy klasyfikator z nową parametryzacją easyham.df i classify.email, zauważymy zauważalną poprawę wydajności w przypadku wyników fałszywie dodatnich

Email type  : Klasyfikowany jako ham :  Klasyfikowany jako spam

Easy ham  : 0.90 : 0.10

Hard ham  : 0.82 : 0.18

Spam : 0.18 : 0.82

Dzięki tym prostym zmianom zmniejszyliśmy nasz wskaźnik fałszywie dodatnich o ponad 50%! Co ciekawe, poprawiając wydajność w ten sposób, cierpią nasze fałszywie ujemne wyniki. Zasadniczo to, co robimy, to przesuwanie granic decyzji (przypomnijmy przykład 3-1). W ten sposób jednoznacznie handlujemy fałszywymi pozytywami w celu poprawy fałszywych negatywów. Jest to doskonały przykład tego, dlaczego specyfikacja modelu ma kluczowe znaczenie i jak każde założenie i wybór funkcji może wpłynąć na wszystkie wyniki. W dalszej części rozwiniemy nasze spojrzenie na klasyfikację poza prosty binarny – ten czy tamten – przykład w tym ćwiczeniu. Jak wspomnieliśmy na początku, klasyfikacja obserwacji na podstawie pojedynczej decyzji często jest trudna lub niemożliwa. W następnej części wyjaśnimy, jak uszeregować wiadomości e-mail w oparciu o funkcje związane z wyższym priorytetem. Ważne, gdy zwiększamy spektrum zadań klasyfikacyjnych, są cechy, które postanowiliśmy uwzględnić w naszym modelu. Jak zobaczysz później, dane mogą ograniczyć wybór funkcji, co może mieć poważny wpływ na projekt naszego modelu

Testowanie klasyfikatora pod kątem wszystkich typów wiadomości e-mail

Pierwszym krokiem jest zbudowanie prostej funkcji, która wykona porównanie prawdopodobieństwa, które zrobiliśmy w poprzedniej sekcji, wszystkie naraz.

spam.classifier <- function(path) {

pr.spam <- classify.email(path, spam.df)

pr.ham <- classify.email(path, easyham.df)

return(c(pr.spam, pr.ham, ifelse(pr.spam > pr.ham, 1, 0)))

}

Dla uproszczenia funkcja spam.classifier określa, czy wiadomość e-mail jest spamem na podstawie danych szkoleniowych spam.df i easyham.df. Jeśli prawdopodobieństwo, że wiadomość jest spamem, jest większe niż prawdopodobieństwo, że jest ham, zwraca jeden; w przeciwnym razie zwraca zero.

Ostatnim krokiem w tym ćwiczeniu będzie przetestowanie drugiego zestawu spamu, easy ham i hard ham przy użyciu naszego prostego klasyfikatora. Kroki te przebiegają dokładnie tak, jak w poprzednich sekcjach: zawijanie funkcji spam.classifier w funkcję lapply, przekazywanie ścieżek plików e-mail i budowanie ramki danych. W związku z tym nie będziemy odtwarzać tych wywołań funkcji, ale zachęcamy do odniesienia się do pliku email_classifier.R rozpoczynającego się od wiersza 158, aby zobaczyć, jak to się robi. Nowa ramka danych zawiera prawdopodobieństwo bycia spamem lub szynką, klasyfikację i typ wiadomości e-mail dla każdej wiadomości we wszystkich trzech zestawach danych. Nowy zestaw danych nazywa się class.df i możemy użyć polecenia head do sprawdzenia jego zawartości:

head (klasa.df)

                Pr.SPAM             Pr.HAM  Klasa   Typ

1 2.712076e-307 1.248948e-282 FALSE EASYHAM

2 9,463296e-84 1,492094e-58 FALSE EASYHAM

3 1.276065e-59 3.264752e-36 FALSE EASYHAM

4 0,000000e + 00 3,539486e-319 FALSE EASYHAM

5 2.342400e-26 3.294720e-17 FALSE EASYHAM

6 2.968972e-314 1.858238e-260 FALSE EASYHAM

Z pierwszych sześciu wpisów wydaje się, że klasyfikator spisał się dobrze, ale obliczmy współczynniki fałszywie dodatnie i fałszywie ujemne dla klasyfikatora we wszystkich zestawach danych. Aby to zrobić, zbudujemy macierz N × M naszych wyników, w której wiersze są rzeczywistymi typami klasyfikacji, a kolumny typami przewidywanymi. Ponieważ mamy trzy typy wiadomości e-mail podzielone na dwa typy, nasza macierz nieporozumień będzie miała trzy wiersze i dwie kolumny (jak pokazano poniżej). Kolumny będą procentem przewidywanym jako szynka lub spam, a jeśli nasz klasyfikator będzie działał idealnie, kolumny odczytują [1,1,0] i  Odpowiednio [0,0,1].

Typ e-maila : Sklasyfikowany jako szynka%  : Sklasyfikowany jako spam

Easy ham :  0,78 : 0,22

Had ham :  0,73 : 0,27

Spam : 0,15 : 0,85

Niestety nie napisaliśmy doskonałego klasyfikatora, ale wyniki są nadal całkiem dobre. Podobnie jak w naszym początkowym teście, otrzymujemy około 25% odsetek wyników fałszywie dodatnich, przy czym nasz klasyfikator radzi sobie nieco lepiej na easy ham niż na twardym materiale. Z drugiej strony, wskaźnik negatywny jest znacznie niższy i wynosi zaledwie 15%. Aby lepiej zrozumieć, jak radził sobie nasz klasyfikator, możemy wykreślić wyniki za pomocą wykresu rozrzutu, z przewidywanymi prawdopodobieństwami bycie ham na osi x i spamem na osi y. Rysunek poniższy  pokazuje ten wykres rozrzutu w skali log-log.

 Transformacja dziennika jest wykonywana, ponieważ wiele przewidywanych prawdopodobieństw jest bardzo niewielkich, podczas gdy inne nie. Przy tak wysokim stopniu wariancji trudno jest bezpośrednio porównać wyniki. Zapisywanie dzienników to prosty sposób zmiany skali wizualnej w celu łatwiejszego porównania wartości. Dodaliśmy również prostą granicę decyzyjną do wykresów, gdzie y = x, lub idealną relację liniową. Dzieje się tak, ponieważ nasz klasyfikator porównuje dwa przewidywane prawdopodobieństwa i określa typ wiadomości e-mail na podstawie tego, czy prawdopodobieństwo spamu jest większe niż w przypadku szynki. Dlatego wszystkie kropki powyżej czarnej ukośnej linii powinny być spamem, a wszystkie poniżej powinny być szynką. Jak widać, tak nie jest – ale istnieje znaczne skupienie typów wiadomości. Rycina  pokazuje również intuicję na temat tego, jak klasyfikator osiąga gorsze wyniki w odniesieniu do wyników fałszywie dodatnich. Wydaje się, że istnieją dwa ogólne sposoby niepowodzenia. Po pierwsze, istnieje wiele wiadomości typu hard ham, które mają pozytywne prawdopodobieństwo, że są spamem, ale prawie zerowe prawdopodobieństwo, że są szynkami. Są to punkty dociśnięte do osi y. Po drugie, istnieją zarówno proste, jak i twarde szynki, które mają znacznie wyższe względne prawdopodobieństwo bycia szynką. Obie te obserwacje mogą wskazywać na słaby zestaw danych treningowych dla wiadomości e-mail z szynką, ponieważ wyraźnie istnieje wiele innych terminów, które powinny być powiązane z szynką, a które obecnie nie są.

Definiowanie klasyfikatora i testowanie go za pomocą hard ham

Chcemy zdefiniować klasyfikator, który będzie pobierał plik wiadomości e-mail i obliczał prawdopodobieństwo, że jest to spam lub ham. Na szczęście stworzyliśmy już większość funkcji i wygenerowaliśmy dane potrzebne do wykonania tego obliczenia. Zanim jednak będziemy mogli kontynuować, musimy wziąć pod uwagę jedną krytyczną komplikację. Musimy zdecydować, jak obsługiwać warunki w nowych wiadomościach e-mail, które pasują do warunków w naszym zestawie szkoleniowym, i jak obsługiwać warunki, które nie pasują do warunków w naszym zestawie szkoleniowym

Aby obliczyć prawdopodobieństwo, że wiadomość e-mail jest spamem lub ham, musimy znaleźć warunki, które są wspólne dla danych szkoleniowych i danej wiadomości. Następnie możemy wykorzystać prawdopodobieństwa związane z tymi terminami do obliczenia prawdopodobieństwa warunkowego, że wiadomość jest typu danych treningowych. Jest to dość proste, ale co robimy z klauzulami zawartymi w wiadomości e-mail, których nie ma w naszych danych szkoleniowych? Aby obliczyć warunkowe prawdopodobieństwo komunikatu, łączymy prawdopodobieństwa każdego terminu w danych szkoleniowych, biorąc ich iloczyn. Na przykład jeśli częstotliwość wyświetlania html w wiadomości spamowej wynosi 0,30, a częstotliwość wyświetlania tabeli w wiadomości spamowej wynosi 0,10, to powiemy, że prawdopodobieństwo wyświetlenia obu wiadomości w spamie wynosi 0,30 × 0,10 = 0,03. Ale w przypadku tych warunków w wiadomości e-mail, które nie są w naszych danych szkoleniowych, nie mamy informacji o ich częstotliwości w wiadomościach spamowych lub ham. Jednym z możliwych rozwiązań byłoby założenie, że ponieważ nie widzieliśmy jeszcze terminu, jego prawdopodobieństwo wystąpienia w pewnej klasie wynosi zero. Jest to jednak bardzo mylące. Po pierwsze, głupotą jest zakładanie, że w całym wszechświecie spam i ham nigdy nie zobaczymy takiego terminu, ponieważ go jeszcze nie widzieliśmy. Ponadto, ponieważ obliczamy prawdopodobieństwa warunkowe za pomocą produktów, jeśli przypisujemy zerowe prawdopodobieństwo terminom spoza naszych danych treningowych, podstawowa arytmetyka mówi nam, że obliczymy zero jako prawdopodobieństwo większości komunikatów, ponieważ pomnożymy wszystkie inne prawdopodobieństwa przez zero za każdym razem, gdy napotkamy nieznany termin. Spowodowałoby to katastrofalne skutki dla naszego klasyfikatora, ponieważ wielu, a nawet wszystkim wiadomościom niepoprawnie przypisano by zerowe prawdopodobieństwo, że będą spamem lub ham. Badacze wymyślili wiele sprytnych sposobów obejścia tego problemu, takich jak wyciąganie losowego prawdopodobieństwa z pewnej dystrybucji lub stosowanie technik przetwarzania języka naturalnego (NLP) w celu oszacowania „spamowania” danego terminu z uwagi na jego kontekst. Do naszych celów zastosujemy bardzo prostą zasadę: przypisuj bardzo małe prawdopodobieństwo terminom, których nie ma w zestawie szkoleniowym. Jest to w rzeczywistości powszechny sposób radzenia sobie z brakującymi terminami w prostych klasyfikatorach tekstów i dla naszych celów będzie dobrze. W tym ćwiczeniu domyślnie ustawimy to prawdopodobieństwo na 0,0001%, czyli jedną dziesiątą tysięcznej części procentowej, co jest wystarczająco małe dla tego zestawu danych. Wreszcie, ponieważ zakładamy, że wszystkie wiadomości e-mail są równie prawdopodobne, że są ham lub spamem, ustaliliśmy nasze wcześniejsze przekonanie, że wiadomość e-mail jest pewnego rodzaju na 50%. Aby jednak później powrócić do tego problemu, konstruujemy funkcję classify.email w taki sposób, aby można było zmieniać wcześniejsze.

Zachowaj ostrożność, aby zawsze używać 0,0001% dla terminów spoza zestawu treningowego. Używamy go w tym przykładzie, ale w innych może być za duży lub za mały, w takim przypadku system, który zbudujesz, w ogóle nie będzie działał!

classify.email <- function(path, training.df, prior=0.5, c=1e-6) {

msg <- get.msg(path)

msg.tdm <- get.tdm(msg)

msg.freq <- rowSums(as.matrix(msg.tdm))

# Find intersections of words

msg.match <- intersect(names(msg.freq), training.df$term)

if(length(msg.match) < 1) {

return(prior*c^(length(msg.freq)))

}

else {

match.probs <- training.df$occurrence[match(msg.match, training.df$term)]

return(prior * prod(match.probs) * c^(length(msg.freq)-length(msg.match)))

}

}

Zauważysz, że pierwsze trzy kroki funkcji classify.email przebiegają tak, jak zrobiła to nasza faza szkolenia. Musimy wyodrębnić tekst wiadomości za pomocą get.msg, przekształcić go w TDM za pomocą get.tdm, a na końcu obliczyć częstotliwość terminów za pomocą rowSums. Następnie musimy dowiedzieć się, w jaki sposób warunki w wiadomości e-mail przecinają się z warunkami w naszych danych szkoleniowych, jak pokazano na powyższym rysunku. Aby to zrobić, używamy polecenia intersect, przekazując warunki znalezione w wiadomości e-mail i warunki w danych szkoleniowych. Zwrócone zostaną te warunki w szarym zacienionym obszarze na powyższym rysunku. Ostatnim krokiem klasyfikacji jest ustalenie, czy którekolwiek ze słów w wiadomości e-mail występuje w zestawie szkoleniowym, a jeśli tak, wykorzystujemy je do obliczenia prawdopodobieństwa, że ​​ta wiadomość należy do danej klasy. Załóżmy na razie, że próbujemy ustalić, czy ta wiadomość e-mail jest spamem. msg.match będzie zawierać wszystkie warunki zawarte w wiadomości e-mail w naszych danych szkoleniowych dotyczących spamu, spam.df. Jeśli to skrzyżowanie jest puste, długość msg.match będzie mniejsza niż zero, a my możemy zaktualizować nasz wcześniejszy wynik, mnożąc go przez iloczyn liczby terminów w wiadomości e-mail przez naszą niewielką wartość prawdopodobieństwa: c. Rezultatem będzie niewielkie prawdopodobieństwo przypisania etykiety spamu do wiadomości e-mail. I odwrotnie, jeśli to skrzyżowanie nie jest puste, musimy znaleźć te terminy z funkcji dopasowania w wiadomości e-mail, aby wykonać wyszukiwanie, co zwróci pozycję elementu terminu w kolumnie terminu naszych danych treningowych. Używamy tych pozycji elementów, aby zwrócić odpowiednie prawdopodobieństwa z kolumny wystąpienia i zwróć te wartości do match.probs. Następnie obliczamy iloczyn tych wartości i łączymy to z naszym wcześniejszym przekonaniem, że wiadomość e-mail jest spamem, z terminem prawdopodobieństwa i prawdopodobieństwem braku terminów. Wynikiem jest nasze oszacowanie bayesowskie dotyczące prawdopodobieństwa, że ​​wiadomość jest spamem, biorąc pod uwagę pasujące warunki w naszych danych szkoleniowych. Jako wstępny test wykorzystamy nasze dane szkoleniowe ze spamu i wiadomości z ham, aby sklasyfikować e-maile z ham twardą. Wiemy, że wszystkie te e-maile to ham, więc idealnie nasze klasyfikator przypisze większe prawdopodobieństwo bycia ham do wszystkich tych komunikatów. Wiemy jednak również, że wiadomości typu hard ham są „trudne” do sklasyfikowania, ponieważ zawierają terminy, które są również powiązane ze spamem. Zobaczmy teraz, jak działa nasz prosty klasyfikator!

hardham.docs <- dir(hardham.path)

hardham.docs <- hardham.docs[which(hardham.docs != “cmds”)]

hardham.spamtest <- sapply(hardham.docs,

function(p) classify.email(paste(hardham.path, p, sep=””),

training.df=spam.df))

hardham.hamtest <- sapply(hardham.docs,

function(p) classify.email(paste(hardham.path, p, sep=””),

training.df=easyham.df))

hardham.res <- ifelse(hardham.spamtest > hardham.hamtest, TRUE, FALSE)

summary(hardham.res)

Tak jak poprzednio, musimy uporządkować wszystkie ścieżki plików, a następnie możemy przetestować klasyfikator dla wszystkich wiadomości z twardą ham, zawijając zarówno test spamu, jak i test szynki w wywoływaniu sapli. Wektory hardham.spamtest i hardham.hamtest zawierają obliczenia prawdopodobieństwa warunkowego dla każdego e-maila z ham twardą, że jest spamem lub ham, biorąc pod uwagę odpowiednie dane szkoleniowe. Następnie używamy polecenia ifelse, aby porównać prawdopodobieństwa w każdym wektorze. Jeśli wartość w hardham.spamtest jest większa niż w hardham.hamtest, klasyfikator sklasyfikował wiadomość jako spam; w przeciwnym razie jest to ham. Na koniec używamy polecenia summary, aby sprawdzić wyniki wymienione poniżej

Testowanie naszego klasyfikatora pod kątem „twardego ham”

Rodzaj e-maila :  Numer sklasyfikowany jako ham :  Numer sklasyfikowany jako spam

Twardy ham  : 184  : 65

Gratulacje! Napisałeś swój pierwszy klasyfikator, który dość dobrze poradził sobie z identyfikacją twardej szynki jako nonspam. W tym przypadku mamy około 26% odsetek wyników fałszywie dodatnich. Oznacza to, że około jedna czwarta wiadomości e-mail z twardą ham jest nieprawidłowo zidentyfikowana jako spam. Możesz myśleć, że jest to słaba wydajność, a podczas produkcji nie chcielibyśmy oferować platformy e-mail z tymi wynikami, ale biorąc pod uwagę, jak prosty jest nasz klasyfikator, robi to całkiem dobrze. Oczywiście lepszym testem jest sprawdzenie, jak klasyfikator radzi sobie nie tylko z ham twardą, ale także z ham łatwą i spamem.

Pisanie naszego pierwszego bayesowskiego klasyfikatora spamu

Jak wspomnieliśmy wcześniej , będziemy używać publicznego korpusu SpamAssassin zarówno do szkolenia, jak i testowania naszego klasyfikatora. Dane te obejmują e-maile opatrzone etykietami z trzech kategorii: „spam”, „ham łatwa” i „ham twarda”. Jak można się spodziewać, „twarda ham” jest trudniejsza do odróżnienia od spamu niż proste rzeczy. Na przykład wiadomości z ham często zawierają tagi HTML. Przypomnij sobie, że jednym ze sposobów łatwego identyfikowania spamu jest obecność tych tagów. Aby dokładniej sklasyfikować twardą szynkę, będziemy musieli podać więcej informacji z wielu innych funkcji tekstowych. Wyodrębnienie tych funkcji wymaga trochę eksploracji tekstu plików e-mail i stanowi nasz pierwszy krok w tworzeniu klasyfikatora. Wszystkie nasze nieprzetworzone pliki e-mail zawierają nagłówki i tekst wiadomości. Typowy e-mail z „łatwą ham” wygląda jak w przykładzie 3-1. Zauważysz kilka interesujących funkcji tego tekstu. Po pierwsze, nagłówek zawiera wiele informacji o tym, skąd pochodzi ten e-mail. W rzeczywistości, ze względu na ograniczenia wielkości, w Przykładzie 3-1 umieściliśmy tylko część całego nagłówka. I pomimo faktu, że w nagłówkach znajduje się wiele przydatnych informacji, nie będziemy wykorzystywać żadnej z tych informacji w naszym klasyfikatorze. Zamiast skupiać się na funkcjach zawartych w transmisji wiadomości, jesteśmy zainteresowani tym, jak treść samych wiadomości może pomóc w przewidywaniu rodzaju wiadomości e-mail. Nie oznacza to, że należy zawsze ignorować nagłówek lub inne informacje. W rzeczywistości wszystkie wyrafinowane nowoczesne filtry antyspamowe wykorzystują informacje zawarte w nagłówkach wiadomości e-mail, takie jak to, czy ich fragmenty zostały sfałszowane, czy wiadomość pochodzi od znanego spamera, czy też brakuje bitów. Ponieważ skupiamy się tylko na treści wiadomości e-mail, musimy wyodrębnić ten tekst z plików wiadomości. Jeśli przejrzysz niektóre pliki wiadomości zawarte w tym ćwiczeniu, zauważysz, że wiadomość e-mail zawsze zaczyna się po pierwszym pełnym podziale wiersza w pliku e-mail. W przykładzie 3-1 widzimy zdanie „Witaj, czy widziałeś i omawiałeś ten artykuł i jego podejście?” przychodzi bezpośrednio po zerwaniu pierwszej linii. Aby rozpocząć budowanie naszego klasyfikatora, musimy najpierw utworzyć funkcje R, które mogą uzyskać dostęp do plików i wyodrębnić tekst wiadomości, korzystając z tej konwencji tekstu.

Przykład 3-1. Typowy e-mail „easy ham”

………………………………………………..

Received: from usw-sf-list1-b.sourceforge.net ([10.3.1.13]

helo=usw-sf-list1.sourceforge.net) by usw-sf-list2.sourceforge.net with

esmtp (Exim 3.31-VA-mm2 #1 (Debian)) id 17hsof-00042r-00; Thu,

22 Aug 2002 07:20:05 -0700

Received: from vivi.uptime.at ([62.116.87.11] helo=mail.uptime.at) by

usw-sf-list1.sourceforge.net with esmtp (Exim 3.31-VA-mm2 #1 (Debian)) id

17hsoM-0000Ge-00 for <spamassassin-devel@lists.sourceforge.net>;

Thu, 22 Aug 2002 07:19:47 -0700

Received: from [192.168.0.4] (chello062178142216.4.14.vie.surfer.at

[62.178.142.216]) (authenticated bits=0) by mail.uptime.at (8.12.5/8.12.5)

with ESMTP id g7MEI7Vp022036 for

<spamassassin-devel@lists.sourceforge.net>; Thu, 22 Aug 2002 16:18:07

+0200

From: David H=?ISO-8859-1?B?9g==?=hn <dh@uptime.at>

To: <spamassassin-devel@example.sourceforge.net>

Message-Id: <B98ABFA4.1F87%dh@uptime.at>

MIME-Version: 1.0

X-Trusted: YES

X-From-Laptop: YES

Content-Type: text/plain; charset=”US-ASCII”

Content-Transfer-Encoding: 7bit

X-Mailscanner: Nothing found, baby

Subject: [SAdev] Interesting approach to Spam handling..

Sender: spamassassin-devel-admin@example.sourceforge.net

Errors-To: spamassassin-devel-admin@example.sourceforge.net

X-Beenthere: spamassassin-devel@example.sourceforge.net

X-Mailman-Version: 2.0.9-sf.net

Precedence: bulk

List-Help: <mailto:spamassassin-devel-request@example.sourceforge.net?subject=help>

List-Post: <mailto:spamassassin-devel@example.sourceforge.net>

List-Subscribe: <https://example.sourceforge.net/lists/listinfo/spamassassin-devel>,

<mailto:spamassassin-devel-request@lists.sourceforge.net?subject=subscribe>

List-Id: SpamAssassin Developers <spamassassin-devel.example.sourceforge.net>

List-Unsubscribe: <https://example.sourceforge.net/lists/listinfo/spamassassin-devel>,

<mailto:spamassassin-devel-request@lists.sourceforge.net?subject=unsubscribe>

List-Archive: <http://www.geocrawler.com/redir-sf.php3?list=spamassassin-devel>

X-Original-Date: Thu, 22 Aug 2002 16:19:48 +0200

Date: Thu, 22 Aug 2002 16:19:48 +0200

Hello, have you seen and discussed this article and his approach?

Thank you

http://www.paulgraham.com/spam.html

— “Hell, there are no rules here– we’re trying to accomplish something.”

— Thomas Alva Edison

——————————————————-

This sf.net email is sponsored by: OSDN – Tired of that same old

cell phone? Get a new here for FREE!

https://www.inphonic.com/r.asp?r=sourceforge1&refcode1=vs3390

_______________________________________________

Spamassassin-devel mailing list

Spamassassin-devel@lists.sourceforge.net

https://lists.sourceforge.net/lists/listinfo/spamassassin-devel

„Linia zerowa” oddzielająca nagłówek od treści wiadomości e-mail jest częścią definicji protokołu.

Jak zawsze, pierwszą rzeczą do zrobienia jest załadowanie bibliotek, których użyjemy do tego ćwiczenia. Do klasyfikacji tekstu użyjemy pakietu tm, który oznacza eksplorację tekstu. Po zbudowaniu i przetestowaniu naszego klasyfikatora użyjemy pakietu ggplot2 do wizualnej analizy wyników. Kolejnym ważnym wstępnym krokiem jest ustawienie zmiennych ścieżki dla wszystkich plików e-mail. Jak wspomniano, mamy trzy rodzaje wiadomości: łatwa ham, twarda ham i spam. W katalogu plików danych dla tego ćwiczenia zauważysz, że dla każdego typu wiadomości istnieją dwa oddzielne zestawy folderów plików. Wykorzystamy pierwszy zestaw plików do wyszkolenia klasyfikatora, a drugi zestaw do przetestowania go.

biblioteka ™

biblioteka (ggplot2)

spam.path <- „data / spam /”

spam2.path <- “data / spam_2 /”

easyham.path <- “data / easy_ham /”

easyham2.path <- “data / easy_ham_2 /”

hardham.path <- “data / hard_ham /”

hardham2.path <- “data / hard_ham_2 /”

Po załadowaniu wymaganych pakietów i ustawieniu zmiennych ścieżek możemy zacząć budować naszą wiedzę na temat rodzajów terminów używanych w spamie i szynce, tworząc korpusy tekstowe z obu zestawów plików. W tym celu napiszemy funkcję, która otwiera każdy plik, znajduje pierwszy podział wiersza i zwraca tekst poniżej tego podziału jako wektor znakowy z pojedynczym elementem tekstowym.

get.msg <- funkcja (ścieżka) {

con <- plik (ścieżka, open = “rt”, kodowanie = “latin1”)

tekst <- readLines (con)

# Wiadomość zawsze zaczyna się po pierwszym pełnym podziale linii

msg <- tekst [sekw. (który (tekst == “”) [1] + 1, długość (tekst), 1)]

zamknij (con)

return (wklej (msg, collapse = “\ n”))

}

Język R wykonuje operacje we / wy pliku w bardzo podobny sposób, jak wiele innych języków programowania. Pokazana tutaj funkcja przyjmuje ścieżkę pliku jako ciąg znaków i otwiera ten plik w trybie RT, który oznacza odczyt jako tekst. Zauważ też, że kodowanie to latin1. Wynika to z faktu, że wiele wiadomości e-mail zawiera znaki spoza ASCII, a to kodowanie pozwoli nam korzystać z tych plików. Funkcja readLines zwróci każdą linię tekstu w połączeniu pliku jako oddzielny element wektora znaków. Dlatego po przeczytaniu wszystkich wierszy chcemy zlokalizować pierwszy pusty element tekstu, a następnie wyodrębnić wszystkie elementy później. Gdy otrzymamy wiadomość e-mail jako wektor znakowy, zamkniemy połączenie pliku, a następnie zwiniemy wektor w pojedynczy element znakowy, używając funkcji wklejania i \ n (nowa linia) dla argumentu zwinięcia. Aby wyszkolić naszego klasyfikatora, będziemy musieli otrzymywać wiadomości e-mail ze wszystkich naszych wiadomości e-mail ze spamem i ham. Jednym z podejść jest utworzenie wektora zawierającego wszystkie wiadomości, tak aby każdy element wektora był pojedynczym e-mailem. Najprostsza droga do osiągnięcie tego w R oznacza użycie funkcji Apply z naszą nowo utworzoną funkcją get.msg.

spam.docs <- dir (spam.path)

spam.docs <- spam.docs [który (spam.docs! = “cmds”)]

all.spam <- sapply (spam.docs, function (p) get.msg (paste (spam.path, p, sep = “”)))

W przypadku spamu zaczynamy od uzyskania listy wszystkich nazw plików w katalogu data / spam przy użyciu funkcji dir. Ten katalog – i wszystkie katalogi zawierające dane e-mail – zawierają również plik cmds, który jest po prostu długą listą podstawowych komend uniksowych do przenoszenia plików w tych katalogach. Nie chcemy tego uwzględniać w twoich danych szkoleniowych, dlatego ignorujemy to, przechowując tylko te pliki, które nie mają cmd jako nazwy pliku. Teraz spam.docs jest wektorem znaków zawierającym wszystkie nazwy plików wiadomości spamowych, których użyjemy do szkolenia naszego klasyfikatora. Aby utworzyć nasz wektor wiadomości spamowych, używamy funkcji sapply, która zastosuje get.msg do wszystkich nazw plików spamu i zbuduje wektor wiadomości ze zwróconego tekstu. Zauważ, że musimy przekazać anonimową funkcję do sapply, aby połączyć nazwę pliku z odpowiednią ścieżką katalogu za pomocą funkcji wklejania. Jest to bardzo powszechna konstrukcja w R. Po wykonaniu tej serii poleceń możesz użyć head (all.spam) do sprawdzenia wyników. Zauważysz, że nazwa każdego wektora lementu odpowiada nazwie pliku. Jest to jedna z zalet używania sapply. Następnym krokiem jest utworzenie korpusu tekstowego z naszego wektora wiadomości e-mail za pomocą funkcji dostarczonych przez pakiet tm. Gdy już przedstawimy tekst jako korpus, możemy manipulować terminami w wiadomościach, aby rozpocząć tworzenie naszego zestawu funkcji dla klasyfikatora spamu. Ogromną zaletą pakietu TM jest to, że znaczna część ciężkiego podnoszenia potrzebnego do czyszczenia i normalizacji tekstu jest niewidoczna. To, co osiągniemy w kilku liniach kodu R, zajęłoby wiele linii przetwarzania łańcucha, gdybyśmy musieli sami wykonać te operacje w języku niższego poziomu. Jednym ze sposobów kwantyfikacji częstotliwości terminów w e-mailu spamowym jest zbudowanie matrycy terminów dokumentów (TDM). Jak sama nazwa wskazuje, TDM to macierz N × M, w której terminy znalezione wśród wszystkich dokumentów w danym korpusie definiują wiersze, a wszystkie dokumenty w korpusie definiują kolumny. Komórka [i, j] tej matrycy odpowiada liczbie przypadków znalezienia terminu i w dokumencie j.Tak jak poprzednio, zdefiniujemy prostą funkcję get.tdm, która pobierze wektor wiadomości e-mail i zwróci TDM:

get.tdm <- function(doc.vec) {

doc.corpus <- Corpus(VectorSource(doc.vec))

control <- list(stopwords=TRUE, removePunctuation=TRUE, removeNumbers=TRUE,

minDocFreq=2)

doc.dtm <- TermDocumentMatrix(doc.corpus, control)

return(doc.dtm)

}

spam.tdm <- get.tdm(all.spam)

Pakiet tm umożliwia konstruowanie korpusu na kilka sposobów. W naszym przypadku zbudujemy korpus z wektora wiadomości e-mail, więc użyjemy funkcji VectorSource. Aby zobaczyć różne inne typy źródeł, których można użyć, wpisz? GetSources na konsoli R. Jak to często ma miejsce podczas pracy z tm, po załadowaniu tekstu źródłowego użyjemy funkcji Corpus w połączeniu z VectorSource do utworzenia obiektu korpusu. Zanim przejdziemy do tworzenia TDM, musimy jednak powiedzieć tm, w jaki sposób chcemy wyczyścić i znormalizować tekst. Aby to zrobić, używamy kontrolki, która jest specjalną listą opcji określających sposób destylacji tekstu. W tym ćwiczeniu wykorzystamy cztery opcje. Najpierw ustawiamy stopwords = TRUE, co każe tm usunąć 488 typowych angielskich słów stop ze wszystkich dokumentów. Aby zobaczyć listę, wpisz stopwords() na konsoli R. Następnie ustawiamy usuń interpunkcję i usuwamy Liczby na TRUE, które są dość oczywiste i służą do zmniejszenia szumu związanego z tymi znakami – zwłaszcza, że ​​wiele naszych dokumentów zawiera znaczniki HTML. Na koniec ustawiamy minDocFreq = 2, co zapewni, że tylko terminy występujące więcej niż jeden raz w korpusie znajdą się w wierszach TDM. Przetwarzamy teraz wiadomości e-mail ze spamem do momentu, w którym możemy zacząć budować nasz klasyfikator. W szczególności możemy użyć TDM do zbudowania zestawu danych szkoleniowych dotyczących spamu. W kontekście R dobrym podejściem do tego jest skonstruowanie ramki danych zawierającej wszystkie zaobserwowane prawdopodobieństwa dla każdego terminu, biorąc pod uwagę, że wiemy, że jest to spam. Podobnie jak w przypadku naszego ważnego przykładu z informatyki, musimy przeszkolić naszego klasyfikatora, aby wiedział, że e-mail jest spamem, zważywszy na pewien termin.

spam.matrix <- as.matrix(spam.tdm)

spam.counts <- rowSums(spam.matrix)

spam.df <- data.frame(cbind(names(spam.counts),

as.numeric(spam.counts)), stringsAsFactors=FALSE)

names(spam.df) <- c(“term”,”frequency”)

spam.df$frequency <- as.numeric(spam.df$frequency)

spam.occurrence <- sapply(1:nrow(spam.matrix),

function(i) {length(which(spam.matrix[i,] > 0))/ncol(spam.matrix)})

spam.density <- spam.df$frequency/sum(spam.df$frequency)

spam.df <- transform(spam.df, density=spam.density,

occurrence=spam.occurrence).

Aby utworzyć tę ramkę danych, musimy najpierw przekonwertować obiekt TDM na standardową macierz R za pomocą polecenia as.matrix. Następnie za pomocą polecenia rowSums możemy utworzyć wektor, który zawiera całkowitą liczbę częstotliwości dla każdego terminu we wszystkich dokumentach. Ponieważ użyjemy funkcji data.frame do połączenia wektora znakowego z wektorem numerycznym, domyślnie R przekonwertuje te wektory na wspólną reprezentację. Liczniki częstotliwości mogą być reprezentowane jako znaki, więc zostaną przekonwertowane, co oznacza, że ​​musimy uważać, aby ustawić stringsAsFactors=FALSE. Następnie wykonamy czynności porządkowe, aby ustawić nazwy kolumn i przekonwertować liczby częstotliwości z powrotem na wektor numeryczny. W następnych dwóch krokach wygenerujemy najważniejsze dane treningowe. Najpierw obliczamy procent dokumentów, w których występuje dany termin. Robimy to, przepuszczając każdy wiersz przez anonimowe wywołanie funkcji przez sapply, która zlicza liczbę komórek z elementem dodatnim, a następnie dzieli sumę przez liczbę kolumn w TDM – tj. Przez liczbę dokumentów w korpusie spamu . Po drugie, obliczamy częstotliwość każdego słowa w całym korpusie. (Nie będziemy wykorzystywać informacji o częstotliwości do klasyfikacji, ale przydatne będzie sprawdzenie, jak te liczby się porównują, gdy weźmiemy pod uwagę, w jaki sposób niektóre słowa mogą wpływać na nasze wyniki.) W ostatnim kroku dodajemy spam.occurrence i spam.density wektory do ramki danych za pomocą funkcji transformacji. Wygenerowaliśmy teraz dane szkoleniowe do klasyfikacji spamu! Sprawdźmy dane i zobaczmy, które warunki są najsilniejszymi wskaźnikami spamu, biorąc pod uwagę nasze dane szkoleniowe. W tym celu sortujemy spam.df według kolumny wystąpienia i sprawdzamy jego nagłówek:

head(spam.df[with(spam.df, order(-occurrence)),])

term frequency density occurrence

2122 html 377 0.005665595 0.338

538 body 324 0.004869105 0.298

4313 table 1182 0.017763217 0.284

1435 email 661 0.009933576 0.262

1736 font 867 0.013029365 0.262

1942 head 254 0.003817138 0.246

Jak wielokrotnie wspominaliśmy, tagi HTML wydają się być najsilniejszymi funkcjami tekstowymi związanymi ze spamem. Ponad 30% wiadomości w danych szkoleniowych dotyczących spamu zawiera termin html, a także inne popularne terminy związane z HTML, takie jak treść, tabela, czcionka i głowa. Należy jednak pamiętać, że te terminy nie są najczęstsze pod względem liczby surowej. Możesz to zobaczyć sam, zastępując -occurrence -frequency w poprzednim zdaniu. Jest to bardzo ważne z punktu widzenia tego, jak definiujemy nasz klasyfikator. Jeśli użyjemy surowych danych liczbowych i późniejszych gęstości jako danych szkoleniowych, możemy przeważyć niektóre rodzaje spamu – w szczególności spam zawierający tabele HTML. Wiemy jednak, że nie wszystkie wiadomości spamowe są konstruowane w ten sposób. Jako takie, lepszym podejściem jest zdefiniowanie warunkowego prawdopodobieństwa spamu na podstawie liczby wiadomości zawierających ten termin. Teraz, gdy mamy dane szkoleniowe dotyczące spamu, musimy zrównoważyć je z danymi szkoleniowymi dotyczącymi ham. W ramach ćwiczenia zbudujemy te dane treningowe, używając tylko wiadomości z ham. Oczywiście byłoby możliwe włączenie komunikatów o hard ham szynce do zestawu treningowego; w rzeczywistości byłoby to wskazane, gdybyśmy budowali system produkcyjny. Jednak w kontekście tego ćwiczenia pomocne jest sprawdzenie, jak dobrze będzie działał klasyfikator tekstu, jeśli zostanie wyszkolony przy użyciu tylko niewielkiego zbioru łatwych do sklasyfikowania dokumentów. Będziemy konstruować dane szkoleniowe szynki dokładnie w ten sam sposób, w jaki zrobiliśmy spam, dlatego nie będziemy tutaj ponownie drukować tych poleceń. Jedyny sposób, w jaki ten krok różni się od generowania danych szkoleniowych dotyczących spamu, jest taki, że używamy tylko pierwszych 500 wiadomości e-mail w folderze data / easy_ham. Możesz zauważyć, że w tym katalogu znajduje się 2500 wiadomości e-mail z ham. Więc dlaczego my ignorujemy cztery piąte danych? Kiedy konstruujemy nasz pierwszy klasyfikator, zakładamy, że każda wiadomość ma równe prawdopodobieństwo, że jest to ham lub spamem. W związku z tym dobrą praktyką jest upewnienie się, że nasze dane szkoleniowe odzwierciedlają nasze założenia. Mamy tylko 500 wiadomości spamowych, więc ograniczymy lub ograniczymy szkolenie do 500 wiadomości. Po skonstruowaniu danych treningowych szynki możemy je sprawdzić tak samo, jak robiliśmy spam w celu porównania:

3553 yahoo 185 0.008712853 0.180

966 dont 141 0.006640607 0.090

2343 people 183 0.008618660 0.086

1871 linux 159 0.007488344 0.084

1876 list 103 0.004850940 0.078

3240 time 91 0.004285782 0.064

Pierwszą rzeczą, którą zauważysz w danych szkoleniowych ham, jest to, że warunki są znacznie rzadziej rozmieszczone w wiadomościach e-mail. Termin występujący w większości dokumentów „yahoo” występuje tylko w 18% z nich. Wszystkie pozostałe warunki występują w mniej niż 10% dokumentów. Porównaj to z najlepszymi warunkami spamu, które występują w ponad 24% wiadomości spamowych. Już teraz możemy zacząć widzieć, jak ta odmiana pozwoli nam oddzielić spam od szynki. Jeśli wiadomość zawiera tylko jeden lub dwa terminy silnie związane ze spamem, zajmie dużo niepisanych słów, aby wiadomość została zaklasyfikowana jako ham. Po zdefiniowaniu obu zestawów treningowych jesteśmy teraz gotowi do ukończenia naszego klasyfikatora i przetestowania go!

Delikatne przejście do prawdopodobieństwa warunkowego

U podstaw klasyfikacji tekstowej leży zastosowanie XVIII-wiecznej koncepcji prawdopodobieństwa warunkowego z XVIII wieku. Warunkowe prawdopodobieństwo to prawdopodobieństwo zaobserwowania jednej rzeczy, biorąc pod uwagę inną rzecz, o której już wiemy. Na przykład możemy chcieć poznać prawdopodobieństwo, że studentka jest kobietą, ponieważ wiemy, że kierunkiem studenta jest informatyka. Możemy to sprawdzić w wynikach ankiety. Według ankiety National Science Foundation z 2005 r. Tylko 22% studentów kierunków informatycznych stanowiły kobiety [SR08]. Ale 51% wszystkich kierunków licencjackich to kobiety, więc uwarunkowanie bycia kierunkiem informatycznym zmniejsza szanse na bycie kobietą z 51% do 22%. Algorytm klasyfikacji tekstu, którego będziemy używać w tym rozdziale, zwany klasyfikatorem Naive Bayes, szuka tego rodzaju różnic, przeszukując tekst w poszukiwaniu słów, które albo (a) zauważalnie częściej występują w wiadomościach spamowych, albo (b ) zauważalnie częściej występuje w wiadomościach z szynką. Kiedy słowo jest zauważalnie lub prawdopodobnie występuje w jednym kontekście, a nie w drugim, jego wystąpienie może być diagnostyczne, czy nowa wiadomość jest spamem lub szynką. Logika jest prosta: jeśli zobaczysz jedno słowo, które częściej występuje w spamie niż szynka, oznacza to, że e-mail jako całość jest spamem. Jeśli zobaczysz wiele słów, które częściej występują w spamie niż szynka, i bardzo mało słów, które częściej występują w szynce niż spamie, powinno to stanowić mocny dowód, że e-mail jako całość jest spamem. Ostatecznie nasz klasyfikator tekstu formalizuje tę intuicję, obliczając (a) prawdopodobieństwo, że dokładna treść wiadomości e-mail uwarunkowana wiadomością e-mail zostanie uznana za spam, oraz (b) prawdopodobieństwo, że treść wiadomości e-mail uwarunkowana jest wiadomością e-mail zakładano, że jest szynką. Jeśli znacznie bardziej prawdopodobne jest, że zobaczylibyśmy ten e-mail, gdyby był spamem, zadeklarujemy, że jest spamem.

To, o ile bardziej prawdopodobne jest, że wiadomość zasługuje na miano spamu, zależy od dodatkowej informacji: podstawowej częstotliwości wyświetlania wiadomości spamowych. Ta informacja o stawce bazowej jest zwykle nazywana wcześniejszą. Możesz pomyśleć o przełożeniu w następujący sposób: jeśli większość ptaków, które widzisz w parku, to kaczki, a pewnego dnia usłyszysz kwakanie ptaka, można założyć, że jest to kaczka. Ale jeśli nigdy nie widziałeś kaczki w parku, bardziej ryzykowne jest założenie, że wszystko, co znosi, musi być kaczką. Podczas pracy z pocztą e-mail pojawia się pierwszeństwo, ponieważ większość wysłanych wiadomości e-mail to spam, co oznacza, że ​​nawet słabe dowody, że wiadomość e-mail jest spamem, mogą wystarczyć do uzasadnienia oznaczenia jej jako spamu. W poniższej sekcji szczegółowo opiszemy tę logikę, pisząc klasyfikator spamu. Aby obliczyć prawdopodobieństwo wysłania wiadomości e-mail, założymy, że liczbę wystąpień każdego słowa można oszacować w oderwaniu od wszystkich pozostałych słów. Formalnie sprowadza się to do założenia często określanego jako niezależność statystyczna. Kiedy przyjmujemy to założenie bez pewności, że jest poprawne, mówimy, że nasz model jest naiwny. Ponieważ wykorzystamy również informacje o stawce podstawowej o wiadomościach e-mail będących spamem, model ten będzie również nazywany modelem Bayesa w hołdzie matematykowi z XVIII wieku, który jako pierwszy opisał prawdopodobieństwa warunkowe. Podsumowując, te dwie cechy sprawiają, że nasz model jest klasyfikatorem Naive Bayes.